refactor struct names, combine clock and audio handlers, builder pattern
This commit is contained in:
+168
-97
@@ -8,7 +8,6 @@ mod log;
|
|||||||
|
|
||||||
use core::cmp::Ordering;
|
use core::cmp::Ordering;
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use core::sync::atomic::AtomicUsize;
|
|
||||||
|
|
||||||
use byteorder_embedded_io::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder_embedded_io::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use constants::*;
|
use constants::*;
|
||||||
@@ -21,8 +20,6 @@ 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::*};
|
||||||
|
|
||||||
pub use constants::USB_CLASS_AUDIO;
|
|
||||||
|
|
||||||
#[cfg(feature = "defmt")]
|
#[cfg(feature = "defmt")]
|
||||||
use defmt;
|
use defmt;
|
||||||
|
|
||||||
@@ -66,6 +63,9 @@ impl RangeType for u8 {}
|
|||||||
impl RangeType for u16 {}
|
impl RangeType for u16 {}
|
||||||
impl RangeType for u32 {}
|
impl RangeType for u32 {}
|
||||||
|
|
||||||
|
/// Represent a range request/response.
|
||||||
|
///
|
||||||
|
/// ref: UAC2 5.2.2
|
||||||
#[derive(PartialEq, Eq, Ord)]
|
#[derive(PartialEq, Eq, Ord)]
|
||||||
pub struct RangeEntry<T: RangeType> {
|
pub struct RangeEntry<T: RangeType> {
|
||||||
pub min: T,
|
pub min: T,
|
||||||
@@ -85,10 +85,7 @@ impl<T: RangeType> RangeEntry<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<W: embedded_io::Write>(
|
fn write<W: embedded_io::Write>(&self, mut buf: W) -> core::result::Result<usize, W::Error> {
|
||||||
&self,
|
|
||||||
mut buf: W,
|
|
||||||
) -> core::result::Result<usize, W::Error> {
|
|
||||||
buf.write_all(self.min.to_le_bytes().as_ref())?;
|
buf.write_all(self.min.to_le_bytes().as_ref())?;
|
||||||
buf.write_all(self.max.to_le_bytes().as_ref())?;
|
buf.write_all(self.max.to_le_bytes().as_ref())?;
|
||||||
buf.write_all(self.res.to_le_bytes().as_ref())?;
|
buf.write_all(self.res.to_le_bytes().as_ref())?;
|
||||||
@@ -96,52 +93,42 @@ impl<T: RangeType> RangeEntry<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The spec guarantees that ranges do not overlap, so compare by min is correct.
|
// The spec guarantees that ranges do not overlap, so compare by min is correct.
|
||||||
impl<T: RangeType + PartialOrd> PartialOrd for RangeEntry<T> {
|
impl<T: RangeType + PartialOrd> PartialOrd for RangeEntry<T> {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
self.min.partial_cmp(&other.min)
|
self.min.partial_cmp(&other.min)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fixed point 10.14, packed to the least significant 3-bytes of a 4-byte USB feedback endpoint response
|
/// Represent Isochronous feedback as integer and fractional parts, internally as 16.16.
|
||||||
|
///
|
||||||
|
/// Provides conversions to and from the different USB formats.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct UsbIsochronousFeedback {
|
pub struct UsbIsochronousFeedback(u32);
|
||||||
pub int: u16,
|
|
||||||
pub frac: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UsbIsochronousFeedback {
|
impl UsbIsochronousFeedback {
|
||||||
|
pub fn new(v: u32) -> Self {
|
||||||
|
Self(v)
|
||||||
|
}
|
||||||
/// Accepts all u16 values, saturating the output depending on format
|
/// Accepts all u16 values, saturating the output depending on format
|
||||||
pub fn new_frac(int: u16, frac: u16) -> Self {
|
pub fn new_frac(int: u16, frac: u16) -> Self {
|
||||||
Self { int, frac }
|
Self(((int as u32) << 16) | frac as u32)
|
||||||
}
|
}
|
||||||
|
/// Convert float to fixed point
|
||||||
pub fn new_float(rate: f32) -> Self {
|
pub fn new_float(rate: f32) -> Self {
|
||||||
let fb = (rate * 65536.0 + 0.5) as u32;
|
let fb = (rate * 65536.0 + 0.5) as u32;
|
||||||
Self::new(fb)
|
Self(fb)
|
||||||
}
|
}
|
||||||
/// Assumed 16.16, not either of the USB formats
|
pub fn parts(&self) -> (u16, u16) {
|
||||||
pub fn new(value: u32) -> Self {
|
((self.0 >> 16) as u16, (self.0 & 0xffff) as u16)
|
||||||
Self {
|
|
||||||
int: (value >> 16) as u16,
|
|
||||||
frac: (value & 0xffff) as u16,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// Serialize into a u32 in 16.16 representation for USB HS
|
/// Serialize into a u32 in 16.16 representation for USB HS
|
||||||
pub fn to_u32_12_13(&self) -> u32 {
|
pub fn to_u32_12_13(&self) -> u32 {
|
||||||
let int = (self.int as u32) << 16;
|
self.0
|
||||||
// ostensibly 13 bits, so should require << 3, but USB allows us to use
|
|
||||||
// these bits for 'extra precision'. So we may as well just treat it as
|
|
||||||
// 16.16. The application can << 3 if it wants to for some reason.
|
|
||||||
let frac = (self.frac as u32) & 0xffff;
|
|
||||||
|
|
||||||
int | frac
|
|
||||||
}
|
}
|
||||||
/// Serialize into a u32 in 10.14 representation for USB FS (take the 3 LSB)
|
/// Serialize into a u32 in 10.14 representation for USB FS
|
||||||
pub fn to_u32_10_14(&self) -> u32 {
|
pub fn to_u32_10_14(&self) -> u32 {
|
||||||
let int = (self.int as u32) << 14;
|
(self.0 + 2) >> 2
|
||||||
let frac = (self.frac as u32) & 0x3fff;
|
|
||||||
|
|
||||||
int | frac
|
|
||||||
}
|
}
|
||||||
/// Serialize into 16.16 little endian byte array for USB HS
|
/// Serialize into 16.16 little endian byte array for USB HS
|
||||||
pub fn to_bytes_12_13(&self) -> [u8; 4] {
|
pub fn to_bytes_12_13(&self) -> [u8; 4] {
|
||||||
@@ -156,16 +143,11 @@ impl UsbIsochronousFeedback {
|
|||||||
|
|
||||||
/// A trait for implementing USB Audio Class 2 devices
|
/// A trait for implementing USB Audio Class 2 devices
|
||||||
///
|
///
|
||||||
/// Contains callback methods which will be called by the class driver. All
|
/// Contains callback methods which will be called by the class driver.
|
||||||
/// callbacks are optional, which may be useful for a tight-loop polling implementation
|
pub trait AudioHandler<'a, B: UsbBus> {
|
||||||
/// but most implementations will want to implement at least `audio_data_rx`.
|
|
||||||
///
|
|
||||||
/// Unimplemented callbacks should return `Ok(())` if a result is required.
|
|
||||||
|
|
||||||
pub trait UsbAudioClass<'a, B: UsbBus> {
|
|
||||||
/// Called when audio data is received from the host. `ep` is ready for
|
/// Called when audio data is received from the host. `ep` is ready for
|
||||||
/// `ep.read()`.
|
/// `ep.read()`.
|
||||||
fn audio_data_rx(&mut self, ep: &Endpoint<'a, B, endpoint::Out>) {}
|
fn audio_data_rx(&mut self, ep: &Endpoint<'a, B, endpoint::Out>);
|
||||||
|
|
||||||
/// Called when it's time to send an isochronous feedback update. Should
|
/// Called when it's time to send an isochronous feedback update. Should
|
||||||
/// return the correct feedback payload. Feedback always runs at 1ms (in
|
/// return the correct feedback payload. Feedback always runs at 1ms (in
|
||||||
@@ -173,14 +155,15 @@ pub trait UsbAudioClass<'a, B: UsbBus> {
|
|||||||
///
|
///
|
||||||
/// Required for isochronous asynchronous mode to work properly. If None is
|
/// Required for isochronous asynchronous mode to work properly. If None is
|
||||||
/// returned, no IN packet will be emitted at feedback time.
|
/// returned, no IN packet will be emitted at feedback time.
|
||||||
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
|
fn feedback(&mut self, nominal: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback>;
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when the alternate setting of `terminal`'s interface is changed,
|
/// Called when the alternate setting of `terminal`'s interface is changed,
|
||||||
/// before the `AudioStream` is updated. Currently not very useful since we
|
/// before the `AudioStream` is updated. This is how the host signals start/stop
|
||||||
/// don't implement alternate settings.
|
/// of audio streaming. We do not expect audio frames when alt setting is 0.
|
||||||
fn alternate_setting_changed(&mut self, terminal: UsbDirection, alt_setting: u8) {}
|
///
|
||||||
|
/// The implementation will also call `alternate_setting_changed(_, 0)` when
|
||||||
|
/// the host disconnects / resets.
|
||||||
|
fn alternate_setting_changed(&mut self, terminal: UsbDirection, alt_setting: u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for implementing Sampling Frequency Control for USB Audio Clock Sources
|
/// A trait for implementing Sampling Frequency Control for USB Audio Clock Sources
|
||||||
@@ -191,7 +174,7 @@ pub trait UsbAudioClass<'a, B: UsbBus> {
|
|||||||
/// Unimplemented callbacks should return `Err(UsbAudioClassError::NotImplemented)`. Other
|
/// Unimplemented callbacks should return `Err(UsbAudioClassError::NotImplemented)`. Other
|
||||||
/// errors will panic (the underlying callbacks are not fallible). If you need to handle errors,
|
/// 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.
|
/// you should use the callback to infalliably signal another task.
|
||||||
pub trait UsbAudioClockImpl {
|
pub trait ClockSource {
|
||||||
const CLOCK_TYPE: ClockType;
|
const CLOCK_TYPE: ClockType;
|
||||||
const SOF_SYNC: bool;
|
const SOF_SYNC: bool;
|
||||||
/// Called when the host or class needs the current sample rate. Returns the
|
/// Called when the host or class needs the current sample rate. Returns the
|
||||||
@@ -200,7 +183,7 @@ pub trait UsbAudioClockImpl {
|
|||||||
///
|
///
|
||||||
/// Should never return 0 as it may be used in divides in the feedback loop
|
/// Should never return 0 as it may be used in divides in the feedback loop
|
||||||
/// and that would cause a hard fault.
|
/// and that would cause a hard fault.
|
||||||
fn get_sample_rate(&self) -> u32;
|
fn sample_rate(&self) -> u32;
|
||||||
/// Called when the host requests to set the sample rate. Not necessarily called at all startups,
|
/// Called when the host requests to set the sample rate. Not necessarily called at all startups,
|
||||||
/// so alt_setting should start/stop the clock. Not required for 'fixed' clocks.
|
/// so alt_setting should start/stop the clock. Not required for 'fixed' clocks.
|
||||||
fn set_sample_rate(
|
fn set_sample_rate(
|
||||||
@@ -211,14 +194,14 @@ pub trait UsbAudioClockImpl {
|
|||||||
}
|
}
|
||||||
/// Called when the host requests to get the clock validity. Returns `true`
|
/// Called when the host requests to get the clock validity. Returns `true`
|
||||||
/// if the clock is stable and on frequency.
|
/// if the clock is stable and on frequency.
|
||||||
fn get_clock_validity(&self) -> core::result::Result<bool, UsbAudioClassError> {
|
fn clock_validity(&self) -> core::result::Result<bool, UsbAudioClassError> {
|
||||||
Err(UsbAudioClassError::NotImplemented)
|
Err(UsbAudioClassError::NotImplemented)
|
||||||
}
|
}
|
||||||
/// Called during descriptor construction to describe if the clock validity can be read (write is not valid).
|
/// 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.
|
/// 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> {
|
fn clock_validity_access(&self) -> core::result::Result<bool, UsbAudioClassError> {
|
||||||
match self.get_clock_validity() {
|
match self.clock_validity() {
|
||||||
Ok(_) => Ok(true),
|
Ok(_) => Ok(true),
|
||||||
Err(UsbAudioClassError::NotImplemented) => Ok(false),
|
Err(UsbAudioClassError::NotImplemented) => Ok(false),
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
@@ -236,35 +219,29 @@ pub trait UsbAudioClockImpl {
|
|||||||
/// its MIN and MAX subattribute and the RES subattribute must be set to zero
|
/// 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
|
/// ref: USB Audio Class Specification 2.0 5.2.1 & 5.2.3.3
|
||||||
fn get_rates(&self) -> core::result::Result<&[RangeEntry<u32>], UsbAudioClassError>;
|
fn sample_rates(&self) -> core::result::Result<&[RangeEntry<u32>], UsbAudioClassError>;
|
||||||
|
|
||||||
/// Called when the audio device's AltSetting is changed. Usually 0 signals shutdown of the
|
/// Build the ClockSource descriptor. It is not intended to override this
|
||||||
/// streaming audio and 1 signals start of streaming. This should be used to start the clock
|
/// method, but provided so you can.
|
||||||
/// (and stop it if desired). If unimplemented, does nothing - keep the clock running at all times.
|
|
||||||
fn alt_setting(&mut self, alt_setting: u8) -> core::result::Result<(), UsbAudioClassError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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,
|
/// Assumes access control based on clock type. Internal fixed/variable are read only,
|
||||||
/// external and internal programmable are programmable.
|
/// external and internal programmable are programmable.
|
||||||
fn get_configuration_descriptor(
|
fn clock_configuration_descriptor(
|
||||||
&self,
|
&self,
|
||||||
id: u8,
|
id: u8,
|
||||||
string: Option<StringIndex>,
|
string: Option<StringIndex>,
|
||||||
) -> usb_device::Result<ClockSource> {
|
) -> usb_device::Result<descriptors::ClockSource> {
|
||||||
let frequency_access = match Self::CLOCK_TYPE {
|
let frequency_access = match Self::CLOCK_TYPE {
|
||||||
ClockType::InternalFixed | ClockType::InternalVariable => AccessControl::ReadOnly,
|
ClockType::InternalFixed | ClockType::InternalVariable => AccessControl::ReadOnly,
|
||||||
ClockType::External | ClockType::InternalProgrammable => AccessControl::Programmable,
|
ClockType::External | ClockType::InternalProgrammable => AccessControl::Programmable,
|
||||||
};
|
};
|
||||||
let validity_access = match self.get_validity_access() {
|
let validity_access = match self.clock_validity_access() {
|
||||||
Ok(true) => AccessControl::ReadOnly,
|
Ok(true) => AccessControl::ReadOnly,
|
||||||
Ok(false) | Err(UsbAudioClassError::NotImplemented) => AccessControl::NotPresent,
|
Ok(false) | Err(UsbAudioClassError::NotImplemented) => AccessControl::NotPresent,
|
||||||
_ => return Err(UsbError::Unsupported),
|
_ => return Err(UsbError::Unsupported),
|
||||||
};
|
};
|
||||||
|
|
||||||
let cs = ClockSource {
|
let cs = descriptors::ClockSource {
|
||||||
id: id,
|
id: id,
|
||||||
clock_type: Self::CLOCK_TYPE,
|
clock_type: Self::CLOCK_TYPE,
|
||||||
sof_sync: Self::SOF_SYNC,
|
sof_sync: Self::SOF_SYNC,
|
||||||
@@ -282,6 +259,102 @@ trait TerminalConfigurationDescriptors {
|
|||||||
fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal);
|
fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TerminalConfigBuilder<D: EndpointDirection> {
|
||||||
|
base_id: Option<u8>,
|
||||||
|
clock_source_id: Option<u8>,
|
||||||
|
num_channels: Option<u8>,
|
||||||
|
format: Option<FormatType1>,
|
||||||
|
terminal_type: Option<TerminalType>,
|
||||||
|
channel_config: Option<ChannelConfig>,
|
||||||
|
sync_type: Option<IsochronousSynchronizationType>,
|
||||||
|
lock_delay: Option<LockDelay>,
|
||||||
|
string: Option<StringIndex>,
|
||||||
|
_direction: PhantomData<D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global builder initialization defaults
|
||||||
|
impl<D: EndpointDirection> TerminalConfigBuilder<D> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
base_id: None,
|
||||||
|
clock_source_id: None,
|
||||||
|
num_channels: None,
|
||||||
|
format: None,
|
||||||
|
terminal_type: None,
|
||||||
|
channel_config: None,
|
||||||
|
sync_type: None,
|
||||||
|
lock_delay: None,
|
||||||
|
string: None,
|
||||||
|
_direction: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base_id(mut self, id: u8) -> Self {
|
||||||
|
self.base_id = Some(id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clock_source_id(mut self, id: u8) -> Self {
|
||||||
|
self.clock_source_id = Some(id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_channels(mut self, channels: u8) -> Self {
|
||||||
|
self.num_channels = Some(channels);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(mut self, format: FormatType1) -> Self {
|
||||||
|
self.format = Some(format);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn terminal_type(mut self, t: TerminalType) -> Self {
|
||||||
|
self.terminal_type = Some(t);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channel_config(mut self, config: ChannelConfig) -> Self {
|
||||||
|
self.channel_config = Some(config);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync_type(mut self, sync: IsochronousSynchronizationType) -> Self {
|
||||||
|
self.sync_type = Some(sync);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock_delay(mut self, delay: LockDelay) -> Self {
|
||||||
|
self.lock_delay = Some(delay);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn string(mut self, index: StringIndex) -> Self {
|
||||||
|
self.string = Some(index);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> TerminalConfig<D> {
|
||||||
|
TerminalConfig {
|
||||||
|
base_id: self.base_id.expect("base_id is required"),
|
||||||
|
clock_source_id: self.clock_source_id.unwrap_or(1),
|
||||||
|
num_channels: self.num_channels.unwrap_or(2),
|
||||||
|
format: self.format.unwrap_or(FormatType1 {
|
||||||
|
bytes_per_sample: 4,
|
||||||
|
bit_resolution: 32,
|
||||||
|
}),
|
||||||
|
terminal_type: self.terminal_type.unwrap_or(TerminalType::ExtUndefined),
|
||||||
|
channel_config: self
|
||||||
|
.channel_config
|
||||||
|
.unwrap_or(ChannelConfig::default_chans(self.num_channels.unwrap_or(2))),
|
||||||
|
sync_type: IsochronousSynchronizationType::Asynchronous,
|
||||||
|
lock_delay: LockDelay::Undefined(0),
|
||||||
|
string: self.string, // Implicitly handles None or Option mapping
|
||||||
|
_direction: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TerminalConfig<D: EndpointDirection> {
|
pub struct TerminalConfig<D: EndpointDirection> {
|
||||||
/// USB terminal in the D direction will have this id, audio terminal will have this id + 1
|
/// USB terminal in the D direction will have this id, audio terminal will have this id + 1
|
||||||
base_id: u8,
|
base_id: u8,
|
||||||
@@ -296,9 +369,8 @@ pub struct TerminalConfig<D: EndpointDirection> {
|
|||||||
_direction: PhantomData<D>,
|
_direction: PhantomData<D>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: builder pattern
|
|
||||||
impl<D: EndpointDirection> TerminalConfig<D> {
|
impl<D: EndpointDirection> TerminalConfig<D> {
|
||||||
pub fn new(
|
fn new(
|
||||||
base_id: u8,
|
base_id: u8,
|
||||||
clock_source_id: u8,
|
clock_source_id: u8,
|
||||||
num_channels: u8,
|
num_channels: u8,
|
||||||
@@ -326,6 +398,10 @@ impl<D: EndpointDirection> TerminalConfig<D> {
|
|||||||
pub fn bytes_per_frame(&self) -> u32 {
|
pub fn bytes_per_frame(&self) -> u32 {
|
||||||
self.format.bytes_per_sample as u32 * self.num_channels as u32
|
self.format.bytes_per_sample as u32 * self.num_channels as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn builder() -> TerminalConfigBuilder<D> {
|
||||||
|
TerminalConfigBuilder::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<'a> TerminalConfigurationDescriptors for TerminalConfig<Out> {
|
impl<'a> TerminalConfigurationDescriptors for TerminalConfig<Out> {
|
||||||
fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) {
|
fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) {
|
||||||
@@ -426,10 +502,9 @@ pub enum UsbSpeed {
|
|||||||
/// A single Clock Source is always required, but a fully custom descriptor set can be built by only providing
|
/// 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.
|
/// the Clock Source and additional descriptors, if the Terminal descriptors are inappropriate.
|
||||||
///
|
///
|
||||||
pub struct AudioClassConfig<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> {
|
pub struct UsbAudioClassConfig<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> {
|
||||||
pub speed: UsbSpeed,
|
pub speed: UsbSpeed,
|
||||||
pub device_category: FunctionCode,
|
pub device_category: FunctionCode,
|
||||||
pub clock_impl: &'a mut CS,
|
|
||||||
pub audio_impl: &'a mut AU,
|
pub audio_impl: &'a mut AU,
|
||||||
pub input_config: Option<TerminalConfig<In>>,
|
pub input_config: Option<TerminalConfig<In>>,
|
||||||
pub output_config: Option<TerminalConfig<Out>>,
|
pub output_config: Option<TerminalConfig<Out>>,
|
||||||
@@ -437,19 +512,11 @@ pub struct AudioClassConfig<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioCl
|
|||||||
_bus: PhantomData<B>,
|
_bus: PhantomData<B>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
|
impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClassConfig<'a, B, AU> {
|
||||||
AudioClassConfig<'a, B, CS, AU>
|
pub fn new(speed: UsbSpeed, device_category: FunctionCode, audio_impl: &'a mut AU) -> Self {
|
||||||
{
|
|
||||||
pub fn new(
|
|
||||||
speed: UsbSpeed,
|
|
||||||
device_category: FunctionCode,
|
|
||||||
clock_impl: &'a mut CS,
|
|
||||||
audio_impl: &'a mut AU,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
speed,
|
speed,
|
||||||
device_category,
|
device_category,
|
||||||
clock_impl,
|
|
||||||
audio_impl,
|
audio_impl,
|
||||||
input_config: None,
|
input_config: None,
|
||||||
output_config: None,
|
output_config: None,
|
||||||
@@ -474,7 +541,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate the various USB IDs, and build the class implementation
|
/// Allocate the various USB IDs, and build the class implementation
|
||||||
pub fn build(self, alloc: &'a UsbBusAllocator<B>) -> Result<AudioClass<'a, B, CS, AU>> {
|
pub fn build(self, alloc: &'a UsbBusAllocator<B>) -> Result<UsbAudioClass<'a, B, AU>> {
|
||||||
let speed = self.speed;
|
let speed = self.speed;
|
||||||
let (interval, fb_interval, audio_rate) = match speed {
|
let (interval, fb_interval, audio_rate) = match speed {
|
||||||
UsbSpeed::Full => (1, 1, 1000),
|
UsbSpeed::Full => (1, 1, 1000),
|
||||||
@@ -482,8 +549,8 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
|
|||||||
UsbSpeed::Low => return Err(Error::InvalidSpeed),
|
UsbSpeed::Low => return Err(Error::InvalidSpeed),
|
||||||
};
|
};
|
||||||
let max_rate = self
|
let max_rate = self
|
||||||
.clock_impl
|
.audio_impl
|
||||||
.get_rates()
|
.sample_rates()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.max()
|
.max()
|
||||||
@@ -492,12 +559,11 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
|
|||||||
let control_iface = alloc.interface();
|
let control_iface = alloc.interface();
|
||||||
|
|
||||||
let nominal_fb = UsbIsochronousFeedback::new_float(
|
let nominal_fb = UsbIsochronousFeedback::new_float(
|
||||||
self.clock_impl.get_sample_rate().to_f32().unwrap() / audio_rate.to_f32().unwrap(),
|
self.audio_impl.sample_rate().to_f32().unwrap() / audio_rate.to_f32().unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut ac = AudioClass {
|
let mut ac = UsbAudioClass {
|
||||||
control_iface,
|
control_iface,
|
||||||
clock_impl: self.clock_impl,
|
|
||||||
audio_impl: self.audio_impl,
|
audio_impl: self.audio_impl,
|
||||||
output: None,
|
output: None,
|
||||||
input: None,
|
input: None,
|
||||||
@@ -663,9 +729,8 @@ impl<'a, B: UsbBus, D: EndpointDirection> AudioStream<'a, B, D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AudioClass<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> {
|
pub struct UsbAudioClass<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> {
|
||||||
control_iface: InterfaceNumber,
|
control_iface: InterfaceNumber,
|
||||||
clock_impl: &'a mut CS,
|
|
||||||
audio_impl: &'a mut AU,
|
audio_impl: &'a mut AU,
|
||||||
output: Option<AudioStream<'a, B, Out>>,
|
output: Option<AudioStream<'a, B, Out>>,
|
||||||
input: Option<AudioStream<'a, B, In>>,
|
input: Option<AudioStream<'a, B, In>>,
|
||||||
@@ -682,8 +747,8 @@ pub struct AudioClass<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a
|
|||||||
audio_rate: u32, // audio packet rate in hz
|
audio_rate: u32, // audio packet rate in hz
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
|
impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbClass<B>
|
||||||
for AudioClass<'a, B, CS, AU>
|
for UsbAudioClass<'a, B, AU>
|
||||||
{
|
{
|
||||||
fn get_configuration_descriptors(
|
fn get_configuration_descriptors(
|
||||||
&self,
|
&self,
|
||||||
@@ -715,7 +780,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
|
|||||||
|
|
||||||
// BUILD CONFIGURATION DESCRIPTORS //
|
// BUILD CONFIGURATION DESCRIPTORS //
|
||||||
let mut total_length: u16 = 9; // HEADER
|
let mut total_length: u16 = 9; // HEADER
|
||||||
let clock_desc = self.clock_impl.get_configuration_descriptor(1, None)?;
|
let clock_desc = self.audio_impl.clock_configuration_descriptor(1, None)?;
|
||||||
total_length += clock_desc.size() as u16;
|
total_length += clock_desc.size() as u16;
|
||||||
let output_descs = match &self.output {
|
let output_descs = match &self.output {
|
||||||
Some(stream) => {
|
Some(stream) => {
|
||||||
@@ -867,9 +932,17 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
defmt::info!("usb reset");
|
||||||
|
self.audio_impl
|
||||||
|
.alternate_setting_changed(UsbDirection::In, 0);
|
||||||
|
self.audio_impl
|
||||||
|
.alternate_setting_changed(UsbDirection::Out, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<'a, B, CS, AU> {
|
impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B, AU> {
|
||||||
fn emit_feedback(&mut self) {
|
fn emit_feedback(&mut self) {
|
||||||
if let Some(fb_ep) = self.feedback.as_ref() {
|
if let Some(fb_ep) = self.feedback.as_ref() {
|
||||||
if let Some(fb) = self.audio_impl.feedback(self.nominal_fb) {
|
if let Some(fb) = self.audio_impl.feedback(self.nominal_fb) {
|
||||||
@@ -945,7 +1018,6 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
|
|||||||
if self.input.is_some() && iface == self.in_iface {
|
if self.input.is_some() && iface == self.in_iface {
|
||||||
let old_alt = self.input.as_ref().unwrap().alt_setting;
|
let old_alt = self.input.as_ref().unwrap().alt_setting;
|
||||||
if old_alt != alt_setting {
|
if old_alt != alt_setting {
|
||||||
self.clock_impl.alt_setting(alt_setting).ok();
|
|
||||||
self.audio_impl
|
self.audio_impl
|
||||||
.alternate_setting_changed(UsbDirection::In, alt_setting);
|
.alternate_setting_changed(UsbDirection::In, alt_setting);
|
||||||
self.input.as_mut().unwrap().alt_setting = alt_setting;
|
self.input.as_mut().unwrap().alt_setting = alt_setting;
|
||||||
@@ -954,7 +1026,6 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
|
|||||||
} else if self.output.is_some() && iface == self.out_iface {
|
} else if self.output.is_some() && iface == self.out_iface {
|
||||||
let old_alt = self.output.as_ref().unwrap().alt_setting;
|
let old_alt = self.output.as_ref().unwrap().alt_setting;
|
||||||
if old_alt != alt_setting {
|
if old_alt != alt_setting {
|
||||||
self.clock_impl.alt_setting(alt_setting).ok();
|
|
||||||
self.audio_impl
|
self.audio_impl
|
||||||
.alternate_setting_changed(UsbDirection::Out, alt_setting);
|
.alternate_setting_changed(UsbDirection::Out, alt_setting);
|
||||||
// Start the IN cycle running
|
// Start the IN cycle running
|
||||||
@@ -1145,7 +1216,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
xfer.accept(|mut buf| {
|
xfer.accept(|mut buf| {
|
||||||
let rate = self.clock_impl.get_sample_rate();
|
let rate = self.audio_impl.sample_rate();
|
||||||
|
|
||||||
debug!(" {}", rate);
|
debug!(" {}", rate);
|
||||||
buf.write_u32::<LittleEndian>(rate)
|
buf.write_u32::<LittleEndian>(rate)
|
||||||
@@ -1162,7 +1233,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
|
|||||||
channel
|
channel
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
xfer.accept(|mut buf| match self.clock_impl.get_clock_validity() {
|
xfer.accept(|mut buf| match self.audio_impl.clock_validity() {
|
||||||
Ok(valid) => {
|
Ok(valid) => {
|
||||||
debug!(" {}", valid);
|
debug!(" {}", valid);
|
||||||
buf.write_u8(valid as u8)
|
buf.write_u8(valid as u8)
|
||||||
@@ -1192,7 +1263,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
|
|||||||
match xfer.data().read_u32::<LittleEndian>() {
|
match xfer.data().read_u32::<LittleEndian>() {
|
||||||
Ok(rate) => {
|
Ok(rate) => {
|
||||||
debug!(" SET SamplingFreqControl CUR {}", rate);
|
debug!(" SET SamplingFreqControl CUR {}", rate);
|
||||||
self.clock_impl.set_sample_rate(rate).ok();
|
self.audio_impl.set_sample_rate(rate).ok();
|
||||||
self.nominal_fb = UsbIsochronousFeedback::new_float(
|
self.nominal_fb = UsbIsochronousFeedback::new_float(
|
||||||
rate.to_f32().unwrap() / self.audio_rate.to_f32().unwrap(),
|
rate.to_f32().unwrap() / self.audio_rate.to_f32().unwrap(),
|
||||||
);
|
);
|
||||||
@@ -1218,7 +1289,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
|
|||||||
channel
|
channel
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
xfer.accept(|mut buf| match self.clock_impl.get_rates() {
|
xfer.accept(|mut buf| match self.audio_impl.sample_rates() {
|
||||||
Ok(rates) => {
|
Ok(rates) => {
|
||||||
buf.write_u16::<LittleEndian>(rates.len() as u16)
|
buf.write_u16::<LittleEndian>(rates.len() as u16)
|
||||||
.map_err(|_e| UsbError::BufferOverflow)?;
|
.map_err(|_e| UsbError::BufferOverflow)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user