【Android FFMPEG 开发】FFMPEG 音视频同步 ( 音视频同步方案 | 视频帧 FPS 控制 | H.264 编码 I / P / B 帧 | PTS | 音视频同步 )(一)

简介: 【Android FFMPEG 开发】FFMPEG 音视频同步 ( 音视频同步方案 | 视频帧 FPS 控制 | H.264 编码 I / P / B 帧 | PTS | 音视频同步 )(一)

I . FFMPEG 音视频同步流程总结


以音频播放的时间为基准 , 调整视频的播放速度 , 让视频与音频进行同步 ;


先计算出音频的播放时间 ; 再计算视频的播放时间 ;


根据视频与音频之间的比较 , 如果视频比音频快 , 那么增大视频帧之间的间隔 , 降低视频帧绘制速度 ;

如果视频比音频慢 , 那么需要丢弃部分视频帧 , 以追赶上音频的速度 ;




II . FFMPEG 音视频同步方案选择


1 . 视频播放 : 视频文件 或 视频流中 , 分别封装了 音频数据 和 视频数据 , 两种数据被封装在了数据包中 , 按照时间线存放 ; 播放的时候 , 音频 和 视频 同时播放 , 这里就需要进行同步 , 让音频的时间 与 画面播放的时间 尽量保持一致 ;



2 . 音视频不能完全同步 : 音频播放时间线 和 视频播放时间线 不可能做到完全同步 , 音频播放 与 视频播放始终都处于一个相对对其播放进度的过程中 , 二者始终 处于你追我赶的过程中 ;



3 . 在音视频同步 , 有以下三种常用的方案 :



① 以音频为基准进行同步 ( 推荐方式 ) : 这种方案是最常用的 , 因为音频有采样率 , 时间 , 指定的采样个数在指定的时间内播放时间是固定的 , 天然是一种计时方式 ;


② 以以视频为基准进行同步 : 控制视频帧按照指定的帧率 ( FPS ) 播放 , 音频与视频同步 ;


③ 以一个外部时钟为基准 : 定义一个外部的开始时间 t tt , 音频 和 视频 都基于该时间进行同步 ; 即 音频 / 视频 与 t tt 的相对时间差尽量保持一致 ;




III . FFMPEG 以音频播放时间线为基准进行音视频同步


1 . 视频 与 音频时间线 :



① 视频播放时间线控制 : 视频解码后是一帧帧的图像 , 其绘制时间都需要开发者进行手动控制 , 通过控制视频帧之间的绘制间隔 , 来达到视频播放时间线的控制 ;


② 音频播放时间线控制 : 音频解码后的数据 , 自带采样率 , 采样个数等信息 , 设置好 OpenSLES 播放器的采样率 , 采样位数 , 通道数等信息 , 将解码后的音频帧丢到缓冲队列 , 就可以自动进行播放 , 这个时间线是随着播放而自动生成的 ;



2 . 以音频为基准进行同步 : 视频时间线需要手动控制 , 音频的时间线是随着音频播放自动生成 , 因此以音频为基准进行同步 , 比较容易 ;



3 . 以音频时间线为基准的同步方案 :



① 视频比音频快 : 如果视频比音频播放的快 , 那么就加降低视频的播放速度 ;


② 视频比音频慢 : 如果视频比音频播放的慢 , 那么就加增加视频的播放速度 ;




IV . FFMPEG 有理数 AVRational 结构体


1 . 有理数 : 有理数是整数和分数的集合 ; 有理数可以用两个整数相除 ( 分数 ) 来表示 ;



2 . FFMPEG 中的有理数变量保存 :



① 数值损失 : 使用 float 或 double 表示有理数 , 会产生数值损失 , 如 无限循环小数 ;


② AVRational 结构体 : 有理数中有无限循环小数 , 为了更精确的表示无限循环小数 , FFMPEG 中定义了 AVRational 结构体更精确的表示有理数 ;



3 . AVRational 结构体原型 : 为了更精确的表示 FFMPEG 中的有理数 , FFMPEG 中定义了 AVRational 结构体 , 其中 int num 表示有理数分子 , int den 表示有理数分母 ;


/**
 * Rational number (pair of numerator and denominator).
 */
typedef struct AVRational{
    int num; ///< Numerator 分子
    int den; ///< Denominator 分母
} AVRational;




V . 获取 AVRational 结构体代表的有理数值


1 . 有理数 -> Double 浮点值 : AVRational 表示一个有理数 , 计算时需要将其转为浮点数 , 调用 av_q2d ( ) 方法 , 可以将其转为 double 双精度浮点类型进行计算 ;



2 . av_q2d ( ) 函数原型 : 该函数直接将 分子 除以 分母 的 double 结果返回 ;


/**
 * Convert an AVRational to a `double`.
 * @param a AVRational to convert
 * @return `a` in floating-point form
 * @see av_d2q()
 */
static inline double av_q2d(AVRational a){
    return a.num / (double) a.den;
}


VI . PTS 数据帧播放理论相对时间


1 . PTS ( Presentation TimeStamp ) : 该值表示视频 / 音频解码后的数据帧应该播放的相对时间 , 这个相对时间是相对于播放开始的时间 , 即 视频 / 音频 开始播放的时间是 0 , PTS 是从该开始时间开始计数 , 到某数据帧播放的时间 ;



2 . PTS 值获取 : PTS 数据被封装在了 AVFrame 结构体中 , 音频解码后的 PCM 数据帧 , 和视频解码后的图片数据帧 , 都可以获取 PTS 值 ;


/**
 * Presentation timestamp in time_base units 
 * (time when frame should be shown to user).
 */
int64_t pts;




VII . 通过 PTS 计算音频播放时间


通过 PTS 获取 音频 播放的时间 : 直接获取 音频帧 AVFrame 结构体的 pts 值 , 这里注意获取的 PTS 值的单位不是秒 , 而是一个特殊单位 , 需要乘以一个 AVRational time_base 时间单位 , 才能获取一个单位为秒的时间 ;


//1 . 获取音视频 同步校准的 PTS 的 time_base 单位
AVRational time_base = stream->time_base;
//2 . 计算该音频播放的 相对时间 , 相对 : 即从播放开始到现在的时间
//  转换成秒 : 这里要注意 pts 需要转成 秒 , 需要乘以 time_base 时间单位
//  其中 av_q2d 是将 AVRational 转为 double 类型
audio_pts_second = avFrame->pts * av_q2d(time_base);



PTS 的单位是 time_base , 从 AVStream 可以获取该 time_base 单位 ;




VIII . FFMPEG 中的时间单位 AVRational time_base


1 . FFMPEG 时间值 : FFMPEG 中很多地方涉及到时间值 , 如获取视频帧的理论播放时间 PTS ;



2 . 时间值的单位 : 这些值获取后并不是实际意义上的秒 , 毫秒等时间 , 其单位是 time_base , 是一个有理数 , 代表每单位的 PTS 值是多少秒 ;


/**
 * This is the fundamental unit of time (in seconds) in terms
 * of which frame timestamps are represented.
 *
 * decoding: set by libavformat
 * encoding: May be set by the caller before avformat_write_header() to
 *           provide a hint to the muxer about the desired timebase. In
 *           avformat_write_header(), the muxer will overwrite this field
 *           with the timebase that will actually be used for the timestamps
 *           written into the file (which may or may not be related to the
 *           user-provided one, depending on the format).
 */
AVRational time_base;


3 . 单位转换 : 将 PTS 值转为单位为秒的值 , 使用 PTS 乘以 time_base 代表的有理数 , 即可获取 PTS 代表的秒数 ;



4 . 时间单位获取 : AVStream 结构体中的 time_base 是 FFMPEG 的时间单位 , 可以直接通过 AVStream 获取该时间单位 ;


//获取音视频 同步校准的 PTS 的 time_base 单位
AVRational time_base = stream->time_base;


5 . PTS 转换为秒 代码示例 :


//1 . 获取音视频 同步校准的 PTS 的 time_base 单位
AVRational time_base = stream->time_base;
//2 . 计算该音频播放的 相对时间 , 相对 : 即从播放开始到现在的时间
//  转换成秒 : 这里要注意 pts 需要转成 秒 , 需要乘以 time_base 时间单位
//  其中 av_q2d 是将 AVRational 转为 double 类型
audio_pts_second = avFrame->pts * av_q2d(time_base);




IX . FFMPEG 中 H.264 视频帧编码


1 . H.264 视频编码帧类型 : H.264 编码的帧有三种类型 , I 帧 , P 帧 , B 帧 三种 ;



① I 帧 ( I Frame ) : 帧内编码帧 , 可以单独解码并显示 ; 解压后是一张完整图片 ;


② P 帧 ( P Frame ) : 前向预测编码帧 , 如果要解码 P 帧 , 需要参考 P 帧前面的编码帧 ; 需要参考前面的 I 帧或 B 帧编码成一张完整图片 ;


③ B 帧 ( B Frame ) : 双向预测帧 , 解码 B 帧 , 需要参考前面的编码帧 和 后面的编码帧 ; 需要参考前面的 I 帧 或 P 帧 , 和 后面的 P 帧编码成一张完整图片 ;



2 . 视频帧图片完整性分析 :



① I 帧 ( I Frame ) : 解压后是一张完整图片 ;


② P 帧 ( P Frame ) : 需要参考前面的 I 帧或 B 帧编码成一张完整图片 ;


③ B 帧 ( B Frame ) : 需要参考前面的 I 帧 或 P 帧 , 和 后面的 P 帧编码成一张完整图片 ;



3 . I / P 帧 举例 : 在一个房间内 , 人在动 , 房间背景不懂 , I 帧是完整的画面 , 其后面的 P 帧只包含了相对于 I 帧改变的画面内容 , 大部分房间背景都需要从 I 帧提取 ;



4 . 编解码的时间与空间考量 :



① 编码 :


B 帧 和 P 帧 的使用 , 能大幅度减小视频的空间 ;



② 解码 :


I 帧 解码时间最短 , 最占用空间 ;


P 帧解码时间稍长 , 需要参考前面的帧进行解码 , 能小幅度节省空间 ;


B 帧解码时间最长 , 需要参考前后两帧进行解码 , 能大幅度节省空间




X . FFMPEG 视频帧绘制帧率 FPS


1 . 帧率 ( FPS ) : 单位时间内 ( 1 秒 ) , 需要显示的图像个数 , 单位是 Hz ;



① 帧率不固定 : 这里要特别注意 , FFMPEG 在播放视频过程中 , 视频的帧率不是固定的 , 中途可能改变 ;


② 视频卡顿问题 : 如果视频播放过程中出现了卡顿 , 是因为没有控制好播放的帧率 ;



3 . 视频帧率获取 : 视频帧率信息封装在音视频流 AVStream 结构体中 , 通过访问 stream->avg_frame_rate 结构体元素 , 即可获取帧率 , 每秒播放的帧数 ;



4 . 帧率数据原型 : 定义在 AVStream 中的 AVRational avg_frame_rate 帧率 ;


/**
 * Average framerate
 *
 * - demuxing: May be set by libavformat when creating the stream or in
 *             avformat_find_stream_info().
 * - muxing: May be set by the caller before avformat_write_header().
 */
AVRational avg_frame_rate;



5 . 帧率 FPS 计算 : 调用 av_q2d(frame_rate) 方法 , 或者直接将 AVRational 结构体中的分子分母相除 , 两种方式都可以获得帧率 ( FPS ) 值 ;


int fps = frame_rate.num / frame_rate.den;
//int fps = av_q2d(frame_rate);



6 . 帧率 FPS 获取代码示例 :


//获取视频的 FPS 帧率 ( 1秒中播放的帧数 )
/*
 该结构体由一个分子和分母组成 , 分子 / 分母就是 fps
 typedef struct AVRational{
    int num; ///< Numerator
    int den; ///< Denominator
} AVRational;
 */
AVRational frame_rate = stream->avg_frame_rate;
// AVRational 结构体由一个分子和分母组成 , 分子 / 分母就是 fps
//  也可以使用 av_q2d() 方法传入 AVRational 结构体进行计算
//  上面两种方法都可以获取 帧率 ( FPS )
//      FPS 的值不是固定的 , 随着视频播放 , 其帧率也会随之改变
int fps = frame_rate.num / frame_rate.den;
//int fps = av_q2d(frame_rate);



目录
相关文章
|
2月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
123 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
2月前
|
编解码 语音技术 内存技术
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
《FFmpeg开发实战:从零基础到短视频上线》一书中的“5.1.2 把音频流保存为PCM文件”章节介绍了将媒体文件中的音频流转换为原始PCM音频的方法。示例代码直接保存解码后的PCM数据,保留了原始音频的采样频率、声道数量和采样位数。但在实际应用中,有时需要特定规格的PCM音频。例如,某些语音识别引擎仅接受16位PCM数据,而标准MP3音频通常采用32位采样,因此需将32位MP3音频转换为16位PCM音频。
64 0
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
|
21天前
|
编解码 监控 网络协议
如何使用FFmpeg实现RTSP推送H.264和H.265(HEVC)编码视频
本文详细介绍了如何使用FFmpeg实现RTSP推送H.264和H.265(HEVC)编码视频。内容涵盖环境搭建、编码配置、服务器端与客户端实现等方面,适合视频监控系统和直播平台等应用场景。通过具体命令和示例代码,帮助读者快速上手并实现目标。
72 6
|
2月前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
145 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
|
2月前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
77 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
|
2月前
|
Java 数据安全/隐私保护
Java ffmpeg 实现视频加文字/图片水印功能
【10月更文挑战第22天】在 Java 中使用 FFmpeg 实现视频加文字或图片水印功能,需先安装 FFmpeg 并添加依赖(如 JavaCV)。通过构建 FFmpeg 命令行参数,使用 `drawtext` 滤镜添加文字水印,或使用 `overlay` 滤镜添加图片水印。示例代码展示了如何使用 JavaCV 实现文字水印。
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
95 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
2月前
|
计算机视觉 Python
FFMPEG学习笔记(一): 提取视频的纯音频及无声视频
本文介绍了如何使用FFmpeg工具从视频中提取纯音频和无声视频。提供了具体的命令行操作,例如使用`ffmpeg -i input.mp4 -vn -c:a libmp3lame output.mp3`来提取音频,以及`ffmpeg -i input.mp4 -c:v copy -an output.mp4`来提取无声视频。此外,还包含了一个Python脚本,用于批量处理视频文件,自动提取音频和生成无声视频。
54 1
|
2月前
|
Android开发 开发者
FFmpeg开发笔记(五十七)使用Media3的Transformer加工视频文件
谷歌推出的Transformer,作为Jetpack Media3架构的一部分,助力开发者实现音视频格式转换与编辑。Media3简化了媒体处理流程,提升了定制性和可靠性。Transformer可用于剪辑、添加滤镜等操作,其示例代码可在指定GitHub仓库中找到。要使用Transformer,需在`build.gradle`中添加相关依赖,并按文档编写处理逻辑,最终完成音视频转换任务。具体步骤包括配置剪辑参数、设置空间效果以及监听转换事件等。
53 0
FFmpeg开发笔记(五十七)使用Media3的Transformer加工视频文件
|
2月前
FFmpeg学习笔记(二):多线程rtsp推流和ffplay拉流操作,并储存为多路avi格式的视频
这篇博客主要介绍了如何使用FFmpeg进行多线程RTSP推流和ffplay拉流操作,以及如何将视频流保存为多路AVI格式的视频文件。
208 0