如何修改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
无效原因
- 直接转发编码数据包,未重新编码:当前代码只是把输入文件中已编码好的AVPacket直接发送到RTMP服务器,数据包的码率是输入文件的原始码率,修改
ofmt_ctx->bit_rate不会改变已编码数据的码率。 - 全局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
相关产品推荐
相关产品推荐

