Android与Nexus 6 - 如何避免降低与应用焦点相关的OpenSL音频线程优先级?

Jac*_*lly 6 c++ audio android opensl nexus6

尝试使用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).

我看到的症状看起来真的像操作系统在与手机不交互几秒钟之后就取消了音频线程的优先级.这是正确的吗?有什么方法可以避免这种行为吗?

gal*_*gal 3

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

只需观看此 YouTube 剪辑的大约一分钟(从 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
Run Code Online (Sandbox Code Playgroud)

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

我知道,这是一个丑陋的黑客行为,它可能会产生必须处理的丑陋的副作用。但目前来说,这只是唯一的解决办法。

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

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

另外,我还不能确保代码 100% 防弹。我的应用程序已应用了该 hack,但目前处于测试状态,因此我无法向您保证这是否在所有设备和 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();

    .....
}
Run Code Online (Sandbox Code Playgroud)