关于ALSA的小结

这一年来的一个重要工作是音频设备驱动方面,如驱动开发、调试debug、性能调优、音频路由、回声消除等。除此之外,熟悉了一下alsa-lib接口,Android音频系统,包括设备抽象层和音频策略。


遗憾的是一直没有什么机会接触蓝牙设备的集成和开发,可预知的未来无线设备会极大地普及。还有就是ffmpeg,以前经常用它来做一些兴致突来能力而及的小工具,年初时计划进一步在它的基础上学习H264或AVS的解码,但至今都未开始。问题一方面是我自身的懒惰,另一方面是公司不允许装虚拟机(PC装的是windows,毕竟办公都必须在windows环境下,开发登陆到Linux服务器)。试过在cygwin上面建立ffmpeg开发环境,以失败告终,ffplay无论如何都不能编译。


而值得自豪的是,alsa-driver算是消化得差不多了。sound/core目录里面的几大主体:control、hwdep和pcm,sound/soc目录的soc-core和soc-dapm都基本理顺了。还有info、jack、timer、rawmidi、usb-audio、ac97-bus,有空再研究。


关于control、hwdep、dapm、codec,写了不少文章记录我的学习成果,均在ALSA专栏。Card和pcm方面,由于内容太庞大,未整理出较完善的文档,暂借用前辈的经验,如下:

Linux音频驱动之二:声卡的创建:http://blog.csdn.net/droidphone/article/details/6289712

Linux音频驱动之三:PCM设备的创建:http://blog.csdn.net/droidphone/article/details/6308006

alsa驱动分析之一:http://blog.csdn.net/wylhistory/article/details/5114217

alsa驱动分析之二:http://blog.csdn.net/wylhistory/article/details/5114230


然后缪谈一些个人对于alsa开发的心得吧。

1、 清楚音频相关概念和原理,如采样率、采样精度、peroid、ADC/DAC、DAI、pops等等;明白音频数据流向,进而明白codec、i2s、dma各起到什么作用。

2、多看内核文档Documentation/sound/alsa,这些文档起到概述作用,配合源码可以让人更清晰音频概念和运作机理。

3、alsa-utils的几个工具,多用aplay和amixer进行调试。

4、熟悉alsa配置脚本asound.rc的写法:http://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html,有时间可以练习写一个类似aplay的播放器工具。

其实这些都是老生常谈了,但很实用。


另外今天在网上找到一个wolfson音频处理芯片的PPT,写得很好,将音频开发的大致流程和关键点都写出来了。

Linux Audio for Android: http://download.csdn.net/detail/sepnic/3783997

hwdep模块简述


字符设备驱动中,ioctl是一个很常见的IO设备操作函数,可以自定义cmd命令字并实现对应的设备IO控制。

音频设备的控制有所不同:驱动层大部分控制操作定义各种snd_kcontrol_new,然后注册到SNDRV_DEV_CONTROL模块中(sound\core\control.c),详见snd_kcontrol探究;而上层调用alsa-lib的snd_ctl_open/snd_mixer_open来打开底层的SNDRV_DEV_CONTROL模块,详见DAPM之二:audio paths与dapm kcontrol。这方法常见于mixer-control,如音量调整、部件开关、通路连接等等。

除此之外,alsa还是可以实现类似于ioctl的函数的,只不过它封装成一个设备模块SNDRV_DEV_HWDEP,代码sound\core\ hwdep.c。该模块实现了read/write/ioctl/llseek/poll/mmap等接口。hwdep是Hardware Dependant Interface的简称。


题外话:如果想看自己板上的alsa有什么类型的设备可以cat /proc/asound/devices,如

view plain print ?
  1. ~ # cat /proc/asound/devices   
  2.   0: [ 0]   : control  
  3.   4: [ 0- 0]: hardware dependent  
  4.  16: [ 0- 0]: digital audio playback  
  5.  24: [ 0- 0]: digital audio capture  
  6.  33:        : timer  
[plain] view plain copy print ?
  1. ~ #  
  2.  cat /proc/asound/devices   
  3.   0: [ 0]   : control  
  4.   4: [ 0- 0]: hardware dependent  
  5.  16: [ 0- 0]: digital audio playback  
  6.  24: [ 0- 0]: digital audio capture  
  7.  33:        : timer  
~ # cat /proc/asound/devices 0: [ 0] : control 4: [ 0- 0]: hardware dependent 16: [ 0- 0]: digital audio playback 24: [ 0- 0]: digital audio capture 33: : timer设备节点号minor=0是control,=4是hwdep,=16是pcm- playback,=24是pcm-capture,=33是timer。


如下简单分析ioctl:

view plain print ?
  1. // 套接字接口函数集  
  2. static const struct file_operations snd_hwdep_f_ops =  
  3. {  
  4.     .owner =    THIS_MODULE,  
  5.     .llseek =   snd_hwdep_llseek,  
  6.     .read =     snd_hwdep_read,  
  7.     .write =    snd_hwdep_write,  
  8.     .open =     snd_hwdep_open,  
  9.     .release =  snd_hwdep_release,  
  10.     .poll =     snd_hwdep_poll,  
  11.     .unlocked_ioctl =   snd_hwdep_ioctl,  
  12.     .compat_ioctl = snd_hwdep_ioctl_compat,  
  13.     .mmap =     snd_hwdep_mmap,  
  14. };  
  15.   
  16. static long snd_hwdep_ioctl(struct file * file, unsigned int cmd,  
  17.                 unsigned long arg)  
  18. {  
  19.     struct snd_hwdep *hw = file->private_data;  
  20.     void __user *argp = (void __user *)arg;  
  21.     switch (cmd) {  
  22.     case SNDRV_HWDEP_IOCTL_PVERSION:  
  23.         return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp);  
  24.     case SNDRV_HWDEP_IOCTL_INFO:  
  25.         return snd_hwdep_info(hw, argp);  
  26.     case SNDRV_HWDEP_IOCTL_DSP_STATUS:  
  27.         return snd_hwdep_dsp_status(hw, argp);  
  28.     case SNDRV_HWDEP_IOCTL_DSP_LOAD:  
  29.         return snd_hwdep_dsp_load(hw, argp);  
  30.     }  
  31.     if (hw->ops.ioctl)  
  32.         return hw->ops.ioctl(hw, file, cmd, arg);  
  33.     return -ENOTTY;  
  34. }  
[cpp] view plain copy print ?
  1. //套接字接口函数集   
  2. static const struct file_operations snd_hwdep_f_ops =  
  3. {  
  4.     .owner =    THIS_MODULE,  
  5.     .llseek =   snd_hwdep_llseek,  
  6.     .read =     snd_hwdep_read,  
  7.     .write =    snd_hwdep_write,  
  8.     .open =     snd_hwdep_open,  
  9.     .release =  snd_hwdep_release,  
  10.     .poll =     snd_hwdep_poll,  
  11.     .unlocked_ioctl =   snd_hwdep_ioctl,  
  12.     .compat_ioctl = snd_hwdep_ioctl_compat,  
  13.     .mmap =     snd_hwdep_mmap,  
  14. };  
  15.   
  16. static long snd_hwdep_ioctl(struct file * file, unsigned int cmd,  
  17.                 unsigned long arg)  
  18. {  
  19.     struct snd_hwdep *hw = file->private_data;  
  20.     void __user *argp = (void __user *)arg;  
  21.     switch (cmd) {  
  22.     case SNDRV_HWDEP_IOCTL_PVERSION:  
  23.         return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp);  
  24.     case SNDRV_HWDEP_IOCTL_INFO:  
  25.         return snd_hwdep_info(hw, argp);  
  26.     case SNDRV_HWDEP_IOCTL_DSP_STATUS:  
  27.         return snd_hwdep_dsp_status(hw, argp);  
  28.     case SNDRV_HWDEP_IOCTL_DSP_LOAD:  
  29.         return snd_hwdep_dsp_load(hw, argp);  
  30.     }  
  31.     if (hw->ops.ioctl)  
  32.         return hw->ops.ioctl(hw, file, cmd, arg);  
  33.     return -ENOTTY;  
  34. }  
//套接字接口函数集 static const struct file_operations snd_hwdep_f_ops = { .owner = THIS_MODULE, .llseek = snd_hwdep_llseek, .read = snd_hwdep_read, .write = snd_hwdep_write, .open = snd_hwdep_open, .release = snd_hwdep_release, .poll = snd_hwdep_poll, .unlocked_ioctl = snd_hwdep_ioctl, .compat_ioctl = snd_hwdep_ioctl_compat, .mmap = snd_hwdep_mmap, }; static long snd_hwdep_ioctl(struct file * file, unsigned int cmd, unsigned long arg) { struct snd_hwdep *hw = file->private_data; void __user *argp = (void __user *)arg; switch (cmd) { case SNDRV_HWDEP_IOCTL_PVERSION: return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp); case SNDRV_HWDEP_IOCTL_INFO: return snd_hwdep_info(hw, argp); case SNDRV_HWDEP_IOCTL_DSP_STATUS: return snd_hwdep_dsp_status(hw, argp); case SNDRV_HWDEP_IOCTL_DSP_LOAD: return snd_hwdep_dsp_load(hw, argp); } if (hw->ops.ioctl) return hw->ops.ioctl(hw, file, cmd, arg); return -ENOTTY; }从snd_hwdep_ioctl可以看出,系统默认只有4个cmd,功能主要是download dsp image。从return hw->ops.ioctl(hw, file, cmd, arg)语句可以看出,我们可自定义cmd和ioctl函数。


实现自定义的hwdep操作函数


1、 首先实现需要的操作函数:

view plain print ?
  1. static int my_hwdep_open(struct snd_hwdep * hw, struct file *file)  
  2. {  
  3.     printk(KERN_INFO "my_hwdep_open\n");  
  4.     return 0;  
  5. }  
  6.   
  7. static int my_hwdep_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg)  
  8. {  
  9. #define MY_SOC_IOCTL_SET_CALL_PATH   _IOWR('H', 0x10, int)  
  10.   
  11.     switch (cmd) {  
  12.     case MY_SOC_IOCTL_SET_CALL_PATH:  
  13.         //设置电话语音通路  
  14.         return 0;  
  15.         break;  
  16.     //......  
  17.     }  
  18.       
  19.     err("Not supported ioctl for MY-HWDEP");  
  20.     return -ENOIOCTLCMD;  
  21. }  
[cpp] view plain copy print ?
  1. static int   
  2. my_hwdep_open(struct snd_hwdep * hw, struct file *file)  
  3. {  
  4.     printk(KERN_INFO "my_hwdep_open\n");  
  5.     return 0;  
  6. }  
  7.   
  8. static int my_hwdep_ioctl(struct snd_hwdep * hw, struct file *file,   
  9. unsigned int cmd, unsigned long arg)  
  10. {  
  11. #define MY_SOC_IOCTL_SET_CALL_PATH   _IOWR('H', 0x10, int)  
  12.   
  13.     switch (cmd) {  
  14.     case MY_SOC_IOCTL_SET_CALL_PATH:  
  15.         //设置电话语音通路   
  16.         return 0;  
  17.         break;  
  18.     //......   
  19.     }  
  20.       
  21.     err("Not supported ioctl for MY-HWDEP");  
  22.     return -ENOIOCTLCMD;  
  23. }  
static int my_hwdep_open(struct snd_hwdep * hw, struct file *file) { printk(KERN_INFO "my_hwdep_open\n"); return 0; } static int my_hwdep_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg) { #define MY_SOC_IOCTL_SET_CALL_PATH _IOWR('H', 0x10, int) switch (cmd) { case MY_SOC_IOCTL_SET_CALL_PATH: //设置电话语音通路 return 0; break; //...... } err("Not supported ioctl for MY-HWDEP"); return -ENOIOCTLCMD; }
2、 注册操作函数到hwdep模块:
view plain print ?
  1. struct snd_hwdep *hwdep;  
  2.   
  3. if (snd_hwdep_new(codec->card, "MY-HWDEP", 0, &hwdep) < 0) {  
  4.     printk(KERN_ERR "create MY-HWDEP fail");  
  5.     return;  
  6. }  
  7.   
  8. sprintf(hwdep->name, "MY-HWDEP %d", 0);  
  9.   
  10. hwdep->iface = SNDRV_HWDEP_IFACE_WMT;  
  11. hwdep->ops.open = wmt_hwdep_open;  
  12. hwdep->ops.ioctl = wmt_hwdep_ioctl;  
[cpp] view plain copy print ?
  1. struct  
  2.  snd_hwdep *hwdep;  
  3.   
  4. if (snd_hwdep_new(codec->card, "MY-HWDEP", 0, &hwdep) < 0) {  
  5.     printk(KERN_ERR "create MY-HWDEP fail");  
  6.     return;  
  7. }  
  8.   
  9. sprintf(hwdep->name, "MY-HWDEP %d", 0);  
  10.   
  11. hwdep->iface = SNDRV_HWDEP_IFACE_WMT;  
  12. hwdep->ops.open = wmt_hwdep_open;  
  13. hwdep->ops.ioctl = wmt_hwdep_ioctl;  
struct snd_hwdep *hwdep; if (snd_hwdep_new(codec->card, "MY-HWDEP", 0, &hwdep) < 0) { printk(KERN_ERR "create MY-HWDEP fail"); return; } sprintf(hwdep->name, "MY-HWDEP %d", 0); hwdep->iface = SNDRV_HWDEP_IFACE_WMT; hwdep->ops.open = wmt_hwdep_open; hwdep->ops.ioctl = wmt_hwdep_ioctl;这里摘录snd_hwdep_new的代码注释,让大家更明白上面的注册过程:
view plain print ?
  1. /** 
  2.  * snd_hwdep_new - create a new hwdep instance 
  3.  * @card: the card instance 
  4.  * @id: the id string 
  5.  * @device: the device index (zero-based) 
  6.  * @rhwdep: the pointer to store the new hwdep instance 
  7.  * 
  8.  * Creates a new hwdep instance with the given index on the card. 
  9.  * The callbacks (hwdep->ops) must be set on the returned instance 
  10.  * after this call manually by the caller. 
  11.  * 
  12.  * Returns zero if successful, or a negative error code on failure. 
  13.  */  
[cpp] view plain copy print ?
  1. /** 
  2.  * snd_hwdep_new - create a new hwdep instance 
  3.  * @card: the card instance 
  4.  * @id: the id string 
  5.  * @device: the device index (zero-based) 
  6.  * @rhwdep: the pointer to store the new hwdep instance 
  7.  * 
  8.  * Creates a new hwdep instance with the given index on the card. 
  9.  * The callbacks (hwdep->ops) must be set on the returned instance 
  10.  * after this call manually by the caller. 
  11.  * 
  12.  * Returns zero if successful, or a negative error code on failure. 
  13.  */  
/** * snd_hwdep_new - create a new hwdep instance * @card: the card instance * @id: the id string * @device: the device index (zero-based) * @rhwdep: the pointer to store the new hwdep instance * * Creates a new hwdep instance with the given index on the card. * The callbacks (hwdep->ops) must be set on the returned instance * after this call manually by the caller. * * Returns zero if successful, or a negative error code on failure. */按照以上实现hwdep ioctl后,上层可以通过alsa-lib的相关接口来调用。


上层调用范例


view plain print ?
  1. #include <fcntl.h>  
  2. #include <sys/ioctl.h>  
  3. #include <alsa/hwdep.h>  
  4. #include <alsa/error.h>  
  5. #include <stdio.h>  
  6.   
  7. #define MY_SOC_IOCTL_SET_CALL_PATH   _IOWR('H', 0x10, int)  
  8.   
  9. int main()  
  10. {  
  11.     const char *devicename = "hw:0,0";  
  12.     snd_hwdep_t *hwdep;  
  13.     int err;  
  14.     int enable = 1;  
  15.       
  16.     if ((err = snd_hwdep_open(&hwdep, devicename, O_RDWR)) < 0) {  
  17.         printf("hwdep interface open error: %s \n", snd_strerror(err));  
  18.         return -1;  
  19.     }  
  20.       
  21.     if ((err = snd_hwdep_ioctl(hwdep, MY_SOC_IOCTL_SET_CALL_PATH, &enable)) < 0) {  
  22.         printf("hwdep ioctl error: %s \n", snd_strerror(err));  
  23.     }  
  24.       
  25.     snd_hwdep_close(hwdep);  
  26.       
  27.     return 0;  
  28. }  
[cpp] view plain copy print ?
  1. #include    
  2. <fcntl.h>  
  3. #include <sys/ioctl.h>   
  4. #include <alsa/hwdep.h>   
  5. #include <alsa/error.h>   
  6. #include <stdio.h>   
  7.   
  8. #define MY_SOC_IOCTL_SET_CALL_PATH   _IOWR('H', 0x10, int)  
  9.   
  10. int main()  
  11. {  
  12.     const char *devicename = "hw:0,0";  
  13.     snd_hwdep_t *hwdep;  
  14.     int err;  
  15.     int enable = 1;  
  16.       
  17.     if ((err = snd_hwdep_open(&hwdep, devicename, O_RDWR)) < 0) {  
  18.         printf("hwdep interface open error: %s \n", snd_strerror(err));  
  19.         return -1;  
  20.     }  
  21.       
  22.     if ((err = snd_hwdep_ioctl(hwdep, MY_SOC_IOCTL_SET_CALL_PATH,   
  23. &enable)) < 0) {  
  24.         printf("hwdep ioctl error: %s \n", snd_strerror(err));  
  25.     }  
  26.       
  27.     snd_hwdep_close(hwdep);  
  28.       
  29.     return 0;  
  30. }  
#include <fcntl.h> #include <sys/ioctl.h> #include <alsa/hwdep.h> #include <alsa/error.h> #include <stdio.h> #define MY_SOC_IOCTL_SET_CALL_PATH _IOWR('H', 0x10, int) int main() { const char *devicename = "hw:0,0"; snd_hwdep_t *hwdep; int err; int enable = 1; if ((err = snd_hwdep_open(&hwdep, devicename, O_RDWR)) < 0) { printf("hwdep interface open error: %s \n", snd_strerror(err)); return -1; } if ((err = snd_hwdep_ioctl(hwdep, MY_SOC_IOCTL_SET_CALL_PATH, &enable)) < 0) { printf("hwdep ioctl error: %s \n", snd_strerror(err)); } snd_hwdep_close(hwdep); return 0; }

总结


可以看出hwdep的本意主要是用于download dsp image,但通过它也可实现类似于其他字符设备的ioctl。我说过音频大多控制是通过snd_kcontrol,但有些功能如果使用这种方式会比较繁琐且模块太过耦合。

举个例子:电话语音通路,它不同于音乐回放通路,通话时才需要打开。如果用snd_kcontrol,则上层需要调用多个control.set,并且更换CODEC芯片的话,上层也要跟着修改control name;如果使用hwdep ioctl的话,就没有这个问题,只需要保证命令字cmd一致,底层如何管理通话通路的一系列部件开关,上层都不需要关心。

你可能感兴趣的:(关于ALSA的小结)