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

目录

学习课题:逐步构建开发播放器【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.mzph.cn/news/189253.shtml

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

相关文章

docker-compose脚本编写及常用命令

安装 linux DOCKER_CONFIG/usr/local/lib/docker/cli-plugins sudo mkdir -p $DOCKER_CONFIG/cli-plugins sudo curl -SL https://521github.com/docker/compose/releases/download/v2.6.1/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose sudo c…

附录A 指令集基本原理

1. 引言 本书主要关注指令集体系结构4个主题&#xff1a; 1. 提出对指令集进行分类的方法&#xff0c;并对各种方法的优缺点进行定性评估&#xff1b; 2. 提出并分析一些在很大程度上独立于特定指令集的指令集评估数据。 3. 讨论语言与编译器议题以及…

shell编程系列(9)-使用cut选择列

文章目录 前言使用cut选择列选择特定的列 结语 前言 前面的文章介绍了sed命令&#xff0c;sed可以帮我们处理文本列&#xff0c;这边文章介绍cut命令&#xff0c;cut命令可以帮我们选择想要的列&#xff0c;在文本处理时候结合sed命令&#xff0c;就可以精准定位了。 cut命令是…

利用 NRF24L01 无线收发模块实现传感器数据的无线传输

NRF24L01 是一款常用的无线收发模块&#xff0c;适用于远程控制和数据传输应用。本文将介绍如何利用 NRF24L01 模块实现传感器数据的无线传输&#xff0c;包括硬件的连接和配置&#xff0c;以及相应的代码示例。 一、引言 NRF24L01 是一款基于 2.4GHz 射频通信的低功耗无线收发…

如何使用windows Terminal终端连接远程Linux服务器

近接触到了zsh这个shell&#xff0c;所以在ubuntu系统上反复折腾&#xff0c;终于在ubuntu-desktop系统上使用oh-my-zsh和powerlevel10k配置好了一个比较好看的终端&#xff08;个人认为挺好看&#xff0c;勿喷&#xff09;。 但是在从windwos的Mobaxterm登录ubuntu查看时&…

无人机助力电力设备螺母缺销智能检测识别,python基于YOLOv5开发构建电力设备螺母缺销小目标检测识别系统

传统作业场景下电力设备的运维和维护都是人工来完成的&#xff0c;随着现代技术科技手段的不断发展&#xff0c;基于无人机航拍飞行的自动智能化电力设备问题检测成为了一种可行的手段&#xff0c;本文的核心内容就是基于YOLOv7来开发构建电力设备螺母缺销检测识别系统&#xf…

从自动化、数字化到智能化,鸿蒙与制造业的双向奔赴

终端万物互联&#xff0c;商业竞争瞬息万变&#xff0c;制造企业面临着数字化转型与产品智能化升级的双重考验。鸿蒙操作系统以统一操作系统方案&#xff0c;可以为制造企业解决设备生态碎片化以及跨终端对接问题&#xff0c;提供安全性、流畅度、多屏协同等功能&#xff0c;实…

Mybatis 的操作(续集)

Mybatis 是一款优秀的 持久性 框架,用于简化 JDBC 的开发 持久层 : 指的就是持久化操作的层,通常指数据访问层(dao),是用来操作数据库的 简单来说 Mybatis 是更简单完成程序和数据库交互的框架 Mybatis 的写法有两种 : 1.xml 2.注解 这两者各有利弊,后面进行总结 Mybati…

pixhawk在树莓派上直接烧录固件

环境 树莓派4Bubuntu20.04 pixhawk2.4.8 执行 在ardupilo根目录下敲指令 ./waf configure --board fmuv3 ./waf sub ./waf --targets bin/adusub --upload过程 pixhawk通过usb接入树莓派中&#xff0c;在烧录过程如果出现以下情况则需要拔插usb线 会擦除原有固件&#xf…

【详细版】基于AWS EC2使用Docker安装部署Superset v2.0

文章目录 1. SuperSet介绍2. 实验说明3. 实验配置4. SSH连接云实例5. 系统版本查看6. 主机名映射7. Docker安装[可选] Docker Compose安装8. 安装superset9. 初始化superset容器10. 为superset加入连接Athena需要的依赖11. 为superset准备一个具有权限的IAM用户12. 添加此IAM用…

ESP32-Web-Server编程- 通过滑动条向 Web 提交数据

ESP32-Web-Server编程- 通过滑动条向 Web 提交数据 概述 上一节我们讲述了通过文本框向 ESP32 发送字符串、数字。有时&#xff0c;我们需要向 ESP32 发送连续的值&#xff0c;这种需求可以通过在网页端实现滑动条来实现。 需求及功能解析 本节演示如何在 ESP32 上部署一个…

Spring @Cacheable缓存注解

一、简介 缓存介绍 缓存&#xff0c;在我们的日常开发中用的非常多&#xff0c;是我们应对各种性能问题支持高并发的一大利器。 Spring 从 3.1 开始就引入了缓存的支持。定义了如下两个接口来统一支持不同的缓存技术。 org.springframework.cache.Cacheorg.springframework.ca…

这个sql有点东西,记录一下

我有一个需求&#xff1a;在订单表里面查询指定时间的订单数据&#xff0c;如果要是没有订单的话&#xff0c;需要展示当天日期和数据&#xff0c;数据为0 先看一下效果&#xff1a; 话不多说&#xff0c;直接上SQL SELECTdate_range.date AS 日期,COUNT( oco.id ) AS 总订单…

Hdoop学习笔记(HDP)-Part.14 安装YARN+MR

十四、安装YARNMR 1.MR中间结果存储权限 使用Yarn提交MapReduce任务的时候&#xff0c;中间结果会保存在HDFS&#xff0c;/user/username/&#xff0c;如果/user目录下用户目录下不存在&#xff0c;则被创建&#xff0c;当MR执行结束之后&#xff0c;中间结果会被删除&#x…

【多线程】-- 08 线程状态观测、线程优先级、守护线程

多线程 5 线程状态 5.5 线程状态观测 Thread.State线程可以处于以下状态之一&#xff1a; NEW&#xff1a;尚未启动的线程处于此状态RUNNABLE&#xff1a;在Java虚拟机中执行的线程处于此状态BLOCKED&#xff1a;被阻塞等待监视器锁定的线程处于此状态WAITING&#xff1a;正…

英语助教求职简历模板(通用10篇)

以下10篇简历内容以英语助教招聘需求为背景制作&#xff0c;大家可以借鉴参考&#xff0c;希望能帮助大家在众多候选人中脱颖而出。 英语助教求职简历下载&#xff08;可在线制作)&#xff1a;百度幻主简历 英语助教简历1&#xff1a; 求职意向 求职类型&#xff1a;全职 …

Jmeter分布式压测

一、jmeter为什么要做分布式压测 jmeter本身的局限性 一台压力机的 Jmeter 支持的线程数受限于 Jmeter 其本身的机制和硬件配置&#xff08;内存、CPU等&#xff09;是有限的由于 Jmeter 是 Java 应用&#xff0c;对 CPU 和内存的消耗较大&#xff0c;在需要模拟大量并发用户…

GPT市场将取代插件商店 openAI已经关闭plugins申请,全部集成到GPTs(Actions)来连接现实世界,可以与物理世界互动了。

Actions使用了plugins的许多核心思想&#xff0c;也增加了新的特性。 ChatGPT的"Actions"与"Plugins"是OpenAI在GPT模型中引入的两种不同的功能扩展机制。这两种机制的目的是增强模型的功能&#xff0c;使其能够处理更多样化的任务和请求。下面是对两者的比…

熬夜会秃头——Beta冲刺总结随笔

这个作业属于哪个课程2301-计算机学院-软件工程社区-CSDN社区云这个作业要求在哪里团队作业—beta冲刺事后诸葛亮-CSDN社区这个作业的目标总结Beta冲刺团队名称熬夜会秃头团队置顶集合随笔链接熬夜会秃头——Beta冲刺置顶随笔-CSDN社区 目录 一、Beta冲刺开始前设立的任务完成…