如何用FFmpeg libavformat API封装已编码x264帧为MP4容器
解决思路:跳过编码步骤,直接封装已编码的H264帧
你现在走了个不必要的弯路——因为已经用x264完成了帧编码,完全不需要再初始化FFmpeg的H264编码器。我们只需要把已编码帧的元数据告诉libavformat,然后把x264输出的NALU(网络抽象层单元)直接封装到MP4容器里就行。下面是具体的修正方案:
核心步骤拆解
- 初始化输出上下文和流(和你之前的逻辑类似,但跳过编码器初始化)
- 配置AVStream的CodecParameters:这是解决你所有错误的关键,必须同步x264编码的核心参数
- 打开输出文件并写入文件头
- 将x264的NALU封装为FFmpeg的AVPacket,写入容器
- 写入文件尾并清理资源
修正后的代码示例
1. 初始化输出上下文和流
AVFormatContext* mAVFormatContext = NULL; int ret = avformat_alloc_output_context2(&mAVFormatContext, NULL, NULL, "gen_vid.mp4"); if (ret < 0) { fprintf(stderr, "Failed to create output context\n"); return ret; } AVStream* mAVStream = avformat_new_stream(mAVFormatContext, NULL); if (!mAVStream) { fprintf(stderr, "Failed to allocate output stream\n"); return AVERROR(ENOMEM); }
2. 配置CodecParameters(重中之重)
你需要从x264编码器的参数中获取这些值,或者手动填入你编码时设置的参数:
AVCodecParameters* codecpar = mAVStream->codecpar; // 必须设置的核心参数 codecpar->codec_id = AV_CODEC_ID_H264; codecpar->codec_type = AVMEDIA_TYPE_VIDEO; codecpar->width = width; // 和x264编码时一致的视频宽度 codecpar->height = height; // 和x264编码时一致的视频高度 codecpar->format = AV_PIX_FMT_YUV420P; // x264默认输出的像素格式,根据你的实际配置调整 // 设置时间基:和x264编码时的时间基保持一致 mAVStream->time_base = (AVRational){1, 30}; // 假设你的帧率是30fps codecpar->time_base = mAVStream->time_base; // 导入x264的SPS/PPS作为extradata(MP4容器必须依赖这个才能解码) x264_param_t* x264_params = x264_encoder_parameters(h); // h是你的x264编码器句柄 uint8_t* extradata = NULL; int extradata_size = x264_encoder_headers(h, &extradata, NULL); if (extradata_size > 0) { codecpar->extradata = av_mallocz(extradata_size + AV_INPUT_BUFFER_PADDING_SIZE); memcpy(codecpar->extradata, extradata, extradata_size); codecpar->extradata_size = extradata_size; x264_free(extradata); // 释放x264分配的内存 }
3. 打开文件并写入头
if (!(mAVFormatContext->oformat->flags & AVFMT_NOFILE)) { ret = avio_open(&mAVFormatContext->pb, "gen_vid.mp4", AVIO_FLAG_WRITE); if (ret < 0) { fprintf(stderr, "Could not open output file\n"); return ret; } } // 写入文件头 ret = avformat_write_header(mAVFormatContext, NULL); if (ret < 0) { fprintf(stderr, "Failed to write header: %s\n", av_err2str(ret)); return ret; }
4. 处理x264的已编码帧(替换你原来的fwrite逻辑)
现在需要把x264输出的NALU封装成FFmpeg的AVPacket,再写入容器:
int delayed_frame_counter = 0; x264_picture_t pic_out; x264_nal_t* nal; int i_nal; while (x264_encoder_delayed_frames(h)) { printf("Writing delayed frame %u\n", delayed_frame_counter++); int i_frame_size = x264_encoder_encode(h, &nal, &i_nal, NULL, &pic_out); if (i_frame_size < 0) { fprintf(stderr, "Failed to encode delayed frame\n"); return i_frame_size; } else if (i_frame_size > 0) { // 遍历所有NALU单元 for (int i = 0; i < i_nal; i++) { AVPacket pkt = {0}; av_init_packet(&pkt); // 设置数据包内容 pkt.data = nal[i].p_payload; pkt.size = nal[i].i_payload; pkt.stream_index = mAVStream->index; // 转换时间戳:从x264的时间基转换为FFmpeg容器的时间基 pkt.pts = av_rescale_q(pic_out.i_pts, mAVStream->time_base, mAVFormatContext->streams[0]->time_base); pkt.dts = pkt.pts; // 如果没有B帧,dts和pts一致;有B帧的话需要从x264获取对应dts pkt.duration = av_rescale_q(1, mAVStream->time_base, mAVFormatContext->streams[0]->time_base); // 单帧时长 // 写入数据包 ret = av_interleaved_write_frame(mAVFormatContext, &pkt); if (ret < 0) { fprintf(stderr, "Failed to write frame: %s\n", av_err2str(ret)); return ret; } av_packet_unref(&pkt); } } }
5. 写入文件尾并清理资源
// 写入文件尾 av_write_trailer(mAVFormatContext); // 释放资源 if (!(mAVFormatContext->oformat->flags & AVFMT_NOFILE)) { avio_close(mAVFormatContext->pb); } avformat_free_context(mAVFormatContext);
对你之前错误的解释
- "Could not find a valid device"错误:你调用了
avcodec_open2,FFmpeg默认选择了v4l2m2m硬件编码器,但你的环境没有对应设备——这完全没必要,因为你不需要重新编码 - "dimensions not set"错误:你没有给
codecpar设置width和height,容器不知道视频的分辨率 - "Could not find tag for codec none"错误:
codecpar的codec_id默认是AV_CODEC_ID_NONE,必须手动设置为AV_CODEC_ID_H264
内容的提问来源于stack exchange,提问作者PJD
相关产品推荐
相关产品推荐

