Android FFmpeg系列——5 音视频同步播放

FFmpeg中PTS与DTS详解 FFmpeg中PTS与DTS详解一、PTS与DTS简介二、时间基(time_base)简介 一、PTS与DTS简介 Decode Time Stamp(DTS)是解码时间戳,解码器解码时相对于SCR(系统参考)的时间戳。 Presentation Time Stamp(PTS)是渲染时间戳,解码器渲染时相对于SCR(系统参考)的时间戳。 duration是视频时长,表示当前帧的持续时间,或者说两... 阅读详情

Android FFmpeg系列——0 编译.so库
Android FFmpeg系列——1 播放视频
Android FFmpeg系列——2 播放音频
Android FFmpeg系列——3 C多线程使用
Android FFmpeg系列——4 子线程播放音视频
Android FFmpeg系列——5 音视频同步播放
Android FFmpeg系列——6 Java 获取播放进度
Android FFmpeg系列——7 实现快进/快退功能

音视频同步播放是做播放器的难点之一,在博主用到的播放器中,有一款播放器我真的无法忍受,那就是百度云播放器!这里我真的得吐槽一下,卡顿之后,然后视频可以正常播放,但是没有声音,声音竟然没了!!你这么一个大厂,连这点都搞不定?!而且我还提过2次建议(因为实在忍不了,但是又必须得用),也不是出现的概率很低,真搞不明白!!好啦,吐槽完了,回归正题,我们来学习怎么实现音视频用不播放!

首先,我们得先了解几个概念,只有了解了这几个东西,我们才容易理解实现音视频同步流程。

概念

时间戳

在 FFmpeg 中,有2个时间戳需要我们注意的:DTS(Decoding Time Stamp,解码时间戳)和 PTS(Presentation Time Stamp,显示时间戳)。

DTS 表示压缩数据应该什么时候被解码;PTS 表示解码数据应该什么时候被显示;对于视频来说,AVFrame就是视频的一帧图像。这帧图像什么时候显示给用户,就取决于它的PTS。

但是为什么需要两个时间戳呢?来,我们再了解一下视频帧相关知识。

I,P,B 帧

视频压缩中,每帧代表一幅静止的图像。而在实际压缩时,会采取各种算法减少数据的容量,其中IPB就是最常见的。简单地说,I帧是关键帧,属于帧内压缩,解码时单独的该帧便可完成解码;P帧为向前预测编码帧,即P帧解码时需要参考前面相关帧的信息才能解码;B帧为双向预测编码帧,解码时既需要参考前面已有的帧又需要参考后面待解码的帧;他们都是基于I帧来压缩数据。

I帧表示关键帧,又称intra picture,I帧画面完整保留,解码时只需要本帧数据就可以完成(因为包含完整画面)。

P帧前向预测编码帧 又称predictive-frame,表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据)

B帧双向预测内插编码帧 又称bi-directional interpolated prediction frame,是双向差别帧,也就是B帧记录的是本帧与前后帧的差别,换言之,要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累。

因此,I帧和P帧的解码算法比较简单,资源占用也比较少,I帧只要自己完成就行了,至于P帧,也只需要解码器把前一个画面缓存一下,遇到P帧时就使用之前缓存的画面就行。如果视频流只有I和P,解码器可以不管后面的数据,边读边解码,线性前进。如果视频流还有B帧,则需要缓存前面和当前的视频帧,待后面视频帧获得后,再解码。

再回到我们之前的问题,为什么需要2个时间戳,举个例子:

对于一个电影,帧是这样来显示的:I B B P。由于显示B帧之前知道P帧中的信息,因此,帧可能会按照这样的方式来存储:I P B B。

StreamIPBB
PTS1423
DTS1234

这下你该懂了吧!!

音视频同步方式

要实现音视频同步,通常需要选择一个参考时钟,参考时钟上的时间是线性递增的,编码音视频流时依据参考时钟上的时间给每帧数据打上时间戳。在播放时,读取数据帧上的时间戳,同时参考当前参考时钟上的时间来安排播放。这里的说的时间戳就是我们前面说的 PTS。

一般来说有三种音视频同步方式 :

  • 音频同步到视频
  • 视频同步到音频
  • 音视频同步到外部时间

在音频的播放中,也有 DTS、PTS 的概念,但是音频没有类似视频中 B 帧,不需要双向预测,所以音频帧的 DTS、PTS 顺序是一致的。

所以在一般情况下,我们可以把视频同步到音频上,实现音视频的同步。

在实现代码之前,我们有几个函数需要了解一下,不然会看得一脸懵逼。

时间基

虽然知道了 PTS 是显示时间戳,AVPacket 里面有 pts 这个属性,你知道 pts 这个值的单位是什么吗?要想知道 pts 值的单位是什么,我们得知道时间基的概念,也就是 time_base,它也是用来度量时间的。

// time_base 表示方法
typedef struct AVRational{
	int num; //numerator   分子
	int den; //denominator 分母
} AVRational;

如果把1秒分为25等份,你可以理解就是一把尺,那么每一格表示的就是1/25秒,此时的time_base={1,25} 。
如果把1秒分成90000等份,每一个刻度就是1/90000秒,此时的time_base={1,90000}。
所谓时间基表示的就是每个刻度是多少秒,pts的值就是占多少个时间刻度(占多少个格子)。它的单位不是秒,而是时间刻度。只有pts加上time_base两者同时在一起,才能表达出时间是多少。

好比我只告诉你,某物体的长度占某一把尺上的20个刻度。但是我不告诉你,这把尺总共是多少厘米的,你就没办法计算每个刻度是多少厘米,你也就无法知道物体的长度。

pts=20个刻度
time_base={1,10} 每一个刻度是1/10厘米
所以物体的长度=ptstime_base=201/10 厘米

在 FFmpeg 中,函数av_q2d(time_base)=每个刻度是多少秒

static inline double av_q2d(AVRational a){
	/**
	* Convert rational to double.
	* @param a rational to convert
	**/
    return a.num / (double) a.den;
}

所以**pts*av_q2d(time_base)**才是帧的显示时间戳(PTS)。

实现音视频同步

我是基于上一节代码继续加了,看不懂的可以看我上一节的博文。

思路已经很清晰了,就是音频正常播放,然后让视频播放速度做调整!

C 层结构播放器结构

// C 层播放器结构体
typedef struct _Player {
    ...
    // 音频相关
    ...
    double audio_clock;
    ...
} Player;

音频添加一个属性,音频时钟,用于记录!

音频时钟

因为我们参考是音频,音频的 PTS 顺序是递增的,所以可以直接获取:

// 消费函数
// 获取 packet 解码之后
player->audio_clock = packet->pts * av_q2d(stream->time_base);
// 播放音频

直接使用 packet 的 pts,乘以时间基。

视频同步

// 消费函数
// 获取 packet 解码之后
 double audio_clock = player->audio_clock;
 double timestamp;
 if(packet->pts == AV_NOPTS_VALUE) {
     timestamp = 0;
 } else {
     timestamp = av_frame_get_best_effort_timestamp(frame) * av_q2d(stream->time_base);
 }
 double frame_rate = av_q2d(stream->avg_frame_rate);
 frame_rate += frame->repeat_pict * (frame_rate * 0.5);
 if (timestamp == 0.0) {
     usleep((unsigned long)(frame_rate * 1000));
 }else {
     if (fabs(timestamp - audio_clock) > AV_SYNC_THRESHOLD_MIN &&
         fabs(timestamp - audio_clock) < AV_NOSYNC_THRESHOLD) {
         if (timestamp > audio_clock) {
             usleep((unsigned long)((timestamp - audio_clock)*1000000));
         }
     }
 }
// 播放视频

代码很简单,av_frame_get_best_effort_timestamp 函数获取解码后 Frame 的显示时间戳,可能获取不到!

然后比较视频 PTS 和音频时钟,如果在可控范围之内,视频做延时后,才播放视频。

这段代码也是参考别人的,只是我有点不明白的是,为什么当timestamp = 0 时要 usleep frame_rate * 1000 时间?目前使用这段代码是暂时没有什么问题,但是肯定不是最优的。我接下来想参考开源的 ijkplayer 源码,详细理解之后再做优化。

对了,ijkplayer 是 B 站开源的播放器代码,目前市场上有很多播放器都是基于其上面开发的,值得学习!

代码地址:https://github.com/JohanMan/Player

参考资料

关于FFMPEG 中I帧、B帧、P帧、PTS、DTS
ffmpeg中的时间
Android NDK开发之旅36–FFmpeg音视频同步播放用C实现
深入理解pts,dts,time_base
FFmpeg 音视频同步

FFmpeg时间戳 例如,如果一个视频帧的dts是40,pts是160,其time_base是1/1000秒,那么可以计算出此视频帧的解码时刻是40毫秒(40/1000),显示时刻是160毫秒(160/1000)。在闭合式GOP中,每个GOP的第一个帧一定是I帧,且当前GOP的数据不会参考前后GOP的数据。根据注释中的建议,实际使用时,在视频解码过程中,我们不使用AVCodecContext.time_base,而用帧率倒数作时间基,在视频编码过程中,我们将AVCodecContext.time_base设置为帧率的倒数。 阅读详情

相关推荐

基于FFmpeg的简单Android视频播放器

1. 模块分割首先对这个视频播放器所采用的一些部件要清楚。这个播放器主要可以拆分为4个部分:1.解码:FFmpeg2.音频输出:OpenSLES3.视频渲染:OpenGLES这些框架都是基于C的api,因此这次我们的主要工作将会集中在NDK部分。而关于NDK的一些知识,之前的博客也有讲过,所以这个工程会是对之前知识的一次综合运用。为了提高可移植性,对关键部件使用接口来规范其API接口。对于播放器内部,播放的音频数据格式为16位PCM,44.1kHz采样率,双声道。

qq_52703909的博客 1298

av_frame_get_best_effort_timestamp的原理和使用

https://blog.youkuaiyun.com/oldmtn/article/details/48155957 在ffmpeg-2.7.1\libavutil\frame.h里面有 /** * Accessors for some AVFrame fields. * The position of these field in the structure is not part of t...

u010029439的博客 3769

Android音视频播放:音视频同步

目录 一、Android音视频硬解码篇: 1,音视频基础知识 2,音视频硬解码流程 3,音视频播放:音视频同步 4,音视频解封和封装:生成一个MP4 二、使用OpenGL渲染视频画面篇 1,初步了解OpenGL ES 2,使用OpenGL渲染视频画面 3,OpenGL渲染多视频,实现画中画 4,深入了解OpenGL之EGL 5,OpenGL FBO数据缓冲区 6,Android音视频硬编码:生成一个MP4 三、Android FFmpeg音视频解码篇 1,FFmpeg so...

1 1146

av_frame_get_best_effort_timestamp deprecated解决方案

最新的ffmpeg下面这个在libavutil/frame.h文件下的 av_frame_get_best_effort_timestamp()方法被移除了。

u011046452的博客 1170

FFmpeg-之音视频解码与音视频同步(二)

/YUV格式的数据转换成RGBA 8888格式的数据, FFmpeg 也可以转换,但是存在问题,使用libyuv这个库实现。LOGI(“视频时长:%lld”, (pFormatCtx->duration) / (1000 * 1000));//rgb_frame的缓冲区就是Window的缓冲区,同一个,解锁的时候就会进行绘制。//封装格式上下文,统领全局的结构体,保存了视频文件封装格式的相关信息。//封装格式上下文,统领全局的结构体,保存了视频文件封装格式的相关信息。

2401_85121663的博客 1324

Android FFmpeg音视频解码播放

FFmpeg(FastForward Mpeg)是一款遵循GPL的开源软件,在音视频处理方面表现十分优秀,几乎囊括了现存所有的视音频格式的编码,解码、转码、混合、过滤及播放。同时也是一款跨平台的软件,完美兼容Linux、Windows、Mac OSX等平台。FFmpeg:由命令行组成,用于多媒体格式转换 FFplay:基于FFmpeg开源代码库libraries做的多媒体播放器 FFprobe:基于FFmpeg做的多媒体流分析器。

yinshipin007的博客 509

Android FFmpeg系列——4 子线程播放音视频

利用工作闲余时间,终于实现在子线程播放音视频! 上一接学习了在 C 使用多线程,接着就是利用 C 多线程同时播放音视频(暂时还不同步)。 不多说,直接上码。 代码 // C 层播放器结构体 typedef struct _Player { // Env JavaVM *java_vm; // Java 实例 jobject instance; jobject...

JohanMan的博客 1914

Qt + FFmpeg 视频播放器

一、 环境搭建 1. 下载 QT:window 5.7.0版本 FFmpeg: ffmpeg-20200522-38490cb-win32-dev 注意:这里下载 32位dev版本,要和编译器对应(我的mingw是32位的) 2. 加载库 在FFmpeg的解压目录里 将 include/ 的头文件拷贝到自己的项目的工程路径下 从 lib/ 拷贝所需要的库到自己的项目里 #这里是我所用到的库和 pro里的配置 //avcodec-58.dll //avdevice-58.dll //avfilte

qq_40714324的博客 4574

Android使用ffmpeg+opengl+opensles实现播放器

终于到了第5章,前面已经学了音频和视频的渲染知识,这一章讲的是视频播放器的实现,记录一下看源码的一些要点。 初始化 1、同步模块整体初始化 初始化一些参数,minBufferedDuration为0.5f,maxBufferedDuration为1.0f。在调用initMeta()之后变成0.5和0.8,最大的音视频偏差设置为了0.05。 bool AVSynchronizer::in...

JabamiYu的博客 2944

Android 音视频系列之 FFmpeg+OpenGL ES播放视频

目录 一、Android音视频硬解码篇: 1,音视频基础知识 2,音视频硬解码流程:封装基础解码框架 3,音视频播放:音视频同步 4,音视频解封和封装:生成一个MP4 二、使用OpenGL渲染视频画面篇 1,初步了解OpenGL ES 2,使用OpenGL渲染视频画面 3,OpenGL渲染多视频,实现画中画 4,深入了解OpenGL之EGL 5,OpenGL FBO数据缓冲区 6,Android音视频硬编码:生成一个MP4 三、Android FFmpeg音视频解码篇 1,FFmpe

1 742

FFmpeg安卓流媒体播放器开发实战视频课程 -基于NDK、C++和 FFmpeg Android

课程目标 https://edu.youkuaiyun.com/combo/detail/986---Android 音视频开发(杨万里付费版) 学会编译安卓平台的ffmpeg库(支持硬解码和neon),学会ffmpeg的解码解封装接口,学会opensles播放音频,学会opengles、egl、shader、glsl播放yuv视频,学会音视频同步策略,学会将设计模式应用到实际项目中。 适用人群 ...

疾风剑豪 1367

Android音视频学习系列(七) — 从0~1开发一款Android端播放器(支持多协议网络拉流本地文件)

现在一个 APP 玩的花样是越来越多了几乎都离不开音频、视频、图片等数据显示,该篇就介绍其中的音视频播放,音视频播放可以用已经成熟开源的播放器,(推荐一个不错的播放器开源项目如果用已开源的播放器就没有太大的学习意义了,该篇文章会介绍基于 FFmpeg 4.2.2 、Librtmp 库从 0~1 开发一款 Android 播放器的流程和实例代码编写。

金戈鐡馬 1585

【Android 音视频开发打怪升级:音视频硬解码篇】三、音视频播放:音视频同步

【声 明】 首先,这一系列文章均基于自己的理解和实践,可能有不对的地方,欢迎大家指正。 其次,这是一个入门系列,涉及的知识也仅限于够用,深入的知识网上也有许许多多的博文供大家学习了。 最后,写文章过程中,会借鉴参考其他人分享的文章,会在文章最后列出,感谢这些作者的分享。 码字不易,转载请注明出处! 教程代码:【Github传送门】 目录 一、Android音视频硬解码篇: 1...

开发的猫的博客 1109

基于FFmpeg的安卓播放器构建

本文详细介绍如何从零构建基于FFmpeg的Android视频播放器,涵盖NDK集成、JNI通信、解码流程与音视频同步等核心技术,实现对非标准格式和低延迟场景的高效支持,适用于安防、医疗等专业领域。

joy55的博客 852

FFmpeg之获取video pts/dts(三十二)

【代码】FFmpeg之获取video pts/dts(三十二)

Android系统攻城狮 2097

Qt/C++音视频开发24-ffmpeg音视频同步

一、前言 用ffmpeg来做音视频同步,个人认为这个是ffmpeg基础处理中最难的一个,无数人就卡在这里,怎么也不准,本人也是尝试过网上各种demo,基本上都是渣渣,要么仅仅支持极其少量的视频文件比如收到的数据包是一帧视频一帧音频的,要么根本没法同步歪七八糟的,要么进度跳过去直接蹦蹦蹦崩溃的,其实最完美的音视频同步处理demo就是ffplay,我亲测过几十种各种各样的音视频本地文件,数十种视频流文件,都是非常完美,当然啦这是亲生的啦,不完美还玩个屁。 如果仅仅是播放视频流(不带音频流),可能不需要音视频同步

Qt/C++视频监控/推流/物联网/大屏系统/自定义控件/UI定制/输入法 3128

C++音视频开发从放弃到入门 (基于FFmpeg+OpenCV)

FFmpeg及OpenCV是开源、跨平台的音视频开发SDK,从事音视频开发基本都需要用到它。因为C及C++都是跨平台的开发语言,因此,基于C和C++开发的FFmpeg+OpenCV应用代码只需做少量修改就可以从一个平台移植到另一个平台而无需担心性能有所下降,如从PC移植到Android就是如此。下面的应用都是基于这个思想开发的,开发的原则就是用最少的代码实现最核心的功能,方便使用者二次开发,方便跨平台移植。

西部秋虫的博客 1万+

生信物种注释软件Kraken2+Bracken的安装与使用

一、软件安装与数据库下载 1.Kraken2安装 Kraken2是一个基于k-mer算法的高精度宏基因组序列分类软件,能够快速的将测序reads进行物种分类。 使用conda一键安装,要先安装好anaconda或者miniconda conda install -y kraken2 输入krakek2 --help 出现如下所示帮助文档,则表示安装成功。 2.Bracken安装 bracken是一种从宏基因组数据中高度准确的计算物种丰度的统计方法。 同样使用conda一键安装 conda install -

jileiqwd的博客 1万+
上一篇: Android FFmpeg系列——3 C多线程使用
下一篇: Android FFmpeg系列——4 子线程播放音视频
JohanMan
博客等级 码龄10年 72粉丝 131原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值