题记:前文介绍了播放的流程和线程,本节开始从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构成
- 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文件进行查看
使用命令行
hexdump -C input.mp4
对照查看MP4文件的原始数据进行对照
由图中可以看到前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
sample
到dts
的时间映射关系 - ctts
sample
的pts
与dts
的差值,pts[n] = dps[n] + ctts[n] - stss 视频trak专用,对应关键帧(I帧)
- stsc 每个
chunk
包含的sample
信息,每个chunk
中的sample
是连续的 - stco 每个
chunk
的偏移 - stsz
sample
的数量和每个sample
的大小
- stbl 解码相关信息和音频位置
- minf
- mdia 采样信息
- trak 轨道(stream),音频或图像各对照一个tack
- mdat media data音视频信息
这里的dts
和pts
是解码用的关键信息,后续会有用到。MP4的具体封装可以参考更专业的文档,这里列出来只需要基本概念:
moov
包含文件信息,包含了mdat
目录和解码器信息- 在线播放MP4,都建议
moov
前置,本地文件很多都位于最后 - MP4需要解析完
moov
才能播放,所以首帧相对时间长,没有解析完moov
也不能seek
FLV
Flash Video(FLV)是一种由Adobe公司开发的视频封装格式,主要用于在网络上播放视频内容。此外流媒体如RTMP和HTTP-FLV传输的也是FLV Tag(此处不是完整的flv封装格式)。
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,真实数据
- Tag Header
- Header 主要记录版本信息
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层直接接触的结构体,主要变量
objectivec 代码解读复制代码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
打印信息
ini 代码解读复制代码long long totalMs = formatContext->duration * 1000 / AV_TIME_BASE;
streams
streams
是后续播放/封装来说最重要的结构,nb_streams
表示流数量。下节讲述,这里先用调试工具看一下信息,对于streams[0]
,包含采样率,解码参数,时长等信息。
iformat
包含当前文件的封装信息
而这个封装信息是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对象关联的对象的类)等。
URLProtocol
到AVFormatContext
层次关系如下
avformat_open_input
解析
- 函数参数:
AVFormatContext **ps
:指向AVFormatContext
指针的指针,赋值信息const char *url
:文件的路径或URLAVInputFormat *fmt
:输入格式,一般为NULL
,avformat_open_input
会尝试自动检测AVDictionary **options
:附加的配置项
- 文件打开:
- 根据路径
url
,打开文件或进行网络连接 - 如果指定了
fmt
,则直接使用该格式;否则,调用av_probe_input_format
函数探测文件格式。
- 根据路径
- 格式探测:
fmt
为NULL
时,读取文件前几个字节(通常是文件头),如前文的MP4和FLV头部- 根据
libavformat
中注册的各种格式,进行对比评分,评分最高的最终格式。
- 初始化
AVFormatContext
:- 根据探测的格式,分配一个
AVInputFormat
。 - 初始化
AVFormatContext
- 根据探测的格式,分配一个
- 解析文件头:
- 调用
AVInputFormat
中的read_header
函数 - 读取文件的头部信息,解析出流(视频流、音频流、字幕流等)信息
- 调用
- 填充
AVFormatContext
:- 根据解析出的信息,填充
AVFormatContext
中的streams
数组,每个AVStream
对应一个媒体流 AVStream
包含该流的编码参数,以及该流中的索引信息
- 根据解析出的信息,填充
- 返回结果:
- 成功,返回
0,并且
*ps指向一个有效的
AVFormatContext`。 - 失败,返回负的错误代码,并且
*ps
通常为NULL
。
- 成功,返回
评论记录:
回复评论: