Ffplay视频播放流程

本文详细分析了ffplay的主框架流程,包括read_thread线程、video_thread线程以及主线程的工作原理。从初始化、循环读取数据到解码处理,再到反初始化的整个过程,逐一剖析了每个部分的关键操作,如创建AVFormatContext、打开输入文件、解析码流信息、解码处理和视频图像显示。此外,还介绍了如何处理pause、resume、seek等操作。
摘要由CSDN通过智能技术生成

背景说明

FFmpeg是一个开源,免费,跨平台的视频和音频流方案,它提供了一套完整的录制、转换以及流化音视频的解决方案。而ffplay是有ffmpeg官方提供的一个基于ffmpeg的简单播放器。学习ffplay对于播放器流程、ffmpeg的调用等等是一个非常好的例子。本文就是对ffplay的一个基本的流程剖析,很多细节内容还需要继续钻研。

注:本文师基于ffmpeg-2.0版本进行分析,具体代码行还请对号入座,谢谢!

主框架流程

下图是一个使用“gcc+eygpt+graphviz+手工调整”生成的一个ffplay函数基本调用关系图,其中只保留了视频部分,去除了音频处理、字幕处理以及一些细节处理部分。

ffplay主流程

注:图中的数字表示了播放中的一次基本调用流程,X?序号表示退出流程。

从上图中我们可以了解到以下几种信息:

  • 三个线程:主流程用于视频图像显示和刷新、read_thread用于读取数据、video_thread用于解码处理;
  • 视频数据处理:由read_thread读取原始数据解复用后,按照packet的方式放入到队列中;由video_thread从packet队列中读取packet解码后,按照picture的方式放入到队列中;由主流程从picture队列中依次取picture进行显示;
  • 启动流程:启动流程如上图中的数字部分
  • 退出流程:退出流程如上图中的X?序号部分

下面将对三个线程分别加以详细描述。

read_thread线程

从read_thread开始说起而不是从main线程,主要原因是考虑按照视频数据转换的方式比较好理解。

read_thread的创建是在main-->stream_open函数中:

    is->read_tid     = SDL_CreateThread(read_thread, is);

read_thread线程主要分为三部分:

  • 初始化部分:主要包括SDL_mutex信号量创建、AVFormatContext创建、打开输入文件、解析码流信息、查找音视频数据流并打开对应的数据流。对应ffplay.c文件中的2693-2810行代码;
  • 循环读取数据部分:主要包括pause和resume操作处理、seek操作处理、packet队列写入失败处理、读数据结束处理、然后是读数据并写入到对应的音视频队列中。对应ffplay.c文件中的2812-2946行代码;
  • 反初始化部分:主要包括退出前的等待、关闭音视频流、关闭avformat、给主线程发送FF_QUIT_EVENT消息以及销毁SDL_mutex信号量。对应ffplay.c文件中的2947-2972行代码;

初始化部分

主要包括SDL_mutex信号量创建、创建avformat上下文、打开输入文件、解析码流信息、查找音视频数据流并打开对应的数据流。

创建wait_mutex互斥量

    SDL_mutex *wait_mutex = SDL_CreateMutex();

该互斥量主要用于在对(VideoState *)is->continue_read_thread操作时加保护,如2887行和2925行:

//代码段一

/* if the queue are full, no need to read more */

if (infinite_buffer<1 &&

      ……) {

    /* wait 10 ms */

    SDL_LockMutex(wait_mutex);

    SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);  <-- line 2887

    SDL_UnlockMutex(wait_mutex);

    continue;

}

 

//代码段二

ret = av_read_frame(ic, pkt);

if (ret < 0) {

    if (ret == AVERROR_EOF || url_feof(ic->pb))

        eof = 1;

    if (ic->pb && ic->pb->error)

        break;

    SDL_LockMutex(wait_mutex);

    SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);  <-- line 2925

    SDL_UnlockMutex(wait_mutex);

    continue;

}

而continue_read_thread从其名字上来看,是一个控制read_thread线程是否继续阻塞的信号量,上面两次阻塞的地方分别是:packet队列已满,需要等待一会(即超时10ms)或者收到信号重新循环;读数据失败,但是并不是IO错误(ic->pb->error),如读取网络实时数据时取不到数据,此时也需要等待或者收到信号重新循环。

注:seek操作时(L1216)和音频队列为空(L2327)时,会发送continue_read_thread信号。

AVFormatContext创建

(AVFormatContext *)ic = avformat_alloc_context();

         此处创建的avformat上下文,类似于一个句柄,后续所有avformat相关的函数调用第一个参数都是该上下文指针,如avformat_open_input、avformat_find_stream_info以及一些和av相关的函数接口第一个参数也是该指针,如av_find_best_stream、av_read_frame等等。

打开输入文件

err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);

         创建好avformat上下文后,就打开is->filename指定的文件(或流),其中第三个和第四个参数可以传NULL,由ffmpeg自动侦测待输入流的文件格式,也可以通过is->iformat手动指定,format_opts参数表示设置的特殊属性。

通过调用avformat_open_input函数,我们可以得到输入流的一个基本信息。我们可以通过调用av_dump_format(ic, 0, is->filename, 0);来输出解析后的码流信息,可以得到如下数据:

Input #0, mpegts, from '/home/nfer/bak/cw880-latency.ts':0B f=0/0  

  Duration: N/A, bitrate: N/A

  Program 1

    Stream #0:0[0x68]:Video:h264 ([27][0][0][0] / 0x001B), 90k tbn

    Stream

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值