Android Audio機制



前言

這篇文章是最近自己學習android audio的學習心得,希望大牛提出寶貴意見。
本文內容基於android 5.0

目錄

一. 硬件架構

(1).編解碼器(codec) 

二. 軟件架構

(1). kernel裏面關於Audio的driver機制
(2). Audio機制服務的核心AudioflingerAudioPolicyService啓動流程
(3). AudioService在SystemServer裏面的啓動
(4). 應用程序接口MediaplayerAudioTrack和AudioRecoder。

一,硬件架構

audio硬件架構

1,上圖所示爲手機上audio的硬件架構

(1).其中PM8916裏面主要是我們的hardware codec他的作用主要是將
    模擬的音頻信號採樣轉換爲pcm格式
(2).MODEM DSP是我們的modem部分,可以看到我們採集到的PCM
    數據可以通過modem的DSP處理,沒有注意這裏沒有給我們的
    audio單獨集成DSP可以節省成本
(3).application processor是我們的主處理器目前手機上面主要
    還是arm
總結:
架構圖所示,我們的Audio數據主要通過I2s接口如圖Audio Interface 和Digital 
codec core之間傳輸,而我們的命令通過spmi接口傳輸到hardware codec,注意這裏我們只是一本圖作爲例子實際中不同的廠商可能使用不同的接口,這個主要在driver裏面與硬件相關的部分,例如有些廠商接口是通過i2c或者spi接口

二,軟件架構

audio的軟件架構

軟件架構模塊:

(1).ALSA是kernel裏面管理audio的核心,我們的audio driver
    部分一般會調用snd_soc_register_card(),
    將我們在軟件層面抽象出來的聲卡註冊進audio核心
(2).在用戶空間android同樣給我們提供了tinyalsa方便
    我們操作我們的driver
(3).audio的hal層,這部分也是和具體的廠商有關,後面我會具體講
(4).mediaservice,圖中的media是在init.rc裏面啓動的native進程
    在我們所用的android版本中這個service其實還啓動了camera
    service,這裏只講解與audio相關的AudioFlinger和
    AudioPolicyService
(5).在mediaservice啓動之後會zygote進程,zygote緊接着會啓動
    android的核心進程systemserver進程。在systemserver進程
    裏面會註冊AudioService,其實這裏AudioService雖然叫做
    service,其實他是通過調用AudioSystem間接地獲得
    AudioFlinger的代理到具體的audio,之所以service是由於
    我們一般在應用程序中又是通過獲得AudioService的代理
    操作的,可見雖然android的binder進程間通訊功能強大
    但是效率可能並沒有其他的進程間通訊機制高效。
    比如我們一般在應用程序中通過AudioManager操作得到當前
    的音量會首先通過binder機制先獲得AudioService代理,然後
    AudioService在通過AudioSystem獲得AudioFlinger的代理才
    能操作到實際的實現部分會發現這裏一次調用卻用了兩次
    binder通信,顯然沒有普通的進程間通信機制高效,優點確實功能很強大。
(6).應用程序中有AudioTrack,AudioRecoder,AudioManager,
    MediaPlayer等接口實際操作我們的audio的。

幾個模塊的單獨分析

  1. ALSA kernel部分 代碼位置:
    kernel/sound/sound_core.c(這部分暫時沒有看完)
    kernel/sound/soc/soc-core.c(我們主要分析這裏)
    kernel/sound/soc/soc-pcm.c
    kernel/sound/soc/soc-compress.c
    kernel/sound/core/pcm_lib.c
    kernel/sound/core/pcm_native.c
    1.soc-core.c裏面註冊了一個platform driver,這份code是與具體的硬件無關的主要是在內存裏面建立幾個重要的鏈表維護我們註冊的關於audio的driver
    2.soc-pcm.c,soc-compress.c,pcm_lib.c,pcm_native.c等這些
    文件裏面主要實現對於用戶具體實現audio pcm讀寫操作
    註冊框架(實際的註冊有具體的driver實現,這個裏面只是管理)。
    3.我們在這幅框架圖裏面看到的platform driver,cpu driver
    ,codec driver,machine driver裏面才audio的具體實現操作
    (不同的廠商實現不一樣的框架是一樣的)
    4.在這幅圖裏能看到音量按鍵volumekey driver,這個是我自己假設
    出來的。實際中由於volume key 功能比較小,可能會和其他
    的driver寫在一起,我們暫且這樣理解,從圖中,我們能看到當我們
    按下volume key時我們的按鍵事件會通過input子系統傳到
    InputManagerService,進而傳送到PhoneWindowManager。這個裏面
    PhoneWindowManagerService和AudioService 都在
    systemserver進程裏面,PhoneWindowManager又會通過binder得到
    AudioService的代理進而按照之前說的又往下實現真正的音量設置。
    我們發現這裏我們會發現binder不僅可以用於進程間通信,即使是同一個進程中,只只要將service註冊進servicemanager,也可以
    通過binder通信
    2.tinyalsa 代碼位置:
    1.external/tinyalsa/
    這個目錄底下主要的兩個文件分別是pcm.c,mixer.c
    主要是我們pcm音頻的播放和錄製以及混音接口
    2.external/tinycompress/
    這個目錄底下重要的一個文件compress.c
    主要是對於壓縮格式的音頻文件的播放。比如MP3,AAC。當然這個得硬件codec支持這種格式的解碼纔可以(硬解碼)我們還會提到軟解碼
    3.Audio HAL
    這一層主要是調用tinyalsa以及一些廠商相關的具體實現。與具體的廠商有關所以我們也是略過。

4.mediaservice
1,代碼在framework/av/media/mediaservice/main_mediaservice.c
在init進程中會解析init.rc 之後在on boot有一個
class_start main 在這個期間會啓動mediaserver進程是我摘抄的
init.rc中的一段
service media /system/bin/mediaserver
class main
user media
。。。
進程啓動了mediaservice進程,下面我們來看mediaservice的代碼

塊代碼

```mediaservice
int main(int argc __unused, char** argv)
{
    signal(SIGPIPE, SIG_IGN);
    char value[PROPERTY_VALUE_MAX];
    bool doLog = (property_get("ro.test_harness", value, "0") > 0) && (atoi(value) == 1);
    pid_t childPid;
    // FIXME The advantage of making the process containing media.log service the parent process of
    // the process that contains all the other real services, is that it allows us to collect more
    // detailed information such as signal numbers, stop and continue, resource usage, etc.
    // But it is also more complex.  Consider replacing this by independent processes, and using
    // binder on death notification instead.
    if (doLog && (childPid = fork()) != 0) {
        // media.log service
        //prctl(PR_SET_NAME, (unsigned long) "media.log", 0, 0, 0);
        // unfortunately ps ignores PR_SET_NAME for the main thread, so use this ugly hack
        strcpy(argv[0], "media.log");
        sp<ProcessState> proc(ProcessState::self());
        MediaLogService::instantiate();
        ProcessState::self()->startThreadPool();
        for (;;) {
            siginfo_t info;
            int ret = waitid(P_PID, childPid, &info, WEXITED | WSTOPPED | WCONTINUED);
            if (ret == EINTR) {
                continue;
            }
            if (ret < 0) {
                break;
            }
            char buffer[32];
            const char *code;
            switch (info.si_code) {
            case CLD_EXITED:
                code = "CLD_EXITED";
                break;
            case CLD_KILLED:
                code = "CLD_KILLED";
                break;
            case CLD_DUMPED:
                code = "CLD_DUMPED";
                break;
            case CLD_STOPPED:
                code = "CLD_STOPPED";
                break;
            case CLD_TRAPPED:
                code = "CLD_TRAPPED";
                break;
            case CLD_CONTINUED:
                code = "CLD_CONTINUED";
                break;
            default:
                snprintf(buffer, sizeof(buffer), "unknown (%d)", info.si_code);
                code = buffer;
                break;
            }
            struct rusage usage;
            getrusage(RUSAGE_CHILDREN, &usage);
            ALOG(LOG_ERROR, "media.log", "pid %d status %d code %s user %ld.%03lds sys %ld.%03lds",
                    info.si_pid, info.si_status, code,
                    usage.ru_utime.tv_sec, usage.ru_utime.tv_usec / 1000,
                    usage.ru_stime.tv_sec, usage.ru_stime.tv_usec / 1000);
            sp<IServiceManager> sm = defaultServiceManager();
            sp<IBinder> binder = sm->getService(String16("media.log"));
            if (binder != 0) {
                Vector<String16> args;
                binder->dump(-1, args);
            }
            switch (info.si_code) {
            case CLD_EXITED:
            case CLD_KILLED:
            case CLD_DUMPED: {
                ALOG(LOG_INFO, "media.log", "exiting");
                _exit(0);
                // not reached
                }
            default:
                break;
            }
        }
    } else {
        // all other services
        if (doLog) {
            prctl(PR_SET_PDEATHSIG, SIGKILL);   // if parent media.log dies before me, kill me also
            setpgid(0, 0);                      // but if I die first, don't kill my parent
        }
        sp<ProcessState> proc(ProcessState::self());
        sp<IServiceManager> sm = defaultServiceManager();
        ALOGI("ServiceManager: %p", sm.get());
        AudioFlinger::instantiate();
        MediaPlayerService::instantiate();
        CameraService::instantiate();
#ifdef AUDIO_LISTEN_ENABLED
        ALOGI("ListenService instantiated");
        ListenService::instantiate();
#endif
        AudioPolicyService::instantiate();
        SoundTriggerHwService::instantiate();
        registerExtensions();
        ProcessState::self()->startThreadPool();
        IPCThreadState::self()->joinThreadPool();
    }
}

```

我們可以看到這個裏面註冊了MediaPlayerService,CameraService,

還有與我們audio相關的AudioFlinge和AudioPolicyService:
AudioFlinger::instantiate();
AudioPolicyService::instantiate();
之後下面兩句等待binder進程間通信,是一個死循環,如果沒有通信請求就休眠
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
這個裏面關鍵的一句是AudioPolicyService::instantiate();
instantiate()和其他的沒有區別就是創建service並且註冊進
servicemanager。如果不懂先暫且這樣理解,可以看我之後關於
binder進程間通信的博文,主要的是AudioPolicyService的
onFirstRef()函數,裏面會創建AudioPolicyManager
而AudioPolicyManager裏面又會加載我們的Hal層
(通過AudioPolicyClient的loadHwModule)AudioPolicyClient的
loadHwModule()又是通過AudioFlinger的loadHwModule()加載hal層
繞了這麼一大圈。我們接下來分析AudioFlinger的loadHwModule()

AudioPolicyManager

```
AudioPolicyManager::AudioPolicyManager(AudioPolicyClientInterface *clientInterface)
。。。
{
    。。。
    for (size_t i = 0; i < mHwModules.size(); i++) 
    {
        //這裏通過binder間接調用AudioFlinger的loadHwModule
        mHwModules[i]->mHandle = mpClientInterface->loadHwModule(mHwModules[i]->mName);
        if (mHwModules[i]->mHandle == 0) {
            ALOGW("could not open HW module %s", mHwModules[i]->mName);
            continue;
        }
    
        for (size_t j = 0; j < mHwModules[i]->mOutputProfiles.size(); j++)
        {
            。。。。
            。。。。
            //這裏類似於loadHwModule調用AudioFinger
            //的openOutput
            status_t status = mpClientInterface->openOutput(outProfile->mModule->mHandle,
                                                            &output,
                                                            &config,
                                                            &outputDesc->mDevice,
                                                            String8(""),
                                                            &outputDesc->mLatency,
                                                            outputDesc->mFlags);
            。。。。
            。。。。
        }
        
        for (size_t j = 0; j < mHwModules[i]->mInputProfiles.size(); j++)
        {
            。。。。
            。。。。
            //這裏類似於loadHwModule調用AudioFinger
            //的openIntput
            status_t status = mpClientInterface->openInput(inProfile->mModule->mHandle,
                                                           &input,
                                                           &config,
                                                           &inputDesc->mDevice,
                                                           String8(""),
                                                           AUDIO_SOURCE_MIC,
                                                           AUDIO_INPUT_FLAG_NONE);
            。。。。
            。。。。
                                                           
        }
    }
}
```

LoadHwModule

```
audio_module_handle_t AudioFlinger::loadHwModule(const char *name)
{
    if (name == NULL) {
        return 0;
    }
    if (!settingsAllowed()) {
        return 0;
    }
    Mutex::Autolock _l(mLock);
    return loadHwModule_l(name);
}

// loadHwModule_l() must be called with AudioFlinger::mLock held
audio_module_handle_t AudioFlinger::loadHwModule_l(const char *name)
{
    for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
        if (strncmp(mAudioHwDevs.valueAt(i)->moduleName(), name, strlen(name)) == 0) {
            ALOGW("loadHwModule() module %s already loaded", name);
            return mAudioHwDevs.keyAt(i);
        }
    }

    audio_hw_device_t *dev;
    //這裏會加載層
    int rc = load_audio_interface(name, &dev);
    if (rc) {
        ALOGI("loadHwModule() error %d loading module %s ", rc, name);
        return 0;
    }
    。。。。
    。。。。
    
    //將我們加載hal層之後創建的AudioHwDevice添加進mAudioHwDevs
    mAudioHwDevs.add(handle,
    new AudioHwDevice(handle,name,dev,flags))
    return handle;

}

```

後面openInput與openOutput與加載hal層類似fenbie會創建
RecoderThread和PlaybackThread並分別添加進RecoderThreads和
mPlaybackThreads供之後應用使用
需要注意的是PlaybackThread有三個子類offloadThread,
MixerThread和DirectOutputThread。mPlaybackThreads裏面add的就是這三種線程。應用程序會根據實際選擇這些從裏面查找使用哪一種thread播放。

AudioFlinger類視圖

只列出剛纔講的thread和AudioHwDevice

這幅圖我們只是將剛纔講的mAudioHwDevs,mPlaybackThreads
和mRecoderThread

到這裏我們的mediaservice結束,Audio也已經完成
5.systemserver註冊AudioService
一個java層的service雖然叫做service實質上是通過AudioSystem取得
AudioFlinger的binder代理讓AudioFlinger來實現實際功能的。

systemserver之startOtherService()

```startOtherService
private void startOtherServices() 
{
    。。。。
    。。。。
    if (!disableMedia && !"0".equals(SystemProperties.get("system_init.startaudioservice"))) 
    {
        try {
            Slog.i(TAG, "Audio Service");
            //創建AudioService並且註冊進servicemanager之後可以通//過binder通信調用
            audioService = new AudioService(context);
            ServiceManager.addService(Context.AUDIO_SERVICE, audioService);
        } catch (Throwable e) {
            reportWtf("starting Audio Service", e);
        }
    }
    。。。。
    。。。。
}
```

至此我們的Audio全部初始化完畢。
6.應用程序
應用程序中我們通常可以使用MediaPlayer,AudioManager,AudioTrack
和AudioRecoder。
如果我們的硬件上面的codec比較低端那麼我們就不能使用硬件解碼,只能通過軟件解碼。應用程序中通常使用MediaPlayer我在軟件框架圖中所畫,先通過libstagefright軟件解碼,然後纔會使用AudioTrack播放。當然如果有一種格式libstagefright也不支持,那麼一般是先使用第三方庫
解碼之後再去播放。
AudioManager在應用程序中一般是需要權限的,他是通過binder使用
AudioService的來實現的
AudioRecode與AudioTrack類似只不過用於錄音。
具體怎麼實現邏輯關係圖請看上面的軟件架構圖。由於篇幅原因這裏就不詳細討論了。
其他的SDK接口這裏就不討論了

最後歡迎大神批評指正

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