linux音频设备两种框架:OSS(有两个最基本的音频设备mixer和dsp),ALSA。
音频设备硬件接口:
1.
PCM
由时钟BLCK,帧同步信号FS,接收数据DR,发送数据DX组成。在FS上升沿,数据从MSB开始,FS等于采样频率。
2.
IIS
当LRCLK为高时,左声道数据被传输;当LRCLK为低时,右声道数据被传输。
3.
AC97
AC97采用AC-link与外部的编码器相连,AC-link包括时钟,同步信号校正,和从编码到处理器及从处理器到编码的数据队列。其数据帧包括12个20位的时间段及16位tag段,256个数据队列。把帧分成时间段使传输控制信号和音频数据仅通过4根线到达9个音频通道或转换成其他数据流成为可能。与IIS相比,AC97明显减少了整体管脚数。
Linux的OSS设备驱动
1.
mixer接口
int register_sound_mixer(struct file_operations *fops, int dev);
mixer为典型的字符设备,编码的主要工作是实现 fops的open(),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:
{
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_OLD_MIXER_INFO:
{
_old_mixer_info info;
strncpy(info.id, "UDA1341", sizeof(info.id));
strncpy(info.name,"Philips UDA1341", sizeof(info.name));
return copy_to_user((void *)arg, &info, sizeof(info));
}
case SOUND_MIXER_READ_STEREODEVS:
return put_user(0, (long *) arg);
case SOUND_MIXER_READ_CAPS:
val = SOUND_CAP_EXCL_INPUT;
return put_user(val, (long *) arg);
case SOUND_MIXER_WRITE_VOLUME:
ret = get_user(val, (long *) arg);
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:
val = ((63 - uda1341_volume) * 100) / 63;
val |= val << 8;
return put_user(val, (long *) arg);
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;
default:
DPRINTK("mixer ioctl %u unknown
", cmd);
return -ENOSYS;
}
audio_mix_modcnt++;
return 0;
}
2.
dsp接口
int register_sound_dsp(struct file_operations *fops, int dev);
dsp也是典型的字符设备,同样主要工作是实现fops的操作。
Dsp的poll()函数向用户反馈目前能否读取DMA缓冲区。
OSS用户空间编程步骤:
DSP编程:
1.
打开设备文件/dev/dsp 全双工才能读写打开
2.
设置缓冲区大小 紧跟在打开之后,ioctl()函数来设置
3.
设置声道数量
4.
设置采样格式和采样频率
5.
读写/dev/dsp实现播放或录音
OSS的dsp实例
/*
* sound.c
* 先录制几秒种音频数据,将其存放在内存缓冲区中,然后再进行回放,其所有的功能都是通过读写/dev/dsp设备文件来完成
*/
#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) {
perror("open of /dev/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 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");
}
}
Mixer编程:
通过驱动程序提供的设备文件/dev/mixer编程。一般通过Ioctl()系统调用来完成,所有的控制命令都以sound_mixer或者mixer开头
1.
sound_mixer_read宏读取获得的麦克的输入增益(百分比形式)
ioctl(fd, sound_mixer_read(sound_mixer_mic), &vol);
提取左右声道的增益:
Int left,right;
left
= vol & 0xff;
right = (vol & 0xff00) >> 8;
2.
sound_mixer_write宏
/* 将两个声道的值合到同一变量中 */
level = (right << 8) + left;
/* 设置增益 */
status = ioctl(fd, MIXER_WRITE(device), &level);
if (status == -1) {
perror("MIXER_WRITE ioctl failed");
exit(1);
}
3.
查询mixer信息
…
OSS mixer实例
/*
* mixer.c
* 对各种混音通道的增益进行调节,其所有的功能都通过读写/dev/mixer设备文件来完成
*/
#include
#include
#include
#include
#include
#include
/* 用来存储所有可用混音设备的名称 */
const char *sound_device_names[] = SOUND_DEVICE_NAMES;
int fd;
/* 混音设备所对应的文件描述符 */
int devmask, stereodevs; /* 混音器信息对应的位图掩码 */
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;
}
Linux的ALSA音频设备驱动
ALSA为开放源码的体系,除了像OSS提供的内核驱动程序模块之外,还专门为简化应用程序的编写提供了函数库。兼容于OSS,包括驱动包alsa-driver(很大的内核程序),开发包alsa-libs,开发包插件alsa-libplugins,设置管理工具包alsa-utils,其他相关处理小程序包alsa-tools,特殊音频固件支持包alsa-firmware,OSS接口兼容模拟层工具alsa-oss
ALSA内核提供给用户很多接口,也以文件的方式提供,但这些接口被提供给alsa-lib使用,不是直接给应用程序使用。应用程序使用alsa-lib,或更高级的接口。
Card和组件管理
Card管理这个声卡上的所有设备(组件),对声卡而言必须创建一个card。
1.创建card
struct snd_card *snd_card_new(int idx, const char *xid, struct module *module,int extra_size)
2.创建组件
int snd_device_new(struct snd_card *card, snd_device_type_t type,void *device_data, struct snd_device_ops *ops)
3.组件释放
4.芯片特定的数据
一般以struct xxxchip的形式组织,包含芯片相关的i/o端口地址,资源指针,中断号等。
定义芯片特定数据方法:
struct xxxchip
{
…
};
card=snd_card_new(int idx, const char *xid, struct module *module,sizeof(struct xxxchip));
struct xxxchip *chip=card->private_data;
6.
注册和释放声卡
Int snd_card_register(struct snd_card *card);
Int snd_card_free(struct snd_card *card);
PCM设备
每个声卡最多可以有4个PCM实例,一个实例对应一个设备文件,PCM实例由播放和录音流组成,每个PCM流又有一个或多个子流组成。
1.
PCM实例构造
Int snd_pcm_new(struct snd_card *card, char *id, int device, int playback_count, int capture_count, struct snd_pcm **rpcm);4,5参数表示录放的子流数
2.
设置PCM操作
Void snd_pcm_set_ops(struct snd_pcm *pcm,int direction, struct snd_pcm_ops *ops);
struct snd_pcm_ops
中的所有操作都需要事先通过snd_pcm_substream_chip()获得xxxchip指针。
3.分配缓冲区
Int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm, int type, void *data, size_t size, size_t max);
4.设置标志
Pcm->info_flags=SNDRV_PCM_INFO_HALF_DUPLEX;半双工
5.PCM信息运行时的结构体 snd_pcm_runtime通过substream->runtime获得
包括硬件信息,dma缓冲区信息,运行状态,私有数据,中断回调函数等
控制接口control(mixer基于control内核api实现)
ALSA中用snd_kcontrol结构体描述。
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
unsigned char *name; /* ASCII name of item */
unsigned int index; /* index of item */
unsigned int access; /* access rights */
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
Name是名称标识字符串,定义标准是“source direction function”
下面几种不采用以上格式:
全局控制 capture source/switch/volume全局录音源,输入开关,音量控制 playback switch/volume
全局输出开关,音量控制
音量控制 tone control-xxx
3D控制 3D control-xxx
麦克风增益 mic boost
Access控制访问权限
private_value字段包含长整型值,通过它给info(),get(),put()函数传递参数
info()函数用于获得control的详细信息(填充第二个参数)
static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo);
get()用于得到control的目前值并返回到用户空间。
Put()用于从用户空间写入值。
构造control方法:
Int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol);
创建一个snd_kcontrol,并添加到card中;
Struct snd_kcontrol *snd_ctl_newl(struct snd_kcontrol_new *ncontrol, void *private_data);
创建一个snd_kcontrol并返回其指针
在中断服务程序中改或变更一个control,可以调用snd_ctl_notify()
Void snd_ctl_notify(struct snd_card *card,unsigned int mask, struct snd_ctl_elem_id *id);
第二个参数为事件掩码,第三个为control元素的ID指针。
AC97 API接口
编码层被很好的定义,我们只需编写少量底层的控制函数。
1.
AC97 实例构造
首先调用snd_ac97_bus()构建AC97
总线及操作,再调用snd_ac97_mixer()注册混音器
函数原型
int snd_ac97_bus(struct snd_card *card, int num, struct snd_ac97_bus_ops
*ops, void *private_data, struct snd_ac97_bus **rbus);
int
snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97);
2.snd_ac97_bus_ops结构体成员
read(),write()完成底层的硬件访问,reset()复位编解码器,wait()编解码器标准初始化过程中的特定等待,init()完成编解码器附加的初