关于 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

相关文章

Python如何将OpenCV摄像头视频流通过浏览器播放

《Python如何将OpenCV摄像头视频流通过浏览器播放》:本文主要介绍Python如何将OpenCV摄像头视频流通过浏览器播放的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完... 目录方法1:使用Flask + MJPEG流实现代码使用方法优点缺点方法2:使用WebSocket传输视

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

Python中OpenCV与Matplotlib的图像操作入门指南

《Python中OpenCV与Matplotlib的图像操作入门指南》:本文主要介绍Python中OpenCV与Matplotlib的图像操作指南,本文通过实例代码给大家介绍的非常详细,对大家的学... 目录一、环境准备二、图像的基本操作1. 图像读取、显示与保存 使用OpenCV操作2. 像素级操作3.

C/C++的OpenCV 进行图像梯度提取的几种实现

《C/C++的OpenCV进行图像梯度提取的几种实现》本文主要介绍了C/C++的OpenCV进行图像梯度提取的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录预www.chinasem.cn备知识1. 图像加载与预处理2. Sobel 算子计算 X 和 Y

c/c++的opencv图像金字塔缩放实现

《c/c++的opencv图像金字塔缩放实现》本文主要介绍了c/c++的opencv图像金字塔缩放实现,通过对原始图像进行连续的下采样或上采样操作,生成一系列不同分辨率的图像,具有一定的参考价值,感兴... 目录图像金字塔简介图像下采样 (cv::pyrDown)图像上采样 (cv::pyrUp)C++ O

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四

Python+wxPython构建图像编辑器

《Python+wxPython构建图像编辑器》图像编辑应用是学习GUI编程和图像处理的绝佳项目,本教程中,我们将使用wxPython,一个跨平台的PythonGUI工具包,构建一个简单的... 目录引言环境设置创建主窗口加载和显示图像实现绘制工具矩形绘制箭头绘制文字绘制临时绘制处理缩放和旋转缩放旋转保存编

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I