You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 15:19:57