Android 使用 Nexus 6 -- 如何避免降低与应用焦点相关的 OpenSL 音频线程优先级?
Android with Nexus 6 -- how to avoid decreased OpenSL audio thread priority relating to app focus?
我在尝试使用 OpenSL ES 在 Nexus 6 运行 Android 6.0.1 上实现低延迟流式音频播放时遇到了一个奇怪的问题。
我最初的尝试似乎遇到了饥饿问题,所以我在缓冲区完成回调函数中添加了一些基本的计时基准。我发现,如果我在我的应用程序打开时不断点击屏幕,音频播放效果很好,但如果我不理会它几秒钟,回调开始需要更长的时间。我能够始终如一地重现此行为。有几点需要注意:
- "a few seconds" ~= 3-5 秒,不足以触发屏幕切换
- 我的应用程序的 activity 设置 FLAG_KEEP_SCREEN_ON,因此无论如何都不应发生屏幕变化
- 我没有采取任何措施来尝试增加音频回调线程的优先级,因为我的印象是 Android 已经为这些线程保留了高优先级
- 该行为发生在我的 Nexus 6 (Android 6.0.1) 上,但不发生在我也有可用的 Galaxy S6 (Android 5.1.1) 上。
我看到的症状看起来确实像是 OS 在与 phone 几秒钟不交互后降低了音频线程优先级。这是正确的吗?有什么办法可以避免这种行为吗?
众所周知the audio pipeline in Android 6 has been completely rewritten。虽然这在大多数情况下改善了与延迟相关的问题,但它并非不可能产生许多不良副作用,这种大规模更改通常会出现这种情况。
虽然您的问题似乎并不常见,但您可以尝试以下几种方法:
提高音频线程优先级。 Android中音频线程的默认优先级是-16
,最大是-20
,通常只对系统服务可用。虽然您不能将此值分配给您的音频线程,但您可以分配下一个最好的东西:-19
通过在设置线程的优先级时使用 ANDROID_PRIORITY_URGENT_AUDIO
标志。
增加缓冲区的数量 以防止任何类型的抖动或延迟(您甚至可以达到 16 个)。但是在某些设备上 the callback to fill a new buffer isn’t always called when it should.
This SO post 有几个改善 Anrdoid 音频延迟的建议。特别感兴趣的是已接受答案中的第 3、4 和 5 点。
通过查询hasSystemFeature(FEATURE_AUDIO_LOW_LATENCY)
或hasSystemFeature(FEATURE_AUDIO_PRO)
来判断当前Android系统是否开启低延迟 .
此外,this academic paper 讨论了改善 Android/OpenSL 中与音频延迟相关的问题的策略,包括与缓冲和回调间隔相关的方法。
在观看最新的Google I/O 2016音频演示时,我终于找到了这个问题的原因和(丑陋的)解决方案。
只需观看这个管夹的大约一分钟(从 8 分 56 秒开始):
https://youtu.be/F2ZDp-eNrh4?t=8m56s
它解释了为什么会发生这种情况以及如何摆脱它。
事实上,Android 会在触摸 activity 几秒钟后减慢 CPU 速度,以减少电池使用量。视频中的人承诺很快就会解决这个问题,但目前唯一摆脱它的方法是发送虚假触摸(这是官方建议)。
Instrumentation instr = new Instrumentation();
instr.sendKeyDownUpSync(KeyEvent.KEYCODE_BACKSLASH); // or whatever event you prefer
每 1.5 秒用计时器重复此操作,问题就会消失。
我知道,这是一个丑陋的 hack,它可能会产生必须处理的丑陋副作用。但就目前而言,这只是唯一的解决方案。
更新:
关于您的最新评论……这是我的解决方案。
我在屏幕边界之外的位置使用常规 MotionEvent.ACTION_DOWN。其他一切都以不受欢迎的方式干扰 UI。要避免 SecurityException,请在主 activity 的 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();
.....
}
在 Android 6.
上强制重新采样为本机设备采样率
使用设备的原生采样率 48000。例如:
SLDataFormat_PCM 数据格式;
dataFormat.samplesPerSec = 48000;
我在尝试使用 OpenSL ES 在 Nexus 6 运行 Android 6.0.1 上实现低延迟流式音频播放时遇到了一个奇怪的问题。
我最初的尝试似乎遇到了饥饿问题,所以我在缓冲区完成回调函数中添加了一些基本的计时基准。我发现,如果我在我的应用程序打开时不断点击屏幕,音频播放效果很好,但如果我不理会它几秒钟,回调开始需要更长的时间。我能够始终如一地重现此行为。有几点需要注意:
- "a few seconds" ~= 3-5 秒,不足以触发屏幕切换
- 我的应用程序的 activity 设置 FLAG_KEEP_SCREEN_ON,因此无论如何都不应发生屏幕变化
- 我没有采取任何措施来尝试增加音频回调线程的优先级,因为我的印象是 Android 已经为这些线程保留了高优先级
- 该行为发生在我的 Nexus 6 (Android 6.0.1) 上,但不发生在我也有可用的 Galaxy S6 (Android 5.1.1) 上。
我看到的症状看起来确实像是 OS 在与 phone 几秒钟不交互后降低了音频线程优先级。这是正确的吗?有什么办法可以避免这种行为吗?
众所周知the audio pipeline in Android 6 has been completely rewritten。虽然这在大多数情况下改善了与延迟相关的问题,但它并非不可能产生许多不良副作用,这种大规模更改通常会出现这种情况。
虽然您的问题似乎并不常见,但您可以尝试以下几种方法:
提高音频线程优先级。 Android中音频线程的默认优先级是
-16
,最大是-20
,通常只对系统服务可用。虽然您不能将此值分配给您的音频线程,但您可以分配下一个最好的东西:-19
通过在设置线程的优先级时使用ANDROID_PRIORITY_URGENT_AUDIO
标志。增加缓冲区的数量 以防止任何类型的抖动或延迟(您甚至可以达到 16 个)。但是在某些设备上 the callback to fill a new buffer isn’t always called when it should.
This SO post 有几个改善 Anrdoid 音频延迟的建议。特别感兴趣的是已接受答案中的第 3、4 和 5 点。
通过查询
hasSystemFeature(FEATURE_AUDIO_LOW_LATENCY)
或hasSystemFeature(FEATURE_AUDIO_PRO)
来判断当前Android系统是否开启低延迟 .
此外,this academic paper 讨论了改善 Android/OpenSL 中与音频延迟相关的问题的策略,包括与缓冲和回调间隔相关的方法。
在观看最新的Google I/O 2016音频演示时,我终于找到了这个问题的原因和(丑陋的)解决方案。
只需观看这个管夹的大约一分钟(从 8 分 56 秒开始): https://youtu.be/F2ZDp-eNrh4?t=8m56s
它解释了为什么会发生这种情况以及如何摆脱它。
事实上,Android 会在触摸 activity 几秒钟后减慢 CPU 速度,以减少电池使用量。视频中的人承诺很快就会解决这个问题,但目前唯一摆脱它的方法是发送虚假触摸(这是官方建议)。
Instrumentation instr = new Instrumentation();
instr.sendKeyDownUpSync(KeyEvent.KEYCODE_BACKSLASH); // or whatever event you prefer
每 1.5 秒用计时器重复此操作,问题就会消失。
我知道,这是一个丑陋的 hack,它可能会产生必须处理的丑陋副作用。但就目前而言,这只是唯一的解决方案。
更新: 关于您的最新评论……这是我的解决方案。 我在屏幕边界之外的位置使用常规 MotionEvent.ACTION_DOWN。其他一切都以不受欢迎的方式干扰 UI。要避免 SecurityException,请在主 activity 的 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();
.....
}
在 Android 6.
上强制重新采样为本机设备采样率使用设备的原生采样率 48000。例如:
SLDataFormat_PCM 数据格式;
dataFormat.samplesPerSec = 48000;