Jean-Francois
Dockes
&cnproj.contributed.by;
&author.cn.spellar;
&cnproj.translated.by;
声音子系统
简介
sound subsystem(声音子系统)
FreeBSD声音子系统清晰地将通用声音处理问题与设备特定的问题分离
开来。这使得更容易加入对新设备的支持。
&man.pcm.4;框架是声音子系统的中心部分。它主要实现下面的组件:
system call interface(系统调用接口)
一个到数字化声音和混音器函数的系统调用接口(read, write,
ioctls)。ioctl命令集合兼容老的OSS
或Voxware接口,允许一般多媒体应用程序
不加修改地移植。
处理声音数据的公共代码(格式转换,虚拟通道)。
一个统一的软件接口,与硬件特定的音频接口模块接口
对某些通用硬件接口(ac97)或共享的硬件特定代码
(例如:ISA DMA例程)的额外支持。
对特定声卡的支持是通过硬件特定的驱动程序来实现的,这些驱动程序
提供通道和混音器接口,插入到通用pcm代码中。
本章中,术语pcm将指声音驱动程序的
中心,通用部分,这是对比硬件特定的模块而言的。
预期的驱动程序编写者当然希望从现有模块开始,并使用那些代码作为
最终参考。但是,由于声音代码十分简洁漂亮,这也基本上免除了注释。
本文档试图给出框架接口的一个概览,并回答改写现有代码时可能出现的
一些问题。
作为另外的途径,或者说除了从一个可工作的范例开始的办法之外,
你可以从
http://people.FreeBSD.org/~cg/template.c找到一个注释过的
驱动程序模板。
文件
除/usr/src/sys/sys/soundcard.h中的公共
ioctl接口定义外,所有的相关代码当前(FreeBSD 4.4)位于
/usr/src/sys/dev/sound/。
在/usr/src/sys/dev/sound/下面,
pcm/目录中保存着中心代码,
而isa/和pci/目录中有
ISA和PCI板的驱动程序。
探测,连接等
声音驱动程序使用与任何硬件驱动程序模块相同的方法探测和连接(设备)。
你可能希望浏览一下手册中ISA或PCI章节的内容来获取更多信息。
然而,声音驱动程序在某些方面又有些不同:
他们将自己声明为pcm类设备,带有一个
设备私有结构struct snddev_info:
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);
device driver(设备驱动程序)sound(声音)
大多数声音驱动程序需要存储关于其设备的附加私有信息。私有数据
结构通常在连接例程中分配。其地址通过调用
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设备接着
会被传递到probe/attach。
使用定制内核配置的方法,为pcm设备设置适当的hints(范例:
sound/isa/mss.c)。
pcm驱动程序应当实现
device_suspend,
device_resume和
device_shutdown例程,这样电源管理和模块卸载就能
正确地发挥作用。
接口
pcm核心与声音驱动程序之间的接口以术语
内核对象的叫法来定义。
声音驱动程序通常提供两种主要的接口:
CHANNEL以及
MIXER或AC97。
AC97是一个很小的硬件访问(寄存器读/写)
接口,由驱动程序为带AC97编码解码器的硬件来实现。这种情况下,实际的
MIXER接口由pcm中共享的AC97代码提供。
CHANNEL接口
函数参数的通常注意事项
声音驱动程序通常用一个私有数据结构来描述他们的设备,驱动
程序所支持的播放和录音数据通道各有一个。
对于所有的CHANNEL接口函数,第一个参数是一个不透明的指针。
第二个参数是指向私有的通道数据结构的指针,
channel_init()是个例外,它的指针指向私有
设备结构(并返回由pcm以后使用的通道指针)。
数据传输操作概览
对于声音数据传输,pcm核心与声音驱动
程序是通过一个由struct snd_dbuf描述的
共享内存区域进行通信的。
struct snd_dbuf是
pcm私有的,声音驱动程序通过调用访问者
函数(sndbuf_getxxx())来获得感兴趣的值。
共享内存区域的大小等于
sndbuf_getsize(),并被分割为大小固定,且等于
sndbuf_getblksz()字节的很多块。
当播放时,常规的传输机制如下(将意思反过来就是录音):
pcm开始时填充缓冲区,然后以
参数PCMTRIG_START调用声音驱动程序的
xxxchannel_trigger()
。
声音驱动程序接着安排以
sndbuf_getblksz()字节大小为块,重复将
整个内存区域(sndbuf_getbuf(),
sndbuf_getsize())传输到设备。对于每个
传输块回调pcm函数
chn_intr()(这通常在中断时间发生)。
chn_intr()安排将新数据拷贝到那些
数据已传输到设备(现在空闲)的区域,并对
snd_dbuf结构进行适当的更新。
channel_init
调用xxxchannel_init()来初始化每个播放
和录音通道。这个调用从声音驱动程序的连接例程中发起。(参看
探测和连接一节)。
static void *
xxxchannel_init(kobj_t obj, void *data,
struct snd_dbuf *b, struct pcm_channel *c, int dir)
{
struct xxx_info *sc = data;
struct xxx_chinfo *ch;
...
return ch;
}
b为通道
struct snd_dbuf的地址。它应当在
函数中通过调用sndbuf_alloc()来初始化。
所用的缓冲区大小通常是设备'典型'传输大小的一个较小的倍数。
c为
pcm通道控制结构的指针。这是个不透明
指针。函数应当将它保存到局部通道结构中,在后面调用
pcm函数(例如:
chn_intr(c))时会使用它。
dir指示通道方向
(PCMDIR_PLAY或
PCMDIR_REC)。
函数应当返回一个指针,此指针指向用于控制此通道的私有
区域。它将作为参数被传递到对其他通道接口的调用。
channel_setformat
xxxchannel_setformat()应当按特定通道,
特定声音格式设置硬件。
static int
xxxchannel_setformat(kobj_t obj, void *data, u_int32_t format)
{
struct xxx_chinfo *ch = data;
...
return 0;
}
format为
AFMT_XXX value值之一
(soundcard.h)。
channel_setspeed
xxxchannel_setspeed()按指定的取样速度
设置通道硬件,并返回返回可能调整过的速度。
static int
xxxchannel_setspeed(kobj_t obj, void *data, u_int32_t speed)
{
struct xxx_chinfo *ch = data;
...
return speed;
}
channel_setblocksize
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;
}
函数返回可能调整过的块大小。如果块大小真的变化了,
这种情况下应当调用sndbuf_resize()调整
缓冲区的大小。
channel_trigger
xxxchannel_trigger()由
pcm来控制驱动程序中的实际传输操作。
static int
xxxchannel_trigger(kobj_t obj, void *data, int go)
{
struct xxx_chinfo *ch = data;
...
return 0;
}
go定义当前调用的动作。可能的值为:
PCMTRIG_START:驱动程序应当
启动从/到通道缓冲区的数据传输。如果需要,应当通过
sndbuf_getbuf()和
sndbuf_getsize()检取缓冲区的
基地址和大小。
PCMTRIG_EMLDMAWR /
PCMTRIG_EMLDMARD:告诉驱动程序,
输入或输出缓冲区可能已被更新过了。大多数驱动程序只是
忽略这些调用。
PCMTRIG_STOP /
PCMTRIG_ABORT:驱动程序应当停止当前
的传输。
如果驱动程序使用ISA DMA,则应当在设备上执行动作前
调用sndbuf_isadma(),并处理DMA芯片一方的
事情。
channel_getptr
xxxchannel_getptr()返回传输缓冲区中
当前的缓冲。它通常由chn_intr()调用,而且
这也是为什么pcm知道它应当往哪儿传送
新数据。
channel_free
调用xxxchannel_free()来释放通道资源,
例如当驱动程序卸载时,并且如果通道数据结构是动态分配的,或者
如果不使用sndbuf_alloc()进行缓冲区分配,
则应当实现这个函数。
channel_getcaps
struct pcmchan_caps *
xxxchannel_getcaps(kobj_t obj, void *data)
{
return &xxx_caps;
}
这个例程返回指向(通常静态定义的)
pcmchan_caps结构的指针(在
sound/pcm/channel.h中定义)。这个结构
保存着最小和最大采样频率和被接受的声音格式。任何声音驱动
程序都可以作为一个范例。
更多函数
channel_reset(),
channel_resetdone()和
channel_notify()用于特殊目的,未与权威人士
(&a.cg;)进行探讨之前不应当在驱动程序中实现它。
不赞成使用channel_setdir().
MIXER接口
mixer_init
xxxmixer_init()初始化硬件,并告诉
pcm什么混音器设备可用来播放和录音。
static int
xxxmixer_init(struct snd_mixer *m)
{
struct xxx_info *sc = mix_getdevinfo(m);
u_int32_t v;
[初始化硬件]
[为播放混音器设置v中适当的位]
mix_setdevs(m, v);
[为录音混音器设置v中适当的位]
mix_setrecdevs(m, v)
return 0;
}
设置一个整数值中的位,并调用
mix_setdevs()和
mix_setrecdevs()来告诉
pcm存在什么设备。
混音器的位定义可以在soundcard.h中
找到。(SOUND_MASK_XXX值和
SOUND_MIXER_XXX移位)。
mixer_set
xxxmixer_set()为混音器设备设置音量级别
(level)。
static int
xxxmixer_set(struct snd_mixer *m, unsigned dev,
unsigned left, unsigned right)
{
struct sc_info *sc = mix_getdevinfo(m);
[设置音量级别(level)]
return left | (right << 8);
}
设备被指定为 SOUND_MIXER_XXX 值
在范围[0-100]之间指定音量值。零值应当让设备静音。
由于硬件(音量)级别(level)可能不匹配输入比例,会出现
某些圆整,例程返回如上面所示的实际级别值(范围0-100内)。
mixer_setrecsrc
xxxmixer_setrecsrc()设定录音源设备。
static int
xxxmixer_setrecsrc(struct snd_mixer *m, u_int32_t src)
{
struct xxx_info *sc = mix_getdevinfo(m);
[查看src中的非零位, 设置硬件]
[更新src反映实际动作]
return src;
}
期望的录音设备由一个位域指定.
返回设置用来录音的实际设备。一些驱动程序只能设置一个
录音设备。如果出现错误,函数应当返回-1。
mixer_uninit, mixer_reinit
xxxmixer_uninit()应当确保不会发出任何
声音,并且如果可能则应当让混音器硬件断电。
xxxmixer_reinit()应当确保混音器硬件
加电,并且恢复所有不受mixer_set()或
mixer_setrecsrc()控制的设置。
AC97接口
AC97
AC97由带有AC97编码解码器的驱动程序实现。
它只有三个方法:
xxxac97_init()返回找到的
ac97编码解码器的数目。
ac97_read()与
ac97_write()读写指定的寄存器。
The AC97接口由
pcm中的AC97代码来执行高层操作。参看
sound/pci/maestro3.c或
sound/pci/下很多其他内容作为范例。