在前面的学习中,视频和音频的播放是分开进行的。这主要是为了学习的方便,经过一段时间的学习,对FFmpeg的也有了一定的了解,本文就介绍了
如何使用多线程同时播放音频和视频(未实现同步),并对前面的学习的代码进行了重构,便于后面的扩展。
本文主要有以下几个方面的内容:
- 多线程播放视音频的整体流程
- 多线程队列
- 音频播放
- 视频播放
- 总结以及后续的计划
1. 整体流程
FFmpeg和SDL的初始化过程这里不再赘述。整个流程如下:
- 对于一个打开的视频文件(也就是取得其
AVFormatContext
),创建一个分离线程,不断的从stream中读取Packet,并按照其stream index,将Packet分别存放到Audio Packet Queue和Video Packet这两个队列缓存中。 - 音频播放线程。创建一个回调函数,从Audio Packet Queue中取出Packet并解码,将解码的数据发送到SDL Audio Device中进行播放
- 视频播放线程。
- 创建Video解码线程,从Video Packet Queue中取出Packet进行解码,并将解码后的数据放入到 Video Frame Queue队列缓存中。
- 进入到SDL Window 事件循环中,按照一定的速度从 Video Frame Queue中取出Frame,并转换为相应的格式,然后在SDL Screen上显示
其整个流程中如下图:
1.1 重构后的main函数
在前面的学习过程中,主要是跟着dranger tutorial。由于该教程是基于C语言的,在其使用多线程播放音视频的教程中,代码使用不是很方便。在本文中,使用C++对其代码进行了重构封装。
封装后的main
函数如下:
av_register_all();
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);
char* filename = "F:\\test.rmvb";
MediaState media(filename);
if (media.openInput())
SDL_CreateThread(decode_thread, "", &media); // 创建解码线程,读取packet到队列中缓存
media.audio->audio_play(); // create audio thread
media.video->video_play(); // create video thread
SDL_Event event;
while (true) // SDL event loop
{
SDL_WaitEvent(&event);
switch (event.type)
{
case FF_QUIT_EVENT:
case SDL_QUIT:
quit = 1;
SDL_Quit();
return 0;
break;
case FF_REFRESH_EVENT:
video_refresh_timer(media.video);
break;
default:
break;
}
}
主函数的主要分为三个部分:
- 初始化FFmpeg和SDL
- 创建Audio播放线程和Video播放线程
- SDL事件循环,显示图像。
1.2 使用到的数据结构
将播放过程中需要使用到的主要数据封装为三个结构:
- MediaState 主要包含了
AudioState
和VideoState
指针,以及AVFormatContext
- AudioState 播放音频所需要的数据
- VideoState 播放视频所需要的数据
这里主要介绍下MediaState
,在后面播放音频和视频时再介绍与其相关的数据结构。
MediaState
的声明如下:
struct MediaState
{
AudioState *audio;
VideoState *video;
AVFormatContext *pFormatCtx;
char* filename;
//bool quit;
MediaState(char *filename);
~MediaState();
bool openInput();
};
结构比较简单,其主要的功能是在oepnInput
中,该函数用来打开相应的video文件,并读取相应的信息填充到VideoState
和AudioState
结构中。
主要有以下几个功能:
- 调用
avformat_open_input
获取AVFormatContext的指针 - 找到audio stream的index,并打开相应的
AVCodecContext
- 找到video stream的index,并打开相应的
AVCodecContext
1.3 Packet分离线程
调用oepnInput
后,以获取到足够的信息,然后创建packet分离线程,按照得到的stream index,将av_read_frame
读取到的packet分别放到相应的packet 缓存队列中。
部分代码如下:
if (packet->stream_index == media->audio->audio_stream) // audio stream
{
media->audio-&