一篇文章助你彻底掌握FFmpeg 7.1播放器核心原理
本文介绍了基于FFmpeg 7.1的多媒体播放器开发框架,采用多线程流水线架构实现音视频同步播放。系统包含解复用、解码、输出三个核心模块,通过线程安全的队列实现数据交换,采用音频主时钟同步策略。关键设计包括:1)模块化解耦的线程架构;2)零拷贝数据传递优化性能;3)智能缓冲和流控策略;4)FFmpeg 7.1新特性如AVChannelLayout的应用。该框架支持多种媒体格式,具备良好的扩展性和稳
视频讲解及完整源码领取:五分钟让你掌握FFmpeg 7.1播放器核心原理!
1. 播放器框架讲解
1.1 为什么需要这样的框架设计?
问题背景:
-
媒体文件包含多种数据流(音频、视频、字幕等)
-
音视频处理速度不同,需要独立处理避免相互阻塞
-
解码是CPU密集型操作,需要并行处理提高性能
-
音视频播放需要精确的时间同步
解决方案:多线程流水线架构
1.2 整体架构流程图
完整流程说明:
🎯 数据流转路径
-
媒体文件输入 → 解复用器读取文件头和数据包
-
流分离阶段 → 按stream_index分发到音视频包队列
-
并行解码 → 音视频独立线程处理,避免相互阻塞
-
队列缓冲 → 平衡生产消费速度差异
-
格式转换 → 原始数据适配硬件设备要求
-
同步输出 → 音频为主时钟,视频跟随同步
⚡ 关键性能特性
-
多线程并行:解复用、解码、输出各自独立运行
-
零拷贝技术:使用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 线程间通信序列图
完整通信流程说明:
🚀 启动时序
-
主线程启动各工作线程:解复用 → 音频解码 → 视频解码
-
队列初始化:所有队列处于就绪状态,等待数据
📊 数据流转控制
-
流控机制:包队列100个上限,帧队列10个上限
-
背压处理:队列满时生产者暂停,实现自然流控
-
超时保护:所有队列操作支持超时,避免死锁
同步时序
-
音频驱动:SDL音频回调连续触发,设置主时钟
-
视频跟随:根据时间戳与音频时钟比较决定显示时机
-
精度控制:毫秒级同步精度,保证音画一致
错误处理
-
协作式退出:abort_标志在所有线程间传播
-
资源清理:队列Abort()确保所有缓存数据正确释放
-
优雅停止:Stop()等待线程完成当前操作后退出
2. 解复用模块开发
2.1 为什么需要解复用模块?
问题分析:
-
媒体文件(如MP4、MKV)包含多个流:音频流、视频流、字幕流等
-
这些流数据交错存储在文件中,需要分离处理
-
不同流有不同的编码格式和参数
-
需要获取流的元数据信息用于后续解码
解复用模块的作用:
-
文件解析: 解析容器格式,获取流信息
-
流分离: 将交错的音视频数据分离到不同队列
-
元数据提供: 为解码器提供编码参数
-
流控制: 控制读取速度,避免内存溢出
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");
}
流控策略说明:
-
队列大小限制: 音视频队列各限制100个包,防止内存爆炸
-
背压机制: 队列满时解复用线程等待10ms再继续
-
选择性处理: 只处理音视频流,其他流直接丢弃
-
错误处理: 使用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.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)
-
并行处理: 音视频独立解码,提高效率
-
错误恢复: 处理损坏数据包,保证播放连续性
-
格式适配: 支持多种编码格式的统一接口
解码模块作用:
-
解码器管理: 初始化和配置解码器
-
数据转换: 执行实际的解码操作
-
错误处理: 处理解码失败的情况
-
资源管理: 管理解码器和帧的生命周期
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 解码器初始化阶段(蓝色区域)
-
AVCodecParameters输入 - 从解复用器获取的编码参数,包含编码格式、尺寸、采样率等信息
-
avcodec_alloc_context3() - 分配解码器上下文内存,为解码器提供工作空间
-
avcodec_parameters_to_context() - 将流参数复制到解码器上下文,让解码器知道如何处理数据
-
avcodec_find_decoder() - 根据编码ID查找对应的解码器(如H264、AAC解码器)
-
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时钟系统设计图
但是这里要注意:目前我们是简化版本的音视频同步:
-
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播放器的完整开发流程:
🎯 核心设计理念
-
多线程流水线: 解复用→解码→输出的并行处理
-
队列缓冲机制: 使用模板队列实现线程安全的数据交换
-
RAII资源管理: 通过析构函数确保资源正确释放
-
音频主时钟同步: 以音频为基准的精确同步算法
🔧 FFmpeg 7.1新特性应用
-
AVChannelLayout: 新的声道布局API替代传统channel_layout
-
改进的内存管理: 更安全的引用传递机制
-
硬件加速支持: 更好的GPU解码集成
-
新编码格式: AV1、VVC等现代编码器支持
📊 项目架构优势
-
可扩展性: 模块化设计便于功能扩展
-
可维护性: 清晰的类继承关系和职责分离
-
高性能: 多线程并行处理和零拷贝技术
-
稳定性: 完善的错误处理和资源管理
更多推荐
所有评论(0)