背景

在Android N上使用MediaPlayer进行高频率的音频播放,会出现很严重的声音播放延迟的问题。比如快递业务场景,在业务员正确扫描快递面单后,需要播放一个声音来提示业务员该面单已经扫描完毕,可以进行下一单扫描。业务员也是通过这个声音来作为判断标准,只有在听到声音播放的情况下才会认为这个面单已经录进了系统(出现漏扫漏件是要扣钱的),才敢进行下一个快件的扫描。这个场景需要很高的工作效率,平均每个人每秒钟会扫描3-4个快件,也就是提示的声音每秒钟需要播放3-4次。这种条件背景下,如果代码中使用MediaPlayer去完成这么高频率的音频播放动作,会有很大的问题,声音播放延迟的问题很严重,因为声音播放不及时,会极大的影响业务员的工作效率。(并不是每个平台、每个Android系统都有这个问题,这里讨论的是高通8909 Android N平台)。

问题定位

08-02 18:45:30.834 367 589 D audio_hw_primary: enable_snd_device: snd_device(2: speaker)
08-02 18:45:30.839 367 589 D audio_hw_primary: enable_audio_route: apply mixer and update path: deep-buffer-playback

通过抓取日志分析,发现日志中有大量如上所示的"deep-buffer-playback"的日志信息,这是Android Audio系统播放音频文件最终采取的播放模式。很显然,这些声音的播放都是采取的"deep-buffer-playback"模式。在Android系统上,声音的播放主要有三种模式,分别是low-latency-playback、deep-buffer-playback和compressed-offload-playback。

  1. low-latency-playback: 用于按键音、游戏背景音等对时延要求高的声音输出。音频文件是在AP侧解码成PCM数据,然后再经由Audio DSP送给codec芯片播放出来。
  2. deep-buffer-playback: 用于音乐等对时延要求不高的声音输出。音频文件是在AP侧解码成PCM数据,如果有音效的话会再对PCM数据处理(android audio framework中有effect音效模块,支持的音效有均衡器、低音增强、环绕声等),然后再经由Audio DSP送给codec芯片播放出来。
  3. compressed-offload-playback: 用于音乐等声音输出,但是音频解码部分的工作是在Audio DSP中完成,AP侧只负责把音频码流送到Audo DSP中,送出去后AP侧会进行休眠,Audo DSP中会分配一块较大的buffer去处理此数据,在Audo DSP中进行解码、音效的处理等工作,在Audo DSP解码器处理完数据之前,它会唤醒AP侧去送下一包数据。用这种模式播放音频能有效的降低功耗,是最为推荐的播放音乐的模式。

结合日志和deep-buffer-playback 的定义,可以得出问题出在播放模式上,既在对实时性要求非常高的场景下采用了高延时的播放模式。那么,系统是如何判断播放音频的时候是采用low-latency-playback 还是 deep-buffer-playback?我们如何自主选择播放模式?

播放模式选择

反编译合作方的apk,发现他们播放的是wav格式的音频文件,并且使用的是MediaPlayer播放器播放音频。

大概的调用方式:
if (isPlayer) {return;
}
isPlayer = true;
if (mediaPlayer != null) {mediaPlayer.stop();mediaPlayer.release();mediaPlayer = null;
}
mediaPlayer = MediaPlayer.create(this, R.raw.xxx);
mediaPlayer.setOnCompletionListener(onCompletionListener);
mediaPlayer.start();

MediaPlayer的功能主要是用来播放音乐的,如mp3这种音频文件较大的流媒体,这种格式的播放对实时性要求并不高,所以在高通8909平台如果采用MediaPlayer来播放音频默认走的就是deep-buffer-playback模式。而用户需要播放的音频文件只是一个很短促的提示音,如果要实时性很高可以采用SoundPool的方式来播放,SoundPool会将音频文件预先缓存为一个16位PCM流,播放的时候直接通过Audio DSP送给codec芯片播放出来,少了很多流程,也就减少了很多延时。

01-01 10:48:51.357   377   608 D audio_hw_primary: start_output_stream: enter: stream(0xb34441c0)usecase(1: low-latency-playback) devices(0x2)

通过实际的测试,发现日志中的deep-buffer-playback 变成了 low-latency-playback,说明使用SoundPool播放音频文件默认走的是 low-latency-playback模式。不过SoundPool对播放的音频文件有很多限制,比如文件大小不能超过1M等,只能用来播放一些很短促的音效。

另外,MediaPlayer其实也有参数可以设置播放模式

AudioAttributes aa = new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).setFlags(AudioAttributes.FLAG_LOW_LATENCY).setUsage(AudioAttributes.USAGE_GAME).build();
mediaPlayer.setAudioAttributes(aa);

如上的setFlags(AudioAttributes.FLAG_LOW_LATENCY),就是设置MediaPlayer低延时播放,但是通过测试发现即使设置了FLAG_LOW_LATENCY也没什么卵用,播放音频的时候还是走的deep-buffer-playback模式,不知道是不是这个属性被废弃的原因。

解决方案

虽然找到了解决方案,换成SoundPool播放既可,但是说服不了合作方修改apk,商业合作就是这样,能把问题踢给别人绝对不会挂在自己身上。而且同样的apk在高通8917平台不会有问题,在MTK平台也没有问题,就在我们的高通8909平台有问题,这就没办法解释了,只能我们想办法在Android系统里改源代码。

status_t NuPlayer::Renderer::onOpenAudioSink(const sp<AMessage> &format,bool offloadOnly,bool hasVideo,uint32_t flags,bool isStreaming) {ALOGV("openAudioSink: offloadOnly(%d) offloadingAudio(%d)",offloadOnly, offloadingAudio());...省略...// We should always be able to set our playback settings if the sink is closed.LOG_ALWAYS_FATAL_IF(mAudioSink->setPlaybackRate(mPlaybackSettings) != OK,"onOpenAudioSink: can't set playback rate on closed sink");
+        int64_t tmp_duration;
+        format->findInt64("durationUs", &tmp_duration);
+        if (tmp_duration < 3000000000) {// duration < 3s.
+            pcmFlags = AUDIO_OUTPUT_FLAG_FAST;
+        }status_t err = mAudioSink->open(sampleRate,numChannels,(audio_channel_mask_t)channelMask,AVNuUtils::get()->getPCMFormat(format),0 /* bufferCount - unused */,mUseAudioCallback ? &NuPlayer::Renderer::AudioSinkCallback : NULL,mUseAudioCallback ? this : NULL,(audio_output_flags_t)pcmFlags,NULL,doNotReconnect,frameCount);...省略...
}

在frameworks/av/meida/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp文件的onOpenAudioSink方法中,添加如上带+号的代码段。

NuPlayer是做什么的?添加的代码段是什么意思?

NuPlayer是Android流媒体框架的核心,播放音频文件也在流媒体也就是NuPlayer的管理范围之内,而onOpenAudioSink这个方法中会最终决定播放音频文件的播放模式。添加的代码意思是如果该音频文件的播放时长小于3秒,就将播放模式强制改为low-latency-playback(既AUDIO_OUTPUT_FLAG_FAST,该常量在audio.h中定义)。

通过这种取巧的方式,既不影响MediaPlayer播放大的音频文件,也不需要合作方修改apk,算是一个比较兼容的修改方式,最终用户也未再反馈过该问题,算了解决了吧。

Android N音频播放延迟相关推荐

  1. android exo解码问题,Android Exoplayer音频播放异常

    我使用ExoPlayer播放声音时遇到问题.即快速切换"停止 - 启动"异常发生Android Exoplayer音频播放异常 java.lang.NullPointerExcep ...

  2. 视频教程-FFmpeg打造Android万能音频播放器-Android

    FFmpeg打造Android万能音频播放器 从事Android移动端开发多年.主导开发过直播.电商.聊天等各种类型APP和游戏SDK:熟悉Android音视频开发.底层NDK开发等:有开源项目:ht ...

  3. Android录音笔-音频播放器,不止适用于保存的录音

    Android录音笔-音频播放器,不止适用于保存的录音 功能说明 点击录音列表项弹出播放对话框 对话框碎片的布局dialog_fragment.xml 对话框碎片MyDialog.java 对话框碎片 ...

  4. android 以音频播放器为例实现通知栏显示通知,并实现切歌、暂停、播放,并实现加载网络图片,并实现关闭第三方APP音频

    首先先给大家看下效果 接下来我们看下具体如何实施 1.首先我们创建一个音频的单例对象,这样能保证每次在播放的的音频是唯一的(类名如:MediaPlayerUtil.java) package xxx; ...

  5. android 视频+音频播放器Demo

    程序主界面 MainActivity.java 1.主界面,头部是两个TextView(自定义类似指针效果),底部是ViewPager.ViewPager中每个页面对应的是一个Fragment.这样就 ...

  6. android MediaPlayer音频播放demo

    网上搜了些关于MediaPlayer的资料 1)如何获得MediaPlayer实例: 可以使用直接new的方式: MediaPlayer mp = new MediaPlayer(); 也可以使用cr ...

  7. android蓝牙和线同时播放,Android蓝牙音频播放和录制

    我有一个蓝牙耳机(它可以播放立体声音乐)连接到我的Android手机(Android 4.4.3) . 现在我希望我的代码能够以高采样率(44100)播放立体声音乐并从该耳机录制音频 . 我按照以下帖 ...

  8. Android MP3音频播放器 仿唱片机播放动画,仿网易云播放动画,旋转动画,MediaPlayer AudioManager

    废话不多说上代码 private AudioManager audioManager;private SimpleDateFormat format;private SeekBar seekBar;p ...

  9. Android——Mediaplay音频播放最详细最全面使用方法-以及自定义seekbar 样式

    ** Mediaplay最详细使用方法** 在前几个星期吧 小编的公司 一直有播放音频的这个 模块 但是因为小编 接触这块比较少 所以走了很多的 "丸子" 比如 狂点 播放 暂停按 ...

最新文章

  1. 2021襄阳谷城高考成绩查询,2021高考襄阳谷城县考生求助电话
  2. 怎么将jenkins打包后的war自动部署到jetty上?
  3. 新的GNSS精度度量是怎样定义的?
  4. 从零开始的linux 第十一章
  5. sql server 2008学习1–系统数据库
  6. NOIP2018-普及总结
  7. python某公司为员工发放奖品_python 练习2
  8. mysql数据库索引回表_简述 MySQL 数据库的覆盖索引与回表
  9. rpg人物制作软件_RPG游戏制作教程
  10. 电源与地之间的电容作用
  11. w3c html验证服务,W3C验证和Vue的HTML绑定语法(W3C Validation and Vue's HTML binding syntax)...
  12. 网站表单自动填写【使用pyautogui 】
  13. 荣耀修改WIFI服务器,数码教程资讯:荣耀路由2S怎么修改wifi的802.11n频宽
  14. 科普读书会丨《被讨厌的勇气》:愤怒不是目的,是一种工具
  15. LSH 近似最近邻查找
  16. 联想主板bios设置u盘启动项的方法怎么操作
  17. java毕业设计——基于JSP+sqlserver的人事管理系统设计与实现(毕业论文+程序源码)——人事管理系统
  18. 超市收银软件测试自学,超市收银系统测试计划.doc
  19. Linux Kernel 6.0 CXL Core pci.c 详解
  20. 国际快递顺丰API接口接入教程代码示例

热门文章

  1. CVE-2021-4034 Pkexec LPE原理精析
  2. 一分钟经理人精髓记录
  3. OSTA该项考试分为几个等级?
  4. Metabase——开源的大数据分析探索、可视化报表神器
  5. php漏洞是什么意思,PHP程序漏洞产生的原因分析与防范方法说明
  6. Building window 2008 Cluster using Virtual Box 4.1.18 with ISCSI (NAS4Free)
  7. htc vive 安装和使用
  8. java判断字符串中是否包含某个字符
  9. LabVIEW CompactRIO 开发指南2 CompactRIO软件架构
  10. js单击、双击、连续多次点击