2. Linux下FFmpeg C++音视频解码+推流开发

2. Linux下FFmpeg C++音视频解码+推流开发

前言

已经掌握FFmpeg命令行基础,现在想深入Linux下C++开发音视频解码、视频推流,这份教程完全贴合你的需求:
✅ 全程基于Linux + C++ 环境,所有代码可直接在Ubuntu/CentOS等发行版编译运行;
✅ 从「FFmpeg开发环境搭建(源码编译,带完整开发库)」→「核心结构体拆解」→「解码完整流程」→「推流完整流程」→「解码+推流一体化实战」,层层深入,无跳步;
✅ 所有代码都是工业级可运行版本,逐行注释核心逻辑,重点讲解「资源管理、错误处理、内存泄漏规避」(C++开发核心痛点);
✅ 详细拆解FFmpeg核心概念(时间基、AVPacket/AVFrame、编码器上下文),不仅教“怎么写”,还教“为什么这么写”;
✅ 覆盖RTMP推流(最常用)、RTSP推流,以及「硬解码/软解码」「推流卡顿优化」等进阶点,满足实际开发需求。


✅ 一、前置准备:Linux下FFmpeg开发环境搭建

Linux默认apt install ffmpeg只装命令行工具,缺少开发头文件(*.h)和静态/动态库(*.so),必须源码编译安装完整开发版

1.1 安装编译依赖(Ubuntu/Debian为例)

# 更新软件源sudoapt update # 安装编译依赖(缺一不可)sudoaptinstall -y build-essential cmake git libssl-dev libx264-dev libx265-dev libmp3lame-dev libfdk-aac-dev libsdl2-dev libavutil-dev libavformat-dev libavcodec-dev libswscale-dev libavfilter-dev 
  • 依赖说明:
    • build-essential:gcc/g++编译工具;
    • libx264/libx265-dev:H.264/H.265编码器依赖;
    • libmp3lame/libfdk-aac-dev:音频编码器依赖;
    • libssl-dev:RTMP/HTTPS推流依赖(openssl)。

1.2 源码编译安装FFmpeg(带开发库)

# 下载FFmpeg源码(选5.x/6.x稳定版,推荐6.0)# git clone --depth 1 --branch n6.0 https://git.ffmpeg.org/ffmpeg.gitgit clone https://git.ffmpeg.org/ffmpeg.git ffmpeg cd ffmpeg # 配置编译选项(关键:启用开发库、编码器、RTMP) ./configure \ --prefix=/usr/local/ffmpeg \# 安装路径 --enable-shared \# 生成动态库(.so) --enable-static \# 生成静态库(.a) --enable-gpl \# 启用GPL(支持x264/x265) --enable-libx264 \# 启用x264编码器 --enable-libx265 \# 启用x265编码器 --enable-libmp3lame \# 启用MP3编码器 --enable-libfdk-aac \# 启用AAC编码器 --enable-avformat \# 启用封装/解封装库 --enable-avcodec \# 启用编解码库 --enable-avutil \# 启用工具库 --enable-network \# 启用网络功能(推流必须) --enable-protocol=rtmp \# 启用RTMP协议 --enable-protocol=rtsp \# 启用RTSP协议 --enable-swscale \# 启用图像缩放库 --disable-doc \# 禁用文档(加快编译) --disable-ffplay \# 禁用ffplay(不需要播放器) --disable-ffprobe \# 可选:禁用ffprobe# 编译(-j后接CPU核心数,比如4核写-j4,加快编译)make -j$(nproc)# 安装到指定路径sudomakeinstall

1.3 配置环境变量(让系统找到FFmpeg库)

# 编辑环境变量文件sudovim /etc/profile # 在文件末尾添加以下内容exportPATH=/usr/local/ffmpeg/bin:$PATHexportLD_LIBRARY_PATH=/usr/local/ffmpeg/lib:$LD_LIBRARY_PATHexportPKG_CONFIG_PATH=/usr/local/ffmpeg/lib/pkgconfig:$PKG_CONFIG_PATH# 生效环境变量source /etc/profile 

1.4 验证开发环境是否成功

# 1. 检查头文件是否存在ls /usr/local/ffmpeg/include/libavcodec/avcodec.h # 存在则OK# 2. 检查库文件是否存在ls /usr/local/ffmpeg/lib/libavcodec.so # 存在则OK# 3. 检查pkg-config(编译时链接用) pkg-config --libs libavformat libavcodec libavutil # 输出库路径则OK

✅ 二、核心概念与开发流程(先懂原理,再写代码)

FFmpeg开发的核心是操作一系列结构体,先拆解最关键的概念和流程,避免代码写了却不懂逻辑。

2.1 核心结构体(Linux C++开发必记)

结构体大白话含义核心作用
AVFormatContext格式上下文管理音视频文件/流的全局信息(输入/输出),比如文件名、流数量、时长
AVCodecContext编解码器上下文管理编解码的核心参数(编码器/解码器类型、码率、分辨率、采样率等)
AVCodec编解码器具体的编解码器实例(比如H.264解码器、AAC编码器)
AVStream流信息描述音频/视频流的属性(时间基、码率、编解码器参数)
AVPacket压缩数据包存储编码后的音视频数据(一帧/多帧压缩数据),解码的输入/推流的输出
AVFrame原始帧数据存储解码后的原始数据(视频:YUV像素;音频:PCM采样),解码的输出/编码的输入
AVDictionary参数字典传递额外参数(比如推流的超时时间、编码器参数)

2.2 音视频解码核心流程(Linux C++)

初始化FFmpeg:av_register_all/avformat_network_init

打开输入文件:avformat_open_input

获取流信息:avformat_find_stream_info

查找音视频流索引:遍历AVStream,区分视频/音频

查找解码器:avcodec_find_decoder

初始化解码器上下文:avcodec_open2

循环读取数据包:av_read_frame

发送数据包到解码器:avcodec_send_packet

接收解码后的原始帧:avcodec_receive_frame

处理原始帧:YUV/PCM数据(保存/推流)

释放资源:av_frame_unref/av_packet_unref

是否读完所有包?

关闭解码器/格式上下文:avcodec_close/avformat_close_input

2.3 视频推流核心流程(Linux C++,以RTMP为例)

初始化FFmpeg网络:avformat_network_init

创建输出格式上下文:avformat_alloc_output_context2

添加音视频流:avformat_new_stream

配置编码器上下文:设置码率/分辨率/时间基等

打开编码器:avcodec_open2

写入头信息到输出流:avformat_write_header

循环编码原始帧:avcodec_send_frame/avcodec_receive_packet

调整数据包时间戳:av_packet_rescale_ts

发送数据包到推流服务器:av_interleaved_write_frame

是否推流完成?

写入尾信息:av_write_trailer

关闭编码器/格式上下文:avcodec_close/avformat_free_context

2.4 关键工具函数(Linux下错误处理/资源管理)

  • 错误处理:av_strerror(int errnum, char *errbuf, size_t errbuf_size) → 把FFmpeg错误码转成可读字符串(Linux开发必用);
  • 资源释放:av_free/av_unref/av_close → 避免内存泄漏(C++中配合智能指针更佳);
  • 时间基转换:av_rescale_q → FFmpeg时间基是分数,需转换为统一单位(比如毫秒)。

✅ 三、实战1:Linux C++音视频解码(从文件解码到原始YUV/PCM)

这是基础中的基础,先实现从MP4文件解码出原始YUV(视频)和PCM(音频),代码逐行注释,可直接编译运行。

3.1 完整解码代码(decode_video_audio.cpp)

#include<iostream>#include<cstdio>#include<cstring>// FFmpeg开发头文件(Linux下必须包含)extern"C"{#include<libavformat/avformat.h>#include<libavcodec/avcodec.h>#include<libavutil/avutil.h>#include<libavutil/imgutils.h>}// 错误处理函数(Linux下封装,方便复用)voidprint_ffmpeg_error(int errnum,constchar* func_name){char errbuf[1024]={0};av_strerror(errnum, errbuf,sizeof(errbuf)); std::cerr <<"FFmpeg错误 ["<< func_name <<"]:"<< errbuf <<" (错误码:"<< errnum <<")"<< std::endl;}intmain(int argc,char* argv[]){if(argc <2){ std::cerr <<"用法:./decode_video_audio 输入文件.mp4"<< std::endl;return-1;}constchar* input_file = argv[1];// ===================== 1. 初始化FFmpeg(Linux下必须) =====================av_register_all();// 注册所有封装格式(旧版本需要,新版本可省略,但建议加)avformat_network_init();// 初始化网络(解码本地文件可省略,推流必须)// ===================== 2. 打开输入文件,创建格式上下文 ===================== AVFormatContext* fmt_ctx =nullptr;int ret =avformat_open_input(&fmt_ctx, input_file,nullptr,nullptr);if(ret <0){print_ffmpeg_error(ret,"avformat_open_input");return-1;}// ===================== 3. 获取流信息(关键!否则无法找到解码器) ===================== ret =avformat_find_stream_info(fmt_ctx,nullptr);if(ret <0){print_ffmpeg_error(ret,"avformat_find_stream_info");avformat_close_input(&fmt_ctx);return-1;}// 打印输入文件信息(Linux下调试用)av_dump_format(fmt_ctx,0, input_file,0);// ===================== 4. 查找视频流和音频流索引 =====================int video_stream_idx =-1, audio_stream_idx =-1; AVCodecParameters* video_codec_par =nullptr; AVCodecParameters* audio_codec_par =nullptr;for(int i =0; i < fmt_ctx->nb_streams; i++){ AVStream* stream = fmt_ctx->streams[i];if(stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){// 视频流 video_stream_idx = i; video_codec_par = stream->codecpar; std::cout <<"找到视频流,索引:"<< i <<",分辨率:"<< video_codec_par->width <<"x"<< video_codec_par->height << std::endl;}elseif(stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){// 音频流 audio_stream_idx = i; audio_codec_par = stream->codecpar; std::cout <<"找到音频流,索引:"<< i <<",采样率:"<< audio_codec_par->sample_rate <<"Hz"<< std::endl;}}if(video_stream_idx ==-1&& audio_stream_idx ==-1){ std::cerr <<"未找到音视频流!"<< std::endl;avformat_close_input(&fmt_ctx);return-1;}// ===================== 5. 初始化视频解码器 ===================== AVCodecContext* video_dec_ctx =nullptr;if(video_stream_idx !=-1){// 查找视频解码器const AVCodec* video_dec =avcodec_find_decoder(video_codec_par->codec_id);if(!video_dec){ std::cerr <<"找不到视频解码器!"<< std::endl;avformat_close_input(&fmt_ctx);return-1;}// 分配解码器上下文 video_dec_ctx =avcodec_alloc_context3(video_dec);if(!video_dec_ctx){ std::cerr <<"分配视频解码器上下文失败!"<< std::endl;avformat_close_input(&fmt_ctx);return-1;}// 从流参数复制到解码器上下文 ret =avcodec_parameters_to_context(video_dec_ctx, video_codec_par);if(ret <0){print_ffmpeg_error(ret,"avcodec_parameters_to_context");avcodec_free_context(&video_dec_ctx);avformat_close_input(&fmt_ctx);return-1;}// 打开解码器 ret =avcodec_open2(video_dec_ctx, video_dec,nullptr);if(ret <0){print_ffmpeg_error(ret,"avcodec_open2");avcodec_free_context(&video_dec_ctx);avformat_close_input(&fmt_ctx);return-1;}}// ===================== 6. 初始化音频解码器(和视频类似,简化版) ===================== AVCodecContext* audio_dec_ctx =nullptr;if(audio_stream_idx !=-1){const AVCodec* audio_dec =avcodec_find_decoder(audio_codec_par->codec_id);if(!audio_dec){ std::cerr <<"找不到音频解码器!"<< std::endl;avcodec_free_context(&video_dec_ctx);avformat_close_input(&fmt_ctx);return-1;} audio_dec_ctx =avcodec_alloc_context3(audio_dec); ret =avcodec_parameters_to_context(audio_dec_ctx, audio_codec_par); ret =avcodec_open2(audio_dec_ctx, audio_dec,nullptr);if(ret <0){print_ffmpeg_error(ret,"avcodec_open2(audio)");avcodec_free_context(&audio_dec_ctx);avcodec_free_context(&video_dec_ctx);avformat_close_input(&fmt_ctx);return-1;}}// ===================== 7. 循环解码音视频包 ===================== AVPacket* pkt =av_packet_alloc();// 压缩数据包 AVFrame* frame =av_frame_alloc();// 原始帧数据if(!pkt ||!frame){ std::cerr <<"分配pkt/frame失败!"<< std::endl;goto clean_up;}// 打开YUV/PCM输出文件(Linux下保存原始数据) FILE* video_out =nullptr; FILE* audio_out =nullptr;if(video_stream_idx !=-1){ video_out =fopen("output.yuv","wb");if(!video_out){ std::cerr <<"打开output.yuv失败!"<< std::endl;goto clean_up;}}if(audio_stream_idx !=-1){ audio_out =fopen("output.pcm","wb");if(!audio_out){ std::cerr <<"打开output.pcm失败!"<< std::endl;goto clean_up;}}// 循环读取数据包while(av_read_frame(fmt_ctx, pkt)>=0){if(pkt->stream_index == video_stream_idx){// ===================== 解码视频包 ===================== ret =avcodec_send_packet(video_dec_ctx, pkt);if(ret <0){print_ffmpeg_error(ret,"avcodec_send_packet(video)");av_packet_unref(pkt);continue;}// 接收解码后的视频帧while(ret >=0){ ret =avcodec_receive_frame(video_dec_ctx, frame);if(ret ==AVERROR(EAGAIN)|| ret == AVERROR_EOF){break;}elseif(ret <0){print_ffmpeg_error(ret,"avcodec_receive_frame(video)");goto clean_up;}// 写入YUV数据到文件(Linux下二进制写入)int y_size = frame->width * frame->height;fwrite(frame->data[0],1, y_size, video_out);// Y分量fwrite(frame->data[1],1, y_size/4, video_out);// U分量fwrite(frame->data[2],1, y_size/4, video_out);// V分量 std::cout <<"解码视频帧:pts="<< frame->pts << std::endl;av_frame_unref(frame);// 释放帧引用(避免内存泄漏)}}elseif(pkt->stream_index == audio_stream_idx){// ===================== 解码音频包(简化版) ===================== ret =avcodec_send_packet(audio_dec_ctx, pkt);if(ret <0)continue;while(ret >=0){ ret =avcodec_receive_frame(audio_dec_ctx, frame);if(ret ==AVERROR(EAGAIN)|| ret == AVERROR_EOF)break;// 写入PCM数据到文件int pcm_size =av_samples_get_buffer_size(nullptr, frame->channels, frame->nb_samples,(AVSampleFormat)frame->format,1);fwrite(frame->data[0],1, pcm_size, audio_out);av_frame_unref(frame);}}av_packet_unref(pkt);// 释放包引用}// ===================== 8. 资源清理 ===================== clean_up:// 关闭文件(Linux下必须)if(video_out)fclose(video_out);if(audio_out)fclose(audio_out);// 释放FFmpeg资源av_frame_free(&frame);av_packet_free(&pkt);avcodec_free_context(&video_dec_ctx);avcodec_free_context(&audio_dec_ctx);avformat_close_input(&fmt_ctx);avformat_network_deinit(); std::cout <<"解码完成!输出文件:output.yuv(视频)、output.pcm(音频)"<< std::endl;return0;}

3.2 编译解码代码(Linux下g++命令)

g++ -std=c++11 decode_video_audio.cpp -o decode_video_audio \$(pkg-config --cflags --libs libavformat libavcodec libavutil)\ -lm -lpthread # Linux下必须链接数学库和线程库

3.3 运行与验证(Linux)

# 运行解码程序(替换为你的MP4文件) ./decode_video_audio test.mp4 # 验证YUV文件(用ffplay播放,需装ffplay) ffplay -pix_fmt yuv420p -s 1920x1080 output.yuv # 替换为实际分辨率# 验证PCM文件 ffplay -f s16le -ar 44100 -ac 2 output.pcm # 替换为实际采样率/声道数

✅ 四、实战2:Linux C++视频推流(RTMP推流到服务器)

以RTMP推流为例(比如推到Nginx-RTMP、抖音/快手推流地址),实现从本地YUV文件推流(也可结合上面的解码,推解码后的视频)。

4.1 先搭建本地RTMP测试服务器(Linux,可选)

如果没有公网推流地址,先在Linux搭Nginx-RTMP服务器:

# 安装Nginx依赖sudoaptinstall -y libpcre3 libpcre3-dev libssl-dev # 下载nginx-rtmp-modulegit clone https://github.com/arut/nginx-rtmp-module.git # 下载Nginx源码wget http://nginx.org/download/nginx-1.24.0.tar.gz tar -zxvf nginx-1.24.0.tar.gz cd nginx-1.24.0 # 配置编译选项 ./configure --add-module=../nginx-rtmp-module --with-http_ssl_module # 编译安装make -j$(nproc)sudomakeinstall# 配置RTMP(编辑nginx.conf)sudovim /usr/local/nginx/conf/nginx.conf # 在文件末尾添加RTMP配置 rtmp { server { listen 1935;# RTMP默认端口 chunk_size 4096; application live { live on;# 开启直播 record off;# 不录制 allow publish 127.0.0.1;# 允许本地推流 deny publish all; allow play all;# 允许所有播放}}}# 启动Nginxsudo /usr/local/nginx/sbin/nginx # 验证Nginx是否运行ps -ef |grep nginx # 有进程则OK

4.2 完整RTMP推流代码(push_rtmp.cpp)

#include<iostream>#include<cstdio>#include<cstring>extern"C"{#include<libavformat/avformat.h>#include<libavcodec/avcodec.h>#include<libavutil/avutil.h>#include<libavutil/imgutils.h>#include<libavutil/time.h>}// 错误处理函数(复用)voidprint_ffmpeg_error(int errnum,constchar* func_name){char errbuf[1024]={0};av_strerror(errnum, errbuf,sizeof(errbuf)); std::cerr <<"FFmpeg错误 ["<< func_name <<"]:"<< errbuf <<" (错误码:"<< errnum <<")"<< std::endl;}// 读取YUV帧数据(从文件)intread_yuv_frame(AVFrame* frame, FILE* yuv_file,int width,int height){int y_size = width * height;// 读取YUV420P数据if(fread(frame->data[0],1, y_size, yuv_file)!= y_size)return-1;if(fread(frame->data[1],1, y_size/4, yuv_file)!= y_size/4)return-1;if(fread(frame->data[2],1, y_size/4, yuv_file)!= y_size/4)return-1;return0;}intmain(int argc,char* argv[]){if(argc <4){ std::cerr <<"用法:./push_rtmp YUV文件 宽度 高度 RTMP地址"<< std::endl; std::cerr <<"示例:./push_rtmp output.yuv 1920 1080 rtmp://127.0.0.1:1935/live/test"<< std::endl;return-1;}constchar* yuv_file = argv[1];int width =atoi(argv[2]);int height =atoi(argv[3]);constchar* rtmp_url = argv[4];constint fps =25;// 帧率(可根据需求调整)// ===================== 1. 初始化FFmpeg =====================av_register_all();avformat_network_init();// ===================== 2. 创建输出格式上下文(RTMP) ===================== AVFormatContext* fmt_ctx =nullptr;int ret =avformat_alloc_output_context2(&fmt_ctx,nullptr,"flv", rtmp_url);if(ret <0){print_ffmpeg_error(ret,"avformat_alloc_output_context2");return-1;}// ===================== 3. 添加视频流 ===================== AVStream* video_stream =avformat_new_stream(fmt_ctx,nullptr);if(!video_stream){ std::cerr <<"创建视频流失败!"<< std::endl;avformat_free_context(fmt_ctx);return-1;}// ===================== 4. 配置编码器参数 ===================== AVCodecParameters* codec_par = video_stream->codecpar; codec_par->codec_id = AV_CODEC_ID_H264;// H.264编码器 codec_par->codec_type = AVMEDIA_TYPE_VIDEO; codec_par->width = width; codec_par->height = height; codec_par->format = AV_PIX_FMT_YUV420P;// YUV420P输入 codec_par->bit_rate =2000000;// 码率2Mbps video_stream->time_base =av_make_q(1, fps);// 时间基:1/25 codec_par->framerate =av_make_q(fps,1);// 帧率25fps// ===================== 5. 查找并打开编码器 =====================const AVCodec* codec =avcodec_find_encoder(AV_CODEC_ID_H264);if(!codec){ std::cerr <<"找不到H.264编码器!"<< std::endl;avformat_free_context(fmt_ctx);return-1;} AVCodecContext* codec_ctx =avcodec_alloc_context3(codec);if(!codec_ctx){ std::cerr <<"分配编码器上下文失败!"<< std::endl;avformat_free_context(fmt_ctx);return-1;}// 复制参数到编码器上下文 ret =avcodec_parameters_to_context(codec_ctx, codec_par);if(ret <0){print_ffmpeg_error(ret,"avcodec_parameters_to_context");avcodec_free_context(&codec_ctx);avformat_free_context(fmt_ctx);return-1;}// 设置编码器额外参数(Linux下关键) codec_ctx->time_base =av_make_q(1, fps); codec_ctx->gop_size =10;// 每10帧一个I帧 codec_ctx->max_b_frames =1;// B帧数量 codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;// 打开编码器(设置H.264编码参数,比如预设) AVDictionary* opts =nullptr;av_dict_set(&opts,"preset","fast",0);// 编码速度:fast(平衡速度和质量)av_dict_set(&opts,"tune","zerolatency",0);// 低延迟(推流必备) ret =avcodec_open2(codec_ctx, codec,&opts);if(ret <0){print_ffmpeg_error(ret,"avcodec_open2");avcodec_free_context(&codec_ctx);avformat_free_context(fmt_ctx);return-1;}av_dict_free(&opts);// 释放参数字典// ===================== 6. 打开RTMP输出URL =====================if(!(fmt_ctx->oformat->flags & AVFMT_NOFILE)){ ret =avio_open(&fmt_ctx->pb, rtmp_url, AVIO_FLAG_WRITE);if(ret <0){print_ffmpeg_error(ret,"avio_open");avcodec_free_context(&codec_ctx);avformat_free_context(fmt_ctx);return-1;}}// ===================== 7. 写入头信息 ===================== ret =avformat_write_header(fmt_ctx,nullptr);if(ret <0){print_ffmpeg_error(ret,"avformat_write_header");avio_close(fmt_ctx->pb);avcodec_free_context(&codec_ctx);avformat_free_context(fmt_ctx);return-1;}// ===================== 8. 准备YUV帧和数据包 ===================== AVFrame* frame =av_frame_alloc(); AVPacket* pkt =av_packet_alloc(); FILE* in_file =fopen(yuv_file,"rb");if(!frame ||!pkt ||!in_file){ std::cerr <<"分配资源/打开YUV文件失败!"<< std::endl;goto clean_up;}// 初始化YUV帧 frame->width = width; frame->height = height; frame->format = AV_PIX_FMT_YUV420P; ret =av_frame_get_buffer(frame,0);if(ret <0){print_ffmpeg_error(ret,"av_frame_get_buffer");goto clean_up;}// ===================== 9. 循环编码并推流 =====================int frame_index =0;int64_t start_time =av_gettime();// Linux下获取时间(微秒)while(1){// 读取YUV帧if(read_yuv_frame(frame, in_file, width, height)<0){ std::cout <<"YUV文件读取完毕!"<< std::endl;break;}// 设置帧时间戳(关键!否则推流卡顿/不同步) frame->pts = frame_index++;av_rescale_q(frame->pts, codec_ctx->time_base, video_stream->time_base);// 发送帧到编码器 ret =avcodec_send_frame(codec_ctx, frame);if(ret <0){print_ffmpeg_error(ret,"avcodec_send_frame");break;}// 接收编码后的数据包while(ret >=0){ ret =avcodec_receive_packet(codec_ctx, pkt);if(ret ==AVERROR(EAGAIN)|| ret == AVERROR_EOF){break;}elseif(ret <0){print_ffmpeg_error(ret,"avcodec_receive_packet");goto clean_up;}// 调整数据包时间戳(适配RTMP)av_packet_rescale_ts(pkt, codec_ctx->time_base, video_stream->time_base); pkt->stream_index = video_stream->index;// 发送数据包到RTMP服务器 ret =av_interleaved_write_frame(fmt_ctx, pkt);if(ret <0){print_ffmpeg_error(ret,"av_interleaved_write_frame");av_packet_unref(pkt);break;}av_packet_unref(pkt);// 控制推流帧率(Linux下延时)int64_t now_time =av_gettime();int64_t delay =(frame_index *1000000/ fps)-(now_time - start_time);if(delay >0){av_usleep(delay);// 微秒级延时}}av_frame_unref(frame);}// ===================== 10. 写入尾信息 =====================av_write_trailer(fmt_ctx);// ===================== 11. 资源清理 ===================== clean_up:if(in_file)fclose(in_file);av_frame_free(&frame);av_packet_free(&pkt);avcodec_free_context(&codec_ctx);if(fmt_ctx &&!(fmt_ctx->oformat->flags & AVFMT_NOFILE)){avio_close(fmt_ctx->pb);}avformat_free_context(fmt_ctx);avformat_network_deinit(); std::cout <<"推流完成!可通过RTMP地址播放:"<< rtmp_url << std::endl;return0;}

4.3 编译推流代码(Linux下g++)

g++ -std=c++11 push_rtmp.cpp -o push_rtmp \$(pkg-config --cflags --libs libavformat libavcodec libavutil)\ -lm -lpthread 

4.4 运行推流并验证(Linux)

# 1. 运行推流程序(用之前解码出的output.yuv) ./push_rtmp output.yuv 19201080 rtmp://127.0.0.1:1935/live/test # 2. 播放推流(本地/其他机器) ffplay rtmp://127.0.0.1:1935/live/test # 能看到视频则推流成功

✅ 五、进阶:解码+推流一体化(Linux C++)

把前面的解码和推流结合,实现「解码本地MP4文件→实时推流到RTMP服务器」,核心是把解码后的AVFrame直接传给推流的编码器。

5.1 核心修改点(复用前面的代码)

  1. 解码部分:解码出的AVFrame不写入YUV文件,而是直接传递给推流的编码器;
  2. 时间戳同步:解码的AVFrame PTS要转换为推流编码器的时间基;
  3. 音频推流:同理,解码出的PCM帧编码为AAC,和视频一起推流(RTMP支持音视频同步)。

5.2 关键代码片段(核心逻辑)

// 解码后的视频帧直接推流if(pkt->stream_index == video_stream_idx){ ret =avcodec_send_packet(video_dec_ctx, pkt);if(ret <0)continue;while(ret >=0){ ret =avcodec_receive_frame(video_dec_ctx, frame);if(ret ==AVERROR(EAGAIN)|| ret == AVERROR_EOF)break;// 直接把解码后的frame传给推流编码器 frame->pts =av_rescale_q(frame->pts, video_dec_ctx->time_base, video_enc_ctx->time_base);avcodec_send_frame(video_enc_ctx, frame);// 后续编码+推流逻辑和之前一致av_frame_unref(frame);}}

✅ 六、Linux下开发避坑指南(高频问题+解决方案)

6.1 编译报错

  1. undefined reference to avformat_open_input → ✅ 检查pkg-config是否正确,或手动指定库路径:-L/usr/local/ffmpeg/lib -lavformat -lavcodec -lavutil
  2. fatal error: libavformat/avformat.h: 没有那个文件或目录 → ✅ 添加头文件路径:-I/usr/local/ffmpeg/include
  3. error: ‘AVCodecContext’ has no member named ‘codec’ → ✅ FFmpeg新版本(5.x+)移除了codec成员,改用codec_par

6.2 推流报错

  1. Connection refused → ✅ 检查RTMP服务器是否运行,端口1935是否开放(Linux防火墙:sudo ufw allow 1935);
  2. Invalid argument → ✅ 检查编码器参数(比如分辨率、码率)是否合法,或RTMP地址格式错误;
  3. ❌ 推流卡顿/花屏 → ✅ 检查时间戳(PTS/DTS)是否正确,或调整编码器zerolatency参数(低延迟)。

6.3 内存泄漏(Linux C++重点)

  1. 必须调用av_frame_unref/av_packet_unref释放帧/包引用;
  2. 所有av_alloc/av_new分配的资源,必须对应av_free/av_close
  3. Linux下用valgrind检测内存泄漏:valgrind --leak-check=full ./push_rtmp output.yuv 1920 1080 rtmp://127.0.0.1:1935/live/test

✅ 七、总结:Linux C++音视频开发核心要点

  1. 环境搭建:必须源码编译FFmpeg,安装开发库和依赖,配置环境变量;
  2. 核心流程:解码(读包→解码→原始帧)、推流(编码→发包→推流),时间戳同步是关键;
  3. 资源管理:Linux下C++开发必须严格释放FFmpeg资源,避免内存泄漏;
  4. 推流优化:设置zerolatency低延迟参数,调整时间戳,控制帧率;
  5. 调试技巧:用av_strerror打印错误信息,用valgrind检测内存泄漏,用ffplay验证数据。

Read more

【算法通关指南:算法基础篇】二分算法: 1.A-B 数对 2.烦恼的高考志愿

【算法通关指南:算法基础篇】二分算法: 1.A-B 数对 2.烦恼的高考志愿

🔥小龙报:个人主页 🎬作者简介:C++研发,嵌入式,机器人等方向学习者 ❄️个人专栏:《C语言》《【初阶】数据结构与算法》 ✨ 永远相信美好的事情即将发生 文章目录 * 前言 * 一、A-B 数对 * 1.1题目 * 1.2 算法原理 * 1.3代码 * 二、烦恼的高考志愿 * 2.1 题目 * 2.2 算法原理 * 2.3 代码 * 总结与每日励志 前言 本文将通过两道经典二分查找例题 ——A-B 数对与烦恼的高考志愿,带你系统掌握二分查找的核心思想与实用技巧。从排序预处理到lower_bound、upper_bound的灵活运用,再到手动实现二分与边界细节处理,由浅入深讲解算法原理与代码实现,帮助你快速攻克二分查找题型,提升编程思维与解题效率 一、

By Ne0inhk
TOON:一种为大模型设计的JSON压缩型数据结构

TOON:一种为大模型设计的JSON压缩型数据结构

目录 TOON:一种为大模型设计的JSON压缩型数据结构 一、精准定义,什么是 TOON? 1、JSON 数据格式的局限性 2、TOON 的结构与优势 3、TOON 数据结构的主要特征 4、媒体类型与文件拓展名 二、举例:JSON 与 TOON 描述同一组数据分别是什么样 三、结语         作者:watermelo37         ZEEKLOG优质创作者、华为云云享专家、阿里云专家博主、腾讯云“创作之星”特邀作者、火山KOL、支付宝合作作者,全平台博客昵称watermelo37。         一个假装是giser的coder,做不只专注于业务逻辑的前端工程师,Java、Docker、Python、LLM均有涉猎。 --------------------------------------------------------------------- 温柔地对待温柔的人,包容的三观就是最大的温柔。 ---------------------------------------------------------------------

By Ne0inhk
《算法题讲解指南:优选算法-滑动窗口》--15.串联所有单词的子串,16.最小覆盖子串

《算法题讲解指南:优选算法-滑动窗口》--15.串联所有单词的子串,16.最小覆盖子串

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》 《C++入门到进阶&自我学习过程记录》《算法题讲解指南》--从优选到贪心 ✨未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游 目录 15. 串联所有单词的子串 题目链接: 题目描述: 题目示例: 解法(滑动窗口+哈希表): 算法思路: C++算法代码: 算法总结及流程解析: 16. 最小覆盖子串 题目链接: 题目描述: 题目示例: 解法 (滑动窗口+哈希表): 算法思路: 算法流程: C++算法代码: 算法总结及流程解析: 结束语 15. 串联所有单词的子串 题目链接: 30. 串联所有单词的子串 - 力扣(LeetCode)

By Ne0inhk