Android 音頻框架記錄
基于android P 高通平臺閱讀與記錄, 只是整體從上層應用到底層驅動的簡單介紹,主要根據audio 的open,find和write 三個流程介紹音頻框架,
1音頻結構

framework:android 的應用框架層
media:主要負責媒體掃描,播放和錄音,和媒體控制等類,在播放和錄音這塊都是基于底層audiotrack 和audiorecord來實作的,
audio:主要負責音頻的流輸出輸入,聲音大小,以及音頻相關的引數操作,
Media jni & audio jni :主要用于封裝和銜接java層部分,
frameworks/av/media/libaudioclient:音頻客戶端部分,也包含一些binder的介面檔案
AudioSystem.cpp:用于AudioFlinger 和AudioPolicyService的一些函式封裝的作業,以static靜態的方式修飾函式,
AudioTrackShared.cpp:是客戶端AudioTrack與服務端Track的共享記憶體類,用于傳遞音頻資料和音頻引數傳遞,
AudioTrack.cpp: 音頻播放的客戶端
AudioRecord.cpp:音頻錄音的客戶端
frameworks/av/services/audiopolicy:android的音頻策略模塊,用于決定音頻在哪個模塊哪個設備上播放和錄音的策略管理,
managerdefinitions:關于音頻策略配置的資料模型,通過Serializer.cpp決議xml填充資料,
AudioPolicyManager.cpp:音頻資料策略管理,比較核心的一個類
AudioPolicyService.cpp: 音頻策略服務,關于音頻策略的具體實作在AudioPolicyManager.cpp,
frameworks/av/services/audioflinger:音頻輸出輸入的作業區域
AudioFlinger.cpp:音頻的設備和流的具體執行控制者
Threads.cpp:音頻讀寫音頻的作業執行緒,內部包含PlaybackThread MixerThread DirectOutputThread OffloadThread執行緒
FastMixer.cpp:加快音頻快取資料,
Tracks.cpp:與客戶端AudioTrack 對應的服務端binder物件,中間傳遞使用共享記憶體的方式共享一個結構體audio_track_cblk_t
AudioHwDevice.cpp:音頻設備的客戶端,持有DeviceHalInterface的hal設備介面
AudioStreamOut.cpp:音頻流的客戶端,持有StreamOutHalInterface的hal輸出流介面
media/libaudiohal/4.0:屬于與hal層對接的模塊
local:兼容老的android 版本的hal模塊,屬于本地直接連接hal模塊
hidl:使用hidl的系結方式連接hal模塊,相當于跨行程通訊
hardware/interfaces/audio/core/all-versions/default/include/core/all-versions/default:音頻hidl的服務端
hal: hal層
system/bt/audio_a2dp_hw/src: 藍牙a2dp 的hal 模塊
hardware/qcom/audio/hal: primary的hal模塊
external/tinyalsa: tinyalsa的客戶端
kernel: kernel內核層
kernel/msm-4.14/sound/core:tinyalsa的內核代碼
sm6150.c : 高通平臺代碼,適配tinyalsa介面
2音頻焦點
2.1內部音頻焦點
內部焦點請求就是指一般的音頻焦點請求方式使用這里就不做詳細解說,大概理一下結構

每個app在播放音頻時都應該遵循AudioFocus的規則,使用AudioManager.RequestAudioFocus方法請求焦點,如果請求成功則可以播放,反之則不能播放,在失去焦點和恢復焦點的時候時通過監聽回呼函式onAudioFocusChange得到對應的操作狀態,
2.2外部音頻焦點
外部焦點是基于AudioFocus的機制情況下擴展的一個代理模塊,比較有代表性的是車載多媒體系統中,音頻的管理比手機較為復雜,在不改手機源有的焦點策略管理的基礎上,就有了外部音頻焦點這個概念,客戶端的請求和回呼保持不變,把焦點請求策略的核心部分代理給famewrok外部模塊實作,例如CarService,

如圖上所述,客戶端的介面未變,只是多了一個CarService 和AudioService互動,把請求焦點和反饋焦點狀態的都轉移給CarService 處理,
3 Audioflinger
這是音頻框架中核心模塊, 具體負責音頻的設備打開,資料流的傳輸作業,
在這里以輸出流為例,

從app到最后kernel,簡單的呈現音頻資料的流向,
其中在MixerThread中會把多個track的音頻資料做混音處理
在tinyalsa會根據傳遞的device 和flag 得出usecase,最后得到相應的card id和device id,
通過card id和service id找到對音的音頻設備,并其傳入資料,
4 Audiopolicy
audiopolicy是音頻的一個策略管理,主要負責通過組態檔和代碼初始化后得到配置資料;用于決策音頻資料進入具體的輸出通道
4.1音頻靜態配置策略
如圖中描述音頻配置的資料結構

通過AudioPolciyManager初始化的時候,通過 Serializer.cpp決議audio_policy_configuration.xml得到HwModule 的集合,
xml與資料模型對應關系如下表格
| xml | class | 描述 |
|---|---|---|
| module | HwModule | 模塊 |
| mixPort | IOProfile | IO概述AUDIO_PORT_ROLE_SOURCE |
| profile | AudioProfile | 音頻的格式引數 |
| devicePort | DeviceDescriptor | 設備描述AUDIO_PORT_ROLE_SINK |
| gain | AudioGain | 音頻的增益 |
| route | AudioRoute | 音頻的路線 |
| attachedDevices | DeviceDescriptor | 系結模塊的設備 |
| defaultOutputDevice | DeviceDescriptor | 默認的輸出設備 |
補充一下:
IOProfile:可以理解記錄了當前所能支持的設備,這個是通過 Serializer.cpp里的AudioRoute決議獲取的,
DeviceDescriptor:設備的描述,記錄設備的地址和設備型別,并與 IOProfile都繼承了AudioPort,
AudioRoute:代表音頻的線路, 可以連接 IOProfile和DeviceDescriptor的關系
4.2音頻動態配置策略

基本上關于動態配置策略的是意圖如上,先從Car Service 注冊相關配置規則,在等音頻應用播放的時候創建Track,到AudioFlinger 需要獲取output ,在AudioPolicyManager 內部拿到之前注冊的動態配置規則獲取相應的output id,
4.3音頻打開設備流程
音頻初始化的時候會根據audio_policy_configuration.xml決議得到的資料打開對應的模塊和設備
通過遍歷HwModule,獲取IOProfile并作為引數創建SwAudioOutputDescriptor,在做open來打開音頻設備獲取output 的id,
這里是以primary模塊為例,使用hidl方式訪問hal層,

4.4音頻尋找設備流程
音頻播放的時候會根據streamtype和attributes 尋找對應的strategy,在根據strategy尋找對應device,在根據device尋找對應的output,根據ooutput找到對應PlaybackThread,通過PlaybackThread創建服務端對應的track與客戶端的AudioTrack傳遞音頻資料和引數調整,

4.5 AudioTrack 的start流程
在AudoTrack構造的時候會獲得服務端的一個代理類mAudioTrack,這個物件的介面是IAudioTrack,由TrackHandle作為服務端,并內部持有一個Track的物件,
所以AudioTrack 在呼叫 start 時會呼叫服務端TrackHandle的start方法

圖中在SwAudioOutputDescriptor會呼叫mProfile->curActiveCount++;,代表當前激活的流加一,具體可以看下面原始碼注釋
frameworks/av/services/audiopolicy/common/managerdefinitions/include/IOProfile.h
// Number of streams currently active for this profile. This is not the number of active clients
// (AudioTrack or AudioRecord) but the number of active HAL streams.
uint32_t curActiveCount;
在看PlaybackThread類的addTrack_l方法,此方法在呼叫mActiveTracks.add(track);把track匯入mActiveTracks這個集合中,代表激活狀態的track集合,
最后一個地方在AudioTrackServerProxy類的start方法,內部通過原子操作更新mStoopLast狀態,代表更新最后一次停止的位置,
void AudioTrackServerProxy::start()
{
mStopLast = android_atomic_acquire_load(&mCblk->u.mStreaming.mStop);
}
4.6 AudioMixer 與FastMixer
這兩個類主要為了在執行緒MixerThread 做混音作業, FastMixer是利用空間換時間的方式多開啟一個執行緒在做資料傳輸,

圖中上面是當initFastMixer當為真的時候會使用FastMixer的快速混合通道流程,當initFastMixer為假的是只走普通的AudioMixer混合通道流程,
audio_utils_fifo:是一個環形記憶體控制元件,
下面關于AudioBufferProvider相關類圖:

Track:代表與客戶端AudioTrack對應的代理類
SourceAudioBufferProvider:是把突通的Track通過AudioMixer混音后的資料通過MonoPipe寫入快取,SourceAudioBufferProvider內部的MonoPipeReader類讀取對應的快取,
如下是在在MixerThread執行緒與FastMixer執行緒之間傳遞Track資訊需要的狀態佇列FastMixerStateQueue,內部佇列元素FastMixerState會包含需要混音的狀態資料,其中有一個結構體FastTrack記錄資料的提供者,ExtendedAudioBufferProvider* mBufferProvider;
// Represents the state of a fast track
struct FastTrack {
FastTrack();
/*virtual*/ ~FastTrack();
ExtendedAudioBufferProvider* mBufferProvider; // must be NULL if inactive, or non-NULL if active
VolumeProvider* mVolumeProvider; // optional; if NULL then full-scale
audio_channel_mask_t mChannelMask; // AUDIO_CHANNEL_OUT_MONO or AUDIO_CHANNEL_OUT_STEREO
audio_format_t mFormat; // track format
int mGeneration; // increment when any field is assigned
};
Track和SourceAudioBufferProvider都是繼承于ExtendedAudioBufferProvider,
查看FastTrack內部的mBufferProvider是一個ExtendedAudioBufferProvider類,他可能是Track,也可能是SourceAudioBufferProvider,這里表示普通的Track會通過MixerThread內部的AudioMixer 混合資料寫入SourceAudioBufferProvider,在SourceAudioBufferProvider和快速標識Track通過FastMixer內部的AudioMixer混合寫入資料到AudioStreamOut在寫入hal層等等,
4.7 AudioPatch 與 PatchPanel
這主要是描述音頻的線路配置,可以一個音頻資料對應多個設備,也可以 一個設備對應一個音頻接收方,也可以設備對應設備
TODO:由于接觸的平臺代碼都不支持,只有一些介面框架,無法把流程串起來,再次就不做擴展介紹了,后續如果相關平臺在補充,
可以參考在Android5.0上Audio Patch和Patch Panel的一些分析
5 tinyalsa
tinyalsa是android 根據alsa裁減的簡化版,
tinyalsa原始碼位于android原始碼目錄下external/tinyalsa,包含了四個命令,分別是tinymix,tinycap, tinyplay,tinymeminfo和一個庫libtinyalsa.so
使用mmm命令編譯,mmm external/tinyalsa
相關目錄及檔案
/dev/snd/ 系統下control設備管理、pcm設備都在此目錄下
/proc/asound/ 聲卡相關資訊可以在此目錄下找到,命令:cat /proc/asound/cards可以查看系統下所有聲卡及其ID
5.1 tinyalsa初始化
這里以高通平臺sm6150為例在驅動檔案sm6150.c 探測方法msm_asoc_machine_probe開始初始化聲卡和pcm設備
如圖:

/dev/snd/ 系 pcm設備 的命名規則是pcmC0D1p pcmC0D1c 這種格式,具體在代碼kernel/msm-4.14/sound/core/pcm.c的snd_pcm_new_stream方法 有定義
int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
{
省略...
dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device,
stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
省略...
return 0;
}
EXPORT_SYMBOL(snd_pcm_new_stream);
在代碼中可以看出pcm設備的命名是以pcmC%iD%i%c來定義的, 第一個引數代表card,第二個引數代表device,第三個引數代表是playback還是capture,
下面是三個引數的由來:
Card:從上面時序圖中init.c 的方法snd_card_new
int snd_card_new(struct device *parent, int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret)
{
struct snd_card *card;
int err;
if (snd_BUG_ON(!card_ret))
return -EINVAL;
*card_ret = NULL;
if (extra_size < 0)
extra_size = 0;
card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
省略...
if (idx < 0) /* first check the matching module-name slot */
idx = get_slot_from_bitmask(idx, module_slot_match, module);
if (idx < 0) /* if not matched, assign an empty slot */
idx = get_slot_from_bitmask(idx, check_empty_slot, module);
省略...
card->number = idx;
省略...
err = kobject_set_name(&card->card_dev.kobj, "card%d", idx);
if (err < 0)
goto __error;
snprintf(card->irq_descr, sizeof(card->irq_descr), "%s:%s",
dev_driver_string(card->dev), dev_name(&card->card_dev));
省略...
*card_ret = card;
return 0;
__error_ctl:
snd_device_free_all(card);
__error:
put_device(&card->card_dev);
return err;
}
EXPORT_SYMBOL(snd_card_new);
引數idx 通過呼叫地方soc-core.c 的方法snd_soc_instantiate_card呼叫snd_card_new的第二個引數SNDRV_DEFAULT_IDX1 定義是-1
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
省略...
/* card bind complete so register a sound card */
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card→snd_card);
省略...
所以在回到snd_card_new中 idx =-1 的情況通過get_slot_from_bitmask計算idx值,一般第一次是0,
在把idx 賦值給card->number,最后通過指標card_ret 獲得card物件地址回傳給呼叫地方(&card→snd_card),
Device:設備這個引數在soc_add_pcm_runtime 中card->num_rtd自增的形式累加
static void soc_add_pcm_runtime(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd)
{
list_add_tail(&rtd->list, &card->rtd_list);
rtd->num = card->num_rtd;
card->num_rtd++;
}
此函式是表示把新創建的snd_soc_pcm_runtime的物件掛在card->rtd_list集合串列里,rtd->num代表device的序號
會在soc_probe_link_dais方法呼叫soc_new_pcm傳入rtd->num,如下代碼:
/* create the pcm */
ret = soc_new_pcm(rtd, rtd->num);
soc-pcm.c
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
省略...
/* create the PCM */
if (rtd->dai_link->no_pcm) {
snprintf(new_name, sizeof(new_name), "(%s)",
rtd->dai_link->stream_name);
ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
playback, capture, &pcm);
} else {
if (rtd->dai_link->dynamic)
snprintf(new_name, sizeof(new_name), "%s (*)",
rtd->dai_link->stream_name);
else
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name,
(rtd->num_codecs > 1) ?
"multicodec" : rtd->codec_dai->name, num);
ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,
capture, &pcm);
}
省略...
上面代碼snd_pcm_new_internal的第三個引數 num 就是代表設備的序號,
int snd_pcm_new_internal(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count,
struct snd_pcm **rpcm)
{
return _snd_pcm_new(card, id, device, playback_count, capture_count,
true, rpcm);
}
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, bool internal,
struct snd_pcm **rpcm)
{
struct snd_pcm *pcm;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
if (rpcm)
*rpcm = NULL;
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (!pcm)
return -ENOMEM;
pcm->card = card;
pcm->device = device;
pcm->internal = internal;
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
INIT_LIST_HEAD(&pcm->list);
if (id)
strlcpy(pcm->id, id, sizeof(pcm->id));
err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK,
playback_count);
if (err < 0)
goto free_pcm;
err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count);
if (err < 0)
goto free_pcm;
err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops);
if (err < 0)
goto free_pcm;
if (rpcm)
*rpcm = pcm;
return 0;
free_pcm:
snd_pcm_free(pcm);
return err;
}
最后設備序號傳入給pcm: pcm->device = device;
最后通過snd_pcm_new_stream 初始化pcm的錄音流和播放流
5.2 tinyalsa 設備的匹配查找
tinyalsa的對外介面pcm_open用于打開指定的pcm設備,主要流程如下

看sound.c 函式snd_lookup_minor_data
void *snd_lookup_minor_data(unsigned int minor, int type)
{
struct snd_minor *mreg;
void *private_data;
if (minor >= ARRAY_SIZE(snd_minors))
return NULL;
mutex_lock(&sound_mutex);
mreg = snd_minors[minor];
if (mreg && mreg->type == type) {
private_data = mreg->private_data;
if (private_data && mreg->card_ptr)
get_device(&mreg->card_ptr->card_dev);
} else
private_data = NULL;
mutex_unlock(&sound_mutex);
return private_data;
}
此函式是為了獲取snd_pcm,通過minor 和type 在snd_minors陣列中尋找對應的private_data,
在看最后pcm_native.c函式snd_pcm_open_substream
int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
struct file *file,
struct snd_pcm_substream **rsubstream)
{
struct snd_pcm_substream *substream;
int err;
//獲取合適的substream
err = snd_pcm_attach_substream(pcm, stream, file, &substream);
if (err < 0)
return err;
if (substream->ref_count > 1) {
*rsubstream = substream;
return 0;
}
err = snd_pcm_hw_constraints_init(substream);
if (err < 0) {
pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n");
goto error;
}
//呼叫substream介面open,具體實作是通過平臺驅動和組件注冊,
if ((err = substream->ops->open(substream)) < 0)
goto error;
substream->hw_opened = 1;
err = snd_pcm_hw_constraints_complete(substream);
if (err < 0) {
pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n");
goto error;
}
*rsubstream = substream;
return 0;
error:
snd_pcm_release_substream(substream);
return err;
}
此函式通過pcm ,stream 和file找到對應的substream, 在執行substream->ops->open
疑問1 snd_pcm的來源

在時序圖中snd_pcm 在_snd_pcm_new創建的,在snd_device_new方法中把snd_pcm保存在snd_device的device_data成員里,
在通過snd_pcm_dev_register把snd_device 的device_data成員取出snd_pcm;通過snd_register_device把snd_pcm保存在snd_minors 的陣列元素的private_data成員,
疑問2 substream的來源
可以先看kernel/msm-4.14/sound/core/pcm.c方法snd_pcm_new_stream, substream是在此方法創建的. ,
疑問3 substream的open流程
需要先看ops在哪里被裝載的,可以在kernel/msm-4.14/sound/soc/soc-pcm.c 的soc_new_pcm方法 下面
/* ASoC PCM operations */
if (rtd->dai_link->dynamic) {
rtd->ops.open = dpcm_fe_dai_open;
rtd->ops.hw_params = dpcm_fe_dai_hw_params;
rtd->ops.prepare = dpcm_fe_dai_prepare;
rtd->ops.trigger = dpcm_fe_dai_trigger;
rtd->ops.hw_free = dpcm_fe_dai_hw_free;
rtd->ops.close = dpcm_fe_dai_close;
rtd->ops.pointer = soc_pcm_pointer;
rtd->ops.delay_blk = soc_pcm_delay_blk;
rtd->ops.ioctl = soc_pcm_ioctl;
rtd->ops.compat_ioctl = soc_pcm_compat_ioctl;
} else {
rtd->ops.open = soc_pcm_open;
rtd->ops.hw_params = soc_pcm_hw_params;
rtd->ops.prepare = soc_pcm_prepare;
rtd->ops.trigger = soc_pcm_trigger;
rtd->ops.hw_free = soc_pcm_hw_free;
rtd->ops.close = soc_pcm_close;
rtd->ops.pointer = soc_pcm_pointer;
rtd->ops.delay_blk = soc_pcm_delay_blk;
rtd->ops.ioctl = soc_pcm_ioctl;
rtd->ops.compat_ioctl = soc_pcm_compat_ioctl;
}
if (platform->driver->ops) {
rtd->ops.ack = platform->driver->ops->ack;
rtd->ops.copy_user = platform->driver->ops->copy_user;
rtd->ops.copy_kernel = platform->driver->ops->copy_kernel;
rtd->ops.fill_silence = platform->driver->ops->fill_silence;
rtd->ops.page = platform->driver->ops->page;
rtd->ops.mmap = platform->driver->ops->mmap;
}
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
在看snd_pcm_set_ops方法
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
const struct snd_pcm_ops *ops)
{
struct snd_pcm_str *stream = &pcm->streams[direction];
struct snd_pcm_substream *substream;
for (substream = stream->substream; substream != NULL; substream = substream->next)
substream->ops = ops;
}
可以看出substream的ops 被上面rtd的ops賦值,
rtd的 ops.open 指向的是soc_pcm_open
/*
* Called by ALSA when a PCM substream is opened, the runtime->hw record is
* then initialized and any private data can be allocated. This also calls
* startup for the cpu DAI, platform, machine and codec DAI.
*/
static int soc_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_component *component;
struct snd_soc_rtdcom_list *rtdcom;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai;
const char *codec_dai_name = "multicodec";
省略...
/* startup the audio subsystem */
if (cpu_dai->driver->ops && cpu_dai->driver->ops->startup) {
ret = cpu_dai->driver->ops->startup(substream, cpu_dai);
if (ret < 0) {
dev_err(cpu_dai->dev, "ASoC: can't open interface"
" %s: %d\n", cpu_dai->name, ret);
goto out;
}
}
if (platform->driver->ops && platform->driver->ops->open) {
ret = platform->driver->ops->open(substream);
if (ret < 0) {
dev_err(platform->dev, "ASoC: can't open platform"
" %s: %d\n", platform->component.name, ret);
goto platform_err;
}
}
for (i = 0; i < rtd->num_codecs; i++) {
codec_dai = rtd->codec_dais[i];
if (codec_dai->driver->ops && codec_dai->driver->ops->startup) {
ret = codec_dai->driver->ops->startup(substream,
codec_dai);
if (ret < 0) {
dev_err(codec_dai->dev,
"ASoC: can't open codec %s: %d\n",
codec_dai->name, ret);
goto codec_dai_err;
}
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
codec_dai->tx_mask = 0;
else
codec_dai->rx_mask = 0;
}
if (rtd->dai_link->ops && rtd->dai_link->ops->startup) {
ret = rtd->dai_link->ops->startup(substream);
if (ret < 0) {
pr_err("ASoC: %s startup failed: %d\n",
rtd->dai_link->name, ret);
goto machine_err;
}
}
省略...
}
這下面都是在其他驅動注冊時候匯入的資料,
cpu_dai->driver->ops
platform->driver->ops
rtd->codec_dais
rtd->dai_link->ops
由于代碼穿插太繁瑣, 這里就以cpu_dai為例分析一下流程
cpu_dai是在rtd結構體內部,所以要看rtd從創建開始后哪里有被賦值,
查看kernel/msm-4.14/sound/soc/soc-core.c的soc_bind_dai_link方法
static int soc_bind_dai_link(struct snd_soc_card *card,
struct snd_soc_dai_link *dai_link)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai_link_component *codecs = dai_link->codecs;
struct snd_soc_dai_link_component cpu_dai_component;
struct snd_soc_component *component;
struct snd_soc_dai **codec_dais;
struct snd_soc_platform *platform;
struct device_node *platform_of_node;
const char *platform_name;
int i;
dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name);
if (soc_is_dai_link_bound(card, dai_link)) {
dev_dbg(card->dev, "ASoC: dai link %s already bound\n",
dai_link->name);
return 0;
}
rtd = soc_new_pcm_runtime(card, dai_link);
if (!rtd)
return -ENOMEM;
cpu_dai_component.name = dai_link->cpu_name;
cpu_dai_component.of_node = dai_link->cpu_of_node;
cpu_dai_component.dai_name = dai_link->cpu_dai_name;
rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
這里可以看到rtd的cpu_dai是由snd_soc_find_dai找出來的,
struct snd_soc_dai *snd_soc_find_dai(
const struct snd_soc_dai_link_component *dlc)
{
struct snd_soc_component *component;
struct snd_soc_dai *dai;
struct device_node *component_of_node;
lockdep_assert_held(&client_mutex);
/* Find CPU DAI from registered DAIs*/
list_for_each_entry(component, &component_list, list) {
component_of_node = component->dev->of_node;
if (!component_of_node && component->dev->parent)
component_of_node = component->dev->parent->of_node;
if (dlc->of_node && component_of_node != dlc->of_node)
continue;
if (dlc->name && strcmp(component->name, dlc->name))
continue;
list_for_each_entry(dai, &component->dai_list, list) {
if (dlc->dai_name && strcmp(dai->name, dlc->dai_name)
&& (!dai->driver->name
|| strcmp(dai->driver->name, dlc->dai_name)))
continue;
return dai;
}
}
return NULL;
}
可以看得出來是通過遍歷component_list找到對應的dai的,那么我又要看看component_list是從哪里獲取的
static void snd_soc_component_add_unlocked(struct snd_soc_component *component)
{
if (!component->write && !component->read) {
if (!component->regmap)
component->regmap = dev_get_regmap(component->dev, NULL);
if (component->regmap)
snd_soc_component_setup_regmap(component);
}
list_add(&component->list, &component_list);
INIT_LIST_HEAD(&component->dobj_list);
}
static void snd_soc_component_add(struct snd_soc_component *component)
{
mutex_lock(&client_mutex);
snd_soc_component_add_unlocked(component);
mutex_unlock(&client_mutex);
}
int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
const struct snd_soc_platform_driver *platform_drv)
{
省略...
snd_soc_component_add_unlocked(&platform->component);
list_add(&platform->list, &platform_list);
省略...
return 0;
}
int snd_soc_register_platform(struct device *dev,
const struct snd_soc_platform_driver *platform_drv)
{
struct snd_soc_platform *platform;
int ret;
dev_dbg(dev, "ASoC: platform register %s\n", dev_name(dev));
platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
if (platform == NULL)
return -ENOMEM;
ret = snd_soc_add_platform(dev, platform, platform_drv);
if (ret)
kfree(platform);
return ret;
}
int snd_soc_register_component(struct device *dev,
const struct snd_soc_component_driver *component_driver,
struct snd_soc_dai_driver *dai_drv,
int num_dai)
{
省略...
snd_soc_component_add(component);
省略...
return ret;
}
從上面代碼可以看到在當前soc-core.c檔案為component_list集合添加注冊的對外方法
snd_soc_register_component
snd_soc_register_platform
這兩個方法會在其他音源驅動中呼叫,然后根據snd_soc_find_dai 這個方法遍歷通過node和名字尋找對應的cpu_dai, 就可以執行相應的startup介面,
這里直接找到對應的open實作位置,
vendor/qcom/opensource/audio-kernel/asoc/msm-dai-q6-v2.c
static struct snd_soc_dai_ops msm_dai_q6_mi2s_ops = {
.startup = msm_dai_q6_mi2s_startup,
.prepare = msm_dai_q6_mi2s_prepare,
.hw_params = msm_dai_q6_mi2s_hw_params,
.hw_free = msm_dai_q6_mi2s_hw_free,
.set_fmt = msm_dai_q6_mi2s_set_fmt,
.shutdown = msm_dai_q6_mi2s_shutdown,
};
static int msm_dai_q6_mi2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
return 0;
}
從這里可以看到startup 沒有做任何事情,
5.3 tinyalsa 音頻資料寫入流程
寫入流程也是和open流程差不多, 也是會通過找substream的ops來確定是拿一個音源驅動,這里就先不做詳細介紹了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/300267.html
標籤:其他
