S3C2440 音频解码芯片WM8976声卡驱动移植、测试以及madplay播放mp3文件(三十一)

  • Post author:
  • Post category:其他



https://www.cnblogs.com/lifexy/p/7867782.html


本节学习:

  • 分析linux中的OOS声卡系统
  • 修改s3c2410-uda1341.c的控制部分,移植wm8976声卡
  • 使用madplay应用程序播放mp3


本节常用英语单词:


volume:

音量,

dsp:

数字信号处理(Digital Signal Processing),

mixer:

混音器,

unit:

单位,个体




1、声音三要素



采样频率

音频采样率是指录音设备在一秒钟内对声音信号的采样次数,常用的采样率有:

  • 8KHz —电话所用采样率,对于人的说话已经足够清楚
  • 22.05KHz —无线电广播所用采样率
  • 32KHz —miniDV数码视频、DAT所用采样率
  • 44.1KHz —音频CD,也常用于MPEG-1 视频(VCD、SVCD、MP3)所用采样率
  • 48KHz —miniDV、数字电视、DVD、DAT、电影和专业视频所用的数字声音所用采样率
  • 50KHz —商用数字录音机所用采样率
  • 96KHz —BD-ROM(蓝光盘)音轨和HD_DVD(高清晰度DVD)音轨等所用采样率

而2440开发板的采样频率IISLRCK最高可以达到96KHz,满足了很多常用的采样场合,如下图所示:



量化位数

指每个采样点里传输的数字信号次数,如下图所示,其中蓝线表示模拟信号,红线表示数字信号,量化位越高,数字信号就越可能接近原始信号,音质越好

一般的量化位数为:

  • 8位:分成256次;
  • 16位:分成65536次,已到CD标准;
  • 32位:分成4294967296次,很少用到

2440的开发板只支持8位,16位,如下图所示:

其中LRCK就是采样频率,当LRCK为低时,表示传输的采样数据是左声道,当LRCK为高时,表示传输的采样数据是右声道,每个采样点,SD(serial data)都可以传输8位,或16位数字信号(从低位到高位传输)



声道数

常有单声道和立体声之分,(有的也处理成两个喇叭输出同一个声道的声音),而立体声更能感受到空间效果,但数据量翻倍


所以,声音的每秒数据量(字节/s)=(采样频率 X 量化位数 X 声道数)/8;



2、WM8976声卡硬件分析

声卡是负责录音、播音、调节音量和声音合成等的一种多媒体板卡

本节使用的声卡是2440板上自带的WM9876声卡

当我们播放声音时,将数字信号传入I2SDO脚,声卡便通过解码,产生模拟信号到喇叭/耳机

当我们录音时,声卡便获取麦克风的模拟信号,编码出数字信号到I2SDI引脚上

VM8976接口分为两种:

I2S接口

(提供音频接收和发送)、

控制接口

(控制音量大小,使能各个输出通道等)



IIS接口相关


的引脚如下:


  • MCLK

    :主机为解码芯片提供的系统同步时钟(Master/system clock input)

  • BCLK

    :编解码芯片提供的串行时钟信号(Audio bit clock output)

  • I2SLRCK

    :采样频率信号,当为低电平时是采样的是左声道信号,为高电平时是采样的是右声道信号

  • I2SDI

    :ADC数据输入

  • I2SDO

    :DAC数据输出

如下图所示:



控制接口相关


的引脚如下:


  • CSB/GPIO1

    :3线 控制数据使能引脚

  • SCLK

    :3线/2线 始终引脚

  • SDIN

    :3线/2线 数据输入输出引脚

  • MODE

    :3线/2线 控制选择,当MODE为高,表示3线控制,MODE为低,表示2线控制,如下图所示:

其他引脚如下:


  • R/LOUT1

    :音频左/右输出通道1,外接耳机插孔

  • R/LOUT2

    :音频左/右输出通道2,未接

  • OUT3

    :单声道输出通道3,未接

  • OUT4

    :单声道输出通道4,未接

  • LIP/LIN

    :音频输入通道,外接麦克风

那么3线和2线的控制引脚又有什么区别呢?



3线控制:

如下图所示,3线控制,每周期都要传输16位数据(7位寄存器地址 + 9位寄存器数据),传输完成后,给CSB一个上升沿便完成一次数据的传输



2线控制:

如下图所示,2线控制就是I2C通信方式

本节的WM8976的MODE引脚的高电平,所以是3线控制



3、接下来便来分析linux内核的声卡系统

在linux声卡中存在两种声卡系统,一种是


OSS


(开放声音系统),一种是


ALSA


(先进linux声音架构)。本节系统以OSS(Open Sound System)为例,

内核以linux-2.6.22.6版本为例,位于:linux-2.6.22.6\sound\sound_core.c



3.1 首先进入入口函数

如下图所示:

入口函数里,只注册了一个主设备号为

(SOUND_MAJOR)

14的”sound”字符设备和class类,这里为什么咩有创建设备节点?

是因为,当注册声卡系统的驱动后,才会有设备节点,此时这里的代码是没有驱动的,后面会分析到



3.2 再来看看”




sound




“字符设备的




file_operations






这里只有一个.open,为什么没有read,write函数?

显然在.open函数里做了某些处理,我们进入

soundcore_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;//定义一个新的file_operations

	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);//里面通过chains[chain]数组里找到sound_unit结构体,一个sount_unit对应一个声卡驱动
	if (s)
		new_fops = fops_get(s->unit_fops);//通过sound_unit,获取对应的file_operations
	... ...
	if (new_fops) {//当找到file_operations
		int err = 0;
		const struct file_operations *old_fops = file->f_op;//设上次的file_operations等于当前的
		file->f_op = new_fops;//设置系统的file_operations等于s->unit_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;
}

通过上面的代码和注释分析到,系统声卡之所以只有一个open(),里面是通过次设备号来调用

__look_for_unit()

函数,找到chains[chain]数组里的驱动声卡sound_unit结构体,然后来替换系统声卡的file_operations,实现偷天换日的效果。


__look_for_unit()

函数如下图所示:


static struct sound_unit *__look_for_unit(int chain, int unit)
{
	struct sound_unit *s;
	
	s=chains[chain];
	while(s && s->unit_minor <= unit)
	{
		if(s->unit_minor==unit)
			return s;
		s=s->next;
	}
	return NULL;
}

其中

chains[]

数组定义如下所示:

其中,

chains[0]

存放的Mixers(混音),实现调节音量,高音等,就是我们VM8976的控制接口


chains[3]

存放的DSP,用来实现音频输入输出,就是我们VM8976的I2S接口

显然VM8976的驱动有两个,需要将2个file_operaions放入chains[0]和chains[3]数组里,供给系统的open()来调用



3.3 我们以DSP为例,搜索chains[3]来看看

如上图所示,显然register_sound_dsp()函数就是被我们声卡驱动调用的,用来注册dsp设备节点,继续进入

sound_insert_unit()

函数看看

3.4 sound_insert_unit()函数如下

static int sound_insert_unit(struct sound_unit **list, const struct file_operations *fops, int index, int low, int top, const char *name, umode_t mode, struct device *dev)
{
	struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL);//分配个新的sound_unit
	int r;

	if (!s)
		return -ENOMEM;
		
	spin_lock(&sound_loader_lock);

//__sound_insert_unit()里主要实现:将分配的新的s插入到chains[3]里,然后并放入fops操作结构体

	r = __sound_insert_unit(s, list, fops, index, low, top);
	spin_unlock(&sound_loader_lock);
	
	if (r < 0)
		goto fail;
	else if (r < SOUND_STEP)
		sprintf(s->name, "sound/%s", name);
	else
		sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP);

	device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor),
		      s->name+6);
    //s->name+6="dsp",也就是在/dev下创建"dsp"的设备节点

	return r;

 fail:
	kfree(s);
	return r;
}

所以,register_sound_dsp()函数用来创建

/dev/dsp

设备节点,同时将dsp相关的file_operations放入

chains[3]

里面



3.5 同样,Mixers的驱动流程也是这样,它的函数是register_sound_mixer(),如下图所示:

也是创建

/dev/mixer

设备节点,同时将dsp相关的file_operations放入

chains[0]

里面



3.6 接下来,我们便搜索register_sound_dsp()函数,看看被哪些声卡驱动调用


uda1341声卡

和WM8976声卡非常相似,音频都是I2S接口,就只有控制部分不一样


uda1341声卡

的硬件,如下图所示:

它的

控制引脚

只有3个:


L3MODE:

模式引脚,为高表示传输的是数据,为低表示传输的是寄存器地址


L3CLOCK:

时钟引脚


L3DATA:

数据输入/输出引脚

控制接口的时序如下所示:

和WM8976的控制时序完全不一样,WM8976控制时序如下所示:

所以接下来,便修改s3c2410-uda1341.c的控制部分,

来移植wm8976驱动



4、移植wm8976驱动

首先进入uda1341的probe()函数


static int s3c2410iis_probe(struct device *dev) 
{
	struct platform_device *pdev = to_platform_device(dev);
	struct resource *res;
	unsigned long flags;

	printk ("s3c2410iis_probe...\n");
    /* 获取资源 */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		printk(KERN_INFO PFX "failed to get memory region resouce\n");
		return -ENOENT;
	}

	iis_base = (void *)S3C24XX_VA_IIS ;
	if (iis_base == 0) {
		printk(KERN_INFO PFX "failed to ioremap() region\n");
		return -EINVAL;
	}

    /* 获取I2S时钟,并使能 */
	iis_clock = clk_get(dev, "iis");
	if (iis_clock == NULL) {
		printk(KERN_INFO PFX "failed to find clock source\n");
		return -ENOENT;
	}

	clk_enable(iis_clock);
    /* 进入临界区,禁止中断,并保存中断状态 */
	local_irq_save(flags);
	
    /* 设置管脚功能 */
	/* 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);

    /* 退出临界区,使能中断,并恢复之前保存的flags中断转台 */
	local_irq_restore(flags);

    /* 设置2440的I2S寄存器 */
	init_s3c2410_iis_bus();

    /* 初始化uda1341声卡的控制部分 */
	//init_uda1341();
	init_wm8976();

    /* 设置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;
	}

    /* 创建/dev/dsp,/dev/mixer两个设备节点,并将smdk2410_audio_fops和smdk2410_mixser_fops两个file_operations放入chains[0]和chains[3]里,供给内核的声卡系统调用 */
	audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1);
	audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1);

	printk(AUDIO_NAME_VERBOSE " initialized\n"); 

	return 0;
}

从上面的代码来看,uda1341的管脚和wm8976的管脚连接都是一样的,只有

init_uda1341()

不一样,里面是初始化uda1341的控制引脚接口,所以需要屏蔽,然后自己来写个init_wm8976()函数



4.1 写init_wm8976()函数之前需要先写一个寄存器操作函数

参考wm8976芯片手册时序图:

所以,代码如下:


//写寄存器
static void wm8976_write_reg(unsigned char reg, unsigned int data)
{
	int i;
	unsigned long flags;

	//reg值:寄存器地址(7位),date值(9位)
	//把这两个值组成一个16位的数据
	unsigned short val = (reg<<9) | (data & 0x1ff);

	//先都设为高电平,方便操作
	//GPB4--L3CLOCK,GPB3--L3DATA,GPB2--L3MODE
	s3c2410_gpio_setpin(S3C2410_GPB2,1);
	s3c2410_gpio_setpin(S3C2410_GPB3,1);
	s3c2410_gpio_setpin(S3C2410_GPB4,1);

	local_irq_save(flags);//先关中断

	for (i = 0; i < 16; i++)
	{
		//先传输最高位
		if(val & (1<<15))//如果这一位是1
		{
			/* 一个周期 */
			s3c2410_gpio_setpin(S3C2410_GPB4,0);//时钟低电平
			s3c2410_gpio_setpin(S3C2410_GPB3,1);//数据线输入1
			udelay(1);//延迟
			s3c2410_gpio_setpin(S3C2410_GPB4,1);//时钟高电平
		}
		else//如果这一位是0
		{
			/* 一个周期 */
			s3c2410_gpio_setpin(S3C2410_GPB4,0);//时钟低电平
			s3c2410_gpio_setpin(S3C2410_GPB3,0);//数据线输入0
			udelay(1);//延迟
			s3c2410_gpio_setpin(S3C2410_GPB4,1);//时钟高电平
		}
		
		val = val << 1;//左移一位,把下一位移过来
	}

	//传输完成后,让CSB信号产生低脉冲,写入wm8976
	s3c2410_gpio_setpin(S3C2410_GPB2,0);
	udelay(1);//延迟
	s3c2410_gpio_setpin(S3C2410_GPB2,1);//过一阵子恢复高电平
	//把其他引脚也恢复高电平状态
	s3c2410_gpio_setpin(S3C2410_GPB3,1);
	s3c2410_gpio_setpin(S3C2410_GPB4,1);

	local_irq_restore(flags);//结束开中断

}



4.2 参考wm8976.pdf第87页,来初始化wm8976,使能输出声道1,2,混响器等


static void init_wm8976(void)
{
	uda1341_volume = 57;
	uda1341_boost = 0;

	/* software reset 软件复位 */
	wm8976_write_reg(0, 0);

	//芯片手册说,先写地址3,再写地址1,最后写地址2
	/* bit[6-5]:OUT2的左/右声道打开 
	 * bit[3-2]:左/右通道输出混音打开
	 * bit[1-0]:左/右DAC打开
	 */
	wm8976_write_reg(0x3,  0x6f);

	//bit[4]:使能输出麦克风电压,为了方便将其他设置为1
	wm8976_write_reg(0x1,  0x1f);
	
	wm8976_write_reg(0x2,  0x185);
	wm8976_write_reg(0x6,  0x0);
	wm8976_write_reg(0x4,  0x10);
	wm8976_write_reg(0x2B, 0x10);
	wm8976_write_reg(0x9,  0x50);
	wm8976_write_reg(0xD,  0x21);
	wm8976_write_reg(0x7,  0x01);
}

wm8976初始化修改完成后,还需要修改音量控制等函数,之前就分析了uda1341的probe()函数,里面会注册dsp、mixer设备节点



/dev/dsp

用来播放和录音,由于uda1341和wm8976都用了I2S接口,所以dsp的file_operations不需要修改



/dev/mixer

用来控制音量,调低音,高音等,由于wm8976的控制接口不一样,所以需要修改mixer的file_operations->ioctl()函数

4.3 mixer的file_operations->ioctl()函数如下所示:

static int smdk2410_mixer_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)
{
       int ret;
       long val = 0; 

switch (cmd) {
       case SOUND_MIXER_INFO:             //CASE : 获取声卡的描述信息
{
       mixer_info info;
       strncpy(info.id, "UDA1341", sizeof(info.id));
       strncpy(info.name,"Philips UDA1341", sizeof(info.name));
       info.modify_counter = audio_mix_modcnt;        
       return copy_to_user((void *)arg, &info, sizeof(info));  //上传用户层
}
    ... ...

case SOUND_MIXER_WRITE_VOLUME:                //CASE: 写音量,音量值为0~99
ret = get_user(val, (long *) arg);  //读用户层的数据,并放在val里
            if (ret)
                   return ret;

     uda1341_volume = 63 - (((val & 0xff) + 1) * 63) / 100; //转换为寄存器音量值
     uda1341_l3_address(UDA1341_REG_DATA0); //写入音量的寄存器地址
     uda1341_l3_data(uda1341_volume);          //写入转换后的寄存器值数据          
     break;

case SOUND_MIXER_READ_VOLUME:                   //CASE:  读音量,音量值为0~100
     val = ((63 - uda1341_volume) * 100) / 63;  //将寄存器音量值转换为原始数据
     val |= val << 8;                  
     return put_user(val, (long *) arg);                     //上传音量值

case SOUND_MIXER_READ_IGAIN:                     //CASE: 读(in gain)混音输入增益
     val = ((31- mixer_igain) * 100) / 31;                 
     return put_user(val, (int *) arg);

case SOUND_MIXER_WRITE_IGAIN:                    //CASE:   写(in gain)混音输入增益
    ret = get_user(val, (int *) arg);
if (ret)
            return ret;
     mixer_igain = 31 - (val * 31 / 100);
     /* use mixer gain channel 1*/
     uda1341_l3_address(UDA1341_REG_DATA0);
     uda1341_l3_data(EXTADDR(EXT0));                  
     uda1341_l3_data(EXTDATA(EXT0_CH1_GAIN(mixer_igain)));     
     break;

default:
      DPRINTK("mixer ioctl %u unknown\n", cmd);
      return -ENOSYS;

    }
    return 0;
}

从上面的代码来看,显然接下还要修改以下几个与控制接口相关的case:


  • case SOUND_MIXER_WRITE_VOLUME:

    写音量

  • case SOUND_MIXER_READ_VOLUME:

    读音量

  • case SOUND_MIXER_READ_IGAIN:

    读(in gain)混音输入增益

  • case SOUND_MIXER_WRITE_IGAIN:

    写(in gain)混音输入增益



4.4 修改“




case




SOUND_MIXER_WRITE_VOLUME




”和”




case SOUND_MIXER_READ_VOLUME






如下图所示(参考wm8976手册的P86页):

其中50,51对应的就是左右混音控制寄存器

我们以50左声道混音寄存器为例:

如上图所示:

bit8~6:混音输入增益,默认值为0,最大值为7

所以修改的内容如下所示:

1)首先修改混音输入增益的的初始默认值为0,如下所示

2)修改”case SOUND_MIXER_READ_IGAIN”和”case SOUND_MIXER_WRITE_IGAIN:”

		case SOUND_MIXER_READ_IGAIN:
			val = ((31- mixer_igain) * 100) / 31;
			return put_user(val, (int *) arg);

		case SOUND_MIXER_WRITE_IGAIN:
			ret = get_user(val, (int *) arg);
			if (ret)
				return ret;
			mixer_igain = 31 - (val * 31 / 100);
			/* use mixer gain channel 1*/
			//uda1341_l3_address(UDA1341_REG_DATA0);
			//uda1341_l3_data(EXTADDR(EXT0));
			//uda1341_l3_data(EXTDATA(EXT0_CH1_GAIN(mixer_igain)));
			break;



5、配置,修改内核文件



5.1 make menuconfig配置内核


-> Device Drivers


-> Sound


-> Advanced Linux Sound Architecture  // 兼容OSS


-> Advanced Linux Sound Architecture


-> System on Chip audio support


<*> I2S of the Samsung S3C24XX chips              //*:将/linux-2.6.22.6/sound/soc/s3c24xx下的makefile指定的文件加入内核里



5.2 将修改好的s3c-wm8976.c放入/linux-2.6.22.6/sound/soc/s3c24xx目录下



5.3 修改该目录下的makefile

obj-y += s3c2410-uda1341.o

改为:


obj-y += s3c-wm8976.o



5.4 make uImage,如下图所示,可以看到内核已经被编译



5.5 最后下载并启动新的内核,如下图所示,可以看到该两个设备节点



6、测试与运行



6.1 使用wav测试声卡

wav是属于一个未经压缩的音频文件(无损),所以直接调用给我们声卡播放



播放:


cat Windows.wav > /dev/dsp



录音


(第一次测试时,还需要修改下驱动才行,因为声音太小了):


cat /dev/dsp > sound.bin

//然后对着麦克风说话


ctrl+c

//退出


cat sound.bin > /dev/dsp

//就可以听到录下的声音



6.2 使用madplay应用程序测试声卡

Madplay是一个根据MAD算法写的MP3播放器,而MP3属于高压缩比(11:1)的文件,所以需要madplay解码后才能给我们声卡播放,使用之前,需要先来移植madplay



步骤如下:


1)首先下载并解压3个文件


  • libid3tag-0.15.1b.tar.gz

    mp3的解码库

  • libmad-0.15.1b.tar.gz

    madplay的文件库

  • madplay-0.15.2b.tar.gz

    madplay播放器的源码


2)先创建安装目录mkdir tmp


3)接下来先安装2个库


(这里面可能会遇到错误,请参考



https://blog.csdn.net/xiaodingqq/article/details/82153464


cd libid3tag-0.15.1b




cd libmad-0.15.1b


./configure –host=arm-linux –prefix=/work/drivers_and_test/21th_sound/app/tmp

//修改configure,设置编译器,设置安装路径


make

//编译


make install

//安装到app/tmp目录下


4)最后安装madplay-0.15.2b


cd madplay-0.15.2b


./configure –host=arm-linux –prefix=/work/drivers/21th_sound/app/tmp CPPFLAGS=”-I /work/drivers/21th_sound/app/tmp/include” LDFLAGS=”-L/work/drivers/21th_sound/app/tmp/lib”

//CFLAGS:指定头文件,LDFLASG:指定库文件


make


make install


5)把/app/tmp/bin目录下的所有文件,复制I到开发板最小根文件nfs的bin目录下


cd app/tmp/bin


cp * /work/nfsroot/first_fs/bin


6)把/app/tmp/lib目录下的带so文件,复制到开发板最小根文件nfs的bin目录下


cd app/tmp/lib


cp *so* /work/nfsroot/first_fs/lib -d

//带链接复制


7)使用madplay播放mp3


madplay –tty-control 1.mp3

//用按键控制声音

播放过程中不断按小键盘的减号(“-“)会降低音量

不断按小键盘的加号(“+”)会降低音量


madplay 1.mp3 2.mp3 3.mp3

//循环播放3首歌

并可以使用热键来控制,常用的有以下几种:

  • f          上一首
  • b         下一首
  • i           获取播放时间和播放歌曲名
  • p          播放暂停
  • s          停止
  • +          音量加
  • –           音量减



版权声明:本文为xiaodingqq原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。