这一年来的一个重要工作是音频设备驱动方面,如驱动开发、调试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,写得很好,将音频开发的大致流程和关键点都写出来了。
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,如
- ~ # 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
- ~ #
- 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
如下简单分析ioctl:
- // 套接字接口函数集
- 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;
- }
- //套接字接口函数集
- 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;
- }
实现自定义的hwdep操作函数
1、 首先实现需要的操作函数:
- 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;
- }
- 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模块:
- 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;
- 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 - 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.
- */
- /**
- * 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.
- */
上层调用范例
- #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;
- }
- #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一致,底层如何管理通话通路的一系列部件开关,上层都不需要关心。