关于 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 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

Python实现多路视频多窗口播放功能

《Python实现多路视频多窗口播放功能》这篇文章主要为大家详细介绍了Python实现多路视频多窗口播放功能的相关知识,文中的示例代码讲解详细,有需要的小伙伴可以跟随小编一起学习一下... 目录一、python实现多路视频播放功能二、代码实现三、打包代码实现总结一、python实现多路视频播放功能服务端开

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

基于WinForm+Halcon实现图像缩放与交互功能

《基于WinForm+Halcon实现图像缩放与交互功能》本文主要讲述在WinForm中结合Halcon实现图像缩放、平移及实时显示灰度值等交互功能,包括初始化窗口的不同方式,以及通过特定事件添加相应... 目录前言初始化窗口添加图像缩放功能添加图像平移功能添加实时显示灰度值功能示例代码总结最后前言本文将

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo