如何用libav将内存中已编码H264帧封装为MP4容器?
问题描述
我正在开发一款C++应用,从相机获取帧后使用非libav的H264编码器编码,编码后的H264帧以void* mem形式存储在内存中。需要借助libavcodec/libavformat将这些帧直接存入MP4容器,无需转码。优先希望每推送一帧就能获取封装后的数据作为函数返回值,若无法实现,直接写入文件也可。目前已完成部分代码但遇到瓶颈,请问后续该如何操作?是否有合适的参考资源?
/* some private fields accessible in MP4Muxer: int frameWidth_, frameHeight_, frameRate_, srcBitRate_; */ void MP4Muxer::muxFrame(void *mem, size_t len, int64_t timestamp, bool keyFrame) { const AVOutputFormat* outputFormat = av_guess_format("mp4", NULL, NULL); AVFormatContext* outputFormatContext = avformat_alloc_context(); outputFormatContext->oformat = outputFormat; AVStream* videoStream = avformat_new_stream(outputFormatContext, NULL); videoStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; videoStream->codecpar->codec_id = AV_CODEC_ID_H264; videoStream->codecpar->width = frameWidth_; videoStream->codecpar->height = frameHeight_; videoStream->avg_frame_rate = (AVRational) {frameRate_, 1}; videoStream->time_base = (AVRational) {1, 90000}; }
注:找到的在线资源多为直接读写文件/流的场景,难以适配需求。
后续操作步骤
1. 重构初始化逻辑,避免重复创建上下文
当前代码在muxFrame中每次都创建AVFormatContext和AVStream,会导致重复初始化资源,必须将初始化逻辑移到类的构造函数或独立init方法中,仅执行一次:
// 新增类私有成员:AVFormatContext* outputFormatContext_; AVStream* videoStream_; bool MP4Muxer::init() { const AVOutputFormat* outputFormat = av_guess_format("mp4", NULL, NULL); if (!outputFormat) { // 错误处理:比如打印日志或返回错误码 return false; } outputFormatContext_ = avformat_alloc_context(); if (!outputFormatContext_) { return false; } outputFormatContext_->oformat = outputFormat; videoStream_ = avformat_new_stream(outputFormatContext_, NULL); if (!videoStream_) { avformat_free_context(outputFormatContext_); outputFormatContext_ = nullptr; return false; } // 填充流参数 videoStream_->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; videoStream_->codecpar->codec_id = AV_CODEC_ID_H264; videoStream_->codecpar->width = frameWidth_; videoStream_->codecpar->height = frameHeight_; videoStream_->avg_frame_rate = (AVRational){frameRate_, 1}; videoStream_->time_base = (AVRational){1, 90000}; videoStream_->codecpar->bit_rate = srcBitRate_; // 如果选择写入文件,打开IO并写入头 if (avio_open(&outputFormatContext_->pb, "output.mp4", AVIO_FLAG_WRITE) < 0) { avformat_free_context(outputFormatContext_); outputFormatContext_ = nullptr; videoStream_ = nullptr; return false; } if (avformat_write_header(outputFormatContext_, NULL) < 0) { avio_close(outputFormatContext_->pb); avformat_free_context(outputFormatContext_); outputFormatContext_ = nullptr; videoStream_ = nullptr; return false; } return true; }
2. 封装单帧H264数据到MP4
在muxFrame中,将内存中的H264数据封装为AVPacket,再写入容器:
void MP4Muxer::muxFrame(void *mem, size_t len, int64_t timestamp, bool keyFrame) { if (!outputFormatContext_ || !videoStream_) { return; // 未初始化,直接返回 } AVPacket pkt = {0}; av_init_packet(&pkt); // 分配缓冲区并拷贝H264数据 pkt.data = (uint8_t*)av_malloc(len); if (!pkt.data) { av_packet_unref(&pkt); return; } memcpy(pkt.data, mem, len); pkt.size = len; // 转换时间戳到流的time_base pkt.pts = av_rescale_q(timestamp, (AVRational){1, 90000}, videoStream_->time_base); pkt.dts = pkt.pts; // 无B帧时dts与pts一致 pkt.stream_index = videoStream_->index; // 标记关键帧 if (keyFrame) { pkt.flags |= AV_PKT_FLAG_KEY; } // 写入数据包 if (av_interleaved_write_frame(outputFormatContext_, &pkt) < 0) { // 错误处理:比如记录日志 } av_packet_unref(&pkt); }
3. 实现内存输出(优先需求)
若要获取每帧封装后的MP4数据而非写入文件,需自定义AVIOContext实现内存IO:
// 自定义内存IO回调 static int read_packet(void *opaque, uint8_t *buf, int buf_size) { return 0; // 无需读操作 } static int write_packet(void *opaque, uint8_t *buf, int buf_size) { std::vector<uint8_t>* output_buf = static_cast<std::vector<uint8_t>*>(opaque); output_buf->insert(output_buf->end(), buf, buf + buf_size); return buf_size; } static int64_t seek(void *opaque, int64_t offset, int whence) { std::vector<uint8_t>* output_buf = static_cast<std::vector<uint8_t>*>(opaque); switch(whence) { case SEEK_SET: if (offset >= 0 && offset <= static_cast<int64_t>(output_buf->size())) { return offset; } break; case AVSEEK_SIZE: return output_buf->size(); } return -1; } // 内存输出版初始化 bool MP4Muxer::init(std::vector<uint8_t>& output_buf) { // ... 复用前面的流参数初始化代码 ... // 创建内存IO上下文 uint8_t* io_buf = static_cast<uint8_t*>(av_malloc(4096)); if (!io_buf) { return false; } AVIOContext* avio_ctx = avio_alloc_context(io_buf, 4096, 1, &output_buf, read_packet, write_packet, seek); if (!avio_ctx) { av_free(io_buf); return false; } outputFormatContext_->pb = avio_ctx; if (avformat_write_header(outputFormatContext_, NULL) < 0) { avio_context_free(&avio_ctx); avformat_free_context(outputFormatContext_); return false; } return true; }
每次调用muxFrame后,output_buf会积累封装后的MP4数据。注意MP4需要最终调用av_write_trailer更新文件头(包含文件大小等信息)。
4. 资源释放
在类析构函数中清理资源:
MP4Muxer::~MP4Muxer() { if (outputFormatContext_) { av_write_trailer(outputFormatContext_); if (outputFormatContext_->pb) { avio_context_free(&outputFormatContext_->pb); } avformat_free_context(outputFormatContext_); } }
参考资源
- FFmpeg官方
libavformat文档,重点掌握AVFormatContext、AVStream、AVIOContext的API用法 - FFmpeg官方示例
muxing.c,核心封装流程与本场景一致,可参考帧处理逻辑 - 研究
av_interleaved_write_frame、av_write_trailer的参数约束与错误处理方式
内容的提问来源于stack exchange,提问作者ImJustACowLol
相关产品推荐
相关产品推荐

