OSS(Open Sound System(开放声音系统)),是unix平台上一个统一的音频接口。ALSA (Advanced Linux Sound Architecture(高级Linux声音体系)) 是为声卡提供驱动的Linux内核组件,替代OSS。在这里先来分析简单的OSS,后面有时间再研究下ALSA。分析OSS的基本框架,最后通过一个例子总结下OSS框架下实现一个音频设备驱动的一般步骤。
内核目录下sound/sound_core.c文件是OSS的核心层,其向上提供了应用程序访问音频设备统一的入口,向下为不同音频设备提供了向上的注册接口。音频驱动开发针对特定的音频设备和系统硬件资源使用总线-设备-驱动模型构建音频设备驱动,在驱动probe中调用OSS提供的接口注册音频设备,并由OSS进行管理。OSS架构是基于文件系统的访问方式,对声音的操作通过对设备节点文件进行open、read、write等操作完成。
在内核中配置,将sound_core.c编译进内核,内核初始化时加载,调用init_soundcore。
make menuconfig
-> Device Drivers -> Sound
-> <*> Sound card support
// linux-2.6.22.6/include/linux/major.h
#define SOUND_MAJOR 14
// linux-2.6.22.6/sound/sound_core.c
static int __init init_soundcore(void)
{
// 向内核注册一个字符设备
if (register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops)==-1) {
printk(KERN_ERR "soundcore: sound device already in use.\n");
return -EBUSY;
}
// 创建一个设备类。/sys/class/sound
sound_class = class_create(THIS_MODULE, "sound");
if (IS_ERR(sound_class))
return PTR_ERR(sound_class);
return 0;
}
OSS标准中有2个最基本的音频设备:mixer(混音器)和DSP(数字信号处理器),mixer的作用将多个信号组合或者叠加到一起,可用来调整音量大小和选择音源。对于不同声卡来说,其混音器的作用可能各不相同。向OSS核心层注册音频接口时创建的/dev/mixer设备节点文件是应用程序对mixer进行操作的软件接口。DSP也称编解码器,主要实现录音和放音的操作,其对应的设备节点文件是/dev/dsp,向该设备节点文件写数据即意味着激活声卡上的D/A 转换器进行放音,而向该设备读数据则意味着激活声卡上的A/D 转换器进行录音。
除了mixer和dsp音频设备外,还有多个不同的音频设备。OSS针对这些设备各下层提供了不同的注册接口。
int register_sound_mixer(const struct file_operations *fops, int dev);
int register_sound_midi(const struct file_operations *fops, int dev);
int register_sound_dsp(const struct file_operations *fops, int dev);
int register_sound_special(const struct file_operations *fops, int unit);
这些接口的本质都是调用sound_insert_unit函数向OSS管理的不同设备链表中插入音频设备,并为不同的音频设备创建不同的音频设备设备节点。参数fops是各种音频设备的操作函数,dev一般传入-1,自动分配设备号。
OSS通过一个指针数组管理多个不同类型的音频设备链表。驱动层下OSS注册音频设备时,即向对应的链表中插入一个sound_unit结构。
/*
* Allocations
*
* 0 *16 Mixers
* 1 *8 Sequencers
* 2 *16 Midi
* 3 *16 DSP
* 4 *16 SunDSP
* 5 *16 DSP16
* 6 -- sndstat (obsolete)
* 7 *16 unused
* 8 -- alternate sequencer (see above)
* 9 *16 raw synthesizer access
* 10 *16 unused
* 11 *16 unused
* 12 *16 unused
* 13 *16 unused
* 14 *16 unused
* 15 *16 unused
*/
static struct sound_unit *chains[SOUND_STEP];
比如注册一个dsp音频设备
register_sound_dsp(&smdk2410_audio_fops, -1) ->
sound_insert_unit(&chains[3], fops, dev, 3, 131,"dsp", S_IWUSR | S_IRUSR, NULL) ->
__sound_insert_unit(s, list, fops, index, low, top) ->
s->unit_minor=n;
s->unit_fops=fops;
s->next=*list;
*list=s;
// 以14为主设备号,以s->unit_minor为次设备号创建音频设备,创建设备节点文件
device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor),
s->name+6);
上面提到OSS初始化时,向内核注册了一个字符设备,其操作函数为soundcore_fops。这里面只有一个open接口,其只是作为一个中转,应用程序访问某个音频设备(open)会根据打开的音频设备的设备节点文件的次设备号(也就是创建时使用的s->unit_minor),从音频设备链表数组(chains)中找到对应类型的音频设备链表从而找到对应的操作函数。
static const struct file_operations soundcore_fops=
{
/* We must have an owner or the module locking fails */
.owner = THIS_MODULE,
.open = soundcore_open,
};
int soundcore_open(struct inode *inode, struct file *file)
{
int chain;
int unit = iminor(inode);
struct sound_unit *s;
const struct file_operations *new_fops = NULL;
chain=unit&0x0F;
if(chain==4 || chain==5) /* dsp/audio/dsp16 */
{
unit&=0xF0;
unit|=3;
chain=3;
}
spin_lock(&sound_loader_lock);
s = __look_for_unit(chain, unit);
if (s)
new_fops = fops_get(s->unit_fops);
if (!new_fops) {
spin_unlock(&sound_loader_lock);
request_module("sound-slot-%i", unit>>4);
request_module("sound-service-%i-%i", unit>>4, chain);
spin_lock(&sound_loader_lock);
s = __look_for_unit(chain, unit);
if (s)
new_fops = fops_get(s->unit_fops);
}
if (new_fops) {
int err = 0;
const struct file_operations *old_fops = file->f_op;
file->f_op = new_fops;
spin_unlock(&sound_loader_lock);
if(file->f_op->open)
err = file->f_op->open(inode,file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
return err;
}
spin_unlock(&sound_loader_lock);
return -ENODEV;
}
对一个声音波形记录一次声音数据的频率。频率越大,对记录的声音数据还原播放出来的声音就越真实。对于入耳采样频率不需要太大,采样率在 8KHz - 96KHz之间就行,到 96KHz 声音已经很饱满了。
一个声音数据用多少位表示。比如说8位精度,即一个声音数据用8位也就是一个字节表示;8位精度,即一个声音数据用16位也就是两个字节表示。
左右声道数据是不同的,两种数据配合还原播放出来的声音更加立体。
用来传输声音数据的接口,由四条线构成,I2SSCLK – 数据位时钟 , I2SLRCK – 左右声道数据采样时钟 , I2SSDI – 声音数据输入 , I2SSDO – 声音数据输出。
IIS接口用来传输音频数据,控制接口用来传输主控对编解码芯片的控制数据,比如设置声音音量、切换声道输出、设置MIC增益等。也就通过GPIO来对编解码芯片的某些寄存器进行数据读写操作。不同的编解码芯片使用不同的传输模式,比如WM8976G提供两种传输模式,一种是三线模式,一种是IIC模式。
L2/GPIO2:单声道音频输入或第二个麦克风接入或者做普通GPIO口
LRC:左右声道数据采样时钟
BCLK:声音数据位时钟
ADCDAT:IIS数据输入
DACDAT:IIS数据输出
MCLK:WM8976工作时钟,由主控提供
MICBIAS:麦克风偏置电压,通过调节该偏置电压可以改善麦克风录制失真问题
LIP:麦克风输入通道
LIN:麦克风输入通道(接地)
AUXL:音频输入左声道(外接音频)
AUXR:音频输入右声道(外接音频)
CSB/GPIO1:控制接口使用3线模式时的片选脚或者做普通GPIO口
SCLK:三线模式时钟或者IIC模式时钟
SDIN:三线模式数据或者IIC模式数据
MODE:控制接口使用三线模式或IIC模式选择脚,高电平时使用三线模式
VMID:参考电压,通过调节参考电压可以改善声音播放时的噪音问题
ROUT1:音频输出通道1,右声道,可外接耳机或喇叭
LOUT1:音频输出通道1,左声道,可外接耳机或喇叭
ROUT2:音频输出通道2,右声道,可外接耳机或喇叭
LOUT2:音频输出通道2,左声道,可外接耳机或喇叭
OUT3:音频输出通道3
OUT4:音频输出通道4
通过总线-设备-驱动模型来构建音频设备驱动,在驱动的probe中初始化硬件资源、向OSS核心层注册dsp音频设备和mixer音频设备。
系统启动初始化时,注册s3c_device_iis平台设备。加载注册过程在前面的文章中已经分析过多次,这里不再赘述。
// linux-2.6.22.6/arch/arm/plat-s3c24xx/devs.c
static struct resource s3c_iis_resource[] = {
[0] = {
.start = S3C24XX_PA_IIS,
.end = S3C24XX_PA_IIS + S3C24XX_SZ_IIS -1,
.flags = IORESOURCE_MEM,
}
};
static u64 s3c_device_iis_dmamask = 0xffffffffUL;
struct platform_device s3c_device_iis = {
.name = "s3c2410-iis",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_iis_resource),
.resource = s3c_iis_resource,
.dev = {
.dma_mask = &s3c_device_iis_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
// linux-2.6.22.6/arch/arm/mach-s3c2440/mach-smdk2440.c
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
&s3c2440_device_sdi,
};
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
// linux-2.6.22.6/sound/soc/s3c24xx/s3c2440-wm8976.c
extern struct bus_type platform_bus_type;
static struct device_driver s3c2410iis_driver = {
.name = "s3c2410-iis",
.bus = &platform_bus_type,
.probe = s3c2410iis_probe,
.remove = s3c2410iis_remove,
};
static int __init s3c2410_uda1341_init(void) {
memzero(&input_stream, sizeof(audio_stream_t));
memzero(&output_stream, sizeof(audio_stream_t));
return driver_register(&s3c2410iis_driver);
}
总线-设备-驱动模型的设备和驱动的匹配过程,以及匹配成功后调用驱动的probe函数的过程在之前的文章中已经分析过多次,这里不再赘述。直接看驱动的probe函数,即s3c2410iis_probe。
......
iis_base = (void *)S3C24XX_VA_IIS ;
// 使能IIS模块
iis_clock = clk_get(dev, "iis");
clk_enable(iis_clock);
// 设置相关引脚功能
/* GPB 4: L3CLOCK, OUTPUT */
s3c2410_gpio_cfgpin(S3C2410_GPB4, S3C2410_GPB4_OUTP);
s3c2410_gpio_pullup(S3C2410_GPB4,1);
/* GPB 3: L3DATA, OUTPUT */
s3c2410_gpio_cfgpin(S3C2410_GPB3,S3C2410_GPB3_OUTP);
/* GPB 2: L3MODE, OUTPUT */
s3c2410_gpio_cfgpin(S3C2410_GPB2,S3C2410_GPB2_OUTP);
s3c2410_gpio_pullup(S3C2410_GPB2,1);
/* GPE 3: I2SSDI */
s3c2410_gpio_cfgpin(S3C2410_GPE3,S3C2410_GPE3_I2SSDI);
s3c2410_gpio_pullup(S3C2410_GPE3,0);
/* GPE 0: I2SLRCK */
s3c2410_gpio_cfgpin(S3C2410_GPE0,S3C2410_GPE0_I2SLRCK);
s3c2410_gpio_pullup(S3C2410_GPE0,0);
/* GPE 1: I2SSCLK */
s3c2410_gpio_cfgpin(S3C2410_GPE1,S3C2410_GPE1_I2SSCLK);
s3c2410_gpio_pullup(S3C2410_GPE1,0);
/* GPE 2: CDCLK */
s3c2410_gpio_cfgpin(S3C2410_GPE2,S3C2410_GPE2_CDCLK);
s3c2410_gpio_pullup(S3C2410_GPE2,0);
/* GPE 4: I2SSDO */
s3c2410_gpio_cfgpin(S3C2410_GPE4,S3C2410_GPE4_I2SSDO);
s3c2410_gpio_pullup(S3C2410_GPE4,0);
// 初始化IIS模块
init_s3c2410_iis_bus();
......
.......
init_wm8976();
.......
static void init_wm8976(void)
{
uda1341_volume = 57;
uda1341_boost = 0;
/* software reset */
wm8976_write_reg(0, 0);
/* OUT2的左/右声道打开
* 左/右通道输出混音打开
* 左/右DAC打开
*/
wm8976_write_reg(0x3, 0x6f);
wm8976_write_reg(0x1, 0x1f);//biasen,BUFIOEN.VMIDSEL=11b
wm8976_write_reg(0x2, 0x185);//ROUT1EN LOUT1EN, inpu PGA enable ,ADC enable
wm8976_write_reg(0x6, 0x0);//SYSCLK=MCLK
wm8976_write_reg(0x4, 0x10);//16bit
wm8976_write_reg(0x2B,0x10);//BTL OUTPUT
wm8976_write_reg(0x9, 0x50);//Jack detect enable
wm8976_write_reg(0xD, 0x21);//Jack detect
wm8976_write_reg(0x7, 0x01);//Jack detect
}
.......
output_stream.dma_ch = DMACH_I2S_OUT;
if (audio_init_dma(&output_stream, "UDA1341 out")) {
audio_clear_dma(&output_stream,&s3c2410iis_dma_out);
printk( KERN_WARNING AUDIO_NAME_VERBOSE
": unable to get DMA channels\n" );
return -EBUSY;
}
input_stream.dma_ch = DMACH_I2S_IN;
if (audio_init_dma(&input_stream, "UDA1341 in")) {
audio_clear_dma(&input_stream,&s3c2410iis_dma_in);
printk( KERN_WARNING AUDIO_NAME_VERBOSE
": unable to get DMA channels\n" );
return -EBUSY;
}
.......
注册之后由OSS核心层管理并操作音频设备,就是第三节分析的一样。
......
audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1);
audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1);
......
待定