下面这两条命令行实现文件格式的转变:
ffmpeg -i h264.mp4 -c:v copy -bsf:v h264_mp4toannexb -an out.h264
ffmpeg -i INPUT.mp4 -codec copy -bsf:v h264_mp4toannexb OUTPUT.ts
第一条命令行将包含h264编码的mp4文件中的h264视频流提取出来保存为raw h264格式
第二条命令行将包含h264编码的mp4文件转为ts文件格式
两者都用到了bitstream filter : h264_mp4toannexb 其作用是把字节长度的前缀转为start code的前缀,其实现在ffmpeg/libavcodec/h264_mp4toannexb_bsf.c文件中。
h264的码流分为VCL层和NAL层,NAL是由一系列NALU单元组成,其中nalu type为1-5的称为VCL的NALU,其他称为非VCL的NALU
NAL层在VCL层之上,解码器在解码时,必须从NAL层到VCL层,而编码过程则相反。
基本上是这样的过程NALU -> RBSP -> SODB
NALU就是一个nalu type加上EBSP,为什么是EBSP而不是直接就RBSP?因为raw h264格式的码流也叫h264 ES流是用start code来分割一个个NALU的,如果RBSP中含有类似start code的字节就麻烦了。这时候需要作emulation prevention处理,也叫escape,即
0x000000
0x000001
0x000002
0x000003
编码时加03和解码时去03,实际上这种情况出现的几率很小1:2^22。对非VCL的NALU也需要作这样处理。
最后总结一下,实际上在处理码流的协议复用和信道间传输与介质存储都不需要考虑escape过程,这是解码器和编码器处理了的。
下面引用几处开源的代码里的实现:
live555代码H264VideoStreamFramer.cpp中,
void H264VideoStreamParser::removeEmulationBytes(u_int8_t* nalUnitCopy, unsigned maxSize, unsigned& nalUnitCopySize) {
u_int8_t* nalUnitOrig = fStartOfFrame + fOutputStartCodeSize;
unsigned const NumBytesInNALunit = fTo - nalUnitOrig;
nalUnitCopySize = 0;
if (NumBytesInNALunit > maxSize) return;
for (unsigned i = 0; i < NumBytesInNALunit; ++i) {
if (i+2 < NumBytesInNALunit && nalUnitOrig[i] == 0 && nalUnitOrig[i+1] == 0 && nalUnitOrig[i+2] == 3) {
nalUnitCopy[nalUnitCopySize++] = nalUnitOrig[i++];
nalUnitCopy[nalUnitCopySize++] = nalUnitOrig[i++];
} else {
nalUnitCopy[nalUnitCopySize++] = nalUnitOrig[i];
}
}
}
在ffmpeg/libavcodec/h264.c文件中的函数ff_h264_decode_nal的实现代码里,有去03的escape过程,
while (si + 2 < length) {
// remove escapes (very rare 1:2^22)
if (src[si + 2] > 3) {
dst[di++] = src[si++];
dst[di++] = src[si++];
} else if (src[si] == 0 && src[si + 1] == 0 && src[si + 2] != 0) {
if (src[si + 2] == 3) { // escape
dst[di++] = 0;
dst[di++] = 0;
si += 3;
continue;
} else // next start code
goto nsc;
}
dst[di++] = src[si++];
}
在x264/common/bitstream.c文件的函数x264_nal_encode里有加03的escape处理,
/* nal header */
*dst++ = ( 0x00 << 7 ) | ( nal->i_ref_idc << 5 ) | nal->i_type;
dst = h->bsf.nal_escape( dst, src, end );
int size = dst - orig_dst;
它调用了一个函数,
static uint8_t *x264_nal_escape_c( uint8_t *dst, uint8_t *src, uint8_t *end )
{
if( src < end ) *dst++ = *src++;
if( src < end ) *dst++ = *src++;
while( src < end )
{
if( src[0] <= 0x03 && !dst[-2] && !dst[-1] )
*dst++ = 0x03;
*dst++ = *src++;
}
return dst;
}