Expand description

The crate is designed to process data expressed by TLV (Type-Length-Value) style in ALSA control interface. The crate produces encoder and decoder for the u32 array of data of TLV style as well as structures and enumerations to express the content.

The data of TLV style is used for several purposes. As of Linux kernel 5.10, it includes information about dB expression as well as information about channel mapping in ALSA PCM substream. The definitions are under include/uapi/sound/tlv.h of source code of Linux kernel.

Structures and enumerations

Linux kernel has the series of macro to build u32 array for data of TLV, instead of definitions of structure in C language. This is convenient to embed binary data to object file, however not friendly to developers and users. The crate has some structures and enumerations to express the data of TLV. The relationship between structures and macros is listed below:

  • 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
  • Container
    • SNDRV_CTL_TLVT_CONTAINER

The crate has TlvItem enumeration to dispatch data of TLV for the above structures.

Usage

Add the following line to your Cargo.toml file:

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

TlvItem enumeration is a good start to use the crate.

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),
}

It implements TryFrom<&[u32]> to decode raw data of TLV which is array of u32 elements. The type of data is retrieved by a shape of Rust enumeration items. Each item has associated value. Both of enumeration itself and the structure of associated value has trait boundary to Vec::<u32>: From(&Self) to generate raw data of TLV.

The associated value can be instantiated directly, then raw data can be generated:

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[..]);

Some of the associated value are container type, which aggregates the other items. In this case, TlvItem is used for the aggregation of Container.

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[..]);

Utilities

Some programs are available under src/bin directory.

tlv-decode.rs

This program decodes raw data of TLV from stdin, or numeric literals as arguments of command line, then print parsed structure.

Without any command line argument, it prints help message and exit.

$ 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.

For data of TLV from arguments in command line:

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

For data of TLV from STDIN, in the case that the machine architecture is little endian:

$ 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 })

The data of TLV can be printed in C language macro expression:

$ 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 ) 

The data of TLV can be printed as either u32 numeric literal array or u8 binary aligned to host endian:

$ 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

This program calculates between dB value and raw value for control element, based on data of TLV from STDIN or command line argument. It uses double precision floating point number for dB calculation internally. For linear type of dB calculation, it uses exponentiation and logarithm.

Without any command line argument, it prints help message and exit.

$ 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.

For calculation from dB to value based on data of TLV from STDIN, in the case that the machine architecture is little endian:

$ 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

For calculation to dB from value based on data of TLV from arguments of command line:

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

The calculation has no validated numerics.

License

The alsa-ctl-tlv-codec crate is released under MIT license.

Support

If finding issue, please file it in https://github.com/alsa-project/snd-firewire-ctl-services/.

Structs

The data to express channel map of PCM substream in TLV (Type-Length-Value) of ALSA control interface.

The entry to express information of each channel in channel map.

The data to express container to aggregate multiple data for TLV (Type-Length-Value) of ALSA control interface.

The data to express dB interval in TLV (Type-Length-Value) of ALSA control interface.

The data to express multiple ranges in the state of control element for dB expression. It has SNDRV_CTL_TLVT_DB_RANGE (=3) in its type field and has variable number of elements in value field.

The entry to express information of each entry of dB range.

The data to express dB scale in TLV (Type-Length-Value) of ALSA control interface.

The structure to store decode context and position for error reporting.

The structure expresses the range of available value in the state of control element with step to change it.

Enums

The enumeration to express generic channel position corresponding to physical port on real device. They are defined as SNDRV_CHMAP_XXX enumeration in UAPI of Linux kernel.

The mode for channel map.

The enumeration to express channel position.

The enumeration to dispatch each type of data for entry of dB range.

The cause of error to decode given array as TLV data.

Available items as data of TLV (Type-Length-Value) style in ALSA control interface.

The enumeration to express conversion error into interval of dB.

Constants

When information about dB includes mute_avail, the value is available to mute the control element. It’s relevant to SNDRV_CTL_TLVD_DB_GAIN_MUTE macro in UAPI of Linux kernel.

The value of dB should be expressed in 0.1 dB unit in data of TLV and crate structures.

Traits

The trait for utilities about range information.

The trait for common methods to data of TLV (Type-Length-Value) in ALSA control interface. The TryFrom supertrait should be implemented to parse the array of u32 elements and it can return TlvDecodeError at failure. The trait boundary to Vec::::From(&Self) should be implemented as well to build the array of u32 element.

The trait for conversion into interval of dB.

The trait for conversion into range of raw value on control element.