FFmpeg视频流内存与帧率异常问题求助
关于FFmpeg推流的内存飙升与播放卡顿问题解决思路
问题背景
首次使用FFmpeg生成视频流,测试环境为Nginx服务器+VLC播放客户端,流程是加载单张图片后逐帧处理、编码推流,遇到两个关联问题:
- 无限制快速生成帧时,流看似实时播放,但电脑内存数秒内急剧飙升至数GB
- 手动添加
sleep_for将帧率限制到约40fps后,内存增长得到控制,但VLC出现间歇性卡顿:等待→播放数秒→停顿→恢复循环
已尝试的帧率设置
曾参考资料配置以下参数,试图标识流帧率为30fps并配置时序(90为FFmpeg采样率):
(*codec_context)->time_base = (AVRational){1, 30};
frame_yuv->pts = (1.0 / 30) * 90 * frame_count;
核心原因分析
- 内存飙升:无限制生成帧时,
avcodec_send_frame会将未编码的帧存入编码器内部队列,编码器的编码速度远低于帧生成速度,导致队列无限膨胀,最终占用大量内存。 - 播放卡顿:固定20ms的
sleep_for对应约50fps,与设置的30fps目标帧率不匹配,导致实际生成的帧时序混乱;同时sleep_for的精度有限,无法严格控制帧发送间隔,进一步加剧VLC解码时的时序冲突。
解决思路与技术建议
1. 严格控制帧生成速率(匹配目标帧率)
替换固定sleep为基于时间戳的动态延迟,确保每帧按30fps的间隔发送:
auto start_time = std::chrono::high_resolution_clock::now(); while (1) { // 计算当前帧的目标发送时间 auto target_time = start_time + std::chrono::microseconds(static_cast<int>(frame_count * 1000000.0 / 30)); auto now = std::chrono::high_resolution_clock::now(); // 动态延迟,确保帧发送间隔严格匹配30fps if (now < target_time) { std::this_thread::sleep_for(target_time - now); } ProcessFrame(framebuffer_in, framebuffer, width, height); EncodeAndStreamFrame(codec_context, format_context, &framebuffer, width, height, sws_ctx); frame_count++; // 输出实际FPS auto end_time = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time); double fps = static_cast<double>(frame_count) / duration.count(); fprintf(stderr, "\r%f fps", fps); fflush(stderr); }
2. 修正编码器与时序配置
- 显式设置编码器帧率,让编码器知晓目标帧率,优化内部队列管理:
// 在InitializeFFMPG函数中添加 (*codec_context)->framerate = (AVRational){30, 1};
- 开启低延迟编码模式,减少编码延迟,避免队列积压:
// 在avcodec_open2之前添加 av_opt_set((*codec_context)->priv_data, "preset", "ultrafast", 0); av_opt_set((*codec_context)->priv_data, "tune", "zerolatency", 0);
- 修正PTS计算,用整数运算避免浮点误差,匹配RTMP的90kHz时钟标准:
// 替换原有的PTS计算行 frame_yuv->pts = frame_count * (90000 / 30); // 或用FFmpeg内置的时间基转换函数(更通用) frame_yuv->pts = av_rescale_q(frame_count, (AVRational){1, 30}, codec_context->time_base);
3. 优化资源复用,减少内存开销
原代码每次调用EncodeAndStreamFrame都重新分配frame_rgb和frame_yuv,频繁的内存分配释放会导致性能下降和内存碎片。将帧的分配移到初始化阶段,复用内存:
// main函数初始化部分添加 AVFrame* frame_rgb = av_frame_alloc(); frame_rgb->format = AV_PIX_FMT_RGB24; frame_rgb->width = width; frame_rgb->height = height; if (av_image_alloc(frame_rgb->data, frame_rgb->linesize, width, height, AV_PIX_FMT_RGB24, 32) < 0) { std::cerr << "Failed to allocate RGB image buffer" << std::endl; return 1; } AVFrame* frame_yuv = av_frame_alloc(); frame_yuv->format = AV_PIX_FMT_YUV420P; frame_yuv->width = width; frame_yuv->height = height; if (av_frame_get_buffer(frame_yuv, 32) < 0) { std::cerr << "Failed to allocate YUV frame buffer" << std::endl; av_frame_free(&frame_rgb); return 1; } // 修改EncodeAndStreamFrame的参数,传入已分配的帧 void EncodeAndStreamFrame(AVCodecContext* codec_context, AVFormatContext* format_context, unsigned char** framebuffer, int width, int height, struct SwsContext* sws_ctx, AVFrame* frame_rgb, AVFrame* frame_yuv) { memcpy(frame_rgb->data[0], *framebuffer, width * height * 3); frame_yuv->pts = frame_count * (90000 / 30); ConvertRGBToYUV(frame_rgb, frame_yuv, sws_ctx); // 原有的编码推流逻辑... // 不再需要在函数内分配和释放frame_rgb、frame_yuv } // 循环内调用修改后的函数 EncodeAndStreamFrame(codec_context, format_context, &framebuffer, width, height, sws_ctx, frame_rgb, frame_yuv); // 清理阶段释放帧 av_frame_free(&frame_rgb); av_frame_free(&frame_yuv);
4. 错误处理优化
- 检查
av_interleaved_write_frame的返回值,如果发送失败(如网络拥塞),需处理重试或丢弃帧,避免未处理的数据包占用内存:
if (av_interleaved_write_frame(format_context, &pkt) < 0) { std::cerr << "Failed to write frame to stream" << std::endl; av_packet_unref(&pkt); // 可添加重试逻辑或记录日志 break; }
完整修正代码
extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/imgutils.h> #include <libavutil/opt.h> #include <libswscale/swscale.h> } #include <iostream> #include <chrono> #include <thread> #include <fstream> #include <cmath> void InitializeFFMPG(AVCodecContext** codec_context, AVFormatContext** format_context, int width, int height) { avformat_network_init(); avformat_alloc_output_context2(format_context, nullptr, "flv", "rtmp://localhost/live/stream"); const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264); if (!codec) { std::cerr << "avcodec_find_encoder err." << std::endl; return; } *codec_context = avcodec_alloc_context3(codec); if (!*codec_context) { std::cerr << "codec_context err." << std::endl; return; } (*codec_context)->width = width; (*codec_context)->height = height; (*codec_context)->pix_fmt = AV_PIX_FMT_YUV420P; (*codec_context)->time_base = (AVRational){1, 30}; (*codec_context)->framerate = (AVRational){30, 1}; // 开启低延迟编码 av_opt_set((*codec_context)->priv_data, "preset", "ultrafast", 0); av_opt_set((*codec_context)->priv_data, "tune", "zerolatency", 0); if (avcodec_open2(*codec_context, codec, nullptr) < 0) { std::cerr << "Could not open codec" << std::endl; return; } AVStream* stream = avformat_new_stream(*format_context, codec); if (!stream) { std::cerr << "Could not create stream" << std::endl; return; } avcodec_parameters_from_context(stream->codecpar, *codec_context); if (avio_open(&(*format_context)->pb, "rtmp://localhost/live/stream", AVIO_FLAG_WRITE) < 0) { std::cerr << "Count not open output URL" << std::endl; return; } if (avformat_write_header(*format_context, nullptr) < 0) { std::cerr << "Could not write header" << std::endl; return; } } int frame_count = 0; void ConvertRGBToYUV(AVFrame* frame_rgb, AVFrame* frame_yuv, struct SwsContext* sws_ctx) { sws_scale(sws_ctx, frame_rgb->data, frame_rgb->linesize, 0, frame_rgb->height, frame_yuv->data, frame_yuv->linesize); } void EncodeAndStreamFrame(AVCodecContext* codec_context, AVFormatContext* format_context, unsigned char** framebuffer, int width, int height, struct SwsContext* sws_ctx, AVFrame* frame_rgb, AVFrame* frame_yuv) { memcpy(frame_rgb->data[0], *framebuffer, width * height * 3); frame_yuv->pts = frame_count * (90000 / 30); ConvertRGBToYUV(frame_rgb, frame_yuv, sws_ctx); if (avcodec_send_frame(codec_context, frame_yuv) < 0) { std::cerr << "Error sending YUV frame for encoding" << std::endl; return; } while (1) { AVPacket pkt = { 0 }; av_packet_unref(&pkt); pkt.data = NULL; pkt.size = 0; int ret = avcodec_receive_packet(codec_context, &pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { std::cerr << "Error receiving encoded packet" << std::endl; break; } if (av_interleaved_write_frame(format_context, &pkt) < 0) { std::cerr << "Failed to write frame to stream" << std::endl; av_packet_unref(&pkt); break; } av_packet_unref(&pkt); } } void ProcessFrame(const unsigned char* in, unsigned char*& framebuffer, int width, int height) { float cosine = std::abs(std::cos(frame_count / 300.f)); for (uint32_t i = 0; i < width * height * 3; ++i) framebuffer[i] = (unsigned char)((float)in[i] * cosine); } int main() { AVCodecContext *codec_context = nullptr; AVFormatContext *format_context = nullptr; int width = 320, height = 160; InitializeFFMPG(&codec_context, &format_context, width, height); if (!codec_context || !format_context) { std::cerr << "Initialization failed" << std::endl; return 1; } struct SwsContext* sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGB24, width, height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, nullptr, nullptr, nullptr); if (!sws_ctx) { std::cerr << "Failed to create sws context" << std::endl; return 1; } unsigned char* framebuffer_in = new unsigned char[width*height*3]; unsigned char* framebuffer = new unsigned char[width*height*3]; memset(framebuffer, 0x0, width * height * 3); std::ifstream ifs("C:/Users/xxx/Downloads/ocean.ppm", std::ios::binary); if (!ifs.is_open()) { std::cerr << "Failed to open PPM file" << std::endl; return 1; } std::string header; ifs >> header; uint32_t w, h, bpc; ifs >> w >> h >> bpc; ifs.ignore(); ifs.read((char*)framebuffer_in, w * h * 3); ifs.close(); // 预分配帧内存,复用避免频繁分配 AVFrame* frame_rgb = av_frame_alloc(); if (!frame_rgb) { std::cerr << "Failed to allocate frame_rgb" << std::endl; return 1; } frame_rgb->format = AV_PIX_FMT_RGB24; frame_rgb->width = width; frame_rgb->height = height; if (av_image_alloc(frame_rgb->data, frame_rgb->linesize, width, height, AV_PIX_FMT_RGB24, 32) < 0) { std::cerr << "Failed to allocate RGB image buffer" << std::endl; av_frame_free(&frame_rgb); return 1; } AVFrame* frame_yuv = av_frame_alloc(); if (!frame_yuv) { std::cerr << "Failed to allocate frame_yuv" << std::endl; av_frame_free(&frame_rgb); return 1; } frame_yuv->format = AV_PIX_FMT_YUV420P; frame_yuv->width = width; frame_yuv->height = height; if (av_frame_get_buffer(frame_yuv, 32) < 0) { std::cerr << "Failed to allocate YUV frame buffer" << std::endl; av_frame_free(&frame_rgb); av_frame_free(&frame_yuv); return 1; } auto start_time = std::chrono::high_resolution_clock::now(); while (1) { auto target_time = start_time + std::chrono::microseconds(static_cast<int>(frame_count * 1000000.0 / 30)); auto now = std::chrono::high_resolution_clock::now(); if (now < target_time) { std::this_thread::sleep_for(target_time - now); } ProcessFrame(framebuffer_in, framebuffer, width, height); EncodeAndStreamFrame(codec_context, format_context, &framebuffer, width, height, sws_ctx, frame_rgb, frame_yuv); frame_count++; auto end_time = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time); double fps = static_cast<double>(frame_count) / duration.count(); fprintf(stderr, "\r%f fps", fps); fflush(stderr); } // 清理资源 delete[] framebuffer; delete[] framebuffer_in; av_frame_free(&frame_rgb); av_frame_free(&frame_yuv); sws_freeContext(sws_ctx); avcodec_close(codec_context); avformat_close_input(&format_context); avformat_free_context(format_context); avcodec_free_context(&codec_context); return 0; }
内容的提问来源于stack exchange,提问作者user18490
相关产品推荐
相关产品推荐

