Linux声卡的驱动实现原理及代码实现(播放和录音的实现)

一、声卡驱动

Linux下经常使用的声卡驱动程序主要有两种:OSS和ALSA。

在对硬件的适应程度上OSS要优于ALSA,它可以支持的声卡种类有很多。

ALSA尽管不及OSS运用得广泛,但却具有更加友好的编程接口,而且全然兼容于OSS。

二、Linux  OSS音频设备驱动

1、 OSS驱动的组成

OSS标准中有2个最主要的音频设备:mixer(混音器)和DSP(数字信号处理器)。

(1)在声卡的硬件电路中,mixer是一个非常重要的组成部分,它的作用是将多个信号组合或者叠加在一起,对于不同的声卡来说,其混音器的作用可能各不同样。

OSS驱动中。/dev/mixer设备文件是应用程序对mixer进行操作的软件接口。

混音器电路通常由两个部分组成:输入混音器(input mixer)和输出混音器(output mixer)。

输入混音器负责从多个不同的信号源接收模拟信号。模拟信号通过增益控制器和由软件控制的音量调节器后,在不同的混音通道中进行级别调制。然后被送到输入混音器中进行声音的合成。经过输入混音器处理后的信号仍然为模拟信号。它们将被送到A/D转换器进行数字化处理。

输出混音器的工作原理与输入混音器类似,也有多个信号源与混音器相连。并且事先都经过了增益调节。当输出混音器对全部的模拟信号进行了混合之后,通常还会有一个总控增益调节器来控制输出声音的大小,此外另一些音调控制器来调节输出声音的音调。经过输出混音器处理后的信号也是模拟信号,它们终会被送给喇叭或者其他的模拟输出设备。

因为混音器的操作不符合典型的读/写操作模式,因此除了 open()和close()两个系统调用之外,大部分的操作都是通过ioctl()系统调用来完成的。

(2)DSP也称为编解码器,实现录音(录音)和放音(播放)。其相应的设备文件是/dev/dsp或/dev/sound/dsp。OSS声卡驱动程序提供的 /dev/dsp是用于数字采样和数字录音的设备文件。向该设备写数据即意味着激活声卡上的D/A转换器进行放音,而向该设备读数据则意味着激活声卡上的 A/D转换器进行录音。

在从DSP设备读取数据时,从声卡输入的模拟信号经过A/D转换器变成数字采样后的样本,保存在声卡驱动程序的内核缓冲区中,当应用程序通过 read()系统调用从声卡读取数据时,保存在内核缓冲区中的数字采样结果将被拷贝到应用程序所指定的用户缓冲区中。

在向DSP设备写入数据时。数字信号会经过D/A转换器变成模拟信号。然后产生出声音。应用程序写入数据的速度应该至少等于声卡的采样频率,过慢会产生声音暂停或者停顿的现象。

不管是从声卡读取数据,或是向声卡写入数据,其实都具有特定的格式。如无符号8位、单声道、8KHz采样率,假设默认值无法达到要求,能够通过ioctl()系统调用来改变它们。通常说来,在应用程序中打开设备文件/dev/dsp之后。接下去就应该为其设置恰当的格式。然后才从声卡读取或者写入数据。

2、mixer接口

int register_sound_mixer(structfile_operations *fops, int dev);

上述函数用于注册1个混音器,第1个参数fops即是文件操作接口,第2个参数dev是设备编号,假设填入-1,则系统自己主动分配1个设备编号。

mixer 是 1个典型的字符设备,因此编码的主要工作是实现file_operations中的open()、ioctl()等函数。

mixer接口file_operations中的最重要函数是ioctl()。它实现混音器的不同IO控制命令。

3、DSP接口

int register_sound_dsp(structfile_operations *fops, int dev);

上述函数与register_sound_mixer()类似,它用于注册1个dsp设备,第1个参数fops即是文件操作接口,第2个参数dev是设备编号,假设填入-1。则系统自己主动分配1个设备编号。

dsp也是1个典型的字符设备。因此编码的主要工作是实现file_operations中的read()、write()、ioctl()等函数。

dsp接口file_operations中的read()和write()函数很重要。read()函数从音频控制器中获取录音数据到缓冲区并复制到用户空间,write()函数从用户空间拷贝音频数据到内核空间缓冲区并终于发送到音频控制器。

dsp接口file_operations中的ioctl()函数处理对采样率、量化精度、DMA缓冲区块大小等参数设置IO控制命令的处理。

在数据从缓冲区复制到音频控制器的过程中,一般会使用DMA。DMA对声卡而言很重要。比如,在放音时,驱动设置完DMA控制器的源数据地址(内存中 DMA缓冲区)、目的地址(音频控制器FIFO)和DMA的数据长度,DMA控制器会自己主动发送缓冲区的数据填充FIFO。直到发送完对应的数据长度后才中断一次。

在OSS驱动初始化过程中,会调用register_sound_dsp()和register_sound_mixer()注册dsp和mixer设备。

在模块卸载的时候。会调用unregister_sound_dsp(audio_dev_dsp)和unregister_sound_mixer(audio_dev_mixer)。

4、OSS用户空间编程

(1)DSP编程

DSP接口的操作一般包含例如以下几个步骤:

①打开设备文件/dev/dsp

采用何种模式对声卡进行操作也必须在打开设备时指定,对于不支持全双工的声卡来说,应该使用仅仅读或者仅仅写的方式打开。

②假设有须要,设置缓冲区大小

执行在Linux内核中的声卡驱动程序专门维护了一个缓冲区,其大小会影响到放音和录音时的效果,使用ioctl()系统调用能够对它的尺寸进行恰当的设置。

③设置声道(channel)数量

依据硬件设备和驱动程序的详细情况。能够设置为单声道或者立体声。

④设置采样格式和采样频率

采样格式包含AFMT_U8(无符号8位)、AFMT_S8(有符号8位)、AFMT_U16_LE(小端模式,无符号16位)、 AFMT_U16_BE(大端模式,无符号16位)、AFMT_MPEG、AFMT_AC3等。使用SNDCTL_DSP_SETFMT IO控制命令能够设置采样格式。
在 Linux下进行音频编程时最经常使用到的几种采样频率是11025Hz、16000Hz、22050Hz、32000Hz 和44100Hz。使用SNDCTL_DSP_SPEED IO控制命令能够设置采样频率。

⑤读写/dev/dsp实现播放或录音

(2) mixer编程

声卡上的混音器由多个混音通道组成,它们能够通过驱动程序提供的设备文件/dev/mixer进行编程。

对声卡的输入增益和输出增益进行调节是混音器的一个主要作用,大部分声卡采用的是8位或者16位的增益控制器,声卡驱动程序会将它们转换成百分比的形式。也就是说不管是输入增益还是输出增益。其取值范围都是从0--100。

三、Linux下实现播放和录音

注意驱动程序中的默认参数,应用程序可以通过ioctl()函数设置新的取值。

 Open()函数:采用何种模式对声卡进行操作也必须在打开设备时指定,对于不支持全双工的声卡来说,应该使用只读或者只写的方式打开,只有那些支持全双工的声卡,才能以读写的方式打开,这还依赖于驱动程序的具体实现。open_mode有三种选择:O_RDONLY,O_WRONLY和O_RDWR,分别表示只读、只写和读写。OSS建议尽量使用只读或只写,只有在全双工的情况下(即录音和放音同时)才使用读写模式。Linux

允许应用程序多次打开或者关闭与声卡对应的设备文件,从而能够很方便地在放音状态和录音状态之间进行切换。

注意,用户始终要读/写一个完整的采样。例如一个16-bit的立体声模式下,每个采样有4个字节,所以应用程序每次必须读/写4的倍数个字节。

源码如下:

#include

#define LENGTH 3 // 存储秒数

#define RATE 44100 // 采样频率

#define SIZE 16 // 量化位数

#define CHANNELS 2 // 声道数目

/* 用于保存数字音频数据的内存缓冲区*/

unsigned charbuf[LENGTH*RATE*SIZE*CHANNELS/8];

int main()

{

intfd; // 声音设备的文件描述符

intarg; // 用于ioctl调用的参数

intstatus; // 系统调用的返回值

/*打开声音设备 */

fd= open("/dev/sound/dsp", O_RDONLY);

if(fd< 0)

{

perror("openof /dev/sound/dsp failed");

exit(1);

}

/*设置采样时的量化位数 */

arg= SIZE;

status= ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);

if(status== -1)

perror("SOUND_PCM_WRITE_BITSioctl failed");

if(arg!= SIZE)

perror("unableto set sample size");

/*设置采样时的声道数目 */

arg= CHANNELS;

status= ioctl(fd, SNDCTL_DSP_STEREO, &arg);

if(status== -1)

perror("SNDCTL_DSP_STEREOioctl failed");

if(arg!= CHANNELS)

perror("unableto set number of channels");

/*设置采样时的采样频率 */

arg= RATE;

status= ioctl(fd, SNDCTL_DSP_SPEED, &arg);

if(status== -1)

perror("SNDCTL_DSP_SPEEDioctl failed");

if(arg!= RATE)

perror("unableto set rate");

printf("Saysomething:\n");

status= read(fd, buf, sizeof(buf)); //recording

if(status!= sizeof(buf))

perror("readwrong number of bytes");

printf("Yousaid:\n");

close(fd);

fd= open("/dev/sound/dsp", O_WRONLY);

if(fd< 0)

{

perror("openof /dev/sound/dsp failed");

exit(1);

}

/*设置采样时的量化位数 */

arg= SIZE;

status= ioctl(fd, SOUND_PCM_WRITE_BITS, &arg);

if(status == -1)

perror("SOUND_PCM_WRITE_BITS ioctl failed");

if(arg != SIZE)

perror("unable toset sample size");

/*设置采样时的声道数目 */

arg = CHANNELS;

status = ioctl(fd, SNDCTL_DSP_STEREO,&arg);

if(status == -1)

perror("SNDCTL_DSP_STEREO ioctl failed");

if(arg != CHANNELS)

perror("unable toset number of channels");

/* 设置采样时的采样频率 */

arg = RATE;

status = ioctl(fd,SNDCTL_DSP_SPEED, &arg);

if(status == -1)

perror("SNDCTL_DSP_SPEED ioctl failed");

if(arg != RATE)

perror("unable toset rate");

status= write(fd, buf, sizeof(buf)); //playing

if(status!= sizeof(buf))

perror("wrotewrong number of bytes");

close(fd);

return0;

}

猜你喜欢

转载自blog.csdn.net/m0_72237363/article/details/128313574