文档目录

一、从编码后的 packet 写出文件/推流的完整流程

struct VideoEncoder {
    AVFormatContext* fmt_ctx = nullptr;
    AVCodecContext*  enc_ctx = nullptr;
    AVStream*        stream = nullptr;
};

bool setup_encoder(VideoEncoder& enc, const char* output_url,
                    int width, int height, int fps) {
    // ① 根据输出 URL 确定容器格式(.mp4 → mp4, .flv → flv, rtmp:// → flv)
    avformat_alloc_output_context2(&enc.fmt_ctx, nullptr, nullptr, output_url);
    if (!enc.fmt_ctx) return false;

    // ② 创建视频流
    enc.stream = avformat_new_stream(enc.fmt_ctx, nullptr);
    enc.stream->time_base = {1, fps};

    // ③ 配置编码器(H.264)
    const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    enc.enc_ctx = avcodec_alloc_context3(codec);
    enc.enc_ctx->width = width;
    enc.enc_ctx->height = height;
    enc.enc_ctx->time_base = enc.stream->time_base;
    enc.enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;

    // ④ 低延迟配置(直播场景)
    enc.enc_ctx->gop_size = 30;       // 每 30 帧一个关键帧
    enc.enc_ctx->max_b_frames = 0;    // 不要 B 帧(减少延迟)
    enc.enc_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;

    avcodec_open2(enc.enc_ctx, codec, nullptr);

    // ⑤ 编码器参数传递给流
    avcodec_parameters_from_context(enc.stream->codecpar, enc.enc_ctx);

    // ⑥ 打开输出文件
    avio_open(&enc.fmt_ctx->pb, output_url, AVIO_FLAG_WRITE);

    // ⑦ 写文件头(MP4 的 ftyp + moov 初始部分)
    avformat_write_header(enc.fmt_ctx, nullptr);
    return true;
}

// 编码并写出一帧
void encode_frame(VideoEncoder& enc, AVFrame* frame) {
    AVPacket* packet = av_packet_alloc();

    // ① 把原始帧送入编码器
    if (avcodec_send_frame(enc.enc_ctx, frame) == 0) {
        while (avcodec_receive_packet(enc.enc_ctx, packet) == 0) {
            // ② 设置时间戳
            av_packet_rescale_ts(packet, enc.enc_ctx->time_base,
                                  enc.stream->time_base);
            packet->stream_index = enc.stream->index;

            // ③ 写出 packet(内部做交错、写入容器的 mdat)
            av_interleaved_write_frame(enc.fmt_ctx, packet);
        }
    }
    av_packet_free(&packet);
}

// 编码结束后记得 flush + 写尾部
void flush_encoder(VideoEncoder& enc) {
    encode_frame(enc, nullptr);  // 送 nullptr 让编码器输出缓存的帧

    // 写文件尾(更新 moov 中的元数据)
    av_write_trailer(enc.fmt_ctx);

    avio_closep(&enc.fmt_ctx->pb);
    avformat_free_context(enc.fmt_ctx);
}