一、引言
在《音视频入门基础:H.264专题(18)------AVCDecoderConfigurationRecord简介》中对H.264的avcC包装格式以及AVCDecoderConfigurationRecord的概念进行了简介。在FFmpeg源码中,通过ff_h264_decode_extradata函数来解码AVCDecoderConfigurationRecord(又称为extradata)。本文对该函数进行讲解。
二、ff_h264_decode_extradata函数的声明
ff_h264_decode_extradata函数声明在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的头文件libavcodec/h264_parse.h中:
cpp
int ff_h264_decode_extradata(const uint8_t *data, int size, H264ParamSets *ps,
int *is_avc, int *nal_length_size,
int err_recognition, void *logctx);
该函数的作用是:解码avcC包装的H.264码流中的AVCDecoderConfigurationRecord(extradata)。
形参data:输入型参数,指向某个缓冲区。该缓冲区存放AVCDecoderConfigurationRecord,缓冲区的第一个字节必须存放AVCDecoderConfigurationRecord的configurationVersion属性**。**
形参size:输入型参数,形参data指向的缓冲区的大小。
形参ps:输出型参数。执行ff_h264_decode_extradata函数后,ps->sps_list[sps_id]->data指向的缓冲区会存放从SPS中提取出来的每个属性(sps_id是该SPS的seq_parameter_set_id);ps->pps_list[pps_id]->data指向的缓冲区会存放从PPS中提取出来的每个属性。(pps_id是该PPS的pic_parameter_set_id)这样在ff_h264_decode_extradata函数外部就可以通过"C语言的结构体指针强转"(const SPS*)ps->sps_list[sps_id]->data 获取结构体SPS中的成员,从而拿到SPS中的每个属性(可以通过类似方法获取到PPS中的成员)。
形参is_avc:输出型参数,表示是否为H.264。*is_avc的值为1表示是H.264,为0表示不是H.264。
形参nal_length_size:输出型参数,*nal_length_size的值为从AVCDecoderConfigurationRecord中解码出来的lengthSizeMinusOne属性的值加1,即表示存贮每个NALU前面的NALUnitLength所需的空间,单位为字节。
形参err_recognition:输入型参数。表示错误识别,值一般为0。
形参logctx:输入型参数。用来输出日志,可忽略。
返回值:解码AVCDecoderConfigurationRecord成功并且该码流为H.264,返回形参size的值;失败返回一个负数。
三、ff_h264_decode_extradata函数的定义
ff_h264_decode_extradata函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavcodec/h264_parse.c中:
cpp
int ff_h264_decode_extradata(const uint8_t *data, int size, H264ParamSets *ps,
int *is_avc, int *nal_length_size,
int err_recognition, void *logctx)
{
int ret;
if (!data || size <= 0)
return -1;
if (data[0] == 1) {
int i, cnt, nalsize;
const uint8_t *p = data;
*is_avc = 1;
if (size < 7) {
av_log(logctx, AV_LOG_ERROR, "avcC %d too short\n", size);
return AVERROR_INVALIDDATA;
}
// Decode sps from avcC
cnt = *(p + 5) & 0x1f; // Number of sps
p += 6;
for (i = 0; i < cnt; i++) {
nalsize = AV_RB16(p) + 2;
if (nalsize > size - (p - data))
return AVERROR_INVALIDDATA;
ret = decode_extradata_ps_mp4(p, nalsize, ps, err_recognition, logctx);
if (ret < 0) {
av_log(logctx, AV_LOG_ERROR,
"Decoding sps %d from avcC failed\n", i);
return ret;
}
p += nalsize;
}
// Decode pps from avcC
cnt = *(p++); // Number of pps
for (i = 0; i < cnt; i++) {
nalsize = AV_RB16(p) + 2;
if (nalsize > size - (p - data))
return AVERROR_INVALIDDATA;
ret = decode_extradata_ps_mp4(p, nalsize, ps, err_recognition, logctx);
if (ret < 0) {
av_log(logctx, AV_LOG_ERROR,
"Decoding pps %d from avcC failed\n", i);
return ret;
}
p += nalsize;
}
// Store right nal length size that will be used to parse all other nals
*nal_length_size = (data[4] & 0x03) + 1;
} else {
*is_avc = 0;
ret = decode_extradata_ps(data, size, ps, 0, logctx);
if (ret < 0)
return ret;
}
return size;
}
四、ff_h264_decode_extradata函数的内部实现分析
ff_h264_decode_extradata函数内部,首先判断形参data指向的缓冲区是否为空,或形参data指向的缓冲区的大小是否小于0。如果是,ff_h264_decode_extradata函数返回-1表示解码失败:
cpp
if (!data || size <= 0)
return -1;
从 《音视频入门基础:H.264专题(18)------AVCDecoderConfigurationRecord简介》中可以知道,AVCDecoderConfigurationRecord的configurationVersion属性值必须为1。如果检测到configurationVersion的值为1(data[0] == 1),则让*is_avc为1,表示该码流为H.264的视频压缩编码格式;如果不为1,让*is_avc为0,表示该码流不是H.264:
cpp
if (data[0] == 1) {
//...
*is_avc = 1;
//...
} else {
*is_avc = 0;
ret = decode_extradata_ps(data, size, ps, 0, logctx);
if (ret < 0)
return ret;
}
configurationVersion属性的值为1时,代码继续往下执行。从 《音视频入门基础:H.264专题(18)------AVCDecoderConfigurationRecord简介》中可以知道,AVCDecoderConfigurationRecord不包含任何SPS和PPS的极端情况下,AVCDecoderConfigurationRecord也占7个字节(configurationVersion + AVCProfileIndication + profile_compatibility + AVCLevelIndication + reserved + lengthSizeMinusOne + reserved + numOfSequenceParameterSets + numOfPictureParameterSets总共7个字节)。所以判断形参data指向的缓冲区的大小是否小于7字节,如果是,打印"avcC too short",并返回AVERROR_INVALIDDATA表示解码失败:
cpp
if (size < 7) {
av_log(logctx, AV_LOG_ERROR, "avcC %d too short\n", size);
return AVERROR_INVALIDDATA;
}
读取出AVCDecoderConfigurationRecord的numOfSequenceParameterSets属性,得到SPS的数目,赋值给局部变量cnt:
cpp
// Decode sps from avcC
cnt = *(p + 5) & 0x1f; // Number of sps
让指针p指向AVCDecoderConfigurationRecord的sequenceParameterSetLength属性:
cpp
p += 6;
通过for循环,根据SPS的数目,读取每个SPS对应的sequenceParameterSetLength和sequenceParameterSetNALUnit:
cpp
for (i = 0; i < cnt; i++) {
nalsize = AV_RB16(p) + 2;
if (nalsize > size - (p - data))
return AVERROR_INVALIDDATA;
ret = decode_extradata_ps_mp4(p, nalsize, ps, err_recognition, logctx);
if (ret < 0) {
av_log(logctx, AV_LOG_ERROR,
"Decoding sps %d from avcC failed\n", i);
return ret;
}
p += nalsize;
}
上述代码块中,首先通过AV_RB16宏定义读取AVCDecoderConfigurationRecord中的sequenceParameterSetLength,把该值加2赋值给局部变量nalsize。所以nalsize的值为该SPS的长度加上2字节,也就是包含NALU Header的该SPS的实际NALU数据的长度 + 存贮sequenceParameterSetLength所需的空间,以字节为单位。关于AV_RB16宏定义的用法可以参考:《FFmpeg源码:AV_RB32、AV_RB16、AV_RB8宏定义分析》:
cpp
nalsize = AV_RB16(p) + 2;
decode_extradata_ps_mp4函数内部调用了decode_extradata_ps函数:
cpp
static int decode_extradata_ps_mp4(const uint8_t *buf, int buf_size, H264ParamSets *ps,
int err_recognition, void *logctx)
{
int ret;
ret = decode_extradata_ps(buf, buf_size, ps, 1, logctx);
//...
}
而decode_extradata_ps函数内部,首先通过ff_h2645_packet_split函数将一个个NALU的NALU Header、EBSP、RBSP和SODB从avcC包装的H.264码流中提取出来,然后通过ff_h264_decode_seq_parameter_set函数解码SPS,把SPS中的每个属性提取出来。关于ff_h2645_packet_split函数的用法可以参考:《音视频入门基础:H.264专题(6)------FFmpeg源码:从H.264码流中提取NALU Header、EBSP、RBSP和SODB》,关于ff_h264_decode_seq_parameter_set函数的用法可以参考:《音视频入门基础:H.264专题(10)------FFmpeg源码中,存放SPS属性的结构体和解码SPS的函数分析》:
cpp
static int decode_extradata_ps(const uint8_t *data, int size, H264ParamSets *ps,
int is_avc, void *logctx)
{
H2645Packet pkt = { 0 };
int i, ret = 0;
ret = ff_h2645_packet_split(&pkt, data, size, logctx, is_avc, 2, AV_CODEC_ID_H264, 1, 0);
if (ret < 0) {
ret = 0;
goto fail;
}
for (i = 0; i < pkt.nb_nals; i++) {
H2645NAL *nal = &pkt.nals[i];
switch (nal->type) {
case H264_NAL_SPS: {
GetBitContext tmp_gb = nal->gb;
ret = ff_h264_decode_seq_parameter_set(&tmp_gb, logctx, ps, 0);
if (ret >= 0)
break;
av_log(logctx, AV_LOG_DEBUG,
"SPS decoding failure, trying again with the complete NAL\n");
init_get_bits8(&tmp_gb, nal->raw_data + 1, nal->raw_size - 1);
ret = ff_h264_decode_seq_parameter_set(&tmp_gb, logctx, ps, 0);
if (ret >= 0)
break;
ret = ff_h264_decode_seq_parameter_set(&nal->gb, logctx, ps, 1);
if (ret < 0)
goto fail;
break;
}
case H264_NAL_PPS:
ret = ff_h264_decode_picture_parameter_set(&nal->gb, logctx, ps,
nal->size_bits);
if (ret < 0)
goto fail;
break;
default:
av_log(logctx, AV_LOG_VERBOSE, "Ignoring NAL type %d in extradata\n",
nal->type);
break;
}
}
fail:
ff_h2645_packet_uninit(&pkt);
return ret;
}
我们回到ff_h264_decode_extradata函数中,代码继续往下执行,通过for循环,根据PPS的数目,读取每个PPS对应的pictureParameterSetLength和pictureParameterSetNALUnit:
cpp
// Decode pps from avcC
cnt = *(p++); // Number of pps
for (i = 0; i < cnt; i++) {
nalsize = AV_RB16(p) + 2;
if (nalsize > size - (p - data))
return AVERROR_INVALIDDATA;
ret = decode_extradata_ps_mp4(p, nalsize, ps, err_recognition, logctx);
if (ret < 0) {
av_log(logctx, AV_LOG_ERROR,
"Decoding pps %d from avcC failed\n", i);
return ret;
}
p += nalsize;
}
// Store right nal length size that will be used to parse all other nals
获取AVCDecoderConfigurationRecord中的lengthSizeMinusOne属性,把该值 + 1赋值给*nal_length_size。所以*nal_length_size的值为存贮NALUnitLength所需的空间,单位为字节:
cpp
// Store right nal length size that will be used to parse all other nals
*nal_length_size = (data[4] & 0x03) + 1;
解码AVCDecoderConfigurationRecord成功并且该码流为H.264,返回形参size的值:
cpp
return size;
五、总结
1.FFmpeg源码中,通过ff_h264_decode_extradata函数来解码avcC包装的H.264码流中的AVCDecoderConfigurationRecord。
2.由于FFmpeg源码中,解码AVCDecoderConfigurationRecord的函数的名称为"ff_h264_decode_extradata"。所以很多人会把AVCDecoderConfigurationRecord称作"extradata"。AVCDecoderConfigurationRecord是ISO/IEC提供的H.264官方文档《H.264-AVC-ISO_IEC_14496-15》中的官方说法,而"extradata"是FFmpeg源码对AVCDecoderConfigurationRecord的称呼。