aaudio
A音频
A音频在AndroidØ版本引入了全新的Android C API。它是专为那些需要低延迟的高性能音频应用。应用程序通过读取和写入数据流与A音频通信。
注:这是A音频库的预览版本。该API可能在将来的版本后向兼容的方式发生变化。不建议在生产中使用。该A音频API是由设计最小的,它不执行这些功能:
- 音频设备枚举
- 音频端点之间自动路由
- 文件I / O
- 压缩音频的解码
- 所有输入的自动演示/流在一个单独的回调。
音频流
A音频应用程式和Android设备上的音频输入和输出之间移动音频数据。你的应用程序通过读取和写入进出数据的音频流,通过结构AAudioStream表示。读/写呼叫可以被阻塞或非阻塞。
流由以下定义:
- 的音频 设备,即源或汇的流中的数据。
- 所述共享模式,其确定一个流是否具有可能另外多个流之间共享的音频装置的独占访问。
- 该格式的流中的音频数据。
音频设备
每个流被连接到单个音频设备。
音频设备是硬件接口或虚拟端点充当源或汇用于数字音频数据的连续流。不要混淆的音频设备 与(一个内置的麦克风或蓝牙耳机)的Android设备运行你的应用程序(电话或观看)。
您可以使用该AudioManager
方法getDevices()
来发现您可以在Android设备上的音频设备。该方法返回有关信息type
的每个设备。
每个音频设备具有Android设备上的唯一ID。您可以使用ID的音频流绑定到特定的音频设备。然而,在大多数情况下,你可以让A音频选择默认的主设备,而不是你自己指定一个。
附连到流音频设备确定该流是否为输入或输出。甲流只能在一个方向上移动数据。当你定义一个流您还可以设置它的方向。当你打开一个流的Android检查,以确保音频设备和流方向一致。
共享模式
甲流有一个共享模式:
AAUDIO_SHARING_MODE_EXCLUSIVE
意味着流具有其音频设备的独占访问; 该装置不能被任何其他音频流被使用。如果音频设备已在使用,它可能无法为流具有独占访问。独家流可能有更低的延迟,但他们也更容易获得断开。您应该尽快关闭独家流,你不再需要它们,以便其他应用程序可以访问该设备。独家流提供尽可能低的延迟。AAUDIO_SHARING_MODE_SHARED
允许A音频混合音频。A音频混合所有分配到同一设备的共享流。
当你创建一个流可以明确设置共享模式。默认情况下,共享模式SHARED
。
音频格式
通过流传递的数据有通常的数字音频属性,当你定义一个流必须指定。这些措施如下:
- 样本格式
- 每帧样本
- 采样率
A音频允许这些样品格式:
aaudio_format_t | C数据类型 | 笔记 |
AAUDIO__FORMAT_PCM_I16 | int16_t | 常见的16位样本,Q0.15格式 |
AAUDIO_FORMAT_PCM_FLOAT | 浮动 | -1.0到+1.0 |
A音频可能对自己进行采样转换。例如,如果一个应用程序被写入FLOAT数据,但HAL使用PCM_I16,A音频会自动转换样本。转换可以在任一方向发生。如果您的应用程序处理音频输入,这是明智的验证输入格式,并准备在必要时转换数据,如下例所示:
aaudio_format_t dataFormat = AAudioStream_getDataFormat(stream);
//... later
if (dataFormat == AAUDIO_FORMAT_PCM_I16) {
convertFloatToPcm16(...)
}
创建音频流
在A音频库遵循制造商的设计模式,并提供AAudioStreamBuilder。
- 创建AAudioStreamBuilder:
AAudioStreamBuilder *builder;
aaudio_result_t result = AAudio_createStreamBuilder(&builder); - 设置在构建器中的音频流的配置,使用对应于所述流参数的助洗剂功能。这些可选设置功能:
AAudioStreamBuilder_setDeviceId(builder, deviceId);
AAudioStreamBuilder_setDirection(builder, direction);
AAudioStreamBuilder_setSharingMode(builder, mode);
AAudioStreamBuilder_setSampleRate(builder, sampleRate);
AAudioStreamBuilder_setSamplesPerFrame(builder, spf);
AAudioStreamBuilder_setFormat(builder, format);
AAudioStreamBuilder_setBufferCapacityInFrames(builder, frames);注意,这些方法不报告错误,如一个未定义的常量或值超出范围。
如果不指定设备ID,默认是主要的输出设备。如果不指定流方向上,默认是输出流。对于所有其它参数,你可以明确地设定一个值,或者让系统不指定所有参数或设置它来分配最佳值
AAUDIO_UNSPECIFIED
。为了安全起见,检查音频流的状态在创建后,按照步骤4所说明的那样。
- 当AAudioStreamBuilder配置,用它来创建流:
AAudioStream *stream;
result = AAudioStreamBuilder_openStream(builder, &stream); - 创建流后,验证其配置。如果您指定每帧的采样格式,采样率,或样品,他们将不会改变。然而,共享模式和缓存容量可能会随流的音频设备的能力,并在其上正在运行的Android设备改变(无论是否设置)。由于良好的防御性编程的问题,你在使用前应检查流的配置。有函数来获取对应于每个建设者设置流设置:
- 您可以保存生成器,它在将来重复使用,使更多的流。但是,如果你不打算使用它了,应该将其删除。
AAudioStreamBuilder_delete(builder);
使用音频流
状态转变
一个A音频流通常是由5种稳定状态中的一种(错误状态,断开,在该部分的端部所描述的):
- 打开
- 入门
- 已暂停
- 酡
- 停止
数据只有当流处于启动状态流过流。移动状态之间的流,使用该请求的状态转变的一个功能:
aaudio_result_t result;
result = AAudioStream_requestStart(stream);
result = AAudioStream_requestStop(stream);
result = AAudioStream_requestPause(stream);
result = AAudioStream_requestFlush(stream);
请注意,您只能请求暂停或平齐的输出流:
这些功能是异步的,并且状态变化不会立即发生。当您请求的状态变化,流使相应的过渡状态中的一种:
- 开始
- 暂停
- 法拉盛
- 停止
- 闭幕
下面的状态图显示了稳定状态为圆角矩形,而过渡状态为点的矩形。虽然它没有显示,你可以调用close()
从任何状态
A音频不提供回调来提醒您状态的变化。一个特殊的功能, AAudioStream_waitForStateChange(stream, inputState, nextState, timeout)
可以用来等待的状态变化。
该功能不检测自身的状态变化,而不会等待一个特定的状态。它等待,直到当前的状态是不同的比inputState
,可以指定。
例如,请求暂停后,流应立即进入过渡状态暂停,并到达暂停状态晚些时候-尽管也不能保证它会的。既然你不能等待暂停状态,请waitForStateChange()
等待比其他暂停任何状态。以下是如何这样做了:
aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_PAUSING;
aaudio_stream_state_t nextState = AAUDIO_STREAM_STATE_UNINITIALIZED;
int64_t timeoutNanos = 100 * AAUDIO_NANOS_PER_MILLISECOND;
result = AAudioStream_requestPause(stream);
result = AAudioStream_waitForStateChange(stream, inputState, &nextState, timeoutNanos);
如果流的状态不是暂停(中inputState
,我们假设是在调用时的当前状态),该函数立即返回。否则,它会阻止,直到状态不再暂停或超时期满。当该函数返回,参数nextState
示出了流的当前状态。
您可以呼叫请求后开始使用同样的技术,停止或冲洗,使用相应的过渡状态作为inputState。
读取和写入音频流
流启动后,你可以阅读或使用该函数写它 AAudioStream_read(stream, buffer, numFrames, timeoutNanos)
和 AAudioStream_write(stream, buffer, numFrames, timeoutNanos)
。
对于阻挡读或写帧指定数目的传送,设置timeoutNanos大于零。对于非阻塞调用,设置timeoutNanos为零。在这种情况下,结果是转移的帧的实际数目。
当你读输入,您应该验证读取帧的正确数目。如果不是,缓冲区可能含有未知的数据,可能会导致音频故障。你可以垫用零缓冲,以创建一个无声辍学:
aaudio_result_t result =
AAudioStream_read(stream, audioData, numFrames, timeout);
if (result < 0) {
// Error!
}
if (result != numFrames) {
// pad the buffer with zeros
memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,
sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
}
您可以通过将数据写入或沉默到它开始流之前素流的缓冲区。这必须与timeoutNanos非阻塞调用设置为零来完成。
在缓冲区中的数据必须通过返回的数据格式相匹配AAudioStream_getDataFormat()
。
关闭音频流
当您使用的是流完成后,将其关闭:
AAudioStream_close(stream);
断开音频流
音频流可以在任何时候断开连接,如果这些事件之一发生:
- 相关的音频设备不再连接。
- 内部会发生错误。
- 音频装置不再是主音频设备。
当流被断开时,它具有状态“断开连接”和执行写入的任何企图()或其他函数返回AAUDIO_ERROR_DISCONNECTED
。当流被切断,所有你能做的就是关闭它。
优化性能
您可以通过调整其内部的缓冲区,并通过使用特殊的高优先级的线程优化的音频应用程序的性能。
调整缓冲区,以尽量减少延迟
A音频进出它保持,一个用于每个音频设备的内部缓冲器的数据传递。
注意:不要混淆A音频的内部缓冲区与A音频流的缓冲参数读写功能。该缓冲区的容量是数据的总量缓冲器可容纳。您可以拨打 AAudioStreamBuilder_setBufferCapacityInFrames()
设置的能力。该方法限制了可分配给最大值,该设备允许的容量。使用 AAudioStream_getBufferCapacityInFrames()
验证缓存的实际容量。
一个应用程序没有使用缓冲区中的全部能力。A音频填补了缓冲高达尺寸,你可以设置。一缓冲区的大小可以不超过它的容量大,并且它往往是更小的。通过控制缓冲大小你确定需要填充它脉冲串的数量,从而控制延迟。使用方法AAudioStreamBuilder_setBufferSizeInFrames()
和AAudioStreamBuilder_getBufferSizeInFrames()
使用缓冲区大小的工作。
当应用程序播放音频输出,直到写操作完成写入缓冲区和块。A音频从以离散的脉冲串缓冲区读取。每个突发包含音频帧的倍数数目,通常是小于正被读取的缓冲区的大小。该系统控制突发大小和速率,这些性能是通过音频设备的电路典型地支配。尽管你不能改变突发或突发速率的大小,就可以根据其包含的突发的数目设置的内部缓冲器的大小。一般情况下,你会得到最低的延迟,如果你的AAudioStream的缓冲区大小是报告的突发大小的倍数。
优化缓冲区大小的一种方法是先从大的缓冲区,并逐步降低,直到欠载开始,然后轻推它回来了。或者,你可以用一个小的缓冲区开始,如果产生欠载运行,增加缓冲区大小,直到输出干净再流动。
这个过程可以发生得非常快,用户播放第一个声音可能之前。您可能需要执行初始缓冲大小首先,使用沉默,使用户不会听到任何声音的毛刺。系统性能可能随时间(例如,用户可能关闭飞行模式)而改变。由于缓冲调整增加了很少的开销,你的应用程序可以在应用程序读取或向流写入的数据,连续做。
这里是一个缓冲优化循环的一个例子:
int32_t previousUnderrunCount = 0;
int32_t framesPerBurst = AAudioStream_getFramesPerBurst(stream);
int32_t bufferSize = AAudioStream_getBufferSizeInFrames(stream);
int32_t bufferCapacity = AAudioStream_getBufferCapacityInFrames(stream);
while (go) {
result = writeSomeData();
if (result < 0) break;
// Are we getting underruns?
if (bufferSize < bufferCapacity) {
int32_t underrunCount = AAudioStream_getXRunCount(stream);
if (underrunCount > previousUnderrunCount) {
previousUnderrunCount = underrunCount;
// Try increasing the buffer size by one burst
bufferSize += framesPerBurst;
bufferSize = AAudioStream_setBufferSize(stream, bufferSize);
}
}
}
没有优势,使用这种技术来优化输入流的缓冲区大小。输入流运行尽可能快,试图缓冲数据的量保持在最低限度,然后填满时,应用程序被抢占。
使用高优先级的回调
如果您的应用程序读取或从一个普通的线程写入音频数据,它可能被抢占或经历定时抖动。这可能会导致音频故障。使用更大的缓冲区可能防范此类故障,但一个大的缓冲区也引入了较长的音频延迟。对于需要低延迟的应用,音频流可以使用异步回调函数来传输数据和从您的应用程序。A音频执行具有更好的性能,更高优先级的线程回调。
回调函数的原型如下:
typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames);
使用流房屋登记回调:
AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);
在最简单的情况下,流周期性地执行回调函数来获取数据以其下一突发。
回调函数不应该进行读或调用它的流写入。如果回调属于一个输入流,您的代码应过程,是在audioData缓冲液(指定为第三个参数)提供的数据。如果回调属于输出流,你的代码应该把数据放入缓冲区。
例如,你可以使用一个回调,不断产生这样的正弦波输出:
aaudio_data_callback_result_t myCallback(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames) {
int64_t timeout = 0;
// Write samples directly into the audioData array.
generateSineWave(static_cast<float *>(audioData), numFrames);
return AAUDIO_CALLABCK_RESULT_CONTINUE;
}
它可以处理使用A音频不止一个流。可以使用一个流作为主,并传递指向其他流中的用户数据。注册为主控流回调。然后在其他流使用非阻塞I / O。下面是通过一个输入流,以输出流的往返回调的一个例子。主呼叫流是输出流。输入流被包括在用户数据。
回调做一个无阻塞从将数据放置到输出流的缓冲器中的输入流中读取:
aaudio_data_callback_result_t myCallback(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames) {
AAudioStream *inputStream = (AAudioStream *) userData;
int64_t timeout = 0;
aaudio_result_t result =
AAudioStream_read(inputStream, audioData, numFrames, timeout);
if (result == numFrames)
return AAUDIO_CALLABCK_RESULT_CONTINUE;
if (result >= 0) {
memset(static_cast<sample_type*>(userData) + result * samplesPerFrame, 0,
sizeof(sample_type) * (numFrames - result) * samplesPerFrame);
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
return AAUDIO_CALLBACK_RESULT_STOP;
}
注意,在该示例中,假设在输入和输出流具有相同数量的信道,格式和采样率的。该流的格式可以不匹配的 - 只要代码正确处理翻译。
设置性能模式
每个AAudioStream有一个性能模式这对您的应用程序的行为有很大的影响。有三种模式:
AAUDIO_PERFORMANCE_MODE_NONE
是默认模式。它使用的是平衡延迟和功耗节省一个基本流。AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
使用较小的缓冲器和用于减少延迟优化的数据路径。AAUDIO_PERFORMANCE_MODE_POWER_SAVING
使用更大的内部缓冲器和该折衷的等待时间更低的功率的数据路径。
你可以通过调用选择性能模式setPerformanceMode() ,并通过调用发现当前模式getPerformanceMode() 。
如果低延迟比你的应用省电更重要的是,使用AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
。这是一个应用程序,是非常互动,如游戏或键盘合成有用。
如果省电比你的应用程序低延迟更加重要,使用AAUDIO_PERFORMANCE_MODE_POWER_SAVING
。这是典型的为回放先前生成的音乐应用,如流式音频或MIDI文件播放器。
在当前版本A音频的,以达到您必须使用尽可能低的延迟AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
高优先级的回调以及性能模式。按照这个例子:
// Create a stream builder
AAudioStreamBuilder *streamBuilder;
AAudio_createStreamBuilder(&streamBuilder);
AAudioStreamBuilder_setDataCallback(streamBuilder, dataCallback, nullptr);
AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
// Use it to create the stream
AAudioStream *stream;
AAudioStreamBuilder_openStream(streamBuilder, &stream);
线程安全
该A音频API没有完全线程安全的。你不能同时从多个线程同时调用一些的A音频功能。这是因为A音频避免使用互斥,这可能会导致线程抢占和毛刺。
为了安全起见,不要打电话AAudioStream_waitForStateChange()
或读或从两个不同的线程写入相同的流。同样,不要在读取或在另一个线程写入它在一个线程关闭流。
返回流设置,如电话AAudioStream_getSampleRate()
和AAudioStream_getSamplesPerFrame()
,是线程安全的。
这些电话也是线程安全的:
AAudio_convert*ToText()
AAudio_createStreamBuilder()
AAudioStream_get*()
除了AAudioStream_getTimestamp()
代码示例
两个小A音频演示的应用程序都可以在我们的GitHub页面:
Hello-Audio
产生正弦波并回放的音频。Echo
演示如何实现的输入/输出音频往返循环。它使用用于同步两个流的回调函数,并且执行连续缓冲器调谐。
已知的问题
- 音频延迟很高阻止写(),因为在AndroidØDP2释放不使用快车道。使用回调以获取更低的延迟。
AAUDIO_SHARING_MODE_EXCLUSIVE
不支持。使用AAUDIO_SHARING_MODE_SHARED
来代替。