more work on descriptors and config model
This commit is contained in:
@@ -488,3 +488,46 @@ pub enum TerminalType {
|
|||||||
Ext1394DaStream = 0x0606,
|
Ext1394DaStream = 0x0606,
|
||||||
Ext1394DvStreamSoundtrack = 0x0607,
|
Ext1394DvStreamSoundtrack = 0x0607,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(u16)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum AudioDataFormatType1 {
|
||||||
|
Undefined = 0,
|
||||||
|
Pcm = 1,
|
||||||
|
Pcm8 = 2,
|
||||||
|
IeeeFloat = 3,
|
||||||
|
Alaw = 4,
|
||||||
|
Mulaw = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u16)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum AudioDataFormatType2 {
|
||||||
|
Undefined = 0x1000,
|
||||||
|
Mpeg = 0x1001,
|
||||||
|
Ac3 = 0x1002,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u16)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum AudioDataFormatType3 {
|
||||||
|
Undefined = 0x2000,
|
||||||
|
Ac3 = 0x2001,
|
||||||
|
Mpeg1Layer1 = 0x2002,
|
||||||
|
Mpeg1Layer23OrMpeg2NoExt = 0x2003,
|
||||||
|
Mpeg2Ext = 0x2004,
|
||||||
|
Mpeg2Layer1Ls = 0x2005,
|
||||||
|
Mpeg2Layer23Ls = 0x2006,
|
||||||
|
}
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum FormatType {
|
||||||
|
Undefined = 0,
|
||||||
|
Type1 = 1,
|
||||||
|
Type2 = 2,
|
||||||
|
Type3 = 3,
|
||||||
|
}
|
||||||
|
|||||||
+208
-20
@@ -6,7 +6,7 @@ use crate::constants::*;
|
|||||||
use byteorder_embedded_io::{LittleEndian, WriteBytesExt};
|
use byteorder_embedded_io::{LittleEndian, WriteBytesExt};
|
||||||
use embedded_io::ErrorType;
|
use embedded_io::ErrorType;
|
||||||
use modular_bitfield::prelude::*;
|
use modular_bitfield::prelude::*;
|
||||||
use usb_device::{UsbError, descriptor::DescriptorWriter};
|
use usb_device::{UsbError, bus::StringIndex, descriptor::DescriptorWriter};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DescriptorWriterError {
|
pub struct DescriptorWriterError {
|
||||||
@@ -218,7 +218,7 @@ pub trait Descriptor {
|
|||||||
fn write<T: embedded_io::Write>(&self, writer: &mut T) -> Result<(), T::Error>;
|
fn write<T: embedded_io::Write>(&self, writer: &mut T) -> Result<(), T::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct ClockSource {
|
pub struct ClockSource {
|
||||||
pub id: u8,
|
pub id: u8,
|
||||||
pub clock_type: ClockType,
|
pub clock_type: ClockType,
|
||||||
@@ -226,7 +226,7 @@ pub struct ClockSource {
|
|||||||
pub frequency_access: AccessControl,
|
pub frequency_access: AccessControl,
|
||||||
pub validity_access: AccessControl,
|
pub validity_access: AccessControl,
|
||||||
pub assoc_terminal: u8,
|
pub assoc_terminal: u8,
|
||||||
pub string: u8,
|
pub string: Option<StringIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClockSource {
|
impl ClockSource {
|
||||||
@@ -243,7 +243,7 @@ impl ClockSource {
|
|||||||
writer.write_u8(self.bm_attributes())?; // bmAttributes
|
writer.write_u8(self.bm_attributes())?; // bmAttributes
|
||||||
writer.write_u8(self.bm_controls())?; // bmControls
|
writer.write_u8(self.bm_controls())?; // bmControls
|
||||||
writer.write_u8(self.assoc_terminal)?; // bAssocTerminal
|
writer.write_u8(self.assoc_terminal)?; // bAssocTerminal
|
||||||
writer.write_u8(self.string)?; // iClockSource
|
writer.write_u8(self.string.map_or(0, |n| u8::from(n)))?; // iClockSource
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -271,7 +271,7 @@ impl Descriptor for ClockSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct InputTerminal {
|
pub struct InputTerminal {
|
||||||
pub id: u8,
|
pub id: u8,
|
||||||
pub terminal_type: TerminalType,
|
pub terminal_type: TerminalType,
|
||||||
@@ -287,7 +287,7 @@ pub struct InputTerminal {
|
|||||||
pub underflow_control: AccessControl,
|
pub underflow_control: AccessControl,
|
||||||
pub overflow_control: AccessControl,
|
pub overflow_control: AccessControl,
|
||||||
pub phantom_power_control: AccessControl,
|
pub phantom_power_control: AccessControl,
|
||||||
pub string: u8,
|
pub string: Option<StringIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputTerminal {
|
impl InputTerminal {
|
||||||
@@ -311,7 +311,7 @@ impl InputTerminal {
|
|||||||
| ((self.overflow_control as u8) << 2)
|
| ((self.overflow_control as u8) << 2)
|
||||||
| ((self.phantom_power_control as u8) << 4),
|
| ((self.phantom_power_control as u8) << 4),
|
||||||
)?;
|
)?;
|
||||||
writer.write_u8(self.string)?;
|
writer.write_u8(self.string.map_or(0, |s| u8::from(s)))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,7 +337,7 @@ impl Descriptor for InputTerminal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct OutputTerminal {
|
pub struct OutputTerminal {
|
||||||
pub id: u8,
|
pub id: u8,
|
||||||
pub terminal_type: TerminalType,
|
pub terminal_type: TerminalType,
|
||||||
@@ -349,7 +349,7 @@ pub struct OutputTerminal {
|
|||||||
pub overload_control: AccessControl,
|
pub overload_control: AccessControl,
|
||||||
pub underflow_control: AccessControl,
|
pub underflow_control: AccessControl,
|
||||||
pub overflow_control: AccessControl,
|
pub overflow_control: AccessControl,
|
||||||
pub string: u8,
|
pub string: Option<StringIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutputTerminal {
|
impl OutputTerminal {
|
||||||
@@ -367,7 +367,7 @@ impl OutputTerminal {
|
|||||||
| ((self.underflow_control as u8) << 6),
|
| ((self.underflow_control as u8) << 6),
|
||||||
)?;
|
)?;
|
||||||
writer.write_u8(self.overflow_control as u8)?;
|
writer.write_u8(self.overflow_control as u8)?;
|
||||||
writer.write_u8(self.string)?; // iTerminal
|
writer.write_u8(self.string.map_or(0, |n| u8::from(n)))?; // iTerminal
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -393,7 +393,7 @@ impl Descriptor for OutputTerminal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub enum Terminal {
|
pub enum Terminal {
|
||||||
Input(InputTerminal),
|
Input(InputTerminal),
|
||||||
Output(OutputTerminal),
|
Output(OutputTerminal),
|
||||||
@@ -803,7 +803,7 @@ impl<const N: usize> Descriptor for ExtensionUnit<N> {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
/// Enum covering basic sized audio class descriptors for building the Class-Specific
|
/// Enum covering basic sized audio class descriptors for building the Class-Specific
|
||||||
/// AC Interface descriptor. Dynamically sized descriptors are not supported yet.
|
/// AC Interface descriptor. Dynamically sized descriptors are not supported yet.
|
||||||
pub enum AudioClassDescriptor {
|
pub enum AudioClassDescriptor {
|
||||||
@@ -830,6 +830,17 @@ impl AudioClassDescriptor {
|
|||||||
AudioClassDescriptor::OutputTerminal(ot) => ot.write(writer),
|
AudioClassDescriptor::OutputTerminal(ot) => ot.write(writer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn write_descriptor(
|
||||||
|
&self,
|
||||||
|
writer: &mut DescriptorWriter,
|
||||||
|
) -> Result<(), DescriptorWriterError> {
|
||||||
|
match self {
|
||||||
|
AudioClassDescriptor::ClockSource(cs) => cs.write_descriptor(writer),
|
||||||
|
AudioClassDescriptor::ClockMultiplier(cm) => cm.write_descriptor(writer),
|
||||||
|
AudioClassDescriptor::InputTerminal(it) => it.write_descriptor(writer),
|
||||||
|
AudioClassDescriptor::OutputTerminal(ot) => ot.write_descriptor(writer),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ClockSource> for AudioClassDescriptor {
|
impl From<ClockSource> for AudioClassDescriptor {
|
||||||
@@ -902,6 +913,116 @@ impl<const N: usize> AudioClassInterfaceDescriptor<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// USB Device Class Definition for Audio Data Formats Type I Format Type Descriptor
|
||||||
|
pub enum SamplingFrequencySet<'a> {
|
||||||
|
Discrete(&'a [u32]),
|
||||||
|
Continuous(u32, u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SamplingFrequencySet<'a> {
|
||||||
|
pub fn size(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
SamplingFrequencySet::Discrete(freqs) => freqs.len() as u8 * 3,
|
||||||
|
SamplingFrequencySet::Continuous(_, _) => 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FormatType1 {
|
||||||
|
pub bytes_per_sample: u8, // bSubframeSize
|
||||||
|
pub bit_resolution: u8, // bBitResolution
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatType1 {
|
||||||
|
fn write_payload<T: embedded_io::Write>(&self, writer: &mut T) -> Result<(), T::Error> {
|
||||||
|
writer.write_u8(ClassSpecificASInterfaceDescriptorSubtype::FormatType as u8)?;
|
||||||
|
writer.write_u8(FormatType::Type1 as u8)?; // bFormatType
|
||||||
|
writer.write_u8(self.bytes_per_sample)?; // bSubslotSize
|
||||||
|
writer.write_u8(self.bit_resolution)?; // bBitResolution
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Descriptor for FormatType1 {
|
||||||
|
const MAX_SIZE: usize = 6;
|
||||||
|
fn size(&self) -> u8 {
|
||||||
|
6
|
||||||
|
}
|
||||||
|
fn write<T: embedded_io::Write>(&self, writer: &mut T) -> Result<(), T::Error> {
|
||||||
|
writer.write_u8(self.size())?;
|
||||||
|
writer.write_u8(ClassSpecificDescriptorType::Interface as u8)?;
|
||||||
|
self.write_payload(writer)
|
||||||
|
}
|
||||||
|
fn write_descriptor<'w, 'd>(
|
||||||
|
&self,
|
||||||
|
writer: &'w mut DescriptorWriter<'d>,
|
||||||
|
) -> Result<(), DescriptorWriterError> {
|
||||||
|
let mut writer =
|
||||||
|
DescriptorWriterAdapter::new(writer, ClassSpecificDescriptorType::Interface);
|
||||||
|
self.write_payload(&mut writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u32)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Type1FormatBitmap {
|
||||||
|
Pcm = (1 << 0),
|
||||||
|
Pcm8 = (1 << 1),
|
||||||
|
IeeeFloat = (1 << 2),
|
||||||
|
Alaw = (1 << 3),
|
||||||
|
Mulaw = (1 << 4),
|
||||||
|
Raw = (1 << 31),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AudioStreamingInterface {
|
||||||
|
terminal_id: u8,
|
||||||
|
active_alt_setting: AccessControl,
|
||||||
|
valid_alt_settings: AccessControl,
|
||||||
|
/// Only type 1 format is supported
|
||||||
|
format_type: FormatType,
|
||||||
|
format_bitmap: Type1FormatBitmap,
|
||||||
|
num_channels: u8,
|
||||||
|
channel_config: ChannelConfig,
|
||||||
|
string: Option<StringIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioStreamingInterface {
|
||||||
|
fn bm_controls(&self) -> u8 {
|
||||||
|
self.active_alt_setting as u8 | ((self.valid_alt_settings as u8) << 2)
|
||||||
|
}
|
||||||
|
fn write_payload<T: embedded_io::Write>(&self, writer: &mut T) -> Result<(), T::Error> {
|
||||||
|
writer.write_u8(ClassSpecificASInterfaceDescriptorSubtype::General as u8)?;
|
||||||
|
writer.write_u8(self.terminal_id)?;
|
||||||
|
writer.write_u8(self.bm_controls())?;
|
||||||
|
writer.write_u8(self.format_type as u8)?;
|
||||||
|
writer.write_u32::<LittleEndian>(self.format_bitmap as u32)?;
|
||||||
|
writer.write_u8(self.num_channels)?;
|
||||||
|
writer.write(&self.channel_config.bytes)?;
|
||||||
|
writer.write_u8(self.string.map_or(0, |s| u8::from(s)))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Descriptor for AudioStreamingInterface {
|
||||||
|
const MAX_SIZE: usize = 16;
|
||||||
|
fn size(&self) -> u8 {
|
||||||
|
Self::MAX_SIZE as u8
|
||||||
|
}
|
||||||
|
fn write<T: embedded_io::Write>(&self, writer: &mut T) -> Result<(), T::Error> {
|
||||||
|
writer.write_u8(self.size())?;
|
||||||
|
writer.write_u8(ClassSpecificDescriptorType::Interface as u8)?;
|
||||||
|
self.write_payload(writer)
|
||||||
|
}
|
||||||
|
fn write_descriptor<'w, 'd>(
|
||||||
|
&self,
|
||||||
|
writer: &'w mut DescriptorWriter<'d>,
|
||||||
|
) -> Result<(), DescriptorWriterError> {
|
||||||
|
let mut writer =
|
||||||
|
DescriptorWriterAdapter::new(writer, ClassSpecificDescriptorType::Interface);
|
||||||
|
self.write_payload(&mut writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate std;
|
extern crate std;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -911,6 +1032,7 @@ mod tests {
|
|||||||
use std::{print, println};
|
use std::{print, println};
|
||||||
#[test]
|
#[test]
|
||||||
fn test_clock_source() {
|
fn test_clock_source() {
|
||||||
|
let string = Some(unsafe { core::mem::transmute(10u8) });
|
||||||
let cs = ClockSource {
|
let cs = ClockSource {
|
||||||
id: 8,
|
id: 8,
|
||||||
clock_type: ClockType::InternalFixed,
|
clock_type: ClockType::InternalFixed,
|
||||||
@@ -918,7 +1040,7 @@ mod tests {
|
|||||||
frequency_access: AccessControl::ReadOnly,
|
frequency_access: AccessControl::ReadOnly,
|
||||||
validity_access: AccessControl::ReadOnly,
|
validity_access: AccessControl::ReadOnly,
|
||||||
assoc_terminal: 6,
|
assoc_terminal: 6,
|
||||||
string: 10,
|
string,
|
||||||
};
|
};
|
||||||
let mut buf = [0u8; ClockSource::MAX_SIZE];
|
let mut buf = [0u8; ClockSource::MAX_SIZE];
|
||||||
let mut cur = Cursor::new(&mut buf[..]);
|
let mut cur = Cursor::new(&mut buf[..]);
|
||||||
@@ -1014,7 +1136,7 @@ mod tests {
|
|||||||
underflow_control: AccessControl::ReadOnly,
|
underflow_control: AccessControl::ReadOnly,
|
||||||
overflow_control: AccessControl::ReadOnly,
|
overflow_control: AccessControl::ReadOnly,
|
||||||
phantom_power_control: AccessControl::NotPresent,
|
phantom_power_control: AccessControl::NotPresent,
|
||||||
string: 20,
|
string: Some(unsafe { core::mem::transmute(20u8) }),
|
||||||
};
|
};
|
||||||
let mut buf = [0u8; InputTerminal::MAX_SIZE];
|
let mut buf = [0u8; InputTerminal::MAX_SIZE];
|
||||||
let mut cur = Cursor::new(&mut buf[..]);
|
let mut cur = Cursor::new(&mut buf[..]);
|
||||||
@@ -1058,7 +1180,7 @@ mod tests {
|
|||||||
overload_control: AccessControl::ReadOnly,
|
overload_control: AccessControl::ReadOnly,
|
||||||
underflow_control: AccessControl::ReadOnly,
|
underflow_control: AccessControl::ReadOnly,
|
||||||
overflow_control: AccessControl::ReadOnly,
|
overflow_control: AccessControl::ReadOnly,
|
||||||
string: 20,
|
string: Some(unsafe { core::mem::transmute(20u8) }),
|
||||||
};
|
};
|
||||||
let mut buf = [0u8; OutputTerminal::MAX_SIZE];
|
let mut buf = [0u8; OutputTerminal::MAX_SIZE];
|
||||||
let mut cur = Cursor::new(&mut buf[..]);
|
let mut cur = Cursor::new(&mut buf[..]);
|
||||||
@@ -1334,7 +1456,7 @@ mod tests {
|
|||||||
frequency_access: AccessControl::NotPresent,
|
frequency_access: AccessControl::NotPresent,
|
||||||
validity_access: AccessControl::NotPresent,
|
validity_access: AccessControl::NotPresent,
|
||||||
assoc_terminal: 0,
|
assoc_terminal: 0,
|
||||||
string: 0,
|
string: None,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
InputTerminal {
|
InputTerminal {
|
||||||
@@ -1352,7 +1474,7 @@ mod tests {
|
|||||||
underflow_control: AccessControl::NotPresent,
|
underflow_control: AccessControl::NotPresent,
|
||||||
overflow_control: AccessControl::NotPresent,
|
overflow_control: AccessControl::NotPresent,
|
||||||
phantom_power_control: AccessControl::NotPresent,
|
phantom_power_control: AccessControl::NotPresent,
|
||||||
string: 0,
|
string: None,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
OutputTerminal {
|
OutputTerminal {
|
||||||
@@ -1366,7 +1488,7 @@ mod tests {
|
|||||||
overload_control: AccessControl::NotPresent,
|
overload_control: AccessControl::NotPresent,
|
||||||
underflow_control: AccessControl::NotPresent,
|
underflow_control: AccessControl::NotPresent,
|
||||||
overflow_control: AccessControl::NotPresent,
|
overflow_control: AccessControl::NotPresent,
|
||||||
string: 0,
|
string: None,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
OutputTerminal {
|
OutputTerminal {
|
||||||
@@ -1380,7 +1502,7 @@ mod tests {
|
|||||||
overload_control: AccessControl::NotPresent,
|
overload_control: AccessControl::NotPresent,
|
||||||
underflow_control: AccessControl::NotPresent,
|
underflow_control: AccessControl::NotPresent,
|
||||||
overflow_control: AccessControl::NotPresent,
|
overflow_control: AccessControl::NotPresent,
|
||||||
string: 0,
|
string: None,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
InputTerminal {
|
InputTerminal {
|
||||||
@@ -1398,7 +1520,7 @@ mod tests {
|
|||||||
underflow_control: AccessControl::NotPresent,
|
underflow_control: AccessControl::NotPresent,
|
||||||
overflow_control: AccessControl::NotPresent,
|
overflow_control: AccessControl::NotPresent,
|
||||||
phantom_power_control: AccessControl::NotPresent,
|
phantom_power_control: AccessControl::NotPresent,
|
||||||
string: 0,
|
string: None,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
];
|
];
|
||||||
@@ -1419,4 +1541,70 @@ mod tests {
|
|||||||
println!();
|
println!();
|
||||||
write_usb_descriptor_pcap("./uac2.pcap", 0x02, 0, bytes);
|
write_usb_descriptor_pcap("./uac2.pcap", 0x02, 0, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_type1() {
|
||||||
|
let format = FormatType1 {
|
||||||
|
bytes_per_sample: 4,
|
||||||
|
bit_resolution: 24,
|
||||||
|
};
|
||||||
|
let mut buf = [0u8; FormatType1::MAX_SIZE];
|
||||||
|
let len = {
|
||||||
|
let mut cur = Cursor::new(&mut buf[..]);
|
||||||
|
format.write(&mut cur).unwrap();
|
||||||
|
cur.position()
|
||||||
|
};
|
||||||
|
let descriptor = &buf[..len];
|
||||||
|
assert_eq!(
|
||||||
|
descriptor,
|
||||||
|
&[
|
||||||
|
6, //bLength
|
||||||
|
0x24, // CS_INTERFACE
|
||||||
|
0x02, // FORMAT_TYPE
|
||||||
|
0x01, // FORMAT_TYPE_I
|
||||||
|
4, // bSubframeSize
|
||||||
|
24, // bBitResolution
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn test_as_interface_desc() {
|
||||||
|
let intf = AudioStreamingInterface {
|
||||||
|
terminal_id: 2,
|
||||||
|
active_alt_setting: AccessControl::Programmable,
|
||||||
|
valid_alt_settings: AccessControl::ReadOnly,
|
||||||
|
format_type: FormatType::Type1,
|
||||||
|
format_bitmap: Type1FormatBitmap::Pcm,
|
||||||
|
num_channels: 2,
|
||||||
|
channel_config: ChannelConfig::default_chans(2),
|
||||||
|
string: None,
|
||||||
|
};
|
||||||
|
let mut buf = [0u8; AudioStreamingInterface::MAX_SIZE];
|
||||||
|
let len = {
|
||||||
|
let mut cur = Cursor::new(&mut buf[..]);
|
||||||
|
intf.write(&mut cur).unwrap();
|
||||||
|
cur.position()
|
||||||
|
};
|
||||||
|
let descriptor = &buf[..len];
|
||||||
|
assert_eq!(
|
||||||
|
descriptor,
|
||||||
|
&[
|
||||||
|
16,
|
||||||
|
0x24, // CS_INTERFACE
|
||||||
|
0x01, // AS_GENERAL
|
||||||
|
2, // bTerminalLink
|
||||||
|
3 | (1 << 2), // bmControls
|
||||||
|
1, // bFormatType
|
||||||
|
1, // bmFormats[0]
|
||||||
|
0, // bmFormats[1]
|
||||||
|
0, // bmFormats[2]
|
||||||
|
0, // bmFormats[3]
|
||||||
|
2, // bNrChannels
|
||||||
|
3, // bmChannelConfig[0]
|
||||||
|
0, // bmChannelConfig[1]
|
||||||
|
0, // bmChannelConfig[2]
|
||||||
|
0, // bmChannelConfig[3]
|
||||||
|
0 // iChannelNames
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+394
-209
@@ -5,6 +5,8 @@ mod constants;
|
|||||||
mod cursor;
|
mod cursor;
|
||||||
mod descriptors;
|
mod descriptors;
|
||||||
|
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
use constants::*;
|
use constants::*;
|
||||||
use descriptors::*;
|
use descriptors::*;
|
||||||
|
|
||||||
@@ -13,99 +15,397 @@ use usb_device::device::DEFAULT_ALTERNATE_SETTING;
|
|||||||
use usb_device::endpoint::{self, Endpoint, EndpointDirection, In, Out};
|
use usb_device::endpoint::{self, Endpoint, EndpointDirection, In, Out};
|
||||||
use usb_device::{UsbDirection, class_prelude::*};
|
use usb_device::{UsbDirection, class_prelude::*};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
mod sealed {
|
||||||
pub enum Format {
|
pub trait Sealed {}
|
||||||
/// Signed, 16 bits per subframe, little endian
|
|
||||||
S16le,
|
|
||||||
/// Signed, 24 bits per subframe, little endian
|
|
||||||
S24le,
|
|
||||||
/// Signed, 32 bits per subframe, little endian
|
|
||||||
S32le,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sampling rates that shall be supported by an steaming endpoint
|
pub enum UsbAudioClassError {
|
||||||
#[derive(Debug)]
|
NotImplemented,
|
||||||
pub enum Rates<'a> {
|
Other,
|
||||||
/// A continuous range of sampling rates in samples/second defined by a
|
|
||||||
/// tuple including a minimum value and a maximum value. The maximum value
|
|
||||||
/// must be greater than the minimum value.
|
|
||||||
Continuous(u32, u32),
|
|
||||||
/// A set of discrete sampling rates in samples/second
|
|
||||||
Discrete(&'a [u32]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<T: core::error::Error> From<T> for UsbAudioClassError {
|
||||||
pub struct StreamConfig<'a> {
|
fn from(_: T) -> Self {
|
||||||
format: Format,
|
UsbAudioClassError::Other
|
||||||
channels: u8,
|
|
||||||
rates: Rates<'a>,
|
|
||||||
terminal_type: TerminalType,
|
|
||||||
/// ISO endpoint size calculated from format, channels and rates (may be
|
|
||||||
/// removed in future)
|
|
||||||
ep_size: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamConfig<'_> {
|
|
||||||
/// Create a stream configuration with one or more discrete sampling rates
|
|
||||||
/// indicated in samples/second. An input stream or an output stream will
|
|
||||||
/// have an Input Terminal or Output Terminal of Terminal Type
|
|
||||||
/// `terminal_type`, respectively.
|
|
||||||
pub fn new_discrete(
|
|
||||||
format: Format,
|
|
||||||
channels: u8,
|
|
||||||
rates: &'_ [u32],
|
|
||||||
terminal_type: TerminalType,
|
|
||||||
) -> Result<StreamConfig<'_>> {
|
|
||||||
let max_rate = rates.iter().max().unwrap();
|
|
||||||
let ep_size = Self::ep_size(format, channels, *max_rate)?;
|
|
||||||
let rates = Rates::Discrete(rates);
|
|
||||||
Ok(StreamConfig {
|
|
||||||
format,
|
|
||||||
channels,
|
|
||||||
rates,
|
|
||||||
terminal_type,
|
|
||||||
ep_size,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a stream configuration with a continuous range of supported
|
pub trait RangeType: sealed::Sealed {}
|
||||||
/// sampling rates indicated in samples/second. An input stream or an output
|
impl sealed::Sealed for i8 {}
|
||||||
/// stream will have an Input Terminal or Output Terminal of Terminal Type
|
impl sealed::Sealed for i16 {}
|
||||||
/// `terminal_type`, respectively.
|
impl sealed::Sealed for i32 {}
|
||||||
pub fn new_continuous(
|
impl sealed::Sealed for u8 {}
|
||||||
format: Format,
|
impl sealed::Sealed for u16 {}
|
||||||
channels: u8,
|
impl sealed::Sealed for u32 {}
|
||||||
min_rate: u32,
|
|
||||||
max_rate: u32,
|
impl RangeType for i8 {}
|
||||||
terminal_type: TerminalType,
|
impl RangeType for i16 {}
|
||||||
) -> Result<StreamConfig<'static>> {
|
impl RangeType for i32 {}
|
||||||
if min_rate >= max_rate {
|
impl RangeType for u8 {}
|
||||||
return Err(Error::InvalidValue);
|
impl RangeType for u16 {}
|
||||||
|
impl RangeType for u32 {}
|
||||||
|
|
||||||
|
pub struct RangeEntry<T: RangeType> {
|
||||||
|
min: T,
|
||||||
|
max: T,
|
||||||
|
res: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: RangeType> RangeEntry<T> {
|
||||||
|
pub fn new(min: T, max: T, res: T) -> Self {
|
||||||
|
Self { min, max, res }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for implementing USB Audio Class 2 devices
|
||||||
|
///
|
||||||
|
/// Contains optional callback methods which will be called by the class driver. All
|
||||||
|
/// callbacks are optional, which may be useful for a tight-loop polling implementation
|
||||||
|
/// but most implementations will want to implement at least `audio_data_rx`.
|
||||||
|
///
|
||||||
|
/// Unimplemented callbacks should return `Err(UsbAudioClassError::NotImplemented)`. Other
|
||||||
|
/// errors will panic (the underlying callbacks are not fallible). If you need to handle errors,
|
||||||
|
/// you should use the callback to infalliably signal another task.
|
||||||
|
pub trait UsbAudioClass<'a, B: UsbBus> {
|
||||||
|
/// Called when audio data is received from the host. The `Endpoint`
|
||||||
|
/// is ready for `read()`.
|
||||||
|
fn audio_data_rx(
|
||||||
|
&self,
|
||||||
|
ep: &Endpoint<'a, B, endpoint::Out>,
|
||||||
|
) -> core::result::Result<(), UsbAudioClassError> {
|
||||||
|
Err(UsbAudioClassError::NotImplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait for implementing Sampling Frequency Control for USB Audio Clock Sources
|
||||||
|
/// ref: USB Audio Class Specification 2.0 5.2.5.1.1
|
||||||
|
///
|
||||||
|
/// Contains optional callback methods which will be called by the class driver. If
|
||||||
|
/// `set_sample_rate` is implemented, `get_sample_rate` must also be implemented.
|
||||||
|
/// Callbacks run in USB context, so should not block.
|
||||||
|
///
|
||||||
|
/// Unimplemented callbacks should return `Err(UsbAudioClassError::NotImplemented)`. Other
|
||||||
|
/// errors will panic (the underlying callbacks are not fallible). If you need to handle errors,
|
||||||
|
/// you should use the callback to infalliably signal another task.
|
||||||
|
pub trait UsbAudioClockSource {
|
||||||
|
const CLOCK_TYPE: ClockType;
|
||||||
|
const SOF_SYNC: bool;
|
||||||
|
/// Called when the host requests the current sample rate. Returns the sample rate in Hz.
|
||||||
|
fn get_sample_rate(&self) -> core::result::Result<u32, UsbAudioClassError> {
|
||||||
|
Err(UsbAudioClassError::NotImplemented)
|
||||||
|
}
|
||||||
|
/// Called when the host requests to set the sample rate. Should reconfigure the clock source
|
||||||
|
/// if necessary.
|
||||||
|
fn set_sample_rate(
|
||||||
|
&mut self,
|
||||||
|
sample_rate: u32,
|
||||||
|
) -> core::result::Result<(), UsbAudioClassError> {
|
||||||
|
Err(UsbAudioClassError::NotImplemented)
|
||||||
|
}
|
||||||
|
/// Called when the host requests to get the clock validity. Returns `true`
|
||||||
|
/// if the clock is stable and on frequency.
|
||||||
|
fn get_clock_validity(&self) -> core::result::Result<bool, UsbAudioClassError> {
|
||||||
|
Err(UsbAudioClassError::NotImplemented)
|
||||||
|
}
|
||||||
|
/// Called during descriptor construction to describe if the clock validity can be read (write is not valid).
|
||||||
|
///
|
||||||
|
/// By default will call `get_clock_validity` to determine if the clock validity can be read.
|
||||||
|
fn get_validity_access(&self) -> core::result::Result<bool, UsbAudioClassError> {
|
||||||
|
match self.get_clock_validity() {
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(UsbAudioClassError::NotImplemented) => Ok(false),
|
||||||
|
Err(err) => Err(err),
|
||||||
}
|
}
|
||||||
let ep_size = Self::ep_size(format, channels, max_rate)?;
|
|
||||||
let rates = Rates::Continuous(min_rate, max_rate);
|
|
||||||
Ok(StreamConfig {
|
|
||||||
format,
|
|
||||||
channels,
|
|
||||||
rates,
|
|
||||||
terminal_type,
|
|
||||||
ep_size,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// calculate ISO endpoint size from format, channels and rates
|
/// Called when the hosts makes a RANGE request for the clock source. Returns a slice of possible sample rates.
|
||||||
fn ep_size(format: Format, channels: u8, max_rate: u32) -> Result<u16> {
|
///
|
||||||
let octets_per_frame = channels as u32
|
/// Must be implemented if the clock source returns programmable get_frequency_access
|
||||||
* match format {
|
///
|
||||||
Format::S16le => 2,
|
/// Rates must meet the invariants in the specification:
|
||||||
Format::S24le => 3,
|
/// * The subranges must be ordered in ascendingorder
|
||||||
Format::S32le => 4,
|
/// * Individual subranges cannot overlap
|
||||||
};
|
/// * If a subrange consists of only a single value, the corresponding triplet must contain that value for both
|
||||||
let ep_size = octets_per_frame * max_rate / 1000;
|
/// its MIN and MAX subattribute and the RES subattribute must be set to zero
|
||||||
// if ep_size > MAX_ISO_EP_SIZE {
|
///
|
||||||
// return Err(Error::BandwidthExceeded);
|
/// ref: USB Audio Class Specification 2.0 5.2.1 & 5.2.3.3
|
||||||
// }
|
fn get_rates(&self) -> core::result::Result<&[RangeEntry<u32>], UsbAudioClassError> {
|
||||||
Ok(ep_size as u16)
|
Err(UsbAudioClassError::NotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the ClockSource descriptor. It is not intended to override this method.
|
||||||
|
///
|
||||||
|
/// Assumes access control based on clock type. Internal fixed/variable are read only,
|
||||||
|
/// external and internal programmable are programmable.
|
||||||
|
fn get_configuration_descriptor(
|
||||||
|
&self,
|
||||||
|
id: u8,
|
||||||
|
string: Option<StringIndex>,
|
||||||
|
) -> usb_device::Result<ClockSource> {
|
||||||
|
let frequency_access = match Self::CLOCK_TYPE {
|
||||||
|
ClockType::InternalFixed | ClockType::InternalVariable => AccessControl::ReadOnly,
|
||||||
|
ClockType::External | ClockType::InternalProgrammable => AccessControl::Programmable,
|
||||||
|
};
|
||||||
|
let validity_access = match self.get_validity_access() {
|
||||||
|
Ok(true) => AccessControl::ReadOnly,
|
||||||
|
Ok(false) | Err(UsbAudioClassError::NotImplemented) => AccessControl::NotPresent,
|
||||||
|
_ => return Err(UsbError::Unsupported),
|
||||||
|
};
|
||||||
|
|
||||||
|
let cs = ClockSource {
|
||||||
|
id: id,
|
||||||
|
clock_type: Self::CLOCK_TYPE,
|
||||||
|
sof_sync: Self::SOF_SYNC,
|
||||||
|
frequency_access,
|
||||||
|
validity_access,
|
||||||
|
assoc_terminal: 0,
|
||||||
|
string,
|
||||||
|
};
|
||||||
|
Ok(cs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TerminalConfig<D: EndpointDirection> {
|
||||||
|
clock_source_id: u8,
|
||||||
|
num_channels: u8,
|
||||||
|
format: FormatType1,
|
||||||
|
terminal_type: TerminalType,
|
||||||
|
channel_config: ChannelConfig,
|
||||||
|
string: Option<StringIndex>,
|
||||||
|
_direction: PhantomData<D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, D: EndpointDirection> TerminalConfig<D> {
|
||||||
|
pub fn new(
|
||||||
|
clock_source_id: u8,
|
||||||
|
num_channels: u8,
|
||||||
|
format: FormatType1,
|
||||||
|
terminal_type: TerminalType,
|
||||||
|
channel_config: ChannelConfig,
|
||||||
|
string: Option<StringIndex>,
|
||||||
|
) -> Self {
|
||||||
|
TerminalConfig {
|
||||||
|
clock_source_id,
|
||||||
|
num_channels,
|
||||||
|
format,
|
||||||
|
terminal_type,
|
||||||
|
channel_config,
|
||||||
|
string,
|
||||||
|
_direction: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> TerminalConfig<In> {
|
||||||
|
fn get_configuration_descriptors(&self, start_id: u8) -> (InputTerminal, OutputTerminal) {
|
||||||
|
let input_terminal = InputTerminal {
|
||||||
|
id: start_id,
|
||||||
|
terminal_type: TerminalType::UsbStreaming,
|
||||||
|
assoc_terminal: start_id + 1,
|
||||||
|
clock_source: self.clock_source_id,
|
||||||
|
num_channels: self.num_channels,
|
||||||
|
channel_config: self.channel_config,
|
||||||
|
channel_names: 0, // not supported
|
||||||
|
copy_protect_control: AccessControl::NotPresent,
|
||||||
|
connector_control: AccessControl::NotPresent,
|
||||||
|
overload_control: AccessControl::NotPresent,
|
||||||
|
cluster_control: AccessControl::NotPresent,
|
||||||
|
underflow_control: AccessControl::NotPresent,
|
||||||
|
overflow_control: AccessControl::NotPresent,
|
||||||
|
phantom_power_control: AccessControl::NotPresent,
|
||||||
|
string: None,
|
||||||
|
};
|
||||||
|
let output_terminal = OutputTerminal {
|
||||||
|
id: start_id + 1,
|
||||||
|
terminal_type: self.terminal_type,
|
||||||
|
assoc_terminal: start_id,
|
||||||
|
source_id: start_id,
|
||||||
|
clock_source: self.clock_source_id,
|
||||||
|
copy_protect_control: AccessControl::NotPresent,
|
||||||
|
connector_control: AccessControl::NotPresent,
|
||||||
|
overload_control: AccessControl::NotPresent,
|
||||||
|
underflow_control: AccessControl::NotPresent,
|
||||||
|
overflow_control: AccessControl::NotPresent,
|
||||||
|
string: self.string,
|
||||||
|
};
|
||||||
|
(input_terminal, output_terminal)
|
||||||
|
}
|
||||||
|
// fn get_interface_descriptor(&self, id: InterfaceIndex) )
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TerminalConfig<Out> {
|
||||||
|
fn get_configuration_descriptors(&self, start_id: u8) -> (OutputTerminal, InputTerminal) {
|
||||||
|
let output_terminal = OutputTerminal {
|
||||||
|
id: start_id,
|
||||||
|
terminal_type: TerminalType::UsbStreaming,
|
||||||
|
assoc_terminal: start_id + 1,
|
||||||
|
source_id: start_id + 1,
|
||||||
|
clock_source: self.clock_source_id,
|
||||||
|
copy_protect_control: AccessControl::NotPresent,
|
||||||
|
connector_control: AccessControl::NotPresent,
|
||||||
|
overload_control: AccessControl::NotPresent,
|
||||||
|
underflow_control: AccessControl::NotPresent,
|
||||||
|
overflow_control: AccessControl::NotPresent,
|
||||||
|
string: self.string,
|
||||||
|
};
|
||||||
|
let input_terminal = InputTerminal {
|
||||||
|
id: start_id + 1,
|
||||||
|
terminal_type: self.terminal_type,
|
||||||
|
assoc_terminal: start_id,
|
||||||
|
clock_source: self.clock_source_id,
|
||||||
|
num_channels: self.num_channels,
|
||||||
|
channel_config: self.channel_config,
|
||||||
|
channel_names: 0,
|
||||||
|
copy_protect_control: AccessControl::NotPresent,
|
||||||
|
connector_control: AccessControl::NotPresent,
|
||||||
|
cluster_control: AccessControl::NotPresent,
|
||||||
|
overload_control: AccessControl::NotPresent,
|
||||||
|
underflow_control: AccessControl::NotPresent,
|
||||||
|
overflow_control: AccessControl::NotPresent,
|
||||||
|
phantom_power_control: AccessControl::NotPresent,
|
||||||
|
string: self.string,
|
||||||
|
};
|
||||||
|
(output_terminal, input_terminal)
|
||||||
|
}
|
||||||
|
fn write_interface_descriptors(
|
||||||
|
&self,
|
||||||
|
writer: &mut DescriptorWriter,
|
||||||
|
if_id: InterfaceNumber,
|
||||||
|
) -> usb_device::Result<()> {
|
||||||
|
writer.interface(
|
||||||
|
if_id,
|
||||||
|
AUDIO,
|
||||||
|
InterfaceSubclass::AudioStreaming as u8,
|
||||||
|
InterfaceProtocol::Version2 as u8,
|
||||||
|
)?;
|
||||||
|
writer.interface_alt(
|
||||||
|
if_id,
|
||||||
|
1,
|
||||||
|
AUDIO,
|
||||||
|
InterfaceSubclass::AudioStreaming as u8,
|
||||||
|
InterfaceProtocol::Version2 as u8,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// 1. Interface specific AS_GENERAL descriptor (4.9.2)
|
||||||
|
// 2. Format Type I descriptor
|
||||||
|
// 3. Endpoint descriptor
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration and references to the Audio Class descriptors
|
||||||
|
///
|
||||||
|
/// Supports one clock source, optionally one input terminal and optionally one output terminal.
|
||||||
|
/// An optional set of additional descriptors can be provided, but must be handled by the user.
|
||||||
|
///
|
||||||
|
/// The two Terminal descriptors will be built per their TerminalConfig
|
||||||
|
///
|
||||||
|
/// Unit IDs will be fixed as follows:
|
||||||
|
/// * Clock Source: 1
|
||||||
|
/// * USB Streaming Input: 2
|
||||||
|
/// * Output Terminal: 3
|
||||||
|
/// * USB Streaming Output: 4
|
||||||
|
/// * Input Terminal: 5
|
||||||
|
/// * User provided descriptors: 6+
|
||||||
|
///
|
||||||
|
/// A single Clock Source is always required, but a fully custom descriptor set can be built by only providing
|
||||||
|
/// the Clock Source and additional descriptors, if the Terminal descriptors are inappropriate.
|
||||||
|
///
|
||||||
|
pub struct AudioClassConfig<'a, CS: UsbAudioClockSource> {
|
||||||
|
pub device_category: FunctionCode,
|
||||||
|
pub clock: CS,
|
||||||
|
pub input_config: Option<TerminalConfig<Out>>,
|
||||||
|
pub output_config: Option<TerminalConfig<In>>,
|
||||||
|
pub additional_descriptors: Option<&'a [AudioClassDescriptor]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, CS: UsbAudioClockSource> AudioClassConfig<'a, CS> {
|
||||||
|
pub fn new(device_category: FunctionCode, clock: CS) -> Self {
|
||||||
|
Self {
|
||||||
|
device_category,
|
||||||
|
clock,
|
||||||
|
input_config: None,
|
||||||
|
output_config: None,
|
||||||
|
additional_descriptors: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn with_input_config(mut self, input_config: TerminalConfig<Out>) -> Self {
|
||||||
|
self.input_config = Some(input_config);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn with_output_terminal(mut self, output_terminal: TerminalConfig<In>) -> Self {
|
||||||
|
self.output_config = Some(output_terminal);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn with_additional_descriptors(
|
||||||
|
mut self,
|
||||||
|
additional_descriptors: &'a [AudioClassDescriptor],
|
||||||
|
) -> Self {
|
||||||
|
self.additional_descriptors = Some(additional_descriptors);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Writes the class-specific configuration descriptor set (after bDescriptortype INTERFACE)
|
||||||
|
fn get_configuration_descriptors(
|
||||||
|
&self,
|
||||||
|
writer: &mut DescriptorWriter<'_>,
|
||||||
|
) -> usb_device::Result<()> {
|
||||||
|
// CONFIGURATION DESCRIPTORS //
|
||||||
|
let mut total_length: u16 = 9; // HEADER
|
||||||
|
let clock_desc = self.clock.get_configuration_descriptor(1, None)?;
|
||||||
|
total_length += clock_desc.size() as u16;
|
||||||
|
let output_descs = match &self.output_config {
|
||||||
|
Some(config) => {
|
||||||
|
let descs = config.get_configuration_descriptors(2);
|
||||||
|
total_length += descs.0.size() as u16 + descs.1.size() as u16;
|
||||||
|
Some(descs)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let input_descs = match &self.input_config {
|
||||||
|
Some(config) => {
|
||||||
|
let descs = config.get_configuration_descriptors(4);
|
||||||
|
total_length += descs.0.size() as u16 + descs.1.size() as u16;
|
||||||
|
Some(descs)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let additional_descs = match &self.additional_descriptors {
|
||||||
|
Some(descs) => {
|
||||||
|
total_length += descs.iter().map(|desc| desc.size() as u16).sum::<u16>();
|
||||||
|
Some(descs)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let ac_header: [u8; 7] = [
|
||||||
|
ClassSpecificACInterfaceDescriptorSubtype::Header as u8,
|
||||||
|
0, // bcdADC[0]
|
||||||
|
2, // bcdADC[1]
|
||||||
|
self.device_category as u8, // bCategory
|
||||||
|
(total_length & 0xff) as u8, // wTotalLength LSB
|
||||||
|
((total_length >> 8) & 0xff) as u8, // wTotalLength MSB
|
||||||
|
0, // bmControls
|
||||||
|
];
|
||||||
|
writer.write(ClassSpecificDescriptorType::Interface as u8, &ac_header)?;
|
||||||
|
clock_desc.write_descriptor(writer)?;
|
||||||
|
if let Some((a, b)) = output_descs {
|
||||||
|
a.write_descriptor(writer)?;
|
||||||
|
b.write_descriptor(writer)?;
|
||||||
|
}
|
||||||
|
if let Some((a, b)) = input_descs {
|
||||||
|
a.write_descriptor(writer)?;
|
||||||
|
b.write_descriptor(writer)?;
|
||||||
|
}
|
||||||
|
if let Some(descs) = additional_descs {
|
||||||
|
for desc in descs.into_iter() {
|
||||||
|
desc.write_descriptor(writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// INTERFACE DESCRIPTORS //
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,136 +426,28 @@ impl From<UsbError> for Error {
|
|||||||
type Result<T> = core::result::Result<T, Error>;
|
type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
struct AudioStream<'a, B: UsbBus, D: EndpointDirection> {
|
struct AudioStream<'a, B: UsbBus, D: EndpointDirection> {
|
||||||
stream_config: StreamConfig<'a>,
|
|
||||||
interface: InterfaceNumber,
|
interface: InterfaceNumber,
|
||||||
endpoint: Endpoint<'a, B, D>,
|
endpoint: Endpoint<'a, B, D>,
|
||||||
alt_setting: u8,
|
alt_setting: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: UsbBus> AudioStream<'_, B, endpoint::In> {
|
pub struct AudioClass<'a, CS: UsbAudioClockSource> {
|
||||||
fn input_terminal_desc(&self, id: u8, clock_source: u8) -> InputTerminal {
|
|
||||||
let channel_config = ChannelConfig::default_chans(self.stream_config.channels);
|
|
||||||
InputTerminal {
|
|
||||||
id,
|
|
||||||
terminal_type: TerminalType::UsbStreaming,
|
|
||||||
assoc_terminal: 0,
|
|
||||||
clock_source,
|
|
||||||
num_channels: self.stream_config.channels,
|
|
||||||
channel_config,
|
|
||||||
channel_names: 0,
|
|
||||||
copy_protect_control: AccessControl::NotPresent,
|
|
||||||
connector_control: AccessControl::NotPresent,
|
|
||||||
overload_control: AccessControl::NotPresent,
|
|
||||||
cluster_control: AccessControl::NotPresent,
|
|
||||||
underflow_control: AccessControl::NotPresent,
|
|
||||||
overflow_control: AccessControl::NotPresent,
|
|
||||||
phantom_power_control: AccessControl::NotPresent,
|
|
||||||
string: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B: UsbBus> AudioStream<'_, B, endpoint::Out> {
|
|
||||||
fn output_terminal_desc(&self, id: u8, source_id: u8, clock_source: u8) -> OutputTerminal {
|
|
||||||
OutputTerminal {
|
|
||||||
id,
|
|
||||||
terminal_type: TerminalType::UsbStreaming,
|
|
||||||
assoc_terminal: 0,
|
|
||||||
source_id,
|
|
||||||
clock_source,
|
|
||||||
copy_protect_control: AccessControl::NotPresent,
|
|
||||||
connector_control: AccessControl::NotPresent,
|
|
||||||
overload_control: AccessControl::NotPresent,
|
|
||||||
underflow_control: AccessControl::NotPresent,
|
|
||||||
overflow_control: AccessControl::NotPresent,
|
|
||||||
string: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AudioClass<'a, B: UsbBus> {
|
|
||||||
control_iface: InterfaceNumber,
|
control_iface: InterfaceNumber,
|
||||||
input: Option<AudioStream<'a, B, In>>,
|
config: AudioClassConfig<'a, CS>,
|
||||||
output: Option<AudioStream<'a, B, Out>>,
|
|
||||||
function: FunctionCode,
|
|
||||||
clock_type: ClockType,
|
|
||||||
input_type: Option<TerminalType>,
|
|
||||||
output_type: Option<TerminalType>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: UsbBus> AudioClass<'_, B> {}
|
impl<CS: UsbAudioClockSource> AudioClass<'_, CS> {}
|
||||||
|
|
||||||
impl<B: UsbBus> UsbClass<B> for AudioClass<'_, B> {
|
impl<B: UsbBus, CS: UsbAudioClockSource> UsbClass<B> for AudioClass<'_, CS> {
|
||||||
fn get_configuration_descriptors(
|
fn get_configuration_descriptors(
|
||||||
&self,
|
&self,
|
||||||
writer: &mut DescriptorWriter,
|
writer: &mut DescriptorWriter,
|
||||||
) -> usb_device::Result<()> {
|
) -> usb_device::Result<()> {
|
||||||
// Build the necessary descriptors
|
// IN, OUT, CONTROL
|
||||||
// Clock Source - id 1
|
let n_interfaces = self.config.input_config.is_some() as u8
|
||||||
// USB Input Terminal - id 2
|
+ self.config.output_config.is_some() as u8
|
||||||
// Audio Output Terminal - id 3
|
+ 1;
|
||||||
// USB Output Terminal - id 4
|
|
||||||
// Audio Input Terminal - id 5
|
|
||||||
let clock_source = ClockSource {
|
|
||||||
id: 1,
|
|
||||||
clock_type: self.clock_type,
|
|
||||||
sof_sync: false,
|
|
||||||
frequency_access: if self.clock_type == ClockType::InternalProgrammable {
|
|
||||||
AccessControl::Programmable
|
|
||||||
} else {
|
|
||||||
AccessControl::NotPresent
|
|
||||||
},
|
|
||||||
validity_access: AccessControl::ReadOnly,
|
|
||||||
assoc_terminal: 0,
|
|
||||||
string: 0,
|
|
||||||
};
|
|
||||||
let in_terminals = match &self.input {
|
|
||||||
Some(i) => Some((
|
|
||||||
i.input_terminal_desc(2, 1),
|
|
||||||
OutputTerminal {
|
|
||||||
id: 3,
|
|
||||||
terminal_type: self.output_type.unwrap_or(TerminalType::OutUndefined),
|
|
||||||
assoc_terminal: 0,
|
|
||||||
source_id: 2,
|
|
||||||
clock_source: 1,
|
|
||||||
copy_protect_control: AccessControl::NotPresent,
|
|
||||||
connector_control: AccessControl::NotPresent,
|
|
||||||
overload_control: AccessControl::NotPresent,
|
|
||||||
underflow_control: AccessControl::NotPresent,
|
|
||||||
overflow_control: AccessControl::NotPresent,
|
|
||||||
string: 0,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let out_terminals = match &self.output {
|
|
||||||
Some(i) => Some((
|
|
||||||
i.output_terminal_desc(4, 5, 1),
|
|
||||||
InputTerminal {
|
|
||||||
id: 5,
|
|
||||||
terminal_type: self.input_type.unwrap_or(TerminalType::InUndefined),
|
|
||||||
assoc_terminal: 0,
|
|
||||||
clock_source: 1,
|
|
||||||
num_channels: i.stream_config.channels,
|
|
||||||
channel_config: ChannelConfig::default_chans(i.stream_config.channels),
|
|
||||||
channel_names: 0,
|
|
||||||
copy_protect_control: AccessControl::NotPresent,
|
|
||||||
connector_control: AccessControl::NotPresent,
|
|
||||||
overload_control: AccessControl::NotPresent,
|
|
||||||
cluster_control: AccessControl::NotPresent,
|
|
||||||
underflow_control: AccessControl::NotPresent,
|
|
||||||
overflow_control: AccessControl::NotPresent,
|
|
||||||
phantom_power_control: AccessControl::NotPresent,
|
|
||||||
string: 0,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let n_interfaces = match (&self.input, &self.output) {
|
|
||||||
(Some(_), Some(_)) => 3, // two audio, one control
|
|
||||||
(Some(_), None) | (None, Some(_)) => 2, // one audio, one control
|
|
||||||
(None, None) => 1, // no audio (?!), one control
|
|
||||||
};
|
|
||||||
writer.iad(
|
writer.iad(
|
||||||
self.control_iface,
|
self.control_iface,
|
||||||
n_interfaces,
|
n_interfaces,
|
||||||
@@ -271,14 +463,7 @@ impl<B: UsbBus> UsbClass<B> for AudioClass<'_, B> {
|
|||||||
InterfaceProtocol::Version2 as u8,
|
InterfaceProtocol::Version2 as u8,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(terminals) = in_terminals {
|
self.config.get_configuration_descriptors(writer)?;
|
||||||
terminals.0.write_descriptor(writer)?;
|
|
||||||
terminals.1.write_descriptor(writer)?;
|
|
||||||
}
|
|
||||||
if let Some(terminals) = out_terminals {
|
|
||||||
terminals.0.write_descriptor(writer)?;
|
|
||||||
terminals.1.write_descriptor(writer)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user