FFmpeg 生成mp4时 Annexb转Avcc失败

今天写代码的时候发现,如果生成的文件是mp4, 编码是h264, FFmpeg没有自动把Annexb格式转换成为Avcc 格式,比较奇怪,用命令的话是可以的。
于是就开始找FFmpeg是怎么把Annexb格式转换成为Avcc的。 找了半天BSF,发现只有从Avcc到Annexb的转换,但是没有Annexb到Avcc的转换。
Debug代码后发现FFmpeg命令行写AVPacket的代码如下


//** Function ff_mov_write_packet, File movenc.c **//

 if (par->codec_id == AV_CODEC_ID_H264 && trk->vos_len > 0 && *(uint8_t *)trk->vos_data != 1 && !TAG_IS_AVCI(trk->tag)) {
        /* from x264 or from bytestream H.264 */
        /* NAL reformatting needed */
        if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) {
            ret = ff_avc_parse_nal_units_buf(pkt->data, &reformatted_data,
                                             &size);
            if (ret < 0)
                return ret;
            avio_write(pb, reformatted_data, size);
        } else {
            if (trk->cenc.aes_ctr) {
                size = ff_mov_cenc_avc_parse_nal_units(&trk->cenc, pb, pkt->data, size);
                if (size < 0) {
                    ret = size;
                    goto err;
                }
            } else {
                size = ff_avc_parse_nal_units(pb, pkt->data, pkt->size);
            }
        }

ff_avc_parse_nal_units

int ff_avc_parse_nal_units(AVIOContext *pb, const uint8_t *buf_in, int size)
{
    const uint8_t *p = buf_in;
    const uint8_t *end = p + size;
    const uint8_t *nal_start, *nal_end;

    size = 0;
    nal_start = ff_avc_find_startcode(p, end);
    for (;;) {
        while (nal_start < end && !*(nal_start++));
        if (nal_start == end)
            break;

        nal_end = ff_avc_find_startcode(nal_start, end);
        //这里写入的时候,才转换
        avio_wb32(pb, nal_end - nal_start); //size
        avio_write(pb, nal_start, nal_end - nal_start); //data
        size += 4 + nal_end - nal_start;
        nal_start = nal_end;
    }
    return size;
}

所以实际上Annexb 到 Avcc是到最后自动写的,也就是说是在调用av_interleaved_write_frame方法时候,AVPacket会给到ff_mov_write_packet方法,然后在ff_mov_write_packet中自动解析AVPacket,然后写成Avcc格式的。

再看一下为什么我自己写的程序不行。
我自己的写的程序,创建AVStream的时候,是这样创建的

 for (int i = 0; i < inputFile->ctx->nb_streams; i++) {
        AVStream *in_stream = inputFile->ctx->streams[i];
        AVStream *out_stream = avformat_new_stream(outputFile->ctx, NULL);
        if (!out_stream) {
            av_log(NULL, AV_LOG_ERROR, "<initOutputFile> Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            return ret;
        }
        //主要是这里,这里是直接调用avcodec_parameters_copy方法
        ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "<initOutputFile> Failed to copy context from input to output stream codec context\n");
            return ret;
        }
        out_stream->codecpar->codec_tag = 0;
        if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            outputFile->frame_interval = (int64_t) (in_stream->time_base.den / (double) (in_stream->time_base.num * DEFINE_FIX_FPS));
            outputFile->video_index = i;
        } else if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
            outputFile->audio_index = i;
    }

avcodec_parameters_copy会把输入文件的 extradataextradata_size都复制过来。
然后在调用avformat_write_header 的时候,会把AVStream中的extradata copy到 track->vos_data中。


//** Function mov_write_header, File movenc.c **//

 for (i = 0; i < s->nb_streams; i++) {
        int j;
        AVStream *st= s->streams[i];
        MOVTrack *track= &mov->tracks[i];

        /* copy extradata if it exists */
        if (st->codecpar->extradata_size) {
            if (st->codecpar->codec_id == AV_CODEC_ID_DVD_SUBTITLE)
                mov_create_dvd_sub_decoder_specific_info(track, st);
            else if (!TAG_IS_AVCI(track->tag) && st->codecpar->codec_id != AV_CODEC_ID_DNXHD) {
                track->vos_len  = st->codecpar->extradata_size;
                track->vos_data = av_malloc(track->vos_len + AV_INPUT_BUFFER_PADDING_SIZE);
                if (!track->vos_data) {
                    return AVERROR(ENOMEM);
                }
                //这里进行copy
                memcpy(track->vos_data, st->codecpar->extradata, track->vos_len);
                memset(track->vos_data + track->vos_len, 0, AV_INPUT_BUFFER_PADDING_SIZE);
            }
        }
        ······
}

最后在回到 ff_mov_write_packet 方法中,发现自动转换是有前提的,判断条件是

par->codec_id == AV_CODEC_ID_H264 && trk->vos_len > 0 && *(uint8_t *)trk->vos_data != 1 && !TAG_IS_AVCI(trk->tag)

而我用代码方式写的时候,copy的*(uint8_t *)trk->vos_data 为1,就导致不会自动转换,写入的时候就是Annexb。也就是说,影响输出文件中的流格式到底是Annexb还是Avcc的是输出文件是视频流AVStream中的extradata
然后AVCC情况下的extradata格式如下:

1字节:version (通常0x01)2字节:avc profile (值同第1个sps的第2字节)3字节:avc compatibility (值同第1个sps的第3字节)4字节:avc level (值同第1个sps的第3字节)5字节前6位:保留全15字节后2位:NALU Length 字段大小减1,通常这个值为3,即NAL码流中使用3+1=4字节表示NALU的长度
第6字节前3位:保留,全16字节后5位:SPS NALU的个数,通常为17字节开始后接1个或者多个SPS数据
 SPS结构 [16位 SPS长度][SPS NALU data]

SPS数据后
第1字节:PPS的个数,通常为12字节开始接1个或多个PPS数据
 PPS结构 [16位 PPS长度][SPS NALU data]
// 来源 https://cloud.tencent.com/developer/article/2145065

下面就是修改后的代码

   extradata_size = sps_size + pps_size + 11;
   extradata = av_malloc(extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
   extradata[0] = 0x01; //version
   extradata[1] = sps[1]; // profile
   extradata[2] = sps[2]; // profile compat
   extradata[3] = sps[3]; // level
   extradata[4] = 0xFC | (4 - 1); // reserved(6) + length size minus one(2)
   extradata[5] = 0xE0 | 1; // 1 个sps
   extradata[6] = sps_size >> 8; // sps size high
   extradata[7] = sps_size & 0xFF; // sps size low
   memcpy(extradata + 8, sps, sps_size); // sps
   extradata[8 + sps_size]  = 0x01; // number of PPS
   extradata[9 + sps_size]  = pps_size >> 8; // pps size high
   extradata[10 + sps_size] = pps_size & 0xFF; //pps size low
   memcpy(extradata + 11 + sps_size, pps, pps_size); //pps
   av_freep(&encodec_ctx->extradata);
   encodec_ctx->extradata = extradata;
   encodec_ctx->extradata_size = extradata_size;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值