你知道Linux 音频设备驱动架构及应用编程?

网友投稿 1243 2022-11-14

你知道Linux 音频设备驱动架构及应用编程?

ALSA和OSS最大的不同之处在于ALSA是由志愿者维护的自由项目,而OSS则是由公司提供的商业产品,因此在对硬件的适应程度上OSS要优于ALSA,它能够支持的声卡种类更多。ALSA虽然不及OSS运用得广泛,但却具有更加友好的编程接口,并且完全兼容于OSS,对应用程序员来讲无疑是一个更佳的选择。

两种音频编程接口驱动的组成如下:

1. 数字音频设备

音频编解码器是数字音频系统的核心,衡量它的指标主要有: • 采样频率        采样的过程就是将通常的模拟音频信号的电信号转换成二进制码0 和1 的过程,这些0 和1 便构成了数字音频文件。如图17.2 中的正弦曲线代表原始音频曲线,方格代表采样后得到的结果,二者越吻合说明采样结果越好。采样频率是每秒钟的采样次数,我们常说的 44.1kHz 采样频率就是每秒钟采样44100 次。理论上采样频率越高,转换精度越高,目前主流的采样频率是48kHz。 • 量化精度        量化精度是指对采样数据分析的精度,比如24bit 量化精度就是是将标准电平信号按照2 的24 次方进行分析,也就是说将图17.2 中的纵坐标等分为224 等分。量化精度越高,声音就越逼真。

2.2 IIS 接口      IIS 接口(Inter-IC Sound)在20 世纪80 年代首先被飞利浦用于消费音频,并在一个称为LRCLK(Left/RightCLOCK)的信号机制中经过多路转换,将两路音频信号变成单一的数据队列。当LRCLK 为高时,左声道数据被传输;LRCLK 为低时,右声道数据被传输。与PCM 相比,IIS 更适合于立体声系统。对于多通道系统,在同样的BCLK 和LRCLK 条件下,并行执行几个数据队列也是可能的。

PCM、IIS 和AC97 各有其优点和应用范围,例如在CD、MD、MP3 随身听多采用IIS 接口,移动电话会采用PCM 接口,具有音频功能的PDA 则多使用和PC 一样的AC'97 编码格式。

3. Linux OSS 音频设备驱动及应用编程 3.1 OSS 驱动的组成      OSS 标准中有2 个最基本的音频设备:mixer(混音器)和DSP(数字信号处理器)。

在声卡的硬件电路中,mixer 是一个很重要的组成部分,它的作用是将多个信号组合或者叠加在一起,对于不同的声卡来说,其混音器的作用可能各不相同。OSS 驱动中,/dev/mixer 设备文件是应用程序对mixer进行操作的软件接口。

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

由于混音器的操作不符合典型的读/写操作模式,因此除了open()和close()两个系统调用之外,大部分的操作都是通过ioctl()系统调用来完成的。与/dev/dsp 不同,/dev/mixer 允许多个应用程序同时访问,并且混音器的设置值会一直保持到对应的设备文件被关闭为止。DSP 也称为编解码器,实现录音(录音)和放音(播放),其对应的设备文件是/dev/dsp 或/dev/sound/dsp。

static int mixdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)

{

...

switch (cmd)

{

case SOUND_MIXER_READ_MIC:

...

case SOUND_MIXER_WRITE_MIC:

...

case SOUND_MIXER_WRITE_RECSRC:

...

case SOUND_MIXER_WRITE_MUTE:

...

}

...

return mixer_ioctl(codec, cmd, arg);

}

3.3 DSP 接口         int register_sound_dsp(struct file_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()函数从用户空间拷贝音频数据到内核空间缓冲区并最终发送到音频控制器。

在OSS 驱动中,建立存放音频数据的环形缓冲区(ring buffer)通常是值得推荐的方法。此外,在OSS 驱动中,一般会将1 个较大的DMA 缓冲区分成若干个大小相同的块(这些块也被称为“段”,即fragment),驱动程序使用DMA 每次在声音缓冲区和声卡之间搬移一个fragment。在用户空间,可以使用ioctl()系统调用来调整块的大小和个数。除了read()、write()和ioctl()外,dsp 接口的poll()函数通常也需要被实现,以向用户反馈目前能否读写DMA 缓冲区。在OSS 驱动初始化过程中,会调用register_sound_dsp()和register_sound_mixer()注册dsp 和mixer 设备;在模块卸载的时候,调用的代码如下:     OSS 驱动初始化注册dsp 和mixer设备:

[cpp] view plain copy

static int myoss_init(void)

{

struct oss_state *s = &myoss_state;

...

//注册dsp 设备

if ((audio_dev_dsp = register_sound_dsp(&xxx_audio_fops, - 1)) < 0)

goto err_dev1;

//注册mixer 设备

if ((audio_dev_mixer = register_sound_mixer(&xxx_mixer_fops, - 1)) < 0)

goto err_dev2;

...

}

void __exit xxx_exit(void)

{

//注销dsp 和mixer 设备接口

unregister_sound_dsp(audio_dev_dsp);

unregister_sound_mixer(audio_dev_mixer);

...

}

3.4 OSS 用户空间编程 1、DSP 编程 对OSS 驱动声卡的编程使用Linux 文件接口函数,DSP 接口的操作一般包括如下几个步骤: ① 打开设备文件/dev/dsp        采用何种模式对声卡进行操作也必须在打开设备时指定,对于不支持全双工的声卡来说,应该使用只读或者只写的方式打开,只有那些支持全双工的声卡,才能以读写的方式打开,这还依赖于驱动程序的具体实现。Linux 允许应用程序多次打开或者关闭与声卡对应的设备文件,从而能够很方便地在放音状态和录音状态之间进行切换。 ② 如果有需要,设置缓冲区大小         运行在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 控制命令可以设置采样格式。对于大多数声卡来说,其支持的采样频率范围一般为5kHz 到44.1kHz 或者48kHz,但并不意味着该范围内的所有连续频率都会被硬件支持,在Linux 下进行音频编程时最常用到的几种采样频率是11025Hz、16000Hz、22050Hz、32000Hz 和44100Hz。使用SNDCTL_DSP_SPEED IO 控制命令可以设置采样频率。 ⑤ 读写/dev/dsp 实现播放或录音       下面代码实现了利用/dev/dsp 接口进行声音录制和播放的过程,它的功能是先录制几秒钟音频数据,将其存放在内存缓冲区中,然后再进行放音。

[cpp] view plain copy

#include

#include

#include

#include

#include

#include

#include

#define LENGTH 3   /* 存储秒数 */

#define RATE 8000  /* 采样频率 */

#define SIZE 8     /* 量化位数 */

#define CHANNELS 1 /* 声道数目 */

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

unsigned char buf[LENGTH *RATE * SIZE * CHANNELS / 8];

int main()

{

int fd; /* 声音设备的文件描述符 */

int arg; /* 用于ioctl 调用的参数 */

int status; /* 系统调用的返回值 */

/* 打开声音设备 */

fd = open("/dev/dsp", O_RDWR);

if (fd < 0)

{

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 to set sample size");

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

arg = CHANNELS;

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

if (status == - 1)

perror("SOUND_PCM_WRITE_CHANNELS ioctl failed");

if (arg != CHANNELS)

perror("unable to set number of channels");

/* 设置采样率 */

arg = RATE;

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

if (status == - 1)

perror("SOUND_PCM_WRITE_WRITE ioctl failed");

/* 循环,直到按下Control-C */

while (1)

{

printf("Say something:");

status = read(fd, buf, sizeof(buf)); /* 录音 */

if (status != sizeof(buf))

perror("read wrong number of bytes");

printf("You said:");

status = write(fd, buf, sizeof(buf)); /* 放音 */

if (status != sizeof(buf))

perror("wrote wrong number of bytes");

/* 在继续录音前等待放音结束 */

status = ioctl(fd, SOUND_PCM_SYNC, 0);

if (status == - 1)

perror("SOUND_PCM_SYNC ioctl failed");

}

}

2、mixer 编程        声卡上的混音器由多个混音通道组成,它们可以通过驱动程序提供的设备文件/dev/mixer 进行编程。对混音器的操作一般都通过ioctl()系统调用来完成,所有控制命令都以SOUND_MIXER 或者MIXER 开头,下表列出了常用的混音器控制命令。

命 令              作 用 SOUND_MIXER_VOLUME 主音量调节 SOUND_MIXER_BASS 低音控制 SOUND_MIXER_TREBLE 高音控制 SOUND_MIXER_SYNTH FM 合成器 SOUND_MIXER_PCM 主D/A 转换器 SOUND_MIXER_SPEAKER PC 喇叭 SOUND_MIXER_LINE 音频线输入 SOUND_MIXER_MIC 麦克风输入 SOUND_MIXER_CD CD 输入 SOUND_MIXER_IMIX 放音音量 SOUND_MIXER_ALTPCM 从D/A 转换器 SOUND_MIXER_RECLEV 录音音量 SOUND_MIXER_IGAIN 输入增益 SOUND_MIXER_OGAIN 输出增益 SOUND_MIXER_LINE1 声卡的第1 输入 SOUND_MIXER_LINE2 声卡的第2 输入 SOUND_MIXER_LINE3 声卡的第3 输入        对声卡的输入增益和输出增益进行调节是混音器的一个主要作用,目前大部分声卡采用的是8 位或者16 位的增益控制器,声卡驱动程序会将它们变换成百分比的形式,也就是说无论是输入增益还是输出增益,其取值范围都是从0 到100。

• SOUND_MIXER_READ        在进行混音器编程时,可以使用 SOUND_MIXER_READ 宏来读取混音通道的增益大小,例如如下代码可以获得麦克风的输入增益: ioctl(fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);        对于只有一个混音通道的单声道设备来说,返回的增益大小保存在低位字节中。而对于支持多个混音通道的双声道设备来说,返回的增益大小实际上包括两个部分,分别代表左、右两个声道的值,其中低位字节保存左声道的音量,而高位字节则保存右声道的音量。下面的代码可以从返回值中依次提取左右声道的增益大小: int left, right; left = vol & 0xff; right = (vol & 0xff00) >> 8;

• SOUND_MIXER_WRITE        如果想设置混音通道的增益大小,则可以通过SOUND_MIXER_WRITE 宏来实现,例如下面的语句可以用来设置麦克风的输入增益: vol = (right << 8) + left; ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);

• 查询Mixer信息        声卡驱动程序提供了多个ioctl()系统调用来获得混音器的信息,它们通常返回一个整型的位掩码,其中每一位分别代表一个特定的混音通道,如果相应的位为1,则说明与之对应的混音通道是可用的。通过 SOUND_MIXER_READ_DEVMASK 返回的位掩码查询出能够被声卡支持的每一个混音通道,而通过SOUND_MIXER_READ_RECMAS 返回的位掩码则可以查询出能够被当作录音源的每一个通道。例如,如下代码可用来检查CD 输入是否是一个有效的混音通道: ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask); if (devmask & SOUND_MIXER_CD)    printf("The CD input is supported");

大多数声卡提供了多个录音源,通过 SOUND_MIXER_READ_RECSRC 可以查询出当前正在使用的录音源,同一时刻可使用2个或2个以上的录音源,具体由声卡硬件本身决定。相应地,使用 SOUND_MIXER_WRITE_RECSRC可以设置声卡当前使用的录音源,如下代码可以将CD输入作为声卡的录音源使用: devmask = SOUND_MIXER_CD; ioctl(fd, SOUND_MIXER_WRITE_RECSRC, &devmask);

此外,所有的混音通道都有单声道和双声道的区别,如果需要知道哪些混音通道提供了对立体声的支持,可以通过SOUND_MIXER_READ_STEREODEVS 来获得。 以下代码实现了利用/dev/mixer 接口对混音器进行编程的过程,该程序可对各种混音通道的增益进行调节。

[cpp] view plain copy

#include

#include

#include

#include

#include

#include

/* 用来存储所有可用混音设备的名称 */

const char *sound_device_names[] = SOUND_DEVICE_NAMES;

int fd; /* 混音设备所对应的文件描述符 */

int devmask, stereodevs; /* 混音器信息对应的bit 掩码 */

char *name;

/* 显示命令的使用方法及所有可用的混音设备 */

void usage()

{

int i;

fprintf(stderr, "usage: %s   "

"%s  ""Where  is one of:", name, name);

for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)

if ((1 << i) &devmask)

/* 只显示有效的混音设备 */

fprintf(stderr, "%s ", sound_device_names[i]);

fprintf(stderr, "");

exit(1);

}

int main(int argc, char *argv[])

{

int left, right, level; /* 增益设置 */

int status; /* 系统调用的返回值 */

int device; /* 选用的混音设备 */

char *dev; /* 混音设备的名称 */

int i;

name = argv[0];

/* 以只读方式打开混音设备 */

fd = open("/dev/mixer", O_RDONLY);

if (fd == - 1)

{

perror("unable to open /dev/mixer");

exit(1);

}

/* 获得所需要的信息 */

status = ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask);

if (status == - 1)

perror("SOUND_MIXER_READ_DEVMASK ioctl failed");

status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs);

if (status == - 1)

perror("SOUND_MIXER_READ_STEREODEVS ioctl failed");

/* 检查用户输入 */

if (argc != 3 && argc != 4)

usage();

/* 保存用户输入的混音器名称 */

dev = argv[1];

/* 确定即将用到的混音设备 */

for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)

if (((1 << i) &devmask) && !strcmp(dev, sound_device_names[i]))

break;

if (i == SOUND_MIXER_NRDEVICES)

{

/* 没有找到匹配项 */

fprintf(stderr, "%s is not a valid mixer device", dev);

usage();

}

/* 查找到有效的混音设备 */

device = i;

/* 获取增益值 */

if (argc == 4)

{

/* 左、右声道均给定 */

left = atoi(argv[2]);

right = atoi(argv[3]);

}

else

{

/* 左、右声道设为相等 */

left = atoi(argv[2]);

right = atoi(argv[2]);

}

/* 对非立体声设备给出警告信息 */

if ((left != right) && !((1 << i) &stereodevs))

{

fprintf(stderr, "warning: %s is not a stereo device", dev);

}

/* 将两个声道的值合到同一变量中 */

level = (right << 8) + left;

/* 设置增益 */

status = ioctl(fd, MIXER_WRITE(device), &level);

if (status == - 1)

{

perror("MIXER_WRITE ioctl failed");

exit(1);

}

/* 获得从驱动返回的左右声道的增益 */

left = level &0xff;

right = (level &0xff00) >> 8;

/* 显示实际设置的增益 */

fprintf(stderr, "%s gain set to %d%% / %d%%", dev, left, right);

/* 关闭混音设备 */

close(fd);

return 0;

}

编译上述程序为可执行文件mixer,执行./mixer 或./mixer 可设置增益,device 可以是vol、pcm、speaker、line、mic、cd、igain、line1、phin、video。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:qml截图
下一篇:qml之SwipeView
相关文章

 发表评论

暂时没有评论,来抢沙发吧~