当前位置:首页 » 《资源分享》 » 正文

Android 音频框架记录_jsm1010的博客

20 人参与  2021年11月03日 07:24  分类 : 《资源分享》  评论

点击全文阅读


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来确定是拿一个音源驱动,这里就先不做详细介绍了。


点击全文阅读


本文链接:http://zhangshiyu.com/post/30425.html

音频  设备  省略  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1