Chinaunix首页 | 论坛 | 博客
  • 博客访问: 55133
  • 博文数量: 21
  • 博客积分: 11
  • 博客等级: 民兵
  • 技术积分: 107
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-21 14:55
文章分类
文章存档

2015年(1)

2013年(1)

2012年(19)

分类:

2012-02-21 15:50:24

原文地址:ALsa学习笔记 作者:fly123456789

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
    }
}
 关于配置,可以参考这个网站:
 

对于period_size和buffer_size,要注意,我将他们修改为1024 ,8192.在我们的平台上用dmix会出现underrun!!! 信息。

 

2 ALSA kernel
2.1 目录
    Alsa-driver包括很多在开发中的驱动,以及一些2.2,2.4 linux内核版本的支持。当这些驱动稳定后,将移入alsa-kernel中,并最终在linux kernel
  的sound目录下.
  sound

/core

    /oss

    /seq

       /oss

       /instr

/ioctl132

/include

/drivers

    /mpu401

    /op13

/i2c

    /13

/synth

    /emux

/pci

   /(cards)
    /isa

   /(cards)

/arm

/ppc

/sparc

/usb

/pcmcia/(cards)

/oss

     

core目录

这 个目录包含了中间层,ALSA的 核心驱动。

core/oss

关 于PCMmixerOSS模拟的模块保存在这个目录里面。Raw midi OSS模拟也被包含在ALSA rawmidi代码中,因为它非常小。音序器代码 被保存在core/seq/oss目 录里面

core/ioctl32

这 个目录包含32bit-ioctl64bit架构(如x86-64,ppc64,sparc64)的转换。对于32bitalpha的架构,他们是不被编译的。

core/seq

它 和它的子目录主要是关于ALSA的 音序器。它包含了音序器的core和 一些主要的音序器模块如:snd-seq-midisnd-seq-virmidi等等。它们仅仅在内核配置中 当CONFIG_SND_SEQUENCER被 设定的时候才会被编译。我们在使用的ALSA驱 动中也没有使用。

core/seq/oss

包 含了OSS音序器的模 拟的代码。

core/seq/instr

包 含了一些音序器工具层的一些模块。

include目录

这 里面放的是ALSA驱 动程序开放给用户空间,或者被其他不同目录引用的共同头文件。

Drivers目录

这 个目录包含了不同架构的系统中的不同驱动共享的文件部分。它们是硬件无关的。在子目录里面,会放一些不同组件的代码,他们是根据不同的buscpu架构实现的。

i2c目 录

这 里面包含了ALSAi2c组 件。

虽 然LINUXi2c的标准协议层,ALSA还是拥有它关于一些card的专用i2c代码,因为一些声卡仅仅需要一些简单的操作,而标准的i2cAPI函数对此显得太过复杂了。

i2c/l3

这 是ARM L3 i2c驱 动的子目录

synth目录

它 包含了synth(合 成器)的中间层模块

pci目录

它 和它的一些子目录文件负责PCI声 卡和一些PCI BUS的 上层card模块。

isa目录

它 和它的一些子目录文件是处理ISA声 卡的上层card模 块。

arm,ppc,sparc目录

这 里放置一些和芯片架构相关的一些上层的card模 块。

usb目录

这 里包含一些USB-AUDIO驱 动。在最新版本里面,已经把USB MIDI 驱 动也集成进USB-AUDIO驱 动了。

pcmcia目录

PCMCIA卡,特别是PCCcard驱动会放到这里。CardBus驱动将会放到pci目录里面,因为API函数和标准PCI卡上统一的。

oss目录

ALSA无关。

在 了解了相关的目录结构后,我们就开始来分析一下ALSA驱 动了,在ALSA驱动 中,我们所要从始至终要记住的是,我们的应用程序是不能直接访问驱动的,必须通过中间的ALSA层,因此我么所写的驱动,所提供的接口也是针对中间层的。

下 面的内容我们从两个方面来考虑ALSA驱 动,一个是驱动在操作硬件的同时,如何为中间层提供接口的,另外一个是应用程序是如何调用ALSAAPI来实现对驱动的连接从而操作硬件的。

这 了解这两个方面之前我们先来看一下ALSA的 系统组成,也就是我们所说的中间层由哪些方面组成:

n       驱 动包alsa-driver: 指内核驱动程序,包括硬件相关的代码和一些公共代码,非常庞大

n       开 发包alsa-libs: 指用户空间的函数库,提供给应用程序使用,应用程序应包括头文件asoundlib.h。 并使用共享库libasound.so

n       设 置管理工具包alsa-utils: 包含一些基于ALSA的 用于控制声卡的应用程序,如alsaconf(侦 测系统中声卡并写一个适合的ALSA配 置文件),aplay(基 于命令行的声音文件播放),arecord(基 于命令行的声音文件录制)等

n       还 包括开发包插件alsa-libplugins, 其他声音相关处理小程序包alsa-tools, 特殊音频固件支持包alsa-firmwareOSS接口兼容模拟层工具alsa-oss7个子项目,其中只有驱动包是必须的

其 实对于驱动包,我们用的是内核自带的,所以我们也没有安装。这个我们在前面已经详细讲解过,不多说了。

     
   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 ???? 为了多处理器吗???

 

 

PCM中的file_operation :
   当调用snd_pcm_new时,
       snd_pcm_new ---> snd_pcm_dev_register --> snd_register_device 此函数更新snd_minor结构,
      
    而在 alsa_sound_init中,register_chrdev(major , "alsa",&snd_fops);当一个alsa主设备打开时,会调用snd_fops中的open函数,
    也就是snd_open( 在sound.c中),而snd_open会根据snd_minor替换其file_ops,如果minor是PCM设备,将用PCM的 file_operation
    (snd_pcm_new中更新了)

阅读(1010) | 评论(0) | 转发(0) |
0

上一篇:Alsa 小结

下一篇:如何安装ALSA驱动

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