• 《Linux驱动:使用音频设备驱动框架-OSS构建音频设备驱动》


    一,前言

    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等操作完成。
    在这里插入图片描述
    在这里插入图片描述

    三,OSS实现

    3.1 OSS初始化

    在内核中配置,将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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.2 向OSS注册音频设备

    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);
    
    • 1
    • 2
    • 3
    • 4

    这些接口的本质都是调用sound_insert_unit函数向OSS管理的不同设备链表中插入音频设备,并为不同的音频设备创建不同的音频设备设备节点。参数fops是各种音频设备的操作函数,dev一般传入-1,自动分配设备号。

    3.3 OSS管理音频设备

    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];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    比如注册一个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);
                
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上面提到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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    在这里插入图片描述

    四,音频基本概念

    4.1 采样频率

    对一个声音波形记录一次声音数据的频率。频率越大,对记录的声音数据还原播放出来的声音就越真实。对于入耳采样频率不需要太大,采样率在 8KHz - 96KHz之间就行,到 96KHz 声音已经很饱满了。

    4.2 采样精度

    一个声音数据用多少位表示。比如说8位精度,即一个声音数据用8位也就是一个字节表示;8位精度,即一个声音数据用16位也就是两个字节表示。

    4.3 左声道/右声道

    左右声道数据是不同的,两种数据配合还原播放出来的声音更加立体。

    4.4 IIS接口

    用来传输声音数据的接口,由四条线构成,I2SSCLK – 数据位时钟 , I2SLRCK – 左右声道数据采样时钟 , I2SSDI – 声音数据输入 , I2SSDO – 声音数据输出。
    在这里插入图片描述

    4.5 声音录制和播放

    在这里插入图片描述

    4.6 控制接口

    IIS接口用来传输音频数据,控制接口用来传输主控对编解码芯片的控制数据,比如设置声音音量、切换声道输出、设置MIC增益等。也就通过GPIO来对编解码芯片的某些寄存器进行数据读写操作。不同的编解码芯片使用不同的传输模式,比如WM8976G提供两种传输模式,一种是三线模式,一种是IIC模式。

    五,实现WM8976G的音频设备驱动

    5.1 硬件电路

    5.1.1 WM8976G相关

    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
    在这里插入图片描述

    5.1.2 S3C2440相关

    在这里插入图片描述
    在这里插入图片描述

    5.2 构建驱动

    通过总线-设备-驱动模型来构建音频设备驱动,在驱动的probe中初始化硬件资源、向OSS核心层注册dsp音频设备和mixer音频设备。

    5.2.1 注册平台设备

    系统启动初始化时,注册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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    5.2.2 注册平台驱动

    // 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5.2.3 probe函数分析

    总线-设备-驱动模型的设备和驱动的匹配过程,以及匹配成功后调用驱动的probe函数的过程在之前的文章中已经分析过多次,这里不再赘述。直接看驱动的probe函数,即s3c2410iis_probe。

    5.2.3.1 初始化硬件资源
    ......
    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();
    
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    5.2.3.2 初始化并设置音频编解码芯片
    .......
    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 
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    5.2.3.3 初始化音频输入和输出的DMA通道
    .......
    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;
    }
    .......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    5.2.3.4 向OSS核心层注册音频设备

    注册之后由OSS核心层管理并操作音频设备,就是第三节分析的一样。

    ......
    audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1);
    audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1);
    ......
    
    • 1
    • 2
    • 3
    • 4

    六,应用程序播放和录制

    待定

  • 相关阅读:
    SpringCloud 核心组件Nacos【NacosRule负载均衡&服务的权重设置】第3章
    R3Live系列学习(五)R3Live源码阅读
    为什么要使用MVP架构
    【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(二)
    3、排序与分页&多表查询 -mysql
    【前端】保姆级的CSS简介
    提高尼日利亚稻米产量 丰收节贸促会:国稻种芯百团计划行动
    asyncio.sleep
    Pandas 2.2 中文官方教程和指南(二十二)
    【深度学习】基于卷积神经网络(tensorflow)的人脸识别项目(二)
  • 原文地址:https://blog.csdn.net/qq_40709487/article/details/127594053