关于 android 6.0 上的 nuplayer 播放时的图像卡顿

2024-05-27 11:18

本文主要是介绍关于 android 6.0 上的 nuplayer 播放时的图像卡顿,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作为一个和 android nuplayer 打了 N年交道, 自以为已经上古司机的老码农, 这一次居然被坑了一个礼拜;
事情描述起来很简单, 测试人员突然发现目前的版本,播放很多视频都卡顿, 由于该项目在几个月之前就已经基本收敛, 实际上近几个月大家都是没怎么测试的; 测试突然报了一堆类似异常过来, 直接把问题级别拉到最高了; // MAGIC1. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/

由于很多低分辨率的码流也卡顿, 因此我们也就不去怀疑 Video Decoder 本身的性能问题, 更倾向于上层的异常; 关掉音频的实验, 也侧面证实了这一点, 直接关掉音频的输出, 在 NuPlayer.cpp 中
             if (mAudioSink != NULL && mAudioDecoder == NULL) {
-                instantiateDecoder(true, &mAudioDecoder);
+                //instantiateDecoder(true, &mAudioDecoder);
             }

然后图像就完全不卡了;
这直接说明就是音画同步的策略问题, 或者音频时间戳的问题嘛...  再见   // MAGIC2. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/



为了更确认这点, 我把 NuPlayer::Renderer::postDrainVideoQueue 直接将 delayUs 置零, 也就是说避开 audio 时间戳的影响, kWhatDrainVideoQueue 的消息发送不做任何延迟, 可是测下来却是图像依然卡顿; 
一下子有点懵, 什么情况, 难道音频 onDrainAudioQueue 的消息处理那几毫秒的耗时, 会对视频消息 kWhatDrainVideoQueue  产生这么大的影响? 
加打印,很快打出来, 此时耗时点是在 ACodec 的  mNativeWindow->dequeueBuffer, 这里会阻塞超过 100毫秒;

抓了个 systrace, 也确认了这点, 但 systrace 上的 surfaceFlinger 进程也看不出什么异常;





然后再复测了下关掉音频输出 //instantiateDecoder,  ANW 的dequeueBuffer 就不再有异常耗时...  // MAGIC3. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/
尼玛神奇了, audio 的输出为什么会对 ANW 刷帧产生影响?    惊讶
只好把此问题先报给了 Display 模块, 自己这边再做实验, 关掉音频输出同时后台起一个 native 进程按相同的参数配置,比如采样率,FramesCount等, 直接向 AudioTrack write 数据, 前台的图像一点都不卡, 这说明眼前这个问题还谈不上系统级的抢带宽或者中断阻塞之类, 不用想太多;
等了三天, Display 终于找到原因了,  SurfaceFlinger 那边现在刷帧需要比对要刷的 buffer 的时间戳! 
这个时间戳是 ACodec 在 queueBuffer 时候设置的:  native_window_set_buffers_timestamp(mCodec->mNativeWindow.get(), timestampNs)
这里的 timestampNs 是 NuPlayerRenderer 那边的 realTime 校正时间, 是根据音频做过校正的!
就是说,虽然在 NuPlayerRenderer 中禁用了 AV 同步, 预期 Video 刷帧不再根据 audio 时间戳做延迟, 但 ANW 显示模块现在是根据 video 的 buffer realtime 时间来刷帧, 这里仍存在 AV 同步的影子;


而这个改动, 居然是 google 在 2014 年已经加进来了: // MAGIC4. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/


commit fc7fca77caa12993dd938d5ff43797d781291027
Author: Lajos Molnar <lajos@google.com>
Date:   Wed May 7 15:31:28 2014 -0700

    MediaCodec: add renderAndReleaseOutputBuffer() method with timestamp

+        err = native_window_set_buffers_timestamp(mCodec->mNativeWindow.get(), timestampNs);





SurfaceFlinger 那边, 也是 2014 年加的, 在 handlePageFlip 这里检测 shouldPresentNow 看是否等待:

commit 6b9454d1fee0347711af1746642aa7820b1ea04d
Author: Dan Stoza <stoza@google.com>
Date:   Fri Nov 7 16:00:59 2014 -0800
    SurfaceFlinger: Do less work when using PTS

    Currently, SurfaceFlinger is very dumb about how it handles buffer
    updates at less than 60fps. If there is a new frame pending, but its
    timestamp says not to present it until later SurfaceFlinger will wake
    up every vsync until it is time to present it. Even worse, if
    SurfaceFlinger has woken up but nothing has changed, it still goes
    through the entire composition process.

    This change (mostly) fixes that inefficiency. SurfaceFlinger will
    still wake up every refresh period while there is a new frame
    pending, but if there is no work to do, it will almost immediately go
    back to sleep.


@@ -1704,8 +1714,12 @@ void SurfaceFlinger::handlePageFlip()
     Vector<Layer*> layersWithQueuedFrames;
     for (size_t i = 0, count = layers.size(); i<count ; i++) {
         const sp<Layer>& layer(layers[i]);
-        if (layer->hasQueuedFrame())
-            layersWithQueuedFrames.push_back(layer.get());
+        if (layer->hasQueuedFrame()) {
+            frameQueued = true;
+            if (layer->shouldPresentNow(mPrimaryDispSync)) {
+                layersWithQueuedFrames.push_back(layer.get());
+            }
+        }




好吧, 至少说明了 Android 4.4 之后没有再关注 ACodec/SurfaceFlinger, 没有遭遇这类的问题...   // MAGIC5. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/
看来, 当 android 升级版本的时候, 逐条核对 google 的提交, 真是一件值得去做的事情, 但是大部分情况是项目时间紧, 给研发人员留出写文档的时间都不容易, 凑一些人去逐条对比 google 的提交记录听起来比较奢侈,即便是比如仅仅是 av 仓库的提交, 也会非常多,于是基本都变成了先升级版本比如升到6.0, 碰到问题的时候就比对一下旧版本比如 4.4 看是否有自己提交的修正, 有的话就 merge 到 6.0 上;  至于 4.4 到 6.0 之间 google 提交的本意, 只好等出了问题时再看吧...  
---  所以项目驱动的时间如果太紧张, 有时不利于团队的学习积累, 但这也容易理解, 所谓学习积累的时间资源是无法量化的,无法体现在相关指标上, 而如果出了问题解 bug 的耗时, 在项目经理的 project 表上是会写的更其清楚, 也更容易驱动研发的投入; // MAGIC6. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/

现在我可以记录下这个坑, 但对未来会出现的坑, 却没有什么信心, 我们需要有足够细致的耐心去核查 AV 仓库的更新;


上面卡顿的问题目前为止,只说了一半, 就是 NuPlayerRenderer 中的 video delayUs 置零并没有关闭音画同步, 需要把 timestampNs 置为系统时间关掉 ANW 那边的同步;
而卡顿的根本原因, 当然是音频问题, 虽然 AudioSink 的 write 操作不会直接阻塞, 但 AudioHAL 中的写数据阻塞,  最终会体现到 mNumFramesWritten 上, 间接的阻塞了 realtime 时间的更新:
这一段在 android 的各版本中就变化不大了: // MAGIC7. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/

-------   NuPlayerRenderer 的 MediaClock  时间系统,  变量很多,   排除各种复杂条件,  那么 queueBuffer 到 ANW 时的 timestampNs  参数可近似为:

timestampNs  = LastAudioRenderUs +  ( CurVideoBufferMediaTs   -  LastAudioBufferMediaTs )  +  (mNumFramesWritten - numFramesPlayed) / SampleRate ;
其中,  LastAudioRenderUs 是最近一次向 AudioSink 刷 audio 帧时的系统时间;
               CurVideoBufferMediaTs    是正要刷的这个 Video 帧的 buffer 时间戳,  是 parser 解析的时间戳, 也就是码流中配置的时间戳;
               LastAudioBufferMediaTs  是最近一次刷出去的 Audio 帧的 buffer 时间戳, 码流中配置的时间戳;
               mNumFramesWritten  是目前 NuPlayerRenderer向 AudioSink  write的数据总量,  对应生产者角色
              numFramesPlayed 是目前 Audio 系统 (AudioFlinger, AudioHAL)  已经播放除去的数据总量, 对应消费者角色
              因此 mNumFramesWritten   - numFramesPlayed  代表着 AF/HAL  即将刷但仍未刷的数据量,   除以采样率后,  就表征了当前 audio 帧真正刷出去所需的延迟时间 ;

两个 buffer 时间戳都是码流文件的定值,   LastAudioRenderUs  和 LastAudioBufferMediaTs   增长并不匀速, 此时就是靠  numFramesPlayed  来矫正,  因为按预期,  numFramesPlayed  会是一个按音频采样率和系统时间, 均匀增长的值; // MAGIC8. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/
而如果 AudioHAL 发生了阻塞,  numFramesPlayed     就无法均匀增长,  在一个 300 毫秒时间段,  PlayedOutAudioDuration   只增长了 200 毫秒;

如果某个 queueBuffer 的时刻, 当时的  numFramesPlayed   没有按预期的速度增长, 按上式,  最终计算出的  timestampNs    就会比上一次 queueBuffer 时的timestampNs   增加的更多 (  即大于 33ms ),  也就是一个突变,    ANW 看到这个突变时的选择是原地等待;// MAGIC9. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/



话说回来, 音画同步的实现, 本来就是靠 AF/HAL  的按采样率均匀阻塞来完成的,   如果 AudioHAL  一直不阻塞,  audio 狂刷帧,  video 就是快进;    只是目前项目上, AudioHAL 要么不阻, 一阻就是差不多一百毫秒,  抖动太大就体现在图像刷帧上了; // MAGIC10. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/

这篇关于关于 android 6.0 上的 nuplayer 播放时的图像卡顿的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1007323

相关文章

Android使用java实现网络连通性检查详解

《Android使用java实现网络连通性检查详解》这篇文章主要为大家详细介绍了Android使用java实现网络连通性检查的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录NetCheck.Java(可直接拷贝)使用示例(Activity/Fragment 内)权限要求

2025最新版Android Studio安装及组件配置教程(SDK、JDK、Gradle)

《2025最新版AndroidStudio安装及组件配置教程(SDK、JDK、Gradle)》:本文主要介绍2025最新版AndroidStudio安装及组件配置(SDK、JDK、Gradle... 目录原生 android 简介Android Studio必备组件一、Android Studio安装二、A

Vue3视频播放组件 vue3-video-play使用方式

《Vue3视频播放组件vue3-video-play使用方式》vue3-video-play是Vue3的视频播放组件,基于原生video标签开发,支持MP4和HLS流,提供全局/局部引入方式,可监听... 目录一、安装二、全局引入三、局部引入四、基本使用五、事件监听六、播放 HLS 流七、更多功能总结在 v

Android实现图片浏览功能的示例详解(附带源码)

《Android实现图片浏览功能的示例详解(附带源码)》在许多应用中,都需要展示图片并支持用户进行浏览,本文主要为大家介绍了如何通过Android实现图片浏览功能,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

在Android中使用WebView在线查看PDF文件的方法示例

《在Android中使用WebView在线查看PDF文件的方法示例》在Android应用开发中,有时我们需要在客户端展示PDF文件,以便用户可以阅读或交互,:本文主要介绍在Android中使用We... 目录简介:1. WebView组件介绍2. 在androidManifest.XML中添加Interne

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

基于Python开发一个图像水印批量添加工具

《基于Python开发一个图像水印批量添加工具》在当今数字化内容爆炸式增长的时代,图像版权保护已成为创作者和企业的核心需求,本方案将详细介绍一个基于PythonPIL库的工业级图像水印解决方案,有需要... 目录一、系统架构设计1.1 整体处理流程1.2 类结构设计(扩展版本)二、核心算法深入解析2.1 自

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期