音视频处理之FFmpeg+SDL视频播放器20180409
一、FFmpeg视频解码器
1.视频解码知识
1).纯净的视频解码流程
压缩编码数据->像素数据。
例如解码H.264,就是“H.264码流->YUV”。
2).一般的视频解码流程
视频码流一般存储在一定的封装格式(例如MP4、AVI等)中。封装格式中通常还包含音频码流等内容。
对于封装格式中的视频,需要先从封装格式中提取中视频码流,然后再进行解码。
例如解码MKV格式的视频文件,就是“MKV->H.264码流->YUV”
2.VC下FFmpeg开发环境的搭建
新建控制台工程
打开visual studio
文件 新建 项目 win32控制台应用程序
拷贝FFmpeg开发文件
头文件(*.h)拷贝至项目文件夹的include子文件夹下
导入库文件(*.lib)拷贝至项目文件夹的lib子文件夹下
动态库文件(*.dll)拷贝至项目文件夹下
PS:如果直接使用官网上下载的FFmpeg开发文件。则可能还需要将MinGW安装目录中的inttypes.h,stdint.h,_mingw.h三个文件拷贝至项目文件夹的include子文件夹下。
配置开发文件
打开属性面板
解决方案资源管理器->右键单击项目->属性
头文件配置
配置属性->C/C++->常规->附加包含目录,输入“include”(刚才拷贝头文件的目录)
导入库配置
配置属性->链接器->常规->附加库目录,输入“lib” (刚才拷贝库文件的目录)
配置属性->链接器->输入->附加依赖项,输入“avcodec.lib; avformat.lib; avutil.lib; avdevice.lib; avfilter.lib; postproc.lib; swresample.lib; swscale.lib”(导入库的文件名)
动态库不用配置
main()中调用一个FFmpeg的接口函数
例如下面代码打印出了FFmpeg的配置信息
int main(int argc, char* argv[]){
printf("%s", avcodec_configuration());
return 0;
}
如果运行无误,则代表FFmpeg已经配置完成。
3.FFmpeg简介
FFmpeg一共包含8个库:
avcodec:编解码(最重要的库)。
avformat:封装格式处理。
avfilter:滤镜特效处理。
avdevice:各种设备的输入输出。
avutil:工具库(大部分库都需要这个库的支持)。
postproc:后加工。
swresample:音频采样数据格式转换。
swscale:视频像素数据格式转换。
1).FFmpeg解码流程图见图1:
2).FFmpeg解码函数简介
av_register_all():注册所有组件。
avformat_open_input():打开输入视频文件。
avformat_find_stream_info():获取视频文件信息。
avcodec_find_decoder():查找解码器。
avcodec_open2():打开解码器。
av_read_frame():从输入文件读取一帧压缩数据。
avcodec_decode_video2():解码一帧压缩数据。
avcodec_close():关闭解码器。
avformat_close_input():关闭输入视频文件。
3).FFmpeg相关结构体(类型)见图2:
I、FFmpeg数据结构简介
AVFormatContext
封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。
AVInputFormat
每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体。
AVStream
视频文件中每个视频(音频)流对应一个该结构体。
AVCodecContext
编码器上下文结构体,保存了视频(音频)编解码相关信息。
AVCodec
每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。
AVPacket
存储一帧压缩编码数据。
AVFrame
存储一帧解码后像素(采样)数据。
II、FFmpeg数据结构分析
AVFormatContext
iformat:输入视频的AVInputFormat
nb_streams :输入视频的AVStream 个数
streams :输入视频的AVStream []数组
duration :输入视频的时长(以微秒为单位)
bit_rate :输入视频的码率
AVInputFormat
name:封装格式名称
long_name:封装格式的长名称
extensions:封装格式的扩展名
id:封装格式ID
一些封装格式处理的接口函数
AVStream
id:序号
codec:该流对应的AVCodecContext
time_base:该流的时基
r_frame_rate:该流的帧率
AVCodecContext
codec:编解码器的AVCodec
width, height:图像的宽高(只针对视频)
pix_fmt:像素格式(只针对视频)
sample_rate:采样率(只针对音频)
channels:声道数(只针对音频)
sample_fmt:采样格式(只针对音频)
AVCodec
name:编解码器名称
long_name:编解码器长名称
type:编解码器类型
id:编解码器ID
一些编解码的接口函数
AVPacket
pts:显示时间戳
dts :解码时间戳
data :压缩编码数据
size :压缩编码数据大小
stream_index :所属的AVStream
AVFrame
data:解码后的图像像素数据(音频采样数据)。
linesize:对视频来说是图像中一行像素的大小;对音频来说是整个音频帧的大小。
width, height:图像的宽高(只针对视频)。
key_frame:是否为关键帧(只针对视频) 。
pict_type:帧类型(只针对视频) 。例如I,P,B。
III、注意事项
裸流文件,即如h264文件,是没有时长等信息的,封装格式的才有
数据结构第一层为封装格式相关的,下面层为编解码相关的
其中
AVStream中的time_base 表示该流的时基,也就是时间的基数,简单可理解为时间的单位,比如时间数是2,时基是1s,则时间为2s
AVPacket:可以理解为h264中的数据包,编码后的包
其中,pts:显示时间戳,表示该视频帧几分几秒的时候放到屏幕上。这个值只是一个整数 没有单位,所以要得到具体的几分几秒需要和前面的time_base时基参数相乘得到。
dts:解码时间戳
stream_index:标识,表示该流属于音频流还是视频流。
data:压缩编码的数据,如h264的编码,就可以取出该数据保存起来形成的文件就是h264文件。
通过av_read_frame可从大的数据结构指针中读取到AVPacket数据
注意播放的顺序不一定按照存储的顺序来。即有一个显示的顺序和一个解码的顺序。
AVFrame
data:解码后的图像像素数据,如YUV等,可形成yuv文件(依次存储的是y、u、v分量,其中u 宽和高都只有y一半,整个数据量就y的四分之一,v的也一样)。data是一个指针数组,其中一个分量对应一个数组
数据先通过解码函数avcodec_decode_video2进行解码,然后由于解码出来的数据由于是有黑边(多出一块多余的数据),所以还需用sws_scale函数把这块多余的裁了。
4).解码后的数据要经过sws_scale()函数处理
解码后YUV格式的视频像素数据保存在AVFrame的data[0]、data[1]、data[2]中。但是这些像素值并不是连续存储的,每行有效像素之后存储了一些无效像素。
以亮度Y数据为例,data[0]中一共包含了linesize[0]*height个数据。但是出于优化等方面的考虑,linesize[0]实际上并不等于宽度width,而是一个比宽度大一些的值。
因此需要使用sws_scale()进行转换。转换后去除了无效数据,width和linesize[0] 取值相等。如下图3:
其中,sws_scale()函数需要用到的转换信息,即第一个参数,是由sws_getContext函数获得的
转换后保存在一个新的帧数据结构体,由于使用的转换函数而不是解码函数,所以这个结构体还需要填充其内部的缓冲区,用于存储像素数据,填充的方法使用avpicture_fill函数:
int avpicture_fill(AVPicture *picture, uint8_t *ptr,int pix_fmt, int width, int height);
这个函数的使用本质上是为已经分配的空间的结构体(AVPicture *)ptFrame挂上一段用于保存数据的空间,
这个结构体中有一个指针数组data[AV_NUM_DATA_POINTERS],挂在这个数组里。一般我们这么使用:
I、pFrameRGB=avcodec_alloc_frame();
II、numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);
buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
III、avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height);
以上就是为pFrameRGB挂上buffer。这个buffer是用于存缓冲数据的。
ptFrame为什么不用fill空间。主要是下面这句:
avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size);
很可能是ptFrame已经挂上了packet.data,所以就不用fill了。
二、SDL视频显示
SDL(Simple DirectMedia Layer)库的作用说白了就是封装了复杂的视音频底层交互工作,简化了视音频处理的难度。
主要用来做游戏,现在只用到其视频显示部分。
特点:跨平台,开源
1.库的结构图见图4:
实际上它调用了底层api完成了和硬件的交互,比如linux下,就操作了framebuffer.
2.配置vc工程和之前ffmpeg的配置几乎都是一样的:
1).新建控制台工程
打开VC++
文件->新建->项目->Win32控制台应用程序
2).拷贝SDL开发文件
头文件(*.h)拷贝至项目文件夹的include子文件夹下
导入库文件(*.lib)拷贝至项目文件夹的lib子文件夹下
动态库文件(*.dll)拷贝至项目文件夹下
3).配置开发文件
打开属性面板
解决方案资源管理器->右键单击项目->属性
4).头文件配置
配置属性->C/C++->常规->附加包含目录,输入“include”(刚才拷贝文件的目录)
5).导入库配置
配置属性->链接器->常规->附加库目录,输入“lib” (刚才拷贝文件的目录)
配置属性->链接器->输入->附加依赖项,输入“SDL2.lib; SDL2main.lib”(导入库的文件名)
6).动态库不用配置
7).是否配置成功,调用sdl的初始化函数,查看其返回值即可确定:
创建源代码文件
在工程中创建一个包含main()函数的C/C++文件(如果已经有了可以跳过这一步),后续步骤在该文件中编写源代码。
包含头文件
如果是C语言中使用SDL,则直接使用下面代码
#include "SDL2/SDL.h"
如果是C++语言中使用SDL,则使用下面代码
extern "C"
{
#include "SDL2/SDL.h"
}
main()中调用一个SDL的接口函数
例如下面代码初始化了SDL
int main(int argc, char* argv[]){
if(SDL_Init(SDL_INIT_VIDEO)) {
printf( "Could not initialize SDL - %s\n", SDL_GetError());
} else{
printf("Success init SDL");
}
return 0;
}
如果运行无误,则代表SDL已经配置完成。
3.SDL视频显示的流程图见图5:
其中特别要注意的是创建纹理数据时要传入渲染器,原因是,
纹理数据依赖于渲染器,只有通过渲染器创建才能得到该渲染器合法的纹理数据((数据格式)符合该渲染器的要求),
填充了像素数据且符合要求的纹理数据才可以拷贝给渲染器,并且渲染器才能正确显示出来。
1).SDL视频显示函数简介
SDL_Init():初始化SDL系统
SDL_CreateWindow():创建窗口SDL_Window
SDL_CreateRenderer():创建渲染器SDL_Renderer
SDL_CreateTexture():创建纹理SDL_Texture
SDL_UpdateTexture():设置纹理的数据
SDL_RenderCopy():将纹理的数据拷贝给渲染器
SDL_RenderPresent():显示
SDL_Delay():工具函数,用于延时。
SDL_Quit():退出SDL系统
其中SDL_Delay 延时函数,控制显示的速度,即控制帧率。通常每秒25帧,所以通常延时也就是40ms
2).SDL视频显示的数据结构
I、SDL视频显示的数据结构如图6所示:
II、SDL数据结构简介
SDL_Window
代表了一个“窗口”
SDL_Renderer
代表了一个“渲染器”
SDL_Texture
代表了一个“纹理”
SDL_Rect
一个简单的矩形结构
具体来说,
SDL_Window 窗口,类似弹出的窗口
SDL_Renderer 渲染器,把纹理数据画(渲染)到window上
一个window上不仅仅只有一副画面,类似多组监控画面,即一个window可对应多个yuv数据
SDL_Rect,正方形结构,存了矩形的坐标,长宽,以便确定纹理数据画在哪个位置,确定位置用,比如画在左上角就用这个来确定。被渲染器调用
SDL_Rect中的x y值是左上角为圆点开始的坐标值,调整x y值以及w h值,就可以实现在窗口的指定位置显示,没有画面的地方为黑框。
当x y等于0,w h等于窗口的宽高时即为全屏显示,此时调整宽高大小,只需调整窗口大小即可。
4.进阶-SDL中事件和多线程
1).SDL多线程
函数
SDL_CreateThread():创建一个线程
数据结构
SDL_Thread:线程的句柄
SDL事件
函数
SDL_WaitEvent()等待一个事件
SDL_PushEvent()发送一个事件
数据结构
SDL_Event:代表一个事件
SDL中事件和多线程,可用于解决上个程序播放过程中鼠标不能动的问题,
使用事件,等待事件中,就会响应鼠标键盘等事件,就不会卡在那了
将延时操作放到一个子线程中,用事件通知主线程,这样主线程就不用延时了,就可以及时响应事件了
SDL_WINDOWENVENT sdl系统自带的事件,当拉伸窗口的时候会触发
SDL_QUIT 也是SDL自带的事件,当点击窗口的×时触发
三、FFmpeg+SDL视频播放器
1.FFmpeg+SDL整合之后实现:视频文件->YUV->屏幕
2.SDL_UpdateTexture函数最后一个参数表示的是,一行像素数据的数据量
窗口的宽和高无所谓,但是纹理数据的宽和高要和视频数据的宽和高一致。
3.对于大数据的调试,如果直接打印出来,数据太多不好分析,一般是写入到文件,然后用工具打开文件去分析。
比如视频像素数据,可以写入到yuv文件,再用yuv分析工具来分析。
4.脱离开发环境的独立播放器
在解决方案中的debug目录下的*.exe文件即为编译好的可执行文件。
执行这个文件就可以脱离开发环境运行。
5.调试时,在属性->调试->命令参数 中填的东西,就是程序的输入参数,也就是argv中的内容
平时独立的exe程序,只需在执行的时候后面加上参数就是输入参数了,如ffplay.exe那样。
四、FFmpeg+SDL视频播放器代码实现
主要是FFmpegAndSDL.cpp文件,代码如下(基本上每一行都有注释):
/*****************************************************************************
* Copyright (C) 2017-2020 Hanson Yu All rights reserved.
------------------------------------------------------------------------------
* File Module : FFmpegAndSDL.cpp
* Description : FFmpegAndSDL Demo * Created : 2017.09.21.
* Author : Yu Weifeng
* Function List :
* Last Modified :
* History :
* Modify Date Version Author Modification
* -----------------------------------------------
* 2017/09/21 V1.0.0 Yu Weifeng Created
******************************************************************************/ #include "stdafx.h"
#include <stdio.h> /*解决错误:
LNK2019 无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用 原因:
……这是链接库问题
就是工程里面没有添加那两个函数需要的库,#progma这个是代码链接库
第二句是vs2015兼容的问题。
lib库的vs编译版本 和 工程的vs开发版本 不一致。
导出函数定义变了。所以要人为加一个函数导出。
*/
#pragma comment(lib, "legacy_stdio_definitions.lib")
extern "C" { FILE __iob_func[] = { *stdin,*stdout,*stderr }; } /*
__STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS are a workaround to allow C++ programs to use stdint.h
macros specified in the C99 standard that aren't in the C++ standard. The macros, such as UINT8_MAX, INT64_MIN,
and INT32_C() may be defined already in C++ applications in other ways. To allow the user to decide
if they want the macros defined as C99 does, many implementations require that __STDC_LIMIT_MACROS
and __STDC_CONSTANT_MACROS be defined before stdint.h is included. This isn't part of the C++ standard, but it has been adopted by more than one implementation.
*/
#define __STDC_CONSTANT_MACROS extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "SDL2/SDL.h"
}; //Refresh Event 自定义事件
#define PLAY_REFRESH_EVENT (SDL_USEREVENT + 1)//自定义刷新图像(播放)事件
#define PLAY_BREAK_EVENT (SDL_USEREVENT + 2) //自定义退出播放事件 static int g_iThreadExitFlag = ;
/*****************************************************************************
-Fuction : RefreshPlayThread
-Description : RefreshPlayThread
-Input :
-Output :
-Return :
* Modify Date Version Author Modification
* -----------------------------------------------
* 2017/09/21 V1.0.0 Yu Weifeng Created
******************************************************************************/
int RefreshPlayThread(void *opaque)
{
g_iThreadExitFlag = ;
SDL_Event tEvent={}; while (!g_iThreadExitFlag)
{
tEvent.type = PLAY_REFRESH_EVENT;
SDL_PushEvent(&tEvent);//发送事件给其他线程
SDL_Delay();//延时函数 填40的时候,视频会有种卡的感觉
}
//Break
g_iThreadExitFlag = ;
tEvent.type = PLAY_BREAK_EVENT;
SDL_PushEvent(&tEvent);//发送事件给其他线程 发送一个事件 return ;
} /*****************************************************************************
-Fuction : main
-Description : main
-Input :
-Output :
-Return :
* Modify Date Version Author Modification
* -----------------------------------------------
* 2017/09/21 V1.0.0 Yu Weifeng Created
******************************************************************************/
int main(int argc, char* argv[])
{
/*------------FFmpeg----------------*/
const char *strFilePath = "屌丝男士.mov";
AVFormatContext *ptFormatContext = NULL;//封装格式上下文,内部包含所有的视频信息
int i = ;
int iVideoindex=;//纯视频信息在音视频流中的位置,也就是指向音视频流数组中的视频元素
AVCodecContext *ptCodecContext;//编码器相关信息上下文,内部包含编码器相关的信息,指向AVFormatContext中的streams成员中的codec成员
AVCodec *ptCodec;//编码器,使用函数avcodec_find_decoder或者,该函数需要的id参数,来自于ptCodecContext中的codec_id成员
AVFrame *ptFrame=NULL;//存储一帧解码后像素(采样)数据
AVFrame *ptFrameAfterScale=NULL;//存储(解码数据)转换后的像素(采样)数据
unsigned char *pucFrameAfterScaleBuf=NULL;//用于存储ptFrameAfterScale中的像素(采样)缓冲数据
AVPacket *ptPacket=NULL;//存储一帧压缩编码数据
int iRet =;
int iGotPicture=;//解码函数的返回参数,got_picture_ptr Zero if no frame could be decompressed, otherwise, it is nonzero /*------------SDL----------------*/
int iScreenWidth=, iScreenHeight=;//视频的宽和高,指向ptCodecContext中的宽和高
SDL_Window *ptSdlWindow=NULL;//用于sdl显示视频的窗口(用于显示的屏幕)
SDL_Renderer* ptSdlRenderer=NULL;//sdl渲染器,把纹理数据画(渲染)到window上
SDL_Texture* ptSdlTexture=NULL;//sdl纹理数据,用于存放像素(采样)数据,然后给渲染器
SDL_Rect tSdlRect ={};//正方形矩形结构,存了矩形的坐标,长宽,以便确定纹理数据画在哪个位置,确定位置用,比如画在左上角就用这个来确定。被渲染器调用
SDL_Thread *ptVideoControlTID=NULL;//sdl线程id,线程的句柄
SDL_Event tSdlEvent = {};//sdl事件,代表一个事件 /*------------像素数据处理----------------*/
struct SwsContext *ptImgConvertInfo;//图像转换(上下文)信息,图像转换函数sws_scale需要的参数,由sws_getContext函数赋值 /*------------FFmpeg----------------*/
av_register_all();//注册FFmpeg所有组件
avformat_network_init();//初始化网络组件 ptFormatContext = avformat_alloc_context();//分配空间给ptFormatContext
if (avformat_open_input(&ptFormatContext, strFilePath, NULL, NULL) != )
{//打开输入视频文件
printf("Couldn't open input stream.\n");
return -;
}
if (avformat_find_stream_info(ptFormatContext, NULL)<)
{//获取视频文件信息
printf("Couldn't find stream information.\n");
return -;
}
//获取编码器相关信息上下文,并赋值给ptCodecContext
iVideoindex = -;
for (i = ; i<ptFormatContext->nb_streams; i++)
{
if (ptFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
iVideoindex = i;
break;
}
}
if (iVideoindex == -)
{
printf("Didn't find a video stream.\n");
return -;
}
ptCodecContext = ptFormatContext->streams[iVideoindex]->codec; ptCodec = avcodec_find_decoder(ptCodecContext->codec_id);//查找解码器
if (ptCodec == NULL)
{
printf("Codec not found.\n");
return -;
}
if (avcodec_open2(ptCodecContext, ptCodec, NULL)<)
{//打开解码器
printf("Could not open codec.\n");
return -;
} ptPacket = (AVPacket *)av_malloc(sizeof(AVPacket));//分配保存解码前数据的空间
ptFrame = av_frame_alloc();//分配结构体空间,结构体内部的指针指向的数据暂未分配,用于保存图像转换前的像素数据 /*------------像素数据处理----------------*/
ptFrameAfterScale = av_frame_alloc();//分配结构体空间,结构体内部的指针指向的数据暂未分配,用于保存图像转换后的像素数据
pucFrameAfterScaleBuf = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height));//分配保存数据的空间
/*int avpicture_fill(AVPicture *picture, uint8_t *ptr,int pix_fmt, int width, int height);
这个函数的使用本质上是为已经分配的空间的结构体(AVPicture *)ptFrame挂上一段用于保存数据的空间,
这个结构体中有一个指针数组data[AV_NUM_DATA_POINTERS],挂在这个数组里。一般我们这么使用:
1) pFrameRGB=avcodec_alloc_frame();
2) numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);
buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
3) avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height);
以上就是为pFrameRGB挂上buffer。这个buffer是用于存缓冲数据的。
ptFrame为什么不用fill空间。主要是下面这句:
avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,packet.data, packet.size);
很可能是ptFrame已经挂上了packet.data,所以就不用fill了。*/
avpicture_fill((AVPicture *)ptFrameAfterScale, pucFrameAfterScaleBuf, PIX_FMT_YUV420P, ptCodecContext->width, ptCodecContext->height);
//sws开头的函数用于处理像素(采样)数据
ptImgConvertInfo = sws_getContext(ptCodecContext->width, ptCodecContext->height, ptCodecContext->pix_fmt,
ptCodecContext->width, ptCodecContext->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);//获取图像转换(上下文)信息 /*------------SDL----------------*/
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER))
{//初始化SDL系统
printf("Could not initialize SDL - %s\n", SDL_GetError());
return -;
}
//SDL 2.0 Support for multiple windows
iScreenWidth = ptCodecContext->width;
iScreenHeight = ptCodecContext->height;
ptSdlWindow = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
iScreenWidth, iScreenHeight, SDL_WINDOW_OPENGL);//创建窗口SDL_Window if (!ptSdlWindow)
{
printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
return -;
}
ptSdlRenderer = SDL_CreateRenderer(ptSdlWindow, -, );//创建渲染器SDL_Renderer
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
//创建纹理SDL_Texture
ptSdlTexture = SDL_CreateTexture(ptSdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, ptCodecContext->width, ptCodecContext->height); tSdlRect.x = ;//x y值是左上角为圆点开始的坐标值,调整x y值以及w h值,就可以实现在窗口的指定位置显示,没有画面的地方为黑框
tSdlRect.y = ;//当x y等于0,w h等于窗口的宽高时即为全屏显示,此时调整宽高大小,只需调整窗口大小即可
tSdlRect.w = iScreenWidth;
tSdlRect.h = iScreenHeight; ptVideoControlTID = SDL_CreateThread(RefreshPlayThread, NULL, NULL);//创建一个线程 while ()
{//Event Loop
SDL_WaitEvent(&tSdlEvent);//Wait,等待其他线程过来的事件
if (tSdlEvent.type == PLAY_REFRESH_EVENT) //自定义刷新图像(播放)事件
{
/*------------FFmpeg----------------*/
if (av_read_frame(ptFormatContext, ptPacket) >= ) //从输入文件读取一帧压缩数据
{
if (ptPacket->stream_index == iVideoindex)
{
iRet = avcodec_decode_video2(ptCodecContext, ptFrame, &iGotPicture, ptPacket);//解码一帧压缩数据
if (iRet < )
{
printf("Decode Error.\n");
return -;
}
if (iGotPicture)
{
//图像转换,sws_scale()函数需要用到的转换信息,即第一个参数,是由sws_getContext函数获得的
sws_scale(ptImgConvertInfo, (const uint8_t* const*)ptFrame->data, ptFrame->linesize, , ptCodecContext->height, ptFrameAfterScale->data, ptFrameAfterScale->linesize); /*------------SDL----------------*/
SDL_UpdateTexture(ptSdlTexture, NULL, ptFrameAfterScale->data[], ptFrameAfterScale->linesize[]);//设置(更新)纹理的数据
SDL_RenderClear(ptSdlRenderer);//先清除渲染器里的数据
//SDL_RenderCopy( ptSdlRenderer, ptSdlTexture, &tSdlRect, &tSdlRect ); //将纹理的数据拷贝给渲染器
SDL_RenderCopy(ptSdlRenderer, ptSdlTexture, NULL, NULL);//将纹理的数据拷贝给渲染器
SDL_RenderPresent(ptSdlRenderer);//显示
}
}
av_free_packet(ptPacket);//释放空间
}
else
{
g_iThreadExitFlag = ;//Exit Thread
}
}
else if (tSdlEvent.type == SDL_QUIT) //也是SDL自带的事件,当点击窗口的×时触发//SDL_WINDOWENVENT sdl系统自带的事件,当拉伸窗口的时候会触发
{
g_iThreadExitFlag = ;
}
else if (tSdlEvent.type == PLAY_BREAK_EVENT) //自定义退出播放事件
{
break;
} } /*------------像素数据处理----------------*/
sws_freeContext(ptImgConvertInfo);//释放空间 /*------------SDL----------------*/
SDL_Quit();//退出SDL系统 /*------------FFmpeg----------------*/
av_frame_free(&ptFrameAfterScale);//释放空间
av_frame_free(&ptFrame);//释放空间
avcodec_close(ptCodecContext);//关闭解码器
avformat_close_input(&ptFormatContext);//关闭输入视频文件 return ;
}
FFmpegAndSDL.cpp
具体代码见github:
https://github.com/fengweiyu/FFmpegAndSDL
音视频处理之FFmpeg+SDL视频播放器20180409的更多相关文章
- 音视频处理之FFmpeg+SDL+MFC视频播放器20180411
一.FFmpeg+SDL+MFC视频播放器 1.MFC知识 1).创建MFC工程的方法 打开VC++ 文件->新建->项目->MFC应用程序 应用程序类型->基于对话框 取消勾 ...
- Vue + WebRTC 实现音视频直播(附自定义播放器样式)
1. 什么是WebRTC 1.1 WebRTC简介 WebRTC,名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频 ...
- 音视频】5.ffmpeg命令分类与使用
GT其实平时也有一些处理音视频的个人或者亲人需求,熟练使用ffmpeg之后也不要借助图示化软件,一个命令基本可以搞定 G: 熟练使用ffmpeg命令!T :不要死记硬背,看一遍,自己找下规律,敲一遍, ...
- ffmpeg+SDl+ 播放器 -01
最近因公司项目需要,打算自己在LINUX平台整一个播放器,来学习和研究音频编解码. 项目需求: 支持下列格式文件播放. 1> WMA 硬件解码,但需要软件分析ASF格式,提取Payload数据 ...
- Android 音视频深入 十一 FFmpeg和AudioTrack播放声音(附源码下载)
项目地址,求starhttps://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpeg%E6%92%AD%E6%94%BE%E ...
- 音视频处理之FFmpeg封装格式20180510
一.FFMPEG的封装格式转换器(无编解码) 1.封装格式转换 所谓的封装格式转换,就是在AVI,FLV,MKV,MP4这些格式之间转换(对应.avi,.flv,.mkv,.mp4文件). 需要注意的 ...
- android形状属性、锁屏密码、动态模糊、kotlin项目、抖音动画、记账app、视频播放器等源码
Android精选源码 直观了解Android的"形状"属性如何影响Drawable的外观. 一个灵活的视频播放器, 可替换播放器内核. android锁屏输入密码功能源码 背景动 ...
- Android 音视频深入 十 FFmpeg给视频加特效(附源码下载)
项目地址,求starhttps://github.com/979451341/Audio-and-video-learning-materials/tree/master/FFmpeg(AVfilte ...
- Android 音视频深入 九 FFmpeg解码视频生成yuv文件(附源码下载)
项目地址,求star https://github.com/979451341/Audio-and-video-learning-materials/tree/master/FFmpeg(MP4%E8 ...
随机推荐
- 转载:GBDT算法梳理
学习内容: 前向分布算法 负梯度拟合 损失函数 回归 二分类,多分类 正则化 优缺点 sklearn参数 应用场景 转自:https://zhuanlan.zhihu.com/p/58105824 G ...
- php学习--变量和数据类型
PHP变量 变量 程序执行期间,可以变化的量即为变量. 声明变量 以美元$ 符号声明 注意:(PHP严格区分大小写) 变量名称以 字母.或下划线开始,后面跟上数字/字母/下划线,不能包含特殊字符 ...
- python中__name__属性的使用
python常用模块目录 1.打印出函数名字而非函数名对应的地址 )打印的是函数地址 def func(): print("我是%s函数"%func) func() ------- ...
- cd命令详解
基础命令学习目录首页 cd 进入用户主目录: cd ~ 进入用户主目录: cd - 返回进入此目录之前所在的目录: cd .. 返回上级目录(若当前目录为“/“,则执行完后还在“/":&qu ...
- lscpu命令详解
基础命令学习目录首页 一.lscpu输出 使用lscpu查看的结果如下图,这里会显示很多信息,如下: 使用lscpu -p会详细的numa信息,如下: [root@localhost ~]# lscp ...
- decode 函数及其用法
http://blog.csdn.net/oscar999/article/details/18399177
- YQCB冲刺第二周第二天
今天的任务依然为实现查看消费明细的功能. 遇到的问题为从数据库中分类读取,实现图标的显示. 站立会议为: 任务面板为:
- java中static使用之静态方法注意点
1.静态方法可以直接调用同类中的静态成员,但是不能直接调用非静态成员,这是为什么呢?大家想一下,静态成员在对象创建之前就要写入内存,所以它在内存中是实实在在的存在的,而非静态还不存在内存中,所以不能调 ...
- Java第一天——环境变量的配置与破解myeclipse2013
一.jdk环境变量的配置 1.下载JDK并安装(官网JavaSE,64位(具体看电脑是多少位的))官网http://www.oracle.com/technetwork/java/javase/dow ...
- cobbler-web 界面技术详解
cobbler-web安装配置过程详解 (1)安装cobbler-web(测试时候,确保物理网络是在内网中进行,在外网会无法访问的哦,cobbler-web的访问入口必须有dhcpd指定的网络保持一致 ...