Linux audio子系统研究
嵌入式开发联盟mcuos.com原创文章
Osboy:mcuos.com@gmail.com
1 认识一些有用的数据结构
SoC 设备结构体:
他是整个Audio子系统描述结构体。
/* SoC Device - the audio subsystem */
struct snd_soc_device {
struct device *dev;
struct snd_soc_card *card;
struct snd_soc_codec_device *codec_dev;
void *codec_data;
};
每个audio驱动都会遵循linux的 platform driver架构,分别有platform_device结构体描述定义和platform_driver结构体描述定义。在audio的machine driver文件里面都会有通过下面语句分配的platform_device结构体。例如:
nuc900evb_asoc_dev = platform_device_alloc("soc-audio", -1);
那么snd_soc_device结构体和这个platform_device结构体之间的关系就是如下图。
下图涉及的所有的结构体都是asoc子系统重要的数据结构,需要大家了解。
2 soc-core.c说起
sound\soc\soc-core.c中有:
static int __init snd_soc_init(void)
{
#ifdef CONFIG_DEBUG_FS
debugfs_root = debugfs_create_dir("asoc", NULL);
if (IS_ERR(debugfs_root) || !debugfs_root) {
printk(KERN_WARNING
"ASoC: Failed to create debugfs directory\n");
debugfs_root = NULL;
}
#endif
return platform_driver_register(&soc_driver);
}
static void __exit snd_soc_exit(void)
{
#ifdef CONFIG_DEBUG_FS
debugfs_remove_recursive(debugfs_root);
#endif
platform_driver_unregister(&soc_driver);
}
module_init(snd_soc_init);
module_exit(snd_soc_exit);
/* ASoC platform driver */
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
从platform_driver_register函数的使用中这个文件中的snd_soc_init符合标准的linux platform驱动架构,在machine driver,nuc900-audio.c中会有:
static struct snd_soc_dai_link nuc900evb_ac97_dai = {
.name = "AC97",
.stream_name = "AC97 HiFi",
.cpu_dai = &nuc900_ac97_dai,
.codec_dai = &ac97_dai,
};
Audio子系统的接口层结构体,他有两部分dai一个是soc片上的ac97控制器dai,一个是codec芯片的dai。
static struct snd_soc_cardnuc900evb_audio_machine = {
.name = "NUC900EVB_AC97",
.dai_link = &nuc900evb_ac97_dai,
/* SoC machine DAI configuration, glues a codec and cpu DAI together */
Soc machine driver的DAI配置结构体,链接codec和cpu的DAI在一起.
struct snd_soc_dai_link {
char *name; /* Codec name */
char *stream_name; /* Stream name */
/* DAI */
struct snd_soc_dai *codec_dai;
struct snd_soc_dai *cpu_dai;
/* machine stream operations */
struct snd_soc_ops *ops;
/* codec/machine specific init - e.g. add machine controls */
int (*init)(struct snd_soc_codec *codec);
/* Keep DAI active over suspend */
unsigned int ignore_suspend:1;
/* Symmetry requirements */
unsigned int symmetric_rates:1;
/* Symmetry data - only valid if symmetry is being enforced */
unsigned int rate;
/* DAI pcm */
struct snd_pcm *pcm;
};
.num_links = 1,
.platform = &nuc900_soc_platform,
Dma分配相关的驱动。
/* SoC platform interface */
structsnd_soc_platform {
char *name;
struct list_head list;
int (*probe)(struct platform_device *pdev);
int (*remove)(struct platform_device *pdev);
int (*suspend)(struct snd_soc_dai_link *dai_link);
int (*resume)(struct snd_soc_dai_link *dai_link);
/* pcm creation and destruction */
int (*pcm_new)(struct snd_card *, struct snd_soc_dai *,
struct snd_pcm *);
void (*pcm_free)(struct snd_pcm *);
/*
* For platform caused delay reporting.
* Optional.
*/
snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
struct snd_soc_dai *);
/* platform stream ops */
struct snd_pcm_ops *pcm_ops;
};
};
snd_soc_card应该是machine driver结构层的。
static struct snd_soc_device nuc900evb_ac97_devdata = {
.card = &nuc900evb_audio_machine,
.codec_dev = &soc_codec_dev_ac97,
};
Soc设备,audio的子系统
static struct platform_device *nuc900evb_asoc_dev;
static int __init nuc900evb_audio_init(void)
{
int ret;
ret = -ENOMEM;
nuc900evb_asoc_dev = platform_device_alloc("soc-audio", -1);
if (!nuc900evb_asoc_dev)
goto out;
/* nuc900 board audio device */
platform_set_drvdata(nuc900evb_asoc_dev, &nuc900evb_ac97_devdata);
nuc900evb_ac97_devdata.dev = &nuc900evb_asoc_dev->dev;
ret = platform_device_add(nuc900evb_asoc_dev);
if (ret) {
platform_device_put(nuc900evb_asoc_dev);
nuc900evb_asoc_dev = NULL;
}
out:
return ret;
}
static void __exit nuc900evb_audio_exit(void)
{
platform_device_unregister(nuc900evb_asoc_dev);
}
module_init(nuc900evb_audio_init);
module_exit(nuc900evb_audio_exit);
3 从字符型驱动原型开始研究
(1)分配和释放设备主次设备号
静态分配:
register_chrdev_region
动态分配:
alloc_chrdev_region
(2)分配cdev字符设备结构体
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
一般使用下面的语句分配一个cdev结构体变量:
struct cdev *cdev;
cdev = cdev_alloc();
if (!cdev)
goto out2;
(3)初始化cdev结构体变量
cdev_init(struct cdev *cdev, const struct file_operations *fops)
(4)cdev_add,添加字符设备到内核
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
介绍一个常用的字符驱动函数:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
参数说明:
其中,major是为设备驱动程序向系统申请的主设备号,如果为0则系统为此驱动程序动态地分配一个主设备号。
name是设备名。
fops就是前面所说的 对各个调用的入口点的说明。
返回值:
此函数返回0表示成功。返回-EINVAL表示申请的主设备号非法,一般来说是主设备号大于系统所允许的最大设备号。返回 -EBUSY表示所申请的主设备号正在被其它设备驱动程序使用。如果是动态分配主设备号成功,此函数将返回所分配的主设备号。
如果 register_chrdev操作成功,设备名就会出现在/proc/devices文件里。
在成功的向系统注册了设备驱动程序后(调用register_chrdev() 成功后),就可以用mknod命令来把设备映射为一个特别文件,其它程序使用这个设备的时候,只要对此特别文件进行操作就行了。
4 ASoc符合字符驱动架构
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open
};
static int __init alsa_sound_init(void)
{
snd_major = major;
snd_ecards_limit = cards_limit;
if (register_chrdev(major, "alsa", &snd_fops)) {
snd_printk(KERN_ERR "unable to register native major device number %d\n", major);
return -EIO;
}
if (snd_info_init() < 0) {
unregister_chrdev(major, "alsa");
return -ENOMEM;
}
snd_info_minor_register();
#ifndef MODULE
printk(KERN_INFO "Advanced Linux Sound Architecture Driver Version " CONFIG_SND_VERSION CONFIG_SND_DATE ".\n");
#endif
return 0;
}
static void __exit alsa_sound_exit(void)
{
snd_info_minor_unregister();
snd_info_done();
unregister_chrdev(major, "alsa");
}
subsys_initcall(alsa_sound_init);
module_exit(alsa_sound_exit);
当应用程序打开sound设备的时候,第一个调用的函数就是snd_open函数:
static int snd_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct snd_minor *mptr = NULL;
const struct file_operations *old_fops;
int err = 0;
if (minor >= ARRAY_SIZE(snd_minors))
return -ENODEV;
mutex_lock(&sound_mutex);
mptr = snd_minors[minor];
if (mptr == NULL) {
mptr = autoload_device(minor);
if (!mptr) {
mutex_unlock(&sound_mutex);
return -ENODEV;
}
}
old_fops = file->f_op;
file->f_op = fops_get(mptr->f_ops);
if (file->f_op == NULL) {
file->f_op = old_fops;
err = -ENODEV;
}
mutex_unlock(&sound_mutex);
if (err < 0)
return err;
if (file->f_op->open) {
err = file->f_op->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
}
fops_put(old_fops);
return err;
}
5 Alsa 控制接口
控制接口非常广泛的应用在许多转换,变调等场合,可以从用户空间进行控制。混音器接口是一个最重要的接口。换句话说,在ALSA中,所有的混音器mixer的工作都是通过控制接口API实现的,ALSA有一个定义很好的AC97的控制模块。如果你的声卡仅仅支持AC97,你可以忽略这章。
5.1 创建一个新的控制接口
5.1.1 Kernel内核驱动端
为了创建一个新的控制接口,需要定义三个函数:info,get和put。然后定义一个snd_kcontrol_new类型的记录,如下结构体定义为:
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;
};
例如marvell levante的control结构体初始化为 :
static const struct snd_kcontrol_new levante_direct_access[] = {
/* Audio Register */
SOC_SINGLE("LEVANTE_PCM_INTERFACE1", PCM_INTERFACE1, 0, 0xff, 0), /* 0xB0 */
SOC_SINGLE("LEVANTE_PCM_INTERFACE2", PCM_INTERFACE2, 0, 0xff, 0), /* 0xB1 */
SOC_SINGLE("LEVANTE_PCM_INTERFACE3", PCM_INTERFACE3, 0, 0xff, 0), /* 0xB2 */
SOC_SINGLE("LEVANTE_PCM_RATE", ADC_PCM, 0, 0xff, 0), /* 0xB3 */
SOC_SINGLE("LEVANTE_ECHO_CANCEL_PATH", ECHO_CANCEL_PATH, 0, 0xff, 0), /* 0xB4 */
SOC_SINGLE("LEVANTE_SIDETONE_GAIN1", SIDETONE_GAIN1, 0, 0xff, 0), /* 0xB5 */
SOC_SINGLE("LEVANTE_SIDETONE_GAIN2", SIDETONE_GAIN2, 0, 0xff, 0), /* 0xB6 */
SOC_SINGLE("LEVANTE_SIDETONE_GAIN3", SIDETONE_GAIN3, 0, 0xff, 0), /* 0xB7 */
SOC_SINGLE:
#define SOC_SINGLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
.put = snd_soc_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
Pxa920 linuxBSP中的这部分实现为:
在machine driver部分,probe实现中:
struct snd_soc_codec_device soc_codec_dev_pm860x_audio = {
.probe = pm860x_audio_probe,
.remove = pm860x_audio_remove,
.suspend = pm860x_audio_suspend,
.resume = pm860x_audio_resume,
};
EXPORT_SYMBOL_GPL(soc_codec_dev_pm860x_audio);
static int pm860x_audio_probe(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct _setup_data *setup;
struct snd_soc_codec *codec;
struct pm860x_audio_priv *pm860x_audio;
int ret = 0;
printk(KERN_INFO "pm860x Audio Codec %s\n", PM860X_AUDIO_VERSION);
setup = socdev->codec_data;
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (codec == NULL)
return -ENOMEM;
pm860x_audio = kzalloc(sizeof(struct pm860x_audio_priv), GFP_KERNEL);
if (pm860x_audio == NULL) {
kfree(codec);
return -ENOMEM;
}
codec->private_data = pm860x_audio;
socdev->card->codec = codec;
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
INIT_DELAYED_WORK(&codec->delayed_work, pm860x_audio_work);
pm860x_audio_init(socdev);
return ret;
}
pm860x_audio_init函数总有调用下面的这个函数接口:
pm860x_audio_add_controls(codec);
static int pm860x_audio_add_controls(struct snd_soc_codec *codec)
{
levante_add_direct_access(codec);
levante_id_access(codec);
return 0;
}
static int levante_add_direct_access(struct snd_soc_codec *codec)
{
int err, i;
for (i = 0; i < ARRAY_SIZE(levante_direct_access); i++) {
err = snd_ctl_add(codec->card,
snd_soc_cnew(&levante_direct_access[i], codec, NULL));
levante_direct_access前面已经定义好了。他是snd_kcontrol_new类型结构体。
.info = snd_soc_info_volsw,
.get = snd_soc_get_volsw,\
.put = snd_soc_put_volsw, \
这三个函数就是我们必须要实现的,真正和硬件codec打交道的函数API.
if (err < 0)
return err;
}
return 0;
}
从这个函数我们看出,snd_ctl_add 和snd_soc_cnew都是前面讲过的创建control的必备函数。到此controll功能创建完毕。
Info函数:
int snd_soc_info_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
int max = mc->max;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
if (max == 1 && !strstr(kcontrol->id.name, " Volume"))
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
else
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = shift == rshift ? 1 : 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = max;
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_info_volsw);
Get函数:
int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
unsigned int reg = mc->reg;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
int max = mc->max;
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
ucontrol->value.integer.value[0] =
(snd_soc_read(codec, reg) >> shift) & mask;
if (shift != rshift)
ucontrol->value.integer.value[1] =
(snd_soc_read(codec, reg) >> rshift) & mask;
/* codec IO */
static inline unsigned int snd_soc_read(struct snd_soc_codec *codec,
unsigned int reg)
{
return codec->read(codec, reg);
}
最终会调用到codec中的读写函数。
if (invert) {
ucontrol->value.integer.value[0] =
max - ucontrol->value.integer.value[0];
if (shift != rshift)
ucontrol->value.integer.value[1] =
max - ucontrol->value.integer.value[1];
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_get_volsw);
Put函数:
int snd_soc_put_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
unsigned int reg = mc->reg;
unsigned int shift = mc->shift;
unsigned int rshift = mc->rshift;
int max = mc->max;
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
unsigned int val, val2, val_mask;
val = (ucontrol->value.integer.value[0] & mask);
if (invert)
val = max - val;
val_mask = mask << shift;
val = val << shift;
if (shift != rshift) {
val2 = (ucontrol->value.integer.value[1] & mask);
if (invert)
val2 = max - val2;
val_mask |= mask << rshift;
val |= val2 << rshift;
}
return snd_soc_update_bits(codec, reg, val_mask, val);
}
EXPORT_SYMBOL_GPL(snd_soc_put_volsw);
int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
unsigned int mask, unsigned int value)
{
int change;
unsigned int old, new;
mutex_lock(&io_mutex);
old = snd_soc_read(codec, reg);
new = (old & ~mask) | value;
change = old != new;
if (change)
snd_soc_write(codec, reg, new);
mutex_unlock(&io_mutex);
return change;
}
EXPORT_SYMBOL_GPL(snd_soc_update_bits);
5.1.2 Kernel提供给Alsa层的API
pm860x_audio_init函数总有调用下面的这个函数接口:
pm860x_audio_add_controls(codec);
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
printk(KERN_ERR "pm860x audio: failed to create pcms\n");
goto pcm_err;}
int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid)
{
struct snd_soc_card *card = socdev->card;
struct snd_soc_codec *codec = card->codec;
int ret, i;
mutex_lock(&codec->mutex);
/* register a sound card */
ret = snd_card_create(idx, xid, codec->owner, 0, &codec->card);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create sound card for codec %s\n",
codec->name);
mutex_unlock(&codec->mutex);
return ret;
}
codec->socdev = socdev;
codec->card->dev = socdev->dev;
codec->card->private_data = codec;
strncpy(codec->card->driver, codec->name, sizeof(codec->card->driver));
/* create the pcms */
for (i = 0; i < card->num_links; i++) {
ret = soc_new_pcm(socdev, &card->dai_link[i], i);
if (ret < 0) {
printk(KERN_ERR "asoc: can't create pcm %s\n",
card->dai_link[i].stream_name);
mutex_unlock(&codec->mutex);
return ret;
}
}
mutex_unlock(&codec->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_new_pcms);
snd_card_create函数中会有下面code片段:
err = snd_ctl_create(card);
if (err < 0) {
snd_printk(KERN_ERR "unable to register control minors\n");
goto __error;
}
int snd_ctl_create(struct snd_card *card)
{
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
if (snd_BUG_ON(!card))
return -ENXIO;
return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
}
int snd_device_new(struct snd_card *card, snd_device_type_t type,
void *device_data, struct snd_device_ops *ops)
{
struct snd_device *dev;
if (snd_BUG_ON(!card || !device_data || !ops))
return -ENXIO;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (dev == NULL) {
snd_printk(KERN_ERR "Cannot allocate device\n");
return -ENOMEM;
}
dev->card = card;
dev->type = type;
dev->state = SNDRV_DEV_BUILD;
dev->device_data = device_data;
dev->ops = ops;
list_add(&dev->list, &card->devices); /* add to the head of list */
return 0;
}
EXPORT_SYMBOL(snd_device_new);
综上所述,我们来看Sound/core/control.c中代码,他是最终提供给user lib层调用的一些API了。
static int snd_ctl_dev_register(struct snd_device *device)
{
struct snd_card *card = device->device_data;
int err, cardnum;
char name[16];
if (snd_BUG_ON(!card))
return -ENXIO;
cardnum = card->number;
if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
return -ENXIO;
sprintf(name, "controlC%i", cardnum);
if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
&snd_ctl_f_ops, card, name)) < 0)
return err;
return 0;
}
static const struct file_operations snd_ctl_f_ops =
{
.owner = THIS_MODULE,
.read = snd_ctl_read,
.open = snd_ctl_open,
.release = snd_ctl_release,
.poll = snd_ctl_poll,
.unlocked_ioctl = snd_ctl_ioctl,
.compat_ioctl = snd_ctl_ioctl_compat,
.fasync = snd_ctl_fasync,
};
snd_ctl_ioctl是个很重要的函数,他直接可以提供给alsalib中的函数接口。
static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct snd_ctl_file *ctl;
struct snd_card *card;
struct snd_kctl_ioctl *p;
void __user *argp = (void __user *)arg;
int __user *ip = argp;
int err;
ctl = file->private_data;
card = ctl->card;
if (snd_BUG_ON(!card))
return -ENXIO;
switch (cmd) {
case SNDRV_CTL_IOCTL_PVERSION:
return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;
case SNDRV_CTL_IOCTL_CARD_INFO:
return snd_ctl_card_info(card, ctl, cmd, argp);
case SNDRV_CTL_IOCTL_ELEM_LIST:
return snd_ctl_elem_list(card, argp);
case SNDRV_CTL_IOCTL_ELEM_INFO:
return snd_ctl_elem_info_user(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_READ:
return snd_ctl_elem_read_user(card, argp);
case SNDRV_CTL_IOCTL_ELEM_WRITE:
return snd_ctl_elem_write_user(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_LOCK:
return snd_ctl_elem_lock(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
return snd_ctl_elem_unlock(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_ADD:
return snd_ctl_elem_add_user(ctl, argp, 0);
case SNDRV_CTL_IOCTL_ELEM_REPLACE:
return snd_ctl_elem_add_user(ctl, argp, 1);
case SNDRV_CTL_IOCTL_ELEM_REMOVE:
return snd_ctl_elem_remove(ctl, argp);
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
return snd_ctl_subscribe_events(ctl, ip);
case SNDRV_CTL_IOCTL_TLV_READ:
return snd_ctl_tlv_ioctl(ctl, argp, 0);
case SNDRV_CTL_IOCTL_TLV_WRITE:
return snd_ctl_tlv_ioctl(ctl, argp, 1);
case SNDRV_CTL_IOCTL_TLV_COMMAND:
return snd_ctl_tlv_ioctl(ctl, argp, -1);
case SNDRV_CTL_IOCTL_POWER:
return -ENOPROTOOPT;
case SNDRV_CTL_IOCTL_POWER_STATE:
#ifdef CONFIG_PM
return put_user(card->power_state, ip) ? -EFAULT : 0;
#else
return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0;
#endif
}
down_read(&snd_ioctl_rwsem);
list_for_each_entry(p, &snd_control_ioctls, list) {
err = p->fioctl(card, ctl, cmd, arg);
if (err != -ENOIOCTLCMD) {
up_read(&snd_ioctl_rwsem);
return err;
}
}
up_read(&snd_ioctl_rwsem);
snd_printdd("unknown ioctl = 0x%x\n", cmd);
return -ENOTTY;
}
5.1.3 Alsa层的Amixer API
alsa\alsa-tools\amixer.c文件中,
int main(int argc, char *argv[])
{
int morehelp, level = 0;
struct option long_option[] =
{
{"help", 0, NULL, HELPID_HELP},
{"card", 1, NULL, HELPID_CARD},
{"device", 1, NULL, HELPID_DEVICE},
{"quiet", 0, NULL, HELPID_QUIET},
{"inactive", 0, NULL, HELPID_INACTIVE},
{"debug", 0, NULL, HELPID_DEBUG},
{"nocheck", 0, NULL, HELPID_NOCHECK},
{"version", 0, NULL, HELPID_VERSION},
{NULL, 0, NULL, 0},
};
morehelp = 0;
while (1) {
int c;
if ((c = getopt_long(argc, argv, "hc:D:qidnv", long_option, NULL)) < 0)
break;
switch (c) {
case 'h':
case HELPID_HELP:
help();
return 0;
case 'c':
case HELPID_CARD:
{
int i;
i = snd_card_get_index(optarg);
if (i >= 0 && i < 32)
sprintf(card, "hw:%i", i);
else {
fprintf(stderr, "\07Invalid card number.\n");
morehelp++;
}
}
break;
case 'D':
case HELPID_DEVICE:
strncpy(card, optarg, sizeof(card)-1);
card[sizeof(card)-1] = '\0';
break;
case 'q':
case HELPID_QUIET:
quiet = 1;
break;
case 'i':
case HELPID_INACTIVE:
level |= LEVEL_INACTIVE;
break;
case 'd':
case HELPID_DEBUG:
debugflag = 1;
break;
case 'n':
case HELPID_NOCHECK:
no_check = 1;
break;
case 'v':
case HELPID_VERSION:
printf("amixer version" "mhn test\n"); // SND_UTIL_VERSION_STR "\n");
return 1;
default:
fprintf(stderr, "\07Invalid switch or option needs an argument.\n");
morehelp++;
}
}
if (morehelp) {
help();
return 1;
}
if (argc - optind <= 0) {
return selems(LEVEL_BASIC | level) ? 1 : 0;
}
if (!strcmp(argv[optind], "help")) {
return help() ? 1 : 0;
} else if (!strcmp(argv[optind], "info")) {
return info() ? 1 : 0;
} else if (!strcmp(argv[optind], "controls")) {
return controls(level) ? 1 : 0;
} else if (!strcmp(argv[optind], "contents")) {
return controls(LEVEL_BASIC | level) ? 1 : 0;
} else if (!strcmp(argv[optind], "scontrols") || !strcmp(argv[optind], "simple")) {
return selems(level) ? 1 : 0;
} else if (!strcmp(argv[optind], "scontents")) {
return selems(LEVEL_BASIC | level) ? 1 : 0;
} else if (!strcmp(argv[optind], "sset") || !strcmp(argv[optind], "set")) {
return sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0) ? 1 : 0;
} else if (!strcmp(argv[optind], "sget") || !strcmp(argv[optind], "get")) {
return sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1) ? 1 : 0;
} else if (!strcmp(argv[optind], "cset")) {
return cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0) ? 1 : 0;
} else if (!strcmp(argv[optind], "cget")) {
return cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1) ? 1 : 0;
} else if (!strcmp(argv[optind], "events")) {
return events(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL);
} else if (!strcmp(argv[optind], "sevents")) {
return sevents(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL);
} else {
fprintf(stderr, "amixer: Unknown command '%s'...\n", argv[optind]);
}
return 0;
}
下面我们来看看cset的实现:
static int cset(int argc, char *argv[], int roflag)
{
int err;
snd_ctl_t *handle;
snd_ctl_elem_info_t *info;
snd_ctl_elem_id_t *id;
snd_ctl_elem_value_t *control;
char *ptr;
unsigned int idx, count;
long tmp;
snd_ctl_elem_type_t type;
snd_ctl_elem_info_alloca(&info);
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_value_alloca(&control);
if (argc < 1) {
fprintf(stderr, "Specify a full control identifier: [[iface=<iface>,][name='name',][index=<index>,][device=<device>,][subdevice=<subdevice>]]|[numid=<numid>]\n");
return -EINVAL;
}
if (parse_control_id(argv[0], id)) {
fprintf(stderr, "Wrong control identifier: %s\n", argv[0]);
return -EINVAL;
}
if (debugflag) {
printf("VERIFY ID: ");
show_control_id(id);
printf("\n");
}
if ((err = snd_ctl_open(&handle, card, 0)) < 0) {
error("Control %s open error: %s\n", card, snd_strerror(err));
return err;
}
snd_ctl_elem_info_set_id(info, id);
if ((err = snd_ctl_elem_info(handle, info)) < 0) {
error("Control %s cinfo error: %s\n", card, snd_strerror(err));
return err;
}
snd_ctl_elem_info_get_id(info, id); /* FIXME: Remove it when hctl find works ok !!! */
type = snd_ctl_elem_info_get_type(info);
count = snd_ctl_elem_info_get_count(info);
snd_ctl_elem_value_set_id(control, id);
if (!roflag) {
ptr = argv[1];
for (idx = 0; idx < count && idx < 128 && ptr && *ptr; idx++) {
switch (type) {
case SND_CTL_ELEM_TYPE_BOOLEAN:
tmp = 0;
if (!strncasecmp(ptr, "on", 2) || !strncasecmp(ptr, "up", 2)) {
tmp = 1;
ptr += 2;
} else if (!strncasecmp(ptr, "yes", 3)) {
tmp = 1;
ptr += 3;
} else if (!strncasecmp(ptr, "toggle", 6)) {
tmp = snd_ctl_elem_value_get_boolean(control, idx);
tmp = tmp > 0 ? 0 : 1;
ptr += 6;
} else if (isdigit(*ptr)) {
tmp = atoi(ptr) > 0 ? 1 : 0;
while (isdigit(*ptr))
ptr++;
} else {
while (*ptr && *ptr != ',')
ptr++;
}
snd_ctl_elem_value_set_boolean(control, idx, tmp);
break;
case SND_CTL_ELEM_TYPE_INTEGER:
tmp = get_integer(&ptr,
snd_ctl_elem_info_get_min(info),
snd_ctl_elem_info_get_max(info));
snd_ctl_elem_value_set_integer(control, idx, tmp);
break;
case SND_CTL_ELEM_TYPE_INTEGER64:
tmp = get_integer64(&ptr,
snd_ctl_elem_info_get_min64(info),
snd_ctl_elem_info_get_max64(info));
snd_ctl_elem_value_set_integer64(control, idx, tmp);
break;
case SND_CTL_ELEM_TYPE_ENUMERATED:
tmp = get_integer(&ptr, 0, snd_ctl_elem_info_get_items(info) - 1);
snd_ctl_elem_value_set_enumerated(control, idx, tmp);
break;
case SND_CTL_ELEM_TYPE_BYTES:
tmp = get_integer(&ptr, 0, 255);
snd_ctl_elem_value_set_byte(control, idx, tmp);
break;
default:
break;
}
if (!strchr(argv[1], ','))
ptr = argv[1];
else if (*ptr == ',')
ptr++;
}
if ((err = snd_ctl_elem_write(handle, control)) < 0) {
error("Control %s element write error: %s\n", card, snd_strerror(err));
return err;
}
}
int snd_ctl_elem_write(snd_ctl_t *ctl, snd_ctl_elem_value_t *control)
{
assert(ctl && control && (control->id.name[0] || control->id.numid));
return ctl->ops->element_write(ctl, control);
}
程序调用到这里,我们就碰到两个问题,是走hw还是ext?
\alsa\alsa-lib\src\control中有两个文件:
control_ext.c à for plugin接口
control_hw.c à for hardware,直接调用codec寄存器
for plugin:
static snd_ctl_ops_t snd_ctl_ext_ops = {
.close = snd_ctl_ext_close,
.nonblock = snd_ctl_ext_nonblock,
.async = snd_ctl_ext_async,
.subscribe_events = snd_ctl_ext_subscribe_events,
.card_info = snd_ctl_ext_card_info,
.element_list = snd_ctl_ext_elem_list,
.element_info = snd_ctl_ext_elem_info,
.element_add = snd_ctl_ext_elem_add,
.element_replace = snd_ctl_ext_elem_replace,
.element_remove = snd_ctl_ext_elem_remove,
.element_read = snd_ctl_ext_elem_read,
snd_ctl_ext_elem_read最终调用:
static int snd_ctl_ext_elem_read(snd_ctl_t *handle, snd_ctl_elem_value_t *control)
{
snd_ctl_ext_t *ext = handle->private_data;
snd_ctl_ext_key_t key;
int type, ret;
unsigned int access, count;
key = ext->callback->find_elem(ext, &control->id);
if (key == SND_CTL_EXT_KEY_NOT_FOUND)
return -ENOENT;
ret = ext->callback->get_attribute(ext, key, &type, &access, &count);
if (ret < 0)
goto err;
ret = -EINVAL;
switch (type) {
case SND_CTL_ELEM_TYPE_BOOLEAN:
case SND_CTL_ELEM_TYPE_INTEGER:
if (! ext->callback->read_integer)
goto err;
ret = ext->callback->read_integer(ext, key, control->value.integer.value);
break;
case SND_CTL_ELEM_TYPE_INTEGER64:
if (! ext->callback->read_integer64)
goto err;
ret = ext->callback->read_integer64(ext, key, control->value.integer64.value);
break;
case SND_CTL_ELEM_TYPE_ENUMERATED:
if (! ext->callback->read_enumerated)
goto err;
ret = ext->callback->read_enumerated(ext, key, control->value.enumerated.item);
break;
case SND_CTL_ELEM_TYPE_BYTES:
if (! ext->callback->read_bytes)
goto err;
ret = ext->callback->read_bytes(ext, key, control->value.bytes.data,
sizeof(control->value.bytes.data));
break;
case SND_CTL_ELEM_TYPE_IEC958:
if (! ext->callback->read_iec958)
goto err;
ret=ext->callback->read_iec958(ext, key, (snd_aes_iec958_t *)&control->value.iec958);
break;
default:
break;
}
err:
if (ext->callback->free_key)
ext->callback->free_key(ext, key);
return ret;
}
蓝色部分需要我们写alsa插件,在插件里面实现这些函数。
.element_write = snd_ctl_ext_elem_write,
.element_lock = snd_ctl_ext_elem_lock,
.element_unlock = snd_ctl_ext_elem_unlock,
.hwdep_next_device = snd_ctl_ext_next_device,
.hwdep_info = snd_ctl_ext_hwdep_info,
.pcm_next_device = snd_ctl_ext_next_device,
.pcm_info = snd_ctl_ext_pcm_info,
.pcm_prefer_subdevice = snd_ctl_ext_prefer_subdevice,
.rawmidi_next_device = snd_ctl_rawmidi_next_device,
.rawmidi_info = snd_ctl_ext_rawmidi_info,
.rawmidi_prefer_subdevice = snd_ctl_ext_prefer_subdevice,
.set_power_state = snd_ctl_ext_set_power_state,
.get_power_state = snd_ctl_ext_get_power_state,
.read = snd_ctl_ext_read,
.poll_descriptors_count = snd_ctl_ext_poll_descriptors_count,
.poll_descriptors = snd_ctl_ext_poll_descriptors,
.poll_revents = snd_ctl_ext_poll_revents,
};
for hardware:
snd_ctl_ops_t snd_ctl_hw_ops = {
.close = snd_ctl_hw_close,
.nonblock = snd_ctl_hw_nonblock,
.async = snd_ctl_hw_async,
.subscribe_events = snd_ctl_hw_subscribe_events,
.card_info = snd_ctl_hw_card_info,
.element_list = snd_ctl_hw_elem_list,
.element_info = snd_ctl_hw_elem_info,
.element_add = snd_ctl_hw_elem_add,
.element_replace = snd_ctl_hw_elem_replace,
.element_remove = snd_ctl_hw_elem_remove,
.element_read = snd_ctl_hw_elem_read,
static int snd_ctl_hw_elem_read(snd_ctl_t *handle, snd_ctl_elem_value_t *control)
{
snd_ctl_hw_t *hw = handle->private_data;
if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_READ, control) < 0)
return -errno;
return 0;
}
该函数会通过系统调用调用到linux内核audio驱动中的 snd_ctl_ioctl函数,然后执行sound/core/control.c中的snd_ctl_elem_read函数,最终通过一句result = kctl->get(kctl, control);调用到前面的get函数。从而操作saremo硬件的codec寄存器。
.element_write = snd_ctl_hw_elem_write,
.element_lock = snd_ctl_hw_elem_lock,
.element_unlock = snd_ctl_hw_elem_unlock,
.element_tlv = snd_ctl_hw_elem_tlv,
.hwdep_next_device = snd_ctl_hw_hwdep_next_device,
.hwdep_info = snd_ctl_hw_hwdep_info,
.pcm_next_device = snd_ctl_hw_pcm_next_device,
.pcm_info = snd_ctl_hw_pcm_info,
.pcm_prefer_subdevice = snd_ctl_hw_pcm_prefer_subdevice,
.rawmidi_next_device = snd_ctl_hw_rawmidi_next_device,
.rawmidi_info = snd_ctl_hw_rawmidi_info,
.rawmidi_prefer_subdevice = snd_ctl_hw_rawmidi_prefer_subdevice,
.set_power_state = snd_ctl_hw_set_power_state,
.get_power_state = snd_ctl_hw_get_power_state,
.read = snd_ctl_hw_read,
};
snd_ctl_close(handle);
if (!quiet) {
snd_hctl_t *hctl;
snd_hctl_elem_t *elem;
if ((err = snd_hctl_open(&hctl, card, 0)) < 0) {
error("Control %s open error: %s\n", card, snd_strerror(err));
return err;
}
if ((err = snd_hctl_load(hctl)) < 0) {
error("Control %s load error: %s\n", card, snd_strerror(err));
return err;
}
elem = snd_hctl_find_elem(hctl, id);
if (elem)
show_control(" ", elem, LEVEL_BASIC | LEVEL_ID);
else if (debugflag)
printf("Could not find the specified element\n");
snd_hctl_close(hctl);
}
return 0;
}
5.1.4 如何写External Control Plugin SDK
http://www.alsa-project.org/alsa-doc/alsa-lib/ctl_external_plugins.html
请看上面的参考文档。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <alsa/asoundlib.h>
#include <alsa/control_external.h>
#ifdef BIONIC
#include <utils/Log.h>
#define LOG_TAG "ALSA_PLUGIN--PHONE"
#else
#define LOGE(format, arg ...) printf(format, ##arg)
#define LOGI(format, arg ...) printf(format, ##arg)
#endif
#include "audio_ipc.h"
#include "audio_protocol.h"
#include "MarvellAmixer.h"
/* debug */
#define CTL_DEBUG 0
#if CTL_DEBUG
#define dbg(format, arg ...) printf("ctl_phone: " format "\n", ## arg)
#else
#define dbg(format, arg ...)
#endif
#define PATH_CONTROL 0
#define VOLUME_CONTROL 1
#define MSASETTINGDATALEN 256
typedef struct snd_ctl_phone {
snd_ctl_ext_t ext;
int num_path_items;
} snd_ctl_phone_t;
static const char *mixer_dev[MIXER_NUM] = {
//For old generic platform
"Voice Path Control", //0
"Voice Volume Control", //1
//For new platform
"Enable HIFI Speaker", //2
"Disable HIFI Speaker", //3
"Volume Control HIFI Speaker", //4
"Mute Control HIFI Speaker", //5
"Enable Voice Speaker", //6
"Disable Voice Speaker", //7
"Volume Control Voice Speaker", //8
"Mute Control Voice Speaker", //9
"Enable FMradio Speaker", //10
"Disable FMradio Speaker", //11
"Volume Control FMradio Speaker", //12
"Mute Control FMradio Speaker", //13
"Enable HIFI Earpiece", //14
"Disable HIFI Earpiece", //15
"Volume Control HIFI Earpiece", //16
"Mute Control HIFI Earpiece", //17
"Enable Voice Earpiece", //18
"Disable Voice Earpiece", //19
"Volume Control Voice Earpiece", //20
"Mute Control Voice Earpiece", //21
"Enable FMradio Earpiece", //22
"Disable FMradio Earpiece", //23
"Volume Control FMradio Earpiece", //24
"Mute Control FMradio Earpiece", //25
"Enable HIFI Headset", //26
"Disable HIFI Headset", //27
"Volume Control HIFI Headset", //28
"Mute Control HIFI Headset", //29
"Enable Voice Headset", //30
"Disable Voice Headset", //31
"Volume Control Voice Headset", //32
"Mute Control Voice Headset", //33
"Enable FMradio Headset", //34
"Disable FMradio Headset", //35
"Volume Control FMradio Headset", //36
"Mute Control FMradio Headset", //37
"Enable HIFI Bluetooth", //38
"Disable HIFI Bluetooth", //39
"Volume Control HIFI Bluetooth", //40
"Mute Control HIFI Bluetooth", //41
"Enable Voice Bluetooth", //42
"Disable Voice Bluetooth", //43
"Volume Control Voice Bluetooth", //44
"Mute Control Voice Bluetooth", //45
"Enable FMradio Bluetooth", //46
"Disable FMradio Bluetooth", //47
"Volume Control FMradio Bluetooth", //48
"Mute Control FMradio Bluetooth", //49
"Enable HIFI Headphone", //50
"Disable HIFI Headphone", //51
"Volume Control HIFI Headphone", //52
"Mute Control HIFI Headphone", //53
"Enable Voice Headphone", //54
"Disable Voice Headphone", //55
"Volume Control Voice Headphone", //56
"Mute Control Voice Headphone", //57
"Enable FMradio Headphone", //58
"Disable FMradio Headphone", //59
"Volume Control FMradio Headphone", //60
"Mute Control FMradio Bluetooth", //61
//For UI calibration test
"UI Calibration Send Settings", //62
};
static const char *voicepath_items[] = {
"Phone no action",
"Disable Voicecall",
"Enable Voicecall",
"Disable Pcmstream Playback",
"Enable Pcmstream Playback",
"Disable Pcmstrean Capture",
"Enable Pcmstream Capture",
"Disable HIFI Playback",
"Enable HIFi Playback",
"Disable HIFI Capture",
"Enable HIFI Capture"
};
static void send_command(AUDIOIPCHEADER *header, AUDIOIPCDATA *data)
{
int sockfd = make_client_mainsock();
int reply;
int ret;
if (send_socket(sockfd, header, sizeof(AUDIOIPCHEADER)) == sizeof(AUDIOIPCHEADER))
{
if (send_socket(sockfd, data, sizeof(AUDIOIPCDATA)) == sizeof(AUDIOIPCDATA))
{
if ((ret = recv_socket(sockfd, &reply, sizeof(int))) != sizeof(int))
{
LOGE("************Unable to read reply from audio server, ret is %d, errno is %d\n", ret, errno);
}
}
else
{
LOGE("Unable to send data to audio server\n");
}
}
else
{
LOGE("Unable to set new value to audio server\n");
}
close_socket(sockfd);
}
static void send_ipc_data(int client, int method_type, int method_id, int device, int param)
{
AUDIOIPCHEADER header = INVALIDIPCHEADER;
AUDIOIPCDATA AudioIpcData = INVALIDIPCDATA;
header.client_type = client;
header.method_type = method_type;
AudioIpcData.device_type = device;
AudioIpcData.method_id = method_id;
AudioIpcData.param = param;
send_command(&header, &AudioIpcData);
}
static int phone_elem_count(snd_ctl_ext_t *ext)
{
ext = ext;
return MIXER_NUM;
}
static int phone_elem_list(snd_ctl_ext_t *ext, unsigned int offset, snd_ctl_elem_id_t *id)
{
ext = ext;
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
if (offset < MIXER_NUM)
{
snd_ctl_elem_id_set_numid(id, offset);
snd_ctl_elem_id_set_name(id, mixer_dev[offset]);
}
return 0;
}
static snd_ctl_ext_key_t phone_find_elem(snd_ctl_ext_t *ext,
const snd_ctl_elem_id_t *id)
{
const char *name;
int i;
ext = ext;
name = snd_ctl_elem_id_get_name(id);
for (i = 0; i < MIXER_NUM; i++)
if (strcmp(name, mixer_dev[i]) == 0)
return i;
return snd_ctl_elem_id_get_numid(id); /*Return numid directly --Raul*/
return SND_CTL_EXT_KEY_NOT_FOUND;
}
static int phone_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
int *type, unsigned int *acc, unsigned int *count)
{
ext = ext;
if (key == 0)
{
*type = SND_CTL_ELEM_TYPE_ENUMERATED;
*acc = SND_CTL_EXT_ACCESS_READWRITE;
*count = 1;
}
else if (key > 0 && key < MIXER_NUM)
{
*type = SND_CTL_ELEM_TYPE_INTEGER;
*acc = SND_CTL_EXT_ACCESS_READWRITE;
*count = 1;
}
else
return -EINVAL;
return 0;
}
static int phone_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
long *imin, long *imax, long *istep)
{
ext = ext;
if (key == 0)
{
LOGE("key 0 is not a integer interface!\n");
return -EINVAL;
}
else if (key > 0 && key < MIXER_NUM)
{
*istep = 1;
*imin = 0;
*imax = 268435455; //0x0fffffff
return 0;
}
else
{
LOGE("key %d is out of range!\n", (int)key);
return -EINVAL;
}
}
static int phone_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
long *value)
{
SOCKETDATA socketdata = INVALIDSOCKETDATA;
AUDIOMIXERDATA AudioMixerData = INVALIDAUDIOMIXERDATA;
ext = ext;
key = key;
if (!value) return 0;
*value = 0;
socketdata.client_type = PHONE_CLIENT;
socketdata.method_type = PHONE_PLUGIN_CTL_READ;
int sockfd = make_client_mainsock();
if (send_socket(sockfd, &socketdata, sizeof(socketdata)) == sizeof(socketdata))
{
if (recv_socket(sockfd, &AudioMixerData, sizeof(AudioMixerData)) == sizeof(AudioMixerData))
{
*value = AudioMixerData.volume_phone;
}
}
else
{
LOGE("Unable to receive data from audio server \n");
}
close_socket(sockfd);
return 0;
}
static int phone_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
long *value)
{
int result = 0;
long curvalue;
int trans_sockfd = 0;
int rsend;
int i;
char dataBuf[MSASETTINGDATALEN];
FILE *sendFile = NULL;
FILE *dumpFile = NULL;
dbg("integer: key is %d, value is %d\n", key, *value);
if (key == 1)
{
phone_read_integer( ext, key, &curvalue);
if (value && *value != curvalue)
{
SOCKETDATA socketdata = INVALIDSOCKETDATA;
AUDIOMIXERDATA AudioMixerData = INVALIDAUDIOMIXERDATA;
socketdata.client_type = PHONE_CLIENT;
socketdata.method_type = PHONE_PLUGIN_CTL_WRITE;
int sockfd = make_client_mainsock();
if (send_socket(sockfd, &socketdata, sizeof(socketdata)) == sizeof(socketdata))
{
AudioMixerData.volume_phone = *value;
if (send_socket(sockfd, &AudioMixerData,
sizeof(AudioMixerData)) == sizeof(AudioMixerData))
{
result = 1;
}
else
{
LOGE("Unable to send data to audio server\n");
}
}
else
{
LOGE("Unable to set new value to audio server\n");
}
close_socket(sockfd);
}
}
else if (key > 1 && key < MIXER_OLD_GENERIC_NUM + MIXER_NEW_ANDROID_NUM)
{
dbg("key is %d, method is %s, value is 0x%x\n", key, mixer_dev[key], *value);
send_ipc_data(gItemCtlInfo[key].client, gItemCtlInfo[key].methodType,
gItemCtlInfo[key].methodId, gItemCtlInfo[key].device, *value);
}
//For UI calibration test
else if (key == MIXER_OLD_GENERIC_NUM + MIXER_NEW_ANDROID_NUM)//UI calibration send settings
{
SOCKETDATA socketdata = INVALIDSOCKETDATA;
socketdata.client_type = CALIBRATION_CLIENT;
socketdata.method_type = PLUGIN_CALIBRATE_MSA;
int sockfd = make_client_mainsock();
if (send_socket(sockfd, &socketdata, sizeof(socketdata)) == sizeof(socketdata))
{
//FIXME: we need wait audio serve to listen the port
while ((trans_sockfd = make_client_transock(TRANSOCKPORT4CALIBRATE)) <= 0)
{
LOGE("make_client_transock failed, try again\n");
usleep(10000);
}
//send MSA setting
if (*value == 0)
sendFile = fopen("/data/SendSetting.raw", "rb");
//get MSA setting
if (*value == 1)
sendFile = fopen("/data/GetSetting.raw", "rb");
if (sendFile)
{
if (fread(dataBuf, 1, MSASETTINGDATALEN, sendFile) != MSASETTINGDATALEN)
{
LOGE("UI calibration read raw file failed!\n");
return 0;
}
rsend = send_socket(trans_sockfd, dataBuf, MSASETTINGDATALEN);
if (rsend > 0)
{
LOGI("UI calibration sent out settings\n");
if (*value == 1)
{
LOGI("UI calibration get reports, trans_sockfd %d\n", trans_sockfd);
dumpFile = fopen("/data/StatusReport.raw", "wb");
//FIXME: Suppose we will get the status report from CP side in a short time, see audio server code
while (1)
{
//FIXME: here we simply always receive reports...
if (recv_socket(trans_sockfd, dataBuf, MSASETTINGDATALEN) != MSASETTINGDATALEN)
{
LOGI("UI calibration get reports: calibration thread in audio server may exit\n");
break;
}
else
{
LOGI("$$$$$$$$$$ dump UI calibration status report $$$$$$$$$$\n");
for (i = 0; i < MSASETTINGDATALEN; i++)
{
if (i % 8 == 0)
{
LOGI("\n");
}
LOGI("0x%2x ", dataBuf[i]);
}
LOGI("\n");
fwrite(dataBuf, 1, MSASETTINGDATALEN, dumpFile);
}
}
fclose(dumpFile);
}
}
else
{
LOGE("UI calibration send settings failed!\n");
close_socket(trans_sockfd);
}
fclose(sendFile);
}
else
{
LOGE("UI calibration open raw file failed!\n");
return 0;
}
}
else
{
LOGE("Unable to set new value to audio server\n");
}
close_socket(sockfd);
close_socket(trans_sockfd);
}
return result;
}
static int phone_get_enumerated_info(snd_ctl_ext_t *ext,
snd_ctl_ext_key_t key,
unsigned int *items)
{
snd_ctl_phone_t *ctl = ext->private_data;
switch (key)
{
case 0:
*items = ctl->num_path_items;
case 1:
break;
default:
return -EINVAL;
}
return 0;
}
static int phone_get_enumerated_name(snd_ctl_ext_t *ext,
snd_ctl_ext_key_t key,
unsigned int item, char *name,
size_t name_max_len)
{
snd_ctl_phone_t *ctl = ext->private_data;
switch (key)
{
case 0:
if ((int)item >= ctl->num_path_items)
return -EINVAL;
strncpy(name, voicepath_items[item], name_max_len - 1);
break;
case 1:
break;
default:
return -EINVAL;
}
name[name_max_len - 1] = 0;
return 0;
}
static int phone_read_enumerated(snd_ctl_ext_t *ext,
snd_ctl_ext_key_t key,
unsigned int *items)
{
SOCKETDATA socketdata = INVALIDSOCKETDATA;
AUDIOMIXERDATA AudioMixerData = INVALIDAUDIOMIXERDATA;
ext = ext;
key = key;
*items = 0;
socketdata.client_type = PHONE_CLIENT;
socketdata.method_type = PHONE_PLUGIN_CTL_READ;
int sockfd = make_client_mainsock();
if (send_socket(sockfd, &socketdata, sizeof(socketdata)) == sizeof(socketdata))
{
if (recv_socket(sockfd, &AudioMixerData, sizeof(AudioMixerData)) == sizeof(AudioMixerData))
{
*items = AudioMixerData.status_phone;
}
}
else
{
LOGE("Unable to receive data from audio server \n");
}
close_socket(sockfd);
return 0;
}
static int phone_write_enumerated(snd_ctl_ext_t *ext,
snd_ctl_ext_key_t key,
unsigned int *items)
{
int result = 0;
unsigned int curitem;
phone_read_enumerated( ext, key, &curitem);
if (items && (*items != curitem))
{
SOCKETDATA socketdata = INVALIDSOCKETDATA;
AUDIOMIXERDATA AudioMixerData = INVALIDAUDIOMIXERDATA;
socketdata.client_type = PHONE_CLIENT;
socketdata.method_type = PHONE_PLUGIN_CTL_WRITE;
int sockfd = make_client_mainsock();
if (send_socket(sockfd, &socketdata, sizeof(socketdata)) == sizeof(socketdata))
{
AudioMixerData.status_phone = *items;
if (send_socket(sockfd, &AudioMixerData,
sizeof(AudioMixerData)) == sizeof(AudioMixerData))
{
result = 1;
}
else
{
LOGE("Unable to send data to audio server\n");
}
}
else
{
LOGE("Unable to set new value to audio server\n");
}
close_socket(sockfd);
}
return result;
}
static void phone_subscribe_events(snd_ctl_ext_t *ext, int subscribe)
{
ext = ext;
subscribe = subscribe;
}
static int phone_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id,
unsigned int *event_mask)
{
ext = ext;
id = id;
event_mask = event_mask;
return 0;
}
static int phone_ctl_poll_descriptors_count(snd_ctl_ext_t *ext)
{
ext = ext;
return 0;
}
static int phone_ctl_poll_descriptors(snd_ctl_ext_t *ext, struct pollfd *pfd, unsigned int space)
{
ext = ext;
pfd = pfd;
space = space;
return 0;
}
static int phone_ctl_poll_revents(snd_ctl_ext_t *ext, struct pollfd *pfd, unsigned int nfds, unsigned short *revents)
{
ext = ext;
pfd = pfd;
nfds = nfds;
revents = revents;
return 0;
}
static void phone_close(snd_ctl_ext_t *ext)
{
snd_ctl_phone_t *ctl = ext->private_data;
close_socket(ext->poll_fd);
free(ctl);
}
static snd_ctl_ext_callback_t phone_ext_callback = {
.elem_count = phone_elem_count,
.elem_list = phone_elem_list,
.find_elem = phone_find_elem,
.get_attribute = phone_get_attribute,
.get_integer_info = phone_get_integer_info,
.read_integer = phone_read_integer,
.write_integer = phone_write_integer,
.get_enumerated_info = phone_get_enumerated_info,
.get_enumerated_name = phone_get_enumerated_name,
.read_enumerated = phone_read_enumerated,
.write_enumerated = phone_write_enumerated,
.subscribe_events = phone_subscribe_events,
.read_event = phone_read_event,
.poll_descriptors_count = phone_ctl_poll_descriptors_count,
.poll_descriptors = phone_ctl_poll_descriptors,
.poll_revents = phone_ctl_poll_revents,
.close = phone_close,
};
SND_CTL_PLUGIN_DEFINE_FUNC(phone)
{
snd_config_iterator_t i, next;
int err;
snd_ctl_phone_t *ctl;
root = root;
snd_config_for_each(i, next, conf)
{
snd_config_t *n = snd_config_iterator_entry(i);
const char *id;
if (snd_config_get_id(n, &id) < 0)
continue;
if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
continue;
}
ctl = calloc(1, sizeof(*ctl));
ctl->ext.version = SND_CTL_EXT_VERSION;
ctl->ext.card_idx = 0;
strncpy(ctl->ext.id, "ALSA-PHONE-CTL", sizeof(ctl->ext.id) - 1);
strncpy(ctl->ext.driver, "alsa phone ctl plugin", sizeof(ctl->ext.driver) - 1);
strncpy(ctl->ext.name, "alsa phone ctl plugin", sizeof(ctl->ext.name) - 1);
strncpy(ctl->ext.longname, "alsa phone ctl plugin for tavor", sizeof(ctl->ext.longname) - 1);
strncpy(ctl->ext.mixername, "alsa phone plugin mixer for tavor", sizeof(ctl->ext.mixername) - 1);
ctl->ext.poll_fd = make_udp_mainsock();
ctl->ext.callback = &phone_ext_callback;
ctl->ext.private_data = ctl;
ctl->num_path_items = 11;
err = snd_ctl_ext_create(&ctl->ext, name, mode);
if (err < 0)
goto error;
*handlep = ctl->ext.handle;
return 0;
error:
if (ctl->ext.poll_fd != -1) close_socket(ctl->ext.poll_fd);
if (ctl != NULL)
free(ctl);
return err;
}
SND_CTL_PLUGIN_SYMBOL(phone);
5.1.5 Socket编程简介
见我自己写的例子。
5.1.6 Amixer 中的cset设置研究
使能耳机的loop测试:
levante_headset_local_loop_enable()
{
#(0x1030064)
amixer -Dphone cset numid=26, 16973924
}
我们先看看Amixer的main函数:
int main(int argc, char *argv[])
{
int morehelp, level = 0;
struct option long_option[] =
{
{"help", 0, NULL, HELPID_HELP},
{"card", 1, NULL, HELPID_CARD},
{"device", 1, NULL, HELPID_DEVICE},
{"quiet", 0, NULL, HELPID_QUIET},
{"inactive", 0, NULL, HELPID_INACTIVE},
{"debug", 0, NULL, HELPID_DEBUG},
{"nocheck", 0, NULL, HELPID_NOCHECK},
{"version", 0, NULL, HELPID_VERSION},
{NULL, 0, NULL, 0},
};
morehelp = 0;
while (1) {
int c;
if ((c = getopt_long(argc, argv, "hc:D:qidnv", long_option, NULL)) < 0)
break;
switch (c) {
case 'h':
case HELPID_HELP:
help();
return 0;
case 'c':
case HELPID_CARD:
{
int i;
i = snd_card_get_index(optarg);
if (i >= 0 && i < 32)
sprintf(card, "hw:%i", i);
else {
fprintf(stderr, "\07Invalid card number.\n");
morehelp++;
}
}
break;
case 'D':
case HELPID_DEVICE:
strncpy(card, optarg, sizeof(card)-1);
optarg指向的是-Dphone中的phone参数
card[sizeof(card)-1] = '\0';
break;
case 'q':
case HELPID_QUIET:
quiet = 1;
break;
case 'i':
case HELPID_INACTIVE:
level |= LEVEL_INACTIVE;
break;
case 'd':
case HELPID_DEBUG:
debugflag = 1;
break;
case 'n':
case HELPID_NOCHECK:
no_check = 1;
break;
case 'v':
case HELPID_VERSION:
printf("amixer version" "mhn test\n"); // SND_UTIL_VERSION_STR "\n");
return 1;
default:
fprintf(stderr, "\07Invalid switch or option needs an argument.\n");
morehelp++;
}
}
if (morehelp) {
help();
return 1;
}
if (argc - optind <= 0) {
return selems(LEVEL_BASIC | level) ? 1 : 0;
}
if (!strcmp(argv[optind], "help")) {
return help() ? 1 : 0;
} else if (!strcmp(argv[optind], "info")) {
return info() ? 1 : 0;
} else if (!strcmp(argv[optind], "controls")) {
return controls(level) ? 1 : 0;
} else if (!strcmp(argv[optind], "contents")) {
return controls(LEVEL_BASIC | level) ? 1 : 0;
} else if (!strcmp(argv[optind], "scontrols") || !strcmp(argv[optind], "simple")) {
return selems(level) ? 1 : 0;
} else if (!strcmp(argv[optind], "scontents")) {
return selems(LEVEL_BASIC | level) ? 1 : 0;
} else if (!strcmp(argv[optind], "sset") || !strcmp(argv[optind], "set")) {
return sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0) ? 1 : 0;
} else if (!strcmp(argv[optind], "sget") || !strcmp(argv[optind], "get")) {
return sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1) ? 1 : 0;
} else if (!strcmp(argv[optind], "cset")) {
return cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0) ? 1 : 0;
我们在amixer -Dphone cset numid=26, 16973924中有设置cset,所以在上面的argv[optind]中的optind = 2,所以正好argv[optind] == cset,此时argc - optind – 1 == 4-2-1==1且 4-2 》 1,所以argv + optind + 1 = argv + 3指向“numid=26, 16973924”所以:
return cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0) ? 1 : 0;相当于:
return cset(1,“numid=26, 16973924”, 0) ? 1 : 0;
} else if (!strcmp(argv[optind], "cget")) {
return cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1) ? 1 : 0;
} else if (!strcmp(argv[optind], "events")) {
return events(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL);
} else if (!strcmp(argv[optind], "sevents")) {
return sevents(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL);
} else {
fprintf(stderr, "amixer: Unknown command '%s'...\n", argv[optind]);
}
return 0;
}
static int cset(int argc, char *argv[], int roflag)
{
int err;
snd_ctl_t *handle;
struct _snd_ctl {
void *dl_handle;
char *name;
snd_ctl_type_t type;
snd_ctl_ops_t *ops;
void *private_data;
int nonblock;
int poll_fd;
struct list_head async_handlers;
};
snd_ctl_elem_info_t *info;
struct sndrv_ctl_elem_info {
struct sndrv_ctl_elem_id id; /* W: element ID */
int type; /* R: value type - SNDRV_CTL_ELEM_TYPE_* */
unsigned int access; /* R: value access (bitmask) - SNDRV_CTL_ELEM_ACCESS_* */
unsigned int count; /* count of values */
pid_t owner; /* owner's PID of this control */
union {
struct {
long min; /* R: minimum value */
long max; /* R: maximum value */
long step; /* R: step (0 variable) */
} integer;
struct {
long long min; /* R: minimum value */
long long max; /* R: maximum value */
long long step; /* R: step (0 variable) */
} integer64;
struct {
unsigned int items; /* R: number of items */
unsigned int item; /* W: item number */
char name[64]; /* R: value name */
} enumerated;
unsigned char reserved[128];
} value;
union {
unsigned short d[4]; /* dimensions */
unsigned short *d_ptr; /* indirect */
} dimen;
unsigned char reserved[64-4*sizeof(unsigned short)];
};
snd_ctl_elem_id_t *id;
struct sndrv_ctl_elem_id {
unsigned int numid; /* numeric identifier, zero = invalid */
int iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
unsigned char name[44]; /* ASCII name of item */
unsigned int index; /* index of item */
};
snd_ctl_elem_value_t *control;
struct sndrv_ctl_elem_value {
struct sndrv_ctl_elem_id id; /* W: element ID */
unsigned int indirect: 1; /* W: use indirect pointer (xxx_ptr member) */
union {
union {
long value[128];
long *value_ptr;
} integer;
union {
long long value[64];
long long *value_ptr;
} integer64;
union {
unsigned int item[128];
unsigned int *item_ptr;
} enumerated;
union {
unsigned char data[512];
unsigned char *data_ptr;
} bytes;
struct sndrv_aes_iec958 iec958;
} value; /* RO */
struct timespec tstamp;
unsigned char reserved[128-sizeof(struct timespec)];
};
char *ptr;
unsigned int idx, count;
long tmp;
snd_ctl_elem_type_t type;
typedef enum _snd_ctl_elem_type {
/** Invalid type */
SND_CTL_ELEM_TYPE_NONE = 0,
/** Boolean contents */
SND_CTL_ELEM_TYPE_BOOLEAN,
/** Integer contents */
SND_CTL_ELEM_TYPE_INTEGER,
/** Enumerated contents */
SND_CTL_ELEM_TYPE_ENUMERATED,
/** Bytes contents */
SND_CTL_ELEM_TYPE_BYTES,
/** IEC958 (S/PDIF) setting content */
SND_CTL_ELEM_TYPE_IEC958,
/** 64-bit integer contents */
SND_CTL_ELEM_TYPE_INTEGER64,
SND_CTL_ELEM_TYPE_LAST = SND_CTL_ELEM_TYPE_INTEGER64
} snd_ctl_elem_type_t;
snd_ctl_elem_info_alloca(&info);
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_value_alloca(&control);
if (argc < 1) {
fprintf(stderr, "Specify a full control identifier: [[iface=<iface>,][name='name',][index=<index>,][device=<device>,][subdevice=<subdevice>]]|[numid=<numid>]\n");
return -EINVAL;
}
if (parse_control_id(argv[0], id)) {
fprintf(stderr, "Wrong control identifier: %s\n", argv[0]);
return -EINVAL;
}
if (debugflag) {
printf("VERIFY ID: ");
show_control_id(id);
printf("\n");
}
if ((err = snd_ctl_open(&handle, card, 0)) < 0) {
error("Control %s open error: %s\n", card, snd_strerror(err));
return err;
}
snd_ctl_elem_info_set_id(info, id);
if ((err = snd_ctl_elem_info(handle, info)) < 0) {
error("Control %s cinfo error: %s\n", card, snd_strerror(err));
return err;
}
snd_ctl_elem_info_get_id(info, id); /* FIXME: Remove it when hctl find works ok !!! */
type = snd_ctl_elem_info_get_type(info);
count = snd_ctl_elem_info_get_count(info);
snd_ctl_elem_value_set_id(control, id);
if (!roflag) {
ptr = argv[1];
for (idx = 0; idx < count && idx < 128 && ptr && *ptr; idx++) {
switch (type) {
case SND_CTL_ELEM_TYPE_BOOLEAN:
tmp = 0;
if (!strncasecmp(ptr, "on", 2) || !strncasecmp(ptr, "up", 2)) {
tmp = 1;
ptr += 2;
} else if (!strncasecmp(ptr, "yes", 3)) {
tmp = 1;
ptr += 3;
} else if (!strncasecmp(ptr, "toggle", 6)) {
tmp = snd_ctl_elem_value_get_boolean(control, idx);
tmp = tmp > 0 ? 0 : 1;
ptr += 6;
} else if (isdigit(*ptr)) {
tmp = atoi(ptr) > 0 ? 1 : 0;
while (isdigit(*ptr))
ptr++;
} else {
while (*ptr && *ptr != ',')
ptr++;
}
snd_ctl_elem_value_set_boolean(control, idx, tmp);
break;
case SND_CTL_ELEM_TYPE_INTEGER:
tmp = get_integer(&ptr,
snd_ctl_elem_info_get_min(info),
snd_ctl_elem_info_get_max(info));
snd_ctl_elem_value_set_integer(control, idx, tmp);
break;
case SND_CTL_ELEM_TYPE_INTEGER64:
tmp = get_integer64(&ptr,
snd_ctl_elem_info_get_min64(info),
snd_ctl_elem_info_get_max64(info));
snd_ctl_elem_value_set_integer64(control, idx, tmp);
break;
case SND_CTL_ELEM_TYPE_ENUMERATED:
tmp = get_integer(&ptr, 0, snd_ctl_elem_info_get_items(info) - 1);
snd_ctl_elem_value_set_enumerated(control, idx, tmp);
break;
case SND_CTL_ELEM_TYPE_BYTES:
tmp = get_integer(&ptr, 0, 255);
snd_ctl_elem_value_set_byte(control, idx, tmp);
break;
default:
break;
}
if (!strchr(argv[1], ','))
ptr = argv[1];
else if (*ptr == ',')
ptr++;
}
if ((err = snd_ctl_elem_write(handle, control)) < 0) {
error("Control %s element write error: %s\n", card, snd_strerror(err));
return err;
}
}
snd_ctl_close(handle);
if (!quiet) {
snd_hctl_t *hctl;
snd_hctl_elem_t *elem;
if ((err = snd_hctl_open(&hctl, card, 0)) < 0) {
error("Control %s open error: %s\n", card, snd_strerror(err));
return err;
}
if ((err = snd_hctl_load(hctl)) < 0) {
error("Control %s load error: %s\n", card, snd_strerror(err));
return err;
}
elem = snd_hctl_find_elem(hctl, id);
if (elem)
show_control(" ", elem, LEVEL_BASIC | LEVEL_ID);
else if (debugflag)
printf("Could not find the specified element\n");
snd_hctl_close(hctl);
}
return 0;
}
5.1.7 NUC900 AUDIO ALSA复习
知识点:
(1)cat proc/asound/devices
sh-3.2# cat proc/asound/devices
0: [ 0] : control
16: [ 0- 0]: digital audio playback
24: [ 0- 0]: digital audio capture
33: : timer
根据此建立文件节点:
mknod controlC0 c 116 0
mknod pcmC0D0c c 116 24
mknod pcmC0D0p c 116 16
mknod timer c 116 33
(2)移植alsa-lib-1.0.23:
./configure --host=arm-linux --prefix=/home/zswan/workdir/NUC900BSP/alsa --enable-static --disable-shared --disable-python --with-configdir=/etc/alsa-nuc900
(3)移植alsa-utils-1.0.23:
./configure --host=arm-linux --enable-static --disable-shared --with-configdir=/etc/alsa-nuc900 CFLAGS="-I/home/zswan/workdir/NUC900BSP/alsa/include" LDFLAGS="-L/home/zswan/workdir/NUC900BSP/alsa/lib -lasound " --disable-alsamixer --disable-xmlto --datarootdir=/etc/alsa-nuc900
(4)测试研究
Rootfs中的etc\alsa-nuc900里面应该有:
alsa.conf
pcm/default.conf
cards/aliases.conf
etc/init.rd/rcS中设置:
/bin/amixer set Master 90% unmute
/bin/amixer set PCM 85% unmute
/bin/amixer set Headphone 90% unmute
/bin/amixer set Capture 90% unmute
/bin/aplay /usr/1.wav
开机后就能听到windows开机的声音。
如果想aplay声音文件,那么蓝色部分是必须要设置的,nuc950的开发板中只有headphone一路,和mic一路。所以从硬件电路图中可以看出:
Alc203 ac97 codec的HP-OUTL 和HP-OUTR IO连接的是耳机,headphone那路,所以我们需要check alc203的headphone是由哪个控制器来控制的。
Alc203的寄存器功能列表:
回到开发板端:
sh-3.2# amixer contents
numid=5,iface=MIXER,name='Master Mono Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=off
numid=6,iface=MIXER,name='Master Mono Playback Volume'
; type=INTEGER,access=rw---R--,values=1,min=0,max=31,step=0
: values=0
| dBscale-min=-46.50dB,step=1.50dB,mute=0
numid=1,iface=MIXER,name='Master Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=on
numid=2,iface=MIXER,name='Master Playback Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=63,step=0
: values=0,0
| dBscale-min=-94.50dB,step=1.50dB,mute=0
numid=3,iface=MIXER,name='Headphone Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=on
numid=4,iface=MIXER,name='Headphone Playback Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=63,step=0
: values=63,63
| dBscale-min=-94.50dB,step=1.50dB,mute=0
numid=26,iface=MIXER,name='3D Control - Switch'
; type=BOOLEAN,access=rw------,values=1
: values=off
numid=25,iface=MIXER,name='PCM Out Path & Mute'
; type=ENUMERATED,access=rw------,values=1,items=2
; Item #0 'pre 3D'
; Item #1 'post 3D'
: values=0
numid=20,iface=MIXER,name='PCM Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=on
numid=21,iface=MIXER,name='PCM Playback Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=31,step=0
: values=0,0
| dBscale-min=-34.50dB,step=1.50dB,mute=0
numid=14,iface=MIXER,name='Line Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=off
numid=15,iface=MIXER,name='Line Playback Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=31,step=0
: values=0,0
| dBscale-min=-34.50dB,step=1.50dB,mute=0
numid=16,iface=MIXER,name='CD Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=off
numid=17,iface=MIXER,name='CD Playback Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=31,step=0
: values=0,0
| dBscale-min=-34.50dB,step=1.50dB,mute=0
numid=13,iface=MIXER,name='Mic Boost (+20dB)'
; type=BOOLEAN,access=rw------,values=1
: values=off
numid=27,iface=MIXER,name='Mic Select'
; type=ENUMERATED,access=rw------,values=1,items=2
; Item #0 'Mic1'
; Item #1 'Mic2'
: values=0
numid=11,iface=MIXER,name='Mic Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=off
numid=12,iface=MIXER,name='Mic Playback Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=31,step=0
: values=16,0
| dBscale-min=-34.50dB,step=1.50dB,mute=0
numid=9,iface=MIXER,name='Phone Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=off
numid=10,iface=MIXER,name='Phone Playback Volume'
; type=INTEGER,access=rw---R--,values=1,min=0,max=31,step=0
: values=0
| dBscale-min=-34.50dB,step=1.50dB,mute=0
numid=18,iface=MIXER,name='Aux Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=off
numid=19,iface=MIXER,name='Aux Playback Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=31,step=0
: values=0,0
| dBscale-min=-34.50dB,step=1.50dB,mute=0
numid=22,iface=MIXER,name='Capture Source'
; type=ENUMERATED,access=rw------,values=2,items=8
; Item #0 'Mic'
; Item #1 'CD'
; Item #2 'Video'
; Item #3 'Aux'
; Item #4 'Line'
; Item #5 'Mix'
; Item #6 'Mix Mono'
; Item #7 'Phone'
: values=0,0
numid=23,iface=MIXER,name='Capture Switch'
; type=BOOLEAN,access=rw------,values=1
: values=on
numid=24,iface=MIXER,name='Capture Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=15,step=0
: values=14,14
| dBscale-min=0.00dB,step=1.50dB,mute=0
numid=28,iface=MIXER,name='IEC958 Playback Con Mask'
; type=IEC958,access=r-------,values=1
: values=[AES0=0x0f AES1=0xff AES2=0x00 AES3=0x0f]
numid=29,iface=MIXER,name='IEC958 Playback Pro Mask'
; type=IEC958,access=r-------,values=1
: values=[AES0=0xcf AES1=0x00 AES2=0x00 AES3=0x00]
numid=32,iface=MIXER,name='IEC958 Playback AC97-SPSA'
; type=INTEGER,access=rw------,values=1,min=0,max=3,step=0
: values=1
numid=30,iface=MIXER,name='IEC958 Playback Default'
; type=IEC958,access=rw------,values=1
: values=[AES0=0x00 AES1=0x82 AES2=0x00 AES3=0x02]
numid=31,iface=MIXER,name='IEC958 Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=off
numid=7,iface=MIXER,name='Beep Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=off
numid=8,iface=MIXER,name='Beep Playback Volume'
; type=INTEGER,access=rw---R--,values=1,min=0,max=15,step=0
: values=0
| dBscale-min=-45.00dB,step=3.00dB,mute=0
numid=33,iface=MIXER,name='External Amplifier'
; type=BOOLEAN,access=rw------,values=1
: values=on
sh-3.2#
sh-3.2#
sh-3.2#
sh-3.2# amixer
Simple mixer control 'Master',0
Capabilities: pvolume pswitch pswitch-joined penum
Playback channels: Front Left - Front Right
Limits: Playback 0 - 63
Mono:
Front Left: Playback 0 [0%] [-94.50dB] [on]
Front Right: Playback 0 [0%] [-94.50dB] [on]
Simple mixer control 'Master Mono',0
Capabilities: pvolume pvolume-joined pswitch pswitch-joined penum
Playback channels: Mono
Limits: Playback 0 - 31
Mono: Playback 0 [0%] [-46.50dB] [off]
Simple mixer control 'Headphone',0
Capabilities: pvolume pswitch pswitch-joined penum
Playback channels: Front Left - Front Right
Limits: Playback 0 - 63
Mono:
Front Left: Playback 63 [100%] [0.00dB] [on]
Front Right: Playback 63 [100%] [0.00dB] [on]
Simple mixer control '3D Control - Switch',0
Capabilities: pswitch pswitch-joined penum
Playback channels: Mono
Mono: Playback [off]
Simple mixer control 'PCM',0
Capabilities: pvolume pswitch pswitch-joined penum
Playback channels: Front Left - Front Right
Limits: Playback 0 - 31
Mono:
Front Left: Playback 0 [0%] [-34.50dB] [on]
Front Right: Playback 0 [0%] [-34.50dB] [on]
Simple mixer control 'PCM Out Path & Mute',0
Capabilities: enum
Items: 'pre 3D' 'post 3D'
Item0: 'pre 3D'
Simple mixer control 'Line',0
Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive penum
Capture exclusive group: 0
Playback channels: Front Left - Front Right
Capture channels: Front Left - Front Right
Limits: Playback 0 - 31
Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
Simple mixer control 'CD',0
Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive penum
Capture exclusive group: 0
Playback channels: Front Left - Front Right
Capture channels: Front Left - Front Right
Limits: Playback 0 - 31
Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
Simple mixer control 'Mic',0
Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive penum
Capture exclusive group: 0
Playback channels: Front Left - Front Right
Capture channels: Front Left - Front Right
Limits: Playback 0 - 31
Front Left: Playback 16 [52%] [-10.50dB] [off] Capture [on]
Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [on]
Simple mixer control 'Mic Boost (+20dB)',0
Capabilities: pswitch pswitch-joined penum
Playback channels: Mono
Mono: Playback [off]
Simple mixer control 'Mic Select',0
Capabilities: enum
Items: 'Mic1' 'Mic2'
Item0: 'Mic1'
Simple mixer control 'Video',0
Capabilities: cswitch cswitch-exclusive penum
Capture exclusive group: 0
Capture channels: Front Left - Front Right
Front Left: Capture [off]
Front Right: Capture [off]
Simple mixer control 'Phone',0
Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive penum
Capture exclusive group: 0
Playback channels: Mono
Capture channels: Front Left - Front Right
Limits: Playback 0 - 31
Mono: Playback 0 [0%] [-34.50dB] [off]
Front Left: Capture [off]
Front Right: Capture [off]
Simple mixer control 'IEC958',0
Capabilities: pswitch pswitch-joined penum
Playback channels: Mono
Mono: Playback [off]
Simple mixer control 'IEC958 Playback AC97-SPSA',0
Capabilities: volume volume-joined penum
Playback channels: Mono
Capture channels: Mono
Limits: 0 - 3
Mono: 1 [33%]
Simple mixer control 'Beep',0
Capabilities: pvolume pvolume-joined pswitch pswitch-joined penum
Playback channels: Mono
Limits: Playback 0 - 15
Mono: Playback 0 [0%] [-45.00dB] [off]
Simple mixer control 'Aux',0
Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive penum
Capture exclusive group: 0
Playback channels: Front Left - Front Right
Capture channels: Front Left - Front Right
Limits: Playback 0 - 31
Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off]
Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off]
Simple mixer control 'Capture',0
Capabilities: cvolume cswitch cswitch-joined penum
Capture channels: Front Left - Front Right
Limits: Capture 0 - 15
Front Left: Capture 14 [93%] [21.00dB] [on]
Front Right: Capture 14 [93%] [21.00dB] [on]
Simple mixer control 'Mix',0
Capabilities: cswitch cswitch-exclusive penum
Capture exclusive group: 0
Capture channels: Front Left - Front Right
Front Left: Capture [off]
Front Right: Capture [off]
Simple mixer control 'Mix Mono',0
Capabilities: cswitch cswitch-exclusive penum
Capture exclusive group: 0
Capture channels: Front Left - Front Right
Front Left: Capture [off]
Front Right: Capture [off]
Simple mixer control 'External Amplifier',0
Capabilities: pswitch pswitch-joined penum
Playback channels: Mono
Mono: Playback [on]
如果要关掉headphone,则使用命令:
sh-3.2# amixer cset numid=3, 0
numid=3,iface=MIXER,name='Headphone Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=off
打开则使用:
sh-3.2# amixer cset numid=3, 1
numid=3,iface=MIXER,name='Headphone Playback Switch'
; type=BOOLEAN,access=rw------,values=1
: values=on