【Linux系统编程应用】Linux音频编程实战(一)
来源:互联网 发布:庄家抬轿指标源码 编辑:程序博客网 时间:2024/04/29 12:37
在Linux下进行音频编程时,重点在于如何正确地操作声卡驱动程序所提供的各种设备文件,由于涉及到的概念和因素比较多,所以遵循一个通用的框架无疑将有助于简化应用程序的设计。
1 DSP编程
对声卡进行编程时首先要做的是打开与之对应的硬件设备,这是借助于open系统调用来完成的,并且一般情况下使用的是/dev/dsp文件。采用何种模式对声卡进行操作也必须在打开设备时指定,对于不支持全双工的声卡来说,应该使用只读或者只写的方式打开,只有那些支持全双工的声卡,才能以读写的方式打开,并且还要依赖于驱动程序的具体实现。Linux允许应用程序多次打开或者关闭与声卡对应的设备文件,从而能够很方便地在放音状态和录音状态之间进行切换,建议在进行音频编程时只要有可能就尽量使用只读或者只写的方式打开设备文件,因为这样不仅能够充分利用声卡的硬件资源,而且还有利于驱动程序的优化。下面的代码示范了如何以只写方式打开声卡进行放音(playback)操作:
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(void){ int fd = -1; fd = open("/dev/dsp", O_RDWR); if (-1 == fd) { perror("open"); goto err0; } close(fd); return 0;err0: return -1;}
int setting = 0xnnnnssss;int result = ioctl(handle, SNDCTL_DSP_SETFRAGMENT, &setting);if (result == -1) {perror("ioctl buffer size");return -1;}// 检查设置值的正确性
在设置缓冲区大小时,参数setting实际上由两部分组成,其低16位标明缓冲区的尺寸,相应的计算公式为buffer_size = 2^ssss,即若参数setting低16位的值为16,那么相应的缓冲区的大小会被设置为65536字节。参数setting的高16位则用来标明分片(fragment)的最大序号,它的取值范围从2一直到0x7FFF,其中0x7FFF表示没有任何限制。
接下来要做的是设置声卡工作时的声道(channel)数目,根据硬件设备和驱动程序的具体情况,可以将其设置为0(单声道,mono)或者1(立体声,stereo)。下面的代码示范了应该怎样设置声道数目:
int channels = 0; // 0=mono 1=stereoint result = ioctl(handle, SNDCTL_DSP_STEREO, &channels);if ( result == -1 ) {perror("ioctl channel number");return -1;}if (channels != 0) {// 只支持立体声}
采样格式和采样频率是在进行音频编程时需要考虑的另一个问题,声卡支持的所有采样格式可以在头文件soundcard.h中找到,而通过ioctl系统调用则可以很方便地更改当前所使用的采样格式。下面的代码示范了如何设置声卡的采样格式:
int format = AFMT_U8;int result = ioctl(handle, SNDCTL_DSP_SETFMT, &format);if ( result == -1 ) {perror("ioctl sample format");return -1;}// 检查设置值的正确性
声卡采样频率的设置也非常容易,只需在调用ioctl时将第二个参数的值设置为SNDCTL_DSP_SPEED,同时在第三个参数中指定采样频率的数值就行了。对于大多数声卡来说,其支持的采样频率范围一般为5kHz到44.1kHz或者48kHz,但并不意味着该范围内的所有频率都会被硬件支持,在Linux下进行音频编程时最常用到的几种采样频率是11025Hz、16000Hz、22050Hz、32000Hz和44100Hz。下面的代码示范了如何设置声卡的采样频率:
int rate = 22050;int result = ioctl(handle, SNDCTL_DSP_SPEED, &rate);if ( result == -1 ) {perror("ioctl sample format");return -1;}// 检查设置值的正确性
2 Mixer编程
声卡上的混音器由多个混音通道组成,它们可以通过驱动程序提供的设备文件/dev/mixer进行编程。对混音器的操作是通过ioctl系统调用来完成的,并且所有控制命令都由SOUND_MIXER或者MIXER开头,表1列出了常用的几个混音器控制命令:
表1 混音器命令
int vol;ioctl(fd, SOUND_MIXER_READ(SOUND_MIXER_MIC), &vol);printf("Mic gain is at %d %%\n", vol);
对于只有一个混音通道的单声道设备来说,返回的增益大小保存在低位字节中。而对于支持多个混音通道的双声道设备来说,返回的增益大小实际上包括两个部分,分别代表左、右两个声道的值,其中低位字节保存左声道的音量,而高位字节则保存右声道的音量。下面的代码可以从返回值中依次提取左右声道的增益大小:
int left, right;left = vol & 0xff;right = (vol & 0xff00) >> 8;printf("Left gain is %d %%, Right gain is %d %%\n", left, right);
vol = (right << 8) + left;ioctl(fd, SOUND_MIXER_WRITE(SOUND_MIXER_MIC), &vol);
在编写实用的音频程序时,混音器是在涉及到兼容性时需要重点考虑的一个对象,这是因为不同的声卡所提供的混音器资源是有所区别的。声卡驱动程序提供了多个ioctl系统调用来获得混音器的信息,它们通常返回一个整型的位掩码(bitmask),其中每一位分别代表一个特定的混音通道,如果相应的位为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");
如果进一步还想知道其是否是一个有效的录音源,则可以使用如下语句:
ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask);if (recmask & SOUND_MIXER_CD)printf("The CD input can be a recording source");
目前大多数声卡提供多个录音源,通过SOUND_MIXER_READ_RECSRC可以查询出当前正在使用的录音源,同一时刻能够使用几个录音源是由声卡硬件决定的。类似地,使用SOUND_MIXER_WRITE_RECSRC可以设置声卡当前使用的录音源,例如下面的代码可以将CD输入作为声卡的录音源使用:
devmask = SOUND_MIXER_CD;ioctl(fd, SOUND_MIXER_WRITE_DEVMASK, &devmask);
3 DSP录音和放音程序
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/ioctl.h>#include <linux/soundcard.h>#define LEN 3 /*存储秒数*/#define RATE 8000 /*采样频率*/#define SIZE 8 /*量化位数*/#define CHANNELS 1 /*声道数目*/unsigned char buf[LEN * RATE * SIZE * CHANNELS / 8];int main(void){ int fd = -1; int ret = -1; int setting = 0x10000; int args; fd = open("/dev/dsp", O_RDWR); if (-1 == fd) { perror("open"); goto err0; } //设置量化位数 args = SIZE; ret = ioctl(fd, SOUND_PCM_WRITE_BITS, &args); if (-1 == ret) { perror("ioctl"); goto err0; } //设置声道数 args = CHANNELS; ret = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &args); if (-1 == ret) { perror("ioctl"); goto err0; } //设置采样频率 args = RATE; ret = ioctl(fd, SOUND_PCM_WRITE_RATE, &args); if (-1 == ret) { perror("ioctl"); goto err0; } while(1) { printf("开始录音.....\n"); //录音 ret = read(fd, buf, sizeof(buf)); if (ret != sizeof(buf)) { perror("read"); } //放音 printf("you say: \n"); ret = write(fd, buf, sizeof(buf)); { perror("write"); } //在继续录音之前等待回放结束 ret = ioctl(fd, SOUND_PCM_SYNC, 0); if (-1 == ret) { perror("ioctl"); } } close(fd); return 0;err0: return -1;}
4 Mixer程序
/* * mixer.c */#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <sys/ioctl.h>#include <fcntl.h>#include <linux/soundcard.h>/* 用来存储所有可用混音设备的名称 */const char *sound_device_names[] = SOUND_DEVICE_NAMES;int fd; /* 混音设备所对应的文件描述符 */int devmask, stereodevs; /* 混音器信息对应的位图掩码 */char *name;/* 显示命令的使用方法及所有可用的混音设备 */void usage(){ int i; fprintf(stderr, "usage: %s <device> <left-gain%%> <right-gain%%>\n" " %s <device> <gain%%>\n\n" "Where <device> is one of:\n", name, name); for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++) if ((1 << i) & devmask) /* 只显示有效的混音设备 */ fprintf(stderr, "%s ", sound_device_names[i]); fprintf(stderr, "\n"); 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\n", 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\n", 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%%\n", dev, left, right); /* 关闭混音设备 */ close(fd); return 0;}
- 【Linux系统编程应用】Linux音频编程实战(一)
- 【Linux系统编程应用】Linux音频编程基础(一)
- 【Linux系统编程应用】Linux音频编程接口
- 【Linux系统编程应用】 V4L2编程基础(一)
- Linux音频编程指南 一
- Linux系统编程(一)
- 【Linux系统编程应用】 Linux Input子系统(一)
- Linux音频编程实例
- Linux下音频编程
- linux音频编程
- Linux音频编程指南
- Linux音频编程指南
- Linux音频编程指南
- Linux音频编程指南
- Linux音频编程
- Linux音频编程指南
- Linux音频编程指南
- Linux音频编程指南
- C++ 学习笔记(一):基础与入门,数据类型,类型限定,运算符,数组
- LightOJ 1331 - Agent J (计算几何)
- PHP学习:数据库(二)
- 机器学习---线性回归
- jdbc简单的连接和实现基本的增删改更功能
- 【Linux系统编程应用】Linux音频编程实战(一)
- Spring开发包介绍
- nyoj 17 单调递增最长子序列
- September 3rd 模拟赛C T1 祖孙询问 Solution
- shell与subshell与执行脚本的几种方式
- 在Android中创建文件
- 极客 - 博文13 - 统计不及格学生人数
- 企业库是如何调用存储过程的
- iOS新升级,Xcode运行报错Could not find developer disk image