Android N音频播放延迟

背景

        在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,算是一个比较兼容的修改方式,最终用户也未再反馈过该问题,算了解决了吧。

 

 

 

 

 

 

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值