6

我在尝试使用 OpenSL ES 在运行 Android 6.0.1 的 Nexus 6 上实现低延迟流式音频播放时遇到了一个奇怪的问题。

我最初的尝试似乎遇到了饥饿问题,所以我在缓冲区完成回调函数中添加了一些基本的计时基准。我发现如果在我的应用程序打开时不断点击屏幕,音频可以正常播放,但如果我让它静置几秒钟,回调开始需要更长的时间。我能够始终如一地重现这种行为。有几点需要注意:

  • “几秒”~= 3-5 秒,不足以触发换屏
  • 我的应用程序的活动设置为 FLAG_KEEP_SCREEN_ON,因此无论如何都不应该发生屏幕更改
  • 我没有采取任何措施来尝试增加音频回调线程的优先级,因为我的印象是 Android 已经为这些线程保留了高优先级
  • 该行为发生在我的 Nexus 6 (Android 6.0.1) 上,但不在我也可用的 Galaxy S6 (Android 5.1.1) 上。

我看到的症状看起来确实像是操作系统在与手机没有交互几秒钟后降低了音频线程的优先级。这是正确的吗?有什么办法可以避免这种行为?

4

3 回答 3

3

在观看最新的 Google I/O 2016 音频演示时,我终于找到了这个问题的原因和(丑陋的)解决方案。

只需观看大约一分钟的这个 you Tube 剪辑(从 8 分 56 秒开始): https ://youtu.be/F2ZDp-eNrh4?t= 8m56s

它解释了为什么会发生这种情况以及如何摆脱它。

事实上,Android 会在几秒钟的触摸不活动后减慢 CPU 的速度,以减少电池使用量。视频中的那个人承诺很快就会为此提供适当的解决方案,但目前摆脱它的唯一方法是发送虚假触摸(这是官方建议)。

Instrumentation instr = new Instrumentation();
instr.sendKeyDownUpSync(KeyEvent.KEYCODE_BACKSLASH); // or whatever event you prefer

每 1.5 秒用计时器重复一次,问题就会消失。

我知道,这是一个丑陋的 hack,它可能会产生丑陋的副作用,必须加以处理。但就目前而言,它只是唯一的解决方案。

更新:关于您的最新评论……这是我的解决方案。我在屏幕边界之外的位置使用常规 MotionEvent.ACTION_DOWN。其他一切都以不受欢迎的方式干扰了 UI。为避免 SecurityException,请在主活动的 onStart() 处理程序中初始化计时器并在 onStop() 处理程序中终止它。当应用程序进入后台(取决于 CPU 负载)时,仍然存在可能遇到 SecurityException 的情况,因此您必须使用 try catch 块包围假触摸调用。

请注意,我使用的是我自己的计时器框架,因此您必须转换代码以使用您想要使用的任何计时器。

此外,我还不能确保代码是 100% 防弹的。我的应用程序应用了该 hack,但目前处于 beta 状态,因此我无法保证它是否在所有设备和 Android 版本上都能正常工作。

Timer fakeTouchTimer = null;
Instrumentation instr;
void initFakeTouchTimer()
{
    if (this.fakeTouchTimer != null)
    {
        if (this.instr == null)
        {
            this.instr = new Instrumentation();
        }
        this.fakeTouchTimer.restart();
    }
    else
    {
        if (this.instr == null)
        {
            this.instr = new Instrumentation();
        }
        this.fakeTouchTimer = new Timer(1500, Thread.MIN_PRIORITY, new TimerTask()
        {
            @Override
            public void execute()
            {
                if (instr != null && fakeTouchTimer != null && hasWindowFocus())
                {
                    try
                    {
                        long downTime = SystemClock.uptimeMillis();

                        MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, -100, -100, 0);
                        instr.sendPointerSync(event);
                        event.recycle();
                    }
                    catch (Exception e)
                    {
                    }
                }
            }
        }, true/*isInfinite*/);
    }
}
void killFakeTouchTimer()
{
    if (this.fakeTouchTimer != null)
    {
        this.fakeTouchTimer.interupt();
        this.fakeTouchTimer = null;
        this.instr = null;
    }
}

@Override
protected void onStop()
{
    killFakeTouchTimer();
    super.onStop();

    .....
}

@Override
protected void onStart()
{
    initFakeTouchTimer();
    super.onStart();

    .....
}
于 2016-05-23T09:13:27.137 回答
2

众所周知,Android 6 中的音频管道已被完全重写。虽然在大多数情况下这改善了与延迟相关的问题,但它产生许多不良副作用并非不可能,就像这种大规模更改的情况一样。

虽然您的问题似乎并不常见,但您可以尝试以下几件事:

  • 增加音频线程优先级。Android 中音频线程的默认优先级为-16,最高为-20,通常仅对系统服务可用。虽然您不能将此值分配给您的音频线程,但您可以分配下一个最好的事情:在设置线程的优先级时-19使用ANDROID_PRIORITY_URGENT_AUDIO标志。

  • 增加缓冲区的数量以防止任何类型的抖动或延迟(您甚至可以增加到 16 个)。然而,在某些设备上,填充新缓冲区的回调并不总是在应该调用的时候调用

  • 这篇 SO 帖子有几个建议来改善 Anrdoid 上的音频延迟。特别感兴趣的是已接受答案中的第 3、4 和 5 点。

  • 通过查询 或 来判断当前安卓系统是否hasSystemFeature(FEATURE_AUDIO_LOW_LATENCY)开启了低延迟hasSystemFeature(FEATURE_AUDIO_PRO)


此外,这篇学术论文还讨论了改善 Android/OpenSL 中与音频延迟相关的问题的策略,包括与缓冲区和回调间隔相关的方法。

于 2016-03-07T12:14:49.370 回答
-1

在 Android 6 上强制重采样为本机设备采样率。

使用设备的原生采样率 48000。例如:

SLDataFormat_PCM 数据格式;

数据格式.samplesPerSec = 48000;

于 2017-03-31T09:45:50.197 回答