美文网首页
Android AAudio高性能音频接口

Android AAudio高性能音频接口

作者: 文泰ChrisTwain | 来源:发表于2022-11-12 17:15 被阅读0次

一、简介

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.读取和写入音频流(读取录到的音频数据 | 写入音频数据播放)

录音数据回调接口实现样例:
通过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
};

对应的状态图

audio_lifecycle

发起录音或者放音,执行对应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

相关文章

  • Android AAudio高性能音频接口

    一、简介 1.AAudio Android Java层的提供的音频接口有MediaPlayer、MediaReco...

  • Android AAudio详解

    本篇介绍 AAudio 是Android O版本引入的C API,专门用于高性能音频场景,本篇介绍下AAudio的...

  • Android音频接口MediaPlayer,SoundPool

    Android提供了三套音频播放的API,分别是MediaPlayer,SoundPool,AudioTrack,...

  • Asus华硕主板主机前置音频口没声音处理办法

    在后置音频接口能够正确输出声音,而前置音频接口无声音输出的情况下,有两种原因: 前置音频接口的音频线没有正确的接在...

  • 稿-Android高性能音频编程

    高性能音频 Android NDK 包含了OpenSL ES的android 版本的实现。OpenSL ES可以让...

  • 2019-06-11

    Android实现高性能的帧动画礼物播放效果 引言:我们都知道Android实现动画的常见方式有那么几种,比如属性...

  • Ubuntu下同时开启后置与前置音频设备

    场景:我在台式机的后置音频接口插入了一个音箱,前置音频接口插入了一个带耳麦的耳机。 问题:我想让耳机与音箱同时输出...

  • OpenGL ES基础篇

    1. 概述 Android通过使用Open Graphics Library(OpenGL®)提供了对高性能2D和...

  • RenderScript介绍

    什么是RenderScript RenderScript 是用于在 Android 上以高性能运行计算密集型任务的...

  • RenderScript

    [译]原文链接 RenderScript是运行在Android系统上的一个高性能密集计算框架。RenderScri...

网友评论

      本文标题:Android AAudio高性能音频接口

      本文链接:https://www.haomeiwen.com/subject/mtfbnrtx.html