Android Framework 音訊子系統(12)HAL層分析
該系列文章總綱連結:專題分綱目錄 Android Framework 音訊子系統
本章關鍵點總結 & 說明:
本章節主要關注➕ 以上思維導圖左上 HAL層分析 部分 即可。主要說明了HAL層的框架分析,後面通過原始碼分析了 讀音訊資料流程、寫音訊資料流程、設定引數流程、獲取引數流程 來深入的理解 HAL層的呼叫流程。
1 HAL層框架分析
音訊系統的 整個框架圖如下所示:
@1 HAL層的兩個部分
這裡關注HAL層,這裡有audio的HAL,也有Audio_policy的HAL(Audio_policy的HAL我們不關心,基本廢棄)。HAL層下一層使用TiniAlsa(AlSA庫 裁剪版)。HAL層分為兩部分:
- 一部分為各種音訊裝置,每種音訊裝置由一個獨立的庫檔案實現:如audio.a2dp.default.so(管理藍芽a2dp音訊),audio.usb.default.so(管理usb外接的音訊),audio.primary.default.so(管理裝置上的大部分音訊)。
- 一部分為廠家自己實現的音訊策略比如: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 過程總結
- 從配置檔案確定庫檔案的名字。 HAL檔案一般位於/system/lib/hardware下面,音訊中操作硬體的庫檔名稱在(廠商提供的HAL部分)在/system/etc/policy_config中指定。
- 載入庫(*.so)檔案。開啟HAL檔案中的open函式,在HAL中會構造audio_hw_device結構體, 該結構體中有各類函式, 特別是 open_output_stream / open_input_stream。AudioFlinger根據audio_hw_device結構體構造一個AudioHwDev物件並放入mAudioHwDevs。
- 呼叫HAL結構體audio_hw_device的open_input_stream/open_output_stream,它會構造audio_stream_in/audio_stream_out結構體。
- 寫入/讀出資料,呼叫tinyALSA的一些操作,直接使用系統呼叫控制音效卡的是tinyalsa庫,位於目錄/external/tinyalsa下,編譯生成庫檔案libtinyalsa.so(只涉及兩個檔案mixer.c,pcm.c),編譯生成工具 tinycap,tinymix,tinypcminfo,tinyplay,可用於直接控制音訊通道,進行錄音播音測試。使用pcm_XXX操作來操作音效卡,是驅動層的封裝。
- tinyALSA庫再操作音訊驅動程式,音訊驅動程式再操作硬體音效卡裝置。
相關文章
- Android HAL 層框架分析以及程式碼示例Android框架
- android HAL層程式碼Android
- Android音訊驅動學習(一) Audio HALAndroid音訊
- Android 12(S) 影像顯示系統 - HWC HAL 初始化與呼叫流程Android
- Android Framework中的Application Framework層介紹AndroidFrameworkAPP
- android studio 除錯 framework 層程式碼Android除錯Framework
- Android FrameWork學習(二)Android系統原始碼除錯AndroidFramework原始碼除錯
- Android硬體抽象層(HAL)模組編寫規範Android抽象
- Android 12(S) 圖形顯示系統 - 簡述Allocator/Mapper HAL服務的獲取過程(十五)AndroidAPP
- Android藍芽子系統"BlueFrag"漏洞分析(CVE-2020-0022)Android藍芽
- 12┃音視訊直播系統之 WebRTC 實現1對1直播系統實戰Web
- Linux ubi子系統原理分析Linux
- Android FrameworkAndroidFramework
- HAL 硬體抽象層介紹抽象
- Simpleperf分析之Android系統篇Android
- Android 系統原始碼-1:Android 系統啟動流程原始碼分析Android原始碼
- android音視訊指南-管理音訊焦點Android音訊
- Linux Media 子系統鏈路分析Linux
- Android Media Framework(三)OpenMAX API閱讀與分析AndroidFrameworkAPI
- android 音訊播放 SoundPoolAndroid音訊
- Android音訊(三)AudioPolicyServiceAndroid音訊
- Android系統原始碼分析之-ContentProviderAndroid原始碼IDE
- Android系統原始碼分析-事件收集Android原始碼事件
- Sieve—Android 記憶體分析系統Android記憶體
- Android Input子系統-含例項原始碼Android原始碼
- Android 音視訊 - MediaCodec 編解碼音視訊Android
- Android 12(S) 圖形顯示系統 - 開篇Android
- Android音視訊之AudioRecordAndroid
- Android 音訊應用框架Android音訊框架
- Mac 使用音訊工具分析音訊資料Mac音訊
- [譯] Android 中的 MVP:如何使 Presenter 層系統化?AndroidMVP
- Android系統原始碼分析-Broadcast傳送Android原始碼AST
- Android顯示子系統相關基礎概念Android
- Android Thermal HAL 降龍十八掌Android
- 基於Android5.0的Camera Framework原始碼分析 (三)AndroidFramework原始碼
- Android音訊處理知識(一)MediaRecorder錄製音訊Android音訊
- Android 音視訊開發 視訊編碼,音訊編碼格式Android音訊
- android音視訊指南-MediaPlayer概述Android