首页 > 代码库 > 帧边界识别简介
帧边界识别简介
为了省事,我没有播放H264编码的AVI或者是MP4等格式的文件,而是直接播放的H264编码的裸数据。如果要播放AVI或者MP4文件,就需要对AVI或者MP4的文件格式进行解析,然后将其中的音频和视频的数据读取出来。现在比较流行的H264的封装都采用了MP4格式, 或许大家对这个不是很熟悉,但是他的兄弟3GP格式想必都听说过了。其实两者差不多,只是其中所谓的BOX有所不同,一个被阉割了而已(明朝那些事儿看多了)。
H264的数据读取最关键的地方就是如何识别一帧,因为ffmpeg的H264解码是按照一帧一帧解码的,如果送去解码的数据不是完整的一帧可能导致解码失败或者产生马赛克的情况。
以下是我在一个文档中摘取关于帧边界识别的一段,可以方便的解决相关的问题:
帧边界识别简介
H.264 将构成一帧图像所有nalu 的集合称为一个AU,帧边界识别实际上就是识别AU。
因为H.264 取消帧级语法,所以无法简单地从码流中获取AU。解码器只有在解码的过
程中,通过某些语法元素的组合才能判断一帧图像是否结束。一般来说,解码器必须在
完成一帧新图像的第一个slice_header 语法解码之后,才能知道前一帧图像已经结束。
因此,最严谨的AU 识别步骤如下:
步骤 1 对码流实施“去03 处理”。
步骤 2 解析nalu 语法。
步骤 3 解析slice_header 语法。
步骤 4 综合判断前后两个nalu 以及对应的slice_header 中的若干个语法元素,看是否发生变化。
如果发生变化,则说明这两个nalu 属于不同的帧,否则说明这两个nalu 属于同一帧。
----结束
显然,在解码前完成上述的AU 识别消耗许多CPU 资源,因此不推荐使用AU 方式解
码
为了提供一种简单的AU 识别方案,H.264 规定一种类型为09 的nalu,即编码器在每次完成一个AU 编码后,在码流中插入一个类型为09 的nalu,在这个前提下,解码器只需要从码流中搜索类型为09 的nalu 即可获得一个AU。
H.264 并不强制要求编码器插入类型为09 的nalu,因此并非所有的码流都具备这种特征。对于
编码器和解码器协同工作的应用场景,建议让编码器插入类型为09 的nalu,这样可以降低解码
器识别AU 的代价。
为了降低解码器在解码前识别AU 的代价,本文提出一种高效的AU 识别方法,其主要
思路是利用一帧图像的第一个slice_header 中的语法元素first_mb_in_slice 一般等于0 这
个特征(对于包含ASO 或者FMO 特性的码流,这个条件不一定成立)。
- typedef struct ParseContext{
- unsigned int FrameStartFound;
- unsigned int iFrameLength;
- } ParseContext;
- signed int DecLoadAU(unsigned char* pStream, unsigned int iStreamLen, ParseContext *pc)
- {
- unsigned int i;
- unsigned int state = 0xffffffff;
- if( NULL == pStream )
- {
- return -1;
- }
- for( i = 0; i < iStreamLen; i++)
- {
- /* 查找nal类型为1和5的nal头 */
- if( (state & 0xFFFFFF1F) == 0x101 || (state & 0xFFFFFF1F) == 0x105 )
- {
- if (i >= iStreamLen) /* 到达Buffer尾部,退出查找循环 */
- {
- break;
- }if( pStream[i] & 0x80) /* 查找first_mb_in_slice为0的
- slice头确定一幅图像的开始 */
- {
- if(pc->FrameStartFound) /* 查找到下一幅图像的开始就可以确
- 定图像的起始和结束 */
- {
- pc->iFrameLength = i - 4;
- pc->FrameStartFound = 0;
- state = 0xffffffff;
- return 0; /* 找到一幅图像的边界返回0 */
- }
- else
- {
- pc->FrameStartFound = 1;
- }
- }
- }
- if (i < iStreamLen)
- {
- state = (state << 8) | pStream[i]; /* 从码流Buffer中读入1个字节 */
- }
- }
- pc->FrameStartFound = 0;
- return -1; /* 没有找到AU边界返回-1 */
- }
解决了最关键的问题后, 下面就是将解码完的数据送去播放的问题了, 可以启动一个Timer, 定时去读取一帧的数据送去解码就可以。针对不同的帧率调整Timer的时间即可,当然最正规的做法是根据MP4或者AVI文件中的时间戳。
播放效果:
Any question: ppnext@163.com