Chinaunix首页 | 论坛 | 博客
  • 博客访问: 568529
  • 博文数量: 185
  • 博客积分: 4031
  • 博客等级: 上校
  • 技术积分: 1591
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-27 19:45
文章分类

全部博文(185)

文章存档

2011年(14)

2010年(63)

2009年(108)

我的朋友

分类:

2009-06-26 14:07:48

ALSA 学习笔记
   因为项目用的kernel为2.6.17,所以以下分析都是基于2.6.17版本,在这个版本里,没有asoc等。                           
1 整体架构
  Application
---------------
  Alsa-lib                   User Space
-------------------------------------
   Alsa                      Kernel Space
-------
  sound driver
----------------------------------
   Hardware
  
Application : 比如aplay ,它不是直接调用Kernel所提供的接口,而是调用ALSA-lib 的接口。所以应用程序只要#include "asound.h"
并链接libasound .
对于上面的架构,在某一时刻只能有一个程序打开声卡并占有它,此时其它程序打开的话,会返回busy.如要支持同时可以多个应用程序打开声卡,需要支持
混音功能,有些声卡支持硬件混音,但大部分声卡不支持硬件混音,需要软件混音。这时需要ESD,pulseAudio等,架构变为:
   App1    App2
---------------
    ESD , pulseaudio
  --------------------  
  Alsa-lib                   User Space
-------------------------------------
   Alsa                      Kernel Space
-------
  sound driver
----------------------------------
   Hardware
  此时,应用程序将调用ESD,pulseaudio等混音器提供的接口。对于ESD,很多程序支持,比如mplayer . 对于pulseaudio ,有相应的patch .
  Alsa本身也提供混音的plugin,dmix .
    App1    App2
---------------
   Alsa-lib (dmix)           User Space
-------------------------------------
   Alsa                      Kernel Space
-------
  sound driver
----------------------------------
   Hardware
  此架构和架构1,应用程序不需要做任何修改,只需要修改asound.conf
  架构1的asound.conf的例子:
  pcm.!default {   
   type hw   
    card 0  
    }
ctl.!default {  
    type hw     
    card 0
     }
   架构3的asound.conf的例子:   
   pcm.card0 {
    type hw
    card 0
}
pcm.!default {
    type plug
    slave.pcm "dmixer"
}
pcm.dmixer  {
    type dmix
    ipc_key 1025
    slave {
        pcm "hw:0,0"
        period_time 0
        period_size 4096
        buffer_size 16384
        periods 128
        rate 44100
    }
    bindings {
        0 0
        1 1
    }
}
关于配置,可以参考这个网站:
  
[url=][/url]

  
2 ALSA kernel
2.1 目录
    Alsa-driver包括很多在开发中的驱动,以及一些2.2,2.4 linux内核版本的支持。当这些驱动稳定后,将移入alsa-kernel中,并最终在linux kernel
  的sound目录下.
          sound
                /core
                        /oss
                        /seq
                                /oss
                                /instr
               /drivers
                        /mpu401
                        /opl3
                /i2c
                        /l3
                /synth
                        /emux
                /pci
                        /(cards)
                /isa
                        /(cards)
                /arm
                /ppc
                /sparc
                /usb
                /pcmcia /(cards)
                /oss
      core目录是alsa的核心,
      core/oss目录主要是为了支持PCM和mixer的OSS模拟,rawmidi OSS模拟放在alsa的rawmidi文件里主要是因为它相当小。这样很多以前支持OSS
      的应用程序也可以在Alsa上运行。
      core/seq , alsa 音序器.core/seq/oss是alsa对OSS音序器模拟。
      driver目录包含各种芯片驱动。
      I2C目录,alsa i2c组件。尽管linux 有标准的i2c层,但alsa对于一些声卡有自己的i2c代码,原因是声卡仅仅需要一些简单的操作而标准的
      I2C API 对于此目的太复杂。
      PCI目录,主要是给PCI总线上的PCI声卡
      ISA目录,主要是给ISA总线上的ISA声卡
      arm, ppc, and sparc目录,这些架构下的某些声卡驱动,如pxa2xx-pcm.c PXA处理器的
      USB目录,USB-audio 驱动
      pcmcia,
      OSS目录, OSS架构的文件,不属于alsa
      
   2.2 接口     
    Alsa kernel为上层主要提供以下接口:
    1 control interface 提供灵活的方式管理注册的声卡和对存在的声卡进行查询。
    2 PCM interface  提供管理数字音频的捕捉和回放。
    3 原始 MIDI 接口
    一种标准电子音乐指令集。 这些 API 提供访问声卡上的 MIDI 总线。这些原始借口直接工作在 The MIDI
事件上,程序员只需要管理协议和时间。
    4 Timer 接口  为支持声音的同步事件提供访问声卡上的定时器。
    5 音序器接口  一个比原始MIDI接口高级的MIDI编程和声音同步高层接口。它可以处理很多的MIDI协议和定时器。
    6 mixer接口   控制发送信号和控制声音大小的声卡上的设备。/dsp/mixer,OSS中存在。
   
   我们主要关心1,2接口
   
   2.3 声卡的管理
   2.3.1 卡 
   对于每一个声卡,一个“卡”的记录必须分配。
   “卡”的记录是声卡的总部,它管理着声卡上的所有的设备(或者组件)的列表,例如PCM,Mixer,MIDI等等。
   数据结构为:snd_card
   其中 number : 第几个声卡,最大为SNDRV_CARDS 8个,对于我们的系统,只有一个声卡的话,number为0
      id     : 声卡的string
         devices : 设备列表
         proc_root :proc文件的根
         private_data:声卡的私有数据
         controls  :声卡的控制接口列表
      还有一些电源管理等
   
   调用snd_card_new来创建一个声卡实体。
  snd_card_new(index, id, module, extra_size);
  其中extra_size为private_data内存空间的大小,在snd_card_new中分配。
  2.3.2 设备(组件)
    卡实例创建后,我们可以attach一个组件(设备)给一个卡的实例。在alsa驱动中,一个组件用结构snd_device对象表示。
    一个组件可以是一个PCM实例,一个控制实例,一个原始MIDI接口等。它调用snd_device_new创建。
     snd_device_new(card, snd_device_type_t, device_data, &ops);
     在control.c ,pcm.c,info.c,Rawmidi.c以及timer.c中,都有snd_device_new的调用,分别创建类型为control,PCM等的deice.
     数据结构为:snd_device
struct list_head list;  /* list of registered devices */
struct snd_card *card;  /* card which holds this device */
snd_device_state_t state; /* state of the device */
snd_device_type_t type;  /* device type */
void *device_data;  /* device structure */
struct snd_device_ops *ops; /* operations */
snd_device_ops包含了注册,unregister,free等函数。
在snd_card_new中,我们创建了一个control的device ,而snd_pcm_new创建了一个pcm的device

  2.3.3 注册与释放
  snd_card_register 调用它后,device 文件可以被外界访问。之前,不能安全被外界所访问。
  snd_card_free 一般在退出的时候调用,这样将把所有的组件都自动释放掉。
  2.4   PCM接口
  ALSA PCM中间层非常强大,驱动只需要实现底层的函数以访问硬件。
  每个卡最多可以有4个PCM实例。
  一个PCM实例包含playback(回放)和capture(录音)流,数据结构为:snd_pcm,其中struct snd_pcm_str streams[2]; stream[0]代表
  playback,stream[1]代表capture.
  每一个pcm流包括一个或者多个pcm子流,
  一些声卡支持多个playback功能。例如,emu10k1有一个PCM回放的32位立体声子流(substream),此时,每次打开,一个空闲的子流自动选择并
  打开,同时,如果只有一个子流存在并已经打开了,接下来的打开要么被阻塞要么返回EAGAIN,但是这些在你的驱动中不需要关心,PCM中间层会
  管理这些工作。
  snd_pcm_new创建一个实体,
  int snd_pcm_new(struct snd_card *card, char *id, int device,
  int playback_count, int capture_count,
         struct snd_pcm ** rpcm)
   device :  第几个pcm实体,从0开始
   playback_count: 回放子流数目 
   capture_count:  录音子流数目
   
  流的数据结构:snd_pcm_str
  其中 stream 表示方向,是playback还是capture
     substream_count子流数目
     struct snd_pcm_substream *substream;子流的指针,指向第一个子流,根据第一个,可以找到下一个(substream->next)
  
  snd_pcm_new_stream创建一个流 ,在snd_pcm_new中被调用。
  
   子流(substream)的数据结构:snd_pcm_substream
   其中 stream 表示方向,是playback还是capture
      buffer_bytes_max 表示最大的环形buffer大小
       dma_buffer     ?????
      dma_max       最大 
   struct snd_pcm_ops *ops; 子流的操作函数,snd_pcm_set_ops会设置ops
    struct snd_pcm_runtime *runtime;
     next  下一个substream,对于只有一个playback的子流,为NULL
  struct snd_timer *timer;  ????
  
  snd_pcm_ops 定义了一系列硬件操作函数,比如open , ioctrl , trigger等
  1 open callback :
     static int snd_xxx_open(struct snd_pcm_substream *substream);
     这个函数在一个子流被打开时调用(snd_pcm_open_substream函数调用)
     调用关系为:
     在 snd_pcm_f_ops中 .open = snd_pcm_playback_open
    snd_pcm_playback_open ---> snd_pcm_open_file---> snd_pcm_open_substream ---> snd_xxx_open
    snd_pcm_f_ops的open ,当你对/dev/snd/PCMC0D0 open时候被调用
    我们的驱动主要是分配substream->runtime,并调用request_irq把中断处理函数和runtime关联起来。
   
    2 close callback
  static int snd_xxx_close(struct snd_pcm_substream *substream);
  这个函数在一个子流被关闭时调用
     调用关系为:
     在 snd_pcm_f_ops中 .release = snd_pcm_release
    snd_pcm_release --->  snd_pcm_release_substream ---> snd_xxx_close
    snd_pcm_f_ops的release ,当你对/dev/snd/PCMC0D0 close时候被调用   
    我们的驱动主要是调用free_irq把中断处理函数和runtime释放掉。
    (是否有内存泄露,因为在open函数里对runtimer 用kmalloc了,在close函数里应该kfree(runtime)???
   
     3 ioctl callback
     一般用snd_pcm_lib_ioctl
     
     4 hw_params callback
     static int snd_xxx_hw_params(struct snd_pcm_substream *substream,
                               struct snd_pcm_hw_params *hw_params);
      调用关系为:
     在 snd_pcm_f_ops中 .unlocked_ioctl = snd_pcm_playback_ioctl
          snd_pcm_playback_ioctl --> snd_pcm_playback_ioctl1
      -->snd_pcm_common_ioctl1 ---> snd_pcm_hw_params_user --->  snd_pcm_hw_params ---> snd_xxx_hw_params
         
    snd_pcm_f_ops的unlocked_ioctl,当你对/dev/snd/PCMC0D0 进行ioctl(int fd, int command, (char *) argstruct)调用时,文件系统的do_ioctl
    会先判断unlocked_ioctl函数是否为空,不为空则调用filp->f_op->unlocked_ioctl ,其中command为   SNDRV_PCM_IOCTL_HW_PARAMS   .
    这个函数在 应用程序设置硬件参数时被调用,也就是说,当pcm子流的buffer大小,周期大小,格式等被定义的时候.
    许多硬件的设置必须在这个回调函数中做,包括buffer的分配.buffer的分配可以调用snd_pcm_lib_malloc_pages函数.
    我们的驱动好像只有buffer的分配,没做别的处理,会不会有问题 ????
   
    5 hw_free callback
    static int snd_xxx_hw_free(struct snd_pcm_substream *substream);
    释放在 hw_params中分配的资源
   
    6 prepare callback
    static int snd_xxx_prepare(struct snd_pcm_substream *substream);
          调用关系为:
     在 snd_pcm_f_ops中 .unlocked_ioctl = snd_pcm_playback_ioctl
          snd_pcm_playback_ioctl --> snd_pcm_playback_ioctl1
      -->snd_pcm_common_ioctl1 ---> snd_pcm_prepare --> snd_pcm_do_prepare ---> snd_xxx_prepare
      IO命令为 SNDRV_PCM_IOCTL_PREPARE.
      你可以在这个函数里设置格式类型,采样率等,它和hw_params的区别在于 prepare回调函数在每次snd_pcm_prepare都会被调用
      例如,underrun后的恢复等
      在这个函数里,你可以通过runtime记录substream->runtime得到一些值,例如,目前的采样率,格式类型,声道数 目,runtime->rate, runtime->format or runtime->channels
      分配的内存设置在 runtime->dma_area,大小和周期分别为runtime->buffer_size和runtime->period_size.
      
      我们的驱动这个函数有问题 .
      
      
     7 trigger callback
      static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd);
          调用关系为:
     在 snd_pcm_f_ops中 .unlocked_ioctl = snd_pcm_playback_ioctl
          snd_pcm_playback_ioctl --> snd_pcm_playback_ioctl1
      -->snd_pcm_common_ioctl1 ---> snd_pcm_drain --> snd_pcm_do_drain_init ---> snd_xxx_trigger
      IO命令为 SNDRV_PCM_IOCTL_DRAIN
     
     这个callback函数是原子的,也就是不能调用会sleep的函数,trigger回调函数应当尽量小,只是triggering DMA,其他的操作在 hw_params和
     perpare里面做.
     我们的驱动实现了start,stop,SUSPEND 和  RESUME, 没有实现pause,unpause.还调用了msleep???? 这不对
     mdelay是一个让CPU空转,一直等待到批定的时间后才退出   
     msleep是让当前进程休眠,让出CPU给其它进程使用,等到时间到了之后再唤醒   
     msleep不能用于中断上下文中   
         
     8 pointer callback
     static snd_pcm_uframes_t snd_xxx_pointer(struct snd_pcm_substream *substream)
     该回调函数在PCM中间层咨询在buffer中当前硬件位置的时候被调用,位置以frames计算,范围为0到buffer_size - 1.
调用关系为:
     snd_pcm_period_elapsed --> snd_pcm_update_hw_ptr_pos ---> snd_xxx_pointer
     在中断处理函数中,snd_pcm_period_elapsed被调用,然后PCM中间层更新位置,并计算剩余空间,并唤醒睡着的线程.
    SNDRV_PCM_POS_XRUN ????
   
    9 其他的callback非强制的,我们驱动没实现他们.
      
     
  
设置好硬件操作函数后,你可以预先分配buffer,调用
snd_pcm_lib_preallocate_pages_for_all
该函数将更新子流的dma_max 以及 dma_buffer , 其中dma_buffer中的area 代表内存区域

PCM实例的释放,一般不需要,PCM中间层会自动释放其中的内存的,除非你在初始化的时候,分配了一些特殊的变量(用kmalloc),
此时在退出函数里,用kfree掉。

2.4.2. runtime
当一个PCM子流打开时,一个PCM的runtime实例被分配并赋给substream-〉runtime 。
runtime保持大多数你需要控制PCM的信息,hw_params and sw_params配置的拷贝,buffer的指针等
数据结构:snd_pcm_runtime
包括 hw  :hw_params 信息
硬件描述符(struct snd_pcm_hardware)包含了基本硬件配置的定义.首先,你将在open 回调函数里定义它.
需要说明的是:runtime实例保持着描述符的copy,不是对已经的描述符的指针,也就是说,在open 回调函数中,你可以修改runtime->hw
根据你的需要.

2.4.3 中断处理
中断处理函数在声卡驱动中的作用:更新buffer的位置并在buffer位置越过以前设置的period size时,告诉PCM中间层.调用snd_pcm_period_elapsed函数
将告诉PCM中间层.
在snd_pcm_hw_params中会对period_size进行设置.
有以下几种声卡类型产生中断的方式:
1)在周期边界产生中断
   最常用的方式
2)高频率的timer中断
   用于那些不产生中断的芯片
   我们的实现:
对于位置pcm_buf_pos ,在prepare调用的时候设为0,在中断的时候,每次会传60个bytes,每次加上60,循环.
然后调用snd_pcm_period_elapsed函数通知上层.

在中断中调用spin_lock ???? 为了多处理器吗???
阅读(2318) | 评论(1) | 转发(0) |
0

上一篇:curl 下载

下一篇:IS_ERR()宏是什么意思

给主人留下些什么吧!~~

chinaunix网友2009-12-11 10:11:37

hi, 楼主,我最近也在学习alsa,请问你这种非常系统的学习体会是从哪些resource获得? 我有种无从下手的感觉~~ 目前我正在从模仿写一个plug-in,看了你的文章,才明白plug-in跟alsa-lib一个layer的,汗,我以前一直以为plug-in作为虚拟设备直接就跟最底层(kernel)交互了,感觉如果plug-in可以实现的功能在app层面也可以实现啊 如果这样的话,plug-in的优势在哪里呢