Android 音频系统:从 AudioTrack 到 AudioFlinger

1. Android 音频框架概述

Android 音频系统:从 AudioTrack 到 AudioFlinger

Audio 是整个 Android 平台非常重要的一个组成部分,负责音频数据的采集和输出、音频流的控制、音频设备的管理、音量调节等,主要包括如下部分:

Audio Application Framework:音频应用框架

AudioTrack:负责回放数据的输出,属 Android 应用框架 API 类
AudioRecord:负责录音数据的采集,属 Android 应用框架 API 类
AudioSystem: 负责音频事务的综合管理,属 Android 应用框架 API 类

Audio Native Framework:音频本地框架

AudioTrack:负责回放数据的输出,属 Android 本地框架 API 类
AudioRecord:负责录音数据的采集,属 Android 本地框架 API 类
AudioSystem: 负责音频事务的综合管理,属 Android 本地框架 API 类

Audio Services:音频服务

AudioPolicyService:音频策略的制定者,负责音频设备切换的策略抉择、音量调节策略等
AudioFlinger:音频策略的执行者,负责输入输出流设备的管理及音频流数据的处理传输
Audio HAL:音频硬件抽象层,负责与音频硬件设备的交互,由 AudioFlinger 直接调用

与 Audio 强相关的有 MultiMedia,MultiMedia 负责音视频的编解码,

MultiMedia 将解码后的数据通过 AudioTrack 输出,而 AudioRecord 采集的录音数据交由 MultiMedia 进行编码。


2. AudioTrack API 概述

播放声音可以使用 MediaPlayer 和 AudioTrack,两者都提供 Java API 给应用开发者使用。两者的差别在于:
MediaPlayer 可以播放多种格式的音源,如 mp3、flac、wma、ogg、wav 等
而 AudioTrack 只能播放解码后的 PCM 数据流.

从上面 Android 音频系统架构图来看:
MediaPlayer 在 Native 层会创建对应的音频解码器和一个 AudioTrack,解码后的数据交由 AudioTrack 输出。
所以 MediaPlayer 的应用场景更广,一般情况下使用它也更方便;只有一些对声音时延要求非常苛刻的应用场景才需要用到 AudioTrack。

2.1. AudioTrack Java API

AudioTrack Java API 两种数据传输模式:

Transfer Mode Description
MODE_STATIC 应用进程将回放数据一次性付给 AudioTrack,适用于数据量小、时延要求高的场景
MODE_STREAM 用进程需要持续调用 write() 写数据到 FIFO,写数据时有可能遭遇阻塞
(等待 AudioFlinger::PlaybackThread 消费之前的数据),基本适用所有的音频场景

AudioTrack Java API 音频流类型:

Stream Type Description
STREAM_VOICE_CALL 电话语音
STREAM_SYSTEM 系统声音
STREAM_RING 铃声声音,如来电铃声、闹钟铃声等
STREAM_MUSIC 音乐声音
STREAM_ALARM 警告音
STREAM_NOTIFICATION 通知音
STREAM_DTMF DTMF 音(拨号盘按键音)

Android 为什么要定义这么多的流类型?这与 Android 的音频管理策略有关,例如:
① 音频流的音量管理,调节一个类型的音频流音量,不会影响到其他类型的音频流
② 根据流类型选择合适的输出设备:比如插着有线耳机期间,音乐声(STREAM_MUSIC)只会输出到有线耳机,而铃声(STREAM_RING)会同时输出到有线耳机和外放

这些属于 AudioPolicyService 的内容, 应用开发者应该根据应用场景选择相应的流类型,以便系统为这道流选择合适的输出设备。

一个 AudioTrack Java API 的测试例子(MODE_STREAM 模式):

    //Test case 1: setStereoVolume() with max volume returns SUCCESS
    @LargeTest
    public void testSetStereoVolumeMax() throws Exception {
        // constants for test
        final String TEST_NAME = "testSetStereoVolumeMax";
        final int TEST_SR = 22050;
        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
        final int TEST_MODE = AudioTrack.MODE_STREAM;
        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;

        //-------- initialization --------------
        // 稍后详细分析 getMinBufferSize
        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
        // 创建一个 AudioTrack 实例
        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, 
                minBuffSize, TEST_MODE);
        byte data[] = new byte[minBuffSize/2];
        //--------    test        --------------
        // 调用 write() 写入回放数据
        track.write(data, 0, data.length);
        track.write(data, 0, data.length);
        // 调用 play() 开始播放
        track.play();
        float maxVol = AudioTrack.getMaxVolume();
        assertTrue(TEST_NAME, track.setStereoVolume(maxVol, maxVol) == AudioTrack.SUCCESS);
        //-------- tear down      --------------
        // 播放完成后,调用 release() 释放 AudioTrack 实例
        track.release();
    }

详细说明下 getMinBufferSize() 接口: 字面意思是返回最小数据缓冲区的大小,它是声音能正常播放的最低保障,从函数参数来看,返回值取决于采样率、采样深度、声道数这三个属性。

MODE_STREAM 模式下,应用程序重点参考其返回值然后确定分配多大的数据缓冲区。
如果数据缓冲区分配得过小,那么播放声音会频繁遭遇 underrun,
underrun 是指生产者(AudioTrack)提供数据的速度跟不上消费者(AudioFlinger::PlaybackThread)消耗数据的速度,
反映到现实的后果就是声音断续卡顿,严重影响听觉体验。

如下是getMinBufferSize()的代码实现:

// AudioTrack.java
/**
     * Returns the estimated minimum buffer size required for an AudioTrack
     * object to be created in the {@link #MODE_STREAM} mode.
     * The size is an estimate because it does not consider either the route or the sink,
     * since neither is known yet.  Note that this size doesn't
     * guarantee a smooth playback under load, and higher values should be chosen according to
     * the expected frequency at which the buffer will be refilled with additional data to play.
     * For example, if you intend to dynamically set the source sample rate of an AudioTrack
     * to a higher value than the initial source sample rate, be sure to configure the buffer size
     * based on the highest planned sample rate.
     * @param sampleRateInHz the source sample rate expressed in Hz.
     *   {@link AudioFormat#SAMPLE_RATE_UNSPECIFIED} is not permitted.
     * @param channelConfig describes the configuration of the audio channels.
     *   See {@link AudioFormat#CHANNEL_OUT_MONO} and
     *   {@link AudioFormat#CHANNEL_OUT_STEREO}
     * @param audioFormat the format in which the audio data is represented.
     *   See {@link AudioFormat#ENCODING_PCM_16BIT} and
     *   {@link AudioFormat#ENCODING_PCM_8BIT},
     *   and {@link AudioFormat#ENCODING_PCM_FLOAT}.
     * @return {@link #ERROR_BAD_VALUE} if an invalid parameter was passed,
     *   or {@link #ERROR} if unable to query for output properties,
     *   or the minimum buffer size expressed in bytes.
     */
    static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
        int channelCount = 0;
        switch(channelConfig) {
        case AudioFormat.CHANNEL_OUT_MONO:
        case AudioFormat.CHANNEL_CONFIGURATION_MONO:
            channelCount = 1; // 单声道
            break;
        case AudioFormat.CHANNEL_OUT_STEREO:
        case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
            channelCount = 2; // 双声道
            break;
        default:
            if (!isMultichannelConfigSupported(channelConfig)) {
                loge("getMinBufferSize(): Invalid channel configuration.");
                return ERROR_BAD_VALUE;
            } else {
                channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
            }
        }

        if (!AudioFormat.isPublicEncoding(audioFormat)) {
            loge("getMinBufferSize(): Invalid audio format.");
            return ERROR_BAD_VALUE;
        }

        // sample rate, note these values are subject to change
        // Note: AudioFormat.SAMPLE_RATE_UNSPECIFIED is not allowed
        if ( (sampleRateInHz < AudioFormat.SAMPLE_RATE_HZ_MIN) ||
                (sampleRateInHz > AudioFormat.SAMPLE_RATE_HZ_MAX) ) {
            loge("getMinBufferSize(): " + sampleRateInHz + " Hz is not a supported sample rate.");
            return ERROR_BAD_VALUE; // 采样率支持:4KHz~192KHz
        }

        // 调用 JNI 方法,下面分析该函数
        int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
        if (size <= 0) {
            loge("getMinBufferSize(): error querying hardware");
            return ERROR;
        }
        else {
            return size;
        }
    }

// android_media_AudioTrack.cpp
// ----------------------------------------------------------------------------
// returns the minimum required size for the successful creation of a streaming AudioTrack
// returns -1 if there was an error querying the hardware.
static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env,  jobject thiz,
    jint sampleRateInHertz, jint channelCount, jint audioFormat) {

    size_t frameCount;
    // 调用 AudioTrack::getMinFrameCount,这里不深究,到 native 层再分析
    // 这个函数用于确定至少设置多少个 frame 才能保证声音正常播放,也就是最低帧数
    const status_t status = AudioTrack::getMinFrameCount(&frameCount, AUDIO_STREAM_DEFAULT,
            sampleRateInHertz);
    if (status != NO_ERROR) {
        ALOGE("AudioTrack::getMinFrameCount() for sample rate %d failed with status %d",
                sampleRateInHertz, status);
        return -1;
    }
    const audio_format_t format = audioFormatToNative(audioFormat);
    if (audio_has_proportional_frames(format)) {
        const size_t bytesPerSample = audio_bytes_per_sample(format);
        return frameCount * channelCount * bytesPerSample; // PCM 数据最小缓冲区大小
    } else {
        return frameCount;
    }
}

可见最小缓冲区的大小 = 最低帧数 * 声道数 * 采样深度,(采样深度以字节为单位)
到这里大家应该有所明悟了吧,在视频中,如果帧数过低,那么画面会有卡顿感,对于音频,道理也是一样的。最低帧数如何求得,我们到 native 层再解释。

关于 MediaPlayer、AudioTrack,更多更详细的 API 接口说明请参考 Android Developer:
MediaPlayer:https://developer.android.com/reference/android/media/MediaPlayer.html
AudioTrack:https://developer.android.com/reference/android/media/AudioTrack.html

2.2. AudioTrack Native API

AudioTrack Native API 四种数据传输模式:

Transfer Mode Description
TRANSFER_CALLBACK 在 AudioTrackThread 线程中通过 audioCallback 回调函数主动从应用进程那里索取数据,
ToneGenerator 采用这种模式
TRANSFER_OBTAIN 应用进程需要调用 obtainBuffer()/releaseBuffer() 填充数据,目前我还没有见到实际的使用场景
TRANSFER_SYNC 应用进程需要持续调用 write() 写数据到 FIFO,写数据时有可能遭遇阻塞
(等待 AudioFlinger::PlaybackThread 消费之前的数据),基本适用所有的音频场景;
对应于 AudioTrack Java API 的 MODE_STREAM 模式
TRANSFER_SHARED 应用进程将回放数据一次性付给 AudioTrack,适用于数据量小、时延要求高的场景;
对应于 AudioTrack Java API 的 MODE_STATIC 模式

AudioTrack Native API 音频流类型:

Stream Type Description
AUDIO_STREAM_VOICE_CALL 电话语音
AUDIO_STREAM_SYSTEM 系统声音
AUDIO_STREAM_RING 铃声声音,如来电铃声、闹钟铃声等
AUDIO_STREAM_MUSIC 音乐声音
AUDIO_STREAM_ALARM 警告音
AUDIO_STREAM_NOTIFICATION 通知音
AUDIO_STREAM_DTMF DTMF 音(拨号盘按键音)

AudioTrack Native API 输出标识:

AUDIO_OUTPUT_FLAG Description
AUDIO_OUTPUT_FLAG_DIRECT 表示音频流直接输出到音频设备,不需要软件混音,
一般用于 HDMI 设备声音输出
AUDIO_OUTPUT_FLAG_PRIMARY 表示音频流需要输出到主输出设备,
一般用于铃声类声音
AUDIO_OUTPUT_FLAG_FAST 表示音频流需要快速输出到音频设备,
一般用于按键音、游戏背景音等对时延要求高的场景
AUDIO_OUTPUT_FLAG_DEEP_BUFFER 表示音频流输出可以接受较大的时延,
一般用于音乐、视频播放等对时延要求不高的场景
AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 表示音频流没有经过软件解码,
需要输出到硬件解码器,由硬件解码器进行解码

我们根据不同的播放场景,使用不同的输出标识,
如按键音、游戏背景音对输出时延要求很高,那么就需要置 AUDIO_OUTPUT_FLAG_FAST,
具体可以参考 ToneGenerator、SoundPool 和 OpenSL ES。

一个 AudioTrack Natvie API 的测试例子(MODE_STATIC/TRANSFER_SHARED 模式),
代码文件位置:frameworks/base/media/tests/audiotests/shared_mem_test.cpp:

int AudioTrackTest::Test01() {

    sp<MemoryDealer> heap;
    sp<IMemory> iMem;
    uint8_t* p;

    short smpBuf[BUF_SZ];
    long rate = 44100;
    unsigned long phi;
    unsigned long dPhi;
    long amplitude;
    long freq = 1237;
    float f0;

    f0 = pow(2., 32.) * freq / (float)rate;
    dPhi = (unsigned long)f0;
    amplitude = 1000;
    phi = 0;
    Generate(smpBuf, BUF_SZ, amplitude, phi, dPhi);  // fill buffer

    for (int i = 0; i < 1024; i++) {
        // 分配一块匿名共享内存
        heap = new MemoryDealer(1024*1024, "AudioTrack Heap Base");

        iMem = heap->allocate(BUF_SZ*sizeof(short));

        // 把音频数据拷贝到这块匿名共享内存上
        p = static_cast<uint8_t*>(iMem->pointer());
        memcpy(p, smpBuf, BUF_SZ*sizeof(short));

        // 构造一个 AudioTrack 实例,该 AudioTrack 的数据方式是 MODE_STATIC
        // 音频数据已经一次性拷贝到共享内存上了,不用再调用 track->write() 填充数据了
        sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type
               rate,
               AUDIO_FORMAT_PCM_16_BIT,// word length, PCM
               AUDIO_CHANNEL_OUT_MONO,
               iMem);

        // 检查 AudioTrack 实例是否构造成功饿了
        status_t status = track->initCheck();
        if(status != NO_ERROR) {
            track.clear();
            ALOGD("Failed for initCheck()");
            return -1;
        }

        // start play
        ALOGD("start");
        track->start(); // 开始播放

        usleep(20000);

        ALOGD("stop");
        track->stop(); // 停止播放
        iMem.clear();
        heap.clear();
        usleep(20000);
    }

    return 0;
}

上个小节还存在一个问题:AudioTrack::getMinFrameCount() 如何计算最低帧数呢?