diff --git a/src/constants.rs b/src/constants.rs index 32e9f9e..cb27e78 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -488,3 +488,46 @@ pub enum TerminalType { Ext1394DaStream = 0x0606, 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, +} diff --git a/src/descriptors.rs b/src/descriptors.rs index 14dad87..ce675fd 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -6,7 +6,7 @@ use crate::constants::*; use byteorder_embedded_io::{LittleEndian, WriteBytesExt}; use embedded_io::ErrorType; use modular_bitfield::prelude::*; -use usb_device::{UsbError, descriptor::DescriptorWriter}; +use usb_device::{UsbError, bus::StringIndex, descriptor::DescriptorWriter}; #[derive(Debug)] pub struct DescriptorWriterError { @@ -218,7 +218,7 @@ pub trait Descriptor { fn write(&self, writer: &mut T) -> Result<(), T::Error>; } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct ClockSource { pub id: u8, pub clock_type: ClockType, @@ -226,7 +226,7 @@ pub struct ClockSource { pub frequency_access: AccessControl, pub validity_access: AccessControl, pub assoc_terminal: u8, - pub string: u8, + pub string: Option, } impl ClockSource { @@ -243,7 +243,7 @@ impl ClockSource { writer.write_u8(self.bm_attributes())?; // bmAttributes writer.write_u8(self.bm_controls())?; // bmControls 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(()) } @@ -271,7 +271,7 @@ impl Descriptor for ClockSource { } } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct InputTerminal { pub id: u8, pub terminal_type: TerminalType, @@ -287,7 +287,7 @@ pub struct InputTerminal { pub underflow_control: AccessControl, pub overflow_control: AccessControl, pub phantom_power_control: AccessControl, - pub string: u8, + pub string: Option, } impl InputTerminal { @@ -311,7 +311,7 @@ impl InputTerminal { | ((self.overflow_control as u8) << 2) | ((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(()) } } @@ -337,7 +337,7 @@ impl Descriptor for InputTerminal { } } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct OutputTerminal { pub id: u8, pub terminal_type: TerminalType, @@ -349,7 +349,7 @@ pub struct OutputTerminal { pub overload_control: AccessControl, pub underflow_control: AccessControl, pub overflow_control: AccessControl, - pub string: u8, + pub string: Option, } impl OutputTerminal { @@ -367,7 +367,7 @@ impl OutputTerminal { | ((self.underflow_control as u8) << 6), )?; 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(()) } } @@ -393,7 +393,7 @@ impl Descriptor for OutputTerminal { } } -#[derive(Clone, Debug)] +#[derive(Clone)] pub enum Terminal { Input(InputTerminal), Output(OutputTerminal), @@ -803,7 +803,7 @@ impl Descriptor for ExtensionUnit { // } // } -#[derive(Clone, Debug)] +#[derive(Clone)] /// Enum covering basic sized audio class descriptors for building the Class-Specific /// AC Interface descriptor. Dynamically sized descriptors are not supported yet. pub enum AudioClassDescriptor { @@ -830,6 +830,17 @@ impl AudioClassDescriptor { 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 for AudioClassDescriptor { @@ -902,6 +913,116 @@ impl AudioClassInterfaceDescriptor { } } +/// 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(&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(&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, +} + +impl AudioStreamingInterface { + fn bm_controls(&self) -> u8 { + self.active_alt_setting as u8 | ((self.valid_alt_settings as u8) << 2) + } + fn write_payload(&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::(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(&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)] extern crate std; #[cfg(test)] @@ -911,6 +1032,7 @@ mod tests { use std::{print, println}; #[test] fn test_clock_source() { + let string = Some(unsafe { core::mem::transmute(10u8) }); let cs = ClockSource { id: 8, clock_type: ClockType::InternalFixed, @@ -918,7 +1040,7 @@ mod tests { frequency_access: AccessControl::ReadOnly, validity_access: AccessControl::ReadOnly, assoc_terminal: 6, - string: 10, + string, }; let mut buf = [0u8; ClockSource::MAX_SIZE]; let mut cur = Cursor::new(&mut buf[..]); @@ -1014,7 +1136,7 @@ mod tests { underflow_control: AccessControl::ReadOnly, overflow_control: AccessControl::ReadOnly, phantom_power_control: AccessControl::NotPresent, - string: 20, + string: Some(unsafe { core::mem::transmute(20u8) }), }; let mut buf = [0u8; InputTerminal::MAX_SIZE]; let mut cur = Cursor::new(&mut buf[..]); @@ -1058,7 +1180,7 @@ mod tests { overload_control: AccessControl::ReadOnly, underflow_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 cur = Cursor::new(&mut buf[..]); @@ -1334,7 +1456,7 @@ mod tests { frequency_access: AccessControl::NotPresent, validity_access: AccessControl::NotPresent, assoc_terminal: 0, - string: 0, + string: None, } .into(), InputTerminal { @@ -1352,7 +1474,7 @@ mod tests { underflow_control: AccessControl::NotPresent, overflow_control: AccessControl::NotPresent, phantom_power_control: AccessControl::NotPresent, - string: 0, + string: None, } .into(), OutputTerminal { @@ -1366,7 +1488,7 @@ mod tests { overload_control: AccessControl::NotPresent, underflow_control: AccessControl::NotPresent, overflow_control: AccessControl::NotPresent, - string: 0, + string: None, } .into(), OutputTerminal { @@ -1380,7 +1502,7 @@ mod tests { overload_control: AccessControl::NotPresent, underflow_control: AccessControl::NotPresent, overflow_control: AccessControl::NotPresent, - string: 0, + string: None, } .into(), InputTerminal { @@ -1398,7 +1520,7 @@ mod tests { underflow_control: AccessControl::NotPresent, overflow_control: AccessControl::NotPresent, phantom_power_control: AccessControl::NotPresent, - string: 0, + string: None, } .into(), ]; @@ -1419,4 +1541,70 @@ mod tests { println!(); 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 + ] + ); + } } diff --git a/src/lib.rs b/src/lib.rs index ab025dd..a9a883b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,8 @@ mod constants; mod cursor; mod descriptors; +use core::marker::PhantomData; + use constants::*; 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::{UsbDirection, class_prelude::*}; -#[derive(Clone, Copy, Debug)] -pub enum Format { - /// Signed, 16 bits per subframe, little endian - S16le, - /// Signed, 24 bits per subframe, little endian - S24le, - /// Signed, 32 bits per subframe, little endian - S32le, +mod sealed { + pub trait Sealed {} } -/// Sampling rates that shall be supported by an steaming endpoint -#[derive(Debug)] -pub enum Rates<'a> { - /// 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]), +pub enum UsbAudioClassError { + NotImplemented, + Other, } -#[derive(Debug)] -pub struct StreamConfig<'a> { - format: Format, - 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> { - 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, - }) +impl From for UsbAudioClassError { + fn from(_: T) -> Self { + UsbAudioClassError::Other } +} - /// Create a stream configuration with a continuous range of supported - /// 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_continuous( - format: Format, - channels: u8, - min_rate: u32, - max_rate: u32, - terminal_type: TerminalType, - ) -> Result> { - if min_rate >= max_rate { - return Err(Error::InvalidValue); +pub trait RangeType: sealed::Sealed {} +impl sealed::Sealed for i8 {} +impl sealed::Sealed for i16 {} +impl sealed::Sealed for i32 {} +impl sealed::Sealed for u8 {} +impl sealed::Sealed for u16 {} +impl sealed::Sealed for u32 {} + +impl RangeType for i8 {} +impl RangeType for i16 {} +impl RangeType for i32 {} +impl RangeType for u8 {} +impl RangeType for u16 {} +impl RangeType for u32 {} + +pub struct RangeEntry { + min: T, + max: T, + res: T, +} + +impl RangeEntry { + 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 { + 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 { + 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 { + 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 - fn ep_size(format: Format, channels: u8, max_rate: u32) -> Result { - let octets_per_frame = channels as u32 - * match format { - Format::S16le => 2, - Format::S24le => 3, - Format::S32le => 4, - }; - let ep_size = octets_per_frame * max_rate / 1000; - // if ep_size > MAX_ISO_EP_SIZE { - // return Err(Error::BandwidthExceeded); - // } - Ok(ep_size as u16) + /// Called when the hosts makes a RANGE request for the clock source. Returns a slice of possible sample rates. + /// + /// Must be implemented if the clock source returns programmable get_frequency_access + /// + /// Rates must meet the invariants in the specification: + /// * The subranges must be ordered in ascendingorder + /// * Individual subranges cannot overlap + /// * If a subrange consists of only a single value, the corresponding triplet must contain that value for both + /// its MIN and MAX subattribute and the RES subattribute must be set to zero + /// + /// ref: USB Audio Class Specification 2.0 5.2.1 & 5.2.3.3 + fn get_rates(&self) -> core::result::Result<&[RangeEntry], UsbAudioClassError> { + 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, + ) -> usb_device::Result { + 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 { + clock_source_id: u8, + num_channels: u8, + format: FormatType1, + terminal_type: TerminalType, + channel_config: ChannelConfig, + string: Option, + _direction: PhantomData, +} + +impl<'a, D: EndpointDirection> TerminalConfig { + pub fn new( + clock_source_id: u8, + num_channels: u8, + format: FormatType1, + terminal_type: TerminalType, + channel_config: ChannelConfig, + string: Option, + ) -> Self { + TerminalConfig { + clock_source_id, + num_channels, + format, + terminal_type, + channel_config, + string, + _direction: PhantomData, + } + } +} +impl<'a> TerminalConfig { + 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 { + 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>, + pub output_config: Option>, + 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) -> Self { + self.input_config = Some(input_config); + self + } + pub fn with_output_terminal(mut self, output_terminal: TerminalConfig) -> 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::(); + 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 for Error { type Result = core::result::Result; struct AudioStream<'a, B: UsbBus, D: EndpointDirection> { - stream_config: StreamConfig<'a>, interface: InterfaceNumber, endpoint: Endpoint<'a, B, D>, alt_setting: u8, } -impl AudioStream<'_, B, endpoint::In> { - 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 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> { +pub struct AudioClass<'a, CS: UsbAudioClockSource> { control_iface: InterfaceNumber, - input: Option>, - output: Option>, - function: FunctionCode, - clock_type: ClockType, - input_type: Option, - output_type: Option, + config: AudioClassConfig<'a, CS>, } -impl AudioClass<'_, B> {} +impl AudioClass<'_, CS> {} -impl UsbClass for AudioClass<'_, B> { +impl UsbClass for AudioClass<'_, CS> { fn get_configuration_descriptors( &self, writer: &mut DescriptorWriter, ) -> usb_device::Result<()> { - // Build the necessary descriptors - // Clock Source - id 1 - // USB Input Terminal - id 2 - // Audio Output Terminal - id 3 - // 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 - }; + // IN, OUT, CONTROL + let n_interfaces = self.config.input_config.is_some() as u8 + + self.config.output_config.is_some() as u8 + + 1; + writer.iad( self.control_iface, n_interfaces, @@ -271,14 +463,7 @@ impl UsbClass for AudioClass<'_, B> { InterfaceProtocol::Version2 as u8, )?; - if let Some(terminals) = in_terminals { - 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)?; - } + self.config.get_configuration_descriptors(writer)?; Ok(()) }