1.  Platform驱动在ASoC中的作用

前面几章内容已经说过,ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。在具体实现上,ASoC有把Platform驱动分为两个部分:snd_soc_platform_driver和snd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。

/*****************************************************************************************************/

声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

2.  snd_soc_platform_driver的注册

通常,ASoC把snd_soc_platform_driver注册为一个系统的platform_driver,不要被这两个相像的术语所迷惑,前者只是针对ASoC子系统的,后者是来自Linux的设备驱动模型。我们要做的就是:
  • 定义一个snd_soc_platform_driver结构的实例;
  • 在platform_driver的probe回调中利用ASoC的API:snd_soc_register_platform()注册上面定义的实例;
  • 实现snd_soc_platform_driver中的各个回调函数;

以kernel3.3中的/sound/soc/samsung/dma.c为例:

[cpp] view plaincopyprint?
  1. static struct snd_soc_platform_driver samsung_asoc_platform = {
  2. .ops        = &dma_ops,
  3. .pcm_new    = dma_new,
  4. .pcm_free   = dma_free_dma_buffers,
  5. };
  6. static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
  7. {
  8. return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
  9. }
  10. static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev)
  11. {
  12. snd_soc_unregister_platform(&pdev->dev);
  13. return 0;
  14. }
  15. static struct platform_driver asoc_dma_driver = {
  16. .driver = {
  17. .name = "samsung-audio",
  18. .owner = THIS_MODULE,
  19. },
  20. .probe = samsung_asoc_platform_probe,
  21. .remove = __devexit_p(samsung_asoc_platform_remove),
  22. };
  23. module_platform_driver(asoc_dma_driver);
static struct snd_soc_platform_driver samsung_asoc_platform = {.ops      = &dma_ops,.pcm_new    = dma_new,.pcm_free    = dma_free_dma_buffers,
};static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
{return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
}static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev)
{snd_soc_unregister_platform(&pdev->dev);return 0;
}static struct platform_driver asoc_dma_driver = {.driver = {.name = "samsung-audio",.owner = THIS_MODULE,},.probe = samsung_asoc_platform_probe,.remove = __devexit_p(samsung_asoc_platform_remove),
};module_platform_driver(asoc_dma_driver);

snd_soc_register_platform() 该函数用于注册一个snd_soc_platform,只有注册以后,它才可以被Machine驱动使用。它的代码已经清晰地表达了它的实现过程:

  • 为snd_soc_platform实例申请内存;
  • 从platform_device中获得它的名字,用于Machine驱动的匹配工作;
  • 初始化snd_soc_platform的字段;
  • 把snd_soc_platform实例连接到全局链表platform_list中;
  • 调用snd_soc_instantiate_cards,触发声卡的machine、platform、codec、dai等的匹配工作;

3.  cpu的snd_soc_dai driver驱动的注册

dai驱动通常对应cpu的一个或几个I2S/PCM接口,与snd_soc_platform一样,dai驱动也是实现为一个platform driver,实现一个dai驱动大致可以分为以下几个步骤:
  • 定义一个snd_soc_dai_driver结构的实例;
  • 在对应的platform_driver中的probe回调中通过API:snd_soc_register_dai或者snd_soc_register_dais,注册snd_soc_dai实例;
  • 实现snd_soc_dai_driver结构中的probe、suspend等回调;
  • 实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;
snd_soc_register_dai  这个函数在上一篇介绍codec驱动的博文中已有介绍,请参考:Linux ALSA声卡驱动之七:ASoC架构中的Codec。 snd_soc_dai  该结构在snd_soc_register_dai函数中通过动态内存申请获得, 简要介绍一下几个重要字段:
  • driver  指向关联的snd_soc_dai_driver结构,由注册时通过参数传入;
  • playback_dma_data  用于保存该dai播放stream的dma信息,例如dma的目标地址,dma传送单元大小和通道号等;
  • capture_dma_data  同上,用于录音stream;
  • platform  指向关联的snd_soc_platform结构;

snd_soc_dai_driver  该结构需要自己根据不同的soc芯片进行定义,关键字段介绍如下:

  • probe、remove  回调函数,分别在声卡加载和卸载时被调用;
  • suspend、resume  电源管理回调函数;
  • ops  指向snd_soc_dai_ops结构,用于配置和控制该dai;
  • playback  snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力;
  • capture  snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力;

4.  snd_soc_dai_driver中的ops字段

ops字段指向一个snd_soc_dai_ops结构,该结构实际上是一组回调函数的集合,dai的配置和控制几乎都是通过这些回调函数来实现的,这些回调函数基本可以分为3大类,驱动程序可以根据实际情况实现其中的一部分:

工作时钟配置函数  通常由machine驱动调用:

  • set_sysclk  设置dai的主时钟;
  • set_pll  设置PLL参数;
  • set_clkdiv  设置分频系数;
  • dai的格式配置函数  通常由machine驱动调用:
  • set_fmt   设置dai的格式;
  • set_tdm_slot  如果dai支持时分复用,用于设置时分复用的slot;
  • set_channel_map 声道的时分复用映射设置;
  • set_tristate  设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调;

标准的snd_soc_ops回调  通常由soc-core在进行PCM操作时调用:

  • startup
  • shutdown
  • hw_params
  • hw_free
  • prepare
  • trigger

抗pop,pop声  由soc-core调用:

  • digital_mute 

以下这些api通常被machine驱动使用,machine驱动在他的snd_pcm_ops字段中的hw_params回调中使用这些api:

  • snd_soc_dai_set_fmt()  实际上会调用snd_soc_dai_ops或者codec driver中的set_fmt回调;
  • snd_soc_dai_set_pll() 实际上会调用snd_soc_dai_ops或者codec driver中的set_pll回调;
  • snd_soc_dai_set_sysclk()  实际上会调用snd_soc_dai_ops或者codec driver中的set_sysclk回调;
  • snd_soc_dai_set_clkdiv()  实际上会调用snd_soc_dai_ops或者codec driver中的set_clkdiv回调;

snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)的第二个参数fmt在这里特别说一下,ASoC目前只是用了它的低16位,并且为它专门定义了一些宏来方便我们使用:

bit 0-3 用于设置接口的格式:

[cpp] view plaincopyprint?
  1. #define SND_SOC_DAIFMT_I2S      1 /* I2S mode */
  2. #define SND_SOC_DAIFMT_RIGHT_J      2 /* Right Justified mode */
  3. #define SND_SOC_DAIFMT_LEFT_J       3 /* Left Justified mode */
  4. #define SND_SOC_DAIFMT_DSP_A        4 /* L data MSB after FRM LRC */
  5. #define SND_SOC_DAIFMT_DSP_B        5 /* L data MSB during FRM LRC */
  6. #define SND_SOC_DAIFMT_AC97     6 /* AC97 */
  7. #define SND_SOC_DAIFMT_PDM      7 /* Pulse density modulation */
#define SND_SOC_DAIFMT_I2S      1 /* I2S mode */
#define SND_SOC_DAIFMT_RIGHT_J      2 /* Right Justified mode */
#define SND_SOC_DAIFMT_LEFT_J       3 /* Left Justified mode */
#define SND_SOC_DAIFMT_DSP_A        4 /* L data MSB after FRM LRC */
#define SND_SOC_DAIFMT_DSP_B        5 /* L data MSB during FRM LRC */
#define SND_SOC_DAIFMT_AC97     6 /* AC97 */
#define SND_SOC_DAIFMT_PDM      7 /* Pulse density modulation */

bit 4-7 用于设置接口时钟的开关特性:

[cpp] view plaincopyprint?
  1. #define SND_SOC_DAIFMT_CONT     (1 << 4) /* continuous clock */
  2. #define SND_SOC_DAIFMT_GATED        (2 << 4) /* clock is gated */
#define SND_SOC_DAIFMT_CONT       (1 << 4) /* continuous clock */
#define SND_SOC_DAIFMT_GATED        (2 << 4) /* clock is gated */

bit 8-11 用于设置接口时钟的相位:

[cpp] view plaincopyprint?
  1. #define SND_SOC_DAIFMT_NB_NF        (1 << 8) /* normal bit clock + frame */
  2. #define SND_SOC_DAIFMT_NB_IF        (2 << 8) /* normal BCLK + inv FRM */
  3. #define SND_SOC_DAIFMT_IB_NF        (3 << 8) /* invert BCLK + nor FRM */
  4. #define SND_SOC_DAIFMT_IB_IF        (4 << 8) /* invert BCLK + FRM */
#define SND_SOC_DAIFMT_NB_NF      (1 << 8) /* normal bit clock + frame */
#define SND_SOC_DAIFMT_NB_IF        (2 << 8) /* normal BCLK + inv FRM */
#define SND_SOC_DAIFMT_IB_NF        (3 << 8) /* invert BCLK + nor FRM */
#define SND_SOC_DAIFMT_IB_IF        (4 << 8) /* invert BCLK + FRM */

bit 12-15 用于设置接口主从格式:

[cpp] view plaincopyprint?
  1. #define SND_SOC_DAIFMT_CBM_CFM      (1 << 12) /* codec clk & FRM master */
  2. #define SND_SOC_DAIFMT_CBS_CFM      (2 << 12) /* codec clk slave & FRM master */
  3. #define SND_SOC_DAIFMT_CBM_CFS      (3 << 12) /* codec clk master & frame slave */
  4. #define SND_SOC_DAIFMT_CBS_CFS      (4 << 12) /* codec clk & FRM slave */
#define SND_SOC_DAIFMT_CBM_CFM      (1 << 12) /* codec clk & FRM master */
#define SND_SOC_DAIFMT_CBS_CFM      (2 << 12) /* codec clk slave & FRM master */
#define SND_SOC_DAIFMT_CBM_CFS      (3 << 12) /* codec clk master & frame slave */
#define SND_SOC_DAIFMT_CBS_CFS      (4 << 12) /* codec clk & FRM slave */

5.  snd_soc_platform_driver中的ops字段

该ops字段是一个snd_pcm_ops结构,实现该结构中的各个回调函数是soc platform驱动的主要工作,他们基本都涉及dma操作以及dma buffer的管理等工作。下面介绍几个重要的回调函数:

ops.open

当应用程序打开一个pcm设备时,该函数会被调用,通常,该函数会使用snd_soc_set_runtime_hwparams()设置substream中的snd_pcm_runtime结构里面的hw_params相关字段,然后为snd_pcm_runtime的private_data字段申请一个私有结构,用于保存该平台的dma参数。

ops.hw_params

驱动的hw_params阶段,该函数会被调用。通常,该函数会通过snd_soc_dai_get_dma_data函数获得对应的dai的dma参数,获得的参数一般都会保存在snd_pcm_runtime结构的private_data字段。然后通过snd_pcm_set_runtime_buffer函数设置snd_pcm_runtime结构中的dma buffer的地址和大小等参数。要注意的是,该回调可能会被多次调用,具体实现时要小心处理多次申请资源的问题。

ops.prepare

正式开始数据传送之前会调用该函数,该函数通常会完成dma操作的必要准备工作。

ops.trigger

数据传送的开始,暂停,恢复和停止时,该函数会被调用。

ops.pointer

该函数返回传送数据的当前位置。

6.  音频数据的dma操作

soc-platform驱动的最主要功能就是要完成音频数据的传送,大多数情况下,音频数据都是通过dma来完成的。

6.1.  申请dma buffer

因为dma的特殊性,dma buffer是一块特殊的内存,比如有的平台规定只有某段地址范围的内存才可以进行dma操作,而多数嵌入式平台还要求dma内存的物理地址是连续的,以方便dma控制器对内存的访问。在ASoC架构中,dma buffer的信息保存在snd_pcm_substream结构的snd_dma_buffer *buf字段中,它的定义如下

[cpp] view plaincopyprint?
  1. struct snd_dma_buffer {
  2. struct snd_dma_device dev;  /* device type */
  3. unsigned char *area;    /* virtual pointer */
  4. dma_addr_t addr;    /* physical address */
  5. size_t bytes;       /* buffer size in bytes */
  6. void *private_data; /* private for allocator; don't touch */
  7. };
struct snd_dma_buffer {struct snd_dma_device dev;  /* device type */unsigned char *area;   /* virtual pointer */dma_addr_t addr;   /* physical address */size_t bytes;     /* buffer size in bytes */void *private_data;   /* private for allocator; don't touch */
};

那么,在哪里完成了snd_dam_buffer结构的初始化赋值操作呢?答案就在snd_soc_platform_driver的pcm_new回调函数中,还是以/sound/soc/samsung/dma.c为例:

[cpp] view plaincopyprint?
  1. static struct snd_soc_platform_driver samsung_asoc_platform = {
  2. .ops        = &dma_ops,
  3. .pcm_new    = dma_new,
  4. .pcm_free   = dma_free_dma_buffers,
  5. };
  6. static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
  7. {
  8. return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
  9. }
static struct snd_soc_platform_driver samsung_asoc_platform = {.ops        = &dma_ops,.pcm_new    = dma_new,.pcm_free    = dma_free_dma_buffers,
};static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
{return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
}

pcm_new字段指向了dma_new函数,dma_new函数进一步为playback和capture分别调用preallocate_dma_buffer函数,我们看看preallocate_dma_buffer函数的实现:

[cpp] view plaincopyprint?
  1. static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
  2. {
  3. struct snd_pcm_substream *substream = pcm->streams[stream].substream;
  4. struct snd_dma_buffer *buf = &substream->dma_buffer;
  5. size_t size = dma_hardware.buffer_bytes_max;
  6. pr_debug("Entered %s\n", __func__);
  7. buf->dev.type = SNDRV_DMA_TYPE_DEV;
  8. buf->dev.dev = pcm->card->dev;
  9. buf->private_data = NULL;
  10. buf->area = dma_alloc_writecombine(pcm->card->dev, size,
  11. &buf->addr, GFP_KERNEL);
  12. if (!buf->area)
  13. return -ENOMEM;
  14. buf->bytes = size;
  15. return 0;
  16. }
static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
{struct snd_pcm_substream *substream = pcm->streams[stream].substream;struct snd_dma_buffer *buf = &substream->dma_buffer;size_t size = dma_hardware.buffer_bytes_max;pr_debug("Entered %s\n", __func__);buf->dev.type = SNDRV_DMA_TYPE_DEV;buf->dev.dev = pcm->card->dev;buf->private_data = NULL;buf->area = dma_alloc_writecombine(pcm->card->dev, size,&buf->addr, GFP_KERNEL);if (!buf->area)return -ENOMEM;buf->bytes = size;return 0;
}

该函数先是获得事先定义好的buffer大小,然后通过dma_alloc_weitecombine函数分配dma内存,然后完成substream->dma_buffer的初始化赋值工作。上述的pcm_new回调会在声卡的建立阶段被调用,调用的详细的过程请参考Linux ALSAs声卡驱动之六:ASoC架构中的Machine中的图3.1。

在声卡的hw_params阶段,snd_soc_platform_driver结构的ops->hw_params会被调用,在该回调用,通常会使用api:snd_pcm_set_runtime_buffer()把substream->dma_buffer的数值拷贝到substream->runtime的相关字段中(.dma_area, .dma_addr,  .dma_bytes),这样以后就可以通过substream->runtime获得这些地址和大小信息了。

dma buffer获得后,即是获得了dma操作的源地址,那么目的地址在哪里?其实目的地址当然是在dai中,也就是前面介绍的snd_soc_dai结构的playback_dma_data和capture_dma_data字段中,而这两个字段的值也是在hw_params阶段,由snd_soc_dai_driver结构的ops->hw_params回调,利用api:snd_soc_dai_set_dma_data进行设置的。紧随其后,snd_soc_platform_driver结构的ops->hw_params回调利用api:snd_soc_dai_get_dma_data获得这些dai的dma信息,其中就包括了dma的目的地址信息。这些dma信息通常还会被保存在substream->runtime->private_data中,以便在substream的整个生命周期中可以随时获得这些信息,从而完成对dma的配置和操作。

6.2  dma buffer管理

播放时,应用程序把音频数据源源不断地写入dma buffer中,然后相应platform的dma操作则不停地从该buffer中取出数据,经dai送往codec中。录音时则正好相反,codec源源不断地把A/D转换好的音频数据经过dai送入dma buffer中,而应用程序则不断地从该buffer中读走音频数据。
图6.2.1   环形缓冲区
环形缓冲区正好适合用于这种情景的buffer管理,理想情况下,大小为Count的缓冲区具备一个读指针和写指针,我们期望他们都可以闭合地做环形移动,但是实际的情况确实:缓冲区通常都是一段连续的地址,他是有开始和结束两个边界,每次移动之前都必须进行一次判断,当指针移动到末尾时就必须人为地让他回到起始位置。在实际应用中,我们通常都会把这个大小为Count的缓冲区虚拟成一个大小为n*Count的逻辑缓冲区,相当于理想状态下的圆形绕了n圈之后,然后把这段总的距离拉平为一段直线,每一圈对应直线中的一段,因为n比较大,所以大多数情况下不会出现读写指针的换位的情况(如果不对buffer进行扩展,指针到达末端后,回到起始端时,两个指针的前后相对位置会发生互换)。扩展后的逻辑缓冲区在计算剩余空间可条件判断是相对方便。alsa driver也使用了该方法对dma buffer进行管理:
图6.2.2  alsa driver缓冲区管理
snd_pcm_runtime结构中,使用了四个相关的字段来完成这个逻辑缓冲区的管理:
  • snd_pcm_runtime.hw_ptr_base  环形缓冲区每一圈的基地址,当读写指针越过一圈后,它按buffer size进行移动;
  • snd_pcm_runtime.status->hw_ptr  硬件逻辑位置,播放时相当于读指针,录音时相当于写指针;
  • snd_pcm_runtime.control->appl_ptr  应用逻辑位置,播放时相当于写指针,录音时相当于读指针;
  • snd_pcm_runtime.boundary  扩展后的逻辑缓冲区大小,通常是(2^n)*size;

通过这几个字段,我们可以很容易地获得缓冲区的有效数据,剩余空间等信息,也可以很容易地把当前逻辑位置映射回真实的dma buffer中。例如,获得播放缓冲区的空闲空间:

[csharp] view plaincopyprint?
  1. static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *runtime)
  2. {
  3. snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;
  4. if (avail < 0)
  5. avail += runtime->boundary;
  6. else if ((snd_pcm_uframes_t) avail >= runtime->boundary)
  7. avail -= runtime->boundary;
  8. return avail;
  9. }
static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *runtime)
{snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;if (avail < 0)avail += runtime->boundary;else if ((snd_pcm_uframes_t) avail >= runtime->boundary)avail -= runtime->boundary;return avail;
}

要想映射到真正的缓冲区位置,只要减去runtime->hw_ptr_base即可。下面的api用于更新这几个指针的当前位置:

[cpp] view plaincopyprint?
  1. int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
所以要想通过snd_pcm_playback_avail等函数获得正确的信息前,应该先要调用这个api更新指针位置。

以播放(playback)为例,我现在知道至少有3个途径可以完成对dma buffer的写入:

  • 应用程序调用alsa-lib的snd_pcm_writei、snd_pcm_writen函数;
  • 应用程序使用ioctl:SNDRV_PCM_IOCTL_WRITEI_FRAMES或SNDRV_PCM_IOCTL_WRITEN_FRAMES;
  • 应用程序使用alsa-lib的snd_pcm_mmap_begin/snd_pcm_mmap_commit;

以上几种方式最终把数据写入dma buffer中,然后修改runtime->control->appl_ptr的值。

播放过程中,通常会配置成每一个period size生成一个dma中断,中断处理函数最重要的任务就是:
  • 更新dma的硬件的当前位置,该数值通常保存在runtime->private_data中;
  • 调用snd_pcm_period_elapsed函数,该函数会进一步调用snd_pcm_update_hw_ptr0函数更新上述所说的4个缓冲区管理字段,然后唤醒相应的等待进程;
[cpp] view plaincopyprint?
  1. <span style="font-family: Arial, Verdana, sans-serif;"><span style="white-space: normal;"></span></span><pre class="cpp" name="code">void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
  2. {
  3. struct snd_pcm_runtime *runtime;
  4. unsigned long flags;
  5. if (PCM_RUNTIME_CHECK(substream))
  6. return;
  7. runtime = substream->runtime;
  8. if (runtime->transfer_ack_begin)
  9. runtime->transfer_ack_begin(substream);
  10. snd_pcm_stream_lock_irqsave(substream, flags);
  11. if (!snd_pcm_running(substream) ||
  12. snd_pcm_update_hw_ptr0(substream, 1) < 0)
  13. goto _end;
  14. if (substream->timer_running)
  15. snd_timer_interrupt(substream->timer, 1);
  16. _end:
  17. snd_pcm_stream_unlock_irqrestore(substream, flags);
  18. if (runtime->transfer_ack_end)
  19. runtime->transfer_ack_end(substream);
  20. kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
  21. }
  22. </pre>如果设置了transfer_ack_begin和transfer_ack_end回调,snd_pcm_period_elapsed还会调用这两个回调函数。<br>
  23. <br>
  24. <pre></pre>
  25. <pre></pre>
  26. <pre></pre>
[cpp] view plaincopyprint?
  1. void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
  2. {
  3. struct snd_pcm_runtime *runtime;
  4. unsigned long flags;
  5. if (PCM_RUNTIME_CHECK(substream))
  6. return;
  7. runtime = substream->runtime;
  8. if (runtime->transfer_ack_begin)
  9. runtime->transfer_ack_begin(substream);
  10. snd_pcm_stream_lock_irqsave(substream, flags);
  11. if (!snd_pcm_running(substream) ||
  12. snd_pcm_update_hw_ptr0(substream, 1) < 0)
  13. goto _end;
  14. if (substream->timer_running)
  15. snd_timer_interrupt(substream->timer, 1);
  16. _end:
  17. snd_pcm_stream_unlock_irqrestore(substream, flags);
  18. if (runtime->transfer_ack_end)
  19. runtime->transfer_ack_end(substream);
  20. kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
  21. }
void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
{struct snd_pcm_runtime *runtime;unsigned long flags;if (PCM_RUNTIME_CHECK(substream))return;runtime = substream->runtime;if (runtime->transfer_ack_begin)runtime->transfer_ack_begin(substream);snd_pcm_stream_lock_irqsave(substream, flags);if (!snd_pcm_running(substream) ||snd_pcm_update_hw_ptr0(substream, 1) < 0)goto _end;if (substream->timer_running)snd_timer_interrupt(substream->timer, 1);_end:snd_pcm_stream_unlock_irqrestore(substream, flags);if (runtime->transfer_ack_end)runtime->transfer_ack_end(substream);kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
}
如果设置了transfer_ack_begin和transfer_ack_end回调,snd_pcm_period_elapsed还会调用这两个回调函数。

7.  图说代码

最后,反正图也画了,好与不好都传上来供参考一下,以下这张图表达了 ASoC中Platform驱动的几个重要数据结构之间的关系:

图7.1   ASoC Platform驱动

一堆的private_data,很重要但也很容易搞混,下面的图不知对大家有没有帮助:

图7.2  private_data

Linux ALSA声卡驱动之八:ASoC架构中的Platform相关推荐

  1. Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介

    ALSA声卡驱动: 1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介 2.Linux ALSA声卡驱动之二:Platform 3. Linux ALSA声卡驱动之三:Platf ...

  2. Linux ALSA声卡驱动之四:Codec 以及Codec_dai

    ALSA声卡驱动: 1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介 2.Linux ALSA声卡驱动之二:Platform 3. Linux ALSA声卡驱动之三:Platf ...

  3. Linux ALSA声卡驱动之二:Platform

    ALSA声卡驱动: 1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介 2.Linux ALSA声卡驱动之二:Platform 3. Linux ALSA声卡驱动之三:Platf ...

  4. Linux ALSA声卡驱动之五:Machine 以及ALSA声卡的注册

    ALSA声卡驱动: 1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介 2.Linux ALSA声卡驱动之二:Platform 3. Linux ALSA声卡驱动之三:Platf ...

  5. Linux ALSA声卡驱动之三:Platform之Cpu_dai

    ALSA声卡驱动: 1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介 2.Linux ALSA声卡驱动之二:Platform 3. Linux ALSA声卡驱动之三:Platf ...

  6. Linux ALSA声卡驱动之七:录音(Capture) 调用流程

    ALSA声卡驱动: 1.Linux ALSA声卡驱动之一:ALSA架构简介和ASOC架构简介 2.Linux ALSA声卡驱动之二:Platform 3. Linux ALSA声卡驱动之三:Platf ...

  7. linux alsa声卡驱动原理分析- 设备打开过程和数据流程,linux alsa声卡驱动原理分析解析- 设备打开过程跟数据流程资料.ppt...

    linux alsa声卡驱动原理分析解析- 设备打开过程跟数据流程资料 Linux ALSA声卡驱动原理分析 -设备打开过程和数据流程;目 录;目 录;一.导 读;目 录;二.ALSA架构简介;二. ...

  8. Linux ALSA声卡驱动之七:ASoC架构中的Codec

    1.  Codec简介 在移动设备中,Codec的作用可以归结为4种,分别是: 对PCM等信号进行D/A转换,把数字的音频信号转换为模拟信号 对Mic.Linein或者其他输入源的模拟信号进行A/D转 ...

  9. Linux ALSA声卡驱动之六:ASoC架构中的Machine

    前面一节的内容我们提到,ASoC被分为Machine.Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上 ...

最新文章

  1. Linux基础命令-mkdir
  2. 人类一败涂地电脑版_【游戏资讯】这是PC版塞尔达?终极无敌暴力缝合怪登陆Steam!PS3模拟器更新后可流畅运行美末!...
  3. 14-数据库连接池和jdbc优化
  4. mysql innodb_data_file_path_MySQL修改innodb_data_file_path参数的一些注意事项
  5. 框架页面中,从子页面刷新父页面问题解决
  6. 深入理解iPhone委托模式兼谈iPhone生命周期
  7. 微软 android 输入法下载官网下载地址,微软五笔输入法官网下载_微软王码五笔输入法8689版官方下载-华军软件园...
  8. lena图的直方图以及与其他图像的直方图匹配
  9. 回顾安全多方计算-SMPC(MPC)
  10. html 保存 文字变乱码怎么办,html文字为什么会乱码
  11. 开新林哲自曝:打造二手车全球经营
  12. txt 文本文件替换快捷键
  13. ReportNG测试报告的定制修改
  14. ubuntu18.04安装cudnn出现错误:FreeImage is not set up correctly. Please ensure FreeImae is set up correctly
  15. 7-4 输出两行短句 (10分)
  16. C语言题目:数字金字塔(有条件的老师同学点一下赞呀)
  17. 个人期货交割日(国内期货交割日)
  18. 设计模式篇:状态模式(一)
  19. Computer:IPFS(星际文件系统)的简介、安装、使用方法之详细攻略
  20. DNF剑皇全技能名+图标

热门文章

  1. 你还在new对象吗?Java8通用Builder了解一下?
  2. 浪漫的形式有100种,单身的就1种!
  3. 常见的神经网络求导总结!
  4. 9 大主题卷积神经网络(CNN)的 PyTorch 实现
  5. 数据分析索引总结(下)Pandas索引技巧
  6. 快乐学习Pandas入门篇:Pandas基础
  7. 论文被拒,项目被砍?斯坦福最惨在读博士教你如何直面失败
  8. PostgreSQL_row_number() over()
  9. 想“看见”高性能计算嘛?戳这里开始
  10. 在字节跳动工作是什么样的体验?