【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月前
|
数据采集 JSON 网络安全
移动端数据抓取:Android App的TLS流量解密方案
本文介绍了一种通过TLS流量解密技术抓取知乎App热榜数据的方法。利用Charles Proxy解密HTTPS流量,分析App与服务器通信内容;结合Python Requests库模拟请求,配置特定请求头以绕过反爬机制。同时使用代理IP隐藏真实IP地址,确保抓取稳定。最终成功提取热榜标题、内容简介、链接等信息,为分析热点话题和用户趋势提供数据支持。此方法也可应用于其他Android App的数据采集,但需注意选择可靠的代理服务。
116 11
移动端数据抓取:Android App的TLS流量解密方案
|
6月前
|
开发框架 Android开发 iOS开发
安卓与iOS开发中的跨平台策略:一次编码,多平台部署
在移动应用开发的广阔天地中,安卓和iOS两大阵营各占一方。随着技术的发展,跨平台开发框架应运而生,它们承诺着“一次编码,到处运行”的便捷。本文将深入探讨跨平台开发的现状、挑战以及未来趋势,同时通过代码示例揭示跨平台工具的实际运用。
263 3
|
7月前
|
编解码 监控 网络协议
如何使用FFmpeg实现RTSP推送H.264和H.265(HEVC)编码视频
本文详细介绍了如何使用FFmpeg实现RTSP推送H.264和H.265(HEVC)编码视频。内容涵盖环境搭建、编码配置、服务器端与客户端实现等方面,适合视频监控系统和直播平台等应用场景。通过具体命令和示例代码,帮助读者快速上手并实现目标。
1746 6
|
8月前
|
Java 数据安全/隐私保护
Java ffmpeg 实现视频加文字/图片水印功能
【10月更文挑战第22天】在 Java 中使用 FFmpeg 实现视频加文字或图片水印功能,需先安装 FFmpeg 并添加依赖(如 JavaCV)。通过构建 FFmpeg 命令行参数,使用 `drawtext` 滤镜添加文字水印,或使用 `overlay` 滤镜添加图片水印。示例代码展示了如何使用 JavaCV 实现文字水印。
664 1
|
8月前
|
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开发知识可参考相关书籍。
272 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
8月前
|
计算机视觉 Python
FFMPEG学习笔记(一): 提取视频的纯音频及无声视频
本文介绍了如何使用FFmpeg工具从视频中提取纯音频和无声视频。提供了具体的命令行操作,例如使用`ffmpeg -i input.mp4 -vn -c:a libmp3lame output.mp3`来提取音频,以及`ffmpeg -i input.mp4 -c:v copy -an output.mp4`来提取无声视频。此外,还包含了一个Python脚本,用于批量处理视频文件,自动提取音频和生成无声视频。
598 1
|
9月前
|
开发框架 Dart 前端开发
Android 跨平台方案对比之Flutter 和 React Native
本文对比了 Flutter 和 React Native 这两个跨平台移动应用开发框架。Flutter 使用 Dart 语言,提供接近原生的性能和丰富的组件库;React Native 则基于 JavaScript,具备庞大的社区支持和灵活性。两者各有优势,选择时需考虑团队技能和项目需求。
636 8
|
9月前
|
XML IDE 开发工具
🔧Android Studio高级技巧大公开!效率翻倍,编码不再枯燥无味!🛠️
【9月更文挑战第11天】在软件开发领域,Android Studio凭借其强大的功能成为Android开发者的首选IDE。本文将揭示一些提升开发效率的高级技巧,包括自定义代码模板、重构工具、高级调试技巧及多模块架构。通过对比传统方法,这些技巧不仅能简化编码流程,还能显著提高生产力。例如,自定义模板可一键插入常用代码块;重构工具能智能分析并安全执行代码更改;高级调试技巧如条件断点有助于快速定位问题;多模块架构则提升了大型项目的可维护性和团队协作效率。掌握这些技巧,将使你的开发之旅更加高效与愉悦。
174 5
|
8月前
FFmpeg学习笔记(二):多线程rtsp推流和ffplay拉流操作,并储存为多路avi格式的视频
这篇博客主要介绍了如何使用FFmpeg进行多线程RTSP推流和ffplay拉流操作,以及如何将视频流保存为多路AVI格式的视频文件。
843 0
|
9月前
|
Web App开发 网络协议 Android开发
Android平台一对一音视频通话方案大比拼:WebRTC VS RTMP VS RTSP,谁才是王者?
【9月更文挑战第4天】本文详细对比了在Android平台上实现一对一音视频通话时常用的WebRTC、RTMP及RTSP三种技术方案。从技术原理、性能表现与开发难度等方面进行了深入分析,并提供了示例代码。WebRTC适合追求低延迟和高质量的场景,但开发成本较高;RTMP和RTSP则在简化开发流程的同时仍能保持较好的传输效果,适用于不同需求的应用场景。
625 1