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
如下简单分析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;
- }
实现自定义的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;
- }
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;
- /**
- * 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;
- }
总结
可以看出hwdep的本意主要是用于download dsp image,但通过它也可实现类似于其他字符设备的ioctl。我说过音频大多控制是通过snd_kcontrol,但有些功能如果使用这种方式会比较繁琐且模块太过耦合。
举个例子:电话语音通路,它不同于音乐回放通路,通话时才需要打开。如果用snd_kcontrol,则上层需要调用多个control.set,并且更换CODEC芯片的话,上层也要跟着修改control name;如果使用hwdep ioctl的话,就没有这个问题,只需要保证命令字cmd一致,底层如何管理通话通路的一系列部件开关,上层都不需要关心。
- 上一篇:ANDROID音频系统散记之三:resample-2
- 下一篇:关于ALSA的小结
-
3楼
kobewylb 2011-11-07 13:42发表 [回复]
-
- 谢谢老大,现在的问题是如果像你们用alsa实现的话,里面会有这种功能吗,我看到2.3的源代码中\device\samsung\crespo\libaudio三星也用了这个,如果是用这种方式博主有好的建议吗? 还有就是您是做移植的,所以本来就是会需要定制的;但是我是做应用的所以只能在框架允许的基础上进行,不要修改android c层的源代码,否则要重烧ROM。
-
2楼
kobewylb 2011-11-06 21:35发表 [回复]
-
- 楼主我之前做了截屏相关的应用,是用/ open("/dev/graphics/fb0", O_RDWR);然后进行文件操作,这样没有修改android底层代码, 但是dev/eac是一个linux的驱动,用类似的方法行不通,还有什么方法能访问到eac。还有AudioDumpInterface和AudioStreamOutGeneric的调用必然会修改android源代码,这样是不是只能烧Rom,但是普通用户不会为了一个软件付出这么大的代价。还有不同公司实现Audio部分的硬件抽象层,是不是会由很大的不同,还是都是通过dev/eac进行读写。小弟对android底层的了解很少,请博主对我说的几种情况是否可行给出您宝贵的意见。
-
1楼
kobewylb 2011-11-04 16:03发表 [回复]
-
-
博主,看了你的一些文章,知道你对android的audio system 有比较深入的了解。你能帮我分析一下在android系统的底层有拿到所有进过混音之后的数据吗? 我看了android的源代码,知道经过AudioFlinger中混音线程最后将buffer传到了AudioHardwareGeneric 中之后写入了/dev/eac驱动。我向问下在这个过程中有没有获得声音(自己没有播放的数据源)的可行性,如果这个不可行有没有别的办法得到声音。先谢谢了!
-
Re:
sepnic 2011-11-04 16:37发表 [回复]
-
-
回复kobewylb:在这篇文章http://blog.youkuaiyun.com/sepnic/article/details/6241019,我有提到“Android可以将上层record的声音保存到一个文件”,你需要看看AudioDumpInterface这个类的实现。同时,在build\target\board\generic\BoardConfig.mk这个文件中,需要配置ENABLE_AUDIO_DUMP := true
以上是保存record数据。如果想按照你的方式保存playback数据,你需要找到AudioStreamOutGeneric::write这个函数:
- ssize_t AudioStreamOutGeneric::write(const void* buffer, size_t bytes)
- {
- Mutex::Autolock _l(mLock);
- return ssize_t(::write(mFd, buffer, bytes));
- }
看到那个buffer没有?这就是音频数据缓冲的首地址,bytes是其大小。你可以在这里将数据保存到文件中,如果怕丢失数据的话,那就开多一个线程来做这个事情吧。总之大致方法就这样了。
-