目录
在前面的文章QT 6.6.0 + FFmpeg + SDL2实现MP4视频播放 已经实现了一个简单的视频播放,但是前面实现的视频播放功能中不包含音频播放,并且界面也实现的比较简单,只是一个简单视频播放弹出窗口。因此,本文基于上一次的视频播放进行改进,不仅仅实现了视频 + 音频的播放,同时界面也更加的完善,相比于其他实现的更加容易理解。但是其中需要了解以及注意的地方非常多,这也是为什么花了比较长的时间才完成了这个功能。
首先看一下整体实现流程:
实现思路
- 创建窗口和渲染器,用于视频文件的选择,播放以及程序关闭
- 选择视频MP4文件
- 根据打开的视频文件,得到视频流和音频流其对应的流索引号,用于后面视频帧和音频数据读取时所用
- 找到对应的视频流和音频流索引号的同时,分别找到对应的视频和音频解码信息
- 创建播放视频和播放音频的线程
- 视频播放线程
- 读取视频帧(根据视频流索引号判断当前是否为视频帧数据)
- 视频帧解码(视频帧的缩放和转换操作)
- 实时更新视频帧
- 将条件信号通知给音频播放线程,唤醒音频播放线程
- 重复上述操作
- 音频播放线程
- 读取音频数据(根据音频流索引号判断当前是否为音频数据)
- 加锁Mutex
- 等待视频播放线程的信号通知
- 音频数据解码(音频数据的转换操作)
- 将音频数据添加到音频流,播放音频
- 解锁Mutex
- 视频播放线程
- 视频播放完成之后,关闭播放窗口,可以继续选择播放或者选择其他视频文件播放
- 关闭主线程窗口,完全结束视频播放。
注:关于视频帧读取以及实时更新部分,之前的文章已经讲解,请看QT 6.6.0 + FFmpeg + SDL2实现MP4视频播放(过程详解)
音频播放核心部分
第一点:互斥量Mutex + 条件变量Cond
- 创建互斥量和条件变量
mutex = SDL_CreateMutex();
cond = SDL_CreateCond();
- 加锁和解锁
ret = SDL_LockMutex(mutex);
if(ret == 0){
.....执行程序
SDL_UnlockMutex(mutex);
}else{
printf("lock is failed");
}
- 等待条件变量和发送信号(唤醒等待在条件变量上的线程)
SDL_CondWait(cond, mutex);
SDL_CondSignal(cond);
注:其实在使用加锁和解锁的中间部分代码并没有对其出现异常以及等其他的情况的过多考虑,读者可以去完善这一点,因为如果加锁和解锁的中间部分出现异常等其他情况时,导致不能解锁,最后造成死锁情况。
第二点:音频格式转换
-
swr_alloc()用于分配并初始化一个
SwrContext
结构体,这是进行音频重采样的上下文。 -
swr_alloc_set_opts2(): 用于设置音频重采样器的参数,如源和目标采样率、源和目标通道布局、源和目标样本格式等。
-
swr_init(): 根据
swr_alloc_set_opts2()
设置的参数初始化音频重采样器。 -
swr_convert(): 用于执行实际的音频重采样转换。
-
swr_free(): 释放与音频重采样器相关联的资源。
int swr_alloc_set_opts2(struct SwrContext **ps, //创建的上下文结构体
const AVChannelLayout *out_ch_layout, //输出通道布局 比如AV_CHANNEL_LAYOUT_*
enum AVSampleFormat out_sample_fmt, //输出采样格式 比如AV_SAMPLE_FMT_*
int out_sample_rate, //输出采样比率,就是频率freq
const AVChannelLayout *in_ch_layout, //输入通道布局,同上
enum AVSampleFormat in_sample_fmt, //输入采样格式,同上
int in_sample_rate, //输入采样比率,同上
int log_offset, //日志偏移 一般为0
void *log_ctx); //日志上下文 一般为NULL
获取最大输出采样,用于后面对音频数据的转换:
获取下一个输入样本相对于下一个输出样本将经历的延迟。
swr_get_delay(struct SwrContext *s, int64_t base)
其中base参数的几种不用输入:
①如果设置为1,则返回的延迟以秒为单位
②如果设置为1000,返回的延迟以毫秒为单位
③如果设置为输入采样率,则返回延迟是在输入样本中
④如果设置为输出采样率,则返回延迟是在输出样本
⑤如果它是in_sample_rate和的最小公倍数out_sample_rate则精确的无舍入延迟将是返回
av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd)
用于在获取时间(通常是使用 AVRational 结构表示的时间单位)和
更高精度的时间单位之间进行转换,并且支持四舍五入选项。
src:需要转换的源时间,由 int64_t 形式的时间戳表示,通常是以某种时间单位(例如帧数、采样数等)表示
的值。
src_tb:源时间的时间基(time base),表示源时间戳的单位。
dst_tb:目标时间的时间基,表示目标时间戳的单位。
rounding:是一个枚举类型 AVRounding,指示如何处理舍入。包含的值包括:
AV_ROUND_NEAR:将值四舍五入到最接近的整数。
AV_ROUND_INF:向正无穷大方向舍入。
AV_ROUND_DOWN:向下舍入。
AV_ROUND_UP:向上舍入。
第三点:获得缓冲区大小和用样本格式sample_fmt填充样本的平面数据指针和行大小
注:关于av_samples_get_buffer_size和av_samples_fill_arrays参数不再详细解释,其实和视频播放 博文中所讲的av_image_get_buffer_size和av_image_fill_arrays是相呼应的,参数都比较好理解。
最后:窗口关闭事件触发
QT 6.6.0 + FFmpeg +SDL2实现视频播放器