音量基本概念

声学中的分贝

因为人耳的特性,我们对声音的大小感知呈对数关系。所以我们通常用分贝描述声音大小,分贝(decibel)是量度两个相同单位之数量比例的单位,主要用于度量声音强度,常用 dB 表示。声学中,声音的强度定义为声压。计算分贝值时采用 20 微帕斯卡为参考值(通常被认为是人类的最少听觉响应值,大约是 3 米以外飞行的蚊子声音)。这一参考值是人类对声音能够感知的 阈值 下限。声压是场量,因此使用声压计算分贝时使用下述版本的公式:

\[ L_p = 20log_{10}(\frac{p_{rms}}{p_{ref}})dB \]

其中的 pref 是标准参考声压值 20 微帕。

分贝声音变化范围

在编程中,我们可以用以下公式计算两个声音之间的动态范围,单位为分贝:

\[ dB = 20 * log(A1 / A2) \]

其中 A1 和 A2 是两个声音的振幅,在程序中表示每个声音样本的大小。声音采样大小(也就是量化深度)为 1bit 时,动态范围为 0,因为只可能有一个振幅。采样大小为 8bit 也就是一个字节时,最大振幅是最小振幅的 256 倍。因此,动态范围是 48 分贝,计算公式如下:

\[ dB = 20 * log(256) \]

48 分贝的动态范围大约是一个安静房间和一台运行着电动割草机之间的区别。如果将声音采样大小增加一倍到 16bit,产生的动态范围则为 96 分贝,计算公式如下:

\[ dB = 20 * log(65536) \]

这非常接近听力最低阈值和产生痛感之间的区别,这个范围被认为非常适合还原音乐。

音量滑块与声音增幅大小线性变化

上述左图中,音量滑块位置与声音振幅为线性增长关系,右图是我们人耳感受的音量大小与滑块位置关系。可知,在左侧移动相同距离的滑块,感知到的声音变化范围很大,在右侧接近声音最大值移动相同距离滑块,感知到的声音大小变化就很小了。

Windows 音量滑块与声音 DB 转换关系

可参考如下链接:

默认的音频音量设置 - Windows drivers

Linux 中默认的配置是-100db 到 0db,而 windows 中的音量范围对应的是-96db-0db(Device 收到的 volume 将是-96db-0db 而不会收到-100db),而用户可调的滑块是从 0-100 的范围值。实测 UAC 中调节滑块发送给 Device 的音量值如下(单位是分贝):

Feature 功能实现

红色框中就是我们要实现的内容,UAC Spec 里说明了通过描述符来表示一个 Function Tologry 关系,只要利用描述符将这个关系描述清楚,Host 端就知道 Device 支持 Feature Unit,那么 Host 在调节音量的时候就会通过 Request 接口请求获得设备的 volume 和 mute 以及通过 Setup 接口来设置 Volume 和 Mute。这个关系的描述如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifdef CONFIG_AMLOGIC_MODIFY
#ifdef CONFIG_USB_CONFIGFS_UAC_FEATURE_UNIT
if (FUIN_EN(opts)) {
usb_in_ot_desc.bSourceID = in_feature_unit_desc->bUnitID;
in_feature_unit_desc->bSourceID = io_in_it_desc.bTerminalID;
} else {
usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
}
#else
usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
#endif
...
#ifdef CONFIG_AMLOGIC_MODIFY
#ifdef CONFIG_USB_CONFIGFS_UAC_FEATURE_UNIT
if (FUOUT_EN(opts)) {
io_out_ot_desc.bSourceID = out_feature_unit_desc->bUnitID;
out_feature_unit_desc->bSourceID = usb_out_it_desc.bTerminalID;
} else {
io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;
}
#else
io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;
#endif

简单描述一下就是将 feature unit 描述符插入 Input Terminal(IT)和 Output Terminal(OT)之间,如下:

1
2
3
4
5
6
										as_in_hdr_desc
|
io_in_it_desc -> in_feature_unit_desc -> usb_in_ot_desc
usb_out_it_desc -> out_feature_unit_desc -> io_out_ot_desc
|
as_out_hdr_desc

然后只需要在 usb function 里的 setup 回调里识别 bRequestType 和 bRequest 来判断是设置采样率还是设置 mute 或者是设置 volume 来做相应的处理(存储 volume 值和通过 snd_ctl_notify 通知 alsa 的 kcontrol volume 以及发生变化)就可以了。

同时如果 device 要主动通知 Host 端 Attributes 发生变化,需要实现一个 interrupt endpoint,然后通过这个中断端点通知 Host 变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
w_index = unit_id << 8 | uac2->ac_intf;
w_value = cs << 8;

msg->bInfo = 0; /* Non-vendor, interface interrupt */
msg->bAttribute = UAC2_CS_CUR;
msg->wIndex = cpu_to_le16(w_index);
msg->wValue = cpu_to_le16(w_value);

req->length = sizeof(*msg);
req->buf = msg;
req->context = agdev;
req->complete = afunc_notify_complete;

ret = usb_ep_queue(uac2->int_ep, req, GFP_ATOMIC);

Feature Unit VS HID

Binding between Physical Buttons and Audio Controls

大多数包含音频功能的设备也会有一个或多个 frontpanel buttons,用于控制设备内某些方面的音频功能。最明显的例子是多媒体扬声器前面的音量控制按钮。由于音频功能可能包含许多相同类型的音频控件,因此需要将物理控件(按钮、旋钮、滑块、点动等)绑定到音频功能中的特定音频控件。 本规范提供了两种互斥的方法来提供这种绑定:

  • 物理按钮实现为 HID 控件
  • 物理按钮是音频控制的集成部分

禁止对同一个物理按钮实现这两种方法。但是,允许对某些 frontpanel buttons 使用第一种方法,对其余 frontpanel buttons 使用第二种方法。强烈建议不使用上述两种方法实现前面板按钮,即对主机软件不可见且仅具有局部效果的按钮。

Physical button is a HID Control

HID 是 USB 设备中的一种设备类型,他与 UAC 之间并没有直接的关联性,两者是完全独立的,只是 HID 设备包括鼠标、键盘、遥杆等具有通过 USB 控制 host 的声音的设备,可以通过实现这些设备的描述符实现对 host 的音量控制,这样就可以用来控制 UAC 的音量。

在这种情况下,物理按键与音频功能完全分离,并在设备的 HID 接口内实现。音频功能甚至不知道按钮的存在。按钮的任何状态变化都通过 HID 报告传送给主机软件。然后由主机软件来解释按钮状态的变化,并从那里获得对音频功能要采取的适当行动。因此,绑定责任完全属于应用程序或操作系统软件。尽管此方法提供了广泛的灵活性但它也给软件带来了提供正确绑定的负担。

Physical button is Integral Part of the Audio Control

而 Feature Unit 是 UAC 标准里定义的特性单元,功能更强大可以实现对每个逻辑通道提供音频控制,包括但不限于 mute 和 volume。

在这种情况下,物理按键直接与实际的音频控制交互。按钮状态更改不会报告给主机软件。相反,由按钮操作引起的音频控制的状态变化通过音频控制中断机制报告给主机软件。因此,物理按钮和 Audio 控件之间的绑定非常直接,完全由设备的设计决定。尽管不太灵活,但该方法提供了一种非常清晰和直接的方式来执行绑定。

测试验证

Host 调节音量

在 pc 上调节音量,Host 会通过 ep0 端点给 Device 发送音量值,其对应关系如上图所示,Device 端接收到消息后会存储该音量值然后通知用户层 volume change 这个消息,用户就可以知道 Host 修改了 uac 的音量,然后可以通过相关接口获取更改后的音量值。然后具体的行为完全由用户决定。例如这里以设备执行 arecord -Dhw:2,0 -r 48000 -c 2 -f S16_LE | aplay -Dhw:0,1 -c 2 -r 48000 -f S16_LE 为例,arecord 录制 uac 的音频然后通过管道 aplay 到 TDMB 设备。 那么我们就可以通过如下命令获取和设置音量:

1
2
3
4
# 获取 uac 声卡的音量
amixer -c 2 cget numid=4,iface=MIXER,name='PCM Capture Volume'
# 设置 tdmb 的音量
amixer -c 0 cset numid=13,iface=MIXER,name='5707_B Master Volume' xxx

这种方式非常灵活,且属性的变化以及对变化的操作完全由应用程序控制。

Device 主动调节音量

之前如果是通过按键调节音量那么是通过 HID 驱动程序通知 Host 去调节音量,现在有了 Feature Unit 之后便不再需要 HID 来同步 Device 和 Host 的音量了,可以直接通过设置 uac 声卡的音量然后 Feature Unit 会通过中断端点将 volume 报告给 Host。例如,我们还是通过 amixer 设置 UAC 声卡的音量:

1
amixer -c 2 cset numid=4,iface=MIXER,name='PCM Capture Volume' 100

那么会在 windows 上看到系统的音量条跟着发生变化。

参考文献

《USB2.0 协议规范》
《UAC2 协议规范》