一、简介
1.AAudio
Android Java层的提供的音频接口有MediaPlayer、MediaRecorder、AudioTrack、AudioRecord
。AAudio则是Google在Android O(8.0版本-API level 26-2017年)引入的低延迟、高性能的JNI录放音接口,采用最精简设计,不负责音频设备管理 , 文件I/O, 音频编解码等操作,只提供写入音频流进行放音、录音的功能。属于NDK开发范围,应用层使用JNI封装c++接口调用。
AAudio-demo
2.API接口部分特点总结
(1)发起录音或者放音后,能从回调函数中直接读取一帧一帧的音频流数据;
(2)线程不安全,为提高性能,AAudio设计上避免使用互斥量,在不同线程中同时调用读写流会导致crash异常,从而需要使用者自己控制并发;
(3)AAudio是轻量级接口,也没有文件I/O,录音API通过回调不断返回帧数据,如果需要保存成文件需自行实现,直接保存录音数据是原始PCM文件,可自己再写头成WAV文件格式播放。
二、架构
AAudio通过mmap内存映射实现音频传输,提升效率,减小延迟
aaudio
3.AAudio音频流三要素(音频设备、共享模式、数据格式)
(1)AAudio音频流设备:数据从耳机输入,数据输出到发音设备;
① 音频输入(声音来源):从话筒等音频输入设备中采集音频数据,然后可使用 AAudio读取音频流;
② 音频输出(声音接收):将音频流写入到 AAudio, AAudio 会以极高性能方式将音频流输出到发音设备中;从输入端获取数据 ( 麦克风 -> 音频流 -> 内存 ),将音频数据写出到输出端 ( 内存 -> 音频流 -> 喇叭 );
(2)音频流读写数据格式:使用AAudioStream 结构表示音频流 , 读取和写出音频流数据都使用该数据结构;
AAudio样本格式:
aaudio_format_t | C 数据类型 | 备注 |
---|---|---|
AAUDIO_FORMAT_PCM_I16 | int16_t | 通用 16 位样本,Q0.15 格式 |
AAUDIO_FORMAT_PCM_FLOAT | 浮点数 | -1.0 ~ +1.0 |
(3)共享模式:可设置独占模式(表示流对音频设备独占访问)和共享模式(允许混合音频,AAudio会将同一设备的所有共享流混合)。独占模式下可能会有音频抢占问题,比如通话场景。
AAudio.h
enum {
/**
* This will be the only stream using a particular source or sink.
* This mode will provide the lowest possible latency.
* You should close EXCLUSIVE streams immediately when you are not using them.
*/
AAUDIO_SHARING_MODE_EXCLUSIVE,
/**
* Multiple applications will be mixed by the AAudio Server.
* This will have higher latency than the EXCLUSIVE mode.
*/
AAUDIO_SHARING_MODE_SHARED
};
三、API说明
1.头文件 #include <aaudio/AAudio.h>
2.通过Builder模式初始化参数
AAudioStreamBuilder *builder;
aaudio_result_t result = AAudio_createStreamBuilder(&builder);
AAudioStreamBuilder_setDeviceId(builder, recordingDeviceId_);
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_INPUT); // 录音,AAUDIO_DIRECTION_OUTPUT表示放音
AAudioStreamBuilder_setSampleRate(builder, 48000);
AAudioStreamBuilder_setChannelCount(builder, inputChannelCount_); // 可设置单声道(Mono)或立体声(Stereo),参数分别为1和2
AAudioStreamBuilder_setDataCallback(builder, ::recordDataCallback, this);
AAudioStreamBuilder_setErrorCallback(builder, ::recordErrorCallback, this);
AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);
AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_EXCLUSIVE);
AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
3.使用音频流
注:接口都是异步函数
// 创建流
aaudio_result_t result = AAudioStreamBuilder_openStream(builder, &recordingStream_);
if (result == AAUDIO_OK && recordingStream_ != nullptr) {
// open success
}
aaudio_result_t result;
result = AAudioStream_requestStart(stream); // 开始
result = AAudioStream_requestPause(stream); // 暂停流
result = AAudioStream_requestFlush(stream); // 刷新
result = AAudioStream_requestStop(stream); // 停止
result = AAudioStream_close(stream); // 关闭流
4.读取和写入音频流(读取录到的音频数据 | 写入音频数据播放)
- ①非阻塞方式读写音频流数据,回调接口 如下代码
- ② 阻塞方式读写 音频流数据,使用函数
AAudioStream_read(stream, buffer, numFrames, timeoutNanos)
和AAudioStream_write(stream, buffer, numFrames, timeoutNanos)
来读取或写入流。
录音数据回调接口实现样例:
通过AAudioStreamBuilder_setDataCallback(builder, ::recordDataCallback, this);
设置音频数据回调
注意:recordDataCallback()函数返回AAUDIO_CALLBACK_RESULT_STOP
流会立刻停止,一般在处理到异常时返回,正常则返回AAUDIO_CALLBACK_RESULT_CONTINUE
录音流数据会被持续接收到
aaudio_data_callback_result_t recordDataCallback(AAudioStream *stream, void *userData,
void *audioData, int32_t numFrames) {
if (userData == nullptr) {
LOGI("AAudioEngineCPP %s", "userDatauserData == nullptr");
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
if (audioData == nullptr) {
LOGI("AAudioEngineCPP %s", "audioData == nullptr");
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
AAudioEngine *audioEngine = reinterpret_cast<AAudioEngine *>(userData);
return audioEngine->dataToRecordCallback(stream, audioData, numFrames);
}
aaudio_data_callback_result_t AAudioEngine::dataToRecordCallback(AAudioStream *stream,
void *audioData,
int32_t numFrames) {
static uint64_t logging_flag;
int recordDataTemp = 0;
if (recordFile && isRecordIng) {
// 可以选择直接将数据写入文件,也可内存中直接存成原始录音数据的数组,方便某些特殊业务直接解析录音数据
fwrite(audioData, 1, 2 * numFrames * sizeof(short), recordFile);
// 直接存到Vector
for(int32_t i = 0; i < numFrames * 2; i++)
{
recordDataTemp = *((int16_t *) audioData + i);
if(i%2 == 0)
{
recordData1.push_back((double) recordDataTemp / 32768);
}
else
{
recordData2.push_back((double) recordDataTemp / 32768);
}
}
if((logging_flag++) % 100 == 0)
{
LOGI("AAudioEngineCPP recordIng");
}
return AAUDIO_CALLBACK_RESULT_CONTINUE;
} else {
LOGI("AAudioEngineCPP isRecordIng status: %d", isRecordIng);
}
return AAUDIO_CALLBACK_RESULT_STOP;
}
放音回调接口实现样例:
aaudio_data_callback_result_t AAudioEngine::dataToPlayCallback(AAudioStream *stream,
void *audioData,
int32_t numFrames) {
static uint64_t logging_flag;
if (playFile && isPlaying) {
size_t treadsize = fread(audioData, 2 * numFrames * sizeof(short), 1, playFile);
if((logging_flag++)%20==0)
{
LOGI("AAudioEngineCPP %s", "playing");
}
if (treadsize > 0) {
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
}
fclose(playFile);
LOGI("AAudioEngineCPP %s", "dataToPlayCallback fclose");
if (listener != nullptr) {
LOGI("AAudioEngineCPP %s", "onPlayingEnd");
}
LOGI("AAudioEngineCPP %s", "AAUDIO_CALLBACK_RESULT_STOP");
return AAUDIO_CALLBACK_RESULT_STOP;
}
四、音频流状态码
创建、打开、关闭...流返回的结果码
返回值处理方式举例
aaudio_result_t result = AAudioStream_close(stream);
if (result != AAUDIO_OK) {
LOGE("Error closing stream. %s", AAudio_convertResultToText(result));
}
}
enum { // aaudio_result_t int32_t
OK = 0, // AAUDIO_OK
ErrorBase = -900, // AAUDIO_ERROR_BASE,
ErrorDisconnected = -899, // AAUDIO_ERROR_DISCONNECTED,
ErrorIllegalArgument = -898, // AAUDIO_ERROR_ILLEGAL_ARGUMENT,
ErrorInternal = -896, // AAUDIO_ERROR_INTERNAL,
ErrorInvalidState = -895, // AAUDIO_ERROR_INVALID_STATE,
ErrorInvalidHandle = -892, // AAUDIO_ERROR_INVALID_HANDLE,
ErrorUnimplemented = -890, // AAUDIO_ERROR_UNIMPLEMENTED,
ErrorUnavailable = -889, // AAUDIO_ERROR_UNAVAILABLE,
ErrorNoFreeHandles = -888, // AAUDIO_ERROR_NO_FREE_HANDLES,
ErrorNoMemory = -887, // AAUDIO_ERROR_NO_MEMORY,
ErrorNull = -886, // AAUDIO_ERROR_NULL,
ErrorTimeout = -885, // AAUDIO_ERROR_TIMEOUT,
ErrorWouldBlock = -884, // AAUDIO_ERROR_WOULD_BLOCK,
ErrorInvalidFormat = -883, // AAUDIO_ERROR_INVALID_FORMAT,
ErrorOutOfRange = -882, // AAUDIO_ERROR_OUT_OF_RANGE,
ErrorNoService = -881, // AAUDIO_ERROR_NO_SERVICE,
ErrorInvalidRate = -880 // AAUDIO_ERROR_INVALID_RATE
};
若无录音权限,则AAudioStream_requestStart
返回AAUDIO_ERROR_ILLEGAL_ARGUMENT ErrorIllegalArgument = -898 AAUDIO_ERROR_ILLEGAL_ARGUMENT
错误码
音频流状态值
可通过此方法读取aaudio_stream_state_t AAudioStream_getState(AAudioStream* stream)
enum { // aaudio_stream_state_t int32_t
Uninitialized = 0, // AAUDIO_STREAM_STATE_UNINITIALIZED,
Unknown = 1, // AAUDIO_STREAM_STATE_UNKNOWN,
Open = 2, // AAUDIO_STREAM_STATE_OPEN,
Starting = 3, // AAUDIO_STREAM_STATE_STARTING,
Started = 4, // AAUDIO_STREAM_STATE_STARTED,
Pausing = 5, // AAUDIO_STREAM_STATE_PAUSING,
Paused = 6, // AAUDIO_STREAM_STATE_PAUSED,
Flushing = 7, // AAUDIO_STREAM_STATE_FLUSHING,
Flushed = 8, // AAUDIO_STREAM_STATE_FLUSHED,
Stopping = 9, // AAUDIO_STREAM_STATE_STOPPING,
Stopped = 10, // AAUDIO_STREAM_STATE_STOPPED,
Closing = 11, // AAUDIO_STREAM_STATE_CLOSING,
Closed = 12, // AAUDIO_STREAM_STATE_CLOSED,
Disconnected = 13 // AAUDIO_STREAM_STATE_DISCONNECTED
};
对应的状态图
发起录音或者放音,执行对应AAudioStream_相关流方法时日志会打印对应的状态码,日志如下:
AAudioStream: setState(s#7) from 0 to 2 打开
AAudioStream: setState(s#7) from 2 to 3 正在打开
AAudioStream: setState(s#7) from 3 to 4 已经打开,开始录音或放音
AAudioStream: setState(s#7) from 4 to 9 停止
AAudioStream: setState(s#7) from 9 to 11 关闭中
AAudioStream: setState(s#7) from 11 to 11
AAudioStream: setState(s#7) from 11 to 12 已关闭
ps:
1.AAudio API线程不安全,并发调用可能导致程序崩溃,上层调用可加锁保护;
2.注意添加录音权限<uses-permission android:name="android.permission.RECORD_AUDIO" />
3.若调用了关闭流AAudioStream_close,仍有dataCallback数据回调在执行完,会等待数据回调执行完才关闭流,故这两个函数中无需加锁
4.在发起AAudio音频流时,若设备中有其它进程也在发起录放音可能存在音频焦点抢占回调recordErrorCallback异常,比如华为手机多屏协同场景通过DMSDP将音频切换到PC可能导致此情况
日志如下
I/AUDIO-APP: AAudioEngineCPP recordIng, numFrames: 96.
D/AAudioStream: setState(s#1) from 4 to 13
W/AudioStreamInternal_Client: onEventFromServer - AAUDIO_SERVICE_EVENT_DISCONNECTED - FIFO cleared
E/AudioStreamInternalCapture_Client: callbackLoop: read() returned -899
I/AUDIO-APP: AAudioEngineCPP recordErrorCallback has result: AAUDIO_ERROR_DISCONNECTED
D/AudioStreamInternalCapture_Client: callbackLoop() exiting, result = -899, isActive() = 0
I/AUDIO-APP: AAudioEngineCPP Resetting RecordStreams
I/AUDIO-APP: AAudioEngineCPP closeRecordStreams begin
D/AAudio: AAudioStream_close(s#1) called ---------------
异常回调函数代码如下
void AAudioEngine::recordErrorCallback(AAudioStream *stream,
aaudio_result_t error) {
LOGI("AAudioEngineCPP recordErrorCallback has result: %s", AAudio_convertResultToText(error));
aaudio_stream_state_t streamState = AAudioStream_getState(stream);
if (streamState == AAUDIO_STREAM_STATE_DISCONNECTED) {
// 收到异常断连,需主动关闭音频流,否则可导致流泄露产生额外功耗,同时注意加锁避免并发关闭音频流
std::function<void(void)> resetRecordStreams = std::bind(&AAudioEngine::resetRecordStreams,this);
std::thread recordStreamResetThread(resetRecordStreams);
recordStreamResetThread.detach();
}
}
void AAudioEngine::resetRecordStreams() {
LOGI("AAudioEngineCPP Resetting RecordStreams");
if (resetRecordLock_.try_lock()) {
closeRecordStreams();
resetRecordLock_.unlock();
} else {
LOGW("AAudioEngineCPP Reset RecordStreams operation already in progress");
}
}
参考:
AAudio使用说明
AAudio API文档
AAudio架构
AAudio架构讲解以及实现范例
Android Audio音频框架
AAudio-demo
网友评论