前言
包含00 00 00 01 或 00 00 01分隔符的H264封装称之为AnnexB,00 00 00 01 或 00 00 01代表了H264 NAL单元的开始,同时也表示上一个NAL单元的结束,在AnnexB中,SPS,PPS信息与普通的数据NAL单元一样传输。在另外一种H264的封装格式avcC中,它的SPS,PPS一般被包含在extradata数据段,详见ISO/IEC 14496-15 Advanced Video Coding的AVC decoder configuration record一节。avcC中无NAL单元的起始结束符,但是包含了NAL单元的长度信息。所以,也可以完整分开NAL单元。MP4,MKV封装的H264不包含SPS,PPS,也可能不带有00 00 00 01 或 00 00 01分隔符,解码器解码需要SPS,PPS信息,有的也需要00 00 00 01 或 00 00 01,bitstream_filter的功能就是将这些信息给加上去。通常AVI封装的H264会使用AnnexB。
API详解
/* 头文件中说此函数已经过时,建议调用使用AVBSFContext的 av_bsf_send_packet()和av_bsf_receive_packet() ,
* 在以前老版本的FFMPEG上,函数的返回值大于等于0表示成功,负数表示失败。
* 在4.2.2此函数返回值1表示返回成功;0表示try again或者eof错误,返回0通常是由内存分配失败引起的;返回-1表示失败
* API具有二义性,有可能是目前不推荐使用的原因。
*/
int av_bitstream_filter_filter(AVBitStreamFilterContext *bsfc,
AVCodecContext *avctx, const char *args,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size, int keyframe)
{
……
/*
* 1. priv本身可以在读取文件header,可以判断codec类型时初始化,
如H264可以调用av_bitstream_filter_init("h264_mp4toannexb")
H265可以调用av_bitstream_filter_init("hevc_mp4toannexb")
如今推荐使用av_bsf_get_by_name(), av_bsf_alloc(), and av_bsf_init()来完成初始化。
* 2.如果ctx不存在,则用av_bsf_alloc函数来分配,并且将filter传给ctx,bsf就是bitstream_filter的缩写
* 3.调用av_bsf_init来初始化。
*/
if (!priv->ctx) {
/*
* 1. 为AVBSFContext类型为AVCodecParameters结构体的para_in,par_out参数分配内存并且初始化。
* 2. 为AVBSFContext类型为AVBSFInternal结构体的internal参数分配内存。
* 3. 为internal参数调用av_packet_alloc()分配buffer_pkt,此时未创建AVBufferRef和AVBuffer。
* 4. 设置默认参数
* 5. 为priv_data分配内存并且设置默认参数
*/
ret = av_bsf_alloc(bsfc->filter, &priv->ctx);
/* 根据AVCodecContext配置para_in */
ret = avcodec_parameters_from_context(priv->ctx->par_in, avctx);
……
/*
* 1. 判断filter是否支持目前的codec。
* 2. 将para_out的参数初始化成和para_in一样,para_in的extradata也复制到了para_out中。
* 3. 进入到具体的filter去完成初始化,比如进入ff_h264_mp4toannexb_bsf 这个filter中
*/
ret = av_bsf_init(priv->ctx);
}
pkt.data = (uint8_t *)buf;
pkt.size = buf_size;
/*
*1. 让pkt具有引用计数功能,在此之前pkt还不具有引用计数功能,只是将data指向了入参的buf。
*2. 要求ctx->internal->buffer_pkt->data必须为空并且不存在ctx->internal->buffer_pkt->side_data_elems
*3. 重新用packet_alloc调用av_buffer_realloc给pkt分配了内存,
av_buffer_realloc和packet_alloc都没有将原来的buf数据复制到新内存data中。在av_packet_make_refcounted中进行了复制。
*4. 通过av_packet_move_ref,让ctx->internal->buffer_pkt引用pkt的资源。注意:此时pkt的AVBuffer引用实际上只有一个。
*/
ret = av_bsf_send_packet(priv->ctx, &pkt);
……
/* 1. 应该使用ff_bsf_get_packet来将ctx->internal->buffer_pkt指向一个空白的pkt.
* 2. 将用实际的filter来parse流,对应MP4的H264就是h264_mp4toannexb_filter这个函数,见h264_mp4toannexb_bsf.c文件
*/
ret = av_bsf_receive_packet(priv->ctx, &pkt);
/* 出错时,已经调用av_packet_unref释放过了pkt */
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return 0;
else if (ret < 0)
return ret;
/* 释放掉av_bsf_send_packet分配的pkt的AVBuffer */
av_packet_unref(&pkt);
/* drain all the remaining packets we cannot return */
while (ret >= 0) {
ret = av_bsf_receive_packet(priv->ctx, &pkt);
av_packet_unref(&pkt);
}
if (!priv->extradata_updated) {
/* update extradata in avctx from the output codec parameters */
if (priv->ctx->par_out->extradata_size && (!args || !strstr(args, "private_spspps_buf"))) {
av_freep(&avctx->extradata);
avctx->extradata_size = 0;
avctx->extradata = av_mallocz(priv->ctx->par_out->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
if (!avctx->extradata)
return AVERROR(ENOMEM);
memcpy(avctx->extradata, priv->ctx->par_out->extradata, priv->ctx->par_out->extradata_size);
avctx->extradata_size = priv->ctx->par_out->extradata_size;
}
priv->extradata_updated = 1;
}
return 1;
}
static int h264_extradata_to_annexb(AVBSFContext *ctx, const int padding)
{
H264BSFContext *s = ctx->priv_data;
uint16_t unit_size;
uint64_t total_size = 0;
uint8_t *out = NULL, unit_nb, sps_done = 0,
sps_seen = 0, pps_seen = 0;
/* 后面附图解释这个4是什么 */
const uint8_t *extradata = ctx->par_in->extradata + 4;
static const uint8_t nalu_header[4] = { 0, 0, 0, 1 };
int length_size = (*extradata++ & 0x3) + 1; // retrieve length coded size
s->sps_offset = s->pps_offset = -1;
/* retrieve sps and pps unit(s) */
unit_nb = *extradata++ & 0x1f; /* number of sps unit(s) */
if (!unit_nb) {
goto pps;
} else {
s->sps_offset = 0;
sps_seen = 1;
}
while (unit_nb--) {
int err;
unit_size = AV_RB16(extradata);
total_size += unit_size + 4;
if (total_size > INT_MAX - padding) {
av_log(ctx, AV_LOG_ERROR,
"Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n");
av_free(out);
return AVERROR(EINVAL);
}
if (extradata + 2 + unit_size > ctx->par_in->extradata + ctx->par_in->extradata_size) {
av_log(ctx, AV_LOG_ERROR, "Packet header is not contained in global extradata, "
"corrupted stream or invalid MP4/AVCC bitstream\n");
av_free(out);
return AVERROR(EINVAL);
}
if ((err = av_reallocp(&out, total_size + padding)) < 0)
return err;
memcpy(out + total_size - unit_size - 4, nalu_header, 4);
memcpy(out + total_size - unit_size, extradata + 2, unit_size);
extradata += 2 + unit_size;
pps:
if (!unit_nb && !sps_done++) {
unit_nb = *extradata++; /* number of pps unit(s) */
if (unit_nb) {
s->pps_offset = total_size;
pps_seen = 1;
}
}
}
if (out)
memset(out + total_size, 0, padding);
if (!sps_seen)
av_log(ctx, AV_LOG_WARNING,
"Warning: SPS NALU missing or invalid. "
"The resulting stream may not play.\n");
if (!pps_seen)
av_log(ctx, AV_LOG_WARNING,
"Warning: PPS NALU missing or invalid. "
"The resulting stream may not play.\n");
av_freep(&ctx->par_out->extradata);
ctx->par_out->extradata = out;
ctx->par_out->extradata_size = total_size;
return length_size;
}
/* 函数返回0表示成功,返回其他值表示失败 */
static int h264_mp4toannexb_filter(AVBSFContext *ctx, AVPacket *out)
{
H264BSFContext *s = ctx->priv_data;
……
/* 取出pkt并将ctx->internal->buffer_pkt指向一个空白的pkt */
ret = ff_bsf_get_packet(ctx, &in);
if (ret < 0)
return ret;
/* nothing to filter */
if (!s->extradata_parsed) {
av_packet_move_ref(out, in);
av_packet_free(&in);
return 0;
}
……
do {
ret= AVERROR(EINVAL);
if (buf + s->length_size > buf_end)
goto fail;
/* 如buf中数据为00 00 00 0b 06 00 07 81时,nal_size就是11,如buf中数据为00 00 00 15 06 05 11 03,
* nal_size就是21,s->length_size是将nal_size的字节数。从nal_size类型看,s->length_size应该小于等于4
*/
for (nal_size = 0, i = 0; i<s->length_size; i++)
nal_size = (nal_size << 8) | buf[i];
/* 这儿有一个问题,如果out中已经有了很多NAL,是否要goto fail的问题,我认为还是应该fail,因为这些NAL可能不完整的DATA SLICE,会花屏,在卡一下和花屏中选择,一般会选择卡一下。 */
if (nal_size > buf_end - buf || nal_size < 0)
goto fail;
if (unit_type == H264_NAL_SPS)
s->idr_sps_seen = s->new_idr = 1;
else if (unit_type == H264_NAL_PPS) {
s->idr_pps_seen = s->new_idr = 1;
/* if SPS has not been seen yet, prepend the AVCC one to PPS */
if (!s->idr_sps_seen) {
if (s->sps_offset == -1)
av_log(ctx, AV_LOG_WARNING, "SPS not present in the stream, nor in AVCC, stream may be unreadable\n");
else {
if ((ret = alloc_and_copy(out,
ctx->par_out->extradata + s->sps_offset,
s->pps_offset != -1 ? s->pps_offset : ctx->par_out->extradata_size - s->sps_offset,
buf, nal_size, 1)) < 0)
goto fail;
s->idr_sps_seen = 1;
goto next_nal;
}
}
}
/* if this is a new IDR picture following an IDR picture, reset the idr flag.
* Just check first_mb_in_slice to be 0 as this is the simplest solution.
* This could be checking idr_pic_id instead, but would complexify the parsing. */
if (!s->new_idr && unit_type == H264_NAL_IDR_SLICE && (buf[1] & 0x80))
s->new_idr = 1;
/* prepend only to the first type 5 NAL unit of an IDR picture, if no sps/pps are already present */
if (s->new_idr && unit_type == H264_NAL_IDR_SLICE && !s->idr_sps_seen && !s->idr_pps_seen) {
if ((ret=alloc_and_copy(out,
ctx->par_out->extradata, ctx->par_out->extradata_size,
buf, nal_size, 1)) < 0)
goto fail;
s->new_idr = 0;
/* if only SPS has been seen, also insert PPS */
} else if (s->new_idr && unit_type == H264_NAL_IDR_SLICE && s->idr_sps_seen && !s->idr_pps_seen) {
if (s->pps_offset == -1) {
av_log(ctx, AV_LOG_WARNING, "PPS not present in the stream, nor in AVCC, stream may be unreadable\n");
if ((ret = alloc_and_copy(out, NULL, 0, buf, nal_size, 0)) < 0)
goto fail;
} else if ((ret = alloc_and_copy(out,
ctx->par_out->extradata + s->pps_offset, ctx->par_out->extradata_size - s->pps_offset,
buf, nal_size, 1)) < 0)
goto fail;
} else {
if ((ret=alloc_and_copy(out, NULL, 0, buf, nal_size, unit_type == H264_NAL_SPS || unit_type == H264_NAL_PPS)) < 0)
goto fail;
if (!s->new_idr && unit_type == H264_NAL_SLICE) {
s->new_idr = 1;
s->idr_sps_seen = 0;
s->idr_pps_seen = 0;
}
}
next_nal:
buf += nal_size;
cumul_size += nal_size + s->length_size;
} while (cumul_size < buf_size);
ret = av_packet_copy_props(out, in);
if (ret < 0)
goto fail;
fail:
if (ret < 0)
av_packet_unref(out);
av_packet_free(&in);
return ret;
}
ctx->par_in->extradata + 4跳过的字节如下图所示。
跳过的字节就是红色框中的内容。此时length_size为4,unit_nb是1。
分析自BHSJ.mp4
00 00 00 15 06 05 11 03
写了很久的文章,一直没空整理发出来。。。