从零写一个ALSA声卡驱动学习(5)

Control Interface:

控制接口被广泛用于许多来自用户空间访问的开关、滑块等控件。其中最重要的用途是混音器接口。换句话说,从 ALSA 0.9.x 开始,所有混音器相关的功能都是基于内核的控制接口 API 实现的。

ALSA 提供了一个定义良好的 AC97 控制模块。如果你的芯片仅支持 AC97 且不需要其他功能,那么你可以跳过本节内容。

控制接口 API 定义在 <sound/control.h> 中。如果你希望添加自定义控制项,请包含这个头文件。

Definition of Controls:

要创建一个新的控制项,你需要定义以下三个回调函数:info、get 和 put。然后,定义一个 struct snd_kcontrol_new 结构体记录,例如:

static struct snd_kcontrol_new my_control = {
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "PCM Playback Switch",
        .index = 0,
        .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
        .private_value = 0xffff,
        .info = my_control_info,
        .get = my_control_get,
        .put = my_control_put
};
  • iface 字段指定控制项的类型,即 SNDRV_CTL_ELEM_IFACE_XXX,通常为 MIXER。如果是全局控制项(不属于混音器逻辑部分),使用 CARD。如果控制项与声卡上的某个特定设备紧密关联,则使用 HWDEP、PCM、RAWMIDI、TIMER 或 SEQUENCER,并通过 device 和 subdevice 字段指定设备编号。

  • name 是控制项的名称标识字符串。从 ALSA 0.9.x 起,控制项名称非常重要,因为其作用往往通过名称进行分类。有一些预定义的标准控制名称,具体细节在“控制名称”子章节中有描述。

  • bindex 字段用于存储该控制项的索引号。如果存在多个同名但不同功能的控制项,可以通过索引号加以区分。常见于一张声卡上有多个编解码器(codec)的情况。如果索引为 0,可以省略该字段。

  • access 字段定义了该控制项的访问类型。在此处填写由 SNDRV_CTL_ELEM_ACCESS_XXX 位掩码组合而成的值。具体内容将在“访问标志”子章节中详细说明。

  • private_value 字段是一个任意的长整型数值,用于存储附加信息。在使用通用的 info、get 和 put 回调函数时,可以通过该字段传递参数。如果需要多个小数值,可以通过位运算组合;也可以将某个结构体的指针强制转换为 unsigned long 存入此字段。

  • tlv 字段可以用于提供该控制项的元数据;详见“元数据”子章节。

  • 最后的三个字段就是控制回调函数(info、get 和 put)。

Control Names

在定义控制项名称时,有一些标准格式。一个控制项的名称通常由三部分组成,格式为:“SOURCE DIRECTION FUNCTION”。

  • 第一部分 SOURCE(来源) 表示该控制项控制的来源,是一个字符串,例如 “Master”(主控)、“PCM”、“CD” 和 “Line”(线路输入)等。ALSA 中预定义了许多来源名称。

  • 第二部分 DIRECTION(方向) 表示控制的方向,可取以下几种字符串之一: “Playback”(播放)、“Capture”(录音)、“Bypass Playback”(旁路播放)和 “Bypass Capture”(旁路录音)。如果省略这一部分,则表示同时适用于播放和录音方向。

  • 第三部分 FUNCTION(功能) 表示控制的功能,常见的有:“Switch”(开关)、“Volume”(音量)和 “Route”(路由)。

因此,控制项名称示例如下:

“Master Capture Switch”(主通道录音开关) 或 “PCM Playback Volume”(PCM播放音量)。

也有一些例外情况:全局录音和播放控制项

  • 用于全局录音(输入)源、开关和音量的名称为:

“Capture Source”、
“Capture Switch”、
“Capture Volume”。
  • 用于全局输出增益(输出开关与音量)的名称为:

“Playback Switch”、
“Playback Volume”。

音调控制(Tone-controls):音调控制的开关和音量用类似 “Tone Control - XXX” 的格式,例如:

“Tone Control - Switch”、
“Tone Control - Bass”、
“Tone Control - Center”。

3D 控制:3D 控制的开关和音量用类似 “3D Control - XXX” 的格式,例如:

“3D Control - Switch”、
“3D Control - Center”、
“3D Control - Space”。

麦克风增益(Mic Boost): 麦克风增益开关命名为:

“Mic Boost” 或 “Mic Boost (6dB)”。

关于控制项命名的更详细信息,请参阅文档: Documentation/sound/designs/control-names.rst

Access Flags

access(访问)标志是一个位掩码(bitmask),用于指定该控制项的访问类型。默认的访问类型是 SNDRV_CTL_ELEM_ACCESS_READWRITE,表示该控制项允许读写操作。当访问标志被省略(即设为 0)时,系统会默认将其视为 READWRITE 访问。

  • 如果该控制项是只读的,应传入 SNDRV_CTL_ELEM_ACCESS_READ。在这种情况下,无需定义 put 回调函数。

  • 同样地,如果该控制项是只写的(虽然这种情况很少见),你可以使用 WRITE 标志,此时无需定义 get 回调函数。

  • 如果控制值会频繁变化(例如 VU 表电平显示),应设置 VOLATILE 标志,表示该控制值可能在没有发送“变更通知”的情况下发生改变。对于这类控制项,应用程序应持续轮询其状态。

  • 如果某个控制项的值可以更改,但当前不影响任何实际功能,那么使用 INACTIVE 标志可能更合适。例如,当没有打开 PCM 设备时,其相关控制项就可以设置为 INACTIVE。

  • 此外,还有 LOCK 和 OWNER 标志,用于限制写操作权限。

Control Callbacks

info callback

info 回调函数用于获取该控制项的详细信息。它必须向提供的 struct snd_ctl_elem_info 结构中填充相关的值。例如,若这是一个布尔类型(boolean)的控制项,且只有一个元素,可以这样设置:

static int snd_myctl_mono_info(struct snd_kcontrol *kcontrol,
                        struct snd_ctl_elem_info *uinfo)
{
        uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
        uinfo->count = 1;
        uinfo->value.integer.min = 0;
        uinfo->value.integer.max = 1;
        return 0;
}
  • type 字段指定了该控制项的类型。类型包括:BOOLEAN(布尔类型)、INTEGER(整数)、ENUMERATED(枚举)、BYTES(字节流)、IEC958 和 INTEGER64(64 位整数)。

  • count 字段表示该控制项中元素的数量。例如,一个立体声音量控制器的 count = 2。

  • value 字段是一个联合体(union),其存储的值取决于 type 的类型。对于 BOOLEAN 和 INTEGER 类型,存储方式是相同的。

而 ENUMERATED(枚举)类型则与其他类型略有不同。你需要为所选条目的索引设置相应的字符串:

static int snd_myctl_enum_info(struct snd_kcontrol *kcontrol,
                        struct snd_ctl_elem_info *uinfo)
{
        static char *texts[4] = {
                "First", "Second", "Third", "Fourth"
        };
        uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
        uinfo->count = 1;
        uinfo->value.enumerated.items = 4;
        if (uinfo->value.enumerated.item > 3)
                uinfo->value.enumerated.item = 3;
        strcpy(uinfo->value.enumerated.name,
               texts[uinfo->value.enumerated.item]);
        return 0;
}

上面的回调函数可以通过一个辅助函数 snd_ctl_enum_info() 来简化。最终的代码如下所示。(你也可以在第三个参数中传入 ARRAY_SIZE(texts) 而不是写死的 4,这取决于你的个人习惯。)

static int snd_myctl_enum_info(struct snd_kcontrol *kcontrol,
                        struct snd_ctl_elem_info *uinfo)
{
        static char *texts[4] = {
                "First", "Second", "Third", "Fourth"
        };
        return snd_ctl_enum_info(uinfo, 1, 4, texts);
}

一些常用的 info 回调函数已经为你提供,方便使用:snd_ctl_boolean_mono_info() 和 snd_ctl_boolean_stereo_info()。很显然,前者是用于单声道布尔类型控制项的 info 回调函数,就像上面提到的 snd_myctl_mono_info() 一样;而后者则用于立体声通道的布尔类型控制项。

get callback

这个回调函数用于读取控制项的当前值,以便将其返回给用户空间。例如:

static int snd_myctl_get(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
        struct mychip *chip = snd_kcontrol_chip(kcontrol);
        ucontrol->value.integer.value[0] = get_some_value(chip);
        return 0;
}

value 字段的内容取决于控制项的类型以及 info 回调函数的设置。例如,sb 驱动使用该字段来存储寄存器偏移量、位移量(bit-shift)以及位掩码(bit-mask)。private_value 字段的设置如下所示:

.private_value = reg | (shift << 16) | (mask << 24)

并且可以在如下回调函数中获取该值:

static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_value *ucontrol)
{
        int reg = kcontrol->private_value & 0xff;
        int shift = (kcontrol->private_value >> 16) & 0xff;
        int mask = (kcontrol->private_value >> 24) & 0xff;
        ....
}

在 get 回调函数中,如果该控件包含多个元素(即 count > 1),你必须填写所有元素。在上面的示例中,我们只填写了一个元素(value.integer.value[0]),这是因为假设 count = 1。

put callback

该回调函数用于写入来自用户空间的值。例如:

static int snd_myctl_put(struct snd_kcontrol *kcontrol,
                         struct snd_ctl_elem_value *ucontrol)
{
        struct mychip *chip = snd_kcontrol_chip(kcontrol);
        int changed = 0;
        if (chip->current_value !=
             ucontrol->value.integer.value[0]) {
                change_current_value(chip,
                            ucontrol->value.integer.value[0]);
                changed = 1;
        }
        return changed;
}

如下所示,如果值发生了变化,你必须返回 1;如果值没有变化,则返回 0。如果发生了严重错误,则像往常一样返回一个负的错误码。

与 get 回调一样,如果该控件包含多个元素,那么在 put 回调中也必须处理所有元素。

回调函数不是原子操作,以上这三个回调函数(info、get、put)都不是原子操作。

Control Constructor

当一切准备就绪后,最后我们就可以创建一个新的控件了。要创建一个控件,需要调用两个函数:snd_ctl_new1() 和 snd_ctl_add()。

最简单的方式如下所示:你可以像这样完成控件的创建:

err = snd_ctl_add(card, snd_ctl_new1(&my_control, chip));
if (err < 0)
        return err;

其中,my_control 是前面定义的 struct snd_kcontrol_new 对象,chip 是一个对象指针,它会被赋值给 kcontrol->private_data,以便在回调函数中使用。

snd_ctl_new1() 会分配一个新的 struct snd_kcontrol 实例,而 snd_ctl_add() 则将指定的控件组件添加到声卡(card)中。

Change Notification

如果你需要在中断处理程序中更改并更新一个控件,可以调用 snd_ctl_notify()。例如:

snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, id_pointer);

这个函数接受三个参数:声卡指针、事件掩码(event-mask)和用于通知的控件 ID 指针。事件掩码指定了通知的类型,例如在上述例子中,表示控件数值的改变需要被通知。ID 指针是指向 struct snd_ctl_elem_id 的指针,用于指定需要通知的控件。你可以在 es1938.c 或 es1968.c 中找到一些硬件音量中断的示例代码。

Metadata

为了提供关于混音器控件的分贝(dB)值信息,需要使用 <sound/tlv.h> 头文件中提供的 DECLARE_TLV_xxx 宏之一来定义一个包含该信息的变量。然后,将控件结构体中的 tlv.p 字段指向这个变量,并在 access 字段中加入 SNDRV_CTL_ELEM_ACCESS_TLV_READ 标志。示例如下所示:

static DECLARE_TLV_DB_SCALE(db_scale_my_control, -4050, 150, 0);

static struct snd_kcontrol_new my_control = {
        ...
        .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
                  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
        ...
        .tlv.p = db_scale_my_control,
};

DECLARE_TLV_DB_SCALE() 宏用于定义一个混音器控件的分贝(dB)信息,其中控件的每一步数值变化对应一个固定的 dB 变化量。

  • 第一个参数是要定义的变量名称;

  • 第二个参数是最小值,单位是 0.01 dB;

  • 第三个参数是每一步的步长,同样单位是 0.01 dB;

  • 如果最小值表示“静音”(mute),则将第四个参数设置为 1。

DECLARE_TLV_DB_LINEAR() 宏用于定义一个混音器控件的分贝信息,其中控件的值以线性方式影响输出音量。

  • 第一个参数是要定义的变量名称;

  • 第二个参数是最小值,单位为 0.01 dB;

  • 第三个参数是最大值,单位也为 0.01 dB;

  • 如果最小值表示静音,请将第二个参数设置为 TLV_DB_GAIN_MUTE。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值