音视频学习笔记四——从0开始的播放器之解封装

346 阅读7分钟

题记:前文介绍了播放的流程和线程,本节开始从0开始学习播放器的详细设计,结合ffplay和Demo讲述播放器的整体设计和FFmpeg的细节,本节从解封装开始。

封装介绍

视频封装是把编码后的图像数据和音频数据按一定格式组装到一个文件中,因此封装格式也称为视频容器,常见容器

  • AVI(Audio Video Interleave)
    • 兼容性好
    • 图像数据和声音数据交互存放
    • 索引放在尾部,不能适用流媒体
  • TS(Transport Stream)
    • 高清视频流传输
    • HLS流媒体传输的m3u8/TS文件
  • MKV(Matroska Video)
    • 支持可变帧率、错误检测及修复功能
    • 支持软字幕、流式传输和交互式菜单等
    • 容纳多种不同类型编码的视频、音频及字幕流
  • MP4(MPEG-4 Part 14)
    • 兼容性强,占系统资源少
    • 文件体积小、清晰度高
  • WMV(Windows Media Video)
    • 微软开发的ASF封装格式
    • 数字版权保护功能
    • 高压缩,清晰度高
    • 依赖Window平台
  • FLV(Flash Video)
    • Adobe开发的封装形式
    • 文件体积小,在线加载速度快
  • RM/RMVB
    • 由RealNetworks开发
    • 同码率下,RMVB编码的体积比H.264编码大

封装格式不影响音视频数据本身,但排布方式的不同会影响到首帧加载和Seek操作的表现,以下可以简单介绍下MP4和FLV,方便后续对代码的理解。

MP4

MP4(MPEG-4 Part 14)格式是常见的标准多媒体容器格式,文件扩展名为.mp4(仅有音频的为.m4a)。

MP4 Box介绍

MP4基本Box结构如下图: mp4_box.jpg

  • MP4由多个Box构成
  • BOX由header和body构成
  • body中可以是嵌套box
  • header中构成:
    • 4字节size 表示box长度
    • 4字节type 表示类型
    • 如果size位数不够,增加8字节layersize字段,size表示为1
    • 如果size==0,表示该box到文件结尾
    • 如果有type为uuid,增加扩展version(1B)和flag(3B)

我们可以使用mp4Info(只有window版本)或者在线工具对MP4文件进行查看 image.png 使用命令行hexdump -C input.mp4对照查看MP4文件的原始数据进行对照 image.png

由图中可以看到前20个字节

  • 0X14表示20该box的长度
  • 后4个字节表示ftyp
  • 由于size>1,type不是uuid,header字段只有8字节
  • 接下来的12字节是ftyp的内容
  • 20字节后就是下一个box内容wide

MP4简单解析

MP4包含很多Box,这里只列出主线所需的内容:

  • ftyp 头文件,记录兼容信息
  • moov 媒体信息
    • trak 轨道(stream),音频或图像各对照一个tack
      • mdia 采样信息
        • minf
          • stbl 解码相关信息和音频位置
            • stsd 编码类型和解码器所需信息
            • stts sampledts 的时间映射关系
            • ctts sampleptsdts的差值,pts[n] = dps[n] + ctts[n]
            • stss 视频trak专用,对应关键帧(I帧)
            • stsc 每个chunk包含的sample信息,每个chunk中的sample是连续的
            • stco 每个chunk的偏移
            • stsz sample的数量和每个sample的大小
  • mdat media data音视频信息

这里的dtspts是解码用的关键信息,后续会有用到。MP4的具体封装可以参考更专业的文档,这里列出来只需要基本概念:

  • moov包含文件信息,包含了mdat目录和解码器信息
  • 在线播放MP4,都建议moov前置,本地文件很多都位于最后
  • MP4需要解析完moov才能播放,所以首帧相对时间长,没有解析完moov也不能seek

MP4结构.jpg

FLV

Flash Video(FLV)是一种由Adobe公司开发的视频封装格式,主要用于在网络上播放视频内容。此外流媒体如RTMP和HTTP-FLV传输的也是FLV Tag(此处不是完整的flv封装格式)。

FLV结构.jpg

FLV封装格式:

  • FLV结构中包含2部分
    • Header 主要记录版本信息
      • 3字节 Signature 就是‘FLV’
      • 1字节 版本
      • 1字节 flag信息,标记是否包含音视频流
      • 4字节 header大小
    • Body 中记录数据
      • Previous Tag Size标记前一个tag大小
      • Tag 数据
        • Tag Header
          • Type 1字节,音频0x08 视频0x09 Script0x12
          • Data Size 4字节 数据段Tag Data的大小
          • TimeStamp 3字节 时间戳
          • TimeStampEx 1字节 扩展
          • StreamId
        • Tag Data,真实数据

Script Data存放整体信息:

  • MetaData数据
    • 时长
    • 文件大小
    • 视频信息,宽、高、编码类型、视频帧率、视频码率等
    • 音频信息 采样率、位宽、码率、编码类型等
  • keyframes
    • 关键帧索引`,为方便seek,一般需要建立关键帧索引。
    • 记录视频关键帧对应的时间和位置

小结

了解封装格式有助于代码理解,这里介绍了MP4和FLV,方便了解首帧性能和Seek时表现的不同。后续可进入解封装的代码阶段。

FFmpeg解封装

对于解封装,主要关注几个函数,代码在Demo 播放器部分或者Decoder部分:

  • av_register_all
    • 已废弃方法
    • 注册所有可用的封装器和解码器,已改为自动机制。
  • avformat_network_init(avformat_network_deinit)
    • 初始化网络协议
    • 访问网络资源时用到
  • avformat_open_input(avformat_close_input)
    • 打开音视频文件或者网络流
    • 传入AVFormatContext **ps类型参数

通过调用avformat_open_input,就得到了FFmpeg第一个重要模型AVFormatContext。

AVFormatContext格式

AVFormatContext是贯穿播放/封装的数据结构,是API层直接接触的结构体,主要变量

struct AVInputFormat *iformat:解封装的结构体,表示输入文件格式
AVIOContext *pb:用于读取文件或网络流数据
unsigned int nb_streams:流个数,与下面的streams一起使用
AVStream **streams:流指针,视频、音频、字幕流的具体信息
char *url:文件名或者网络流
int64_t duration:时长(微秒),需要`/AV_TIME_BASE`得到秒数
AVDictionary *metadata:元数据,例如标题,作者,秒数等
  • 时长及基本信息使用av_dump_format打印信息
long long totalMs = formatContext->duration * 1000 / AV_TIME_BASE;
  • streams streams是后续播放/封装来说最重要的结构,nb_streams表示流数量。下节讲述,这里先用调试工具看一下信息,对于streams[0],包含采样率,解码参数,时长等信息。

streams.jpg

  • iformat包含当前文件的封装信息 iformat.jpg

而这个封装信息是FFmpeg中保存的支持格式中最适合当前文件的,具体来源:

avformat_open_input解析

avformat_open_input在解析文件时,会识别文件格式(参考雷神文档)。具体根据

  • 判断文件名后缀
  • 读取文件头的数据格式进行对比(参考前面的MP4和FLV格式)

对文件或者视频流进行评分,获取得分最高的文件协议对应的URLProtocol。URLProtocol是对文件或者视频流操作的抽象,具体包括

  • name:协议的名称,如"http"、"rtmp"、"file"等
  • url_open:打开URL的函数指针
  • url_read:从URL读取数据的函数指针
  • url_write:向URL写入数据的函数指针
  • url_seek:在URL中定位的函数指针
  • url_close:关闭URL的函数指针。
  • 其他成员:如priv_data_size(与URLProtocol对象关联的对象的大小)、priv_data_class(与URLProtocol对象关联的对象的类)等。

URLProtocolAVFormatContext层次关系如下 formatcontext结构.jpg

avformat_open_input解析

  • 函数参数
    • AVFormatContext **ps:指向 AVFormatContext 指针的指针,赋值信息
    • const char *url:文件的路径或URL
    • AVInputFormat *fmt:输入格式,一般为NULLavformat_open_input会尝试自动检测
    • AVDictionary **options:附加的配置项
  • 文件打开
    • 根据路径url,打开文件或进行网络连接
    • 如果指定了fmt,则直接使用该格式;否则,调用 av_probe_input_format 函数探测文件格式。
  • 格式探测
    • fmtNULL时,读取文件前几个字节(通常是文件头),如前文的MP4和FLV头部
    • 根据libavformat 中注册的各种格式,进行对比评分,评分最高的最终格式。
  • 初始化 AVFormatContext
    • 根据探测的格式,分配一个 AVInputFormat
    • 初始化 AVFormatContext
  • 解析文件头
    • 调用AVInputFormat中的 read_header 函数
    • 读取文件的头部信息,解析出流(视频流、音频流、字幕流等)信息
  • 填充 AVFormatContext
    • 根据解析出的信息,填充AVFormatContext中的streams数组,每个 AVStream 对应一个媒体流
    • AVStream包含该流的编码参数,以及该流中的索引信息
  • 返回结果
    • 成功,返回0,并且 *ps 指向一个有效的 AVFormatContext`。
    • 失败,返回负的错误代码,并且 *ps 通常为 NULL