编写一个ALSA驱动
- 格式:pdf
- 大小:237.26 KB
- 文档页数:43
DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思,DAPM是为了使基于linux的移动设备上的音频子系统,在任何时候都工作在最小功耗状态下。
DAPM对用户空间的应用程序来说是透明的,所有与电源相关的开关都在ASoc core中完成。
用户空间的应用程序无需对代码做出修改,也无需重新编译,DAPM根据当前激活的音频流(playback/capture)和声卡中的mixer等的配置来决定那些音频控件的电源开关被打开或关闭。
/******************************************************************************************* **********/声明:本博内容均由/droidphone原创,转载请注明出处,谢谢!/******************************************************************************************* **********/DAPM控件是由普通的soc音频控件演变而来的,所以本章的内容我们先从普通的soc音频控件开始。
snd_kcontrol_new结构在正式讨论DAPM之前,我们需要先搞清楚ASoc中的一个重要的概念:kcontrol,不熟悉的读者需要浏览一下我之前的文章:Linux ALSA声卡驱动之四:Control设备的创建。
通常,一个kcontrol代表着一个mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。
从上述文章中我们知道,定义一个kcontrol主要就是定义一个snd_kcontrol_new 结构,为了方便讨论,这里再次给出它的定义:[cpp]view plaincopystruct snd_kcontrol_new {snd_ctl_elem_iface_t iface; /* interface identifier * /unsigned int device; /* device/client number * /unsigned int subdevice; /* subdevice (substream) number */const 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 {1snd_kcontrol_tlv_rw_t *c;const unsigned int *p;} tlv;unsigned long private_value;};回到Linux ALSA声卡驱动之四:Control设备的创建中,我们知道,对于每个控件,我们需要定义一个和他对应的snd_kcontrol_new结构,这些snd_kcontrol_new结构会在声卡的初始化阶段,通过snd_soc_add_codec_controls函数注册到系统中,用户空间就可以通过amixer或alsamixer等工具查看和设定这些控件的状态。
alsa 用法
ALSA(Advanced Linux Sound Architecture)是一种音频驱动架构,用于在Linux系统上管理音频设备和提供音频功能。
下面是一些ALSA常用的用法:
1. 查看音频设备列表:可以使用以下命令查看系统中可用的音频设备列表:
```
cat /proc/asound/cards
```
2. 音频播放:使用`aplay`命令来播放音频文件。
例如:
```
aplay filename.wav
```
3. 音频录制:使用`arecord`命令录制音频。
例如:
```
arecord -f cd -d 10 -t wav filename.wav
```
4. 调整音量:使用`amixer`命令来调整音量。
例如,将音量设置为50%:
```
amixer set Master 50%
```
5. 调整音频设置:使用`alsamixer`命令在终端界面中调整音频设置,包括音量、通道等。
以上仅为一些常见的ALSA用法,更多用法可以参考ALSA 的文档和相关资料。
linux alsa应用实例Linux ALSA(Advanced Linux Sound Architecture)是Linux操作系统中的一套音频驱动程序和API,用于实现音频设备的输入和输出功能。
本文将介绍一些使用Linux ALSA的实例,以帮助读者更好地理解和应用该技术。
一、录音应用实例:在Linux系统中,使用ALSA可以方便地实现音频录制功能。
下面是一个录音应用的实例代码:```c#include <stdio.h>#include <stdlib.h>#include <alsa/asoundlib.h>#define BUFFER_SIZE 1024int main(){int err;char *buffer;snd_pcm_t *handle;snd_pcm_hw_params_t *params;// 打开默认的录音设备err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);if (err < 0) {printf("无法打开录音设备: %s\n", snd_strerror(err)); return 1;}// 分配和设置硬件参数对象snd_pcm_hw_params_alloca(¶ms);snd_pcm_hw_params_any(handle, params);snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);snd_pcm_hw_params_set_channels(handle, params, 2);unsigned int sample_rate = 44100;snd_pcm_hw_params_set_rate_near(handle, params, &sample_rate, 0);snd_pcm_hw_params_set_period_size(handle, params, BUFFER_SIZE, 0);// 应用硬件参数设置err = snd_pcm_hw_params(handle, params);if (err < 0) {printf("无法设置录音参数: %s\n", snd_strerror(err)); return 1;}// 分配缓冲区buffer = (char *)malloc(BUFFER_SIZE * 2 * sizeof(char)); // 开始录音while (1) {err = snd_pcm_readi(handle, buffer, BUFFER_SIZE);if (err != BUFFER_SIZE) {printf("录音错误: %s\n", snd_strerror(err));return 1;}// 处理录音数据// ...}// 关闭录音设备snd_pcm_close(handle);free(buffer);return 0;}```这段代码使用ALSA库函数实现了从默认录音设备中读取音频数据,并将其存储在缓冲区中。
DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思,DAPM是为了使基于linux的移动设备上的音频子系统,在任何时候都工作在最小功耗状态下。
DAPM对用户空间的应用程序来说是透明的,所有与电源相关的开关都在ASoc core中完成。
用户空间的应用程序无需对代码做出修改,也无需重新编译,DAPM根据当前激活的音频流(playback/capture)和声卡中的mixer等的配置来决定那些音频控件的电源开关被打开或关闭。
/******************************************************************************************* **********/声明:本博内容均由/droidphone原创,转载请注明出处,谢谢!/******************************************************************************************* **********/DAPM控件是由普通的soc音频控件演变而来的,所以本章的内容我们先从普通的soc音频控件开始。
snd_kcontrol_new结构在正式讨论DAPM之前,我们需要先搞清楚ASoc中的一个重要的概念:kcontrol,不熟悉的读者需要浏览一下我之前的文章:Linux ALSA声卡驱动之四:Control设备的创建。
通常,一个kcontrol代表着一个mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。
从上述文章中我们知道,定义一个kcontrol主要就是定义一个snd_kcontrol_new 结构,为了方便讨论,这里再次给出它的定义:[cpp]view plaincopystruct snd_kcontrol_new {snd_ctl_elem_iface_t iface; /* interface identifier * /unsigned int device; /* device/client number * /unsigned int subdevice; /* subdevice (substream) number */const 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 {1snd_kcontrol_tlv_rw_t *c;const unsigned int *p;} tlv;unsigned long private_value;};回到Linux ALSA声卡驱动之四:Control设备的创建中,我们知道,对于每个控件,我们需要定义一个和他对应的snd_kcontrol_new结构,这些snd_kcontrol_new结构会在声卡的初始化阶段,通过snd_soc_add_codec_controls函数注册到系统中,用户空间就可以通过amixer或alsamixer等工具查看和设定这些控件的状态。
在移植alsa-lib和alsa-utils之前首先要移植alsa-device,保证系统支持alsa 驱动,移好alsa-device后再dev/snd后出现相应的设备:∙controlC0 --> 用于声卡的控制,例如通道选择,混音,麦克风的控制等∙midiC0D0 --> 用于播放midi音频∙pcmC0D0c --〉用于录音的pcm设备∙pcmC0D0p --〉用于播放的pcm设备∙seq --〉音序器∙timer --〉定时器1. tar -xvf alsa-lib_1.0.23.orig.tar.bz2(1)配置alsa-lib./configure --host=arm-fsl-linux-gnueabi--prefix=/opt/ALSA/alsa_libCC=/opt/gcc-4.4.4-glibc-2.11.1-multilib-1.0/arm-fsl-linux-gnueabi/bin/a rm-fsl-linux-gnueabi-gccerror:configure error required courses helper header not found安装libncursesw5-dev。
apt-get install libncursesw5-devconfigure: error: panelw library not found./configure加入--with-curses=ncurses/bin/bash: xmlto: command not found安装xmlto/bin/rm: cannot remove `libtoolt': No such file or directory强制make!(2)makemake install生成的库alsa_lib$ lsbin include lib share在这个文件lib下的库*.so.*是要发布到板子上去的,可以直接copy到开发板的根目录lib下(这里只是copy,不要剪贴,后面还要用到,注意的是有些软连接不能copy,只能自己到板子上创建。
LinuxALSA⾳频库(⼀)交叉编译详细说明ALSA应⽤库是核⼼功能,⽽alsa-utils是⼀些⼯具功能集合库。
单纯地播放⼀个wav⽂件,使⽤alsa-utils即可,如果还需要合成⾳频、调试⾳频质量,那么就需要ALSA应⽤库。
1.
alsa-utils安装后,可以执⾏⼀下aplay -h,测试是否输出,如果有信息打印输出,说明已经安装成功了:
aplay的使⽤:
2.
欲安装使⽤ALSA应⽤库,先执⾏下⾯指令,会看到相应设备:
执⾏:cat /proc/asound/devices
正常情况下,在你的/dev/snd会看到⼀些设备结点(有例外,就是内核驱动调整了结点位置)
这就说明驱动⽀持了。
我下载的版本:
****reference BLOGS:
该alsa库安装⽅法:
root权限下:
./configure --host=mips-linux-gnu --prefix=/usr/local/open_lib
make ARCH=mips
make install
交叉编译后,挪到linux板⼦上去:
开发板上需要ubuntu主机安装路径下的lib⽂件夹内的所有⽂件,存到板⼦上的/usr/lib内,或者/lib内。
还要把⼀些⽂件(ubuntu主机安装路径下的share⽂件夹,这整个⽂件夹)传到板上的同名路径/usr/local/open_lib内(./configure时,由prefix指定的路径)。
是挪动整个share⽂件夹,对于这点,上⾯介绍的参考博客写得都不清晰,见下图:
.。
Linux ALSA 声卡驱动的分析一、ALSA && OSS在linux系统中,先后出现了音频设备的两种主要框架:OSS和ALSA.针对不同的数字音频子系统,出现了几种微处理器或DSP与音频器件间用于数字转换的接口。
1.1 音频设备的硬件接口:(1)PCM接口。
(2)IIS(I2S)接口。
(3)AC97接口。
在CD,MD,MP3随身听多采用IIS接口,移动电话会采用PCM接口,具有音频功能的PDA则多使用和PC一样的AC97编码格式。
1.2 linux OSS音频设备驱动OSS标准中有两个最基本的音频设备:mixer(混音器)和dsp(数字信号处理器)。
在声卡的硬件电路中,mixer是一个很重要的组成部分,它的作用是将多个信号组合或者叠加在一起,对于不同的声卡来说,其混音器的作用可能各不相同。
在OSS驱动中,/dev/mixer设备文件时应用程序对mixer进行操作的软件接口。
1.2.1 OSS用户空间编程(dsp编程)对OSS驱动声卡的编程使用linux文件接口函数,dsp接口的操作一般包括如下几个步骤(1)打开设备文件/dev/dsp。
(2)如果有需要,设置缓冲区大小。
(3)设置声道(channel)数量。
根据硬件设备和驱动程序的具体情况,可以设置为单声道或者立体声。
(4)设置采样格式和采样频率。
(5)读写/dev/dsp实现播放和录音。
1.3 linux ALSA音频设备驱动1.3.1 ALSA的主要特点.(1)支持多种声卡设备。
(2)模块化的内核驱动程序。
(3)支持SMP和多线程。
(4)提供应用开发函数库(alsa-lib)以简化应用程序开发。
(5)支持OSS API,兼容OSS应用程序。
ALSA系统包括驱动包alsa-driver,开发包alsa-libs,开发板插件alsa-libplugins,设置管理工具包alsa-utils,其他声音相关处理小程序包alsa-tools,特殊音频固件支持包alsa-firmware,OSS接口兼容模拟层工具alsa-oss,其中只有驱动包是必需的。
1./**alsa play test2.*ALSA用户空间编译,ALSA驱动的声卡在用户空间,不宜直接使用3.*文件接口中,而应使用alsa-lib4.*打开---->设置参数--->读写音频数据 ALSA全部使用alsa-lib中的API5.*交叉编译6.*export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH7.*arm-linux-gcc -o alsa_play alsa_play_test.c -L. -lasound8.*需要交叉编译后的libasound.so库的支持9.*10.*/11.#include <stdio.h>12.#include <stdlib.h>13.#include "alsa/asoundlib.h"14.15.int main(int argc, char *argv[])16.{17.int i;18.int ret;19.int buf[128];20. unsigned int val;21.int dir=0;22.char *buffer;23.int size;24. snd_pcm_uframes_t frames;25. snd_pcm_uframes_t periodsize;26. snd_pcm_t *playback_handle;//PCM设备句柄pcm.h27. snd_pcm_hw_params_t *hw_params;//硬件信息和PCM流配置28.if (argc != 2) {29. printf("error: alsa_play_test [music name]\n");30. exit(1);31. }32. printf("play song %s by wolf\n", argv[1]);33. FILE *fp = fopen(argv[1], "rb");34.if(fp == NULL)35.return 0;36. fseek(fp, 100, SEEK_SET);37.38.//1. 打开PCM,最后一个参数为0意味着标准配置39. ret = snd_pcm_open(&playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);40.if (ret < 0) {41. perror("snd_pcm_open");42. exit(1);43. }44.45.//2. 分配snd_pcm_hw_params_t结构体46. ret = snd_pcm_hw_params_malloc(&hw_params);47.if (ret < 0) {48. perror("snd_pcm_hw_params_malloc");49. exit(1);50. }51.//3. 初始化hw_params52. ret = snd_pcm_hw_params_any(playback_handle, hw_params);53.if (ret < 0) {54. perror("snd_pcm_hw_params_any");55. exit(1);56. }57.//4. 初始化访问权限58. ret = snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);59.if (ret < 0) {60. perror("snd_pcm_hw_params_set_access");61. exit(1);62. }63.//5. 初始化采样格式SND_PCM_FORMAT_U8,8位64. ret = snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_U8);65.if (ret < 0) {66. perror("snd_pcm_hw_params_set_format");67. exit(1);68. }69.//6. 设置采样率,如果硬件不支持我们设置的采样率,将使用最接近的70.//val = 44100,有些录音采样频率固定为8KHz71.72.73. val = 8000;74. ret = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params,&val, &dir);75.if (ret < 0) {76. perror("snd_pcm_hw_params_set_rate_near");77. exit(1);78. }79.//7. 设置通道数量80. ret = snd_pcm_hw_params_set_channels(playback_handle, hw_params, 2);81.if (ret < 0) {82. perror("snd_pcm_hw_params_set_channels");83. exit(1);84. }85.86./* Set period size to 32 frames. */87. frames = 32;88. periodsize = frames * 2;89. ret = snd_pcm_hw_params_set_buffer_size_near(playback_handle, hw_params, &periodsize);90.if (ret < 0)91. {92. printf("Unable to set buffer size %li : %s\n", frames * 2, snd_strerror(ret));93.94. }95. periodsize /= 2;96.97. ret = snd_pcm_hw_params_set_period_size_near(playback_handle, hw_params, &periodsize, 0);98.if (ret < 0)99. {100. printf("Unable to set period size %li : %s\n", periodsi ze, snd_strerror(ret));101. }102.103.//8. 设置hw_params104. ret = snd_pcm_hw_params(playback_handle, hw_params);105.if (ret < 0) {106. perror("snd_pcm_hw_params");107. exit(1);108. }109.110./* Use a buffer large enough to hold one period */111. snd_pcm_hw_params_get_period_size(hw_params, &frames, &dir) ;112.113. size = frames * 2; /* 2 bytes/sample, 2 channels */114. buffer = (char *) malloc(size);115. fprintf(stderr,116."size = %d\n",117. size);118.119.while (1)120. {121. ret = fread(buffer, 1, size, fp);122.if(ret == 0)123. {124. fprintf(stderr, "end of file on input\n"); 125.break;126. }127.else if (ret != size)128. {129. }130.//9. 写音频数据到PCM设备131.while(ret = snd_pcm_writei(playback_handle, buffer, fra mes)<0)132. {133. usleep(2000);134.if (ret == -EPIPE)135. {136./* EPIPE means underrun */137. fprintf(stderr, "underrun occurred\n"); 138.//完成硬件参数设置,使设备准备好139. snd_pcm_prepare(playback_handle);140. }141.else if (ret < 0)142. {143. fprintf(stderr,144."error from writei: %s\n",145. snd_strerror(ret));146. }147. }148.149. }150.//10. 关闭PCM设备句柄151. snd_pcm_close(playback_handle);152.153.return 0;154.}aplay.c==> main==> snd_pcm_open(&handle, pcm_name, stream, open_mode); // 打开一路pcm,刷新config配置如果是"default",同时type等于SND_CONFIG_TYPE_COMPOUND 那么这里对应"empty"static const char *const build_in_pcms[] = {"adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat","linear", "meter", "mulaw", "multi", "null", "empty", "plug", "rate", "route", "share","shm", "dsnoop", "dshare", "asym", "iec958", "softvol","mmap_emul",NULL};_snd_pcm_empty_open和snd_pcm_open_named_slave==> snd_pcm_open_conf(pcmp, name, root, conf, stream, mode); ==> open_func = snd_dlobj_cache_lookup(open_name);将获得lib库中_snd_pcm_empty_open函数所以open_func将等于_snd_pcm_empty_open_snd_pcm_empty_open_snd_pcm_asym_open_snd_pcm_plug_open_snd_pcm_softvol_open_snd_pcm_dmix_open_snd_pcm_hw_open==> snd_pcm_hw_open(pcmp, name, card, device, subdevice, stream,mode | (nonblock ? SND_PCM_NONBLOCK : 0),0, sync_ptr_ioctl);==> snd_ctl_hw_openfilename等于"/dev/snd/controlC0"==> snd_open_device(filename, fmode);ctl->ops = &snd_ctl_hw_ops;ctl->private_data = hw;ctl->poll_fd = fd;*handle = ctl;filename等于"/dev/snd/pcmC0D0p"==> fd = snd_open_device(filename, fmode);==> return snd_pcm_hw_open_fd(pcmp, name, fd, 0, sync_ptr_ioctl); ==> snd_pcm_new(&pcm, SND_PCM_TYPE_HW, name, info.stream, mode);pcm->ops = &snd_pcm_hw_ops;pcm->fast_ops = &snd_pcm_hw_fast_ops;static int snd_pcm_hw_mmap_control(snd_pcm_t *pcm){snd_pcm_hw_t *hw = pcm->private_data;void *ptr;int err;if (hw->sync_ptr == NULL) { // 如果还没有mmap,那么执行mmap 映射内核空间驱动使用的声音缓冲区ptr = mmap(NULL, page_align(sizeof(structsndrv_pcm_mmap_control)),PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, hw->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);if (ptr == MAP_FAILED || ptr == NULL) {err = -errno;SYSMSG("control mmap failed");return err;}hw->mmap_control = ptr; // 声卡驱动头部填充了一个结构体sndrv_pcm_mmap_control,类似qvfb显示原理.// struct sndrv_pcm_mmap_control {// sndrv_pcm_uframes_t appl_ptr; /* RW: appl ptr (0...boundary-1) */// sndrv_pcm_uframes_t avail_min; /* RW: min available frames for wakeup */// };} else {hw->mmap_control->avail_min = 1;}snd_pcm_set_appl_ptr(pcm, &hw->mmap_control->appl_ptr, hw->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);return 0;}snd_pcm_mmapswitch (i->type) {case SND_PCM_AREA_MMAP: // 表示为数据区分配驱动内存,在snd_pcm_hw_channel_info中设置了typeptr = mmap(NULL, size, PROT_READ|PROT_WRITE,MAP_FILE|MAP_SHARED, i->u.mmap.fd, i->u.mmap.offset);/*mmap==> snd_pcm_mmap_data==> snd_pcm_default_mmap// mmap the DMA buffer on RAMstatic int snd_pcm_default_mmap(struct snd_pcm_substream*substream,struct vm_area_struct *area){area->vm_ops = &snd_pcm_vm_ops_data; // vma操作函数,当应用程序向该area读写不存在的内存数据时,area->vm_private_data = substream; // 将执行snd_pcm_vm_ops_data中的fault// 函数snd_pcm_mmap_data_fault进一步以页为单位申请内存空间,所以如果用户程序需要64k,那么将执行16次,每次申请4k空间[luther.gliethttp].area->vm_flags |= VM_RESERVED;atomic_inc(&substream->mmap_count);return 0;}*/if (ptr == MAP_FAILED) {SYSERR("mmap failed");return -errno;}i->addr = ptr;==> snd_pcm_mmap_controlstatic int snd_pcm_mmap_control(struct snd_pcm_substream*substream, struct file *file,struct vm_area_struct *area){struct snd_pcm_runtime *runtime;long size;if (!(area->vm_flags & VM_READ))return -EINVAL;runtime = substream->runtime;size = area->vm_end - area->vm_start;if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control))) return -EINVAL;area->vm_ops = &snd_pcm_vm_ops_control; // 当对( area->vm_start,area->vm_end)之间空间操作,发生area->vm_private_data = substream; // 缺页时,内核将调用该vm_ops方法来处理fault异常,area->vm_flags |= VM_RESERVED; // 进而执行snd_pcm_mmap_control_fault申请1个page空间return 0;}==> writei_func = snd_pcm_writei;==> playback(argv[optind++]);==> playback_go(fd, dtawave, pbrec_count, FORMAT_WAVE, name);==> pcm_write(audiobuf, l);==> writei_func(handle, data, count);就是调用上面的snd_pcm_writei ==> snd_pcm_writei==> _snd_pcm_writei==> pcm->fast_ops->writei(pcm->fast_op_arg, buffer, size);==> snd_pcm_plugin_writei==> snd_pcm_write_areas(pcm, areas, 0, size,snd_pcm_plugin_write_areas);==> avail = snd_pcm_avail_update(pcm); // 获取可用缓冲区位置偏移索引值==> func()就是snd_pcm_plugin_write_areas函数发送1024帧音频数据,一帧对应一次完整采样,比如stereo立体声,24bits量化,那么这里一帧对应3*2字节数据,即一次完整采样所需空间[luther.gliethttp].==> plugin->write(pcm, areas, offset, frames,slave_areas, slave_offset, &slave_frames);即调用snd_pcm_linear_write_areas函数将areas中的frames频数据拷贝到slave_areas内存区==> pcm->fast_ops->mmap_commit(pcm->fast_op_arg, offset, frames);==> snd_pcm_dmix_mmap_commit==> snd_pcm_dmix_sync_area/** synchronize shm ring buffer with hardware*/static void snd_pcm_dmix_sync_area(snd_pcm_t *pcm)==> /* add sample areas here */src_areas = snd_pcm_mmap_areas(pcm);dst_areas = snd_pcm_mmap_areas(dmix->spcm); // 添加==> mix_areas(dmix, src_areas, dst_areas, appl_ptr, slave_appl_ptr, transfer);if (dmix->interleaved) { // 可以将缓冲中的音频数据填充到硬件中[luther.gliethttp]/** process all areas in one loop* it optimizes the memory accesses for this case*/do_mix_areas(size * channels,(unsigned char *)dst_areas[0].addr + sample_size *dst_ofs * channels,(unsigned char *)src_areas[0].addr + sample_size *src_ofs * channels,dmix->u.dmix.sum_buffer + dst_ofs * channels,sample_size,sample_size,sizeof(signed int));return;}==> do_mix_areas(size * channels,(unsigned char *)dst_areas[0].addr + sample_size *dst_ofs * channels,(unsigned char *)src_areas[0].addr + sample_size *src_ofs * channels,dmix->u.dmix.sum_buffer + dst_ofs * channels,sample_size,sample_size,sizeof(signed int));这里的do_mix_areas在i386中,使用下面完全用汇编实现的拷贝函数MIX_AREAS_32完成数据从src到dst的快速拷贝,每拷贝一次,声卡就会发出一点声音[luther.gliethttp]/** for plain i386, 32-bit version (24-bit resolution)*/static void MIX_AREAS_32(unsigned int size,volatile signed int *dst, signed int *src,volatile signed int *sum, size_t dst_step,size_t src_step, size_t sum_step)_snd_pcm_asym_open_snd_pcm_dmix_opensnd_pcm_plugin_avail_update==> snd_pcm_avail_update(slave);==> pcm->fast_ops->avail_update(pcm->fast_op_arg);==> snd_pcm_dmix_avail_update==> snd_pcm_mmap_playback_avail(pcm);alsa_sound_init#define CONFIG_SND_MAJOR 116 /* standard configuration */ static int major = CONFIG_SND_MAJOR;module_init(alsa_sound_init)alsa_sound_init==> register_chrdev(major, "alsa", &snd_fops) // 主设备号为116的所有设备都为alsa设备,节点方法集为snd_fopsstatic const struct file_operations snd_fops = // alsa的设备名为pcmC0D1c或pcmC0D1p等[luther.gliethttp].{.owner = THIS_MODULE,.open = snd_open};snd_open==> __snd_open(inode, file);==> __snd_openunsigned int minor = iminor(inode);mptr = snd_minors[minor];file->f_op = fops_get(mptr->f_ops);file->f_op->open(inode, file);const struct file_operations snd_pcm_f_ops[2] = {{ // alsa使用到的SNDRV_PCM_STREAM_PLAYBACK放音方法集[luther.gliethttp].owner = THIS_MODULE,.write = snd_pcm_write,.aio_write = snd_pcm_aio_write,.open = snd_pcm_playback_open,.release = snd_pcm_release,.poll = snd_pcm_playback_poll,.unlocked_ioctl = snd_pcm_playback_ioctl,.compat_ioctl = snd_pcm_ioctl_compat,.mmap = snd_pcm_mmap,.fasync = snd_pcm_fasync,.get_unmapped_area = dummy_get_unmapped_area,},{ // alsa使用到的SNDRV_PCM_STREAM_CAPTURE录音方法集.owner = THIS_MODULE,.read = snd_pcm_read,.aio_read = snd_pcm_aio_read,.open = snd_pcm_capture_open,.release = snd_pcm_release,.poll = snd_pcm_capture_poll,.unlocked_ioctl = snd_pcm_capture_ioctl,.compat_ioctl = snd_pcm_ioctl_compat,.mmap = snd_pcm_mmap,.fasync = snd_pcm_fasync,.get_unmapped_area = dummy_get_unmapped_area,}};==================================================== =====================snd_intel8x0_probe==> snd_intel8x0_create==> request_irq(pci->irq, snd_intel8x0_interrupt, IRQF_SHARED, card->shortname, chip)snd_intel8x0_interruptsnd_intel8x0_updatesnd_open==> snd_pcm_playback_open==> snd_pcm_open==> snd_pcm_open_file==> snd_pcm_open_substream==> substream->ops->open(substream)即snd_intel8x0_playback_ops.open==> snd_intel8x0_playback_open==> snd_intel8x0_pcm_openstatic int snd_intel8x0_pcm_open(struct snd_pcm_substream*substream, struct ichdev *ichdev){struct intel8x0 *chip = snd_pcm_substream_chip(substream);struct snd_pcm_runtime *runtime = substream->runtime;int err;ichdev->substream = substream;runtime->hw = snd_intel8x0_stream; // 声卡配置硬件信息[luther.gliethttp]runtime->hw.rates = ichdev->pcm->rates;snd_pcm_limit_hw_rates(runtime);if (chip->device_type == DEVICE_SIS) {runtime->hw.buffer_bytes_max = 64*1024;runtime->hw.period_bytes_max = 64*1024;}if ((err = snd_pcm_hw_constraint_integer(runtime,SNDRV_PCM_HW_PARAM_PERIODS)) < 0)return err;runtime->private_data = ichdev;return 0;}ioctl(SNDRV_PCM_IOCTL_HW_PARAMS)==> snd_pcm_f_ops.unlocked_ioctl即:snd_pcm_playback_ioctl==> snd_pcm_playback_ioctl==> snd_pcm_playback_ioctl1==> snd_pcm_common_ioctl1case SNDRV_PCM_IOCTL_HW_PARAMS:return snd_pcm_hw_params_user(substream, arg);==> snd_pcm_hw_params_user==> snd_pcm_hw_params==> substream->ops->hw_params即snd_intel8x0_playback_ops.hw_params==> snd_intel8x0_hw_params==> snd_ac97_pcm_open(ichdev->pcm, params_rate(hw_params), params_channels(hw_params),ichdev->pcm->r[dbl].slots);ioctl(SNDRV_PCM_IOCTL_PREPARE)==> snd_pcm_playback_ioctl==> snd_pcm_playback_ioctl1==> snd_pcm_common_ioctl1==> snd_pcm_prepare // prepare the PCM substream to be triggerable==> snd_pcm_action_nonatomic(&snd_pcm_action_prepare,substream, f_flags);==> snd_pcm_action_single(ops, substream, state);ops->pre_action(substream, state);ops->do_action(substream, state);ops->post_action(substream, state);上面ops就是之前提到的snd_pcm_action_prepare==> snd_pcm_do_prepare调用snd_pcm_do_reset(substream, 0);复位substream->ops->prepare(substream);即snd_intel8x0_playback_ops.prepare==> snd_intel8x0_pcm_preparestatic int snd_intel8x0_pcm_prepare(struct snd_pcm_substream*substream){struct intel8x0 *chip = snd_pcm_substream_chip(substream);struct snd_pcm_runtime *runtime = substream->runtime;struct ichdev *ichdev = get_ichdev(substream);《浅析ac97声卡intel8x0的runtime->dma_area是怎么获取的》ichdev->physbuf = runtime->dma_addr; // dma缓冲区地址ichdev->size = snd_pcm_lib_buffer_bytes(substream); // 将帧缓冲大小转为字节空间大小[luther.gliethttp]ichdev->fragsize = snd_pcm_lib_period_bytes(substream);if (ichdev->ichd == ICHD_PCMOUT) {snd_intel8x0_setup_pcm_out(chip, runtime); // 为play模式设置ac97寄存器[luther.gliethttp]if (chip->device_type == DEVICE_INTEL_ICH4)ichdev->pos_shift = (runtime->sample_bits > 16) ? 2 : 1;}snd_intel8x0_setup_periods(chip, ichdev); // 设置PCI总线ac97的bank地址空间[luther.gliethttp]return 0;}==> snd_intel8x0_setup_pcm_outstatic void snd_intel8x0_setup_pcm_out(struct intel8x0 *chip,struct snd_pcm_runtime *runtime){unsigned int cnt;int dbl = runtime->rate > 48000;// 一共有如下几种设备:enum { DEVICE_INTEL,DEVICE_INTEL_ICH4, DEVICE_SIS, DEVICE_ALI,DEVICE_NFORCE };spin_lock_irq(&chip->reg_lock);switch (chip->device_type) {case DEVICE_ALI:cnt = igetdword(chip, ICHREG(ALI_SCR));cnt &= ~ICH_ALI_SC_PCM_246_MASK;if (runtime->channels == 4 || dbl)cnt |= ICH_ALI_SC_PCM_4;else if (runtime->channels == 6)cnt |= ICH_ALI_SC_PCM_6;iputdword(chip, ICHREG(ALI_SCR), cnt);break;case DEVICE_SIS:cnt = igetdword(chip, ICHREG(GLOB_CNT));cnt &= ~ICH_SIS_PCM_246_MASK;if (runtime->channels == 4 || dbl)cnt |= ICH_SIS_PCM_4;else if (runtime->channels == 6)cnt |= ICH_SIS_PCM_6;iputdword(chip, ICHREG(GLOB_CNT), cnt);break;default:cnt = igetdword(chip, ICHREG(GLOB_CNT));cnt &= ~(ICH_PCM_246_MASK | ICH_PCM_20BIT);if (runtime->channels == 4 || dbl)cnt |= ICH_PCM_4;else if (runtime->channels == 6)cnt |= ICH_PCM_6;else if (runtime->channels == 8)cnt |= ICH_PCM_8;if (chip->device_type == DEVICE_NFORCE) {/* reset to 2ch once to keep the 6 channel data in alignment, * to start from Front Left always*/if (cnt & ICH_PCM_246_MASK) {iputdword(chip, ICHREG(GLOB_CNT), cnt &~ICH_PCM_246_MASK);spin_unlock_irq(&chip->reg_lock);msleep(50); /* grrr... */spin_lock_irq(&chip->reg_lock);}} else if (chip->device_type == DEVICE_INTEL_ICH4) {if (runtime->sample_bits > 16)cnt |= ICH_PCM_20BIT;}iputdword(chip, ICHREG(GLOB_CNT), cnt);break;}spin_unlock_irq(&chip->reg_lock);}ioctl(SNDRV_PCM_IOCTL_START)==> snd_pcm_playback_ioctl==> snd_pcm_playback_ioctl1==> snd_pcm_common_ioctl1==> snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);==> snd_pcm_action_single // state等于SNDRV_PCM_STATE_RUNNINGstatic struct action_ops snd_pcm_action_start = {.pre_action = snd_pcm_pre_start,.do_action = snd_pcm_do_start,.undo_action = snd_pcm_undo_start,.post_action = snd_pcm_post_start};ops->pre_action(substream, state);ops->do_action(substream, state);ops->post_action(substream, state);上面ops就是之前提到的snd_pcm_action_start==> snd_pcm_do_start==> substream->ops->trigger(substream,SNDRV_PCM_TRIGGER_START);即snd_intel8x0_playback_ops.trigger==> snd_intel8x0_pcm_trigger启动ac97数据传输以上都只是执行一次[luther.gliethttp]只要发送音频数据,就会执行该ioctl更新pointerioctl(SNDRV_PCM_IOCTL_HWSYNC)==> snd_pcm_playback_ioctl==> snd_pcm_playback_ioctl1==> snd_pcm_common_ioctl1==> snd_pcm_hwsynccase SNDRV_PCM_STATE_RUNNING:if ((err = snd_pcm_update_hw_ptr(substream)) < 0)break;==> snd_pcm_update_hw_ptr==> snd_pcm_update_hw_ptr_post==> snd_pcm_update_hw_ptr_pos==> substream->ops->pointer(substream);即snd_intel8x0_playback_ops.pointer==> snd_intel8x0_pcm_pointer // 更新dma缓冲区数据最后可用数据索引值[luther.gliethttp]。
使用最新ALSA 驱动解决UBUNTU LINUX INTEL 集成声卡问题目前用户所抱怨的Ubuntu 系列的声卡问题,基本上归结为几类:一,找不到声音设备;二,不发声;三,耳机和音箱同时发声;四、话筒没声。
大部分这种问题都是由笔记本上Intel 集成声卡驱动引起的,关于这个问题的具体描述和解决方案,可以查看下面两个页面:BUG:https:///ubuntu/+source/linux-source-2.6.22/+bug/131133SOLUTION:https:///Gutsy_Intel_HD_Audio_Controller其实大部分问题都可以通过自己动手编译安装最新ALSA 驱动解决,解决方法上面两个链接中已经解释得很清楚了,我这里介绍一下我的思路:第一,查看ALSA 版本,如果最新,就不用重新安装了,仔细查看一下配置吧。
$ alsactl -v如果打印出:alsactl version 1.0.20,那么ALSA 已经是最新了。
第二,在ALSA 官方网站 上,下载最新的ALSA 驱动,怎么解压我就不说了吧。
$ wget ftp:///pub/driver/alsa-driver-1.0.20.tar.bz2$ wget ftp:///pub/lib/alsa-lib-1.0.20.tar.bz2$ wget ftp:///pub/utils/alsa-utils-1.0.20.tar.bz2第三,查看自己的内核版本和声卡解码芯片是否被支持。
查看支持的内核版本$ less alsa-driver-1.0.15/SUPPORTED_KERNELS查看自己声卡解码芯片(如果系统不能识别声卡,可能无法由下面两个查到,那么查看你电脑配置单吧)$ tail -2 /proc/asound/oss/sndstat或$ head -1 /proc/asound/card0/codec#0比如我的DELL D630 就显示的是下面这个Codec: SigmaTel STAC9205在alsa-driver-1.0.20/sound/Documentation/ALSA-Configuration.txt 中查找自己声卡解码芯片对应的model 名字,比如我的STAC9205 对应的就是:STAC9205/9254ref Reference boarddell-m42 Dell (unknown)dell-m43 Dell Precisiondell-m44 Dell Inspiron如果存在对应的model,恭喜你可以继续安装了。
编写一个ALSA驱动Takashi lwai编写一个ALSA驱动(by Takashi Iwai)0.3.6版本翻译:creator sz111@翻译这篇文章主要是为了学习ALSA驱动,因为感觉ALSA是Linux音频发展方向,所以下决心仔细看看,但是中文资料太少,就想翻译一份奉献给广大初学并且英文不好的朋友。
不过自己的英文也非常不好,我也在努力学习中。
翻译的不好,有些地方也不准确,希望大家多提宝贵意见,共同维护这篇文档。
这篇文档主要描述如何写一个ALSA(Linux 高级声音体系 )驱动。
目录前言1.目录树架构概述内核core/osscore/ioctl32core/seqcore/seq/osscore/seq/instr头文件驱动drviers/mpu401drviers/opl3 和 opl4i2ci2c/l3synthpciisaarm,ppc,和sparcusbpcmciaoss2.PCI驱动的基本流程概要代码示例构造器1)检查并增加设备索引2)创建一个声卡实例3)创建一个主要部件4)设定驱动ID和名字5)创建其他部件,如:混音器(mixer),MIDI,等6)注册声卡实例7)设定PCI驱动数据,然后返回零。
析构器头文件3.管理声卡和部件声卡实例部件chip相关数据1.通过snd_card_new()分配2.分配其他设备注册和释放4.PCI资源管理代码示例一些必须做的事情资源分配设备结构体的注册PCI入口5.PCM接口概述代码示例构造器析构器PCM相关的运行时数据硬件描述PCM配置DMA缓冲区信息运行状态私有数据中断的回调函数操作函数(回调函数)opencloseioctlhw_paramshw_freepreparetriggerpointercopy,silenceackpage中断向量周期中断高频率的时钟中断调用snd_pcm_period_elapsed()原子约束6.控制接口概述控制接口描述控制接口名称通用capture和playback Tone控制3D控制MIC B oost接口标识回调函数infogetput回调函数并不是原子的 构造器更新通知7.AC97解码器的API函数概述代码示例构造器回调函数驱动中更新寄存器调整时钟Proc文件多个解码器8.MIDI(MP U401-U A R T)接口概述构造器中断向量9.R aw MIDI接口概述构造器回调函数openclose输出子系统的trigger输入子系统的trigger drain10.杂项设备F M O PL3硬件相关设备I E C958(S/PDI F)11.缓存和内存管理缓存类型附加硬件缓存不相邻缓存通过V malloc申请的缓存12.Proc接口13.电源管理14.模块参数15.如何把你的驱动加入到ALS代码中 概述单一文件的驱动程序多个文件的驱动程序16.一些有用的函数snd_printk()相关函数snd_assert() snd_BUG()17.致谢前言这篇文章主要介绍如何写一个ALSA(Advanced Linux Sound Architecture)(http:// www.alsa-pro j )驱动.这篇文档主要针对PCI声卡。
假如是其他类似的设备,API函数可能会是不同的。
尽管如此,至少ALSA内核的API是一样的。
因此,这篇文章对于写其他类型的设备驱动同样有帮助。
本文的读者需要有足够的C语言的知识和基本的linux内核编程知识。
本文不会去解释一些Linux内核编码的基本话题,也不会去详细介绍底层驱动的实现。
它仅仅描述如何写一个基于ALSA的PCI声卡驱动。
假如你对0.5.x版本以前的ALSA驱动非常熟悉的话,你可以检查驱动文件,例如es1938.c或maestro3.c,那些是在0.5.x版本基础上而来的,你可以对比一下他们的差别。
这篇文档仍然是一个草稿,非常欢迎大家的反馈和指正。
第一章 文件目录结构概述有两种方式可以得到ALSA驱动程序。
一个是通过ALSA的ftp网站上下载,另外一个是2.6(或以后)Linux源码里面。
为了保持两者的同步,ALSA驱动程序被分为两个源码树:alsa-kernel和alsa -drvier。
前者完全包含在Linux2.6(或以后)内核树里面,这个源码树仅仅是为了保持2.6(或以后)内核的兼容。
后者,ALSA驱动,包含了很多更细分的文件,为了在2.2和2.4内核上编译,配置,同时为了适应最新的内核API函数,和一些在开发中或尚在测试的附加功能。
当他们那些功能完成并且工作稳定的时候最终会被移入到alsa 内核树中。
ALSA驱动的文件目录描述如下。
alsa-kernel和alsa-drvier几乎含有相同的文件架构,除了“core”目录和alsa-drvier目录树中被称为“acore”。
示例1-1.ALSA文件目录结构sound/core/oss/seq/oss/instr/ioctl32/include/drivers/mpu401/opl3/i2c/l3/synth/emux/pci/(cards)/isa/(cards)/arm/ppc/sparc/usb/pcmcia/(cards)/osscore目录这个目录包含了中间层,ALSA的核心驱动。
那些本地ALSA模块保持在这个目录里。
一些子目录包含那些与内核配置相关的不同的模块。
core/oss关于PCM和mixer的O SS模拟的模块保存在这个目录里面。
R aw midi O SS模拟也被包含在ALSA rawmidi 代码中,因为它非常小。
音序器代码被保存在core/seq/oss目录里面(如下)。
core/ioctl32这个目录包含32bit-ioctl到64bit架构(如x86-64,ppc64,sparc64)的转换。
对于32bit和alpha的架构,他们是不被编译的。
core/seq这和它的子目录主要是关于ALSA的音序器。
它包含了音序器的core和一些主要的音序器模块如:snd-seq-midi,snd-seq-virmidi等等。
它们仅仅在内核配置中当C ONF I G_S N D_S EQUEN C ER被设定的时候才会被编译。
core/seq/oss包含了O SS音序器的模拟的代码。
core/seq/instr包含了一些音序器工具层的一些模块。
include目录这里面放的是ALSA驱动程序开放给用户空间,或者被其他不同目录引用的共同头文件。
一般来说,私有头文件不应该被放到此文件目录,但是你仍然会发现一些这样的文件,那是历史遗留问题了。
Drivers目录这个目录包含了在不同架构的系统中的不同驱动共享的文件部分。
它们是硬件无关的。
例如:一个空的pcm驱动和一系列MIDI驱动会放在这个文件夹中。
在子目录里面,会放一些不同的组件的代码,他们是根据不同的bus和cpu架构实现的。
drivers/mpu401MP U401和MP U401-U A R T模块被放到这里。
drviers/opl3和opl4O PL3和O PL4 F M-synth相关放到这里。
i2c目录这里面包含了ALSA的i2c组件。
虽然Linux有个i2c的标准协议层,ALSA还是拥有它关于一些card的专用i2c代码,因为一些声卡仅仅需要一些简单的操作,而标准的i2c的API函数对此显得太过复杂了。
i2c/l3这是A R M L3 i2c驱动的子目录synth目录它包含了synth(合成器)的中间层模块到目前为止,仅仅在synth/emux目录下面有E mu8000/Tmu10k1 synth驱动。
pci目录它和它的一些子目录文件负责PCI声卡和一些PCI BU S的上层card模块。
在pci目录下面保存着一些简单的驱动文件,而一些比较复杂的,同时包含多个程序文件的驱动会被放置在pci目录下面一个单独的子目录里面(如:emu10k1,ice1712)。
isa目录它和它的一些子目录文件是处理ISA声卡的上层card模块。
arm,ppc,和sparc目录这里放置一些和芯片架构相关的一些上层的card模块。
usb目录这里包含一些U S B-A U DI O驱动。
在最新版本里面,已经把U S B MIDI驱动也集成进U S B-A U DI O驱动了。
pcmcia目录PCMCIA卡,特别是PCCard驱动会放到这里。
Card B us驱动将会放到pci目录里面,因为API函数和标准PCI卡上统一的。
oss目录O SS/Lite源文件将被放置在linux2.6(或以后)版本的源码树中。
(在ALSA驱动中,它是空的:)第二章 PCI驱动的基本流程概要PCI声卡的最简单的流程如下:●定义一个PCI Id表(参考PCI E ntries章节)●建立probe()回调函数●建立remove()回调函数●建立包含上面三个函数入口的pci_driver表●建立init()函数,调用pci_register_driver()注册上面的pci_drvier表●建立exit()函数,调用pci_unregister_driver()函数代码示例代码如下所示。
一些部分目前还没有实现,将会在后续章节逐步实现。
如:在snd_mychip_probe()函数的注释部分的号码。
E xample2-1.PCI驱动示例的基本流程#include <sound/driver.h>#include <linux/init.h>#include <linux/pci.h>#include <linux/slab.h>#include <sound/core.h>#include <sound/initval.h>/*模块参数(参考“Module Parameters”)*/static int index[S N D RV_CA R DS]= S N D RV_D EF A U LT_ID X;static char *id[S N D RV_CA R DS]= S N D RV_D EF A U LT_ST R;static int enable[S N D RV_CA R DS]= S N D RV_D EF A U LT_EN A B L E_P N P;/*定义和chip相关的记录*/struct mychip{struct snd_card *card;//剩余的其他实现将放到这里//”PCI R esource Management”};/*chip-specific destrcutor*(see “PCI R esource Management”*/static int snd_mychip_free(struct mychip *chip){...//后续会实现}/*component-destructor*(see “Management of Cards and Components”*/static int snd_mychip_dev_free(struct snd_device *device) {return snd_mychip_free(device->device_data);}/*chip-specific constructor*(see “Management of Cards and Components”)*/static int __devinit snd_mychip_create(struct snd_card *card, struct pci_dev *pci,struct mychip **rchip) {struct mychip *chip;int err;static struct snd_device_ops ops ={.dev_freee= snd_mychip_dev_free,};*rchip =NU LL;//check PCI avilability here//(see "PCI R esource Management")/*allocate a chip-specific data with zero filled*/chip = kzalloc(sizeof(*chip), GF P_KERNE L);if (chip ==NU LL)return -ENO M E M;chip->card = card;//rest of initialization here;will be implemented//later,see "PCI R esource Management"....if ((err = snd_device_new(card, S N D RV_D EV_L OW L EVE L,chip, &ops)) < 0) {snd_mychip_free(chip);return err;}snd_card_set_dev(card, &pci->dev);*rchip = chip;return 0;}/*constructor --see "Constructor"sub - section*/static int __devinit snd_mychip_probe(struct pci_dev *pci,const struct pci_device_id *pci_id) {static int dev;struct snd_card *card;struct mychip *chip;int err;/*(1)*/if (dev >= S N D RV_CA R DS)return -ENO D EV;if (!enable[dev]) {dev++;return -ENOEN T;}/*(2)*/card = snd_card_new(index[dev], id[dev], T H IS_M O D U L E, 0);if (card ==NU LL)return -ENO M E M;/*(3)*/if ((err = snd_mychip_create(card, pci, &chip)) < 0){snd_card_free(card);return err;}/*(4)*/strcpy(card->driver,"My Chip");strcpy(card->shortname,"My O wn Chip 123");sprintf(card->longname,"%s at 0x%1x irq %i",card->shortname, chip->ioport, chip->irq);/*(5)*/....//implemented later/*(6)*/if ((err = snd_card_register(card)) < 0) {snd_card_free(card);return err;}/*(7)*/pci_set_drvdata(pci, card);dev++;return 0;}/*destructor --seee"Destructor" sub-section*/static void __devexit snd_mychip_remove(struct pci_dev *pci) {snd_card_free(pci_get_drvdata(pci);pci_set_drvdata(pci,NU LL);}构造函数PCI驱动的真正构造函数是回调函数probe。