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

如何修改AVFormatContext的bit_rate以控制RTMP推流码率?

解决FFmpeg RTMP推流中修改码率无效的问题

问题背景

尝试通过修改AVFormatContext->bit_rate控制RTMP推流的视频码率,但设置后完全不生效。当前代码逻辑是直接读取本地FLV文件的编码数据包,转发到RTMP服务器,核心无效代码片段:

AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
int64_t bit_rate = 400*1000;
....
ofmt_ctx->bit_rate = bit_rate;

环境信息:

  • FFmpeg版本:ffmpeg version 4.4.2-0ubuntu0.22.04.1
  • 编译命令:
g++ -Wall -g -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/local/include -L/usr/local/lib  simplt_push_streaming.cpp -o test.out -lavformat -lavcodec -lavutil -lgobject-2.0 -lglib-2.0 -lpthread

无效原因

  1. 直接转发编码数据包,未重新编码:当前代码只是把输入文件中已编码好的AVPacket直接发送到RTMP服务器,数据包的码率是输入文件的原始码率,修改ofmt_ctx->bit_rate不会改变已编码数据的码率。
  2. 全局bit_rate设置时机错误:AVFormatContext->bit_rate是全局码率参数,仅在输出流初始化阶段参考,实际生效的是对应视频流AVStream->codec->bit_rate的设置,全局参数优先级低于流级参数。

解决方案

要实现码率控制,必须对输入视频帧进行解码,再用新的码率参数重新编码,核心步骤:

  • 为输入流打开解码器,解码出原始视频帧
  • 修改输出视频流的AVCodecContext->bit_rate为目标码率
  • 打开输出编码器,将解码后的原始帧编码为指定码率的数据包
  • 将编码后的数据包推流到RTMP服务器

修改后的完整代码

#include <stdio.h>
extern "C"
{
#include <libavformat/avformat.h>
#include <libavutil/mathematics.h>
#include <libavutil/time.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
};

int main(int argc, char* argv[])
{
    AVOutputFormat *ofmt = NULL;
    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    AVPacket pkt, enc_pkt;
    AVFrame *frame = NULL;
    const char *in_filename, *out_filename;
    int ret, i;
    int videoindex=-1, out_videoindex=-1;
    int frame_index=0;
    int64_t start_time=0;
    int64_t target_bitrate = 400*1000; // 目标码率400kbps
    in_filename  = "/home/zion/video/mecha.flv";
    out_filename = "rtmp://localhost:1935/live/test";

    av_register_all();
    avformat_network_init();
    avcodec_register_all();

    // 打开输入文件
    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
        fprintf(stderr, "Could not open input file\n");
        goto end;
    }
    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
        fprintf(stderr, "Failed to retrieve input stream information\n");
        goto end;
    }

    // 找到视频流索引
    for(i=0; i<ifmt_ctx->nb_streams; i++) {
        if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
            videoindex=i;
            break;
        }
    }
    if (videoindex == -1) {
        fprintf(stderr, "No video stream found in input file\n");
        goto end;
    }

    // 打开输入解码器
    AVCodecContext *in_codec_ctx = ifmt_ctx->streams[videoindex]->codec;
    AVCodec *in_codec = avcodec_find_decoder(in_codec_ctx->codec_id);
    if (!in_codec) {
        fprintf(stderr, "Failed to find input decoder\n");
        goto end;
    }
    if ((ret = avcodec_open2(in_codec_ctx, in_codec, NULL)) < 0) {
        fprintf(stderr, "Failed to open input decoder\n");
        goto end;
    }

    // 初始化输出上下文
    avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename);
    ofmt = ofmt_ctx->oformat;

    // 创建输出视频流
    AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);
    if (!out_stream) {
        fprintf(stderr, "Failed to create output stream\n");
        goto end;
    }
    out_videoindex = out_stream->index;
    AVCodecContext *out_codec_ctx = out_stream->codec;

    // 设置输出编码参数
    out_codec_ctx->codec_id = in_codec_ctx->codec_id;
    out_codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
    out_codec_ctx->pix_fmt = in_codec_ctx->pix_fmt;
    out_codec_ctx->width = in_codec_ctx->width;
    out_codec_ctx->height = in_codec_ctx->height;
    out_codec_ctx->time_base = in_codec_ctx->time_base;
    out_codec_ctx->bit_rate = target_bitrate; // 设置目标码率
    out_codec_ctx->gop_size = 25; // 关键帧间隔
    out_codec_ctx->max_b_frames = 3;

    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
        out_codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    // 打开输出编码器
    AVCodec *out_codec = avcodec_find_encoder(out_codec_ctx->codec_id);
    if (!out_codec) {
        fprintf(stderr, "Failed to find output encoder\n");
        goto end;
    }
    if ((ret = avcodec_open2(out_codec_ctx, out_codec, NULL)) < 0) {
        fprintf(stderr, "Failed to open output encoder\n");
        goto end;
    }

    // 打开输出URL
    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            fprintf(stderr, "Failed to open output URL\n");
            goto end;
        }
    }

    // 写入文件头
    if ((ret = avformat_write_header(ofmt_ctx, NULL)) < 0) {
        fprintf(stderr, "Error occurred when opening output URL\n");
        goto end;
    }

    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Failed to allocate frame\n");
        goto end;
    }
    start_time=av_gettime();

    while (1) {
        AVStream *in_stream, *out_stream;
        // 读取输入数据包
        ret = av_read_frame(ifmt_ctx, &pkt);
        if (ret < 0)
            break;

        in_stream  = ifmt_ctx->streams[pkt.stream_index];
        out_stream = ofmt_ctx->streams[pkt.stream_index];

        // 仅处理视频流
        if(pkt.stream_index == videoindex){
            // 解码视频帧
            ret = avcodec_send_packet(in_codec_ctx, &pkt);
            if (ret < 0) {
                fprintf(stderr, "Error sending packet to decoder\n");
                av_free_packet(&pkt);
                continue;
            }

            while (ret >= 0) {
                ret = avcodec_receive_frame(in_codec_ctx, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                    break;
                else if (ret < 0) {
                    fprintf(stderr, "Error receiving frame from decoder\n");
                    goto end;
                }

                frame->pts = av_frame_get_best_effort_timestamp(frame);

                // 编码视频帧
                av_init_packet(&enc_pkt);
                enc_pkt.data = NULL;
                enc_pkt.size = 0;

                ret = avcodec_send_frame(out_codec_ctx, frame);
                if (ret < 0) {
                    fprintf(stderr, "Error sending frame to encoder\n");
                    continue;
                }

                while (ret >= 0) {
                    ret = avcodec_receive_packet(out_codec_ctx, &enc_pkt);
                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                        break;
                    else if (ret < 0) {
                        fprintf(stderr, "Error receiving packet from encoder\n");
                        goto end;
                    }

                    // 调整时间戳
                    av_packet_rescale_ts(&enc_pkt, in_codec_ctx->time_base, out_stream->time_base);
                    enc_pkt.stream_index = out_videoindex;

                    // 延迟控制
                    AVRational time_base_q={1,AV_TIME_BASE};
                    int64_t pts_time = av_rescale_q(enc_pkt.dts, out_stream->time_base, time_base_q);
                    int64_t now_time = av_gettime() - start_time;
                    if (pts_time > now_time)
                        av_usleep(pts_time - now_time);

                    // 发送数据包到RTMP
                    if ((ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt)) < 0) {
                        fprintf(stderr, "Error muxing packet\n");
                        goto end;
                    }
                    printf("Send %8d video frames to output URL\n", frame_index);
                    frame_index++;
                    av_free_packet(&enc_pkt);
                }
            }
        }
        av_free_packet(&pkt);
    }

    // 刷新编码器
    avcodec_send_frame(out_codec_ctx, NULL);
    while (ret >= 0) {
        ret = avcodec_receive_packet(out_codec_ctx, &enc_pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        else if (ret < 0) {
            fprintf(stderr, "Error receiving packet from encoder\n");
            goto end;
        }
        av_packet_rescale_ts(&enc_pkt, out_codec_ctx->time_base, ofmt_ctx->streams[out_videoindex]->time_base);
        enc_pkt.stream_index = out_videoindex;
        av_interleaved_write_frame(ofmt_ctx, &enc_pkt);
        av_free_packet(&enc_pkt);
    }

    // 写入文件尾
    av_write_trailer(ofmt_ctx);

end:
    av_frame_free(&frame);
    avformat_close_input(&ifmt_ctx);
    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
        avio_close(ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);
    if (in_codec_ctx)
        avcodec_close(in_codec_ctx);
    if (out_codec_ctx)
        avcodec_close(out_codec_ctx);
    return ret >= 0 ? 0 : ret;
}

验证方法

使用ffplay拉取RTMP流,同时执行ffmpeg -i rtmp://localhost:1935/live/test -f null -查看输出日志中的实际码率,确认是否达到设置的400kbps。


内容的提问来源于stack exchange,提问作者Zion Liu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 06:12:04