播放器开发(七):音视频同步实现

目录

学习课题:逐步构建开发播放器【QT5 + FFmpeg6 + SDL2】

原理

简单分析:
下图简单描述了在一个播放过程中,假设我们先播放音频,对比一个公共时间轴,视频就会始终比音频慢0.003s
我们在日常中用一些播放器播放视频资源时,可能会遇见“画面中人先说话,声音后面才听到” 或者是“声音先出来了,画面中人物嘴巴还没动”的情况,这些情况的发生就是“音频播放当前帧的时间轴位置>视频播放当前帧时间轴的位置 或者是<”。

所以需要进行音视频同步,这里说的同步并不是完全同步,只是在“音频”>"视频"时,让"视频"加速追上"音频";在“音频”<"视频"时,让"视频"减速等待"音频"
就像是有两个叫”音频“和”视频“的同学在操场上跑一千米,两个人的差距非常小”音频“超过了”视频“,”视频“就会加速追上去,两人几乎保持全程”同步“最后冲刺终点时两个人同时冲线。

如何实现:

通过分析,我们现在知道的就是要做【在“音频”>"视频"时,让"视频"加速追上"音频"】【在“音频”<"视频"时,让"视频"减速等待"音频"】 这个事情,因为我们使用了FFmpeg框架,在AVFrame[帧结构体]中,定义了字段“pts
解释:Presentation timestamp in time_base units (time when frame should be shown to user)
就是指这一帧需要显示给用户的“时间点”。


即在“音频pts”>"视频pts"时,让"视频"加速追上"音频"

    在“音频pts”<"视频pts"时,让"视频"减速等待"音频"。

步骤

MediaSync模块

1、在audio写入实际播放数据之前记录对应当前帧数据的pts

2、在video读取帧数据开始进行缩放渲染之前判断“视频pts”是否小于"音频pts",根据结果进行加速或者是等待。

AudioOutPut模块

添加代码在合适的位置设置pts

VideoOutPut模块

添加代码在帧读取后判断“视频pts”是否小于"音频pts"

添加的部分

AudioOutPut

1、添加了一个存放音频pts的队列

2、在SDL回调中把pts设置进MediaSync,提供给video获取进行“加速” or “等待


为什么使用队列

我看过很多的文章,他们在实现音视频同步的时候都不会用到队列去存放pts,而是使用一些样本计算公式去算出pts,然后设置进时钟。
例如:
“时长=音频数据长度(bytes)/(声道数∗采样率∗位深度/8)”
”时长=采样数/采样率
 

        这两条公式在理论上是可行的,但是在一些特殊情况下就会变得不同步,比如根据公式计算出来的帧时长与实际帧的时长不同,即使是非常细微的差距也会导致音视频同步出现异常。
        我在做音视频同步的时候就刚好遇到了这种特殊情况,根据公式计算出来的pts一直是固定的0.02322,而实际帧(AVFrame)结构体下pts字段所表示的每一帧的pts差距并不固定是0.02322,有时候会小于,有时候会大于,我的理解是这个“帧差距”代表的就是这一音频帧实际的持续时间,如果我们用公式计算出来的值与实际的不符,就会导致在video进行同步时获得了错误的音频pts,导致同步出现异常。

//AudioOutPut.h
std::queue<double>*ptsQueue;//音频帧pts队列
MediaSync *sync;
AVRational streamTimeBase;
// 设置音频流的TimeBase
void setStreamTimeBase(AVRational &streamTimeBase);
// 添加同步对象
void setSync(MediaSync *sync);//AudioOutPut.cpp
int AudioOutPut::init(int mode) {...fifo = av_audio_fifo_alloc(playSampleFmt, playChannels, spec.samples * 5);ptsQueue = new std::queue<double>();...
}void AudioOutPut::AudioCallBackFromQueue(Uint8 *stream, int len) {...//locksync->setAudioPts(ptsQueue->front());ptsQueue->pop();//read...
}void AudioOutPut::run() {...while (true) {SDL_LockMutex(mtx);if (av_audio_fifo_space(fifo) >= playSamples) {//保存ptspts = frame->pts * av_q2d(streamTimeBase);ptsQueue->push(pts);av_audio_fifo_write(fifo, (void **) &audioBuffer, playSamples);SDL_UnlockMutex(mtx);av_frame_unref(frame);break;}SDL_UnlockMutex(mtx);//队列可用空间不足则延时等待SDL_Delay((double) playSamples / playSampleRate);}...
}    void AudioOutPut::setSync(MediaSync *sync) {this->sync = sync;
}
void AudioOutPut::setStreamTimeBase(AVRational &streamTimeBase) {this->streamTimeBase = streamTimeBase;
}

VideoOutPut

获取从audio中拿到的pts,计算vidio_pts与audio_pts的差距,判断进行“加速“还是”等待

//VideoOutPut.h
MediaSync *sync;
AVRational streamTimeBase;// 设置视频流的streamTimeBase
void setStreamTimeBase(AVRational &streamTimeBase);
// 添加同步对象
void setSync(MediaSync *sync);//VideoOutPut.cpp
void VideoOutPut::run() {AVFrame *frame;double pts;double diff;double audio_pts;while (!isStopped) {frame = frameQueue->pop(10);if (frame) {//同步pts = frame->pts * av_q2d(streamTimeBase);audio_pts = sync->getAudioPts();diff = pts - audio_pts;if (diff > 0) {av_usleep(diff * 1000000.0);}//图像缩放、颜色空间转换sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0, decCtx->height, playFrame->data, playFrame->linesize);av_frame_unref(frame);//视频区域SDL_Rect sdlRect;sdlRect.x = 0;sdlRect.y = 0;sdlRect.w = decCtx->width;sdlRect.h = decCtx->height;//渲染到sdl窗口emit refreshImage(sdlRect, playFrame);}}
}void VideoOutPut::setStreamTimeBase(AVRational &streamTimeBase) {this->streamTimeBase = streamTimeBase;
}
void VideoOutPut::setSync(MediaSync *sync) {this->sync = sync;
}

完整代码

MediaSync

直接添加get set函数即可,单独新建类存放,后续可能进行优化拓展

//MediaSync.h
#include <mutex>/*** 用于进行音视频同步*/
class MediaSync {
private:std::mutex m_mutex; // 互斥锁double m_audioPts=0;
public:/*** 设置音频pts* @param pts 经过frame->pts * av_q2d(time_base)的pts*/void setAudioPts(double pts);/*** 获取音频经过frame->pts * av_q2d(time_base)的pts* @return m_audioPts*/double getAudioPts();
};//MediaSync.cpp
#include "MediaSync.h"
void MediaSync::setAudioPts(double pts) {m_audioPts = pts;
}double MediaSync::getAudioPts() {return m_audioPts;
}

测试运行结果

PlayerMain

//PlayerMain.h
MediaSync *sync;//PlayerMain.cpp
PlayerMain::PlayerMain(QWidget *parent): QWidget(parent), ui(new Ui::PlayerMain) {ui->setupUi(this);sync = new MediaSync();// 解复用demuxThread = new DemuxThread(&audioPacketQueue, &videoPacketQueue);demuxThread->setUrl("/Users/mac/Downloads/0911超前派对:于文文孟佳爆笑猜词 王源欧阳靖脑洞大开.mp4");//    demuxThread->setUrl("/Users/mac/Downloads/23.mp4");demuxThread->start();int ret;// 解码-音频audioDecodeThread = new DecodeThread(demuxThread->getCodec(MediaType::Audio),demuxThread->getCodecParameters(MediaType::Audio),&audioPacketQueue,&audioFrameQueue);audioDecodeThread->init();audioDecodeThread->start();// 解码-视频videoDecodeThread = new DecodeThread(demuxThread->getCodec(MediaType::Video),demuxThread->getCodecParameters(MediaType::Video),&videoPacketQueue,&videoFrameQueue);videoDecodeThread->init();videoDecodeThread->start();//output// audioaudioOutPut = new AudioOutPut(audioDecodeThread->dec_ctx, &audioFrameQueue);audioOutPut->init(1);audioOutPut->setSync(sync);audioOutPut->setStreamTimeBase(*demuxThread->getStreamTimeBase(MediaType::Audio));// videothis->resize(1920 / 2, 1080 / 2);videoOutPut = new VideoOutPut(videoDecodeThread->dec_ctx, &videoFrameQueue);videoOutPut->init();videoOutPut->setSync(sync);videoOutPut->setStreamTimeBase(*demuxThread->getStreamTimeBase(MediaType::Video));VideoWidget *videoWidget = new VideoWidget(this);connect(videoOutPut, &VideoOutPut::refreshImage, videoWidget, &VideoWidget::updateImage);videoWidget->show();videoWidget->initSDL();audioOutPut->start();videoOutPut->start();//    videoWidget->setParent(this);
}

播放器开发(七):音视频同步实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/238799.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux删除了大文件为什么磁盘空间没有释放?

某天&#xff0c;收到监控系统的告警信息&#xff0c;说磁盘空间占用过高&#xff0c;登上服务器&#xff0c;使用 df -h 一看&#xff0c;发现磁盘占用率已经 96%了&#xff1a; 通过查看 /usr/local/nginx/conf/vhost/xxx.conf 找到 access_log 和 error_log 的路径&#x…

基于python的FMCW雷达工作原理仿真

这篇文章将介绍如何使用python来实现FMCW工作原理的仿真&#xff0c;第1章内容将介绍距离检测原理&#xff0c;第2章内容会介绍速度检测原理。 第1章 第1部分: 距离检测原理 调制的连续波雷达通常也被叫做调频连续波&#xff08;FMCW&#xff09;雷达是一个使用频率调制来测量…

华为云obs在java中的使用

1、申请obs服务。 申请完成后&#xff0c;会获得以下几个配置信息&#xff1a; AK"****************************"; SK"******************************************************"; ENDPOINT"obs.*************************"; BUCKET_NAME&q…

2023年12月2日历史上的今天大事件早读

823年12月2日 《门罗宣言》发表 1908年12月2日 末代皇帝溥仪登基 1919年12月2日 平江开展驱除湘督张敬尧运动 1929年12月2日 北平周口店发现中国猿人头盖骨 1941年12月2日 美裔华人物理学家朱经武出生 1949年12月2日 中央决定发行人民胜利公债 1952年12月2日 拿破仑三世成…

SQL练习2

1.查询student表的所有记录 mysql> select * from student; --------------------------------------------------------------- | id | name | sex | birth | department | address | -------------------------------------------------------------…

Linux:vim的简单使用

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、vim的基本概念二、vim的基本操作三、vim正常模式命令集四、vim底行模式命令集五、.xxx.swp的解决总结 前言 本文是对Linux中vim使用的总结 一、vim的基本概念 …

NodeJs(一):初识nodejs、模块化、CommonJS、ESModule等

目录 (一)Nodejs简介 1.nodejs是什么 2.nodejs架构 3.nodejs的应用场景 (二)准备工作 1.安装nodejs 2.nodejs版本管理工具 (三)nodejs的使用 1.node的输入 2.node的输出 3.其他的console方法 (四)全局对象 1.常见的全局对象 2.特殊的全局对象 3.global和window的…

(详细教程)笔记本电脑安装Ubuntu系统

1.前言 老的小米笔记本淘汰了&#xff0c;装一下linux系统玩一下。 使用工具如下&#xff1a;一台小米笔记本pro15.6一个惠普32G U盘一个台式机用于下载镜像等资源 2.下载Ubuntu桌面版 cn.ubuntu.com/download/de… 这里我下载的是 22.04.3 LTS 3.下载烧录工具&#xff0c…

微信开发者工具真机调试连接状态在正常和未连接之间反复横跳

开启局域网模式能解决这个问题&#xff0c;目前只找到这一个方法

【C/PTA —— 13.指针2(课内实践)】

C/PTA —— 13.指针2&#xff08;课内实践&#xff09; 一.函数题6-1使用函数实现字符串部分复制6-2 拆分实数的整数部分和小数部分6-3 存在感 二.编程题7-1 单词反转 一.函数题 6-1使用函数实现字符串部分复制 void strmcpy(char* t, int m, char* s) {int len 0;char* ret …

nvidia安装出现7-zip crc error解决办法

解决办法&#xff1a;下载network版本&#xff0c;重新安装。&#xff08;选择自己需要的版本&#xff09; 网址&#xff1a;CUDA Toolkit 12.3 Update 1 Downloads | NVIDIA Developer 分析原因&#xff1a;local版本的安装包可能在下载过程中出现损坏。 本人尝试过全网说的…

从0开始学习JavaScript--JavaScript ES6 模块系统

JavaScript ES6&#xff08;ECMAScript 2015&#xff09;引入了官方支持的模块系统&#xff0c;使得前端开发更加现代化和模块化。本文将深入探讨 ES6 模块系统的各个方面&#xff0c;通过丰富的示例代码详细展示其核心概念和实际应用。 ES6 模块的基本概念 1 模块的导出 ES…