一、从编码后的 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);
}