写在前面的话

本期是移动端音视频的第二期,自上期已经足足过了一个月之久,这一个月笔者也没少闲着,自从做完了公司的一个“采用两种不同方式,在混乱的基础代码上,复杂的呈现数据的视图”的需求之后,便看起了关于“读取音视频流”“获取音视频包”的相关内容。所以到现在才开始书写这篇文章。(虽然我也干了其他重要的事,比如买了个烤肉架边喝酒边烤肉吃)

本期将会从创建一些FFmpeg的数据结构开始,一直到读取音视频流。

AVFormatContext

为什么直接把AVFormatContext拿到最开始讲呢,因为敏锐的我察觉到,它将会贯穿整个FFmpeg开发的伊始到终焉。
FFmpeg的官方文档中,关于AVFormatContext的链接在这里

简单来说,AVFormatContext用来打开媒体文件或者媒体流,可以从这个结构体中获取想要的数据信息,比如时长,缓存,文件名等等。

首先需要初始化一下ffmpeg:

然后,我们需要准备一个视频文件。
视频(资源文件)在工程目录下,且已经被包括进 Project - BuildPhases - Copy Bundle Resource 里了。

 

我们一步一步搭取这个读取音视频流的流程。
接下来,需要的类就是AVFormatContext,通过以下代码引入它,并且新建一个指针变量用以指向它(只是它现在还是为NULL)。

 

在这篇文章里,比较好的说明了avformate_open_inputavformate_close_input的引用关系 —— FFmpeg源代码简单分析:avformat_close_input()

 

 

接下来需要为AVFormatContext指定一下探测尺寸。

 

并且马上会用到avformat_find_stream_info这个函数,我们先看一下avformat_find_stream_info的定义。

 

观察到第27行,指定了默认的max_analyze_duration的值为“5*AV_TIME_BASE”,这里的“5*AV_TIME_BASE”代表时间基数,可以先不管,把它当做5s长度就行了。

然后,对于probesize,官方文档中描述道:

Maximum size of the data read from input for determining the input container format.

Demuxing only, set by the caller before avformat_open_input().

Definition at line 1292 of file avformat.h.

Referenced by avformat_find_stream_info(), lavfi_read_header(), and mpegts_read_header().

—— AVFormatContext Struct Reference

 

并且它代表的是字节数。那么就是搜索,5s,512 x 1024 = 5KB 的长度。

至于决定于哪一个数值?这里这么写到:

which will cause ffmpeg to search until the first of those limits is reached. Note that both of these options must appear on the command line before the specification of the input via -i. For example:

ffmpeg -probesize 50M -analyzeduration 100M -i vts.vob

will search through vts.vob for all streams until it has read 50 MB of data or 100 seconds of video, whichever comes first.

—— FFMPEG An Intermediate Guide/subtitle options

 

也就是说,先到达了5s,停止,先到达了5KB,也停止。

AVStream & AVCodecContext

设定了探测尺寸后,就可以开始遍历所有的流了,所有的流以AVStream的数据结构存在于AVFormateContextstreams变量之中,共有nb_streams个数据流。
所以这样书写代码:

 

而每个AVStream中包含一个codecpar变量,类型为AVCodecParameters,让我们来看看它的部分数据结构:

AVCodecParameters中包含了一些基本信息:

  • enum AVMediaType codec_type:流的类型;
  • enum AVCodecID codec_id:编码器ID,是一个标识符;
  • int format:在视频流中代表像素格式AVPixelFormat,在音频流中代表采样格式AVSampleFormat
  • int64_t bit_rate:比特率。
  • ...

 

而接下来需要把这个codecpar转换为一个标准的AVCodecContext

【!】原来的AVStream包含一个类型为AVCodecContextcodec变量,可以直接调取,但是已经在ffmpeg3.3中被废弃了。经查,这个原因似乎是因为更好的解耦,以前使用codec代码简洁,但是耦合性比较大,在多线程分开处理解码+封装问题需要考虑互斥问题,但是现在使用codecpar可以单独生成新的AVCodecContext,但是目前只有找到这篇文章提及了这点: ffmpeg3.3新版本AVStream的封装流参数由codec替换codecpar(解码)

 

如何将AVCodecParameters转换为AVCodecContext?ffmpeg已经为我们提供一个完美的参数复制的函数:

至此,我们已经一个得到了一个充满信息的AVCodecContext实例。

接下来还有一步:

但是笔者并不知道为何需要重新设定AVCodecContexttime_base
有关于av_codec_set_pkt_timebase这个函数:

如果在座的各位能让我请教一番便再好不过了。

接下来,我们就可以愉快的获取各个流内的信息啦。

 

需要注意的是:

  • 这里的r_frame_rate是基本帧率,仅仅是一个猜测,优先级较低 ,avg_frame_rate是平均帧率,由整个流计算而来,优先级较高。
    详情见 —— FFmpeg之ffprobe
  • 这里的sample_fmtpix_fmt是分开的,而非AVCodecParameters中仅有一个format变量。

到此,我们流的信息就分析完毕了。
但是!最后别忘了做一些善后工作:

 

完成的测试用例在这里:

在最后

以上就是本次想要稍微梳理介绍的,通过ffmpeg读取音视频流数据信息的基本步骤。
下次就是到通过多线程机制来进行AVPacket的读取啦。