Linux音频设备驱动-2【转】


Linux音频设备驱动-2【转】
2011年01月20日
  转:http://hi.baidu.com/geyangshun/blog/item/2df32a382 e6b2e22b9998f39.html 17.4.2 PCM设备
  每个声卡最多可以有4个PCM实例,1个PCM实例对应1个设备文件。PCM实例由PCM放音和录音流组成,而每个PCM流又由1个或多个PCM子流组成。有的声卡支持多重放音功能,例如,emu10k1包含1个32个立体声子流的PCM放音设备。
  1、PCM实例构造
  int snd_pcm_new(struct snd_card *card, char *id, int device,
  int playback_count, int capture_count, struct snd_pcm ** rpcm);
  第 1个参数是card指针,第2个是标识字符串,第3个是PCM设备索引(0表示第1个PCM设备),第4和第5个分别为放音和录音设备的子流数。当存在多个子流时,需要恰当地处理open()、close()和其它函数。在每个回调函数中,可以通过snd_pcm_substream的number成员得知目前操作的究竟是哪个子流,如:
  struct snd_pcm_substream *substream;
  int index = substream->number;
  一种习惯的做法是在驱动中定义1个PCM"构造函数",负责PCM实例的创建,如代码清单17.7。
  代码清单17.7 PCM设备"构造函数"
  1 static int __devinit snd_xxxchip_new_pcm(struct xxxchip *chip)
  2 {
  3    struct snd_pcm *pcm;
  4    int err;
  5    //创建PCM实例
  6    if ((err = snd_pcm_new(chip->card, "xxx Chip", 0, 1, 1, &pcm)) private_data = chip; //置pcm->private_data为芯片特定数据
  9    strcpy(pcm->name, "xxx Chip");
  10   chip->pcm = pcm;
  11   ...
  12   return 0;
  13 }
  2、设置PCM操作
  void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops);
  第1个参数是snd_pcm的指针,第2个参数是SNDRV_PCM_STREAM_PLAYBACK或SNDRV_PCM_STREAM_CAPTURE,而第3个参数是PCM操作结构体snd_pcm_ops,这个结构体的定义如代码清单17.8。
  代码清单17.8 snd_pcm_ops结构体
  1 struct snd_pcm_ops
  2 {
  3   int (*open)(struct snd_pcm_substream *substream);//打开
  4   int (*close)(struct snd_pcm_substream *substream);//关闭
  5   int (*ioctl)(struct snd_pcm_substream * substream,
  6         unsigned int cmd, void *arg);//io控制
  7   int (*hw_params)(struct snd_pcm_substream *substream,
  8      struct snd_pcm_hw_params *params);//硬件参数
  9   int (*hw_free)(struct snd_pcm_substream *substream); //资源释放
  10 int (*prepare)(struct snd_pcm_substream *substream);//准备
  11 //在PCM被开始、停止或暂停时调用
  12 int (*trigger)(struct snd_pcm_substream *substream, int cmd);
  13 snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);// 当前缓冲区的硬件位置
  14 //缓冲区拷贝
  15 int (*copy)(struct snd_pcm_substream *substream, int channel,
  16       snd_pcm_uframes_t pos,
  17       void __user *buf, snd_pcm_uframes_t count);
  18 int (*silence)(struct snd_pcm_substream *substream, int channel,
  19          snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
  20 struct page *(*page)(struct snd_pcm_substream *substream,
  21         unsigned long offset);
  22 int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
  23 int (*ack)(struct snd_pcm_substream *substream);
  24 };
  snd_pcm_ops中的所有操作都需事先通过snd_pcm_substream_chip()获得xxxchip指针,例如:
  int xxx()
  {
  struct xxxchip *chip = snd_pcm_substream_chip(substream);
  ...
  }
  当1个PCM子流被打开时,snd_pcm_ops中的open()函数将被调用,在这个函数中,至少需要初始化runtime->hw字段,代码清单17.9给出了open()函数的范例。
  代码清单17.9 snd_pcm_ops结构体中open()函数
  1 static int snd_xxx_open(struct snd_pcm_substream *substream)
  2 {
  3    //从子流获得xxxchip指针
  4    struct xxxchip *chip = snd_pcm_substream_chip(substream);    
  5    //获得PCM运行时信息指针
  6    struct snd_pcm_runtime *runtime = substream->runtime;
  7    ...
  8    //初始化runtime->hw
  9    runtime->hw = snd_xxxchip_playback_hw;
  10   return 0;
  11 }
  上述代码中的snd_xxxchip_playback_hw是预先定义的硬件描述。在open()函数中,可以分配1段私有数据。如果硬件配置需要更多的限制,也需设置硬件限制。
  当PCM子流被关闭时,close()函数将被调用。如果open()函数中分配了私有数据,则在close()函数中应该释放substream的私有数据,代码清单17.10给出了close()函数的范例。
  代码清单17.10 snd_pcm_ops结构体中close()函数
  1 static int snd_xxx_close(struct snd_pcm_substream *substream)
  2 {
  3   //释放子流私有数据
  4   kfree(substream->runtime->private_data);
  5   //...
  6 }
  驱动中通常可以给snd_pcm_ops的ioctl()成员函数传递通用的snd_pcm_lib_ioctl()函数。
  snd_pcm_ops的hw_params()成员函数将在应用程序设置硬件参数(PCM子流的周期大小、缓冲区大小和格式等)的时候被调用,它的形式如下:
  static int snd_xxx_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *hw_params);
  在这个函数中,将完成大量硬件设置,甚至包括缓冲区分配,这时可调用如下辅助函数:
  snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
  仅当DMA缓冲区已被预先分配的情况下,上述调用才可成立。
  与hw_params()对应的函数是hw_free(),它释放由hw_params()分配的资源,例如,通过如下调用释放snd_pcm_lib_malloc_pages()缓冲区:
  snd_pcm_lib_free_pages(substream);
  当 PCM被"准备"时,prepare()函数将被调用,在其中可以设置采样率、格式等。prepare()函数与hw_params()函数的不同在于对 prepare()的调用发生在snd_pcm_prepare()每次被调用的时候。prepare()的形式如下:
  static int snd_xxx_prepare(struct snd_pcm_substream *substream);
  trigger()成员函数在PCM被开始、停止或暂停时调用,函数的形式如下:
  static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd);
  cmd 参数定义了具体的行为,在trigger()成员函数中至少要处理SNDRV_PCM_TRIGGER_START和 SNDRV_PCM_TRIGGER_STOP命令,如果PCM支持暂停,还应处理SNDRV_PCM_TRIGGER_PAUSE_PUSH和 SNDRV_PCM_TRIGGER_PAUSE_RELEASE命令。如果设备支持挂起/恢复,当能量管理状态发生变化时将处理 SNDRV_PCM_TRIGGER_SUSPEND和SNDRV_PCM_TRIGGER_RESUME这2个命令。注意trigger()函数是原子的,中途不能睡眠。代码清单17.11给出了1个trigger()函数的范例。
  代码清单17.11 snd_pcm_ops结构体中trigger()函数
  1 static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd)
  2 {
  3    switch (cmd)
  4    {
  5      case SNDRV_PCM_TRIGGER_START:
  6        // 开启PCM引擎
  7        break;
  8      case SNDRV_PCM_TRIGGER_STOP:
  9        // 停止PCM引擎
  10       break;
  11     ...//其它命令
  12     default:
  13       return - EINVAL;
  14   }
  15 }
  pointer()函数用于PCM中间层查询目前缓冲区的硬件位置,该函数以帧的形式返回0~buffer_size   1的位置(ALSA 0.5.x中为字节形式),此函数也是原子的。
  copy() 和silence()函数一般可以省略,但是,当硬件缓冲区不处于常规内存中时需要。例如,一些设备有自己的不能被映射的硬件缓冲区,这种情况下,我们不得不将数据从内存缓冲区拷贝到硬件缓冲区。例外,当内存缓冲区在物理和虚拟地址上都不连续时,这2个函数也必须被实现。
  3、分配缓冲区
  分配缓冲区的最简单方法是调用如下函数:
  int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
  int type, void *data, size_t size, size_t max);
  type 参数是缓冲区的类型,包含SNDRV_DMA_TYPE_UNKNOWN(未知)、SNDRV_DMA_TYPE_CONTINUOUS(连续的非DMA 内存)、SNDRV_DMA_TYPE_DEV (连续的通用设备),SNDRV_DMA_TYPE_DEV_SG(通用设备SG-buffer)和 SNDRV_DMA_TYPE_SBUS(连续的SBUS)。如下代码将分配64KB的缓冲区:
  snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
  snd_dma_pci_data(chip->pci),64*1024, 64*1024);
  4、设置标志
  在构造PCM实例、设置操作集并分配缓冲区之后,如果有需要,应设置PCM的信息标志,例如,如果PCM设备只支持半双工,则这样定义标志:
  pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
  5、PCM实例析构
  PCM 实例的"析构函数"并非是必须的,因为PCM实例会被PCM中间层代码自动释放,如果驱动中分配了一些特别的内存空间,则必须定义"析构函数",代码清单 17.x给出了PCM"析构函数"与对应的"构造函数","析构函数"会释放"构造函数"中创建的xxx_private_pcm_data。
  代码清单17.12 PCM设备"析构函数"
  1 static void xxxchip_pcm_free(struct snd_pcm *pcm)
  2 {
  3    /* 从pcm实例得到chip */
  4    struct xxxchip *chip = snd_pcm_chip(pcm);
  5    /* 释放自定义用途的内存 */
  6    kfree(chip->xxx_private_pcm_data);
  7    ...
  8 }
  9 
  10 static int __devinit snd_xxxchip_new_pcm(struct xxxchip *chip)
  11 {
  12   struct snd_pcm *pcm;
  13   ...
  14   /* 分配自定义用途的内存 */
  15   chip->xxx_private_pcm_data = kmalloc(...);
  16   pcm->private_data = chip;
  17   /* 设置"析构函数" */
  18   pcm->private_free = xxxchip_pcm_free;
  19   ...
  20 }
  上述代码第4行的snd_pcm_chip()从PCM实例指针获得xxxchip指针,实际上它就是返回第16行给PCM实例赋予的xxxchip指针。
  6、PCM信息运行时指针
  当 PCM子流被打开后,PCM运行时实例(定义为结构体snd_pcm_runtime,如代码清单17.13)将被分配给这个子流,这个指针通过 substream->runtime获得。运行时指针包含各种各样的信息:hw_params及sw_params配置的拷贝、缓冲区指针、 mmap记录、自旋锁等,几乎要控制PCM的所有信息均能从中取得。
  代码清单17.13 snd_pcm_runtime结构体
  1 struct snd_pcm_runtime
  2 {
  3    /* 状态 */
  4    struct snd_pcm_substream *trigger_master;
  5    snd_timestamp_t trigger_tstamp; /* 触发时间戳 */
  6    int overrange;
  7    snd_pcm_uframes_t avail_max;
  8    snd_pcm_uframes_t hw_ptr_base; /* 缓冲区复位时的位置 */
  9    snd_pcm_uframes_t hw_ptr_interrupt; /* 中断时的位置*/
  10   /* 硬件参数 */
  11   snd_pcm_access_t access; /* 存取模式 */
  12   snd_pcm_format_t format; /* SNDRV_PCM_FORMAT_* */
  13   snd_pcm_subformat_t subformat; /* 子格式 */
  14   unsigned int rate; /* rate in Hz */
  15   unsigned int channels; /* 通道 */
  16   snd_pcm_uframes_t period_size; /* 周期大小 */
  17   unsigned int periods; /* 周期数 */
  18   snd_pcm_uframes_t buffer_size; /* 缓冲区大小 */
  19   unsigned int tick_time; /* tick time */
  20   snd_pcm_uframes_t min_align; /* 格式对应的最小对齐*/
  21   size_t byte_align;
  22   unsigned int frame_bits;
  23   unsigned int sample_bits;
  24   unsigned int info;
  25   unsigned int rate_num;
  26   unsigned int rate_den;
  27   /* 软件参数 */
  28   struct timespec tstamp_mode; /* mmap时间戳被更新*/
  29   unsigned int period_step;
  30   unsigned int sleep_min; /* 睡眠的最小节拍 */
  31   snd_pcm_uframes_t xfer_align;
  32   snd_pcm_uframes_t start_threshold;
  33   snd_pcm_uframes_t stop_threshold;
  34   snd_pcm_uframes_t silence_threshold; /* Silence填充阈值 */
  35   snd_pcm_uframes_t silence_size; /* Silence填充大小 */
  36   snd_pcm_uframes_t boundary;
  37   snd_pcm_uframes_t silenced_start;
  38   snd_pcm_uframes_t silenced_size;
  39   snd_pcm_sync_id_t sync; /* 硬件同步ID */
  40   /* mmap */
  41   volatile struct snd_pcm_mmap_status *status;
  42   volatile struct snd_pcm_mmap_control *control;
  43   atomic_t mmap_count;
  44   /* 锁/调度 */
  45   spinlock_t lock;
  46   wait_queue_head_t sleep;
  47   struct timer_list tick_timer;
  48   struct fasync_struct *fasync;
  49   /* 私有段 */
  50   void *private_data;
  51   void(*private_free)(struct snd_pcm_runtime *runtime);
  52   /* 硬件描述 */
  53   struct snd_pcm_hardware hw;
  54   struct snd_pcm_hw_constraints hw_constraints;
  55   /* 中断回调函数 */
  56   void(*transfer_ack_begin)(struct snd_pcm_substream*substream);
  57   void(*transfer_ack_end)(struct snd_pcm_substream *substream);
  58   /* 定时器 */
  59   unsigned int timer_resolution; /* timer resolution */
  60   /* DMA */
  61   unsigned char *dma_area; /* DMA区域*/
  62   dma_addr_t dma_addr; /* 总线物理地址*/
  64   size_t dma_bytes; /* DMA区域大小 */
  65   struct snd_dma_buffer *dma_buffer_p; /* 被分配的缓冲区 */
  66   #if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
  67     /* OSS信息 */
  68     struct snd_pcm_oss_runtime oss;
  69   #endif
  70 };
  snd_pcm_runtime中的大多数记录对被声卡驱动操作集中的函数是只读的,仅仅PCM中间层可更新或修改这些信息,但是硬件描述、中断回调函数、DMA缓冲区信息和私有数据是例外的。
  下面解释snd_pcm_runtime结构体中的几个重要成员:
  ?? 硬件描述
  硬件描述(snd_pcm_hardware结构体)包含了基本硬件配置的定义,需要在open()函数中赋值。runtime实例保存的是硬件描述的拷贝而非指针,这意味着在open()函数中可以修改被拷贝的描述(runtime->hw),例如:
  struct snd_pcm_runtime *runtime = substream->runtime;
  ...
  runtime->hw = snd_xxchip_playback_hw; /* "大众"硬件描述 */
  /* 特定的硬件描述 */
  if (chip->model == VERY_OLD_ONE)
  runtime->hw.channels_max = 1;
  snd_pcm_hardware结构体的定义如代码清单17.14。
  代码清单17.14 snd_pcm_hardware结构体
  1 struct snd_pcm_hardware
  2 {
  3   unsigned int info; /* SNDRV_PCM_INFO_* /
  4   u64 formats;   /* SNDRV_PCM_FMTBIT_* */
  5   unsigned int rates; /* SNDRV_PCM_RATE_* */
  6   unsigned int rate_min; /* 最小采样率 */
  7   unsigned int rate_max; /* 最大采样率 */
  8   unsigned int channels_min; /* 最小的通道数 */
  9   unsigned int channels_max; /* 最大的通道数 */
  10 size_t buffer_bytes_max; /* 最大缓冲区大小 */
  11 size_t period_bytes_min; /* 最小周期大小 */
  12 size_t period_bytes_max; /* 最大奏曲大小 */
  13 unsigned int periods_min; /* 最小周期数 */
  14 unsigned int periods_max; /* 最大周期数 */
  15 size_t fifo_size; /* FIFO字节数 */
  16 };
  snd_pcm_hardware 结构体中的info字段标识PCM设备的类型和能力,形式为SNDRV_PCM_INFO_XXX。info字段至少需要定义是否支持mmap,当支持时,应设置SNDRV_PCM_INFO_MMAP标志;当硬件支持interleaved或non-interleaved格式,应设置 SNDRV_PCM_INFO_INTERLEAVED或SNDRV_PCM_INFO_NONINTERLEAVED标志,如果都支持,则二者都可设置;MMAP_VALID和BLOCK_TRANSFER标志针对OSS mmap,只有mmap被真正支持时,才可设置MMAP_VALID;SNDRV_PCM_INFO_PAUSE意味着设备可支持暂停操作,而 SNDRV_PCM_INFO_RESUME意味着设备可支持挂起/恢复操作;当PCM子流能被同步,如同步放音和录音流的start/stop,可设置 SNDRV_PCM_INFO_SYNC_START标志。
  formats包含PCM设备支持的格式,形式为SNDRV_PCM_FMTBIT_XXX,如果设备支持多种模式,应将各种模式标志进行"或"操作。
  rates包含了PCM设备支持的采样率,形式如SNDRV_PCM_RATE_XXX,如果支持连续的采样率,则传递CONTINUOUS。
  rate_min和rate_max分别定义了最大和最小的采样率,注意要与rates字段相符。
  channel_min和channel_max定义了最大和最小的通道数量。
  buffer_bytes_max定义最大的缓冲区大小,注意没有buffer_bytes_min字段,这是因为它可以通过最小的周期大小和最小的周期数量计算出来。
  period信息与OSS中的fragment对应,定义了PCM中断产生的周期。更小的周期大小意味着更多的中断,在录音时,周期大小定义了输入延迟,在放音时,整个缓冲区大小对应着输出延迟。
  PCM可被应用程序通过alsa-lib发送hw_params来配置,配置信息将保存在运行时实例中。对缓冲区和周期大小的配置以帧形式存储,而frames_to_bytes()和 bytes_to_frames()可完成帧和字节的转换,如:
  period_bytes = frames_to_bytes(runtime, runtime->period_size);
  ?? DMA缓冲区信息
  包含dma_area(逻辑地址)、dma_addr(物理地址)、dma_bytes(缓冲区大小)和dma_private(被ALSA DMA分配器使用)。可以由snd_pcm_lib_malloc_pages()实现,ALSA中间层会设置DMA缓冲区信息的相关字段,这种情况下,驱动中不能再写这些信息,只能读取。也就是说,如果使用标准的缓冲区分配函数snd_pcm_lib_malloc_pages()分配缓冲区,则我们不需要自己维护DMA缓冲区信息。如果缓冲区由自己分配,则需在hw_params()函数中管理缓冲区信息,至少需管理dma_bytes和 dma_addr,如果支持mmap,则必须管理dma_area,对dma_private的管理视情况而定。
  ?? 运行状态
  通过 runtime->status可以获得运行状态,它是snd_pcm_mmap_status结构体的指针,例如,通过 runtime->status->hw_ptr可以获得目前的DMA硬件指针。此外,通过runtime->control可以获得 DMA应用指针,它指向snd_pcm_mmap_control结构体指针。
  ?? 私有数据
  驱动中可以为子流分配一段内存并赋值给runtime->private_data,注意不要与pcm->private_data混淆,后者一般指向xxxchip,而前者是在PCM设备的open()函数中分配的动态数据,如:
  static int snd_xxx_open(struct snd_pcm_substream *substream)
  {
  struct xxx_pcm_data *data;
  ....
  data = kmalloc(sizeof(*data), GFP_KERNEL);
  substream->runtime->private_data = data; //赋值runtime->private_data
  ....
  } 
  ?? 中断回调函数:
  transfer_ack_begin()和transfer_ack_end()函数分别在snd_pcm_period_elapsed()的开始和结束时被调用。
  根据以上分析,代码清单17.15给出了一个完整的PCM设备接口模板。
  代码清单17.15 PCM设备接口模板
  1   #include 
  2   ....
  3   /* 放音设备硬件定义 */
  4   static struct snd_pcm_hardware snd_xxxchip_playback_hw =
  5   {
  6     .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
  7       SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
  8     .formats = SNDRV_PCM_FMTBIT_S16_LE,
  9     .rates = SNDRV_PCM_RATE_8000_48000,
  10    .rate_min = 8000,
  11    .rate_max = 48000,
  12    .channels_min = 2,
  13    .channels_max = 2,
  14    .buffer_bytes_max = 32768,
  15    .period_bytes_min = 4096,
  16    .period_bytes_max = 32768,
  17    .periods_min = 1,
  18    .periods_max = 1024,
  19 };
  20 
  21 /* 录音设备硬件定义 */
  22 static struct snd_pcm_hardware snd_xxxchip_capture_hw =
  23 {
  24    .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
  25      SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
  26    .formats = SNDRV_PCM_FMTBIT_S16_LE,
  27    .rates = SNDRV_PCM_RATE_8000_48000,
  28    .rate_min = 8000,
  29    .rate_max = 48000,
  30    .channels_min = 2,
  31    .channels_max = 2,
  32    .buffer_bytes_max = 32768,
  33    .period_bytes_min = 4096,
  34    .period_bytes_max = 32768,
  35    .periods_min = 1,
  36    .periods_max = 1024,
  37 };
  38 
  39 /* 放音:打开函数 */
  40 static int snd_xxxchip_playback_open(struct snd_pcm_substream*substream)
  41 {
  42    struct xxxchip *chip = snd_pcm_substream_chip(substream);
  43    struct snd_pcm_runtime *runtime = substream->runtime;
  44    runtime->hw = snd_xxxchip_playback_hw;
  45    ... // 硬件初始化代码
  46    return 0;
  47 }
  48 
  49 /* 放音:关闭函数 */
  50 static int snd_xxxchip_playback_close(struct snd_pcm_substream*substream)
  51 {
  52    struct xxxchip *chip = snd_pcm_substream_chip(substream);
  53    // 硬件相关的代码
  54    return 0;
  55 }
  56 
  57 /* 录音:打开函数 */
  58 static int snd_xxxchip_capture_open(struct snd_pcm_substream*substream)
  59 {
  60    struct xxxchip *chip = snd_pcm_substream_chip(substream);
  61    struct snd_pcm_runtime *runtime = substream->runtime;
  62    runtime->hw = snd_xxxchip_capture_hw;
  63    ... // 硬件初始化代码
  64    return 0;
  65 }
  66 
  67 /* 录音:关闭函数 */
  68 static int snd_xxxchip_capture_close(struct snd_pcm_substream*substream)
  69 {
  70    struct xxxchip *chip = snd_pcm_substream_chip(substream);
  71    ... // 硬件相关的代码
  72    return 0;
  73 }
  74 /* hw_params函数 */
  75 static int snd_xxxchip_pcm_hw_params(struct snd_pcm_substream*substream, struct
  76    snd_pcm_hw_params *hw_params)
  77 {
  78    return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
  79 }
  80 /* hw_free函数 */
  81 static int snd_xxxchip_pcm_hw_free(struct snd_pcm_substream*substream)
  82 {
  83    return snd_pcm_lib_free_pages(substream);
  84 }
  85 /* prepare函数 */
  86 static int snd_xxxchip_pcm_prepare(struct snd_pcm_substream*substream)
  87 {
  88    struct xxxchip *chip = snd_pcm_substream_chip(substream);
  89    struct snd_pcm_runtime *runtime = substream->runtime;
  90    /* 根据目前的配置信息设置硬件
  91     * 例如:
  92     */
  93    xxxchip_set_sample_format(chip, runtime->format);
  94    xxxchip_set_sample_rate(chip, runtime->rate);
  95    xxxchip_set_channels(chip, runtime->channels);
  96    xxxchip_set_dma_setup(chip, runtime->dma_addr, chip->buffer_size, chip
  97      ->period_size);
  98    return 0;
  99 }
  100 /* trigger函数 */
  101 static int snd_xxxchip_pcm_trigger(struct snd_pcm_substream*substream, int cmd)
  102 {
  103   switch (cmd)
  104   {
  105     case SNDRV_PCM_TRIGGER_START:
  106       // do something to start the PCM engine
  107       break;
  108     case SNDRV_PCM_TRIGGER_STOP:
  109       // do something to stop the PCM engine
  110       break;
  111     default:
  112       return - EINVAL;
  113   }
  114 }
  115
  116 /* pointer函数 */
  117 static snd_pcm_uframes_t snd_xxxchip_pcm_pointer(struct snd_pcm_substream
  118   *substream)
  119 {
  120   struct xxxchip *chip = snd_pcm_substream_chip(substream);
  121   unsigned int current_ptr;
  122   /*获得当前的硬件指针*/
  123   current_ptr = xxxchip_get_hw_pointer(chip);
  124   return current_ptr;
  125 }
  126 /* 放音设备操作集 */
  127 static struct snd_pcm_ops snd_xxxchip_playback_ops =
  128 {
  129   .open = snd_xxxchip_playback_open,
  130   .close = snd_xxxchip_playback_close,
  131   .ioctl = snd_pcm_lib_ioctl,
  132   .hw_params = snd_xxxchip_pcm_hw_params,
  133   .hw_free = snd_xxxchip_pcm_hw_free,
  134   .prepare = snd_xxxchip_pcm_prepare,
  135   .trigger = snd_xxxchip_pcm_trigger,
  136   .pointer = snd_xxxchip_pcm_pointer,
  137 };
  138 /* 录音设备操作集 */
  139 static struct snd_pcm_ops snd_xxxchip_capture_ops =
  140 {
  141   .open = snd_xxxchip_capture_open,
  142   .close = snd_xxxchip_capture_close,
  143   .ioctl = snd_pcm_lib_ioctl,
  144   .hw_params = snd_xxxchip_pcm_hw_params,
  145   .hw_free = snd_xxxchip_pcm_hw_free,
  146   .prepare = snd_xxxchip_pcm_prepare,
  147   .trigger = snd_xxxchip_pcm_trigger,
  148   .pointer = snd_xxxchip_pcm_pointer,
  149 };
  150
  151 /* 创建1个PCM设备 */
  152 static int __devinit snd_xxxchip_new_pcm(struct xxxchip *chip)
  153 {
  154   struct snd_pcm *pcm;
  155   int err;
  156   if ((err = snd_pcm_new(chip->card, "xxx Chip", 0, 1, 1, &pcm)) private_data = chip;
  159   strcpy(pcm->name, "xxx Chip");
  160   chip->pcm = pcm;
  161   /* 设置操作集 */
  162   snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_xxxchip_playback_ops);
  163   snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_xxxchip_capture_ops);
  164   /* 分配缓冲区 */
  165   snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
  166     snd_dma_pci_data(chip - > pci), 64 *1024, 64 *1024);
  167   return 0;
  168 }
  17.4.3控制接口
  1、control
  控制接口对于许多开关(switch)和调节器(slider)而言应用相当广泛,它能从用户空间被存取。control的最主要用途是mixer,所有的 mixer元素基于control内核API实现,在ALSA中,control用snd_kcontrol结构体描述。
  ALSA有一个定义很好的AC97控制模块,对于仅支持AC97的芯片而言,不必实现本节的内容。
  创建1个新的control至少需要实现snd_kcontrol_new中的info()、get()和put()这3个成员函数,snd_kcontrol_new结构体的定义如代码清单17.16。
  代码清单17.16 snd_kcontrol_new结构体
  1 struct snd_kcontrol_new
  2 {
  3    snd_ctl_elem_iface_t iface; /*接口ID,SNDRV_CTL_ELEM_IFACE_XXX */
  4    unsigned int device; /* 设备号 */
  5    unsigned int subdevice; /* 子流(子设备)号 */
  6    unsigned char *name; /* 名称(ASCII格式) */
  7    unsigned int index; /* 索引 */
  8    unsigned int access; /* 访问权限 */
  9    unsigned int count; /* 享用元素的数量 */
  10   snd_kcontrol_info_t *info;
  11   snd_kcontrol_get_t *get;
  12   snd_kcontrol_put_t *put;
  13   unsigned long private_value;
  14 };
  iface 字段定义了control的类型,形式为SNDRV_CTL_ELEM_IFACE_XXX,通常是MIXER,对于不属于mixer的全局控制,使用 CARD。如果关联于某类设备,则使用HWDEP、 PCM、RAWMIDI、TIMER或SEQUENCER。
  name是名称标识字符串,control的名称非常重要,因为control的作用由名称来区分。对于名称相同的control,则使用index区分。name定义的标准是 "SOURCE DIRECTION FUNCTION"即"源 方向功能",SOURCE定义了control的源,如"Master"、"PCM"、"CD"和"Line",方向则为"Playback"、 "Capture"、"Bypass Playback"或"Bypass Capture",如果方向省略,意味着playback和capture双向,第3个参数可以是"Switch"、"Volume"和"Route" 等。
  "SOURCE DIRECTION FUNCTION"格式的名称例子如Master Capture Switch、PCM Playback Volume。
  下面几种control的命名不采用"SOURCE DIRECTION FUNCTION"格式,属于例外:
  ?? 全局控制
  "Capture Source"、 "Capture Switch"和"Capture Volume"用于全局录音源、输入开关和录音音量控制;"Playback Switch"、"Playback Volume"用于全局输出开关和音量控制。
  ?? 音调控制
  音调控制名称的形式为"Tone Control   XXX",例如"Tone Control   Switch"、"Tone Control   Bas"和"Tone Control   Center"。
  ?? 3D控制
  3D控制名称的形式为"3D Control   XXX",例如"3D Control   Switch"、"3D Control   Center"和"3D Control   Space"。
  ?? 麦克风增益(Mic boost)
  麦克风增益被设置为"Mic Boost"或"Mic Boost (6dB)"。
  snd_kcontrol_new 结构体的access字段是访问控制权限,形式如SNDRV_CTL_ELEM_ACCESS_XXX。 SNDRV_CTL_ELEM_ACCESS_READ意味着只读,这时put()函数不必实现;SNDRV_CTL_ELEM_ACCESS_WRITE意味着只写,这时get()函数不必实现。若control值频繁变化,则需定义 VOLATILE标志。当control处于非激活状态时,应设置INACTIVE标志。
  private_value字段包含1个长整型值,可以通过它给info()、get()和put()函数传递参数。
  2、info()函数
  snd_kcontrol_new结构体中的info()函数用于获得该control的详细信息,该函数必须填充传递给它的第2个参数snd_ctl_elem_info结构体,info()函数的形式如下:
  static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo);
  snd_ctl_elem_info结构体的定义如代码清单17.17。
  代码清单17.17 snd_ctl_elem_info结构体
  1 struct snd_ctl_elem_info
  2 {
  3    struct snd_ctl_elem_id id; /* W: 元素ID */
  4    snd_ctl_elem_type_t type; /* R: 值类型 - SNDRV_CTL_ELEM_TYPE_* */
  5    unsigned int access; /* R: 值访问权限(位掩码) - SNDRV_CTL_ELEM_ACCESS_* */
  6    unsigned int count; /* 值的计数 */
  7    pid_t owner; /* 该control的拥有者PID */
  8    union
  9    {
  10     struct
  11     {
  12       long min; /* R: 最小值 */
  13       long max; /* R: 最大值 */
  14       long step; /* R: 值步进 (0 可变的) */
  15     } integer;
  16     struct
  17     {
  18       long long min; /* R: 最小值 */
  19       long long max; /* R: 最大值 */
  20       long long step; /* R: 值步进 (0 可变的) */
  21     } integer64;
  22     struct
  23     {
  24       unsigned int items; /* R: 项目数 */
  25       unsigned int item; /* W: 项目号 */
  26       char name[64]; /* R: 值名称 */
  27     } enumerated; /* 枚举 */
  28     unsigned char reserved[128];
  29   }
  30   value;
  31   union
  32   {
  33     unsigned short d[4];
  34     unsigned short *d_ptr;
  35   } dimen;
  36   unsigned char reserved[64-4 * sizeof(unsigned short)];
  37 };
  snd_ctl_elem_info 结构体的type字段定义了control的类型,包括BOOLEAN、INTEGER、ENUMERATED、BYTES、IEC958和 INTEGER64。count字段定义了这个control中包含的元素的数量,例如1个立体声音量control的count = 2。value是1个联合体,其所存储的值的具体类型依赖于type。代码清单17.18给出了1个info()函数填充 snd_ctl_elem_info结构体的范例。
  代码清单17.18 snd_ctl_elem_info结构体中info()函数范例
  1 static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct
  2   snd_ctl_elem_info *uinfo)
  3 {
  4   uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;//类型为BOOLEAN
  5   uinfo->count = 1;//数量为1
  6   uinfo->value.integer.min = 0;//最小值为0
  7   uinfo->value.integer.max = 1;//最大值为1
  8   return 0;
  9 }
  枚举类型和其它类型略有不同,对枚举类型,应为目前项目索引设置名称字符串,如代码清单17.19。
  代码清单17.19 填充snd_ctl_elem_info结构体中枚举类型值
  1 static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct
  2    snd_ctl_elem_info *uinfo)
  3 {
  4    //值名称字符串
  5    static char *texts[4] =
  6    {
  7      "First", "Second", "Third", "Fourth"
  8    };
  9    uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;//枚举类型
  10   uinfo->count = 1;//数量为1
  11   uinfo->value.enumerated.items = 4;//项目数量为1
  12   //超过3的项目号改为3
  13   if (uinfo->value.enumerated.item > 3)
  14     uinfo->value.enumerated.item = 3;
  15   //为目前项目索引拷贝名称字符串
  16   strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
  17   return 0;
  18 }
  3、get()函数
  get()函数用于得到control的目前值并返回用户空间,代码清单17.20给出了get()函数的范例。
  代码清单17.20 snd_ctl_elem_info结构体中get()函数范例
  1 static int snd_xxxctl_get(struct snd_kcontrol *kcontrol, struct
  2   snd_ctl_elem_value *ucontrol)
  3 {
  4   //从snd_kcontrol获得xxxchip指针
  5   struct xxxchip *chip = snd_kcontrol_chip(kcontrol);
  6   //从xxxchip获得值并写入snd_ctl_elem_value
  7   ucontrol->value.integer.value[0] = get_some_value(chip);
  8   return 0;
  9 }
  get() 函数的第2个参数的类型为snd_ctl_elem_value,其定义如代码清单10.21。snd_ctl_elem_value结构体的内部也包含 1个由integer、integer64、enumerated等组成的值联合体,它的具体类型依赖于control的类型和info()函数。
  代码清单17.21 snd_ctl_elem_value结构体
  1 struct snd_ctl_elem_value
  2 {
  3    struct snd_ctl_elem_id id; /* W: 元素ID */
  4    unsigned int indirect: 1; /* W: 使用间接指针(xxx_ptr成员) */
  5    //值联合体
  6    union
  7    {
  8      union
  9      {
  10       long value[128];
  11       long *value_ptr;
  12     } integer;
  13     union
  14     {
  15       long long value[64];
  16       long long *value_ptr;
  17     } integer64;
  18     union
  19     {
  20       unsigned int item[128];
  21       unsigned int *item_ptr;
  22     } enumerated;
  23     union
  24     {
  25       unsigned char data[512];
  26       unsigned char *data_ptr;
  27     } bytes;
  28     struct snd_aes_iec958 iec958;
  29   }
  30   value; /* 只读 */
  31   struct timespec tstamp;
  32   unsigned char reserved[128-sizeof(struct timespec)];
  33 };

猜你喜欢

转载自nng13nng.iteye.com/blog/1362788