使用AAudio进行音频采集
介绍
AAudio 是在Android 8.0版本中引入的一种基于C语言的本地音频API。
Android 8.1版本具有增强功能,可在支持MMAP的HAL和驱动程序结合使用时缩短延迟时间。
AAUdio提供 OpenSL ES 的替代方案,使用“生成器”设计模式来创建音频流。
AAudio 提供了一个低延迟数据路径。
在“专有”模式下,该功能可让客户端应用代码直接写入到与 ALSA 驱动程序共享的内存映射缓冲区。
在“共享”模式下,MMAP 缓冲区由在 AudioServer 中运行的混音器使用。
在“专有”模式下,由于数据会绕过混音器,延迟时间会明显缩短。
在下图中,我们可以看到脉冲编码调制 (PCM) 数据通过 MMAP FIFO 向下流入 ALSA 驱动程序。AAudio 服务会定期请求时间戳,然后通过原子消息队列将其传递给客户端的计时模型。
PS:AAudio是在OpenSL ES的基础上调用的,如果成功则使用AAudio,如果失败则使用OpenSL ES。
前提
OpenSL ES对音频的采集方面有的不兼容,准备实现一个通过AAudio进行音频采集。我刚接触只是踩坑。
上网搜了很久,关于AAudio的资料不多。
个人觉得参考`NDK官网AAudio介绍`和`韩曙亮博主的分析`,足够理解AAudio的相关API用处了。
具体的demo可参考`Oboe项目test部分`(直接搜“GitHub Oboe”就可以)。
使用软件和NDK:
- 编辑软件:Android Studio。
- NDK版本:r20。
1. 初始化
1.1头文件
#include <aaudio/AAudio.h> // 调用AAudio库
#include <android/log.h> // 打印log
#include <iostream>
using namespace std;
1.2 相关变量
// AAudio相关变量
AAudioStream *stream;
AAudioStreamBuilder *builder = NULL;
// 存储数据的相关变量
char g_Buffer[1024 * 1024] = {0}; // 音频大小限制为1M,可自行修改
int g_dataSize = 0; // 存放数据大小
int g_readIndex = 0; // 用于定位文件写操作的位置
1.2 初始化StreamBuilder
result=AAudio_createStreamBuilder(&builder);
if(result!=AAUDIO_OK){
LOGE("AAudio_createStreamBuildererror%d",result);
return0;
}
1.3 相关配置
//myAAudioStreamDataCallbackPlay是采集音频数据或者播放音频数据的函数,类似于线程的线程函数。
AAudioStreamBuilder_setDataCallback(builder,myAAudioStreamDataCallbackPlay,this);
//AAUDIO_PERFORMANCE_MODE_LOW_LATENCY是低延迟方式。
AAudioStreamBuilder_setPerformanceMode(builder,AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
1.4 设置具体参数
AAudioStreamBuilder_setDeviceId(builder,18);//设置音频流设备ID
//设置音频流方向,AAUDIO_DIRECTION_INPUT是录音,AAUDIO_DIRECTION_OUTPUT是播放
AAudioStreamBuilder_setDirection(builder,AAUDIO_DIRECTION_OUTPUT);
//设置共享模式,独占模式性能更高,延迟更低;
AAudioStreamBuilder_setSharingMode(builder,AAUDIO_SHARING_MODE_SHARED);
AAudioStreamBuilder_setSampleRate(builder,48000);//设置Rate
AAudioStreamBuilder_setChannelCount(builder,1);//设置音频通道个数
AAudioStreamBuilder_setFormat(builder,AAUDIO_FORMAT_PCM_I16);//设置音频流样本格式
AAudioStreamBuilder_setBufferCapacityInFrames(builder,4096);//在帧中设置缓冲区容量
1.5 获取帧大小
int32_tsizeInFrames = AAudioStream_getBufferSizeInFrames(stream);
LOGI("sizeInFrames = %d",sizeInFrames);
1.6 获取音频流设备ID:
int32_tDeviceId = AAudioStream_getDeviceId(stream);
1.5 打开Stream
result=AAudioStreamBuilder_openStream(builder,&stream);
if(result!=AAUDIO_OK){
LOGE("AAudioStreamBuilder_openStreamerror%d",result);
return0;
}
result=AAudioStream_requestStart(stream);//状态请求。开启音频流,即录音/播放
if(result!=AAUDIO_OK){
LOGE("AAudioStream_requestStarterror%d",result);
return0;
}
2. 数据的采集和播放
2.1 录音
aaudio_data_callback_result_tmyAAudioStreamDataCallback(
AAudioStream*stream,
void*userData,
void*audioData,
int32_tnumFrames){
// 把录音数据存储到本地
//staticFILE*fp=fopen("/sdcard/aaudio_record.pcm","wb");
//if(fp){
// fwrite(audioData,1,2*numFrames*sizeof(short),fp);
//}
//LOGI("myAAudioStreamDataCallbackcall%d,fp:%p",numFrames,fp);
// 把录音数据存储到临时变量中
memcpy(g_Buffer+g_dataSize,audioData,2*numFrames*sizeof(short));
g_dataSize+=2*numFrames*sizeof(short);
returnAAUDIO_CALLBACK_RESULT_CONTINUE;
}
2.2 播放
aaudio_data_callback_result_tmyAAudioStreamDataCallbackPlay(
AAudioStream*stream,
void*userData,
void*audioData,
int32_tnumFrames){
// 从本地视频文件中获取数据
//staticFILE*fp=fopen("/sdcard/aaudio_record.pcm","rb");
//LOGI("myAAudioStreamDataCallbackPlaycall%d,fp:%p",numFrames,fp);
//if(fp){
//size_treadSize=fread(audioData,1,2*numFrames*sizeof(short),fp);
//if(readSize>0){
// returnAAUDIO_CALLBACK_RESULT_CONTINUE;
//}
//}
//returnAAUDIO_CALLBACK_RESULT_STOP;
// 在临时变量中获取数据
LOGI("myAAudioStreamDataCallbackPlaynumFrames:%d,g_dataSize:%d",numFrames,g_dataSize);
constintreadSize=numFrames*2*sizeof(short);
if(g_readIndex+readSize<g_dataSize){
memcpy(audioData,g_Buffer+g_readIndex,readSize);
g_readIndex+=readSize;
returnAAUDIO_CALLBACK_RESULT_CONTINUE;
}else
returnAAUDIO_CALLBACK_RESULT_STOP;
}
其他
关于项目:
- 具体的实现直接设置几个按钮:开始录音,停止录音,开始播放,停止播放就可以了。
- 具体的代码就不上传了,代码量不多,主要时间花费在Android Studio的工程配置上了。
参考资料:
Android NDK中AAudio介绍
由于官网文件都是英文,看着也比较枯燥,我找了个整理好的大佬博客:韩博主的博客主页
其中一篇AAudio的分析博客链接:https://blog.csdn.net/shulianghan/article/details/103005057
PS:对于AAudio只是初学者,如有不对的地方欢迎指出。