问题1:如何识别关键帧?
从x264的源代码中可以看到,如果某一帧为关键帧,这么这一帧的所携带的几个nalu中,必然有一个的type 必然为NAL_SLICE_IDR。
/* Set output picture properties */ if( h->sh.i_type == SLICE_TYPE_I ) pic_out->i_type = h->i_nal_type == NAL_SLICE_IDR ? X264_TYPE_IDR : X264_TYPE_I; else if( h->sh.i_type == SLICE_TYPE_P ) pic_out->i_type = X264_TYPE_P; else pic_out->i_type = X264_TYPE_B;
我的理解就是如果nalu的type为NAL_SLICE_IDR的话,那么这帧肯定就是关键帧了。
因此只需要判断每一帧的nalu的type中是否包还有type为NAL_SLICE_IDR的,如果有则是关键帧。
判断nalu的type的可用如下的方法。
for(int i=0; i<i_nalu; i++)
{
uint8_t *ptr = nalu[i];
int type = ptr[4]&0x1F;
if(type == NAL_SLICE_IDR)
{
key_frame = true;
break;
}
}
判断nalu的type中用到int type = ptr[4]&0x1F; 为什么是0x1F呢,这得说说每个nalu的结构;
nalu有三部分组成。
起始码 nalu_header data
起始码: 分为longcode 和 shortcode ;
longcode :0x 00 00 00 01;
shortcode :0x 00 00 01;
nalu_header:有三个子域;
NALU 头由一个字节组成, 它的语法如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
F: 1 个比特.
forbidden_zero_bit. 在 H.264 规范中规定了这一位必须为 0.
NRI: 2 个比特.
nal_ref_idc. 取 00 ~ 11, 似乎指示这个 NALU 的重要性, 如 00 的 NALU 解码器可以丢弃它而不影响图像的回放. 不过一般情
况下不太关心
这个属性.
Type: 5 个比特.
nal_unit_type. 这个 NALU 单元的类型. 简述如下:
0 没有定义
1-23 NAL单元 单个 NAL 单元包.
24 STAP-A 单一时间的组合包
24 STAP-B 单一时间的组合包
26 MTAP16 多个时间的组合包
27 MTAP24 多个时间的组合包
28 FU-A 分片的单元
29 FU-B 分片的单元
30-31 没有定义
大家可以看到,要获得type的话,就得将nalu起始码后面紧跟的第一个字节的内容与0x1F做与运算后得到。
问题2:如何判断每一帧的中nalu的起始和结束。
数据在经过int x264_nal_encode( void *, int *, int b_annexeb, x264_nal_t *nal )后,会将数据组装成nalu的标准格式,即按照上面的格式:
起始码 nalu_header data。
x264使用0x 00 00 00 01作为起始码(longcode)。int x264_nal_encode( void *, int *, int b_annexeb, x264_nal_t *nal )的作用就是使得数据流中不会再出现连续的两个以上的0。如果有的话就会插入一个0x03;
因此如果要还原一帧数据中的逻辑结构的话,可以遍历数据流,寻找0x00000001这样的特征代码。
这一点对于使用x264自带的mkv文件保存接口很是有用。因为我们接收到的网络数据可能没有没有带上详细的nalu分界信息和帧类型,需要自己重新还原。