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为例)

bash 复制代码
# 更新软件源
sudo apt update
# 安装编译依赖(缺一不可)
sudo apt install -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(带开发库)

bash 复制代码
# 下载FFmpeg源码(选5.x/6.x稳定版,推荐6.0)
# git clone --depth 1 --branch n6.0 https://git.ffmpeg.org/ffmpeg.git

git 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)

# 安装到指定路径
sudo make install

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

bash 复制代码
# 编辑环境变量文件
sudo vim /etc/profile

# 在文件末尾添加以下内容
export PATH=/usr/local/ffmpeg/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/ffmpeg/lib:$LD_LIBRARY_PATH
export PKG_CONFIG_PATH=/usr/local/ffmpeg/lib/pkgconfig:$PKG_CONFIG_PATH

# 生效环境变量
source /etc/profile

1.4 验证开发环境是否成功

bash 复制代码
# 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)

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下封装,方便复用)
void print_ffmpeg_error(int errnum, const char* func_name) {
    char errbuf[1024] = {0};
    av_strerror(errnum, errbuf, sizeof(errbuf));
    std::cerr << "FFmpeg错误 [" << func_name << "]:" << errbuf << " (错误码:" << errnum << ")" << std::endl;
}

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << "用法:./decode_video_audio 输入文件.mp4" << std::endl;
        return -1;
    }
    const char* 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;
        } else if (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;
                } else if (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);  // 释放帧引用(避免内存泄漏)
            }
        } else if (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;
    return 0;
}

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

bash 复制代码
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)

bash 复制代码
# 运行解码程序(替换为你的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服务器:

bash 复制代码
# 安装Nginx依赖
sudo apt install -y libpcre3 libpcre3-dev libssl-dev

# 下载nginx-rtmp-module
git 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)
sudo make install

# 配置RTMP(编辑nginx.conf)
sudo vim /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;           # 允许所有播放
        }
    }
}

# 启动Nginx
sudo /usr/local/nginx/sbin/nginx

# 验证Nginx是否运行
ps -ef | grep nginx  # 有进程则OK

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

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>
}

// 错误处理函数(复用)
void print_ffmpeg_error(int errnum, const char* func_name) {
    char errbuf[1024] = {0};
    av_strerror(errnum, errbuf, sizeof(errbuf));
    std::cerr << "FFmpeg错误 [" << func_name << "]:" << errbuf << " (错误码:" << errnum << ")" << std::endl;
}

// 读取YUV帧数据(从文件)
int read_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;
    return 0;
}

int main(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;
    }
    const char* yuv_file = argv[1];
    int width = atoi(argv[2]);
    int height = atoi(argv[3]);
    const char* rtmp_url = argv[4];
    const int 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;
            } else if (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;
    return 0;
}

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

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

4.4 运行推流并验证(Linux)

bash 复制代码
# 1. 运行推流程序(用之前解码出的output.yuv)
./push_rtmp output.yuv 1920 1080 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 关键代码片段(核心逻辑)

cpp 复制代码
// 解码后的视频帧直接推流
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验证数据。

相关推荐
星火开发设计17 小时前
C++ set 全面解析与实战指南
开发语言·c++·学习·青少年编程·编程·set·知识
zhanglb1217 小时前
Linux -kylin麒麟系统安装openSSH
linux·apache kylin
zhanglb1217 小时前
Linux-麒麟系统安装内网穿透工具
linux·apache kylin
飞凌嵌入式17 小时前
解析一下面向教育领域的RV1126B\RK3506B\RK3576开发板
linux·人工智能
Xの哲學17 小时前
Linux io_uring 深度剖析: 重新定义高性能I/O的架构革命
linux·服务器·网络·算法·边缘计算
aqi0017 小时前
FFmpeg开发笔记(九十七)国产的开源视频剪辑工具AndroidVideoEditor
android·ffmpeg·音视频·直播·流媒体
scx2013100417 小时前
20260105 莫队总结
c++
Sleepless_斑马17 小时前
RTMP/RTSP流媒体服务器搭建、ffmpeg推流桌面、vlc拉流
ffmpeg·rtmp·rtsp
Q741_14718 小时前
海致星图招聘 数据库内核研发实习生 一轮笔试 总结复盘(1) 作答语言:C/C++ 链表 二叉树
开发语言·c++·经验分享·面试·笔试