主頁 > 移動端開發 > Android 音頻框架記錄

Android 音頻框架記錄

2021-09-15 09:49:42 移動端開發

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與資料模型對應關系如下表格

xmlclass描述
moduleHwModule模塊
mixPortIOProfileIO概述AUDIO_PORT_ROLE_SOURCE
profileAudioProfile音頻的格式引數
devicePortDeviceDescriptor設備描述AUDIO_PORT_ROLE_SINK
gainAudioGain音頻的增益
routeAudioRoute音頻的路線
attachedDevicesDeviceDescriptor系結模塊的設備
defaultOutputDeviceDeviceDescriptor默認的輸出設備

補充一下:
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

標籤:其他

上一篇:為了徹底搞懂性能優化,肝了十幾家大廠的性能優化專案實戰,最后濃縮成這份性能優化實戰篇.

下一篇:IOS技術分享| any自習室場景實作

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more