音视频处理之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 ...
随机推荐
- visudo命令详解
基础命令学习目录首页 原文链接:https://www.cnblogs.com/ImJerryChan/p/6667819.html 目录前言一.介绍二.配置文件简介三.实战配置 前言: su ...
- Scrum立会报告+燃尽图(十月十一日总第二次):需求分析
此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2191 Scrum立会master:张俊余 一.小组介绍 组长:付佳 组员 ...
- Daily Scrum7 11.11
今日任务: 徐钧鸿:结束了SQL和Affairs的移植,修改了连接池,学习C#和java的正则表达式并且完成相关的移植 张艺:个人阅读作业 黄可嵩:完成高亮显示的移植,进一步移植搜索代码 徐方宇:继续 ...
- Daily Srum 10.28
这两天我们和其他两组进行了一次会议,主要讨论的是用什么框架来搭建这个平台.在线系统的那一组希望我们用nutch.solr.hbase这一套工具,这对于我们两组来说是一次挑战,毕竟我们一开始用的是关系型 ...
- 结对项目:SudokuGame
1. Github项目地址:https://github.com/ZiJiaW/SudokuGame GUI在BIN目录下的SudokuGUI.rar中,解压后打开SudokuGame.exe即可.2 ...
- scanf() scanf_s() 区别
写博原因:这几天由于小学期的缘故,接触到了好多C代码,在VS2013中编译的时候,遇到了如下问题: 错误 1 error C4996: 'scanf': This function or variab ...
- 团队编程--MP3播放器
设计思路: 这次的作业是一个MP3播放器,它是一个团队项目.由于我们都没接触过这类的编程.刚开始的时候我们是不知道从什么地方着手的.经过我们的商量我们决定从现在市场主流的音乐播放器上找到几个主要的功能 ...
- POJ 2151 Check the difficulty of problems 概率dp+01背包
题目链接: http://poj.org/problem?id=2151 Check the difficulty of problems Time Limit: 2000MSMemory Limit ...
- HDU 4126 Genghis Khan the Conqueror 最小生成树+树形dp
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4126 Genghis Khan the Conqueror Time Limit: 10000/50 ...
- 05_Java基础语法_第5天(方法)_讲义
今日内容介绍 1.方法基础知识 2.方法高级内容 3.方法案例 01方法的概述 * A: 为什么要有方法 * 提高代码的复用性 * B: 什么是方法 * 完成特定功能的代码块. 02方法的定义格式 * ...