static driver_t xxx_driver = { "pcm", xxx_methods, sizeof(struct snddev_info) }; DRIVER_MODULE(snd_xxxpci, pci, xxx_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_xxxpci, snd_pcm, PCM_MINVER, PCM_PREFVER,PCM_MAXVER);
第 15 章。声音子系统
目录
15.1。介绍
FreeBSD 声音子系统将通用声音处理问题与特定于设备的问题清晰地分开。这使得为新硬件添加支持变得更容易。
pcm(4) 框架是声音子系统的核心部分。它主要实现以下元素
一个系统调用接口(读取、写入、ioctl),用于数字化声音和混音器功能。ioctl 命令集与传统的 OSS 或 Voxware 接口兼容,允许常见的媒体应用程序无需修改即可移植。
用于处理声音数据的通用代码(格式转换、虚拟通道)。
一个统一的软件接口,用于特定于硬件的音频接口模块。
对一些常见的硬件接口(ac97)的额外支持,或共享的特定于硬件的代码(例如:ISA DMA 例程)。
对特定声卡的支持是通过特定于硬件的驱动程序实现的,这些驱动程序提供通道和混音器接口,以插入通用的 pcm 代码。
在本章中,术语 pcm 将指声音驱动程序的核心、通用部分,而不是特定于硬件的模块。
未来的驱动程序编写者当然希望从现有的模块开始,并将代码用作最终参考。但是,虽然声音代码很好且干净,但它也基本上没有注释。本文档试图概述框架接口并回答在调整现有代码时可能出现的某些问题。
作为替代方案,或者除了从工作示例开始之外,您还可以找到一个带有注释的驱动程序模板,位于 https://people.FreeBSD.org/~cg/template.c
15.2。文件
所有相关的代码都位于 /usr/src/sys/dev/sound/ 中,除了公共 ioctl 接口定义,这些定义位于 /usr/src/sys/sys/soundcard.h 中
在 /usr/src/sys/dev/sound/ 下,pcm/ 目录包含核心代码,而 pci/、isa/ 和 usb/ 目录包含 PCI 和 ISA 板卡以及 USB 音频设备的驱动程序。
15.3。探测、连接等
但是,声音驱动程序在某些方面有所不同
它们将自己声明为 pcm 类设备,具有一个
struct snddev_info
设备私有结构大多数声音驱动程序需要存储有关其设备的额外私有信息。私有数据结构通常在连接例程中分配。它的地址通过调用
pcm_register()
和mixer_init()
传递给 pcm。 pcm 随后将此地址作为参数传递给对声音驱动程序接口的调用。声音驱动程序连接例程应通过调用
mixer_init()
向 pcm 声明其 MIXER 或 AC97 接口。对于 MIXER 接口,这反过来会导致调用xxxmixer_init()
。声音驱动程序连接例程通过调用
pcm_register(dev, sc, nplay, nrec)
向 pcm 声明其通用的 CHANNEL 配置,其中sc
是设备数据结构的地址,用于从 pcm 的后续调用中使用,而nplay
和nrec
是播放和录制通道的数量。声音驱动程序连接例程通过调用
pcm_addchan()
声明其每个通道对象。这在 pcm 中设置通道粘合,并反过来会导致调用xxxchannel_init()
。声音驱动程序分离例程应在释放其资源之前调用
pcm_unregister()
。
有两种可能的方法来处理非 PnP 设备
使用
device_identify()
方法(例如:sound/isa/es1888.c)。device_identify()
方法在已知的地址探测硬件,如果找到受支持的设备,则创建一个新的 pcm 设备,然后将其传递给探测/连接。使用带有适合 pcm 设备的适当提示的自定义内核配置(例如:sound/isa/mss.c)。
pcm 驱动程序应实现 device_suspend
、device_resume
和 device_shutdown
例程,以便电源管理和模块卸载功能正常工作。
15.4。接口
pcm 核心和声音驱动程序之间的接口是根据 内核对象 定义的。
声音驱动程序通常会提供两个主要接口:CHANNEL 和 MIXER 或 AC97 中的任何一个。
AC97 接口是一个非常小的硬件访问(寄存器读/写)接口,由具有 AC97 编解码器的硬件驱动程序实现。在这种情况下,实际的 MIXER 接口由 pcm 中的共享 AC97 代码提供。
15.4.1。CHANNEL 接口
15.4.1.1。函数参数的通用说明
声音驱动程序通常具有一个私有数据结构来描述其设备,以及一个结构,用于描述其支持的每个播放和录制数据通道。
对于所有 CHANNEL 接口函数,第一个参数都是一个不透明指针。
第二个参数是指向私有通道数据结构的指针,除了 channel_init()
,它具有指向私有设备结构的指针(并返回通道指针,供 pcm 进一步使用)。
15.4.1.2。数据传输操作概述
对于声音数据传输,pcm 核心和声音驱动程序通过一个共享内存区域进行通信,该区域由一个 struct snd_dbuf
描述。
struct snd_dbuf
对 pcm 是私有的,声音驱动程序通过调用访问器函数 (sndbuf_getxxx()
) 获取感兴趣的值。
共享内存区域的大小为 sndbuf_getsize()
,并被划分为大小为 sndbuf_getblksz()
字节的固定大小的块。
播放时,一般的传输机制如下(反转录制的想法)
pcm 最初填充缓冲区,然后调用声音驱动程序的
xxxchannel_trigger()
函数,参数为 PCMTRIG_START。声卡驱动程序会安排将整个内存区域(`sndbuf_getbuf()`、`sndbuf_getsize()`)以`sndbuf_getblksz()`字节为块的大小,重复传输到设备。它会为每个传输的块回调`chn_intr()`pcm 函数(通常在中断时间发生)。
`chn_intr()` 会安排将新数据复制到已传输到设备的区域(现在已释放),并对`snd_dbuf` 结构进行相应的更新。
15.4.1.3. 声道初始化
`xxxchannel_init()` 用于初始化每个播放或录制通道。调用由声卡驱动程序附加例程发起。(参见交叉引用:sound[pcm-probe-and-attach,探测和附加部分])。
static void * xxxchannel_init(kobj_t obj, void *data, struct snd_dbuf *b, struct pcm_channel *c, int dir) (1) { struct xxx_info *sc = data; struct xxx_chinfo *ch; ... return ch; (2) }
1 | `b` 是通道`struct snd_dbuf` 的地址。它应该通过调用`sndbuf_alloc()` 在函数中初始化。要使用的缓冲区大小通常是设备“典型”单位传输大小的几倍。`c` 是pcm 通道控制结构指针。这是一个不透明对象。该函数应将其存储在本地通道结构中,以便在以后对pcm 的调用中使用(例如:`chn_intr(c)`)。`dir` 指示通道方向(`PCMDIR_PLAY` 或 `PCMDIR_REC`)。 |
2 | 该函数应返回一个指向用于控制此通道的私有区域的指针。该指针将作为参数传递给其他通道接口调用。 |
15.4.1.4. 设置通道格式
`xxxchannel_setformat()` 应该为指定通道设置指定声音格式的硬件。
static int xxxchannel_setformat(kobj_t obj, void *data, u_int32_t format) (1) { struct xxx_chinfo *ch = data; ... return 0; }
1 | `format` 指定为`AFMT_XXX` 值(soundcard.h)。 |
15.4.1.5. 设置通道速度
`xxxchannel_setspeed()` 为指定采样速度设置通道硬件,并返回可能调整后的速度。
static int xxxchannel_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct xxx_chinfo *ch = data; ... return speed; }
15.4.1.6. 设置通道块大小
`xxxchannel_setblocksize()` 设置块大小,即 pcm 和声卡驱动程序之间,以及声卡驱动程序和设备之间单位传输的大小。通常,这将是发生中断之前传输的字节数。在传输过程中,声卡驱动程序应该在每次传输此大小的数据时调用 pcm 的 `chn_intr()`。
大多数声卡驱动程序只在此处记录块大小,以便在实际启动传输时使用。
static int xxxchannel_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { struct xxx_chinfo *ch = data; ... return blocksize; (1) }
1 | 该函数返回可能调整后的块大小。如果块大小确实发生变化,则应调用`sndbuf_resize()` 来调整缓冲区。 |
15.4.1.7. 触发通道
`xxxchannel_trigger()` 由 pcm 调用,用于控制驱动程序中的数据传输操作。
static int xxxchannel_trigger(kobj_t obj, void *data, int go) (1) { struct xxx_chinfo *ch = data; ... return 0; }
1 | `go` 定义当前调用的操作。可能的取值有: |
如果驱动程序使用 ISA DMA,则应在对设备执行操作之前调用`sndbuf_isadma()`,它将处理 DMA 芯片方面的事情。 |
15.4.1.9. 释放通道
`xxxchannel_free()` 用于释放通道资源,例如在卸载驱动程序时,如果通道数据结构是动态分配的或者如果`sndbuf_alloc()` 未用于缓冲区分配,则应实现此函数。
15.4.1.10. 获取通道功能
struct pcmchan_caps * xxxchannel_getcaps(kobj_t obj, void *data) { return &xxx_caps; (1) }
1 | 该例程返回一个指向(通常是静态定义的)`pcmchan_caps` 结构的指针(在sound/pcm/channel.h 中定义)。该结构保存最小和最大采样频率以及可接受的声音格式。查看任何声卡驱动程序以获取示例。 |
15.4.1.11. 更多函数
`channel_reset()`、`channel_resetdone()` 和 `channel_notify()` 用于特殊用途,如果不与FreeBSD 多媒体邮件列表 讨论,则不应在驱动程序中实现这些函数。
`channel_setdir()` 已被弃用。
15.4.2. 混音器接口
15.4.2.1. 混音器初始化
`xxxmixer_init()` 初始化硬件并告知 pcm 哪些混音器设备可用于播放和录制
static int xxxmixer_init(struct snd_mixer *m) { struct xxx_info *sc = mix_getdevinfo(m); u_int32_t v; [Initialize hardware] [Set appropriate bits in v for play mixers] (1) mix_setdevs(m, v); [Set appropriate bits in v for record mixers] mix_setrecdevs(m, v) return 0; }
1 | 设置一个整数值中的位,并调用`mix_setdevs()` 和 `mix_setrecdevs()` 以告知 pcm 存在哪些设备。 |
混音器位定义可以在 soundcard.h 中找到(`SOUND_MASK_XXX` 值和 `SOUND_MIXER_XXX` 位移位)。
15.4.2.2. 设置混音器
`xxxmixer_set()` 设置一个混音器设备的音量级别。
static int xxxmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) (1) { struct sc_info *sc = mix_getdevinfo(m); [set volume level] return left | (right << 8); (2) }
1 | 设备指定为 `SOUND_MIXER_XXX` 值。音量值指定在 [0-100] 范围内。值为零应使设备静音。 |
2 | 由于硬件级别可能与输入比例不匹配,并且会发生一些舍入,因此例程返回实际级别值(在 0-100 范围内),如所示。 |
15.4.2.3. 设置录制源
`xxxmixer_setrecsrc()` 设置录制源设备。
static int xxxmixer_setrecsrc(struct snd_mixer *m, u_int32_t src) (1) { struct xxx_info *sc = mix_getdevinfo(m); [look for non zero bit(s) in src, set up hardware] [update src to reflect actual action] return src; (2) }
1 | 所需录制设备指定为位字段 |
2 | 返回实际为录制设置的设备。某些驱动程序只能为录制设置一个设备。如果发生错误,该函数应返回 -1。 |
最后修改于:2024 年 8 月 11 日 由 Fernando Apesteguía