本文为作者原创,转载请注明出处:https://blog.csdn.net/leisure_chn/article/details/87926291
ffplay是FFmpeg工程自带的简单播放器,使用FFmpeg提供的解码器和SDL库进行视频播放。本文基于FFmpeg工程4.1版本进行分析,其中ffplay源码清单如下:
https://github.com/FFmpeg/FFmpeg/blob/n4.1/fftools/ffplay.c
在尝试分析源码前,可先阅读如下参考文章作为铺垫:
[1]. 雷霄骅,视音频编解码技术零基础学习方法
[2]. 视频编解码基础概念
[3]. 色彩空间与像素格式
[4]. 音频参数解析
[5]. FFmpeg基础概念
“ffplay源码分析”系列文章如下:
[1]. ffplay源码分析1-概述
[2]. ffplay源码分析2-数据结构
[3]. ffplay源码分析3-代码框架
[4]. ffplay源码分析4-音视频同步
[5]. ffplay源码分析5-图像格式转换
[6]. ffplay源码分析6-音频重采样
[7]. ffplay源码分析7-播放控制
3. 代码框架
本节简单梳理ffplay.c代码框架。一些关键问题及细节问题在后续章节探讨。
3.1 流程图
3.2 主线程
主线程主要实现三项功能:视频播放(音视频同步)、字幕播放、SDL消息处理。
主线程在进行一些必要的初始化工作、创建解复用线程后,即进入event_loop()主循环,处理视频播放和SDL消息事件:
main() -->
static void event_loop(VideoState *cur_stream)
{
SDL_Event event;
......
for (;;) {
// SDL event队列为空,则在while循环中播放视频帧。否则从队列头部取一个event,退出当前函数,在上级函数中处理event
refresh_loop_wait_event(cur_stream, &event);
// SDL事件处理
switch (event.type) {
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_f: // f键:强制刷新
......
break;
case SDLK_p: // p键
case SDLK_SPACE: // 空格键:暂停
......
case SDLK_s: // s键:逐帧播放
......
break;
......
......
}
}
}
3.2.1 视频播放
主要代码在**refresh_loop_wait_event()**函数中,如下:
static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {
double remaining_time = 0.0;
SDL_PumpEvents();
while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) {
if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) {
SDL_ShowCursor(0);
cursor_hidden = 1;
}
if (remaining_time > 0.0)
av_usleep((int64_t)(remaining_time * 1000000.0));
remaining_time = REFRESH_RATE;
if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
// 立即显示当前帧,或延时remaining_time后再显示
video_refresh(is, &remaining_time);
SDL_PumpEvents();
}
}
while()
语句表示如果SDL event队列为空,则在while循环中播放视频帧;否则从队列头部取一个event,退出当前函数,在上级函数中处理event。
refresh_loop_wait_event()
中调用了非常关键的函数video_refresh()
,video_refresh()
函数实现音视频的同步及视频帧的显示,是ffplay.c中最核心函数之一,在“4.3节 视频同步到音频”中详细分析。
3.2.2 SDL消息处理
处理各种SDL消息,比如暂停、强制刷新等按键事件。比较简单。
main() -->
static void event_loop(VideoState *cur_stream)
{
SDL_Event event;
......
for (;;) {
// SDL event队列为空,则在while循环中播放视频帧。否则从队列头部取一个event,退出当前函数,在上级函数中处理event
refresh_loop_wait_event(cur_stream, &event);
// SDL事件处理
switch (event.type) {
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_f: // f键:强制刷新
......
break;
case SDLK_p: // p键
case SDLK_SPACE: // 空格键:暂停
......
break;
......
......
}
}
}
3.3 解复用线程
解复用线程读取视频文件,将取到的packet根据类型(音频、视频、字幕)存入不同是packet队列中。
为节省篇幅,如下源码中非关键内容的源码使用“…”替代。代码流程参考注释。
/* this thread gets the stream from the disk or the network */
static int read_thread(void *arg)
{
VideoState *is = arg;
AVFormatContext *ic = NULL;
int st_i