一篇文章助你彻底掌握FFmpeg 7.1播放器核心原理

视频讲解及完整源码领取:五分钟让你掌握FFmpeg 7.1播放器核心原理!

1. 播放器框架讲解

1.1 为什么需要这样的框架设计?

问题背景:

  • 媒体文件包含多种数据流(音频、视频、字幕等)
  • 音视频处理速度不同,需要独立处理避免相互阻塞
  • 解码是CPU密集型操作,需要并行处理提高性能
  • 音视频播放需要精确的时间同步

解决方案:多线程流水线架构

1.2 整体架构流程图

完整流程说明:

🎯 数据流转路径

  1. 媒体文件输入 → 解复用器读取文件头和数据包
  2. 流分离阶段 → 按stream_index分发到音视频包队列
  3. 并行解码 → 音视频独立线程处理,避免相互阻塞
  4. 队列缓冲 → 平衡生产消费速度差异
  5. 格式转换 → 原始数据适配硬件设备要求
  6. 同步输出 → 音频为主时钟,视频跟随同步

⚡ 关键性能特性

  • 多线程并行:解复用、解码、输出各自独立运行
  • 零拷贝技术:使用move_ref避免大量数据复制
  • 智能缓冲:包队列100个,帧队列10个的流控策略
  • 硬件加速:支持GPU解码和SDL硬件渲染

🔄 错误恢复机制

  • 线程协作退出:通过abort_标志优雅停止
  • 资源自动清理:RAII模式确保内存正确释放
  • 超时保护:队列操作支持超时避免死锁

1.3 类继承关系图

完整类设计说明:

继承层次结构

1.Thread基类

  • 提供统一的线程管理接口
  • RAII模式:析构时自动停止线程
  • 协作式退出:通过abort_标志优雅停止

2.具体线程类

  • DemuxThread:专职文件读取和流分离
  • DecodeThread:可复用的音视频解码器

模板设计模式

1.Queue<T>模板类

  • 泛型设计支持任意数据类型
  • 线程安全:mutex + condition_variable
  • 超时机制:避免无限阻塞

2.特化实现

  • AVPacketQueue:针对FFmpeg数据包优化
  • AVFrameQueue:针对FFmpeg帧数据优化

💡 设计优势

  • 代码复用:Thread基类和Queue模板减少重复代码
  • 类型安全:模板编译时类型检查
  • 资源管理:析构函数确保资源正确释放
  • 扩展性:易于添加新的线程类型和队列类型

1.4 核心组件设计原理

1.4.1 为什么使用Thread基类?

设计目的:

  • 统一接口: 所有线程类都有相同的启动、停止接口
  • 生命周期管理: 基类负责线程的创建、销毁和异常处理
  • 优雅退出: 通过abort_标志实现线程的协作式退出

Thread基类核心实现:

class Thread {
protected:
    int abort_ = 0;                    // 退出标志
    std::thread *thread_ = NULL;       // 线程句柄
public:
    virtual ~Thread() {
        if(thread_) Thread::Stop();    // 析构时自动停止线程
    }
    virtual void Run() = 0;            // 纯虚函数,子类必须实现
};

1.4.2 为什么使用Queue模板类?

设计目的:

  • 代码复用: AVPacketQueue和AVFrameQueue共享相同的队列逻辑
  • 类型安全: 模板确保编译时类型检查
  • 线程安全: 统一的互斥锁和条件变量机制
  • 阻塞控制: 支持超时和中止操作

1.4.3 为什么分离Packet队列和Frame队列?

设计原理:

  • 数据大小差异: Packet是压缩数据(KB级),Frame是原始数据(MB级)
  • 处理速度差异: 解复用速度快,解码速度慢
  • 内存管理策略: Packet队列可以缓存更多数据,Frame队列需要控制内存使用

1.5 线程间通信序列图

完整通信流程说明:

🚀 启动时序

  1. 主线程启动各工作线程:解复用 → 音频解码 → 视频解码
  2. 队列初始化:所有队列处于就绪状态,等待数据

📊 数据流转控制

  1. 流控机制:包队列100个上限,帧队列10个上限
  2. 背压处理:队列满时生产者暂停,实现自然流控
  3. 超时保护:所有队列操作支持超时,避免死锁

同步时序

  1. 音频驱动:SDL音频回调连续触发,设置主时钟
  2. 视频跟随:根据时间戳与音频时钟比较决定显示时机
  3. 精度控制:毫秒级同步精度,保证音画一致

错误处理

  1. 协作式退出:abort_标志在所有线程间传播
  2. 资源清理:队列Abort()确保所有缓存数据正确释放
  3. 优雅停止:Stop()等待线程完成当前操作后退出

2. 解复用模块开发

2.1 为什么需要解复用模块?

问题分析:

  • 媒体文件(如MP4、MKV)包含多个流:音频流、视频流、字幕流等
  • 这些流数据交错存储在文件中,需要分离处理
  • 不同流有不同的编码格式和参数
  • 需要获取流的元数据信息用于后续解码

解复用模块的作用:

  1. 文件解析: 解析容器格式,获取流信息
  2. 流分离: 将交错的音视频数据分离到不同队列
  3. 元数据提供: 为解码器提供编码参数
  4. 流控制: 控制读取速度,避免内存溢出

2.2.1 核心数据结构和FFmpeg结构体详解

class DemuxThread : public Thread {
private:
    std::string url_;                    // 媒体文件路径
    AVFormatContext *ifmt_ctx_ = NULL;   // 格式上下文
    int audio_index_ = -1;               // 音频流索引
    int video_index_ = -1;               // 视频流索引
    AVPacketQueue *audio_queue_ = NULL;  // 音频包队列
    AVPacketQueue *video_queue_ = NULL;  // 视频包队列
    char err2str[256] = {0};            // 错误信息缓冲区
};

核心FFmpeg结构体详解:

1. AVFormatContext 结构体

typedef struct AVFormatContext {
    const AVClass *av_class;           // 用于日志和选项
    const struct AVInputFormat *iformat;  // 输入格式
    AVIOContext *pb;                   // I/O上下文
    unsigned int nb_streams;           // 流的数量
    AVStream **streams;                // 流数组指针
    char *url;                         // 输入或输出URL
    int64_t duration;                  // 总时长(微秒)
    int64_t bit_rate;                  // 总比特率
    AVDictionary *metadata;            // 元数据
    // ... 更多字段
} AVFormatContext;

作用:

  • 封装了整个媒体文件的信息
  • 管理所有的音视频流
  • 提供文件格式相关的操作接口

2. AVStream 结构体

typedef struct AVStream {
    int index;                         // 在AVFormatContext中的索引
    int id;                           // 流ID
    AVCodecParameters *codecpar;       // 编码参数
    AVRational time_base;             // 时间基
    int64_t start_time;               // 开始时间
    int64_t duration;                 // 持续时间
    int64_t nb_frames;                // 帧数(如果已知)
    AVDictionary *metadata;           // 流元数据
    // ... 更多字段
} AVStream;

3. AVRational 结构体

typedef struct AVRational {
    int num;                          // 分子
    int den;                          // 分母
} AVRational;

作用和使用:

  • 表示有理数,用于精确的时间计算
  • time_base: 时间单位,如{1, 90000}表示1/90000秒
  • 转换为浮点数: double seconds = av_q2d(time_base) * pts

2.2.2 关键FFmpeg 7.1 API详解

1. avformat_alloc_context()

AVFormatContext *avformat_alloc_context(void);
  • 功能: 分配AVFormatContext结构体
  • 返回值: 成功返回AVFormatContext指针,失败返回NULL
  • 使用场景: 在打开输入文件前必须先分配上下文
  • 内存管理: 需要用avformat_free_context()释放

2. avformat_open_input()

int avformat_open_input(AVFormatContext **ps, const char *filename, 
                       const AVInputFormat *fmt, AVDictionary **options);

功能: 打开输入文件并读取文件头

参数详解:

  • ps: AVFormatContext指针的指针,如果*ps为NULL会自动分配
  • filename: 文件路径或URL
  • fmt: 输入格式,NULL为自动检测
  • options: 选项字典,可以传入解复用器特定选项

返回值: 成功返回0,失败返回负的错误码

FFmpeg 7.1变化: 增强了网络协议支持,改进错误处理

3. avformat_find_stream_info()

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

功能: 读取数据包以获取流信息

重要性: 获取编码器参数、时长、比特率等关键信息

注意事项:

  • 对直播流可能阻塞较长时间
  • 会读取并缓存一些数据包

FFmpeg 7.1改进: 对AV1、VVC等新编码格式支持更好

4. av_find_best_stream()

int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type,
                       int wanted_stream_nb, int related_stream,
                       const AVCodec **decoder_ret, int flags);

功能: 选择指定类型的最佳流

参数详解:

  • type: AVMEDIA_TYPE_AUDIO或AVMEDIA_TYPE_VIDEO
  • wanted_stream_nb: -1为自动选择最佳流
  • related_stream: 相关流索引(用于选择匹配的音视频流)
  • decoder_ret: 返回对应的解码器(可为NULL)

选择策略: 优先选择质量高、兼容性好的流

5. av_read_frame()

int av_read_frame(AVFormatContext *s, AVPacket *pkt);

功能: 从输入中读取下一个数据包

重要特性:

  • 返回的包可能是任何流的数据
  • 包含完整的编码帧数据
  • 自动处理容器格式的解析

返回值:

  • 0: 成功
  • AVERROR_EOF: 文件结束
  • 其他负值: 错误

2.2.3 解复用流程实现和流控策略

void DemuxThread::Run() {
    LogInfo("Run into");
    int ret = 0;
    AVPacket pkt;
    
    while (abort_ != 1) {
        // 流控:防止队列过大占用过多内存, ffplay
        if(audio_queue_->Size() > 100 || video_queue_->Size() > 100) {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            continue;
        }
        
        // 读取数据包
        ret = av_read_frame(ifmt_ctx_, &pkt);
        if(ret < 0) {
            av_strerror(ret, err2str, sizeof(err2str));
            LogError("av_read_frame failed, ret:%d, err2str:%s", ret, err2str);
            break;
        }
        
        // 根据流索引分发到不同队列
        if(pkt.stream_index == audio_index_) {
            log_packet(ifmt_ctx_, &pkt, "ain");  // 音频包日志
            ret = audio_queue_->Push(&pkt);
        } else if(pkt.stream_index == video_index_) {
            log_packet(ifmt_ctx_, &pkt, "vin");  // 视频包日志
            ret = video_queue_->Push(&pkt);
        } else {
            av_packet_unref(&pkt);  // 释放不需要的包(字幕、数据流等)
        }
    }
    LogInfo("Run finish");
}

流控策略说明:

  1. 队列大小限制: 音视频队列各限制100个包,防止内存爆炸
  2. 背压机制: 队列满时解复用线程等待10ms再继续
  3. 选择性处理: 只处理音视频流,其他流直接丢弃
  4. 错误处理: 使用av_strerror()获取详细错误信息

2.2.4 AVPacket结构体详解

typedef struct AVPacket {
    AVBufferRef *buf;                  // 数据缓冲区引用
    int64_t pts;                       // 显示时间戳
    int64_t dts;                       // 解码时间戳
    uint8_t *data;                     // 压缩数据指针
    int size;                          // 数据大小
    int stream_index;                  // 所属流索引
    int flags;                         // 标志位(关键帧等)
    AVPacketSideData *side_data;       // 附加数据
    int side_data_elems;               // 附加数据数量
    int64_t duration;                  // 包持续时间
    int64_t pos;                       // 在文件中的位置
} AVPacket;

重要字段说明:

  • pts/dts: PTS用于显示时序,DTS用于解码时序
  • stream_index: 标识数据包属于哪个流
  • flags: AV_PKT_FLAG_KEY标识关键帧
  • data/size: 实际的编码数据和大小

3. 音视频包队列设计

3.1 为什么需要包队列?

设计需求分析:

  • 解耦合: 解复用线程和解码线程速度不匹配,需要缓冲
  • 平滑播放: 网络抖动或I/O延迟时,队列提供数据缓冲
  • 内存管理: 统一管理AVPacket的生命周期
  • 线程安全: 多线程环境下的数据交换

队列设计目标:

  1. 线程安全: 支持多生产者多消费者模式
  2. 内存高效: 避免不必要的数据拷贝
  3. 阻塞控制: 支持超时和中止操作
  4. 流量控制: 防止内存使用过多

完整队列设计说明:

🏗️ 模板设计架构

1.Queue<T>泛型模板

  • 支持任意数据类型的线程安全队列
  • 统一的接口设计:Push()、Pop()、Front()、Size()、Abort()
  • 内置流控机制:超时等待避免死锁

2.线程安全机制

  • 互斥锁(mutex):保护队列数据结构
  • 条件变量(cond):高效的生产者-消费者通知
  • 原子操作:abort_标志的线程安全设置

📦 AVPacketQueue特化实现

1.编码数据包管理

  • 存储压缩后的音视频数据(KB级别)
  • 支持100个包的缓冲队列(约几MB内存)
  • av_packet_move_ref()零拷贝传递

2.内存优化策略

  • 引用移动而非数据拷贝
  • 自动释放机制防止内存泄漏
  • 析构时清理所有残留数据

🖼️ AVFrameQueue特化实现

1.原始帧数据管理

  • 存储解码后的音视频数据(MB级别)
  • 限制10帧缓冲控制内存使用
  • 支持Front()预览不出队(视频同步需要)

2.同步支持特性

  • Front()方法支持视频时间戳检查
  • Pop()方法实际消费帧数据
  • 帧数据较大,严格控制队列长度

⚡ 性能优化特点

  • 零拷贝传递:move_ref技术避免大量数据复制
  • 智能流控:队列满时自动阻塞生产者
  • 超时机制:避免线程无限等待
  • 优雅退出:Abort()方法安全终止所有等待操作

基于标准库实现的通用线程安全队列:

template <typename T>
class Queue {
private:
    std::mutex mutex_;                    // 互斥锁
    std::condition_variable cond_;        // 条件变量
    std::queue<T> queue_;                // STL队列
    int abort_ = 0;                      // 中止标志
public:
    int Push(T val);                     // 入队操作
    int Pop(T &val, const int timeout); // 出队操作(支持超时)
    void Abort();                        // 中止队列操作
};

3.1.2 AVPacket内存管理

FFmpeg 7.1内存管理要点:

int AVPacketQueue::Push(AVPacket *val) {
    AVPacket *tmp_pkt = av_packet_alloc();  // 分配新包结构
    av_packet_move_ref(tmp_pkt, val);       // 移动引用(避免拷贝)
    return queue_.Push(tmp_pkt);
}

AVPacket *AVPacketQueue::Pop(const int timeout) {
    AVPacket *tmp_pkt = NULL;
    int ret = queue_.Pop(tmp_pkt, timeout);
    return tmp_pkt;  // 返回包指针,调用者负责释放
}

关键FFmpeg 7.1 API详解:

1. av_packet_alloc()

AVPacket *av_packet_alloc(void);
  • 功能: 分配并初始化AVPacket结构体
  • 返回: 成功返回AVPacket指针,失败返回NULL
  • 内存管理: 必须用av_packet_free()释放

2. av_packet_move_ref()

void av_packet_move_ref(AVPacket *dst, AVPacket *src);
  • 功能: 移动包引用,避免数据拷贝
  • 效果: src被重置为空,dst获得所有权
  • 应用: 队列传递时的标准操作

3. av_packet_free()

void av_packet_free(AVPacket **pkt);
  • 功能: 释放包结构体及数据,指针置NULL
  • 安全性: 可重复调用,防止重复释放

4. 解码模块开发

4.1 为什么需要解码模块?

解码需求分析:

  • 数据转换: 将压缩数据(H264/AAC)转换为原始数据(YUV/PCM)
  • 并行处理: 音视频独立解码,提高效率
  • 错误恢复: 处理损坏数据包,保证播放连续性
  • 格式适配: 支持多种编码格式的统一接口

解码模块作用:

  1. 解码器管理: 初始化和配置解码器
  2. 数据转换: 执行实际的解码操作
  3. 错误处理: 处理解码失败的情况
  4. 资源管理: 管理解码器和帧的生命周期

4.2 DecodeThread解码流程图

完整解码流程说明:

🏗️ 解码器初始化阶段(蓝色路径)

1.参数获取阶段

  • AVCodecParameters输入:从DemuxThread获取流的编码参数
  • 包含codec_id、width/height、sample_rate、extradata(SPS/PPS)等关键信息

2.解码器准备阶段

  • avcodec_alloc_context3():为解码器分配工作内存空间
  • avcodec_parameters_to_context():将流参数复制到解码器上下文
  • avcodec_find_decoder():根据codec_id查找对应解码器(H264、AAC等)
  • avcodec_open2():初始化解码器,使其进入工作状态

🔄 解码处理循环(紫色路径)

1.数据获取阶段

  • PacketQueue::Pop():从包队列超时获取数据包(10ms)
  • 支持流控:当帧队列>10时解码线程暂停

2.解码执行阶段

  • avcodec_send_packet():将编码包发送到解码器内部缓冲区
  • avcodec_receive_frame():从解码器获取解码后的原始帧

3.结果处理阶段

  • 成功(0):获得完整帧数据,推入FrameQueue
  • EAGAIN:解码器需要更多输入包,继续等待
  • 错误:解码失败,记录错误并准备退出

🚨 错误处理机制(红色路径)

1.错误类型检测

  • 数据包损坏:文件损坏或网络传输错误
  • 格式不匹配:解码器不支持该编码格式
  • 内存不足:系统资源耗尽

2.错误恢复策略

  • av_strerror():获取详细错误描述信息
  • abort_标志设置:通知所有相关线程停止工作
  • 资源清理:确保解码器和内存正确释放

⚡ 性能优化特点

  • 流水线处理:解码器内部有缓冲,支持并行处理
  • 智能流控:帧队列大小控制防止内存爆炸
  • 超时保护:队列操作超时机制避免死锁
  • 零拷贝优化:av_frame_move_ref避免数据复制

解码流程详细说明:

4.2.1 解码器初始化阶段(蓝色区域)

  1. AVCodecParameters输入 - 从解复用器获取的编码参数,包含编码格式、尺寸、采样率等信息
  2. avcodec_alloc_context3() - 分配解码器上下文内存,为解码器提供工作空间
  3. avcodec_parameters_to_context() - 将流参数复制到解码器上下文,让解码器知道如何处理数据
  4. avcodec_find_decoder() - 根据编码ID查找对应的解码器(如H264、AAC解码器)
  5. avcodec_open2() - 打开并初始化解码器,使其进入就绪状态

4.2.2 解码处理循环(紫色区域)

PacketQueue::Pop() - 从包队列中获取编码数据包(阻塞等待10ms)

avcodec_send_packet() - 将数据包发送到解码器内部缓冲区

avcodec_receive_frame() - 从解码器获取解码后的原始帧数据

解码成功判断:

  • 成功(0): 获得完整解码帧,推入帧队列
  • EAGAIN: 解码器需要更多输入数据,继续读取包
  • 其他错误: 解码失败,进入错误处理

4.2.3 错误处理机制(红色区域)

  • 错误检测 - 识别解码过程中的各种错误(数据损坏、格式不匹配等)
  • abort_标志 - 设置线程退出标志,优雅地停止解码线程
  • 资源清理 - 确保在错误状态下正确释放资源

4.2.4 关键设计特点

  • 流水线处理: 解码器内部有缓冲,可以并行处理多个包
  • 错误恢复: EAGAIN状态允许解码器在数据不足时继续等待
  • 线程安全: 通过队列机制实现与其他线程的安全数据交换
  • 内存管理: 每个阶段都有明确的内存分配和释放策略

性能优化要点:

  • 控制帧队列大小(≤10帧)防止内存过度占用
  • 使用超时机制避免线程无限阻塞
  • 错误时及时退出,避免无效处理消耗CPU

4.3 DecodeThread类详细设计

4.3.1 核心数据结构

class DecodeThread : public Thread {
private:
    char err2str[256] = {0};              // 错误信息缓冲区
    AVCodecContext *codec_ctx_ = NULL;    // 编解码器上下文
    AVPacketQueue *packet_queue_ = NULL;  // 输入包队列
    AVFrameQueue *frame_queue_ = NULL;    // 输出帧队列
};

4.2.2 关键FFmpeg结构体详解

1. AVCodecContext结构体

typedef struct AVCodecContext {
    const AVClass *av_class;              // 日志和选项系统
    enum AVMediaType codec_type;          // 编码类型(音频/视频)
    const struct AVCodec *codec;          // 编解码器
    enum AVCodecID codec_id;              // 编码器ID
    
    // 视频相关
    int width, height;                    // 图像尺寸
    enum AVPixelFormat pix_fmt;           // 像素格式
    
    // 音频相关
    int sample_rate;                      // 采样率
    AVChannelLayout ch_layout;            // 声道布局 (FFmpeg 7.1新特性)
    enum AVSampleFormat sample_fmt;       // 采样格式
    
    // 通用
    AVRational time_base;                 // 时间基
    int64_t bit_rate;                     // 比特率
} AVCodecContext;

2. AVCodecParameters结构体

typedef struct AVCodecParameters {
    enum AVMediaType codec_type;          // 媒体类型
    enum AVCodecID codec_id;              // 编码器ID
    uint32_t codec_tag;                   // 编码器标签
    
    uint8_t *extradata;                   // 额外数据(如SPS/PPS)
    int extradata_size;                   // 额外数据大小
    
    int format;                           // 格式(像素格式或采样格式)
    int64_t bit_rate;                     // 比特率
    
    // 视频参数
    int width, height;                    // 视频尺寸
    AVRational sample_aspect_ratio;       // 采样宽高比
    
    // 音频参数
    int sample_rate;                      // 采样率
    AVChannelLayout ch_layout;            // 声道布局
    int frame_size;                       // 帧大小
} AVCodecParameters;

4.2.3 解码器初始化详细流程

int DecodeThread::Init(AVCodecParameters *par) {
    if(!par) {
        LogError("Init par is null");
        return -1;
    }
    
    // 1. 分配编解码器上下文
    codec_ctx_ = avcodec_alloc_context3(NULL);
    if(!codec_ctx_) {
        LogError("avcodec_alloc_context3 failed");
        return -1;
    }
    
    // 2. 复制参数到上下文
    int ret = avcodec_parameters_to_context(codec_ctx_, par);
    if(ret < 0) {
        av_strerror(ret, err2str, sizeof(err2str));
        LogError("avcodec_parameters_to_context failed, ret:%d, err2str:%s", ret, err2str);
        return -1;
    }
    
    // 3. 查找解码器
    const AVCodec *codec = avcodec_find_decoder(codec_ctx_->codec_id);
    if(!codec) {
        LogError("avcodec_find_decoder failed for codec_id:%d", codec_ctx_->codec_id);
        return -1;
    }
    
    // 4. 打开解码器
    ret = avcodec_open2(codec_ctx_, codec, NULL);
    if(ret < 0) {
        av_strerror(ret, err2str, sizeof(err2str));
        LogError("avcodec_open2 failed, ret:%d, err2str:%s", ret, err2str);
        return -1;
    }
    
    LogInfo("Decoder initialized: %s", codec->name);
    return 0;
}

4.2.4 FFmpeg 7.1解码API详解

1. avcodec_alloc_context3()

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
  • 功能: 分配并初始化AVCodecContext
  • 参数: codec可为NULL,后续通过avcodec_open2指定
  • 返回: 成功返回上下文指针,失败返回NULL

2. avcodec_parameters_to_context()

int avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par);
  • 功能: 将流参数复制到编解码器上下文
  • 重要性: 解码器需要这些参数进行正确解码
  • FFmpeg 7.1变化: 更好支持新的声道布局API

3. avcodec_find_decoder()

const AVCodec *avcodec_find_decoder(enum AVCodecID id);
  • 功能: 根据编码器ID查找对应的解码器
  • 返回: 找到返回解码器指针,否则返回NULL
  • 支持的格式: H264, H265, AV1, AAC, MP3等

4. avcodec_open2()

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
  • 功能: 初始化并打开编解码器
  • 参数: options可传递编解码器特定选项
  • 重要性: 必须成功调用后才能开始编解码

5. avcodec_send_packet()

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
  • 功能: 向解码器发送压缩包
  • 特性: 非阻塞,可能返回EAGAIN
  • FFmpeg 7.1: 改进了错误处理机制

6. avcodec_receive_frame()

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

功能: 从解码器接收解码后的帧

返回值:

  • 0: 成功获得帧
  • AVERROR(EAGAIN): 需要更多输入
  • AVERROR_EOF: 解码器已刷新完毕
  • 其他负值: 解码错误

4.2.5 解码线程运行流程

void DecodeThread::Run() {
    AVFrame *frame = av_frame_alloc();
    LogInfo("DecodeThread::Run info");
    
    while (abort_ != 1) {
        // 流控:控制解码帧队列大小
        if(frame_queue_->Size() > 10) {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            continue;
        }
        
        // 从包队列获取数据
        AVPacket *pkt = packet_queue_->Pop(10);
        if(pkt) {
            // 发送包到解码器
            int ret = avcodec_send_packet(codec_ctx_, pkt);
            av_packet_free(&pkt);  // 立即释放包
            
            if(ret < 0) {
                av_strerror(ret, err2str, sizeof(err2str));
                LogError("avcodec_send_packet failed, ret:%d, err2str:%s", ret, err2str);
                break;
            }
            
            // 接收解码帧(可能有多帧)
            while (true) {
                ret = avcodec_receive_frame(codec_ctx_, frame);
                if(ret == 0) {
                    frame_queue_->Push(frame);
                    LogInfo("%s frame queue size %d", codec_ctx_->codec->name, frame_queue_->Size());
                    continue;
                } else if(AVERROR(EAGAIN) == ret) {
                    break;  // 需要更多输入包
                } else {
                    abort_ = 1;
                    av_strerror(ret, err2str, sizeof(err2str));
                    LogError("avcodec_receive_frame failed, ret:%d, err2str:%s", ret, err2str);
                    break;
                }
            }
        }
    }
    LogInfo("DecodeThread::Run Finish");
}

4.2.6 硬件加速支持(进阶特性)

// 示例:启用NVIDIA CUVID硬件解码
if(codec_ctx_->codec_id == AV_CODEC_ID_H264) {
    const AVCodec *hw_codec = avcodec_find_decoder_by_name("h264_cuvid");
    if(hw_codec) {
        codec = hw_codec;  // 使用硬件解码器
    }
}

FFmpeg 7.1硬件加速改进:

  • 更好的GPU内存管理
  • 支持更多硬件平台(Intel QSV、AMD AMF等)
  • 改进的硬件解码器自动检测

5. 音视频帧队列设计

5.1 为什么需要帧队列?

设计需求:

  • 大数据处理: 解码后的帧数据比包数据大10-100倍
  • 格式转换: 需要缓存原始数据供后续处理
  • 显示同步: 视频帧需要按时间戳顺序显示
  • 音频连续性: 音频帧需要连续供给避免播放卡顿

5.2 AVFrameQueue详细实现

5.2.1 AVFrame结构体详解

typedef struct AVFrame {
    uint8_t *data[AV_NUM_DATA_POINTERS];    // 数据指针数组
    int linesize[AV_NUM_DATA_POINTERS];     // 每行字节数
    
    int width, height;                       // 视频尺寸
    int nb_samples;                         // 音频样本数
    int format;                             // 像素格式或采样格式
    
    int64_t pts;                            // 显示时间戳
    int64_t pkt_dts;                        // 包解码时间戳
    int64_t duration;                       // 帧持续时间
    
    AVChannelLayout ch_layout;              // 声道布局(FFmpeg 7.1新特性)
    int sample_rate;                        // 采样率
    
    AVBufferRef *buf[AV_NUM_DATA_POINTERS]; // 缓冲区引用
    AVFrameSideData **side_data;            // 附加数据
} AVFrame;

5.2.2 帧队列核心实现

class AVFrameQueue {
private:
    Queue<AVFrame *> queue_;                // 帧指针队列
    
public:
    int Push(AVFrame *val) {
        AVFrame *tmp_frame = av_frame_alloc();
        av_frame_move_ref(tmp_frame, val);   // 移动引用,避免拷贝
        return queue_.Push(tmp_frame);
    }
    
    AVFrame *Pop(const int timeout) {
        AVFrame *tmp_frame = NULL;
        int ret = queue_.Pop(tmp_frame, timeout);
        if(ret < 0 && ret == -1) {
            LogError("AVFrameQueue::Pop failed");
        }
        return tmp_frame;                    // 调用者负责释放
    }
    
    AVFrame *Front() {                       // 获取但不移除队首帧
        AVFrame *tmp_frame = NULL;
        int ret = queue_.Front(tmp_frame);
        if(ret < 0 && ret == -1) {
            LogError("AVFrameQueue::Front failed");
        }
        return tmp_frame;
    }
    
    ~AVFrameQueue() {
        Abort();                             // 析构时清理
    }
    
private:
    void release() {
        while (true) {
            AVFrame *frame = NULL;
            int ret = queue_.Pop(frame, 1);
            if(ret < 0) break;
            av_frame_free(&frame);           // 释放帧数据
        }
    }
};

5.2.3 FFmpeg 7.1帧管理API详解

1. av_frame_alloc()

AVFrame *av_frame_alloc(void);
  • 功能: 分配AVFrame结构体并初始化
  • 注意: 只分配结构体,不分配数据缓冲区
  • 释放: 必须用av_frame_free()释放

2. av_frame_move_ref()

void av_frame_move_ref(AVFrame *dst, AVFrame *src);
  • 功能: 移动帧引用,src被重置
  • 优势: 避免大量数据拷贝,高效传递
  • 应用: 队列传递的标准操作

3. av_frame_free()

void av_frame_free(AVFrame **frame);
  • 功能: 释放帧结构体及其数据
  • 安全性: 指针会被置为NULL

4. av_frame_clone()

AVFrame *av_frame_clone(const AVFrame *src);
  • 功能: 深度拷贝帧数据
  • 使用场景: 需要修改帧数据时
  • 性能: 开销较大,谨慎使用

6. 声音输出和视频渲染

6.1 为什么需要音视频输出模块?

输出需求分析:

  • 格式转换: 解码器输出格式可能与设备要求不匹配
  • 设备适配: 不同音频设备支持的格式不同
  • 实时播放: 需要精确的时间控制保证流畅播放
  • 同步控制: 音视频必须保持同步

6.2 音视频输出和同步架构图

完整音视频输出和同步流程说明:

🎵 音频输出处理流程(绿色路径)

1.数据获取阶段

  • AudioFrameQueue:存储解码后的PCM原始音频数据
  • SDL回调触发:音频设备以固定频率(如每10ms)请求数据

2.格式适配处理

  • 格式检查:比较源音频格式与SDL设备支持格式
  • 重采样处理:使用SwrContext进行采样率、声道、格式转换
  • 直接拷贝:格式匹配时避免不必要的处理开销

3.音频时钟设置

  • 时间戳转换:将AVRational时间基转换为秒
  • 主时钟更新:音频连续播放,作为整个播放系统的时钟基准

🎥 视频输出处理流程(蓝色路径)

1.视频同步检查

  • 帧时间计算:将视频帧PTS转换为绝对时间
  • 时钟对比:与音频主时钟比较确定显示时机

2.同步策略处理

  • 帧太早(diff>0):延迟显示,等待正确时间
  • 帧正常(diff≈0):立即渲染显示
  • 帧太晚(diff<0):立即显示(简化版本不丢帧)

3.GPU渲染输出

  • 纹理更新:将YUV数据传递给GPU纹理
  • 硬件渲染:利用GPU加速进行颜色空间转换和缩放

⏱️ 音视频同步机制(橙色连接)

1.音频主时钟

  • 音频设备连续播放,无法暂停或变速
  • 提供稳定的时间基准给视频同步

2.视频跟随策略

  • 视频根据音频时钟调整显示时机
  • 支持帧率不匹配的自适应处理

3.同步精度控制

  • 毫秒级精度保证音画同步
  • 简化版本允许视频略微滞后但不丢帧

🔧 技术优化特点

  • SDL硬件加速:音视频都利用硬件加速
  • 智能重采样:仅在必要时进行格式转换
  • 零拷贝优化:GPU纹理直接接收YUV数据
  • 流控同步:通过时间戳精确控制播放节奏

6.3 AudioOutput详细设计

6.3.1 AudioOutput核心数据结构

typedef struct AudioParams {
    int freq;                           // 采样率
    AVChannelLayout ch_layout;          // 声道布局(FFmpeg 7.1新特性)
    enum AVSampleFormat fmt;            // 采样格式
    int frame_size;                     // 帧大小
} AudioParams;
​
class AudioOutput {
private:
    AudioParams src_tgt_;               // 源音频参数
    AudioParams dst_tgt_;               // SDL输出参数
    SwrContext *swr_ctx_ = NULL;        // 重采样上下文
    
    uint8_t *audio_buf_ = NULL;         // 当前音频缓冲区
    uint8_t *audio_buf1_ = NULL;        // 重采样缓冲区
    uint32_t audio_buf_size = 0;        // 缓冲区大小
    uint32_t audio_buf_index = 0;       // 当前读取位置
    
    int64_t pts_ = AV_NOPTS_VALUE;      // 当前时间戳
    AVSync *avsync_ = NULL;             // 同步控制器
    AVRational time_base_;              // 时间基
    FILE *dump_pcm_ = NULL;             // PCM数据转储
};

6.2.2 FFmpeg 7.1音频API详解

1. AVChannelLayout结构体(新特性)

typedef struct AVChannelLayout {
    enum AVChannelOrder order;          // 声道排列方式
    int nb_channels;                    // 声道数量
    union {
        uint64_t mask;                  // 传统声道掩码
        AVChannelCustom *map;           // 自定义声道映射
    } u;
    void *opaque;                       // 不透明数据
} AVChannelLayout;

2. SwrContext和重采样API

// 分配和设置重采样上下文
int swr_alloc_set_opts2(SwrContext **ps,
    const AVChannelLayout *out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
    const AVChannelLayout *in_ch_layout,  enum AVSampleFormat in_sample_fmt,  int in_sample_rate,
    int log_offset, void *log_ctx);
​
// 执行重采样
int swr_convert(SwrContext *s, uint8_t **out, int out_count,
                const uint8_t **in,  int in_count);

6.2.3 音频回调函数详细实现

void fill_audio_pcm(void *udata, Uint8 *stream, int len) {
    AudioOutput *is = (AudioOutput *)udata;
    int len1 = 0;
    
    while (len > 0) {
        if(is->audio_buf_index == is->audio_buf_size) {
            is->audio_buf_index = 0;
            AVFrame *frame = is->frame_queue_->Pop(10);
            
            if(frame) {
                is->pts_ = frame->pts;
                
                // 检查是否需要重采样
                if(((frame->format != is->dst_tgt_.fmt) ||
                    (frame->sample_rate != is->dst_tgt_.freq) ||
                    av_channel_layout_compare(&frame->ch_layout, &is->dst_tgt_.ch_layout)) 
                   && (!is->swr_ctx_)) {
                   
                    // 创建重采样上下文
                    swr_alloc_set_opts2(&is->swr_ctx_,
                        &is->dst_tgt_.ch_layout, (AVSampleFormat)is->dst_tgt_.fmt, is->dst_tgt_.freq,
                        &frame->ch_layout, (AVSampleFormat)frame->format, frame->sample_rate,
                        0, NULL);
                        
                    if(swr_init(is->swr_ctx_) < 0) {
                        LogError("swr_init failed");
                        swr_free(&is->swr_ctx_);
                        return;
                    }
                }
                
                if(is->swr_ctx_) {
                    // 重采样处理
                    const uint8_t **in = (const uint8_t **)frame->extended_data;
                    uint8_t **out = &is->audio_buf1_;
                    int out_samples = frame->nb_samples * is->dst_tgt_.freq / frame->sample_rate + 256;
                    
                    int len2 = swr_convert(is->swr_ctx_, out, out_samples, in, frame->nb_samples);
                    is->audio_buf_ = is->audio_buf1_;
                    is->audio_buf_size = av_samples_get_buffer_size(NULL, 
                        is->dst_tgt_.ch_layout.nb_channels, len2, is->dst_tgt_.fmt, 1);
                } else {
                    // 直接使用原始数据
                    int audio_size = av_samples_get_buffer_size(NULL, 
                        frame->ch_layout.nb_channels, frame->nb_samples, 
                        (AVSampleFormat)frame->format, 1);
                    is->audio_buf_ = is->audio_buf1_;
                    is->audio_buf_size = audio_size;
                    memcpy(is->audio_buf_, frame->data[0], audio_size);
                }
                av_frame_free(&frame);
            }
        }
        
        // 拷贝数据到SDL音频流
        len1 = is->audio_buf_size - is->audio_buf_index;
        if(len1 > len) len1 = len;
        
        if(is->audio_buf_) {
            memcpy(stream, is->audio_buf_ + is->audio_buf_index, len1);
            // PCM数据转储(调试用)
            if(is->dump_pcm_) {
                fwrite(is->audio_buf_ + is->audio_buf_index, 1, len1, is->dump_pcm_);
                fflush(is->dump_pcm_);
            }
        }
        
        len -= len1;
        stream += len1;
        is->audio_buf_index += len1;
    }
    
    // 设置音频时钟
    if(is->pts_ != AV_NOPTS_VALUE) {
        double pts = is->pts_ * av_q2d(is->time_base_);
        is->avsync_->SetClock(pts);
    }
}

6.3 VideoOutput详细设计

6.3.1 视频渲染核心实现

class VideoOutput {
private:
    SDL_Window *win_ = NULL;
    SDL_Renderer *renderer_ = NULL;
    SDL_Texture *texture_ = NULL;
    SDL_Rect rect_;
    
    AVFrameQueue *frame_queue_ = NULL;
    AVSync *avsync_ = NULL;
    AVRational time_base_;
    int video_width_, video_height_;
};
​
void VideoOutput::videoRefresh(double *remaining_time) {
    AVFrame *frame = frame_queue_->Front();  // 获取不移除
    if(frame) {
        double pts = frame->pts * av_q2d(time_base_);
        double diff = pts - avsync_->GetClock();  // 与音频时钟比较
        
        if(diff > 0) {
            *remaining_time = FFMIN(*remaining_time, diff);
            return;  // 帧显示时间未到
        }
        
        // 更新YUV纹理
        SDL_UpdateYUVTexture(texture_, &rect_,
            frame->data[0], frame->linesize[0],  // Y平面
            frame->data[1], frame->linesize[1],  // U平面
            frame->data[2], frame->linesize[2]); // V平面
            
        SDL_RenderClear(renderer_);
        SDL_RenderCopy(renderer_, texture_, NULL, &rect_);
        SDL_RenderPresent(renderer_);
        
        frame = frame_queue_->Pop(1);  // 移除已显示的帧
        av_frame_free(&frame);
    }
}

7. 音视频同步处理

7.1 为什么需要音视频同步?

同步问题分析:

  • 时钟差异: 音频和视频有独立的时钟源
  • 处理延迟: 音视频解码和输出速度不同
  • 设备特性: 音频设备连续播放,视频设备按帧显示
  • 用户体验: 音画不同步严重影响观看体验

7.2 AVSync时钟系统设计图

ERROR: [Mermaid] Lexical error on line 14. Unrecognized text.
...或加速] I -->|diff ≈ 0| L[正常显示]
----------------------^

但是这里要注意:目前我们是简化版本的音视频同步:

  • diff < 0
  • diff ≈ 0

两种状态都会做显示。 ffplay

7.3 AVSync同步算法详细实现

7.3.1 AVSync类详细实现

class AVSync {
private:
    double pts_drift_;                      // 时间漂移量
    
public:
    void InitClock() {
        SetClock(NAN);                      // 初始化为无效值
    }
    
    void SetClockAt(double pts, double time) {
        pts_drift_ = pts - time;            // 计算并设置漂移
    }
    
    void SetClock(double pts) {
        double time = GetMicroseconds() / 1000000.0;
        SetClockAt(pts, time);
    }
    
    double GetClock() {
        double time = GetMicroseconds() / 1000000.0;
        return pts_drift_ + time;           // 当前时钟时间
    }
    
    time_t GetMicroseconds() {
        auto time_point = std::chrono::system_clock::now();
        auto duration = time_point.time_since_epoch();
        return std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
    }
};

7.2.2 同步算法详细分析

1. 为什么选择音频时钟为主时钟?

  • 连续性: 音频不能中断,必须连续播放
  • 设备约束: 音频设备有固定的播放速率
  • 用户感知: 音频中断比视频卡顿更容易被察觉
  • 实现简单: 视频可以丢帧,音频不能丢样本

2. 同步策略实现

void VideoOutput::videoRefresh(double *remaining_time) {
    AVFrame *frame = frame_queue_->Front();
    if(frame) {
        double video_pts = frame->pts * av_q2d(time_base_);
        double audio_clock = avsync_->GetClock();
        double diff = video_pts - audio_clock;
        
        const double sync_threshold = 0.04;  // 40ms同步阈值
        
        if(diff > sync_threshold) {
            // 视频太快,延迟显示
            *remaining_time = FFMIN(*remaining_time, diff);
            return;
        } else if(diff < -sync_threshold) {
            // 视频太慢,可能需要丢帧
            LogInfo("Video lagging behind audio: %0.3f", diff);
        }
        
        // 正常显示帧
        DisplayFrame(frame);
        frame = frame_queue_->Pop(1);
        av_frame_free(&frame);
    }
}

3. 时间戳转换和计算

// AVRational时间基转换
double av_q2d(AVRational a) {
    return a.num / (double)a.den;
}
​
// 时间戳转换示例
AVRational time_base = {1, 90000};        // 时间基: 1/90000秒
int64_t pts = 90000;                      // PTS值
double seconds = pts * av_q2d(time_base); // 转换为秒: 1.0秒

8. FFmpeg 7.1 API详解

8.1 7-ffmpeg_sdl_player项目中使用的FFmpeg结构体分类详解

8.1.1 容器和格式相关结构体

1. AVFormatContext - 格式上下文

typedef struct AVFormatContext {
    const AVClass *av_class;               // 类信息,用于日志
    const AVInputFormat *iformat;          // 输入格式描述
    void *priv_data;                       // 私有数据
    AVIOContext *pb;                       // I/O上下文
    
    unsigned int nb_streams;               // 流的数量
    AVStream **streams;                    // 流数组
    char *url;                            // 文件URL
    
    int64_t start_time;                   // 开始时间 (AV_TIME_BASE单位)
    int64_t duration;                     // 持续时间 (AV_TIME_BASE单位)
    int64_t bit_rate;                     // 总比特率
    
    AVDictionary *metadata;               // 元数据
    AVProgram **programs;                 // 节目信息
    enum AVDiscard skip_streams;          // 跳过流的策略
} AVFormatContext;

2. AVStream - 流信息

typedef struct AVStream {
    int index;                            // 流索引
    int id;                              // 流ID
    AVCodecParameters *codecpar;          // 编码参数
    void *priv_data;                     // 私有数据
    
    AVRational time_base;                // 时间基
    int64_t start_time;                  // 开始时间
    int64_t duration;                    // 持续时间
    int64_t nb_frames;                   // 帧数(如果已知)
    
    enum AVDiscard discard;              // 丢弃策略
    AVRational sample_aspect_ratio;      // 采样宽高比
    AVDictionary *metadata;              // 流元数据
} AVStream;

8.1.2 编解码相关结构体

1. AVCodecParameters - 编码参数

typedef struct AVCodecParameters {
    enum AVMediaType codec_type;         // 媒体类型
    enum AVCodecID codec_id;            // 编码器ID
    uint32_t codec_tag;                 // 编码器标签
    
    uint8_t *extradata;                 // 额外数据(SPS/PPS等)
    int extradata_size;                 // 额外数据大小
    
    int format;                         // 格式(像素或采样格式)
    int64_t bit_rate;                   // 比特率
    int bits_per_coded_sample;          // 每样本位数
    int bits_per_raw_sample;            // 原始样本位数
    int profile;                        // 编码profile
    int level;                          // 编码level
    
    // 视频相关
    int width, height;                  // 尺寸
    AVRational sample_aspect_ratio;     // 采样宽高比
    enum AVFieldOrder field_order;     // 场序
    enum AVColorRange color_range;      // 色彩范围
    enum AVColorPrimaries color_primaries; // 色彩原色
    enum AVColorTransferCharacteristic color_trc; // 色彩传递特性
    enum AVColorSpace colorspace;       // 色彩空间
    
    // 音频相关
    AVChannelLayout ch_layout;          // 声道布局(FFmpeg 7.1新特性)
    int sample_rate;                    // 采样率
    int block_align;                    // 块对齐
    int frame_size;                     // 帧大小
} AVCodecParameters;

2. AVCodecContext - 编解码器上下文

typedef struct AVCodecContext {
    const AVClass *av_class;            // 类信息
    int log_level_offset;               // 日志级别偏移
    
    enum AVMediaType codec_type;        // 媒体类型
    const struct AVCodec *codec;        // 编解码器
    enum AVCodecID codec_id;           // 编码器ID
    
    // 视频相关
    enum AVPixelFormat pix_fmt;         // 像素格式
    int width, height;                  // 视频尺寸
    AVRational sample_aspect_ratio;     // 采样宽高比
    int gop_size;                       // GOP大小
    int max_b_frames;                   // 最大B帧数
    
    // 音频相关
    int sample_rate;                    // 采样率
    AVChannelLayout ch_layout;          // 声道布局
    enum AVSampleFormat sample_fmt;     // 采样格式
    int frame_size;                     // 帧大小
    
    // 通用
    AVRational time_base;               // 时间基
    int64_t bit_rate;                   // 比特率
    int global_quality;                 // 全局质量
    
    // 解码相关
    int thread_count;                   // 线程数
    enum AVDiscard skip_loop_filter;    // 跳过环路滤波
    enum AVDiscard skip_idct;           // 跳过IDCT
    enum AVDiscard skip_frame;          // 跳过帧
} AVCodecContext;

8.1.3 数据包和帧结构体

1. AVPacket - 编码数据包

typedef struct AVPacket {
    AVBufferRef *buf;                   // 缓冲区引用
    int64_t pts;                        // 显示时间戳
    int64_t dts;                        // 解码时间戳
    uint8_t *data;                      // 数据指针
    int size;                           // 数据大小
    int stream_index;                   // 所属流索引
    int flags;                          // 标志位
    AVPacketSideData *side_data;        // 附加数据
    int side_data_elems;                // 附加数据元素数
    int64_t duration;                   // 持续时间
    int64_t pos;                        // 在文件中的位置
    void *opaque;                       // 不透明指针
} AVPacket;

2. AVFrame - 解码帧数据

typedef struct AVFrame {
    uint8_t *data[AV_NUM_DATA_POINTERS]; // 数据指针
    int linesize[AV_NUM_DATA_POINTERS];  // 行字节数
    uint8_t **extended_data;            // 扩展数据指针
    
    int width, height;                  // 视频尺寸
    int nb_samples;                     // 音频样本数
    int format;                         // 格式
    int key_frame;                      // 关键帧标志
    enum AVPictureType pict_type;       // 图像类型
    
    AVRational sample_aspect_ratio;     // 采样宽高比
    int64_t pts;                        // 显示时间戳
    int64_t pkt_dts;                    // 包解码时间戳
    AVRational time_base;               // 时间基
    int quality;                        // 质量
    void *opaque;                       // 不透明指针
    
    // 音频相关
    int sample_rate;                    // 采样率
    AVChannelLayout ch_layout;          // 声道布局
    
    // 缓冲区管理
    AVBufferRef *buf[AV_NUM_DATA_POINTERS]; // 缓冲区引用
    AVFrameSideData **side_data;        // 附加数据
    int nb_side_data;                   // 附加数据数量
} AVFrame;

8.2 FFmpeg 7.1 API函数分类详解

8.2.1 格式处理函数

1. avformat_alloc_context()

AVFormatContext *avformat_alloc_context(void);
  • 功能: 分配AVFormatContext结构体
  • 返回值: 成功返回指针,失败返回NULL
  • 配对函数: avformat_free_context()

2. avformat_open_input()

int avformat_open_input(AVFormatContext **ps, const char *url,
                       const AVInputFormat *fmt, AVDictionary **options);

功能: 打开输入文件/流

参数:

  • ps: 格式上下文指针的地址
  • url: 输入URL或文件路径
  • fmt: 输入格式(NULL为自动检测)
  • options: 选项字典

返回值: 成功返回0,失败返回负数

配对函数: avformat_close_input()

3. avformat_find_stream_info()

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
  • 功能: 读取流信息头
  • 重要性: 获取完整的流参数信息
  • 注意: 可能会读取并缓存一些数据包

4. av_find_best_stream()

int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type,
                       int wanted_stream_nb, int related_stream,
                       const AVCodec **decoder_ret, int flags);
  • 功能: 查找最佳流
  • 类型: AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO
  • 策略: 根据质量、兼容性等选择最优流

5. av_read_frame()

int av_read_frame(AVFormatContext *s, AVPacket *pkt);
  • 功能: 读取下一个数据包
  • 返回值: 0成功, AVERROR_EOF文件结束, 其他为错误
  • 注意: 读取的包可能属于任何流

8.2.2 编解码函数

1. avcodec_alloc_context3()

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
  • 功能: 分配编解码器上下文
  • 参数: codec可为NULL
  • 配对函数: avcodec_free_context()

2. avcodec_parameters_to_context()

int avcodec_parameters_to_context(AVCodecContext *codec,
                                 const AVCodecParameters *par)
  • 功能: 将流参数复制到编解码器上下文
  • 重要性: 解码器初始化的必要步骤
  • FFmpeg 7.1: 更好支持新的参数格式

3. avcodec_find_decoder()

const AVCodec *avcodec_find_decoder(enum AVCodecID id);
  • 功能: 根据ID查找解码器
  • 支持格式: H264, H265, AV1, VP9, AAC, MP3等
  • 硬件加速: 可查找硬件解码器

4. avcodec_open2()

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec,
                 AVDictionary **options);
  • 功能: 初始化编解码器
  • options: 可设置线程数、硬件加速等
  • 配对函数: avcodec_close()

5. avcodec_send_packet()

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
  • 功能: 向解码器发送数据包
  • 特性: 非阻塞,内部有缓冲
  • 返回值: EAGAIN表示需要先接收帧

6. avcodec_receive_frame()

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
  • 功能: 从解码器接收帧
  • 返回值: 0成功, EAGAIN需要更多输入, EOF解码结束

8.2.3 内存管理函数

1. 数据包管理

AVPacket *av_packet_alloc(void);                    // 分配包
void av_packet_free(AVPacket **pkt);               // 释放包
int av_packet_ref(AVPacket *dst, const AVPacket *src); // 引用包
void av_packet_unref(AVPacket *pkt);               // 解除引用
void av_packet_move_ref(AVPacket *dst, AVPacket *src); // 移动引用
AVPacket *av_packet_clone(const AVPacket *src);    // 克隆包

2. 帧管理

AVFrame *av_frame_alloc(void);                     // 分配帧
void av_frame_free(AVFrame **frame);               // 释放帧
int av_frame_ref(AVFrame *dst, const AVFrame *src); // 引用帧
void av_frame_unref(AVFrame *frame);               // 解除引用
void av_frame_move_ref(AVFrame *dst, AVFrame *src); // 移动引用
AVFrame *av_frame_clone(const AVFrame *src);       // 克隆帧

8.2.4 音频处理函数(FFmpeg 7.1新特性)

1. 声道布局函数

int av_channel_layout_copy(AVChannelLayout *dst, const AVChannelLayout *src);
void av_channel_layout_uninit(AVChannelLayout *channel_layout);
int av_channel_layout_compare(const AVChannelLayout *chl, const AVChannelLayout *chl1);
void av_channel_layout_default(AVChannelLayout *ch_layout, int nb_channels);

2. 音频样本函数

int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,
                              enum AVSampleFormat sample_fmt, int align);

3. 重采样函数

SwrContext *swr_alloc(void);
int swr_alloc_set_opts2(SwrContext **ps, /* ... 参数 ... */);
int swr_init(SwrContext *s);
int swr_convert(SwrContext *s, uint8_t **out, int out_count,
                const uint8_t **in, int in_count);
void swr_free(SwrContext **s);

8.2.5 工具函数

1. 时间转换

double av_q2d(AVRational a);                       // 转换为double
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq); // 时间基转换

2. 错误处理

int av_strerror(int errnum, char *errbuf, size_t errbuf_size); // 错误转字符串

3. 数学函数

#define FFMIN(a,b) ((a) > (b) ? (b) : (a))         // 最小值
#define FFMAX(a,b) ((a) > (b) ? (a) : (b))         // 最大值

总结

本教程基于7-ffmpeg_sdl_player项目,深入讲解了FFmpeg 7.1播放器的完整开发流程:

🎯 核心设计理念

  1. 多线程流水线: 解复用→解码→输出的并行处理
  2. 队列缓冲机制: 使用模板队列实现线程安全的数据交换
  3. RAII资源管理: 通过析构函数确保资源正确释放
  4. 音频主时钟同步: 以音频为基准的精确同步算法

🔧 FFmpeg 7.1新特性应用

  • AVChannelLayout: 新的声道布局API替代传统channel_layout
  • 改进的内存管理: 更安全的引用传递机制
  • 硬件加速支持: 更好的GPU解码集成
  • 新编码格式: AV1、VVC等现代编码器支持

📊 项目架构优势

  • 可扩展性: 模块化设计便于功能扩展
  • 可维护性: 清晰的类继承关系和职责分离
  • 高性能: 多线程并行处理和零拷贝技术
  • 稳定性: 完善的错误处理和资源管理

#校招##实习##c++##牛客创作赏金赛##互联网行业现在还值得去吗#
全部评论

相关推荐

秋招第一个offer——TP-LINK,系统测试工程师,一共三面推进的都很快,预计9月中旬出具体薪资,求一个大包
落糖糖:同学,瞅瞅我司,医疗独角兽,校招刚开,名额有限,先到先得,我的主页最新动态,绿灯直达,免笔试~
我的秋招日记
点赞 评论 收藏
分享
评论
1
4
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务