Android Auido(2)- AudioTrack to AudioFlinger

1. Android 音頻框架概述

Android_Audio_Architecture

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 進行編碼。

本文分析基於 Android 7.0 - Nougat

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();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

詳細說明下 getMinBufferSize() 接口,字面意思是返回最小數據緩衝區的大小,它是聲音能正常播放的最低保障,從函數參數來看,返回值取決於採樣率、採樣深度、聲道數這三個屬性。MODE_STREAM 模式下,應用程序重點參考其返回值然後確定分配多大的數據緩衝區。如果數據緩衝區分配得過小,那麼播放聲音會頻繁遭遇 underrun,underrun 是指生產者(AudioTrack)提供數據的速度跟不上消費者(AudioFlinger::PlaybackThread)消耗數據的速度,反映到現實的後果就是聲音斷續卡頓,嚴重影響聽覺體驗。

// 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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

可見最小緩衝區的大小 = 最低幀數 * 聲道數 * 採樣深度,(採樣深度以字節爲單位),到這裏大家應該有所明悟了吧,在視頻中,如果幀數過低,那麼畫面會有卡頓感,對於音頻,道理也是一樣的。最低幀數如何求得,我們到 native 層再解釋。

關於 MediaPlayer、AudioTrack,更多更詳細的 API 接口說明請參考 Android Developer:

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

上個小節還存在一個問題:AudioTrack::getMinFrameCount() 如何計算最低幀數呢?

首先要了解音頻領域中,幀(frame)的概念:幀表示一個完整的聲音單元,所謂的聲音單元是指一個採樣樣本;如果是雙聲道,那麼一個完整的聲音單元就是 2 個樣本,如果是 5.1 聲道,那麼一個完整的聲音單元就是 6 個樣本了。幀的大小(一個完整的聲音單元的數據量)等於聲道數乘以採樣深度,即 frameSize = channelCount * bytesPerSample。幀的概念非常重要,無論是框架層還是內核層,都是以幀爲單位去管理音頻數據緩衝區的。

其次還得了解音頻領域中,傳輸延遲(latency)的概念:傳輸延遲表示一個週期的音頻數據的傳輸時間。可能有些讀者一臉懵逼,一個週期的音頻數據,這又是啥?我們再引入週期(period)的概念:Linux ALSA 把數據緩衝區劃分爲若干個塊,dma 每傳輸完一個塊上的數據即發出一個硬件中斷,cpu 收到中斷信號後,再配置 dma 去傳輸下一個塊上的數據;一個塊即是一個週期,週期大小(periodSize)即是一個數據塊的幀數。再回到傳輸延遲(latency),傳輸延遲等於週期大小除以採樣率,即 latency = periodSize / sampleRate

最後瞭解下音頻重採樣:音頻重採樣是指這樣的一個過程——把一個採樣率的數據轉換爲另一個採樣率的數據。Android 原生系統上,音頻硬件設備一般都工作在一個固定的採樣率上(如 48 KHz),因此所有音軌數據都需要重採樣到這個固定的採樣率上,然後再輸出。爲什麼這麼做?系統中可能存在多個音軌同時播放,而每個音軌的採樣率可能是不一致的;比如在播放音樂的過程中,來了一個提示音,這時需要把音樂和提示音混音並輸出到硬件設備,而音樂的採樣率和提示音的採樣率不一致,問題來了,如果硬件設備工作的採樣率設置爲音樂的採樣率的話,那麼提示音就會失真;因此最簡單見效的解決方法是:硬件設備工作的採樣率固定一個值,所有音軌在 AudioFlinger 都重採樣到這個採樣率上,混音後輸出到硬件設備,保證所有音軌聽起來都不失真。

sample、frame、period、latency 這些概念與 Linux ALSA 及硬件設備的關係非常密切,這裏點到即止,如有興趣深入瞭解的話,可參考:Linux ALSA 音頻系統:邏輯設備篇

瞭解這些前置知識後,我們再分析 AudioTrack::getMinFrameCount() 這個函數:

status_t AudioTrack::getMinFrameCount(
        size_t* frameCount,
        audio_stream_type_t streamType,
        uint32_t sampleRate)
{
    if (frameCount == NULL) {
        return BAD_VALUE;
    }

    // 通過 binder 調用到 AudioFlinger::sampleRate(),取得硬件設備的採樣率
    uint32_t afSampleRate;
    status_t status;
    status = AudioSystem::getOutputSamplingRate(&afSampleRate, streamType);
    if (status != NO_ERROR) {
        ALOGE("Unable to query output sample rate for stream type %d; status %d",
                streamType, status);
        return status;
    }
    // 通過 binder 調用到 AudioFlinger::frameCount(),取得硬件設備的週期大小
    size_t afFrameCount;
    status = AudioSystem::getOutputFrameCount(&afFrameCount, streamType);
    if (status != NO_ERROR) {
        ALOGE("Unable to query output frame count for stream type %d; status %d",
                streamType, status);
        return status;
    }
    // 通過 binder 調用到 AudioFlinger::latency(),取得硬件設備的傳輸延遲
    uint32_t afLatency;
    status = AudioSystem::getOutputLatency(&afLatency, streamType);
    if (status != NO_ERROR) {
        ALOGE("Unable to query output latency for stream type %d; status %d",
                streamType, status);
        return status;
    }

    // When called from createTrack, speed is 1.0f (normal speed).
    // This is rechecked again on setting playback rate (TODO: on setting sample rate, too).
    // 根據 afSampleRate、afFrameCount、afLatency 計算出一個最低幀數
    *frameCount = calculateMinFrameCount(afLatency, afFrameCount, afSampleRate, sampleRate, 1.0f);

    // The formula above should always produce a non-zero value under normal circumstances:
    // AudioTrack.SAMPLE_RATE_HZ_MIN <= sampleRate <= AudioTrack.SAMPLE_RATE_HZ_MAX.
    // Return error in the unlikely event that it does not, as that's part of the API contract.
    if (*frameCount == 0) {
        ALOGE("AudioTrack::getMinFrameCount failed for streamType %d, sampleRate %u",
                streamType, sampleRate);
        return BAD_VALUE;
    }
    ALOGV("getMinFrameCount=%zu: afFrameCount=%zu, afSampleRate=%u, afLatency=%u",
            *frameCount, afFrameCount, afSampleRate, afLatency);
    return NO_ERROR;
}

// 有興趣的可以研究 calculateMinFrameCount() 的實現,需大致瞭解重採樣算法原理
static size_t calculateMinFrameCount(
        uint32_t afLatencyMs, uint32_t afFrameCount, uint32_t afSampleRate,
        uint32_t sampleRate, float speed)
{
    // Ensure that buffer depth covers at least audio hardware latency
    uint32_t minBufCount = afLatencyMs / ((1000 * afFrameCount) / afSampleRate);
    if (minBufCount < 2) {
        minBufCount = 2;
    }
    ALOGV("calculateMinFrameCount afLatency %u  afFrameCount %u  afSampleRate %u  "
            "sampleRate %u  speed %f  minBufCount: %u",
            afLatencyMs, afFrameCount, afSampleRate, sampleRate, speed, minBufCount);
    return minBufCount * sourceFramesNeededWithTimestretch(
            sampleRate, afFrameCount, afSampleRate, speed);
}

static inline size_t sourceFramesNeededWithTimestretch(
        uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate,
        float speed) {
    // required is the number of input frames the resampler needs
    size_t required = sourceFramesNeeded(srcSampleRate, dstFramesRequired, dstSampleRate);
    // to deliver this, the time stretcher requires:
    return required * (double)speed + 1 + 1; // accounting for rounding dependencies
}

// Returns the source frames needed to resample to destination frames.  This is not a precise
// value and depends on the resampler (and possibly how it handles rounding internally).
// Nevertheless, this should be an upper bound on the requirements of the resampler.
// If srcSampleRate and dstSampleRate are equal, then it returns destination frames, which
// may not be true if the resampler is asynchronous.
static inline size_t sourceFramesNeeded(
        uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate) {
    // +1 for rounding - always do this even if matched ratio (resampler may use phases not ratio)
    // +1 for additional sample needed for interpolation
    return srcSampleRate == dstSampleRate ? dstFramesRequired :
            size_t((uint64_t)dstFramesRequired * srcSampleRate / dstSampleRate + 1 + 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

我們不深入分析 calculateMinFrameCount() 函數了,並不是說這個函數的流程有多複雜,而是它涉及到音頻重採樣的背景原理,說清楚 how 很容易,但說清楚 why 就很困難了。目前我們只需要知道:這個函數根據硬件設備的配置信息(採樣率、週期大小、傳輸延遲)和音軌的採樣率,計算出一個最低幀數(應用程序至少設置多少個幀才能保證聲音正常播放)。

說點題外話,Anroid 2.2 時,AudioTrack::getMinFrameCount() 的處理很簡單:

status_t AudioTrack::getMinFrameCount(
        int* frameCount,
        int streamType,
        uint32_t sampleRate)
{
    int afSampleRate;
    if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
        return NO_INIT;
    }
    int afFrameCount;
    if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
        return NO_INIT;
    }
    uint32_t afLatency;
    if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) {
        return NO_INIT;
    }

    // Ensure that buffer depth covers at least audio hardware latency
    uint32_t minBufCount = afLatency / ((1000 * afFrameCount) / afSampleRate);
    if (minBufCount < 2) minBufCount = 2;

    *frameCount = (sampleRate == 0) ? afFrameCount * minBufCount :
              afFrameCount * minBufCount * sampleRate / afSampleRate;
    return NO_ERROR;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

從這段來看,最低幀數也是基於重採樣來計算的,只不過這裏的處理很粗糙:afFrameCount 是硬件設備處理單個數據塊的幀數,afSampleRate 是硬件設備配置的採樣率,sampleRate 是音軌的採樣率,如果要把音軌數據重採樣到 afSampleRate 上,那麼反推算出應用程序最少傳入的幀數爲 afFrameCount * sampleRate / afSampleRate,而爲了播放流暢,實際上還要大一點,所以再乘以一個係數(可參照 framebuffer 雙緩衝,一個緩衝緩存當前的圖像,一個緩衝準備下一幅的圖像,這樣圖像切換更流暢),然後就得出一個可以保證播放流暢的最低幀數 minFrameCount = (afFrameCount * sampleRate / afSampleRate) * minBufCount

爲什麼 Android 7.0 這方面的處理比 Android 2.2 複雜那麼多呢?我想是兩個原因:

  1. Android 7.0 充分考慮了邊界處理
  2. Android 2.2 只支持採樣率 4~48 KHz 的音軌,但 Android 7.0 支持採樣率 4~192 KHz 的音軌,因此現在對重採樣處理提出更嚴格的要求

3. AudioFlinger 概述

AudioPolicyService 與 AudioFlinger 是 Android 音頻系統的兩大基本服務。前者是音頻系統策略的制定者,負責音頻設備切換的策略抉擇、音量調節策略等;後者是音頻系統策略的執行者,負責音頻流設備的管理及音頻流數據的處理傳輸,所以 AudioFlinger 也被認爲是 Android 音頻系統的引擎。

3.1. AudioFlinger 代碼文件結構

$ tree ./frameworks/av/services/audioflinger/
./frameworks/av/services/audioflinger/
├── Android.mk
├── AudioFlinger.cpp
├── AudioFlinger.h
├── AudioHwDevice.cpp
├── AudioHwDevice.h
├── AudioMixer.cpp
├── AudioMixer.h
├── AudioMixerOps.h
├── audio-resampler
│   ├── Android.mk
│   ├── AudioResamplerCoefficients.cpp
│   └── filter_coefficients.h
├── AudioResampler.cpp
├── AudioResamplerCubic.cpp
├── AudioResamplerCubic.h
├── AudioResamplerDyn.cpp
├── AudioResamplerDyn.h
├── AudioResamplerFirGen.h
├── AudioResamplerFirOps.h
├── AudioResamplerFirProcess.h
├── AudioResamplerFirProcessNeon.h
├── AudioResampler.h
├── AudioResamplerSinc.cpp
├── AudioResamplerSincDown.h
├── AudioResamplerSinc.h
├── AudioResamplerSincUp.h
├── AudioStreamOut.cpp
├── AudioStreamOut.h
├── AudioWatchdog.cpp
├── AudioWatchdog.h
├── BufferProviders.cpp
├── BufferProviders.h
├── Configuration.h
├── Effects.cpp
├── Effects.h
├── FastCapture.cpp
├── FastCaptureDumpState.cpp
├── FastCaptureDumpState.h
├── FastCapture.h
├── FastCaptureState.cpp
├── FastCaptureState.h
├── FastMixer.cpp
├── FastMixerDumpState.cpp
├── FastMixerDumpState.h
├── FastMixer.h
├── FastMixerState.cpp
├── FastMixerState.h
├── FastThread.cpp
├── FastThreadDumpState.cpp
├── FastThreadDumpState.h
├── FastThread.h
├── FastThreadState.cpp
├── FastThreadState.h
├── MODULE_LICENSE_APACHE2
├── NOTICE
├── PatchPanel.cpp
├── PatchPanel.h
├── PlaybackTracks.h
├── RecordTracks.h
├── ServiceUtilities.cpp
├── ServiceUtilities.h
├── SpdifStreamOut.cpp
├── SpdifStreamOut.h
├── StateQueue.cpp
├── StateQueue.h
├── StateQueueInstantiations.cpp
├── test-resample.cpp
├── tests
│   ├── Android.mk
│   ├── build_and_run_all_unit_tests.sh
│   ├── mixer_to_wav_tests.sh
│   ├── resampler_tests.cpp
│   ├── run_all_unit_tests.sh
│   ├── test-mixer.cpp
│   └── test_utils.h
├── Threads.cpp
├── Threads.h
├── TrackBase.h
└── Tracks.cpp

2 directories, 77 files
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

記得剛接觸 Android 時,版本是 Android 2.2-Froyo,AudioFlinger 只有 3 個源文件:AudioFlinger.cpp、AudioMixer.cpp、AudioResampler.cpp。

現在文件多了許多,代碼量就不用說了。但是接口及其基本流程一直沒有改變的,只是更加模塊化了,Google 把多個子類抽取出來獨立成文件,比如 Threads.cpp、Tracks.cpp、Effects.cpp,而 AudioFlinger.cpp 只包含對外提供的服務接口了。另外相比以前,增加更多的功能特性,如 teesink、Offload、FastMixer、FastCapture、FastThread、PatchPanel 等,這裏不對這些功能特性擴展描述,有興趣的可以自行分析。

  • AudioResampler.cpp:重採樣處理類,可進行採樣率轉換和聲道轉換;由錄製線程 AudioFlinger::RecordThread 直接使用
  • AudioMixer.cpp:混音處理類,包括重採樣、音量調節、聲道轉換等,其中的重採樣複用了 AudioResampler;由回放線程 AudioFlinger::MixerThread 直接使用
  • Effects.cpp:音效處理類
  • Tracks.cpp:音頻流管理類,可控制音頻流的狀態,如 start、stop、pause
  • Threads.cpp:回放線程和錄製線程類;回放線程從 FIFO 讀取回放數據並混音處理,然後寫數據到輸出流設備;錄製線程從輸入流設備讀取錄音數據並重採樣處理,然後寫數據到 FIFO
  • AudioFlinger.cpp:AudioFlinger 對外提供的服務接口

本文內容主要涉及 AudioFlinger.cpp、Threads.cpp、Tracks.cpp 這三個文件。

3.2. AudioFlinger 服務啓動

從 Android 7.0 開始,AudioFlinger 在系統啓動時由 audioserver 加載(之前版本由 mediaserver 加載),詳見 frameworks/av/media/audioserver/main_audioserver.cpp:

int main(int argc __unused, char **argv)
{
    // ......

    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm = defaultServiceManager();
    ALOGI("ServiceManager: %p", sm.get());
    AudioFlinger::instantiate();
    AudioPolicyService::instantiate();
    RadioService::instantiate();
    SoundTriggerHwService::instantiate();
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可見 audioserver 把音頻相關的服務都加載了,包括 AudioFlinger、AudioPolicyService、RadioService、SoundTriggerHwService。

main_audioserver.cpp 編譯生成的可執行文件存放在 /system/bin/audioserver,系統啓動時由 init 進程運行,詳見 frameworks/av/media/audioserver/audioserver.rc:

service audioserver /system/bin/audioserver
    class main
    user audioserver
    # media gid needed for /dev/fm (radio) and for /data/misc/media (tee)
    group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct
    ioprio rt 4
    writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

AudioFlinger 服務啓動後,其他進程可以通過 ServiceManager 來獲取其代理對象 IAudioFlinger,通過 IAudioFlinger 可以向 AudioFlinger 發出各種服務請求,從而完成自己的音頻業務。

3.3. AudioFlinger 服務接口

AudioFlinger 對外提供的主要的服務接口如下:

Interface Description
sampleRate 獲取硬件設備的採樣率
format 獲取硬件設備的音頻格式
frameCount 獲取硬件設備的週期幀數
latency 獲取硬件設備的傳輸延遲
setMasterVolume 調節主輸出設備的音量
setMasterMute 靜音主輸出設備
setStreamVolume 調節指定類型的音頻流的音量,這種調節不影響其他類型的音頻流的音量
setStreamMute 靜音指定類型的音頻流
setVoiceVolume 調節通話音量
setMicMute 靜音麥克風輸入
setMode 切換音頻模式:音頻模式有 4 種,分別是 Normal、Ringtone、Call、Communicatoin
setParameters 設置音頻參數:往下調用 HAL 層相應接口,常用於切換音頻通道
getParameters 獲取音頻參數:往下調用 HAL 層相應接口
openOutput 打開輸出流:打開輸出流設備,並創建 PlaybackThread 對象
closeOutput 關閉輸出流:移除並銷燬 PlaybackThread 上面掛着的所有的 Track,退出 PlaybackThread,關閉輸出流設備
openInput 打開輸入流:打開輸入流設備,並創建 RecordThread 對象
closeInput 關閉輸入流:退出 RecordThread,關閉輸入流設備
createTrack 新建輸出流管理對象: 找到對應的 PlaybackThread,創建輸出流管理對象 Track,然後創建並返回該 Track 的代理對象 TrackHandle
openRecord 新建輸入流管理對象:找到 RecordThread,創建輸入流管理對象 RecordTrack,然後創建並返回該 RecordTrack 的代理對象 RecordHandle

可以歸納出 AudioFlinger 響應的服務請求主要有:

  • 獲取硬件設備的配置信息
  • 音量調節
  • 靜音操作
  • 音頻模式切換
  • 音頻參數設置
  • 輸入輸出流設備管理
  • 音頻流管理

就本文範圍而言,主要涉及 openOutput() 和 createTrack() 這兩個接口,後面也會詳細分析這兩個接口的流程。

3.4. AudioFlinger 回放錄製線程

AndioFlinger 作爲 Android 的音頻系統引擎,重任之一是負責輸入輸出流設備的管理及音頻流數據的處理傳輸,這是由回放線程(PlaybackThread 及其派生的子類)和錄製線程(RecordThread)進行的,我們簡單看看回放線程和錄製線程類關係:

Threads

  • ThreadBase:PlaybackThread 和 RecordThread 的基類
  • RecordThread:錄製線程類,由 ThreadBase 派生
  • PlaybackThread:回放線程基類,同由 ThreadBase 派生
  • MixerThread:混音回放線程類,由 PlaybackThread 派生,負責處理標識爲 AUDIO_OUTPUT_FLAG_PRIMARY、AUDIO_OUTPUT_FLAG_FAST、AUDIO_OUTPUT_FLAG_DEEP_BUFFER 的音頻流,MixerThread 可以把多個音軌的數據混音後再輸出
  • DirectOutputThread:直輸回放線程類,由 PlaybackThread 派生,負責處理標識爲 AUDIO_OUTPUT_FLAG_DIRECT 的音頻流,這種音頻流數據不需要軟件混音,直接輸出到音頻設備即可
  • DuplicatingThread:複製回放線程類,由 MixerThread 派生,負責複製音頻流數據到其他輸出設備,使用場景如主聲卡設備、藍牙耳機設備、USB 聲卡設備同時輸出
  • OffloadThread:硬解回放線程類,由 DirectOutputThread 派生,負責處理標識爲 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音頻流,這種音頻流未經軟件解碼的(一般是 MP3、AAC 等格式的數據),需要輸出到硬件解碼器,由硬件解碼器解碼成 PCM 數據

PlaybackThread 中有個極爲重要的函數 threadLoop(),當 PlaybackThread 被強引用時,threadLoop() 會真正運行起來進入循環主體,處理音頻流數據相關事務,threadLoop() 大致流程如下(以 MixerThread 爲例):

bool AudioFlinger::PlaybackThread::threadLoop()
{
    // ......

    while (!exitPending())
    {
        // ......

        { // scope for mLock

            Mutex::Autolock _l(mLock);

            processConfigEvents_l();

            // ......

            if ((!mActiveTracks.size() && systemTime() > mStandbyTimeNs) ||
                                   isSuspended()) {
                // put audio hardware into standby after short delay
                if (shouldStandby_l()) {

                    threadLoop_standby();

                    mStandby = true;
                }

                // ......
            }
            // mMixerStatusIgnoringFastTracks is also updated internally
            mMixerStatus = prepareTracks_l(&tracksToRemove);

            // ......
        } // mLock scope ends

        // ......

        if (mBytesRemaining == 0) {
            mCurrentWriteLength = 0;
            if (mMixerStatus == MIXER_TRACKS_READY) {
                // threadLoop_mix() sets mCurrentWriteLength
                threadLoop_mix();
            }
            // ......
        }

        // ......

        if (!waitingAsyncCallback()) {
            // mSleepTimeUs == 0 means we must write to audio hardware
            if (mSleepTimeUs == 0) {
                // ......
                if (mBytesRemaining) {
                    // FIXME rewrite to reduce number of system calls
                    ret = threadLoop_write();
                    lastWriteFinished = systemTime();
                    delta = lastWriteFinished - mLastWriteTime;
                    if (ret < 0) {
                        mBytesRemaining = 0;
                    } else {
                        mBytesWritten += ret;
                        mBytesRemaining -= ret;
                        mFramesWritten += ret / mFrameSize;
                    }
                }
                // ......
            }
            // ......
        }

        // Finally let go of removed track(s), without the lock held
        // since we can't guarantee the destructors won't acquire that
        // same lock.  This will also mutate and push a new fast mixer state.
        threadLoop_removeTracks(tracksToRemove);
        tracksToRemove.clear();

        // ......
    }

    threadLoop_exit();

    if (!mStandby) {
        threadLoop_standby();
        mStandby = true;
    }

    // ......
    return false;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  1. threadLoop() 循環的條件是 exitPending() 返回 false,如果想要 PlaybackThread 結束循環,則可以調用 requestExit() 來請求退出;
  2. processConfigEvents_l() :處理配置事件;當有配置改變的事件發生時,需要調用 sendConfigEvent_l() 來通知 PlaybackThread,這樣 PlaybackThread 才能及時處理配置事件;常見的配置事件是切換音頻通路;
  3. 檢查此時此刻是否符合 standby 條件,比如當前並沒有 ACTIVE 狀態的 Track(mActiveTracks.size() = 0),那麼調用 threadLoop_standby() 關閉音頻硬件設備以節省能耗;
  4. prepareTracks_l(): 準備音頻流和混音器,該函數非常複雜,這裏不詳細分析了,僅列一下流程要點:
    • 遍歷 mActiveTracks,逐個處理 mActiveTracks 上的 Track,檢查該 Track 是否爲 ACTIVE 狀態;
    • 如果 Track 設置是 ACTIVE 狀態,則再檢查該 Track 的數據是否準備就緒了;
    • 根據音頻流的音量值、格式、聲道數、音軌的採樣率、硬件設備的採樣率,配置好混音器參數;
    • 如果 Track 的狀態是 PAUSED 或 STOPPED,則把該 Track 添加到 tracksToRemove 向量中;
  5. threadLoop_mix():讀取所有置了 ACTIVE 狀態的音頻流數據,混音器開始處理這些數據;
  6. threadLoop_write(): 把混音器處理後的數據寫到輸出流設備;
  7. threadLoop_removeTracks(): 把 tracksToRemove 上的所有 Track 從 mActiveTracks 中移除出來;這樣下一次循環時就不會處理這些 Track 了。

這裏說說 PlaybackThread 與輸出流設備的關係:PlaybackThread 實例與輸出流設備是一一對應的,比方說 OffloadThread 只會將音頻數據輸出到 compress_offload 設備中,MixerThread(with FastMixer) 只會將音頻數據輸出到 low_latency 設備中。

從 Audio HAL 中,我們通常看到如下 4 種輸出流設備,分別對應着不同的播放場景:

  • primary_out:主輸出流設備,用於鈴聲類聲音輸出,對應着標識爲 AUDIO_OUTPUT_FLAG_PRIMARY 的音頻流和一個 MixerThread 回放線程實例
  • low_latency:低延遲輸出流設備,用於按鍵音、遊戲背景音等對時延要求高的聲音輸出,對應着標識爲 AUDIO_OUTPUT_FLAG_FAST 的音頻流和一個 MixerThread 回放線程實例
  • deep_buffer:音樂音軌輸出流設備,用於音樂等對時延要求不高的聲音輸出,對應着標識爲 AUDIO_OUTPUT_FLAG_DEEP_BUFFER 的音頻流和一個 MixerThread 回放線程實例
  • compress_offload:硬解輸出流設備,用於需要硬件解碼的數據輸出,對應着標識爲 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音頻流和一個 OffloadThread 回放線程實例

其中 primary_out 設備是必須聲明支持的,而且系統啓動時就已經打開 primary_out 設備並創建好對應的 MixerThread 實例。其他類型的輸出流設備並非必須聲明支持的,主要是看硬件上有無這個能力。

可能有人產生這樣的疑問:既然 primary_out 設備一直保持打開,那麼能耗豈不是很大?這裏闡釋一個概念:輸出流設備屬於邏輯設備,並不是硬件設備。所以即使輸出流設備一直保持打開,只要硬件設備不工作,那麼就不會影響能耗。那麼硬件設備什麼時候纔會打開呢?答案是 PlaybackThread 將音頻數據寫入到輸出流設備時。

下圖簡單描述 AudioTrack、PlaybackThread、輸出流設備三者的對應關係:

PlaybackThread&StreamDevice

我們可以這麼說:輸出流設備決定了它對應的 PlaybackThread 是什麼類型。怎麼理解呢?意思是說:只有支持了該類型的輸出流設備,那麼該類型的 PlaybackThread 纔有可能被創建。舉個例子:只有硬件上具備硬件解碼器,系統才建立 compress_offload 設備,然後播放 mp3 格式的音樂文件時,纔會創建 OffloadThread 把數據輸出到 compress_offload 設備上;反之,如果硬件上並不具備硬件解碼器,系統則不應該建立 compress_offload 設備,那麼播放 mp3 格式的音樂文件時,通過 MixerThread 把數據輸出到其他輸出流設備上。

那麼有無可能出現這種情況:底層並不支持 compress_offload 設備,但偏偏有個標識爲 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音頻流送到 AudioFlinger 了呢?這是不可能的。系統啓動時,會檢查並保存輸入輸出流設備的支持信息;播放器在播放 mp3 文件時,首先看 compress_offload 設備是否支持了,如果支持,那麼不進行軟件解碼,直接把數據標識爲 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD;如果不支持,那麼先進行軟件解碼,然後把解碼好的數據標識爲 AUDIO_OUTPUT_FLAG_DEEP_BUFFER,前提是 deep_buffer 設備是支持了的;如果 deep_buffer 設備也不支持,那麼把數據標識爲 AUDIO_OUTPUT_FLAG_PRIMARY。

系統啓動時,就已經打開 primary_out、low_latency、deep_buffer 這三種輸出流設備,並創建對應的 MixerThread 了;而此時 DirectOutputThread 與 OffloadThread 不會被創建,直到標識爲 AUDIO_OUTPUT_FLAG_DIRECT/AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音頻流需要輸出時,纔開始創建 DirectOutputThread/OffloadThread 和打開 direct_out/compress_offload 設備。這一點請參考如下代碼,註釋非常清晰:

AudioPolicyManager::AudioPolicyManager(AudioPolicyClientInterface *clientInterface)
    // ......
{
    // ......

    // mAvailableOutputDevices and mAvailableInputDevices now contain all attached devices
    // open all output streams needed to access attached devices
    // ......
    for (size_t i = 0; i < mHwModules.size(); i++) {
        // ......
        // open all output streams needed to access attached devices
        // except for direct output streams that are only opened when they are actually
        // required by an app.
        // This also validates mAvailableOutputDevices list
        for (size_t j = 0; j < mHwModules[i]->mOutputProfiles.size(); j++)
        {
            // ......
            if ((outProfile->getFlags() & AUDIO_OUTPUT_FLAG_DIRECT) != 0) {
                continue;
            }
            // ......
            audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
            status_t status = mpClientInterface->openOutput(outProfile->getModuleHandle(),
                                                            &output,
                                                            &config,
                                                            &outputDesc->mDevice,
                                                            address,
                                                            &outputDesc->mLatency,
                                                            outputDesc->mFlags);

            // ......
        }
        // open input streams needed to access attached devices to validate
        // mAvailableInputDevices list
        for (size_t j = 0; j < mHwModules[i]->mInputProfiles.size(); j++)
        {
            // ......
            status_t status = mpClientInterface->openInput(inProfile->getModuleHandle(),
                                                           &input,
                                                           &config,
                                                           &inputDesc->mDevice,
                                                           address,
                                                           AUDIO_SOURCE_MIC,
                                                           AUDIO_INPUT_FLAG_NONE);

            // ......
        }
    }
    // ......

    updateDevicesAndOutputs();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

其中 mpClientInterface->openOutput() 最終會調用到 AudioFlinger::openOutput():打開輸出流設備,並創建 PlaybackThread 對象:

status_t AudioFlinger::openOutput(audio_module_handle_t module,
                                  audio_io_handle_t *output,
                                  audio_config_t *config,
                                  audio_devices_t *devices,
                                  const String8& address,
                                  uint32_t *latencyMs,
                                  audio_output_flags_t flags)
{
    // ......

    Mutex::Autolock _l(mLock);

    sp<PlaybackThread> thread = openOutput_l(module, output, config, *devices, address, flags);
    // ......
}

sp<AudioFlinger::PlaybackThread> AudioFlinger::openOutput_l(audio_module_handle_t module,
                                                            audio_io_handle_t *output,
                                                            audio_config_t *config,
                                                            audio_devices_t devices,
                                                            const String8& address,
                                                            audio_output_flags_t flags)
{
    // ......
    // 分配全局唯一的 audio_io_handle_t,可以理解它是回放線程的索引號
    if (*output == AUDIO_IO_HANDLE_NONE) {
        *output = nextUniqueId(); 
    }

    mHardwareStatus = AUDIO_HW_OUTPUT_OPEN;

    //......

    // 打開音頻輸出流設備,HAL 層根據 flags 選擇打開相關類型的輸出流設備
    AudioStreamOut *outputStream = NULL;
    status_t status = outHwDev->openOutputStream(
            &outputStream,
            *output,
            devices,
            flags,
            config,
            address.string());

    mHardwareStatus = AUDIO_HW_IDLE;

    if (status == NO_ERROR) {

        PlaybackThread *thread;
        if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
            // AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 音頻流,創建 OffloadThread 實例
            thread = new OffloadThread(this, outputStream, *output, devices, mSystemReady);
            ALOGV("openOutput_l() created offload output: ID %d thread %p", *output, thread);
        } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)
                || !isValidPcmSinkFormat(config->format)
                || !isValidPcmSinkChannelMask(config->channel_mask)) {
            // AUDIO_OUTPUT_FLAG_DIRECT 音頻流,創建 DirectOutputThread 實例
            thread = new DirectOutputThread(this, outputStream, *output, devices, mSystemReady);
            ALOGV("openOutput_l() created direct output: ID %d thread %p", *output, thread);
        } else {
            // 其他標識的音頻流,創建 MixerThread 實例
            thread = new MixerThread(this, outputStream, *output, devices, mSystemReady);
            ALOGV("openOutput_l() created mixer output: ID %d thread %p", *output, thread);
        }
        // 把 audio_io_handle_t 和 PlaybackThread 添加到鍵值對向量 mPlaybackThreads 中
        // 鍵值對向量 mPlaybackThreads 中,由於 audio_io_handle_t 和 PlaybackThread 是一
        // 一對應的關係,所以拿到一個 audio_io_handle_t,就能找到它對應的 PlaybackThread
        // 所以可以理解 audio_io_handle_t 爲 PlaybackThread 的索引號
        mPlaybackThreads.add(*output, thread);
        return thread;
    }

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

3.5. AudioFlinger OffloadThread

系統啓動時,就已經打開 primary_out、low_latency、deep_buffer 這三種輸出流設備,並創建對應的 MixerThread 了;而此時 DirectOutputThread 與 OffloadThread 不會被創建,直到標識爲 AUDIO_OUTPUT_FLAG_DIRECT/AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音頻流需要輸出時,纔開始創建 DirectOutputThread/OffloadThread 和打開 direct_out/compress_offload 設備。

這裏不知大家有無疑問:爲什麼 DirectOutputThread 與 OffloadThread 會被單獨對待?DirectOuputThread 使用率較低,尚可以理解,但 OffloadThread 的使用率還是很高的,爲什麼不讓 OffloadThread/compress_offload 設備也進入待命狀態呢?

要回答這個問題:我們首先得明白 compress_offload 設備是什麼東東,與其他輸出流設備有什麼不同。先看個圖:

compress_offload

紅色的是 Offload 音頻流,它與其他音頻流有什麼本質的不同?Offload 音頻流是未經 NuPlayerDecoder 進行解碼的(NuPlayerDecoder 設置了 Passthrough 模式),所以必須把這些音頻流數據送到 DSP,讓 DSP 對其解碼,解碼後的 PCM 數據再送到 Codec 輸出。

compress_offload 設備,說白了,就是驅動 DSP 解碼數據、Codec 輸出聲音。 而 DSP 要解碼數據,首先得知道數據的編碼信息,如編碼器 codec_id、採樣率 sample_rate、聲道數 channel_mask、比特率 bit_rate 等信息,由於 DSP 並沒有實現 DataSource/Parser 部件,不能自己解析數據的編碼信息,所以得有“人”告訴它,這個“人”無疑是 compress_offload 設備。

AudioTrack 構造函數有個 offloadInfo 的參數,參數原型定義如下:

/* Additional information about compressed streams offloaded to
 * hardware playback
 * The version and size fields must be initialized by the caller by using
 * one of the constants defined here.
 */
typedef struct {
    uint16_t version;                   // version of the info structure
    uint16_t size;                      // total size of the structure including version and size
    uint32_t sample_rate;               // sample rate in Hz
    audio_channel_mask_t channel_mask;  // channel mask
    audio_format_t format;              // audio format
    audio_stream_type_t stream_type;    // stream type
    uint32_t bit_rate;                  // bit rate in bits per second
    int64_t duration_us;                // duration in microseconds, -1 if unknown
    bool has_video;                     // true if stream is tied to a video stream
    bool is_streaming;                  // true if streaming, false if local playback
} audio_offload_info_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

NuPlayer DataSource/Parser 解析 mp3、flac 等文件得到數據編碼信息,並在構造 AudioTrack 實例時作爲參數傳入,AudioFlinger 將基於這些編碼信息打開 compress_offload 設備。

到這裏,大家明白了嗎?每個 mp3/flac 文件的編碼信息可能是不一樣的,比如 a.mp3 文件的編碼信息是 mp3&44.1KHZ&16bit… ,而 b.flac 文件的編碼信息是 flac&48KHz&24bit…; 播放 a.mp3 時,AudioFlinger 打開一個配置爲 mp3&44.1KHz&16bit… 的 compress_offload 設備,接着播放 b.flac,就需要關閉之前的 compress_offload 設備,重新打開一個配置爲 flac&48KHz&24bit… 的 compress_offload 設備。所以系統不會提前打開 compress_offload 設備,只有等到播放 mp3、flac 時取到明確的數據編碼信息,才基於這些編碼信息打開 compress_offload 設備。

編碼信息包含很多條目,切換音源時,是否編碼信息有一點點不一樣,都需要重新打開 compress_offload 設備呢?不能運行時更新信息到 DSP 嗎?其實 stagefright 和 compress_offload 是支持運行期更新某些信息的,也就是無縫切換,至於是哪些信息,依賴於 DSP 算法實現;有興趣深入的可以參考 sendMetaDataToHal() 和 compress_set_gapless_metadata() 。

最後附上 NuPlayer Offload Playback 初始化流程,摘自《Linux Audio Compress Offload Playback and PCM Offload Playback》:

  1. NuPlayer gets the file format metadata based on the type created by the source parser and decoder. For compress offload, DecoderPassThrough is created, causing the software decoder to enter Passthrough mode and not perform decoding.
  2. NuPlayer calls CanOffloadStream, which in turn calls the APM API isOffloadSupported to
    decide if a stream can be offloaded.
  3. NuPlayer creates a new NuPlayerRenderer, which then creates a new AudioTrack. Based on
    the information NuPlayer has received from the APM, it sets the flag FLAG_OFFLOAD_AUDIO, which passes to NuPlayerRenderer.
  4. AudioTrack then calls getOutput(), which gets the profile information from the APM. The
    APM then calls openOutput(), which goes to AudioFlinger. Based on the AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD flag previously set by AudioPlayer, AudioFlinger creates the offload thread. AudioFlinger also calls open_output_stream() which goes to the HAL. The HAL then creates the offload callback thread used to notify the write and drain completion events.
  5. AudioTrack creates the track to be used for this stream.
  6. NuPlayerRenderer issues a start command to AudioSink, which then calls start_output_stream(). The HAL sends the appropriate mixer control to the back-end (BE) platform driver to configure the output device.
  7. The HAL opens the compress driver and sends the codec and stream parameters to the
    compress driver though the IOCTL SNDRV_COMPRESS_SET_PARAMS. The compress driver opens an ASM session accordingly and requests the ASM driver to allocate the buffers to be used for this session.

3.6. AudioFlinger 音頻流管理

從 AudioTrack、PlaybackThread、輸出流設備三者的關係圖中,我們看到 AudioTrack 把音頻流數據送入到對應的 PlaybackThread 中,那麼應用進程想控制這些音頻流的話,比如開始播放 start()、停止播放 stop()、暫停播放 pause(),怎麼辦呢?注意應用進程與 AudioFlinger 並不在一個進程上。這就需要 AudioFlinger 提供音頻流管理功能,並提供一套通訊接口可以讓應用進程跨進程控制 AudioFlinger 中的音頻流狀態(通訊接口參考下一章的描述,暫且不表)。

AudioFlinger 音頻流管理由 AudioFlinger::PlaybackThread::Track 實現,Track 與 AudioTrack 是一對一的關係,一個 AudioTrack 創建後,那麼 AudioFlinger 會創建一個 Track 與之對應;PlaybackThread 與 AudioTrack/Track 是一對多的關係,一個 PlaybackThread 可以掛着多個 Track。

具體來說:AudioTrack 創建後,AudioPolicyManager 根據 AudioTrack 的輸出標識和流類型,找到對應的輸出流設備和 PlaybackThread(如果沒有找到的話,則系統會打開對應的輸出流設備並新建一個 PlaybackThread),然後創建一個 Track 並掛到這個 PlaybackThread 下面。

PlaybackThread 有兩個私有成員向量與此強相關:

  • mTracks:該 PlaybackThread 創建的所有 Track 均添加保存到這個向量中
  • mActiveTracks:只有需要播放(設置了 ACTIVE 狀態)的 Track 會添加到這個向量中;PlaybackThread 會從該向量上找到所有設置了 ACTIVE 狀態的 Track,把這些 Track 數據混音後寫到輸出流設備

音頻流控制最常用的三個接口:

  • AudioFlinger::PlaybackThread::Track::start:開始播放:把該 Track 置 ACTIVE 狀態,然後添加到 mActiveTracks 向量中,最後調用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情況有變
  • AudioFlinger::PlaybackThread::Track::stop:停止播放:把該 Track 置 STOPPED 狀態,最後調用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情況有變
  • AudioFlinger::PlaybackThread::Track::pause:暫停播放:把該 Track 置 PAUSING 狀態,最後調用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情況有變

AudioFlinger::PlaybackThread::threadLoop() 得悉情況有變後,調用 prepareTracks_l() 重新準備音頻流和混音器:ACTIVE 狀態的 Track 會添加到 mActiveTracks,此外的 Track 會從 mActiveTracks 上移除出來,然後重新準備 AudioMixer。

可見這三個音頻流控制接口是非常簡單的,主要是設置一下 Track 的狀態,然後發個事件通知 PlaybackThread 就行,複雜的處理都在 AudioFlinger::PlaybackThread::threadLoop() 中了。

3.7. AudioFlinger 混音器處理

// TODO: …

4. AudioTrack 實例創建

現在我們開始分析 AudioTrack 的創建過程,特別留意 AudioTrack 與 AudioFlinger 如何建立聯繫、用於 AudioTrack 與 AudioFlinger 交換數據的匿名共享內存如何分配。

4.1. AudioTrack & AudioFlinger 相關類

首先看一下 AudioTrack & AudioFlinger 的類圖,理一下 AudioFlinger 的主要類及其關係、AudioTrack 與 AudioFlinger 之間的聯繫,後面將以該圖爲脈絡展開分析。

AudioTrack&AudioFlinger

  • AudioFlinger::PlaybackThread:回放線程基類,不同輸出標識的音頻流對應不同類型的 PlaybackThread 實例(分爲四種:MixerThread、DirectOutputThread、DuplicatingThread、OffloadThread),具體見 3.4. AudioFlinger 回放錄製線程 小節,所有的 PlaybackThread 實例都會添加到 AudioFlinger.mPlaybackThreads 向量中;這個向量的定義: DefaultKeyedVector< audio_io_handle_t, sp<PlaybackThread> > mPlaybackThreads;,可見 audio_io_handle_t 是與 PlaybackThread 是一一對應的,由已知的 audio_io_handle_t 就能找到對應的 PlaybackThread;audio_io_handle_t 在創建 PlaybackThread 時由系統分配,這個值是全局唯一的
  • AudioFlinger::PlaybackThread::Track:音頻流管理類,創建一塊匿名共享內存用於 AudioTrack 與 AudioFlinger 之間的數據交換(方便起見,這塊匿名共享內存,以後均簡單稱爲 FIFO),同時實現 start()、stop()、pause() 等音頻流常用控制手段;注意,多個 Track 對象可能都註冊到同一個 PlaybackThread 中(尤其對於 MixerThread 而言,一個 MixerThread 往往掛着多個 Track 對象),這多個 Track 對象都會添加到 PlaybackThread.mTracks 向量中統一管理
  • AudioFlinger::TrackHandle:Track 對象只負責音頻流管理業務,對外並沒有提供跨進程的 Binder 調用接口,而應用進程又需要對音頻流進行控制,所以需要一個對象來代理 Track 的跨進程通訊,這個角色就是 TrackHandle,AudioTrack 通過它與 Track 交互
  • AudioTrack:Android 音頻系統對外提供的一個 API 類,負責音頻流數據輸出;每個音頻流對應着一個 AudioTrack 實例,不同輸出標識的 AudioTrack 會匹配到不同的 AudioFlinger::PlaybackThread;AudioTrack 與 AudioFlinger::PlaybackThread 之間通過 FIFO 來交換音頻數據,AudioTrack 是 FIFO 生產者,AudioFlinger::PlaybackThread 是 FIFO 消費者
  • AudioTrack::AudioTrackThread:數據傳輸模式爲 TRANSFER_CALLBACK 時,需要創建該線程,它通過調用 audioCallback 回調函數主動從用戶進程處索取數據並填充到 FIFO 上;數據傳輸模式爲 TRANSFER_SYNC 時,則不需要創建這個線程,因爲用戶進程會持續調用 AudioTrack.write() 填充數據到 FIFO;數據傳輸模式爲 TRANSFER_SHARED 時,也不需要創建這個線程,因爲用戶進程會創建一塊匿名共享內存,並把要播放的音頻數據一次性拷貝到這塊匿名共享內存上了
  • IAudioTrack:IAudioTrack 是鏈結 AudioTrack 與 AudioFlinger 的橋樑;它在 AudioTrack 端的對象是 BpAudioTrack,在 AudioFlinger 端的對象是 BnAudioTrack,從圖中不難看出,AudioFlinger::TrackHandle 繼承自 BnAudioTrack,而 AudioFlinger::TrackHandle 恰恰是AudioFlinger::PlaybackThread::Track 的代理對象,所以 AudioTrack 得到 IAudioTrack 實例後,就可以調用 IAudioTrack 的接口與 AudioFlinger::PlaybackThread::Track 交互

audio_io_handle_t

這裏再詳細說明一下 audio_io_handle_t,它是 AudioTrack/AudioRecord/AudioSystem、AudioFlinger、AudioPolicyManager 之間一個重要的鏈結點。3.4. AudioFlinger 回放錄製線程 小節在 AudioFlinger::openOutput_l() 註釋中大致說明了它的來歷及其作用,現在回顧下:當打開輸出流設備及創建 PlaybackThread 時,系統會分配一個全局唯一的值作爲 audio_io_handle_t,並把 audio_io_handle_t 和 PlaybackThread 添加到鍵值對向量 mPlaybackThreads 中,由於 audio_io_handle_t 和 PlaybackThread 是一一對應的關係,因此拿到一個 audio_io_handle_t,就能遍歷鍵值對向量 mPlaybackThreads 找到它對應的 PlaybackThread,可以簡單理解 audio_io_handle_t 爲 PlaybackThread 的索引號或線程 id。由於 audio_io_handle_t 具有 PlaybackThread 索引特性,所以應用進程想獲取 PlaybackThread 某些信息的話,只需要傳入對應的 audio_io_handle_t 即可。例如 AudioFlinger::format(audio_io_handle_t output),這是 AudioFlinger 的一個服務接口,用戶進程可以通過該接口獲取某個 PlaybackThread 配置的音頻格式:

audio_format_t AudioFlinger::format(audio_io_handle_t output) const
{
    Mutex::Autolock _l(mLock);
    // checkPlaybackThread_l() 根據傳入的 audio_io_handle_t,從鍵值對向量
    // mPlaybackThreads 中找到它對應的 PlaybackThread
    PlaybackThread *thread = checkPlaybackThread_l(output);
    if (thread == NULL) {
        ALOGW("format() unknown thread %d", output);
        return AUDIO_FORMAT_INVALID;
    }
    return thread->format();
}

AudioFlinger::PlaybackThread *AudioFlinger::checkPlaybackThread_l(audio_io_handle_t output) const
{
    return mPlaybackThreads.valueFor(output).get();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

4.2. AudioTrack 構造過程

當我們構造一個 AudioTrack 實例時(以 MODE_STREAM/TRANSFER_SYNC 模式爲例,這也是最常用的模式了,此時 sharedBuffer 爲空),系統都發生了什麼事?闡述下大致流程:

  1. 如果 cbf(audioCallback 回調函數)非空,那麼創建 AudioTrackThread 線程處理 audioCallback 回調函數(MODE_STREAM 模式時,cbf 爲空);
  2. 根據 streamType(流類型)、flags(輸出標識)等參數調用 AudioSystem::getOutputForAttr();經過一系列的調用,進入 AudioPolicyManager::getOutputForDevice():
    • 如果輸出標識置了 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 或 AUDIO_OUTPUT_FLAG_DIRECT,那麼最終調用 AudioFlinger::openOutput() 打開輸出標識對應的輸出流設備並創建相應的 PlaybackThread,保存該 PlaybackThread 對應的 audio_io_handle_t 給 AudioTrack;
    • 如果輸出標識是其他類型,那麼根據策略選擇一個輸出流設備和 PlaybackThread,並保存該 PlaybackThread 對應的 audio_io_handle_t 給 AudioTrack;別忘了在 3.4. AudioFlinger 回放錄製線程 小節中提到:系統啓動時,就已經打開 primary_out、low_latency、deep_buffer 這三種輸出流設備,並創建對應的 PlaybackThread 了;
  3. 通過 Binder 機制調用 AudioFlinger::createTrack()(注意 step2 中 AudioTrack 已經拿到一個 audio_io_handle_t 了,此時把這個 audio_io_handle_t 傳入給 createTrack()):
    • 根據傳入的 audio_io_handle_t 找到它對應的 PlaybackThread;
    • PlaybackThread 新建一個音頻流管理對象 Track;Track 構造時會分配一塊匿名共享內存用於 AudioFlinger 與 AudioTrack 的數據交換緩衝區(FIFO)及其控制塊(audio_track_cblk_t),並創建一個 AudioTrackServerProxy 對象(PlaybackThread 將使用它從 FIFO 上取得可讀數據的位置);
    • 最後新建一個 Track 的通訊代理 TrackHandle,並以 IAudioTrack 作爲返回值給 AudioTrack(TrackHandle、BnAudioTrack、BpAudioTrack、IAudioTrack 的關係見上一個小節);
  4. 通過 IAudioTrack 接口,取得 AudioFlinger 中的 FIFO 控制塊(audio_track_cblk_t),由此再計算得到 FIFO 的首地址;
  5. 創建一個 AudioTrackClientProxy 對象(AudioTrack 將使用它從 FIFO 上取得可用空間的位置);

AudioTrack 由此建立了和 AudioFlinger 的全部聯繫工作:

  • 通過 IAudioTrack 接口可以控制該音軌的狀態,例如 start、stop、pause
  • 持續寫入數據到 FIFO 上,實現音頻連續播放
  • 通過 audio_io_handle_t,可以找到它對應的 PlaybackThread,從而查詢該 PlaybackThread 的相關信息,如所設置的採樣率、格式等等

構造 1 個 AudioTrack 實例時,AudioFlinger 會有 1 個 PlaybackThread 實例、1 個 Track 實例、1 個 TrackHandle 實例、1 個 AudioTrackServerProxy 實例、1 塊 FIFO 與之對應。

當同時構造 1 個 AudioTrack with AUDIO_OUTPUT_FLAG_PRIMARY、1 個 AudioTrack with AUDIO_OUTPUT_FLAG_FAST、3 個 AudioTrack with AUDIO_OUTPUT_FLAG_DEEP_BUFFER、1 個 AudioTrack with AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD、1 個 AudioTrack with AUDIO_OUTPUT_FLAG_DIRECT 時(事實上,Android 音頻策略不允許出現這種情形的),AudioFlinger 擁有的 PlaybackThread、Track、TrackHandle 實例如下圖所示:

AudioTrack&PlaybackThread

最後附上相關代碼的流程分析,我本意是不多貼代碼的,但不上代碼總覺得缺點什麼,這裏我儘量把代碼精簡,提取主幹,忽略細節。

AudioTrack::AudioTrack(
        audio_stream_type_t streamType,    // 音頻流類型:如 Music、Voice-Call、DTMF、Alarm 等等
        uint32_t sampleRate,               // 採樣率:如 16KHz、44.1KHz、48KHz 等等
        audio_format_t format,             // 音頻格式:如 PCM、MP3、AAC 等等
        audio_channel_mask_t channelMask,  // 聲道數:如 Mono(單聲道)、Stereo(雙聲道)
        const sp<IMemory>& sharedBuffer,   // 共享內存緩衝區:數據模式是 MODE_STATIC 時使用,數據模式是 MODE_STREAM 時爲空
        audio_output_flags_t flags,        // 輸出標識位,詳見 AUDIO_OUTPUT_FLAG 描述
        callback_t cbf,                    // 回調函數
        void* user,                        // 回調函數的參數
        uint32_t notificationFrames,
        int sessionId,
        transfer_type transferType,        // 數據傳輸類型
        const audio_offload_info_t *offloadInfo,
        int uid,
        pid_t pid,
        const audio_attributes_t* pAttributes,
        bool doNotReconnect)
    : mStatus(NO_INIT),
      mIsTimed(false),
      mPreviousPriority(ANDROID_PRIORITY_NORMAL),
      mPreviousSchedulingGroup(SP_DEFAULT),
      mPausedPosition(0),
      mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE)
{
    mStatus = set(streamType, sampleRate, format, channelMask,
            0 /*frameCount*/, flags, cbf, user, notificationFrames,
            sharedBuffer, false /*threadCanCallJava*/, sessionId, transferType, offloadInfo,
            uid, pid, pAttributes, doNotReconnect);
}

status_t AudioTrack::set(
        audio_stream_type_t streamType,
        uint32_t sampleRate,
        audio_format_t format,
        audio_channel_mask_t channelMask,
        size_t frameCount,
        audio_output_flags_t flags,        
        callback_t cbf,
        void* user,
        uint32_t notificationFrames,
        const sp<IMemory>& sharedBuffer,
        bool threadCanCallJava,
        int sessionId,
        transfer_type transferType,
        const audio_offload_info_t *offloadInfo,
        int uid,
        pid_t pid,
        const audio_attributes_t* pAttributes,
        bool doNotReconnect)
{
    // 參數格式合法性檢查、音軌音量初始化

    // 如果 cbf 非空,那麼創建 AudioTrackThread 線程處理 audioCallback 回調函數
    if (cbf != NULL) {
        mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
        mAudioTrackThread->run("AudioTrack", ANDROID_PRIORITY_AUDIO, 0 /*stack*/);
        // thread begins in paused state, and will not reference us until start()
    }

    // create the IAudioTrack
    status_t status = createTrack_l();

    //......
}

status_t AudioTrack::createTrack_l()
{
    // 獲取 IAudioFlinger,通過 binder 請求 AudioFlinger 服務
    const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
    if (audioFlinger == 0) {
        ALOGE("Could not get audioflinger");
        return NO_INIT;
    }

    //......

    // AudioSystem::getOutputForAttr() 經過一系列的調用,進入 AudioPolicyManager::getOutputForDevice()
    // 如果輸出標識置了 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 或 AUDIO_OUTPUT_FLAG_DIRECT,
    // 那麼最終調用 AudioFlinger::openOutput() 打開輸出標識對應的輸出流設備並創建相關的
    // PlaybackThread,保存該 PlaybackThread 對應的 audio_io_handle_t 給 AudioTrack;
    // 如果輸出標識是其他類型,那麼根據策略選擇一個輸出流設備和 PlaybackThread,並保存該
    // PlaybackThread 對應的 audio_io_handle_t 給 AudioTrack
    audio_io_handle_t output;
    status = AudioSystem::getOutputForAttr(attr, &output,
                                           (audio_session_t)mSessionId, &streamType, mClientUid,
                                           mSampleRate, mFormat, mChannelMask,
                                           mFlags, mSelectedDeviceId, mOffloadInfo);

    //......

    // 向 AudioFlinger 發出 createTrack 請求
    sp<IAudioTrack> track = audioFlinger->createTrack(streamType,
                                                      mSampleRate,
                                                      mFormat,
                                                      mChannelMask,
                                                      &temp,
                                                      &trackFlags,
                                                      mSharedBuffer,
                                                      output,
                                                      tid,
                                                      &mSessionId,
                                                      mClientUid,
                                                      &status);
    //......

    // AudioFlinger 創建 Track 對象時會分配一個 FIFO,這裏獲取 FIFO 的控制塊
    sp<IMemory> iMem = track->getCblk();
    if (iMem == 0) {
        ALOGE("Could not get control block");
        return NO_INIT;
    }
    // 匿名共享內存首地址
    void *iMemPointer = iMem->pointer();
    if (iMemPointer == NULL) {
        ALOGE("Could not get control block pointer");
        return NO_INIT;
    }
    mAudioTrack = track; // 保存 AudioFlinger::PlaybackThread::Track 的代理對象 IAudioTrack
    mCblkMemory = iMem; // 保存匿名共享內存首地址

    // 控制塊位於 AudioFlinger 分配的匿名共享內存的首部
    audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMemPointer);
    mCblk = cblk;
    mOutput = output; // 保存返回的 audio_io_handle_t,用它可以找到對應的 PlaybackThread
    //......

    // update proxy
    if (mSharedBuffer == 0) {
        // 當 mSharedBuffer 爲空,意味着音軌數據模式爲 MODE_STREAM,那麼創建 AudioTrackClientProxy 對象
        mStaticProxy.clear();
        mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
    } else {
        // 當 mSharedBuffer 非空,意味着音軌數據模式爲 MODE_STATIC,那麼創建 StaticAudioTrackClientProxy 對象
        mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
        mProxy = mStaticProxy;
    }

    //......
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139

AudioFlinger::createTrack(),顧名思義,創建一個 Track 對象,將用於音頻流的控制:

sp<IAudioTrack> AudioFlinger::createTrack(
        audio_stream_type_t streamType,
        uint32_t sampleRate,
        audio_format_t format,
        audio_channel_mask_t channelMask,
        size_t *frameCount,
        IAudioFlinger::track_flags_t *flags,
        const sp<IMemory>& sharedBuffer,
        audio_io_handle_t output,
        pid_t tid,
        int *sessionId,
        int clientUid,
        status_t *status)
{
    sp<PlaybackThread::Track> track;
    sp<TrackHandle> trackHandle;
    sp<Client> client;
    status_t lStatus;
    int lSessionId;

    //......

    {
        Mutex::Autolock _l(mLock);
        // 根據傳入來的 audio_io_handle_t,找到對應的 PlaybackThread
        PlaybackThread *thread = checkPlaybackThread_l(output);
        if (thread == NULL) {
            ALOGE("no playback thread found for output handle %d", output);
            lStatus = BAD_VALUE;
            goto Exit;
        }

        //......

        // 在 PlaybackThread 上創建一個音頻流管理對象 Track
        track = thread->createTrack_l(client, streamType, sampleRate, format,
                channelMask, frameCount, sharedBuffer, lSessionId, flags, tid, clientUid, &lStatus);
        //......

        setAudioHwSyncForSession_l(thread, (audio_session_t)lSessionId);
    }

    //......

    // 創建 Track 的通訊代理 TrackHandle 並返回它
    trackHandle = new TrackHandle(track);

Exit:
    *status = lStatus;
    return trackHandle;
}

sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTrack_l(
        const sp<AudioFlinger::Client>& client,
        audio_stream_type_t streamType,
        uint32_t sampleRate,
        audio_format_t format,
        audio_channel_mask_t channelMask,
        size_t *pFrameCount,
        const sp<IMemory>& sharedBuffer,
        int sessionId,
        IAudioFlinger::track_flags_t *flags,
        pid_t tid,
        int uid,
        status_t *status)
{
    size_t frameCount = *pFrameCount;
    sp<Track> track;
    status_t lStatus;

    bool isTimed = (*flags & IAudioFlinger::TRACK_TIMED) != 0;

    // ......

    { // scope for mLock
        Mutex::Autolock _l(mLock);

        // ......

        if (!isTimed) {
            // 創建 Track,等會再看看 Track 構造函數幹些啥
            track = new Track(this, client, streamType, sampleRate, format,
                              channelMask, frameCount, NULL, sharedBuffer,
                              sessionId, uid, *flags, TrackBase::TYPE_DEFAULT);
        } else {
            // 創建 TimedTrack,帶時間戳的 Track?這裏不深究
            track = TimedTrack::create(this, client, streamType, sampleRate, format,
                    channelMask, frameCount, sharedBuffer, sessionId, uid);
        }

        // ......

        // 把創建的 Track 添加到 mTracks 向量中,方便 PlaybackThread 統一管理
        mTracks.add(track);

        // ......
    }

    lStatus = NO_ERROR;

Exit:
    *status = lStatus;
    return track;
}

// ----------------------------------------------------------------------------
// 如下是 TrackHandle 的相關代碼,可以看到,TrackHandle 其實就是一個殼子,是 Track 的包裝類
// 所有 TrackHandle 接口都是調向 Track 的
// Google 爲什麼要搞這麼一則?Track 是 PlaybackThread 內部使用的,不適宜對外暴露,但應用進程
// 又確實需要控制音頻流的狀態(start、stop、pause),所以就採取這麼一種方式實現

AudioFlinger::TrackHandle::TrackHandle(const sp<AudioFlinger::PlaybackThread::Track>& track)
    : BnAudioTrack(),
      mTrack(track)
{
}

AudioFlinger::TrackHandle::~TrackHandle() {
    // just stop the track on deletion, associated resources
    // will be freed from the main thread once all pending buffers have
    // been played. Unless it's not in the active track list, in which
    // case we free everything now...
    mTrack->destroy();
}

sp<IMemory> AudioFlinger::TrackHandle::getCblk() const {
    return mTrack->getCblk();
}

status_t AudioFlinger::TrackHandle::start() {
    return mTrack->start();
}

void AudioFlinger::TrackHandle::stop() {
    mTrack->stop();
}

void AudioFlinger::TrackHandle::flush() {
    mTrack->flush();
}

void AudioFlinger::TrackHandle::pause() {
    mTrack->pause();
}
// ----------------------------------------------------------------------------
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145

最後,我們看看 Track 的構造過程,主要分析數據 FIFO 及它的控制塊是如何分配的:

AudioFlinger::PlaybackThread::Track::Track(
            PlaybackThread *thread,
            const sp<Client>& client,
            audio_stream_type_t streamType,
            uint32_t sampleRate,
            audio_format_t format,
            audio_channel_mask_t channelMask,
            size_t frameCount,
            void *buffer,
            const sp<IMemory>& sharedBuffer,
            int sessionId,
            int uid,
            IAudioFlinger::track_flags_t flags,
            track_type type)
    :   TrackBase(thread, client, sampleRate, format, channelMask, frameCount,
                  (sharedBuffer != 0) ? sharedBuffer->pointer() : buffer,
                  sessionId, uid, flags, true /*isOut*/,
                  (type == TYPE_PATCH) ? ( buffer == NULL ? ALLOC_LOCAL : ALLOC_NONE) : ALLOC_CBLK,
                  type),
    mFillingUpStatus(FS_INVALID),
    // mRetryCount initialized later when needed
    mSharedBuffer(sharedBuffer),
    mStreamType(streamType),
    mName(-1),  // see note below
    mMainBuffer(thread->mixBuffer()),
    mAuxBuffer(NULL),
    mAuxEffectId(0), mHasVolumeController(false),
    mPresentationCompleteFrames(0),
    mFastIndex(-1),
    mCachedVolume(1.0),
    mIsInvalid(false),
    mAudioTrackServerProxy(NULL),
    mResumeToStopping(false),
    mFlushHwPending(false)
{
    // client == 0 implies sharedBuffer == 0
    ALOG_ASSERT(!(client == 0 && sharedBuffer != 0));

    ALOGV_IF(sharedBuffer != 0, "sharedBuffer: %p, size: %d", sharedBuffer->pointer(),
            sharedBuffer->size());

    // 檢查 FIFO 控制塊(audio_track_cblk_t)是否分配好了,上面代碼並未分配 audio_track_cblk_t
    // 因此只可能是構造 TrackBase 時分配的,等下再看看 TrackBase 的構造函數
    if (mCblk == NULL) {
        return;
    }

    if (sharedBuffer == 0) {
        // 數據傳輸模式爲 MODE_STREAM 模式,創建一個 AudioTrackServerProxy 對象
        // PlaybackThread 將持續使用它從 FIFO 上取得可讀數據的位置
        mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize, !isExternalTrack(), sampleRate);
    } else {
        // 數據傳輸模式爲 MODE_STATIC 模式,創建一個 StaticAudioTrackServerProxy 對象
        mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize);
    }
    mServerProxy = mAudioTrackServerProxy;

    // 爲 Track 分配一個名稱,AudioMixer 會根據 TrackName 找到對應的 Track
    mName = thread->getTrackName_l(channelMask, format, sessionId);
    if (mName < 0) {
        ALOGE("no more track names available");
        return;
    }
    // ......
}

AudioFlinger::ThreadBase::TrackBase::TrackBase(
            ThreadBase *thread,
            const sp<Client>& client,
            uint32_t sampleRate,
            audio_format_t format,
            audio_channel_mask_t channelMask,
            size_t frameCount,
            void *buffer,
            int sessionId,
            int clientUid,
            IAudioFlinger::track_flags_t flags,
            bool isOut,
            alloc_type alloc,
            track_type type)
    :   RefBase(),
        mThread(thread),
        mClient(client),
        mCblk(NULL),
        // mBuffer
        mState(IDLE),
        mSampleRate(sampleRate),
        mFormat(format),
        mChannelMask(channelMask),
        mChannelCount(isOut ?
                audio_channel_count_from_out_mask(channelMask) :
                audio_channel_count_from_in_mask(channelMask)),
        mFrameSize(audio_is_linear_pcm(format) ?
                mChannelCount * audio_bytes_per_sample(format) : sizeof(int8_t)),
        mFrameCount(frameCount),
        mSessionId(sessionId),
        mFlags(flags),
        mIsOut(isOut),
        mServerProxy(NULL),
        mId(android_atomic_inc(&nextTrackId)),
        mTerminated(false),
        mType(type),
        mThreadIoHandle(thread->id())
{
    // ......

    // ALOGD("Creating track with %d buffers @ %d bytes", bufferCount, bufferSize);
    size_t size = sizeof(audio_track_cblk_t);
    size_t bufferSize = (buffer == NULL ? roundup(frameCount) : frameCount) * mFrameSize;
    if (buffer == NULL && alloc == ALLOC_CBLK) {
        // 這個 size 將是分配的匿名共享內存的大小
        // 等於控制塊的大小(sizeof(audio_track_cblk_t)加上數據 FIFO的大小(bufferSize)
        // 待會看到這塊內存的結構,就明白這樣分配的意義了
        size += bufferSize;
    }

    if (client != 0) {
        // 分配一塊匿名共享內存
        mCblkMemory = client->heap()->allocate(size);
        if (mCblkMemory == 0 ||
                (mCblk = static_cast<audio_track_cblk_t *>(mCblkMemory->pointer())) == NULL) {
            ALOGE("not enough memory for AudioTrack size=%u", size);
            client->heap()->dump("AudioTrack");
            mCblkMemory.clear();
            return;
        }
    } else {
        // this syntax avoids calling the audio_track_cblk_t constructor twice
        mCblk = (audio_track_cblk_t *) new uint8_t[size];
        // assume mCblk != NULL
    }

    // construct the shared structure in-place.
    if (mCblk != NULL) {
        // 這是 C++ 的 placement new(定位創建對象)語法:new(@BUFFER) @CLASS();
        // 可以在特定內存位置上構造一個對象
        // 這裏,在匿名共享內存首地址上構造了一個 audio_track_cblk_t 對象
        // 這樣 AudioTrack 與 AudioFlinger 都能訪問這個 audio_track_cblk_t 對象了
        new(mCblk) audio_track_cblk_t();

        // 如下分配數據 FIFO,將用於 AudioTrack 與 AudioFlinger 的數據交換
        switch (alloc) {
        // ......
        case ALLOC_CBLK:
            // clear all buffers
            if (buffer == NULL) {
                // 數據傳輸模式爲 MODE_STREAM/TRANSFER_SYNC 時,數據 FIFO 的分配
                // 數據 FIFO 的首地址緊靠控制塊(audio_track_cblk_t)之後
                //   |                                                         |
                //   | -------------------> mCblkMemory <--------------------- |
                //   |                                                         |
                //   +--------------------+------------------------------------+
                //   | audio_track_cblk_t |             Buffer                 |
                //   +--------------------+------------------------------------+
                //   ^                    ^
                //   |                    |
                //   mCblk               mBuffer
                mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
                memset(mBuffer, 0, bufferSize);
            } else {
                // 數據傳輸模式爲 MODE_STATIC/TRANSFER_SHARED 時,直接指向 sharedBuffer
                // sharedBuffer 是應用進程分配的匿名共享內存,應用進程已經一次性把數據
                // 寫到 sharedBuffer 來了,AudioFlinger 可以直接從這裏讀取
                //   +--------------------+    +-----------------------------------+
                //   | audio_track_cblk_t |    |            sharedBuffer           |
                //   +--------------------+    +-----------------------------------+
                //   ^                         ^
                //   |                         |
                //   mCblk                    mBuffer
                mBuffer = buffer;
            }
            break;
        // ......
        }

        // ......
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180

5. AudioTrack 數據寫入

AudioTrack 實例構造後,應用程序接着可以寫入音頻數據了。如之前所描述:AudioTrack 與 AudioFlinger 是 生產者-消費者 的關係:

  • AudioTrack:AudioTrack 在 FIFO 中找到一塊可用空間,把用戶傳入的音頻數據寫入到這塊可用空間上,然後更新寫位置(對於 AudioFinger 來說,意味 FIFO 上有更多的可讀數據了);如果用戶傳入的數據量比可用空間要大,那麼要把用戶傳入的數據拆分多次寫入到 FIFO 中(AudioTrack 和 AudioFlinger 是不同的進程,AudioFlinger 同時也在不停地讀取數據,所以 FIFO 可用空間是在不停變化的)
  • AudioFlinger:AudioFlinger 在 FIFO 中找到一塊可讀數據塊,把可讀數據拷貝到目的緩衝區上,然後更新讀位置(對於 AudioTrack 來說,意味着 FIFO 上有更多的可用空間了);如果FIFO 上可讀數據量比預期的要小,那麼要進行多次的讀取,才能積累到預期的數據量(AudioTrack 和 AudioFlinger 是不同的進程,AudioTrack 同時也在不停地寫入數據,所以 FIFO 可讀的數據量是在不停變化的)

上面的過程中,如果 AudioTrack 總能及時生產數據,並且 AudioFlinger 總能及時消耗掉這些數據,那麼整個過程將是非常和諧的;但系統可能會發生異常,出現如下的狀態:

  • Block:AudioFlinger 長時間不讀取 FIFO 上的可讀數據,使得 AudioTrack 長時間獲取不到可用空間,無法寫入數據;這種情況的根本原因大多是底層驅動發生阻塞異常,導致 AudioFlinger 無法繼續寫數據到硬件設備中,AudioFlinger 本身並沒有錯
  • Underrun:AudioTrack 寫入數據的速度跟不上 AudioFlinger 讀取數據的速度,使得 AudioFlinger 不能及時獲取到預期的數據量,反映到現實的後果就是聲音斷續;這種情況的根本原因大多是應用程序不能及時寫入數據或者緩衝區分配過小,AudioTrack 本身並沒有錯;AudioFlinger 針對這點做了容錯處理:當發現 underrun 時,先陷入短時間的睡眠,不急着讀取數據,讓應用程序準備更多的數據(如果某一天做應用的哥們意識到自己的錯誤原來由底層的兄弟默默埋單了,會不會感動得哭了^_^)

5.1. AudioTrack 寫數據流程

我們看一下 AudioTrack 寫數據的代碼,流程很簡單:obtainBuffer() 在 FIFO 中找到一塊可用區間,memcpy() 把用戶傳入的音頻數據拷貝到這個可用區間上,releaseBuffer() 更新寫位置。

ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking)
{
    if (mTransfer != TRANSFER_SYNC) {
        return INVALID_OPERATION;
    }

    if (isDirect()) {
        AutoMutex lock(mLock);
        int32_t flags = android_atomic_and(
                            ~(CBLK_UNDERRUN | CBLK_LOOP_CYCLE | CBLK_LOOP_FINAL | CBLK_BUFFER_END),
                            &mCblk->mFlags);
        if (flags & CBLK_INVALID) {
            return DEAD_OBJECT;
        }
    }

    if (ssize_t(userSize) < 0 || (buffer == NULL && userSize != 0)) {
        // Sanity-check: user is most-likely passing an error code, and it would
        // make the return value ambiguous (actualSize vs error).
        ALOGE("AudioTrack::write(buffer=%p, size=%zu (%zd)", buffer, userSize, userSize);
        return BAD_VALUE;
    }

    size_t written = 0;
    Buffer audioBuffer;

    while (userSize >= mFrameSize) {
        // 單幀數據量 frameSize = channelCount * bytesPerSample
        //   對於雙聲道,16位採樣的音頻數據來說,frameSize = 2 * 2 = 4(bytes)
        // 用戶傳入的數據幀數 frameCount = userSize / frameSize
        audioBuffer.frameCount = userSize / mFrameSize;

        // obtainBuffer() 從 FIFO 上得到一塊可用區間
        status_t err = obtainBuffer(&audioBuffer,
                blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
        if (err < 0) {
            if (written > 0) {
                break;
            }
            if (err == TIMED_OUT || err == -EINTR) {
                err = WOULD_BLOCK;
            }
            return ssize_t(err);
        }

        // toWrite 是 FIFO 可用區間的大小,可能比 userSize(用戶傳入數據的大小)要小
        //   因此用戶傳入的數據可能要拆分多次拷貝到 FIFO 上
        // 注意:AudioTrack 和 AudioFlinger 是不同的進程,AudioFlinger 同時也在不停地
        //   消耗數據,所以 FIFO 可用區間是在不停變化的
        size_t toWrite = audioBuffer.size;
        memcpy(audioBuffer.i8, buffer, toWrite); // 把用戶數據拷貝到 FIFO 可用區間
        buffer = ((const char *) buffer) + toWrite; // 未拷貝數據的位置
        userSize -= toWrite; // 未拷貝數據的大小
        written += toWrite; // 已拷貝數據的大小

        // releaseBuffer() 更新 FIFO 寫位置
        // 對於 AudioFinger 來說,意味 FIFO 上有更多的可讀數據
        releaseBuffer(&audioBuffer);
    }

    if (written > 0) {
        mFramesWritten += written / mFrameSize;
    }
    return written;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

5.2. AudioFlinger 讀數據流程

AudioFlinger 消費數據的流程稍微複雜一點,3.4. AudioFlinger 回放錄製線程 小節中描述了 AudioFlinger::PlaybackThread::threadLoop() 工作流程,這裏不累述了,我們把焦點放在“如何從 FIFO 讀取數據”節點上。

我們以 DirectOutputThread/OffloadThread 爲例說明(MixerThread 讀數據也是類似的過程,只不過是在 AudioMixer 中進行的,3.7. AudioFlinger 混音器處理 小節中有相關描述)。

void AudioFlinger::DirectOutputThread::threadLoop_mix()
{
    // mFrameCount 是硬件設備(PCM 設備)處理單個數據塊的幀數(週期大小)
    //   上層必須積累了足夠多(mFrameCount)的數據,才寫入到 PCM 設備
    //   所以 mFrameCount 也就是 AudioFlinger 預期的數據量
    size_t frameCount = mFrameCount;
    // mSinkBuffer 目的緩衝區,threadLoop_write() 會把 mSinkBuffer 上的數據寫到 PCM 設備
    int8_t *curBuf = (int8_t *)mSinkBuffer;
    // output audio to hardware
    // FIFO 上可讀的數據量可能要比預期的要小,因此可能需要多次讀取才能積累足夠的數據量
    // 注意:AudioTrack 和 AudioFlinger 是不同的進程,AudioTrack 同時也在不停地生產數據
    //   所以 FIFO 可讀的數據量是在不停變化的
    while (frameCount) {
        AudioBufferProvider::Buffer buffer;
        buffer.frameCount = frameCount;
        // getNextBuffer() 從 FIFO 上獲取可讀數據塊
        status_t status = mActiveTrack->getNextBuffer(&buffer);
        if (status != NO_ERROR || buffer.raw == NULL) {
            memset(curBuf, 0, frameCount * mFrameSize);
            break;
        }
        // memcpy() 把 FIFO 可讀數據拷貝到 mSinkBuffer 目的緩衝區
        memcpy(curBuf, buffer.raw, buffer.frameCount * mFrameSize);
        frameCount -= buffer.frameCount;
        curBuf += buffer.frameCount * mFrameSize;
        // releaseBuffer() 更新 FIFO 讀位置
        // 對於 AudioTrack 來說,意味着 FIFO 上有更多的可用空間
        mActiveTrack->releaseBuffer(&buffer);
    }
    mCurrentWriteLength = curBuf - (int8_t *)mSinkBuffer;
    mSleepTimeUs = 0;
    mStandbyTimeNs = systemTime() + mStandbyDelayNs;
    mActiveTrack.clear();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

5.3. 環形 FIFO 管理

在上述過程中,不知大家有無意識到:整個過程中,最難的是如何協調生產者與消費者之間的步調。上文所說的 FIFO 是環形 FIFO,AudioTrack 寫指針、AudioFlinger 讀指針都是基於 FIFO 當前的讀寫位置來計算的。

  • AudioTrack 與 AudioFlinger 不在同一個進程上,怎麼保證讀寫指針的線程安全
  • 讀寫指針越過 FIFO 後,怎麼處理
  • AudioTrack 寫數據完成後,需要同步狀態給 AudioFlinger,讓 AudioFlinger 知道當前有可讀數據了,而 AudioFlinger 讀數據完成後,也需要同步狀態給 AudioTrack,讓 AudioTrack 知道當前有可用空間了;這裏採取什麼同步機制

我們回顧下創建 AudioTrack 對象時,FIFO 及其控制塊的結構如下所示:

  • MODE_STREAM 模式下的匿名共享內存結構:
  |                                                         |
  | -------------------> mCblkMemory <--------------------- |
  |                                                         |
  +--------------------+------------------------------------+
  | audio_track_cblk_t |               FIFO                 |
  +--------------------+------------------------------------+
  ^                    ^
  |                    |
mCblk               mBuffer

mCblk = static_cast<audio_track_cblk_t *>(mCblkMemory->pointer());
new(mCblk) audio_track_cblk_t();
mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • MODE_STATIC 模式下的匿名共享內存結構:
  +--------------------+    +-----------------------------------+
  | audio_track_cblk_t |    |         FIFO (sharedBuffer)       |
  +--------------------+    +-----------------------------------+
  ^                         ^
  |                         |
mCblk                    mBuffer

mCblk = (audio_track_cblk_t *) new uint8_t[size];
new(mCblk) audio_track_cblk_t();
mBuffer = sharedBuffer->pointer()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

FIFO 管理相關的類圖:

AudioTrackShared

  • AudioTrackClientProxy:MODE_STREAM 模式下,生產者 AudioTrack 使用它在 FIFO 中找到可用空間的位置
  • AudioTrackServerProxy:MODE_STREAM 模式下,消費者 AudioFlinger::PlaybackThread 使用它在 FIFO 中找到可讀數據的位置
  • StaticAudioTrackClientProxy:MODE_STATIC 模式下,生產者 AudioTrack 使用它在 FIFO 中找到可用空間的位置
  • StaticAudioTrackServerProxy:MODE_STATIC 模式下,消費者 AudioFlinger::PlaybackThread 使用它在 FIFO 中找到可讀數據的位置
  • AudioRecordClientProxy:消費者 AudioRecord 使用它在 FIFO 中找到可讀數據的位置
  • AudioTrackServerProxy:生產者 AudioFlinger::RecordThread 使用它在 FIFO 中找到可用空間的位置

到這裏,我決定結束本文了。環形 FIFO 管理是 Android 音頻系統的精髓,一個小節並不足以描述其原理及實現細節;Android 環形 FIFO 的實現可說得上精妙絕倫,其他項目如果要用到環形 FIFO,不妨多借鑑它。因此我想另寫一篇博文詳細分析 Android 環形 FIFO 的原理及實現,初定提綱如下,以作備忘:

  1. 傳統環形 FIFO 的原理
  2. Android 環形 FIFO 的原理
  3. 讀寫指針的線程安全
  4. Futex 進程同步機制
  5. Android 環形 FIFO 的實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章