Android Framework 音訊子系統(12)HAL層分析

AGS-wangdsh發表於2020-04-13

該系列文章總綱連結:專題分綱目錄 Android Framework 音訊子系統​​​​​​​


本章關鍵點總結 & 說明:

本章節主要關注➕ 以上思維導圖左上 HAL層分析 部分 即可。主要說明了HAL層的框架分析,後面通過原始碼分析了 讀音訊資料流程、寫音訊資料流程、設定引數流程、獲取引數流程 來深入的理解 HAL層的呼叫流程。


1 HAL層框架分析

音訊系統的 整個框架圖如下所示:

@1 HAL層的兩個部分

這裡關注HAL層,這裡有audio的HAL,也有Audio_policy的HAL(Audio_policy的HAL我們不關心,基本廢棄)。HAL層下一層使用TiniAlsa(AlSA庫 裁剪版)。HAL層分為兩部分:

  1. 一部分為各種音訊裝置,每種音訊裝置由一個獨立的庫檔案實現:如audio.a2dp.default.so(管理藍芽a2dp音訊),audio.usb.default.so(管理usb外接的音訊),audio.primary.default.so(管理裝置上的大部分音訊)。
  2. 一部分為廠家自己實現的音訊策略比如:audio.primary.tiny4412.so。

@2 關鍵類與結構體

一般來講,為了方便,HAL要向上層提供統一的介面,操作硬體也會有一組介面/一個類。分別為:

  • 向上提供介面struct audio_hw_device:struct audio_hw_device介面在audio_hw_hal.cpp檔案中,它是對hw_device_t結構的封裝。audio_hw_hal.cpp位於hardware/libhardware_legacy/audio下(新的架構在hardware/libhardware/modules/audio下有個audio_hw.c檔案,它就是新的Audio的HAL檔案,但是在5.0板中沒有使用它,它裡面的函式全部為空。這個檔案同樣沒有實現,所以libhardware_legacy下的還是起主打作用)。
  • 向下訪問硬體class AudioHardware:一般在device/"平臺廠商"/common/libaudio/audioHardware.cpp中實現, 由廠商提供,裡面使用到了tinyalsa庫的介面。廠商實現硬體的訪問介面,Android指定了一套介面給它。幾個關鍵類的繼承關係為:
AudioHardwareInterface   //hardware/AudioHardwareInterface.cpp 最基礎 介面類
    ↑ (繼承)
AudioHardwareBase        //hardware/AudioHardwareBase.h 這個應該是Audio HAL給廠商定義的介面
    ↑ (繼承)
AudioHardware            //device/"平臺廠商"/common/libaudio/audioHardware.cpp 廠商的實現

在廠商的HAL中,AudioHardware (audioHardware.cpp中)表示一個音效卡,它使用audio_stream_out結構來表示輸出,使用audio_stream_in來表示輸入。(audio_stream_out中有write(),audio_stream_in中有read())。

@3 HAL相關的資料結構總結:

/* 上下銜接
 * Audio HAL的呼叫流程總結上層應用程式呼叫
 * audio_hw_hal.cpp中的legacy_adev_open()
 * 會得到一個struct audio_hw_device結構體,
 * 這個結構體代表上層使用硬體的介面,這個
 * 結構體中的函式都依賴於廠家提供的
 * struct AudioHardwareInterface結構。
 */
struct legacy_audio_device {
    struct audio_hw_device device;      //規範了向上提供的介面
    struct AudioHardwareInterface *hwif;//向下訪問硬體,指向廠家的AudioHardware
};

/*由於HAL對上層直接提供的介面中沒有read/write函式
 *因此,應用程式想要錄音或播放聲音的時候需要先開啟
 *output或input(audio_hw_device中的open_output_stream/open_input_stream),
 *進而使用其裡面的write/read函式通過音效卡硬體讀/寫音訊資料。
 *這也就是audio_hw_device與audio_stream_out/audio_stream_in之間的關係
 */
struct legacy_stream_out {
    struct audio_stream_out stream; //規範了向上提供的介面
    AudioStreamOut *legacy_out;     //向下訪問硬體,指向廠家的AudioStreamOutALSA
};

struct legacy_stream_in {
    struct audio_stream_in stream;//規範了向上提供的介面
    AudioStreamIn *legacy_in;     //向下訪問硬體,指向廠家的AudioStreamInALSA
};

2 HAL層原始碼分析

接下來 主要分析幾個關鍵流程(寫資料操作、讀資料操作、獲取引數、設定引數)來解讀HAL的呼叫過程。

AudioFlinger載入庫的過程如下:

AudioFlinger::loadHwModule
->AudioFlinger::loadHwModule_l
-->load_audio_interface
--->audio_hw_device_open(mod, dev);//獲取audio_hw_device_t結構體以及它的操作
---->module->methods->open //這裡對應的是legacy_adev_open

這裡對中呼叫到了HAL層的legacy_adev_open方法,程式碼實現如下:

static int legacy_adev_open(const hw_module_t* module, const char* name,
                            hw_device_t** device)
{
    struct legacy_audio_device *ladev;
    int ret;

    if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0)
        return -EINVAL;

    ladev = (struct legacy_audio_device *)calloc(1, sizeof(*ladev));
    if (!ladev)
        return -ENOMEM;

    //結構體賦值
    ladev->device.common.tag = HARDWARE_DEVICE_TAG;
    ladev->device.common.version = AUDIO_DEVICE_API_VERSION_2_0;
    ladev->device.common.module = const_cast<hw_module_t*>(module);
    ladev->device.common.close = legacy_adev_close;
    ladev->device.init_check = adev_init_check;
    ladev->device.set_voice_volume = adev_set_voice_volume;
    ladev->device.set_master_volume = adev_set_master_volume;
    ladev->device.get_master_volume = adev_get_master_volume;
    ladev->device.set_mode = adev_set_mode;
    ladev->device.set_mic_mute = adev_set_mic_mute;
    ladev->device.get_mic_mute = adev_get_mic_mute;
    ladev->device.set_parameters = adev_set_parameters;
    ladev->device.get_parameters = adev_get_parameters;
    ladev->device.get_input_buffer_size = adev_get_input_buffer_size;
    ladev->device.open_output_stream = adev_open_output_stream;
    ladev->device.close_output_stream = adev_close_output_stream;
    ladev->device.open_input_stream = adev_open_input_stream;
    ladev->device.close_input_stream = adev_close_input_stream;
    ladev->device.dump = adev_dump;
    /* 關鍵點:
     * audio_hw_device_t結構體 和 hwif(hardwareInterface)介面之間建立聯絡
     * 這裡通過createAudioHardware 獲取 實現hardwareInterface介面的廠商指標
     * 後面呼叫hwif的相關操作 <=等價=> 使用廠商的庫函式中的方法
     */
    ladev->hwif = createAudioHardware();
    if (!ladev->hwif) {
        ret = -EIO;
        goto err_create_audio_hw;
    }
    *device = &ladev->device.common;
    return 0;

err_create_audio_hw:
    free(ladev);
    return ret;
}

通過上面的分析,這裡從Framework Native層到HAL層框架,再到第三方平臺廠商庫的呼叫,他們之間的關係就打通了。

2.1 寫資料操作

從Framework的Native層開始,audio_hw_device_t 結構體首先會通過adev_open_output_stream獲取audio_stream_out,因此從它開始分析。程式碼如下:

static int adev_open_output_stream(struct audio_hw_device *dev,
                                   audio_io_handle_t handle,
                                   audio_devices_t devices,
                                   audio_output_flags_t flags,
                                   struct audio_config *config,
                                   struct audio_stream_out **stream_out,
                                   const char *address __unused)
{
    struct legacy_audio_device *ladev = to_ladev(dev);
    status_t status;
    struct legacy_stream_out *out;
    int ret;

    //這裡的legacy_stream_out <=結構體型別 等價=>audio_stream_out
    out = (struct legacy_stream_out *)calloc(1, sizeof(*out));
    if (!out)
        return -ENOMEM;

    devices = convert_audio_device(devices, HAL_API_REV_2_0, HAL_API_REV_1_0);
    //這裡將audio_stream_out與ladev->hwif之間建立聯絡
    out->legacy_out = ladev->hwif->openOutputStreamWithFlags(devices, flags,
                                                    (int *) &config->format,
                                                    &config->channel_mask,
                                                    &config->sample_rate, &status);
    if (!out->legacy_out) {
        ret = status;
        goto err_open;
    }

    out->stream.common.get_sample_rate = out_get_sample_rate;
    out->stream.common.set_sample_rate = out_set_sample_rate;
    out->stream.common.get_buffer_size = out_get_buffer_size;
    out->stream.common.get_channels = out_get_channels;
    out->stream.common.get_format = out_get_format;
    out->stream.common.set_format = out_set_format;
    out->stream.common.standby = out_standby;
    out->stream.common.dump = out_dump;
    out->stream.common.set_parameters = out_set_parameters;
    out->stream.common.get_parameters = out_get_parameters;
    out->stream.common.add_audio_effect = out_add_audio_effect;
    out->stream.common.remove_audio_effect = out_remove_audio_effect;
    out->stream.get_latency = out_get_latency;
    out->stream.set_volume = out_set_volume;
    out->stream.write = out_write;
    out->stream.get_render_position = out_get_render_position;
    out->stream.get_next_write_timestamp = out_get_next_write_timestamp;

    //將out->stream寫回到引數 stream_out中
    *stream_out = &out->stream;
    return 0;

err_open:
    free(out);
    *stream_out = NULL;
    return ret;
}

這裡通過hwif獲取了音訊輸出流(audio_stream_out型別),然後對out->stream進行初始化,註冊了 寫入函式out_write 之後將其返回給傳遞進來的指標變數stream_out(audio_stream_out型別),當上層進行寫操作時,就會執行這個out_write函式,程式碼如下:

static ssize_t out_write(struct audio_stream_out *stream, const void* buffer,
                         size_t bytes)
{
    struct legacy_stream_out *out = reinterpret_cast<struct legacy_stream_out *>(stream);
    return out->legacy_out->write(buffer, bytes);
}

這裡直接呼叫到 第三方平臺廠商庫的write(out->legacy_out->write)方法(如果這裡是 高通平臺,則所謂的hwif就是AudioHardwareALSA,所謂的write就是AudioStreamOutALSA的write方法,最終會帶哦用pcm_write進行寫資料操作)。

2.2 讀資料操作

從Framework的Native層開始,audio_hw_device_t 結構體首先會通過adev_open_Input_stream獲取audio_stream_in,因此從它開始分析。程式碼如下:


/** This method creates and opens the audio hardware input stream */
static int adev_open_input_stream(struct audio_hw_device *dev,
                                  audio_io_handle_t handle,
                                  audio_devices_t devices,
                                  struct audio_config *config,
                                  struct audio_stream_in **stream_in,
                                  audio_input_flags_t flags __unused,
                                  const char *address __unused,
                                  audio_source_t source __unused)
{
    struct legacy_audio_device *ladev = to_ladev(dev);
    status_t status;
    struct legacy_stream_in *in;
    int ret;
	//這裡的legacy_stream_in <=結構體型別 等價=>audio_stream_in
    in = (struct legacy_stream_in *)calloc(1, sizeof(*in));
    if (!in)
        return -ENOMEM;

    devices = convert_audio_device(devices, HAL_API_REV_2_0, HAL_API_REV_1_0);
	//這裡將audio_stream_in與ladev->hwif之間建立聯絡
    in->legacy_in = ladev->hwif->openInputStream(devices, (int *) &config->format,
                                                 &config->channel_mask, &config->sample_rate,
                                                 &status, (AudioSystem::audio_in_acoustics)0);
    if (!in->legacy_in) {
        ret = status;
        goto err_open;
    }

    in->stream.common.get_sample_rate = in_get_sample_rate;
    in->stream.common.set_sample_rate = in_set_sample_rate;
    in->stream.common.get_buffer_size = in_get_buffer_size;
    in->stream.common.get_channels = in_get_channels;
    in->stream.common.get_format = in_get_format;
    in->stream.common.set_format = in_set_format;
    in->stream.common.standby = in_standby;
    in->stream.common.dump = in_dump;
    in->stream.common.set_parameters = in_set_parameters;
    in->stream.common.get_parameters = in_get_parameters;
    in->stream.common.add_audio_effect = in_add_audio_effect;
    in->stream.common.remove_audio_effect = in_remove_audio_effect;
    in->stream.set_gain = in_set_gain;
    in->stream.read = in_read;
    in->stream.get_input_frames_lost = in_get_input_frames_lost;
	//將in->stream寫回到引數 stream_in中
    *stream_in = &in->stream;
    return 0;

err_open:
    free(in);
    *stream_in = NULL;
    return ret;
}

這裡通過hwif獲取了音訊輸入流(audio_stream_in型別),然後對in->stream進行初始化,註冊了 寫入函式in_read 之後將其返回給傳遞進來的指標變數stream_in(audio_stream_in型別),當上層進行寫操作時,就會執行這個in_read函式,程式碼如下:

static ssize_t in_read(struct audio_stream_in *stream, void* buffer,
                       size_t bytes)
{
    struct legacy_stream_in *in =
        reinterpret_cast<struct legacy_stream_in *>(stream);
    return in->legacy_in->read(buffer, bytes);
}

這裡直接呼叫到 第三方平臺廠商庫的read(out->legacy_in->read)方法(如果這裡是 高通平臺,則所謂的hwif就是AudioHardwareALSA,所謂的read就是AudioStreamInALSA的read方法,最終會帶哦用pcm_read進行讀資料操作)。

2.3 獲取引數

從Framework的Native層開始,audio_hw_device_t 結構體的獲取引數的操作最後一定會呼叫到 adev_get_parameters,因此從它開始分析。程式碼如下:

static char * adev_get_parameters(const struct audio_hw_device *dev,
                                  const char *keys)
{
    const struct legacy_audio_device *ladev = to_cladev(dev);
    String8 s8;

    s8 = ladev->hwif->getParameters(String8(keys));
    return strdup(s8.string());
}

這裡直接呼叫到 第三方平臺廠商庫的getParameters方法。

2.4 設定引數

從Framework的Native層開始,audio_hw_device_t 結構體的設定引數的操作最後一定會呼叫到 adev_set_parameters,因此從它開始分析。程式碼如下:

static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)
{
    struct legacy_audio_device *ladev = to_ladev(dev);
    return ladev->hwif->setParameters(String8(kvpairs));
}

這裡直接呼叫到 第三方平臺廠商庫的setParameters方法。

2.5 過程總結

  1. 從配置檔案確定庫檔案的名字。 HAL檔案一般位於/system/lib/hardware下面,音訊中操作硬體的庫檔名稱在(廠商提供的HAL部分)在/system/etc/policy_config中指定。
  2. 載入庫(*.so)檔案。開啟HAL檔案中的open函式,在HAL中會構造audio_hw_device結構體, 該結構體中有各類函式, 特別是 open_output_stream / open_input_stream。AudioFlinger根據audio_hw_device結構體構造一個AudioHwDev物件並放入mAudioHwDevs。
  3. 呼叫HAL結構體audio_hw_device的open_input_stream/open_output_stream,它會構造audio_stream_in/audio_stream_out結構體。
  4. 寫入/讀出資料,呼叫tinyALSA的一些操作,直接使用系統呼叫控制音效卡的是tinyalsa庫,位於目錄/external/tinyalsa下,編譯生成庫檔案libtinyalsa.so(只涉及兩個檔案mixer.c,pcm.c),編譯生成工具 tinycap,tinymix,tinypcminfo,tinyplay,可用於直接控制音訊通道,進行錄音播音測試。使用pcm_XXX操作來操作音效卡,是驅動層的封裝。
  5. tinyALSA庫再操作音訊驅動程式,音訊驅動程式再操作硬體音效卡裝置。

相關文章