AAudio的原理可以参考Android音频API介绍「转载」-优快云博客
本文主要的目标是从指定的音频输入设备读取音频流,写入指定的音频输出设备。
1,主要流程
- 配置并创建音频输入流
- 打开音频输入流
- 启动音频输入流(设备)
- 配置并创建音频输出流
- 打开音频输出流
- 启动音频输出流(设备)
- 从音频输入流读取数据,写入音频输出流
2,AAudio的具体NDK代码
下面代码实现了从麦克风收音,输出到场声器或耳机。尽量使用耳机测试,使用外放扬声器和麦克会出现刺耳的声音回授audio feedback。
- 创建Builder,并配置Builder
AAudioStreamBuilder *builder;
aaudio_result_t result = AAudio_createStreamBuilder(&builder);
AAudioStreamBuilder_setDeviceId(builder, input_device_id);
AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_INPUT);
AAudioStreamBuilder_setSampleRate(builder, 44100);
AAudioStreamBuilder_setChannelCount(builder, 2);
AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);
- 用Builder打开音频输入流
AAudioStream *stream;
result = AAudioStreamBuilder_openStream(builder, &stream);
- 启动音频输入流,并等待音频流状态从Starting到Started。
result = AAudioStream_requestStart(stream);
int MY_TIMEOUT_NANOS = 100000000;
result = AAUDIO_ERROR_TIMEOUT;
aaudio_stream_state_t currentState = AAudioStream_getState(stream);
aaudio_stream_state_t inputState = currentState;
while (result == AAUDIO_ERROR_TIMEOUT && currentState != AAUDIO_STREAM_STATE_STARTED) {
result = AAudioStream_waitForStateChange(
stream, inputState, ¤tState, MY_TIMEOUT_NANOS);
}
- 获取流信息
int32_t deviceId = AAudioStream_getDeviceId(stream);
input_device_stat += "input device id:" + std::to_string(deviceId) + '\n';
aaudio_direction_t Direct = AAudioStream_getDirection(stream);
input_device_stat += "Direct:" + std::to_string(Direct) + '\n';
aaudio_sharing_mode_t SharingMode = AAudioStream_getSharingMode(stream);
input_device_stat += "SharingMode:" + std::to_string(SharingMode) + '\n';
int32_t SampleRate = AAudioStream_getSampleRate(stream);
input_device_stat += "SampleRate:" + std::to_string(SampleRate) + '\n';
int32_t ChannelCount = AAudioStream_getChannelCount(stream);
input_device_stat += "ChannelCount:" + std::to_string(ChannelCount) + '\n';
aaudio_format_t format = AAudioStream_getFormat(stream);
input_device_stat += "format:" + std::to_string(format) + '\n';
int32_t BufferCapacityInFrames = AAudioStream_getBufferCapacityInFrames(stream);
input_device_stat += "BufferCapacityInFrames:" + std::to_string(BufferCapacityInFrames) + '\n';
- 音频输出流代码与输入流类似,这里不重复给出
- 从音频输入流读取数据,写入音频输出流
while(Sampling){
aaudio_result_t readSize = AAudioStream_read(stream, audioBuffer, bufferSize, MY_TIMEOUT_NANOS);
if (readSize < 0) {
// Error!
break;
}
int ret = AAudioStream_write(output_stream, audioBuffer, readSize, MY_TIMEOUT_NANOS);
if (ret < 0) {
// Error!
break;
}
}
- 关闭流
AAudioStream_requestStop(output_stream);
AAudioStream_close(output_stream);
AAudioStream_requestStop(stream);
AAudioStream_close(stream);
AAudioStreamBuilder_delete(output_builder);
AAudioStreamBuilder_delete(builder);
这里代码存在的问题是不能重复打开流,可能是设备关闭代码有问题。
3,指定输入/输出音频设备的坑
这里希望使用函数AAudioStreamBuilder_setDeviceId来指定音频的输入和输出设备,例如
AAudioStreamBuilder_setDeviceId(builder, input_device_id);
官方手册说明了ID是从AudioManager.getDevices()处获取的,也提示了,你指定的设备,有可能设置不成功,并且不会返回错误。要求你自己获取并校验ID的设置。
这里我测试了两款手机:
3.1 Samsung S7 edge
硬件简要说明:手机机身底部麦克风、手机机身顶部麦克风,如果插耳机还有耳机麦克风;输出音频有手机外放、手机听筒、如果插耳机还有耳机;
AudioManager.getDevices()获取的输入设备列表:
AudioManager.getDevices()获取的输出设备列表:
输出设备无论指定什么,都是耳机输出(未插耳机是扬声器)
当指定输入设备为6 “内置麦克风”时,只有底部麦克风收音;
当指定输入设备为8 “通话麦克风”时,无法打开设备;
3.2 红米K40Pro
硬件简要说明:输入音频有手机机身底部麦克、手机机身顶部麦克、手机机身背部麦克风,如果插耳机还有耳机麦克风;输出音频有手机外放、手机听筒、如果插耳机还有耳机;
AudioManager.getDevices()获取的输入设备列表:
AudioManager.getDevices()获取的输出设备列表:
可以看出,硬件设备,AudioManager.getDevices()获取的输入设备并不等同于实际硬件。
测试时输出设备无论指定什么,都是耳机输出(未插耳机是扬声器)
当指定输入设备为17 “内置麦克风bottom”时,4个麦克风都收音;
当指定输入设备为19 “内置麦克风back”时,实测只有底部麦克风收音;
当指定输入设备为18 、20时,无声音;
当指定输入设备为32 “混音设备”时,无法打开设备;
通过向硬件麦克风吹气,可以从耳机中听到声音,能听出这款手机的收音,除了顶部和底部麦克风双声道对应双声道收音,其它都是单声道。
结论
Andoird音频架构中,HAL之上均无法完全控制硬件。AAudio只能在一定程度上指定硬件,并且打开流后,不一定成功打开的是你指定的硬件。
AAudioStreamBuilder_setDeviceId无法指定输出音频设备。
AAudioStreamBuilder_setDeviceId部分类型可以指定输入音频设备,也可能是权限有关系不可用。有单声道(所有麦克输入都是一样输出)和双声道(底部麦克左声道,顶部麦克是右声道)输出,具体不同的体现,应该取决于HAL层的实现。