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

目录

学习课题:逐步构建开发播放器【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) {
    ...
    //lock

    sync->setAudioPts(ptsQueue->front());
    ptsQueue->pop();

    //read
    ...
}


void AudioOutPut::run() {
    ...
    while (true) {
                SDL_LockMutex(mtx);
                if (av_audio_fifo_space(fifo) >= playSamples) {
                    //保存pts
                    pts = 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
    // audio
    audioOutPut = new AudioOutPut(audioDecodeThread->dec_ctx, &audioFrameQueue);
    audioOutPut->init(1);
    audioOutPut->setSync(sync);
    audioOutPut->setStreamTimeBase(*demuxThread->getStreamTimeBase(MediaType::Audio));


    // video
    this->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);
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

41 - 如何使用缓存优化系统性能?

缓存是我们提高系统性能的一项必不可少的技术&#xff0c;无论是前端、还是后端&#xff0c;都应用到了缓存技术。前端使用缓存&#xff0c;可以降低多次请求服务的压力&#xff1b;后端使用缓存&#xff0c;可以降低数据库操作的压力&#xff0c;提升读取数据的性能。 今天我…

基于springboot+vue的点餐系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

基于SpringBoot校园周边美食探索及分享平台的设计与实现

摘要&#xff1a; 美食一直是与人们日常生活息息相关的产业。传统的电话订餐或者到店消费已经不能适应市场发展的需求。随着网络的迅速崛起&#xff0c;互联网日益成为提供信息的最佳俱渠道和逐步走向传统的流通领域&#xff0c;传统的美食业进而也面临着巨大的挑战&#xff0c…

[二分查找]LeetCode1964:找出到每个位置为止最长的有效障碍赛跑路线

本文涉及的基础知识点 二分查找算法合集 作者推荐 动态规划LeetCode2552&#xff1a;优化了6版的1324模式 题目 你打算构建一些障碍赛跑路线。给你一个 下标从 0 开始 的整数数组 obstacles &#xff0c;数组长度为 n &#xff0c;其中 obstacles[i] 表示第 i 个障碍的高度…

uni-app 微信小程序之自定义圆形 tabbar

文章目录 1. 自定义tabbar效果2. pages新建tabbar页面3. tabbar 页面结构4. tabbar 页面完整代码 1. 自定义tabbar效果 2. pages新建tabbar页面 首先在 pages.json 文件中&#xff0c;新建一个 tabbar 页面 "pages": [ //pages数组中第一项表示应用启动页&#xff…

如何快速选出一支好股票?

俗话说得好&#xff1a;股票选得好&#xff0c;收益少不了&#xff01;不用多说&#xff0c;相信大伙儿都知道选一支好股票究竟有多重要。 但是选股可不像咱们去菜市场买菜一样&#xff0c;看着顺眼就成。选股&#xff0c;其实是一个专业性特别强的技术活儿。 目前最常用的选股…

vscode如何在没有网络的情况下安装插件

vscode如何在没有网络的情况下安装插件 start 遇到没有网络的电脑&#xff0c;无法直接从插件市场安装vscode的插件。写一下 vscode 插件离线安装的方法. 解决方案 目标电脑没有可以安装插件的网络&#xff0c;那我们只能在有网络的环境下载好我们的插件。然后拷贝软件到无…

SHAP(三):在解释预测模型以寻求因果见解时要小心

SHAP&#xff08;三&#xff09;&#xff1a;在解释预测模型以寻求因果见解时要小心 与 Microsoft 的 Eleanor Dillon、Jacob LaRiviere、Scott Lundberg、Jonathan Roth 和 Vasilis Syrgkanis 合作撰写的关于因果关系和可解释机器学习的文章。 当与 SHAP 等可解释性工具配合…

一个简单的参数帮助框架,c实现

文章目录 具体实现如下&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> void print_help(char *argv[]) { printf("Usage: %s [options]\n", argv[0]); printf("Options:\n"); printf(" -h, -…

鉴源实验室 | 汽车网络安全攻击实例解析(三)

作者 | 张璇 上海控安可信软件创新研究院工控网络安全组 来源 | 鉴源实验室 社群 | 添加微信号“TICPShanghai”加入“上海控安51fusa安全社区” 引言&#xff1a;随着现代汽车技术的迅速发展&#xff0c;车辆的进入和启动方式经历了显著的演变。传统的物理钥匙逐渐被无钥匙进…

Seaborn图形可视化基础_Python数据分析与可视化

Seaborn图形可视化基础 Seaborn可视化Seaborn与Matplotlib Seaborn可视化 即使matplotlib已经如此强大了&#xff0c;但是不得不承认它不支持的功能还有很多。 例如&#xff1a; 2.0之前的版本的默认配置样式绝对不是用户的最佳选择&#xff1b; matplotlib的API比较底层。虽…

第十节HarmonyOS 常用基础组件-Image

一、组件介绍 组件&#xff08;Component&#xff09;是界面搭建与显示的最小单位&#xff0c;HarmonyOS ArkUI声名式为开发者提供了丰富多样的UI组件&#xff0c;我们可以使用这些组件轻松的编写出更加丰富、漂亮的界面。 组件根据功能可以分为以下五大类&#xff1a;基础组件…

Selenium+Python自动化测试之验证码处理

两种方式&#xff1a; 验证码识别技术 (很难达到100%) 添加Cookie &#xff08;*****五星推荐&#xff09; 方式一&#xff1a;验证码识别技术 逻辑方式&#xff1a; 1&#xff1a;打开验证码所在页面&#xff0c;截图。获取验证码元素坐标&#xff0c;剪切出验证码图片&…

Pandas进阶:文本处理

引言 文本的主要两个类型是string和object。如果不特殊指定类型为string&#xff0c;文本类型一般为object。 文本的操作主要是通过访问器str 来实现的&#xff0c;功能十分强大&#xff0c;但使用前需要注意以下几点。 访问器只能对Series数据结构使用。 除了常规列变量df.c…

对el-select封装成组件使用

效果与直接使用el-select一样&#xff0c;多处用el-select显得代码冗余就进行了封装 效果图&#xff1a; el-select封装&#xff1a; <template><div class"my-select"><el-selectv-model"person.modelValue":placeholder"placehold…

H5 Canvas 打飞机青春版

没事儿写写练习一下&#xff0c;说不准哪天就用到今天所用到的知识点了呢。 在线链接 https://linyisonger.github.io/H5.Examples/?name./053.%E9%A3%9E%E6%9C%BA%E5%A4%A7%E6%88%98.html 功能清单 循环滚动背景 矩形碰撞 随机生成敌人 飞机左右移动 苹果屏蔽长按 移动端屏…

京东数据运营-京东数据开放平台-鲸参谋10月粮油调味市场品牌店铺销售数据分析

鲸参谋监测的京东平台10月份料油调味市场销售数据已出炉&#xff01; 根据鲸参谋数据显示&#xff0c;今年10月份&#xff0c;京东平台粮油调味市场的销量将近4600万&#xff0c;环比增长约10%&#xff0c;同比降低约20%&#xff1b;销售额将近19亿&#xff0c;环比增长约4%&am…

(C++)三数之和--双指针法

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 算法原理 双指针法&#xff0c;不一定是说就要使用指针&#xff0c;只是一种形象的说法&#xff0c;在数组中&#xff0c;我们一般将数组下标当做指针。我们首先对数组进行排序&#xff0c;从左向右标定一个下标i&#xff0…

LED屏幕信息安全如何预防?

随着科技的不断进步&#xff0c;LED屏幕在我们生活和工作中扮演着越来越重要的角色&#xff0c;然而&#xff0c;随之而来的是信息安全面临的挑战。为了有效预防LED屏幕信息的泄露和被盗取&#xff0c;我们需要采取一系列的安全措施。以下是一些建议&#xff1a; 物理安全措施&…

中国毫米波雷达产业分析6——毫米波雷达行业发展展望

一、行业发展驱动力分析 &#xff08;一&#xff09;需求带动 当前&#xff0c;全球智能化变革浪潮正在汽车、交通、安防、工业、家居、健康监护等诸多产业蓬勃发展&#xff0c;创造了巨大的感知产品增量需求。 在汽车领域&#xff0c;毫米波雷达传感器作为一种非接触…