第17章Linux 音频设备驱动之Linux ALSA 音频设备驱动(一)

时间:2024-03-30 09:52:51

17.4 Linux ALSA 音频设备驱动

17.4.1 ALSA 的组成

    虽然 OSS 已经非常成熟,但OSS是一个没有完全开放源代码的商业产品,而且目前基本上在 Linux mainline 中失去了更新。 ALSA (Advanced Linux Sound Architecture)恰好弥补这一空白,ALSA符合 GPL,是在 Linux 下进行音频编程时另一种可供选择的声卡驱动体系结构。ALSA 除了像 OSS 提供一组内核驱动程序模块之外,还专门为简化应用程序的编写提供相应的函数库,与 OSS 提供的基于 ioctl 的原始编程接口相比,ALSA 函

数库使用起来更加方便。ALSA 的主要特点如下。

1)支持多种声卡设备

2)模块化的内核驱动程序

3)支持 SMP 和多线程

4)提供应用开发函数库(alsa-lib)以简化应用程序开发

5)支持 OSS API,兼容 OSS 应用程序

    ALSA 具友好的编程接口,并且完全兼容OSS,对应用程序员来讲是一个更佳的选择。ALSA 系统包括驱动包 alsa-driver、开发包 alsa-libs、开发包插件 alsa-libplugins、设置管理工具包 alsa-utils、其他声音相关处理小程序包 alsa-tools、特殊音频固件支持包 alsa- firmware、OSS 接口兼容模拟层工具 alsa-oss 共 7 个子项目,其中只有驱动包是必需的。

    alsa-driver 指内核驱动程序,包括硬件相关的代码和一些公共代码,非常庞大,代码总量达数十万行;alsa-libs 指用户空间的函数库,提供给应用程序使用,应用程序应包含头文件asoundlib.h,并使用共享库 libasound.so;alsa-utils 包含一些基于 ALSA 的用于控制声卡的应用程序,如 alsaconf(侦测系统中声卡并写一个适合的 ALSA 配置文件)、alsactl(控制 ALSA 声卡驱动的高级设置)、alsamixer (基于窗口菜单 ncurses 的混音器程序)、amidi (用于读写 ALSA RawMIDI)、amixer(ALSA 声卡混音器的命令行控制)、aplay(基于命令行的声音文件播放)、arecord(基于命令行的声音文件录制)等。

目前 ALSA 内核提供给用户空间的接口有:

信息接口(Information Interface,/proc/asound

控制接口(Control Interface,/dev/snd/controlCX

混音器接口(Mixer Interface,/dev/snd/mixerCXDX

PCM 接口(PCM Interface,/dev/snd/pcmCXDX

Raw 迷笛接口(Raw MIDI Interface,/dev/snd/midiCXDX

音序器接口(Sequencer Interface,/dev/snd/seq

定时器接口(Timer Interface,/dev/snd/timer

    和 OSS 类似,上述接口也以文件的方式被提供,不同的是这些接口被提供给 alsa-lib 使用,不是直接给应用程序使用的。应用程序最好使用 alsa-lib,或者更高级的接口,比如 jack 提供的接口。

    图17.6所示为ALSA声卡驱动与用户空间体系结构的简图,从中可以看出 ALSA 内核驱动与用户空间库及 OSS 之间的关系。

第17章Linux 音频设备驱动之Linux ALSA 音频设备驱动(一)

17.6 ALSA 体系结构

17.4.2 card 和组件

    对于每个声卡,必须创建一个 card 实例。card 是声卡的“总部”,它管理这个声卡上的所有设备(组件),如 PCM、mixers、MIDI、synthesizer(合成器) 等。因此,card 和组件是 ALSA 声卡驱动中的主要组成元素。

1.创建 card

struct snd_card *snd_card_new(int idx, const char *xid, struct module *module, int extra_size);

idx :card 索引号

xid :标识字符串

module: 一般为 THIS_MODULE

extra_size :要分配的额外数据的大小,分配的 extra_size 大小的内存将作为 card->private_data

2.创建组件

int snd_device_new(struct snd_card *card, snd_device_type_t type, void *device_data, 

        struct snd_device_ops *ops);

当 card 被创建后,设备(组件)能够被创建并关联于该 card。

第 1 个参数card是 snd_card_new()创建的 card 指针

第 2 个参数 type 指的是 device-level 即设备类型,形式为 SNDRV_DEV_XXX,包括 SNDRV_DEV_CODEC、SNDRV_DEV_CONTROL、SNDRV_DEV_PCM、SNDRV_DEV_RAWMIDI 等,用户自定义设备的 device-level 是 SNDRV_DEV_LOWLEVEL,ops 参数是 1 个函数集(定义为 snd_device_ops 结构体)的指针,device_data 是设备数据指针,注意函数 snd_device_new()本身不会分配设备数据的内存,因此应事先分配

3.组件释放

    每个ALSA 预定义的组件在构造时需调用创建组件snd_device_new()函数,而每个组件的析构方法则在函数集中被包含。对于PCM、AC97  此类预定义组件,不需关心它们的析构,而对于自定义的组件,则需要填充 snd_device_ops 中的析构函数指针 dev_free,这样,当 snd_card_free()被调用时,组件将自动被释放。

4.芯片特定的数据(Chip-Specific Data)

    芯片特定的数据一般以 struct xxxchip 结构体形式组织,这个结构体中包含芯片相关的 I/O 端口地址、资源指针、中断号等,其意义等同于字符设备驱动中的 file->private_data。

    定义芯片特定的数据主要有两种方法,一种方法是将sizeof(struct xxxchip)传入snd_card_new()作为 extra_size 参数,它将自动成为 snd_card 的 private_data 成员,如代码清单 17.5 所示。

代码清单 17.5 创建芯片特定的数据方法 1

/* 芯片特定的数据结构体 */

struct xxxchip {
         ...
};

/* 创建声卡并申请 xxxchip 内存作为 card-> private_data */

card = snd_card_new(index, id, THIS_MODULE, sizeof(struct xxxchip)); 

struct xxxchip *chip = card->private_data;

    另一种方法是在 snd_card_new()传入给 extra_size 参数 0,再分配 sizeof(struct xxxchip)的内存,将分配内

存的地址传入 snd_device_new()的 device_data 的参数,如代码清单 17.6 所示。

代码清单 17.6 创建芯片特定的数据方法 2

struct snd_card *card;

struct xxxchip *chip;

/* 使用 0 作为第 4 个参数,并动态分配 xxx_chip 的内存*/

card = snd_card__new(index[dev], id[dev], THIS_MODULE, 0);

...

chip = kzalloc(sizeof(*chip), GFP_KERNEL);

/* 在 xxxchip 结构体中,应该包括声卡指针*/

struct xxxchip {
        struct snd_card *card;
         ...
};

/* 并将其 card 成员赋值为 snd_card_new()创建的 card 指针*/

chip->card = card;

static struct snd_device_ops ops = {

         . dev_free = snd_xxx_chip_dev_free, /* 组件析构*/

};

...

/* 创建自定义组件*/

snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);

/* 在析构函数中释放 xxxchip 内存*/

static int snd_xxx_chip_dev_free(struct snd_device *device)
{
        return snd_xxx_chip_free(device->device_data); /* 释放*/
}

5.注册/释放声卡

当 snd_card 被准备好以后,可使用 snd_card_register()函数注册这个声卡,如下所示:

int snd_card_register(struct snd_card *card);

对应的 snd_card_free()完成相反的功能,如下所示:

int snd_card_free(struct snd_card *card);

17.4.3 PCM 设备

    每个声卡最多可以有 4 个 PCM(脉冲编码调制) 实例,一个 PCM 实例对应一个设备文件。PCM 实例由 PCM播放和录音流组成,而每个 PCM 流又由一个或多个 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;

    一种习惯的做法是在驱动中定义一个 PCM“构造函数”,负责 PCM 实例的创建,如代码清单 17.7所示。

代码清单 17.7 PCM 设备的“构造函数”

static int _ _devinit snd_xxxchip_new_pcm(struct xxxchip *chip)
 {
         struct snd_pcm *pcm;
         int err;
         /* 创建 PCM 实例 */
        if ((err = snd_pcm_new(chip->card, "xxx Chip", 0, 1, 1, &pcm)) < 0)

             return err;

        /* 置 pcm->private_data 为芯片特定数据*/

         pcm->private_data = chip; 
         strcpy(pcm->name, "xxx Chip");
         chip->pcm = pcm;
         ...
        return 0;
}

2.设置 PCM 操作

include/sound/pcm.h

void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops);

sound/core/pcm_lib.c

/**
 * snd_pcm_set_ops - set the PCM operators
 * @pcm: the pcm instance
 * @direction: stream direction, SNDRV_PCM_STREAM_XXX
 * @ops: the operator table
 *
 * Sets the given PCM operators to the pcm instance.
 */
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;
}


EXPORT_SYMBOL(snd_pcm_set_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 结构体

include/sound/pcm.h

struct snd_pcm_ops {
        int (*open)(struct snd_pcm_substream *substream);
        int (*close)(struct snd_pcm_substream *substream);
        int (*ioctl)(struct snd_pcm_substream * substream,
                     unsigned int cmd, void *arg);
        int (*compat_ioctl)(struct snd_pcm_substream *substream,
                     unsigned int cmd, void *arg);
        int (*hw_params)(struct snd_pcm_substream *substream,
                         struct snd_pcm_hw_params *params);
        int (*hw_free)(struct snd_pcm_substream *substream);/* 资源释放*/

        int (*prepare)(struct snd_pcm_substream *substream);

        /* 在 PCM 被开始、停止或暂停时调用*/

        int (*trigger)(struct snd_pcm_substream *substream, int cmd);

        / * 当前缓冲区的硬件位置*/

        snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
        int (*delay_blk)(struct snd_pcm_substream *substream);

        int (*wall_clock)(struct snd_pcm_substream *substream, struct timespec *audio_ts);

        /* 缓冲区复制*/

        int (*copy)(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos,
                    void __user *buf, snd_pcm_uframes_t count);
        int (*silence)(struct snd_pcm_substream *substream, int channel,
                       snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
        struct page *(*page)(struct snd_pcm_substream *substream,
                             unsigned long offset);
        int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
        int (*ack)(struct snd_pcm_substream *substream);
        int (*restart)(struct snd_pcm_substream *substream);
};

备注:

    snd_pcm_ops 中的所有操作都需事先通过 snd_pcm_substream_chip()获得 xxxchip (芯片特定的数据结构体)指针,例如:

int xxx()
{
        struct xxxchip *chip = snd_pcm_substream_chip(substream);
        ...
}

    当一个 PCM 子流被打开时,snd_pcm_ops 中的 open()函数将被调用,在这个函数中,至少需要初始化 runtime->hw 字段,代码清单 17.9 所示为 open()函数的范例。

代码清单 17.9 snd_pcm_ops 结构体中的 open()函数

static int snd_xxx_open(struct snd_pcm_substream *substream)

 {

        /* 从子流获得 xxxchip 指针*/
        struct xxxchip *chip = snd_pcm_substream_chip(substream);
        /* 获得 PCM 运行时信息指针*/

        struct snd_pcm_runtime *runtime = substream->runtime;

         ...

        /* 初始化 runtime->hw */

         runtime->hw = snd_xxxchip_playback_hw;

        return 0;
 }

分析:

    snd_xxxchip_playback_hw 是预先定义的硬件描述。在 open()函数中,可以分配一段私有数据。如果硬件配置需要更多的限制,也需设置硬件限制。

    当 PCM 子流被关闭时,close()函数将被调用。如果 open()函数中分配了私有数据,则在 close()函数中应该释放 substream 的私有数据,代码清单 17.10 所示为 close()函数的范例。

    代码清单 17.10 snd_pcm_ops 结构体中的 close()函数

  static int snd_xxx_close(struct snd_pcm_substream *substream)
  {
        /* 释放子流私有数据*/
         kfree(substream->runtime->private_data);
         ...
 }

驱动中通常可以给 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_PUSHSNDRV_PCM_TRIGGER_PAUSE_RELEASE 命令。如果设备支持挂起/恢复,当能量管理状态发生变化时将处理 SNDRV_PCM_TRIGGER_SUSPENDSNDRV_PCM_TRIGGER_RESUME 这两个命令。

分析:

trigger()函数是原子的,中途不能睡眠。代码清单 17.11所示为 trigger()函数的范例。

代码清单 17.11 snd_pcm_ops 结构体中的 trigger()函数

static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd)
 {
         switch (cmd) {
         case SNDRV_PCM_TRIGGER_START:
         /* 开启 PCM 引擎*/
                 break;
        case SNDRV_PCM_TRIGGER_STOP:
        /* 停止 PCM 引擎*/

                break;
         ... /* 其他命令*/
        default:
            return - EINVAL;

        }

 }

    pointer()函数用于 PCM 中间层查询目前缓冲区的硬件位置,该函数以帧的形式返回 0~buffer_size – 1 的位置,此函数也是原子的。

    copy()和 silence()函数一般可以省略,但是,当硬件缓冲区不处于常规内存中时需要。例如,一些设备有自己的不能被映射的硬件缓冲区,这种情况下,将数据从内存缓冲区复制到硬件缓冲区。当内存缓冲区在物理和虚拟地址上都不连续时,这两个函数也必须被实现。

3.分配缓冲区

分配缓冲区的最简单方法是调用如下函数:

include/sound/pcm.h

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);

sound/core/pcm_memory.c

/**
 * snd_pcm_lib_preallocate_pages_for_all - pre-allocation for continous memory type (all substreams)
 * @pcm: the pcm instance
 * @type: DMA type (SNDRV_DMA_TYPE_*)
 * @data: DMA type dependant data
 * @size: the requested pre-allocation size in bytes
 * @max: the max. allowed pre-allocation size
 *
 * Do pre-allocation to all substreams of the given pcm for the
 * specified DMA type.
 *
 * Returns zero if successful, or a negative error code on failure.
 */
int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
                                          int type, void *data,
                                          size_t size, size_t max)
{
        struct snd_pcm_substream *substream;
        int stream, err;

        for (stream = 0; stream < 2; stream++)
             for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
                        if ((err = snd_pcm_lib_preallocate_pages(substream, type, data, size, max)) < 0)
                                return err;
        return 0;
}

EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all);

4.设置标志

        在构造 PCM 实例、设置操作集并分配缓冲区之后,如果有需要,应设置 PCM 的信息标志,例如,如果 PCM 设备只支持半双工,则定义标志:

pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;

5.PCM 实例析构

        PCM 实例的“析构函数”并非是必须的,因为 PCM 实例会被 PCM 中间层代码自动释放,如果驱动中分配了一些特别的内存空间,则必须定义“析构函数”,代码清单 17.12 所示为 PCM“析构函数”与对应的“构造函数”,“析构函数”会释放“构造函数”中创建的 xxx_private__pcm_data。

代码清单 17.12 PCM 设备“析构函数”

static void xxxchip_pcm_free(struct snd_pcm *pcm)
 {
         /* 从 pcm 实例指针得到 chip指针(芯片特定的数据结构体) */
         struct xxxchip *chip = snd_pcm_chip(pcm);
         /* 释放自定义用途的内存 */
         kfree(chip->xxx_private_pcm_data);
         ...
 }

static int __devinit snd_xxxchip_new_pcm(struct xxxchip *chip)
{
        struct snd_pcm *pcm;

        ...

         /* 分配自定义用途的内存 */

        chip->xxx_private_pcm_data = kmalloc(...);

        pcm->private_data = chip; // 给 PCM 实例赋予的 xxxchip 指针
         /* 设置“析构函数” */
         pcm->private_free = xxxchip_pcm_free;
         ...
}

6.PCM 信息运行时结构体

    当 PCM 子流被打开后,PCM 运行时实例(定义为结构体 snd_pcm_runtime,如代码清单 17.13所示)将被分配给这个子流,这个指针通过 substream->runtime 获得。运行时指针包含各种各样的信息:hw_params 及 sw_params 配置的拷贝、缓冲区指针、mmap 记录、自旋锁等,几乎 PCM的所有控制信息均能从中取得。

代码清单 17.13 snd_pcm_runtime 结构体

include/sound/pcm.h

struct snd_pcm_runtime {
        /* -- Status -- */
        struct snd_pcm_substream *trigger_master;
        struct timespec trigger_tstamp; /* trigger timestamp */
        int overrange;
        snd_pcm_uframes_t avail_max;
        snd_pcm_uframes_t hw_ptr_base;  /* Position at buffer restart */
        snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time*/

        /* -- HW params -- */
        snd_pcm_access_t access;        /* access mode */
        snd_pcm_format_t format;        /* SNDRV_PCM_FORMAT_* */
        snd_pcm_subformat_t subformat;  /* subformat */
        unsigned int rate;              /* rate in Hz */
        unsigned int channels;          /* channels */
        snd_pcm_uframes_t period_size;  /* period size */
        unsigned int periods;           /* periods */
        snd_pcm_uframes_t buffer_size;  /* buffer size */
        unsigned int tick_time;         /* tick time */
        snd_pcm_uframes_t min_align;    /* Min alignment for the format */
        size_t byte_align;
        unsigned int frame_bits;
        unsigned int sample_bits;
        unsigned int info;
        unsigned int rate_num;
        unsigned int rate_den;

        /* -- SW params -- */
        int tstamp_mode;                /* mmap timestamp is updated */
        unsigned int period_step;
        unsigned int sleep_min;         /* min ticks to sleep */
        snd_pcm_uframes_t xfer_align;   /* xfer size need to be a multiple */
        snd_pcm_uframes_t start_threshold;
        snd_pcm_uframes_t stop_threshold;
        snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
                                                noise is nearest than this */
        snd_pcm_uframes_t silence_size; /* Silence filling size */
        snd_pcm_uframes_t boundary;     /* pointers wrap point */

        snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
        snd_pcm_uframes_t silence_filled; /* size filled with silence */

        union snd_pcm_sync_id sync;     /* hardware synchronization ID */

        /* -- mmap -- */
        volatile struct snd_pcm_mmap_status *status;
        volatile struct snd_pcm_mmap_control *control;

        /* -- locking / scheduling -- */
        wait_queue_head_t sleep;
        struct timer_list tick_timer;
        struct fasync_struct *fasync;

        /* -- private section -- */
        void *private_data;
        void (*private_free)(struct snd_pcm_runtime *runtime);

        /* -- hardware description -- */
        struct snd_pcm_hardware hw;
        struct snd_pcm_hw_constraints hw_constraints;

        /* -- interrupt callbacks -- */
        void (*transfer_ack_begin)(struct snd_pcm_substream *substream);
        void (*transfer_ack_end)(struct snd_pcm_substream *substream);

        /* -- timer -- */
        unsigned int timer_resolution;  /* timer resolution */

        /* -- DMA -- */
        unsigned char *dma_area;        /* DMA area */
        dma_addr_t dma_addr;            /* physical bus address (not accessible from main CPU) */
        size_t dma_bytes;               /* size of DMA area */

        struct snd_dma_buffer *dma_buffer_p;    /* allocated buffer */

#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
        /* -- OSS things -- */
        struct snd_pcm_oss_runtime oss;
#endif
};

分析:

    snd_pcm_runtime 中的大多数记录对被声卡驱动操作集中的函数是只读的,仅仅 PCM(脉冲编码调制) 中间层可从更新或修改这些信息,但是硬件描述、中断回调函数、DMA 缓冲区信息和私有数据是例外的。

    下面解释 snd_pcm_runtime 结构体中的几个重要成员。

(1)硬件描述。

    硬件描述(snd_pcm_hardware 结构体)包含了基本硬件配置的定义,需要在 open()函数中赋值。runtime 实例保存的是硬件描述的拷贝,这意味着在 open()函数中可以修改被拷贝的描述(runtime->hw),例如:    

    struct snd_pcm_runtime *runtime = substream->runtime;

    ...
    runtime->hw = snd_xxchip_playback_hw; /* generic 的硬件描述 */
    /* 特定的硬件描述 */
    if (chip->model == VERY_OLD_ONE)
                runtime->hw.channels_max = 1;

snd_pcm_hardware 结构体的定义如代码清单 17.14 所示。

代码清单 17.14 snd_pcm_hardware 结构体

/*
 *  Hardware (lowlevel) section
 */
struct snd_pcm_hardware {
        unsigned int info;              /* SNDRV_PCM_INFO_* */
        u64 formats;                    /* SNDRV_PCM_FMTBIT_* */
        unsigned int rates;             /* SNDRV_PCM_RATE_* */
        unsigned int rate_min;          /* min rate */
        unsigned int rate_max;          /* max rate */
        unsigned int channels_min;      /* min channels */
        unsigned int channels_max;      /* max channels */
        size_t buffer_bytes_max;        /* max buffer size */
        size_t period_bytes_min;        /* min period size */
        size_t period_bytes_max;        /* max period size */
        unsigned int periods_min;       /* 最小周期数 */
        unsigned int periods_max;       /* 最大周期数 */
        size_t fifo_size;               /* FIFO 字节数 */
};

分析:

   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(Open Sound System) 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 字段相符。

    channels_min 和 channels_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);

(2)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 的管理视情况而定。

(3)运行状态。

    通过 runtime->status 可以获得运行状态,它是 snd_pcm_mmap_status 结构体的指针,例如,通过 runtime->status->hw_ptr 可以获得目前的 DMA 硬件指针。

include/sound/asound.h

struct snd_pcm_mmap_status {
        snd_pcm_state_t state;          /* RO: state - SNDRV_PCM_STATE_XXXX */
        int pad1;                       /* Needed for 64 bit alignment */
        snd_pcm_uframes_t hw_ptr;       /* RO: hw ptr (0...boundary-1) */
        struct timespec tstamp;         /* Timestamp */
        snd_pcm_state_t suspended_state; /* RO: suspended stream state */
};

    此外,通过 runtime->control 可以获得 DMA 应用指针,它指向 snd_pcm_mmap_control 结构体指针,但是不建议直接访问该指针。

    include/sound/asound.h

    struct snd_pcm_mmap_control {
            snd_pcm_uframes_t appl_ptr;     /* RW: appl ptr (0...boundary-1) */
            snd_pcm_uframes_t avail_min;    /* RW: min available frames for wakeup */

    };

(4)私有数据。

    驱动中可以为子流分配一段内存并赋值给 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 */
        ....
}

(5)中断回调函数:
    transfer_ack_begin()和transfer_ack_end()函数分别在snd_pcm_period_elapsed()的开始和结束时被调用。

根据以上分析,代码清单 17.15 给出了一个完整的 PCM 设备接口模板。

代码清单 17.15 PCM 设备接口模板

#include <sound/pcm.h>

....

 /* 播放设备硬件定义 */

static struct snd_pcm_hardware snd_xxxchip_playback_hw = {
         .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
                     SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
         .formats = SNDRV_PCM_FMTBIT_S16_LE,
         .rates = SNDRV_PCM_RATE_8000_48000,
         .rate_min = 8000,
         .rate_max = 48000,
         .channels_min = 2,
         .channels_max = 2,
         .buffer_bytes_max = 32768,
         .period_bytes_min = 4096,
         .period_bytes_max = 32768,
         .periods_min = 1,
         .periods_max = 1024,
 };

 /* 录音设备硬件定义 */

static struct snd_pcm_hardware snd_xxxchip_capture_hw = {
         .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
                     SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID),
         .formats = SNDRV_PCM_FMTBIT_S16_LE,
         .rates = SNDRV_PCM_RATE_8000_48000,
         .rate_min = 8000,
         .rate_max = 48000,
         .channels_min = 2,

         .channels_max = 2,

          .buffer_bytes_max = 32768,
         .period_bytes_min = 4096,
         .period_bytes_max = 32768,
         .periods_min = 1,
         .periods_max = 1024,
    };

 /* 播放:打开函数 */
 static int snd_xxxchip_playback_open(struct snd_pcm_substream*substream)
 {
         struct xxxchip *chip = snd_pcm_substream_chip(substream);
         struct snd_pcm_runtime *runtime = substream->runtime;
         runtime->hw = snd_xxxchip_playback_hw;
         ... /* 硬件初始化代码*/
         return 0;

 }

/* 播放:关闭函数 */
static int snd_xxxchip_playback_close(struct snd_pcm_substream*substream)
 {
         struct xxxchip *chip = snd_pcm_substream_chip(substream);
         /* 硬件相关的代码*/
         return 0;
 }

/* 录音:打开函数 */
 static int snd_xxxchip_capture_open(struct snd_pcm_substream*substream)
 {
         struct xxxchip *chip = snd_pcm_substream_chip(substream);
         struct snd_pcm_runtime *runtime = substream->runtime;
         runtime->hw = snd_xxxchip_capture_hw;
         ... /* 硬件初始化代码*/
         return 0;
 }

/* 录音:关闭函数 */
 static int snd_xxxchip_capture_close(struct snd_pcm_substream*substream)
 {
         struct xxxchip *chip = snd_pcm_substream_chip(substream);
         ... /* 硬件相关的代码*/
         return 0;
 }

/* hw_params 函数 */
 static int snd_xxxchip_pcm_hw_params(struct snd_pcm_substream*substream, struct
 snd_pcm_hw_params *hw_params)
 {
         return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
 }
 /* hw_free 函数 */
 static int snd_xxxchip_pcm_hw_free(struct snd_pcm_substream*substream)
 {
         return snd_pcm_lib_free_pages(substream);
 }
 /* prepare 函数 */
 static int snd_xxxchip_pcm_prepare(struct snd_pcm_substream*substream)

{
         struct xxxchip *chip = snd_pcm_substream_chip(substream);
         struct snd_pcm_runtime *runtime = substream->runtime;
         /* 根据目前的配置信息设置硬件
         * 例如:
         */
         xxxchip_set_sample_format(chip, runtime->format);
         xxxchip_set_sample_rate(chip, runtime->rate);
         xxxchip_set_channels(chip, runtime->channels);
         xxxchip_set_dma_setup(chip, runtime->dma_addr, chip->buffer_size, chip->period_size);
         return 0;
 }

/* trigger 函数 */
 static int snd_xxxchip_pcm_trigger(struct snd_pcm_substream*substream, int cmd)
 {
         switch (cmd) {
         case SNDRV_PCM_TRIGGER_START:
                /* do something to start the PCM engine */
                 break;
         case SNDRV_PCM_TRIGGER_STOP:
                 /* do something to stop the PCM engine */
                 break;
         default:
                 return - EINVAL;
         }
 }

/* pointer 函数 */
 static snd_pcm_uframes_t snd_xxxchip_pcm_pointer(struct snd_pcm_substream *substream)
 {
         struct xxxchip *chip = snd_pcm_substream_chip(substream);
         unsigned int current_ptr;
         /*获得当前的硬件指针*/
         current_ptr = xxxchip_get_hw_pointer(chip);
         return current_ptr;
 }

/* 放音设备操作集 */
static struct snd_pcm_ops snd_xxxchip_playback_ops = {
         .open = snd_xxxchip_playback_open,
         .close = snd_xxxchip_playback_close,
         .ioctl = snd_pcm_lib_ioctl,
         .hw_params = snd_xxxchip_pcm_hw_params,
         .hw_free = snd_xxxchip_pcm_hw_free,
         .prepare = snd_xxxchip_pcm_prepare,
         .trigger = snd_xxxchip_pcm_trigger,
         .pointer = snd_xxxchip_pcm_pointer,
 };

/* 录音设备操作集 */

static struct snd_pcm_ops snd_xxxchip_capture_ops = {
         .open = snd_xxxchip_capture_open,
         .close = snd_xxxchip_capture_close,

         .ioctl = snd_pcm_lib_ioctl,

         .hw_params = snd_xxxchip_pcm_hw_params,
         .hw_free = snd_xxxchip_pcm_hw_free,
         .prepare = snd_xxxchip_pcm_prepare,
         .trigger = snd_xxxchip_pcm_trigger,
         .pointer = snd_xxxchip_pcm_pointer,
 };

/* 创建一个 PCM 设备 */
 static int _ _devinit snd_xxxchip_new_pcm(struct xxxchip *chip)
 {
         struct snd_pcm *pcm;
         int err;
         if ((err = snd_pcm_new(chip->card, "xxx Chip", 0, 1, 1, &pcm)) < 0)
                 return err;
         pcm->private_data = chip;
         strcpy(pcm->name, "xxx Chip");
         chip->pcm = pcm;
         /* 设置操作集 */
         snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_xxxchip_playback_ops);
         snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_xxxchip_capture_ops);
         /* 分配缓冲区 */
         snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
         snd_dma_pci_data(chip - > pci), 64 *1024, 64 *1024);
         return 0;

 }