Quantcast
Channel: 英特尔开发人员专区文章
Viewing all articles
Browse latest Browse all 583

基于 x86 的移动设备上的 Android* 低延迟音频

$
0
0

下载 PDF

目标

本文将介绍如何在 x86 设备上实施 Android* 低延迟音频。首先从基于英特尔® 凌动™ 处理器(代号 Bay Trail)的平台开始。 大家可以使用该指南,在基于低延迟 Android 构建 (4.4.4) 的英特尔® 设备上调查研究各种低延迟音频开发方法。

注: Android M Release 音频正在调查研究中。

简介

在很长的一段时间里,在生产低延迟音频解决方案以支持专注于声音创作的应用方面,Android 一直没有成功。 高延迟会对音乐创作、游戏、DJ 和卡拉 OK 应用造成负面影响。 用户基于这些应用的交互创作了声音,但最终用户发现,声音信号中过高的延迟严重影响了他们的用户体验。

延迟指从音频信号创建到该信号通过交互回放时之间的时间延迟。 往返延迟 (RTL) 指系统或用户的输入动作向信号发出提示及其耗费的时间,到生成下行信号之间的时间延迟。

用户接触物体以生成声音时,会遭遇 Android 应用中的音频延迟,而声音输出至扬声器之前也会耽误一些时间。 在大多数基于 ARM* 和 x86 的设备上,经过测量,音频 RTL 最低是 300 毫秒,最高是 600 毫秒,大多是采用面向音频的 Android 方法(位于此处: 面向降低延迟而设计)开发的应用。 用户群无法接受这种延迟范围。 预期延迟必须低于 100 毫秒,在大多数情况下,低于 20 毫秒才是最理想的 RTL。 还需要考虑的一点是Android 在基于触摸的音乐应用中产生的总体延迟,即触控延迟、音频处理延迟和缓冲区队列的总数。

本文重点是如何降低音频延迟,而非总体延迟;不过也会将总体延迟考虑在内。

面向音频的 Android 设计

Android 的音频硬件抽象层 (HAL) 将android.media中特定于音频的高级框架 API 连接至基础音频驱动程序和硬件。

如欲查看音频框架示意图,请访问此处: https://source.android.com/devices/audio/index.html

OpenSL ES*

Android 规定了 OpenSL ES API 的用途,以开发最稳健的方法来高效处理往返音频。 尽管不是面向低延迟音频支持的最佳选择,但它值得推荐。 这主要是由于 OpenSL 使用的缓冲区队列机制,使其在 Android 媒体框架中更加高效。 由于该实施为原生代码,所以它可以提供更高的性能,因为原生代码不会受制于 Java* 或 Dalvik VM 开销。 我们假定这种方法有助于基于 Android 的音频开发。 正如面向 Open SL 的 Android 原生开发套件文档所述,Android 版本将继续改进 Open SL 实施。

本文将通过 NDK 考察 Open SL ES API 的用途。 作为有关 OpenSL 的介绍,首先使用 OpenSL 查看构成 Android Audio 代码库的三个层级。

  • 顶层应用编程环境是基于 Java 的 Android SDK。
  • 底层编程环境名为 NDK,有助于开发人员编写可通过 Java 原生接口 (JNI) 在应用中使用的 C 或 C++ 代码。
  • OpenSL ES API 自 Android 2.3 起开始实施并内置于 NDK。

与其他 API 一样,OpenSL 的工作原理是采用回调机制。 在OpenSL 中,回调仅用于通知应用,新缓冲区可以排队(用于回放或录制)。 在其他 API 中,回调还可以处理指向有待填充或使用的音频缓冲区的指示器。 但在 OpenSL 中,更具选择,可以实施 API 以便回调以信令机制的形式运行,从而将所有处理维持在音频处理线程上。 这样在收到分配的信号后,将包含为所需的缓冲区排队。

Google 推荐使用 OpenSL 中一种名为 Sched_FIFO 策略的方法。 Sched_FIFO 策略基于环形或循环缓冲技巧。

Sched_FIFO 策略

由于 Android 基于 Linux*,因此 Android 建立了 Linux CFS 调度器。 CFS 能够以意想不到的方式分配 CPU 资源。 例如,它会将 CPU 从顺畅程度低的线程移至顺畅程度高的线程。 如果是音频,这可能会导致缓冲区计时问题。

主要解决方法是,避免面向高性能视频线程的 CFS,并使用 SCHED_FIFO调度策略,而非 CFS 实施的 SCHED_NORMAL(亦称 SCHED_OTHER)调度策略。

调度延迟

调度延迟指线程准备好运行,与最终环境切换完成以便线程确在 CPU 上运行之间的时间。 延迟越短约好,只要超过两毫秒,音频就会出现问题。 长调度延迟最有可能发生在模式转换期间,比如启动或关闭 CPU,在安全内核与普通内核之间切换,从全功率模式切换至低功率模式,或调整 CPU 的时钟频率和电压。

循环缓冲区接口

如欲测试缓冲区是否准确实施,首先需要准备供代码使用的循环缓冲区接口。 为此,需要四项功能: 1) 创建循环缓冲区,2) 写入至该缓冲区,3) 读取该缓冲区,4) 毁坏该缓冲区。

代码示例:

circular_buffer* create_circular_buffer(int bytes);
int read_circular_buffer_bytes(circular_buffer *p, char *out, int bytes);
int write_circular_buffer_bytes(circular_buffer *p, const char *in, int bytes);
void free_circular_buffer (circular_buffer *p);

预期的效果是,读取操作仅读取其数量最多为已写入至缓冲区的请求字节。 写入功能仅写入缓冲区为其准备了空间的字节。 它们将返回一个读/写字节数,在 0 到请求数之间。

消费线程(回放情况下的音频 I/O 回调或录制情况下的音频处理线程)读取循环缓冲区,然后对音频做一些处理。 同时,供应线程填充循环缓冲区,一直该缓冲区被填满。 如果循环缓冲区大小合适,两条线程将实现无缝协作。

音频 I/O

使用之前示例中创建的接口,可以编写音频 I/O 函数,以使用 OpenSL 回调。 输入流 I/O 函数示例如下所示:

// this callback handler is called every time a buffer finishes recording
void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
 OPENSL_STREAM *p = (OPENSL_STREAM *) context;
 int bytes = p->inBufSamples*sizeof(short);
 write_circular_buffer_bytes(p->inrb, (char *) p->recBuffer,bytes);
 (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue,p->recBuffer,bytes);
}
// gets a buffer of size samples from the device
int android_AudioIn(OPENSL_STREAM *p,float *buffer,int size){
 short *inBuffer;
 int i, bytes = size*sizeof(short);
 if(p == NULL || p->inBufSamples == 0) return 0;
 bytes = read_circular_buffer_bytes(p->inrb, (char *)p->inputBuffer,bytes);
 size = bytes/sizeof(short);
 for(i=0; i < size; i++){
 buffer[i] = (float) p->inputBuffer[i]*CONVMYFLT;
 }
 if(p->outchannels == 0) p->time += (double) size/(p->sr*p->inchannels);
 return size;
}

在每次新的完整缓冲区 (recBuffer) 准备好时所调用的回调函数(第 2-8 行)中,所有数据均写入循环缓冲区。 然后,recBuffer 准备重新排队(第 7 行)。 音频处理函数(第 10-21 行)尝试将请求的字节数(第 14 行)读取至 inputBuffer,然后将该样本数拷贝至输出(转换成浮动样本)。 该函数报告拷贝的样本数。

输出函数:

// puts a buffer of size samples to the device</pre>
int android_AudioOut(OPENSL_STREAM *p, float *buffer,int size){

short *outBuffer, *inBuffer;
int i, bytes = size*sizeof(short);
if(p == NULL || p->outBufSamples == 0) return 0;
for(i=0; i < size; i++){
p->outputBuffer[i] = (short) (buffer[i]*CONV16BIT);
}
bytes = write_circular_buffer_bytes(p->outrb, (char *) p->outputBuffer,bytes);
p->time += (double) size/(p->sr*p->outchannels);
return bytes/sizeof(short);
}

// this callback handler is called every time a buffer finishes playing
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
 OPENSL_STREAM *p = (OPENSL_STREAM *) context;
 int bytes = p->outBufSamples*sizeof(short);
 read_circular_buffer_bytes(p->outrb, (char *) p->playBuffer,bytes);
 (*p->bqPlayerBufferQueue)->Enqueue(p->bqPlayerBufferQueue,p->playBuffer,bytes);
}

音频处理函数(第 2-13 行)接受一定数量的浮动样本,将其转换成 short,然后将完整 outputBuffer 写入循环缓冲区,从而报告写入的样本数量。 OpenSL 回调(第 16-22 行)读取并排列所有样本。

如欲使其顺畅运行,从输入读取的样本数需要沿着缓冲区传递至输出。 以下是将输入回环至输出的处理循环代码:

while(on)

samps = android_AudioIn(p,inbuffer,VECSAMPS_MONO);

for(i = 0, j=0; i < samps; i++, j+=2)
 outbuffer[j] = outbuffer[j+1] = inbuffer[i];
 android_AudioOut(p,outbuffer,samps*2);
 }

在该代码片段中,第 5-6 行循环读取样本,并将它们拷贝至输出通道。 它是一种 stereo-out/mono-in 设置,因此,输入样本被拷贝至两个连续的输出缓冲区位置。 由于队列出现在 OpenSL 线程中,所以为了开启回调机制,打开设备上的音频之后,我们需要排列录制缓冲区和回放缓冲区。 这样将可以确保,当缓冲区需要替换时,回调能够发布出来。

此示例说明了如何通过 OpenSL 实施针对处理的音频 I/O 追踪线程。 每种实施都是独有的,并需要修改 HAL 和 ALSA 驱动程序,以发挥 OpenSL 实施的最大作用。

面向 Android 音频的 x86 设计

OpenSL 实施无法确保前往 Android “fast mixer” 的低延迟路径,从而支持所有设备达到预期延迟率(低于 40 毫秒)。 不过,修改媒体服务器、HAL 和 ALSA 驱动程序后,不同的设备在低延迟音频方面都可以实现不同的成效。 在研究需具备哪些要素才能降低 Android 延迟的过程中,英特尔在 Dell* Venue 8 7460 平板电脑上实施了低延迟音频解决方案。

试验成果是一种供独立低延迟输入服务器管理输入处理线程的混合媒体处理引擎,其中该服务器负责处理原始音频,然后将其传输至仍然使用 “fast mixer” 线程的实施 Android 的媒体服务器。 输入和输出服务器均使用 OpenSL Sched_FIFO 策略中的调度方法。

图 1. 实施示意图。

Implementation Diagram

示意图由 Eric Serre 提供

修改取得了可喜的成效,RTL 降低至 45 毫秒。 在努力降低延迟的过程中,这种实施已成为英特尔凌动 SoC 和平板电脑设计的一部分。 测试在英特尔软件开发平台上开展,适用于整个英特尔合作伙伴软件开发项目。

实施 OpenSL 和 SCHED_FIFO 策略展示了如何在上述硬件平台上高效处理实时往返音频,但并不适用于所有设备。 使用本文提供的示例测试应用时,必须在特定设备上执行,而且可供合作伙伴软件开发人员使用。

总结

本文探讨了如何使用 OpenSL 在应用中按照 Android 音频开发方法创建回调和缓冲区队列。 本文还介绍了英特尔通过努力,提供了一种使用修改后的媒体框架实现低延迟音频信号发送的选择。 为了开展试验和测试低延迟路径,开发人员必须遵守面向音频的 Android 开发设计原则,使用 OpenSL 和基于 Android Kit Kat 4.4.2 或更高版本的英特尔软件开发平台。

撰稿人:
英特尔公司 Eric Serre
Victor Lazzarini


Viewing all articles
Browse latest Browse all 583

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>