今天写代码的时候发现,如果生成的文件是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
会把输入文件的 extradata
和extradata_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位:保留全1
第5字节后2位:NALU Length 字段大小减1,通常这个值为3,即NAL码流中使用3+1=4字节表示NALU的长度
第6字节前3位:保留,全1
第6字节后5位:SPS NALU的个数,通常为1
第7字节开始后接1个或者多个SPS数据
SPS结构 [16位 SPS长度][SPS NALU data]
SPS数据后
第1字节:PPS的个数,通常为1
第2字节开始接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;