#codec #tlv #alsa #decoder #encoder #linux-kernel

bin+lib alsa-ctl-tlv-codec

ALSA 控制接口中 TLV 风格数据的编解码器

2 个版本

0.1.1 2022 年 7 月 27 日
0.1.0 2022 年 7 月 19 日

解析实现 中排名 #1032

MIT 许可证

115KB
2.5K SLoC

该包旨在处理在 ALSA 控制接口中表示的 TLV (类型-长度-值) 风格数据。该包生成 TLV 风格数据的 u32 数组的编解码器以及用于表示内容的结构和枚举。

TLV 风格的数据用于多种目的。截至 Linux 内核 5.10,它包括 dB 表达式以及 ALSA PCM 子流的通道映射信息。这些定义在 Linux 内核源代码的 include/uapi/sound/tlv.h 中。

结构和枚举

Linux 内核有一系列宏来构建用于 TLV 数据的 u32 数组,而不是 C 语言中的结构定义。这方便将二进制数据嵌入到对象文件中,但对开发者和用户不够友好。该包提供了一些结构和枚举来表示 TLV 数据。结构和宏之间的关系如下:

  • DbScale
    • SNDRV_CTL_TLVT_DB_SCALE
  • DbInterval
    • SNDRV_CTL_TLVT_DB_LINEAR
    • SNDRV_CTL_TLVT_DB_MINMAX
    • SNDRV_CTL_TLVT_DB_MINMAX_MUTE
  • Chmap / ChmapMode / ChmapEntry / ChmapPos / ChmapGenericPos
    • SNDRV_CTL_TLVT_CHMAP_FIXED
    • SNDRV_CTL_TLVT_CHMAP_VAR
    • SNDRV_CTL_TLVT_CHMAP_PAIRED
  • DbRange / DbRangeEntry / DbRangeEntryData
    • SNDRV_CTL_TLVT_DB_RANGE
  • 容器
    • SNDRV_CTL_TLVT_CONTAINER

该包提供了 TlvItem 枚举,用于将上述结构的数据分派到 TLV。

用法

将以下行添加到您的 Cargo.toml 文件中

[dependencies]
alsa-ctl-tlv-codec = "0.1"

TlvItem 枚举是使用该包的不错起点。

use alsa_ctl_tlv_codec::TlvItem;
use std::convert::TryFrom;

// Prepare raw data of TLV as array of u32 elements.
let raw = [2 as u32, 8, -100i32 as u32, 0]; // This is for SNDRV_CTL_TLVT_DB_LINEAR.

match TlvItem::try_from(&raw[..]) {
    Ok(data) => {
        let raw_generated: Vec<u32> = match &data {
          TlvItem::Container(d) => d.into(),
          TlvItem::DbRange(d) => d.into(),
          TlvItem::DbScale(d) => d.into(),
          TlvItem::DbInterval(d) => d.into(),
          TlvItem::Chmap(d) => d.into(),
          TlvItem::Unknown(d) => d.to_owned(),
        };

        assert_eq!(&raw[..], &raw_generated[..]);
    }
    Err(err) => println!("{}", err),
}

它实现了 TryFrom<&[u32]> 来解码 TLV 的原始数据,TLV 是 u32 元素数组。数据类型通过 Rust 枚举项的形状来检索。每个项都有关联的值。枚举本身及其关联值的结构都具有与 Vec::<u32>: From(&Self) 的 trait 边界,以生成 TLV 的原始数据。

关联值可以直接实例化,然后生成原始数据。

use alsa_ctl_tlv_codec::DbScale;

let scale = DbScale{
    min: -100,
    step: 10,
    mute_avail: true,
};

let raw_generated: Vec<u32> = (&scale).into();

let raw_expected = [1 as u32, 8, -100i32 as u32, 10 | 0x00010000];

assert_eq!(&raw_generated[..], &raw_expected[..]);

一些关联值是容器类型,它聚集了其他项。在这种情况下,使用 TlvItemContainer 进行聚合。

use alsa_ctl_tlv_codec::*;

let cntr = Container{
    entries: vec![
        TlvItem::Chmap(Chmap{
            mode: ChmapMode::Fixed,
            entries: vec![
                ChmapEntry{pos: ChmapPos::Generic(ChmapGenericPos::FrontLeft), ..Default::default()},
                ChmapEntry{pos: ChmapPos::Generic(ChmapGenericPos::FrontRight), ..Default::default()},
                ChmapEntry{pos: ChmapPos::Generic(ChmapGenericPos::LowFrequencyEffect), ..Default::default()},
            ],
        }),
        TlvItem::Chmap(Chmap{
            mode: ChmapMode::ArbitraryExchangeable,
            entries: vec![
                ChmapEntry{pos: ChmapPos::Generic(ChmapGenericPos::FrontLeft), ..Default::default()},
                ChmapEntry{pos: ChmapPos::Generic(ChmapGenericPos::FrontRight), ..Default::default()},
            ],
        }),
        TlvItem::Chmap(Chmap{
            mode: ChmapMode::PairedExchangeable,
            entries: vec![
                ChmapEntry{pos: ChmapPos::Generic(ChmapGenericPos::FrontLeft), ..Default::default()},
                ChmapEntry{pos: ChmapPos::Generic(ChmapGenericPos::FrontRight), ..Default::default()},
                ChmapEntry{pos: ChmapPos::Generic(ChmapGenericPos::RearLeft), ..Default::default()},
                ChmapEntry{pos: ChmapPos::Generic(ChmapGenericPos::RearRight), ..Default::default()},
            ],
        }),
    ],
};

let raw_generated: Vec<u32> = (&cntr).into();

let raw_expected = [0 as u32, 60,
                    0x101, 12, 3, 4, 8,
                    0x102, 8, 3, 4,
                    0x103, 16, 3, 4, 5, 6];

assert_eq!(&raw_generated[..], &raw_expected[..]);

工具

一些程序位于 src/bin 目录下。

tlv-decode.rs

此程序从 stdin 解码 TLV 的原始数据,或作为命令行参数的数值字面量,然后打印解析的结构。

如果没有命令行参数,它将打印帮助信息并退出。

$ cargo run --bin tlv-decode
Usage:
  tlv-decode MODE DATA | "-"

  where:
    MODE:           The mode to process after parsing DATA:
                        "structure":    prints data structures.
                        "macro":        prints C macro expression
                        "literal":      prints space-separated decimal array.
                        "raw":          prints binary with host endian.
    DATA:           space-separated DECIMAL and HEXADECIMAL array for the data of TLV.
    "-":            use binary from STDIN to interpret DATA according to host endian.
    DECIMAL:        decimal number. It can be signed if needed.
    HEXADECIMAL:    hexadecimal number. It should have '0x' as prefix.

对于命令行参数中的 TLV 数据

$ cargo run --bin tlv-decode -- structure 5 8 0xfffffe00 128
...
DbInterval(DbInterval { min: -512, max: 128, linear: false, mute_avail: true })

对于来自 STDIN 的 TLV 数据,在机器架构为小端的情况下

$ echo -en "\x05\x00\x00\x00\x08\x00\x00\x00\x00\xfe\xff\xff\x80\x00\x00\x00" | \
    cargo run --bin tlv-decode -- structure -
...
DbInterval(DbInterval { min: -512, max: 128, linear: false, mute_avail: true })

TLV 数据可以以 C 语言宏表达式形式打印

$ echo -en "\x05\x00\x00\x00\x08\x00\x00\x00\x00\xfe\xff\xff\x80\x00\x00\x00" | \
    cargo run --bin tlv-decode -- macro -
...
SNDRV_CTL_TLVD_ITEM ( SNDRV_CTL_TLVT_DB_MINMAX_MUTE, 0xfffffe00, 0x80 ) 

TLV 数据可以以 u32 数值字面量数组或按主机端序对齐的 u8 二进制形式打印

$ echo -en "\x05\x00\x00\x00\x08\x00\x00\x00\x00\xfe\xff\xff\x80\x00\x00\x00" | \
    cargo run --bin tlv-decode -- literal -
...
5 8 4294966784 128 

$ echo -en "\x05\x00\x00\x00\x08\x00\x00\x00\x00\xfe\xff\xff\x80\x00\x00\x00" | \
    cargo run --bin tlv-decode -- raw -
...

db-calculate.rs

此程序根据 STDIN 或命令行参数中的 TLV 数据,计算控制元素的 dB 值和原始值。它使用双精度浮点数进行 dB 计算。对于线性类型的 dB 计算,它使用指数和对数。

如果没有命令行参数,它将打印帮助信息并退出。

$ cargo run --bin db-calculate
Usage:
  db-calculate "db" DECIMAL-FLOATING-POINT VALUE-RANGE DATA | "-"
  db-calculate "value" DECIMAL | HEXADECIMAL VALUE-RANGE DATA | "-"

  where:
    "db":                   Use this program for db calculation.
    "value":                Use this program for value calculation.
    DECIMAL-FLOATING-POINT: decimal floating point number. It can be signed if needed.
    DECIMAL:                decimal number. It can be signed if needed.
    HEXADECIMAL:            hexadecimal number. It should have '0x' as prefix.
    VALUE-RANGE:            space-separated triplet of MIN, MAX, and STEP comes from information of
                            control element. All of them are DECIMAL or HEXADECIMAL.
    DATA:                   space-separated DECIMAL and HEXADECIMAL array for the data of TLV.
    "-":                    use STDIN to interpret DATA according to host endian.

   When data of TLV has information to support mute, "-9999999" for value and "-inf" for db are
   available.

对于基于 STDIN 的 TLV 数据从 dB 到值的计算,在机器架构为小端的情况下

$ echo -en "\x05\x00\x00\x00\x08\x00\x00\x00\x00\xfe\xff\xff\x80\x00\x00\x00" | \
    cargo run --bin db-calculate db 1.0    128 512 1    -
  ...
  495

对于基于命令行参数的 TLV 数据从值到 dB 的计算

$ cargo run --bin db-calculate value 495    128 512 1    5 8 0xfffffe00 0
  ...
  0.996666666666667

计算没有经过验证的数字。

许可证

alsa-ctl-tlv-codec crate 在 MIT 许可证 下发布。

支持

如果发现问题,请在 https://github.com/alsa-project/snd-firewire-ctl-services/ 中提交。

无运行时依赖