From ef18c07d1b94fdb3d875ff2fe3a5c4fba018b60f Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Thu, 3 May 2018 20:39:44 -0500 Subject: [PATCH] Update USBAudio Update the USBAudio class to use the new USB API. This patch also adds buffering and blocking functionality so it can be used in practice from thread context. --- usb/device/USBAudio/USBAudio.cpp | 1002 ++++++++++++++++++++++++++ usb/device/USBAudio/USBAudio.h | 375 ++++++++++ usb/device/USBAudio/USBAudio_Types.h | 95 +++ 3 files changed, 1472 insertions(+) create mode 100644 usb/device/USBAudio/USBAudio.cpp create mode 100644 usb/device/USBAudio/USBAudio.h create mode 100644 usb/device/USBAudio/USBAudio_Types.h diff --git a/usb/device/USBAudio/USBAudio.cpp b/usb/device/USBAudio/USBAudio.cpp new file mode 100644 index 0000000000..dfd04ced26 --- /dev/null +++ b/usb/device/USBAudio/USBAudio.cpp @@ -0,0 +1,1002 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "stdint.h" +#include "USBAudio.h" +#include "USBAudio_Types.h" +#include "EndpointResolver.h" +#include "usb_phy_api.h" + +#define SAMPLE_SIZE 2 +#define XFER_FREQUENCY_HZ 1000 +#define WRITE_READY_UNBLOCK (1 << 0) +#define READ_READY_UNBLOCK (1 << 1) + +class USBAudio::AsyncWrite: public AsyncOp { +public: + AsyncWrite(USBAudio *audio, uint8_t *buf, uint32_t size): + audio(audio), tx_buf(buf), tx_size(size), result(false) + { + + } + + virtual ~AsyncWrite() + { + + } + + virtual bool process() + { + if (audio->_tx_state != Opened) { + result = false; + return true; + } + + uint32_t actual_size = 0; + audio->write_nb(tx_buf, tx_size, &actual_size); + tx_size -= actual_size; + tx_buf += actual_size; + if (tx_size == 0) { + result = true; + return true; + } + + return false; + } + + USBAudio *audio; + uint8_t *tx_buf; + uint32_t tx_size; + bool result; +}; + +class USBAudio::AsyncRead: public AsyncOp { +public: + AsyncRead(USBAudio *audio, uint8_t *buf, uint32_t size, uint32_t *size_read, bool read_all) + : audio(audio), rx_buf(buf), rx_size(size), rx_actual(size_read), all(read_all), result(false) + { + + } + + virtual ~AsyncRead() + { + + } + + virtual bool process() + { + if (audio->_rx_state != Opened) { + result = false; + return true; + } + + uint32_t actual_size = 0; + audio->read_nb(rx_buf, rx_size, &actual_size); + rx_buf += actual_size; + *rx_actual += actual_size; + rx_size -= actual_size; + if ((!all && *rx_actual > 0) || (rx_size == 0)) { + // Wake thread if request is done + result = true; + return true; + } + + return false; + } + + USBAudio *audio; + uint8_t *rx_buf; + uint32_t rx_size; + uint32_t *rx_actual; + bool all; + bool result; +}; + +static void stub_volume() +{ + +} + +static void stub_handler(USBAudio::AudioEvent event) +{ + (void)event; +} + +USBAudio::USBAudio(bool connect, uint32_t frequency_rx, uint8_t channel_count_rx, uint32_t frequency_tx, uint8_t channel_count_tx, uint32_t buffer_ms, uint16_t vendor_id, uint16_t product_id, uint16_t product_release): + USBDevice(get_usb_phy(), vendor_id, product_id, product_release) +{ + _init(frequency_rx, channel_count_rx, frequency_tx, channel_count_tx, buffer_ms); + + // connect or init device + if (connect) { + USBDevice::connect(); + } else { + USBDevice::init(); + } +} + +USBAudio::USBAudio(USBPhy *phy, uint32_t frequency_rx, uint8_t channel_count_rx, uint32_t frequency_tx, uint8_t channel_count_tx, uint32_t buffer_ms, uint16_t vendor_id, uint16_t product_id, uint16_t product_release): + USBDevice(phy, vendor_id, product_id, product_release) +{ + _init(frequency_rx, channel_count_rx, frequency_tx, channel_count_tx, buffer_ms); +} + +void USBAudio::_init(uint32_t frequency_rx, uint8_t channel_count_rx, uint32_t frequency_tx, uint8_t channel_count_tx, uint32_t buffer_ms) +{ + _connected = false; + + _volume = 0; + _mute = 0; + _vol_cur = 0x0080; + _vol_min = 0x0000; + _vol_max = 0x0100; + _vol_res = 0x0004; + + _update_vol = callback(stub_volume); + _tx_done = callback(stub_handler); + _rx_done = callback(stub_handler); + + _rx_overflow = 0; + _tx_underflow = 0; + + _tx_freq = frequency_tx; + _rx_freq = frequency_rx; + + _tx_channel_count = channel_count_tx; + _rx_channel_count = channel_count_rx; + + _tx_idle = true; + _tx_frame_fract = 0; + _tx_whole_frames_per_xfer = _tx_freq / XFER_FREQUENCY_HZ; + _tx_fract_frames_per_xfer = _tx_freq % XFER_FREQUENCY_HZ; + + uint32_t max_frames = _tx_whole_frames_per_xfer + (_tx_fract_frames_per_xfer ? 1 : 0); + _tx_packet_size_max = max_frames * SAMPLE_SIZE * _tx_channel_count; + _rx_packet_size_max = (_rx_freq + 1000 - 1) / 1000 * _rx_channel_count * 2; + + _tx_packet_buf = new uint8_t[_tx_packet_size_max](); + _rx_packet_buf = new uint8_t[_rx_packet_size_max](); + + _tx_queue.resize(buffer_ms * _tx_channel_count * SAMPLE_SIZE * _tx_freq / XFER_FREQUENCY_HZ); + _rx_queue.resize(buffer_ms * _rx_channel_count * SAMPLE_SIZE * _rx_freq / XFER_FREQUENCY_HZ); + + _tx_state = Closed; + _rx_state = Closed; + + EndpointResolver resolver(endpoint_table()); + resolver.endpoint_ctrl(64); + _episo_out = resolver.endpoint_out(USB_EP_TYPE_ISO, _tx_packet_size_max); + _episo_in = resolver.endpoint_in(USB_EP_TYPE_ISO, _rx_packet_size_max); + MBED_ASSERT(resolver.valid()); + + _channel_config_rx = (_rx_channel_count == 1) ? CHANNEL_M : CHANNEL_L + CHANNEL_R; + _channel_config_tx = (_tx_channel_count == 1) ? CHANNEL_M : CHANNEL_L + CHANNEL_R; + + _build_configuration_desc(); +} + +USBAudio::~USBAudio() +{ + disconnect(); + deinit(); + + delete[] _tx_packet_buf; + delete[] _rx_packet_buf; +} + +void USBAudio::connect() +{ + lock(); + + if (!_connected) { + USBDevice::connect(); + _connected = true; + _receive_change(Closed); + _send_change(Closed); + } + + unlock(); +} + +void USBAudio::disconnect() +{ + lock(); + + if (_connected) { + _connected = false; + USBDevice::disconnect(); + _receive_change(Powerdown); + _send_change(Powerdown); + } + + unlock(); +} + +bool USBAudio::read(uint8_t *buf, uint32_t size) +{ + lock(); + + uint32_t actual; + AsyncRead read(this, buf, size, &actual, true); + _read_list.add(&read); + + unlock(); + + read.wait(NULL); + return read.result; +} + +void USBAudio::read_nb(uint8_t *buf, uint32_t size, uint32_t *actual) +{ + + lock(); + + uint32_t available = _rx_queue.size(); + uint32_t copy_size = available > size ? size : available; + _rx_queue.read(buf, copy_size); + *actual = copy_size; + + unlock(); +} + +uint32_t USBAudio::read_overflows(bool clear) +{ + lock(); + + uint32_t overflows = _rx_overflow; + if (clear) { + _rx_overflow = 0; + } + + unlock(); + return overflows; +} + + +bool USBAudio::read_ready() +{ + lock(); + + bool ready = _rx_state == Opened; + + unlock(); + + return ready; +} + +void USBAudio::read_wait_ready() +{ + _flags.wait_any(READ_READY_UNBLOCK, osWaitForever, false); +} + +bool USBAudio::write(uint8_t *buf, uint32_t size) +{ + lock(); + + AsyncWrite write(this, buf, size); + _write_list.add(&write); + + unlock(); + + write.wait(NULL); + return write.result; +} + +void USBAudio::write_nb(uint8_t *buf, uint32_t size, uint32_t *actual) +{ + lock(); + + uint32_t available = _tx_queue.free(); + uint32_t copy_size = available > size ? size : available; + _tx_queue.write(buf, copy_size); + *actual = copy_size; + _send_isr_start(); + + unlock(); +} + +uint32_t USBAudio::write_underflows(bool clear) +{ + lock(); + + uint32_t underflows = _tx_underflow; + if (clear) { + _tx_underflow = 0; + } + + unlock(); + return underflows; +} + +bool USBAudio::write_ready() +{ + lock(); + + bool ready = _tx_state == Opened; + + unlock(); + + return ready; +} + +void USBAudio::write_wait_ready() +{ + _flags.wait_any(WRITE_READY_UNBLOCK, osWaitForever, false); +} + + +float USBAudio::get_volume() +{ + lock(); + + float ret = _mute ? 0.0 : _volume; + + unlock(); + return ret; +} + +void USBAudio::attach(Callback &cb) +{ + lock(); + + _update_vol = cb; + if (!_update_vol) { + _update_vol = stub_volume; + } + + unlock(); +} + +void USBAudio::attach_tx(Callback &cb) +{ + lock(); + + _tx_done = cb; + if (!_tx_done) { + _tx_done = callback(stub_handler); + } + + unlock(); +} + +void USBAudio::attach_rx(Callback &cb) +{ + lock(); + + _rx_done = cb; + if (!_rx_done) { + _rx_done = callback(stub_handler); + } + + unlock(); +} + +void USBAudio::callback_state_change(DeviceState new_state) +{ + assert_locked(); + + if (_connected && (new_state != Configured)) { + _receive_change(Closed); + _send_change(Closed); + } +} + +void USBAudio::callback_request(const setup_packet_t *setup) +{ + assert_locked(); + + RequestResult result = PassThrough; + uint8_t *data = NULL; + uint32_t size = 0; + + // Process class-specific requests + if (setup->bmRequestType.Type == CLASS_TYPE) { + + // Feature Unit: Interface = 0, ID = 2 + if (setup->wIndex == 0x0200) { + + // Master Channel + if ((setup->wValue & 0xff) == 0) { + + switch (setup->wValue >> 8) { + case MUTE_CONTROL: + switch (setup->bRequest) { + case REQUEST_GET_CUR: + size = 1; + data = &_mute; + result = Send; + break; + + case REQUEST_SET_CUR: + size = 1; + data = _control_receive; + result = Receive; + break; + default: + break; + } + break; + case VOLUME_CONTROL: + switch (setup->bRequest) { + case REQUEST_GET_CUR: + size = 2; + data = (uint8_t *)&_vol_cur; + result = Send; + break; + case REQUEST_GET_MIN: + size = 2; + data = (uint8_t *)&_vol_min; + result = Send; + break; + case REQUEST_GET_MAX: + size = 2; + data = (uint8_t *)&_vol_max; + result = Send; + break; + case REQUEST_GET_RES: + size = 2; + data = (uint8_t *)&_vol_res; + result = Send; + break; + + case REQUEST_SET_CUR: + size = 2; + data = _control_receive; + result = Receive; + break; + case REQUEST_SET_MIN: + size = 2; + data = _control_receive; + result = Receive; + break; + case REQUEST_SET_MAX: + size = 2; + data = _control_receive; + result = Receive; + break; + case REQUEST_SET_RES: + size = 2; + data = _control_receive; + result = Receive; + break; + } + break; + default: + break; + } + } + } + } + complete_request(result, data, size); +} + +void USBAudio::callback_request_xfer_done(const setup_packet_t *setup, bool aborted) +{ + assert_locked(); + + if (aborted) { + complete_request_xfer_done(false); + return; + } + + if (setup->bmRequestType.dataTransferDirection == DEVICE_TO_HOST) { + complete_request_xfer_done(true); + return; + } + + if ((setup->wLength == 1) || (setup->wLength == 2)) { + uint16_t data = (_control_receive[0] << 0) | (_control_receive[1] << 8); + data &= ((setup->wLength == 1) ? 0xFF : 0xFFFF); + switch (setup->wValue >> 8) { + case MUTE_CONTROL: + switch (setup->bRequest) { + case REQUEST_SET_CUR: + _mute = data & 0xff; + _update_vol.call(); + break; + default: + break; + } + break; + case VOLUME_CONTROL: + switch (setup->bRequest) { + case REQUEST_SET_CUR: + _vol_cur = data; + _volume = (float)_vol_cur / (float)_vol_max; + _update_vol.call(); + break; + default: + break; + } + break; + default: + break; + } + complete_request_xfer_done(true); + return; + } + + complete_request_xfer_done(false); +} + +void USBAudio::callback_set_configuration(uint8_t configuration) +{ + assert_locked(); + + bool ret = false; + if (configuration == DEFAULT_CONFIGURATION) { + endpoint_remove_all(); + + // Configure isochronous endpoint + endpoint_add(_episo_out, _rx_packet_size_max, USB_EP_TYPE_ISO, static_cast(&USBAudio::_receive_isr)); + endpoint_add(_episo_in, _tx_packet_size_max, USB_EP_TYPE_ISO, static_cast(&USBAudio::_send_isr)); + + // activate readings on this endpoint + read_start(_episo_out, _rx_packet_buf, _rx_packet_size_max); + ret = true; + } + complete_set_configuration(ret); +} + +void USBAudio::callback_set_interface(uint16_t interface, uint8_t alternate) +{ + assert_locked(); + + bool ret = false; + if (interface == 0 && alternate == 0) { + ret = true; + } + if (interface == 1 && (alternate == 0 || alternate == 1)) { + _receive_change(alternate == 1 ? Opened : Closed); + ret = true; + } + if (interface == 2 && (alternate == 0 || alternate == 1)) { + _send_change(alternate == 1 ? Opened : Closed); + ret = true; + } + complete_set_interface(ret); +} + +const uint8_t *USBAudio::configuration_desc(uint8_t index) +{ + if (index != 0) { + return NULL; + } + return _config_descriptor; +} + +const uint8_t *USBAudio::string_iinterface_desc() +{ + static const uint8_t stringIinterfaceDescriptor[] = { + 0x0c, //bLength + STRING_DESCRIPTOR, //bDescriptorType 0x03 + 'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 0 //bString iInterface - Audio + }; + return stringIinterfaceDescriptor; +} + +const uint8_t *USBAudio::string_iproduct_desc() +{ + static const uint8_t stringIproductDescriptor[] = { + 0x16, //bLength + STRING_DESCRIPTOR, //bDescriptorType 0x03 + 'M', 0, 'b', 0, 'e', 0, 'd', 0, ' ', 0, 'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 0 //bString iProduct - Mbed Audio + }; + return stringIproductDescriptor; +} + +#define TOTAL_DESCRIPTOR_LENGTH ((1 * CONFIGURATION_DESCRIPTOR_LENGTH) \ + + (5 * INTERFACE_DESCRIPTOR_LENGTH) \ + + (1 * CONTROL_INTERFACE_DESCRIPTOR_LENGTH + 1) \ + + (2 * INPUT_TERMINAL_DESCRIPTOR_LENGTH) \ + + (1 * FEATURE_UNIT_DESCRIPTOR_LENGTH) \ + + (2 * OUTPUT_TERMINAL_DESCRIPTOR_LENGTH) \ + + (2 * STREAMING_INTERFACE_DESCRIPTOR_LENGTH) \ + + (2 * FORMAT_TYPE_I_DESCRIPTOR_LENGTH) \ + + (2 * (ENDPOINT_DESCRIPTOR_LENGTH + 2)) \ + + (2 * STREAMING_ENDPOINT_DESCRIPTOR_LENGTH) ) + +#define TOTAL_CONTROL_INTF_LENGTH (CONTROL_INTERFACE_DESCRIPTOR_LENGTH + 1 + \ + 2*INPUT_TERMINAL_DESCRIPTOR_LENGTH + \ + FEATURE_UNIT_DESCRIPTOR_LENGTH + \ + 2*OUTPUT_TERMINAL_DESCRIPTOR_LENGTH) + +void USBAudio::_build_configuration_desc() +{ + uint8_t config_descriptor_temp[] = { + // Configuration 1 + CONFIGURATION_DESCRIPTOR_LENGTH, // bLength + CONFIGURATION_DESCRIPTOR, // bDescriptorType + LSB(TOTAL_DESCRIPTOR_LENGTH), // wTotalLength (LSB) + MSB(TOTAL_DESCRIPTOR_LENGTH), // wTotalLength (MSB) + 0x03, // bNumInterfaces + DEFAULT_CONFIGURATION, // bConfigurationValue + 0x00, // iConfiguration + 0x80, // bmAttributes + 50, // bMaxPower + + // Interface 0, Alternate Setting 0, Audio Control + INTERFACE_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR, // bDescriptorType + 0x00, // bInterfaceNumber + 0x00, // bAlternateSetting + 0x00, // bNumEndpoints + AUDIO_CLASS, // bInterfaceClass + SUBCLASS_AUDIOCONTROL, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0x00, // iInterface + + + // Audio Control Interface + CONTROL_INTERFACE_DESCRIPTOR_LENGTH + 1,// bLength + INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType + CONTROL_HEADER, // bDescriptorSubtype + LSB(0x0100), // bcdADC (LSB) + MSB(0x0100), // bcdADC (MSB) + LSB(TOTAL_CONTROL_INTF_LENGTH), // wTotalLength + MSB(TOTAL_CONTROL_INTF_LENGTH), // wTotalLength + 0x02, // bInCollection + 0x01, // baInterfaceNr + 0x02, // baInterfaceNr + + // Audio Input Terminal (Speaker) + INPUT_TERMINAL_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType + CONTROL_INPUT_TERMINAL, // bDescriptorSubtype + 0x01, // bTerminalID + LSB(TERMINAL_USB_STREAMING), // wTerminalType + MSB(TERMINAL_USB_STREAMING), // wTerminalType + 0x00, // bAssocTerminal + _rx_channel_count, // bNrChannels + (uint8_t)(LSB(_channel_config_rx)), // wChannelConfig + (uint8_t)(MSB(_channel_config_rx)), // wChannelConfig + 0x00, // iChannelNames + 0x00, // iTerminal + + // Audio Feature Unit (Speaker) + FEATURE_UNIT_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType + CONTROL_FEATURE_UNIT, // bDescriptorSubtype + 0x02, // bUnitID + 0x01, // bSourceID + 0x01, // bControlSize + CONTROL_MUTE | + CONTROL_VOLUME, // bmaControls(0) + 0x00, // bmaControls(1) + 0x00, // iTerminal + + // Audio Output Terminal (Speaker) + OUTPUT_TERMINAL_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType + CONTROL_OUTPUT_TERMINAL, // bDescriptorSubtype + 0x03, // bTerminalID + LSB(TERMINAL_SPEAKER), // wTerminalType + MSB(TERMINAL_SPEAKER), // wTerminalType + 0x00, // bAssocTerminal + 0x02, // bSourceID + 0x00, // iTerminal + + + // Audio Input Terminal (Microphone) + INPUT_TERMINAL_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType + CONTROL_INPUT_TERMINAL, // bDescriptorSubtype + 0x04, // bTerminalID + LSB(TERMINAL_MICROPHONE), // wTerminalType + MSB(TERMINAL_MICROPHONE), // wTerminalType + 0x00, // bAssocTerminal + _tx_channel_count, // bNrChannels + (uint8_t)(LSB(_channel_config_tx)), // wChannelConfig + (uint8_t)(MSB(_channel_config_tx)), // wChannelConfig + 0x00, // iChannelNames + 0x00, // iTerminal + + // Audio Output Terminal (Microphone) + OUTPUT_TERMINAL_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType + CONTROL_OUTPUT_TERMINAL, // bDescriptorSubtype + 0x05, // bTerminalID + LSB(TERMINAL_USB_STREAMING), // wTerminalType + MSB(TERMINAL_USB_STREAMING), // wTerminalType + 0x00, // bAssocTerminal + 0x04, // bSourceID + 0x00, // iTerminal + + + + + + + // Interface 1, Alternate Setting 0, Audio Streaming - Zero Bandwith + INTERFACE_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR, // bDescriptorType + 0x01, // bInterfaceNumber + 0x00, // bAlternateSetting + 0x00, // bNumEndpoints + AUDIO_CLASS, // bInterfaceClass + SUBCLASS_AUDIOSTREAMING, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0x00, // iInterface + + // Interface 1, Alternate Setting 1, Audio Streaming - Operational + INTERFACE_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR, // bDescriptorType + 0x01, // bInterfaceNumber + 0x01, // bAlternateSetting + 0x01, // bNumEndpoints + AUDIO_CLASS, // bInterfaceClass + SUBCLASS_AUDIOSTREAMING, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0x00, // iInterface + + // Audio Streaming Interface + STREAMING_INTERFACE_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType + STREAMING_GENERAL, // bDescriptorSubtype + 0x01, // bTerminalLink + 0x00, // bDelay + LSB(FORMAT_PCM), // wFormatTag + MSB(FORMAT_PCM), // wFormatTag + + // Audio Type I Format + FORMAT_TYPE_I_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType + STREAMING_FORMAT_TYPE, // bDescriptorSubtype + FORMAT_TYPE_I, // bFormatType + _rx_channel_count, // bNrChannels + 0x02, // bSubFrameSize + 16, // bBitResolution + 0x01, // bSamFreqType + (uint8_t)(LSB(_rx_freq)), // tSamFreq + (uint8_t)((_rx_freq >> 8) & 0xff), // tSamFreq + (uint8_t)((_rx_freq >> 16) & 0xff), // tSamFreq + + // Endpoint - Standard Descriptor + ENDPOINT_DESCRIPTOR_LENGTH + 2, // bLength + ENDPOINT_DESCRIPTOR, // bDescriptorType + _episo_out, // bEndpointAddress + E_ISOCHRONOUS, // bmAttributes + (uint8_t)(LSB(_rx_packet_size_max)), // wMaxPacketSize + (uint8_t)(MSB(_rx_packet_size_max)), // wMaxPacketSize + 0x01, // bInterval + 0x00, // bRefresh + 0x00, // bSynchAddress + + // Endpoint - Audio Streaming + STREAMING_ENDPOINT_DESCRIPTOR_LENGTH, // bLength + ENDPOINT_DESCRIPTOR_TYPE, // bDescriptorType + ENDPOINT_GENERAL, // bDescriptor + 0x00, // bmAttributes + 0x00, // bLockDelayUnits + LSB(0x0000), // wLockDelay + MSB(0x0000), // wLockDelay + + + // Interface 1, Alternate Setting 0, Audio Streaming - Zero Bandwith + INTERFACE_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR, // bDescriptorType + 0x02, // bInterfaceNumber + 0x00, // bAlternateSetting + 0x00, // bNumEndpoints + AUDIO_CLASS, // bInterfaceClass + SUBCLASS_AUDIOSTREAMING, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0x00, // iInterface + + // Interface 1, Alternate Setting 1, Audio Streaming - Operational + INTERFACE_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR, // bDescriptorType + 0x02, // bInterfaceNumber + 0x01, // bAlternateSetting + 0x01, // bNumEndpoints + AUDIO_CLASS, // bInterfaceClass + SUBCLASS_AUDIOSTREAMING, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0x00, // iInterface + + // Audio Streaming Interface + STREAMING_INTERFACE_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType + SUBCLASS_AUDIOCONTROL, // bDescriptorSubtype + 0x05, // bTerminalLink (output terminal microphone) + 0x01, // bDelay + 0x01, // wFormatTag + 0x00, // wFormatTag + + // Audio Type I Format + FORMAT_TYPE_I_DESCRIPTOR_LENGTH, // bLength + INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType + SUBCLASS_AUDIOSTREAMING, // bDescriptorSubtype + FORMAT_TYPE_I, // bFormatType + _tx_channel_count, // bNrChannels + 0x02, // bSubFrameSize + 0x10, // bBitResolution + 0x01, // bSamFreqType + (uint8_t)(LSB(_tx_freq)), // tSamFreq + (uint8_t)((_tx_freq >> 8) & 0xff), // tSamFreq + (uint8_t)((_tx_freq >> 16) & 0xff), // tSamFreq + + // Endpoint - Standard Descriptor + ENDPOINT_DESCRIPTOR_LENGTH + 2, // bLength + ENDPOINT_DESCRIPTOR, // bDescriptorType + _episo_in, // bEndpointAddress + E_ISOCHRONOUS, // bmAttributes + (uint8_t)(LSB(_tx_packet_size_max)), // wMaxPacketSize + (uint8_t)(MSB(_tx_packet_size_max)), // wMaxPacketSize + 0x01, // bInterval + 0x00, // bRefresh + 0x00, // bSynchAddress + + // Endpoint - Audio Streaming + STREAMING_ENDPOINT_DESCRIPTOR_LENGTH, // bLength + ENDPOINT_DESCRIPTOR_TYPE, // bDescriptorType + ENDPOINT_GENERAL, // bDescriptor + 0x00, // bmAttributes + 0x00, // bLockDelayUnits + LSB(0x0000), // wLockDelay + MSB(0x0000), // wLockDelay + }; + + MBED_ASSERT(sizeof(config_descriptor_temp) == sizeof(_config_descriptor)); + memcpy(_config_descriptor, config_descriptor_temp, sizeof(_config_descriptor)); +} + +void USBAudio::_receive_change(ChannelState new_state) +{ + assert_locked(); + + ChannelState prev_state = _rx_state; + _rx_state = new_state; + if (prev_state == new_state) { + // no change + return; + } + + if (prev_state == Opened) { + // Leaving the opened state + _read_list.process(); + _rx_done.call(End); + } + if (new_state == Opened) { + // Entering the opened state + _read_list.process(); + _rx_done.call(Start); + } + if (new_state == Closed) { + // Only block if the channel is closed + _flags.clear(READ_READY_UNBLOCK); + } else { + _flags.set(READ_READY_UNBLOCK); + } +} + +void USBAudio::_receive_isr(usb_ep_t ep) +{ + assert_locked(); + MBED_ASSERT(ep == _episo_out); + + uint32_t size = read_finish(_episo_out); + + if (size > _rx_queue.free()) { + _rx_overflow++; + } else { + + // Copy data over + _rx_queue.write(_rx_packet_buf, size); + + // Signal that there is more data available + _read_list.process(); + if (_rx_done) { + _rx_done.call(Transfer); + } + } + + read_start(_episo_out, _rx_packet_buf, _rx_packet_size_max); +} + +void USBAudio::_send_change(ChannelState new_state) +{ + assert_locked(); + + ChannelState prev_state = _tx_state; + _tx_state = new_state; + if (prev_state == new_state) { + // no change + return; + } + + if (prev_state == Opened) { + // Leaving the opened state + _write_list.process(); + _tx_done.call(End); + } + if (new_state == Opened) { + // Entering the opened state + _write_list.process(); + _tx_done.call(Start); + } + if (new_state == Closed) { + // Only block if the channel is closed + _flags.clear(WRITE_READY_UNBLOCK); + } else { + _flags.set(WRITE_READY_UNBLOCK); + } +} +void USBAudio::_send_isr_start() +{ + assert_locked(); + + if (!_tx_idle) { + return; + } + + _send_isr_next_sync(); +} + +void USBAudio::_send_isr_next_sync() +{ + // Compute size to send + uint32_t fames = _tx_whole_frames_per_xfer; + if (_tx_frame_fract >= XFER_FREQUENCY_HZ) { + _tx_frame_fract -= XFER_FREQUENCY_HZ; + fames += 1; + } + uint32_t send_size = fames * _tx_channel_count * 2; + + // Check if this is the initial TX packet + if (_tx_idle && !_tx_queue.full()) { + // Don't start until the TX buffer is full + return; + } + + // Check if this stream was closed + if (_tx_state != Opened) { + _tx_idle = true; + return; + } + + // Check for enough data to send + if (_tx_queue.size() < send_size) { + _tx_underflow++; + _tx_idle = true; + return; + } + + // Copy data over + _tx_queue.read(_tx_packet_buf, send_size); + + // Start the write + write_start(_episo_in, _tx_packet_buf, send_size); + _tx_idle = false; + _tx_frame_fract += _tx_fract_frames_per_xfer; +} + +void USBAudio::_send_isr(usb_ep_t ep) +{ + assert_locked(); + MBED_ASSERT(ep == _episo_in); + + write_finish(_episo_in); + + _send_isr_next_sync(); + + // Signal that there is space for more data + _write_list.process(); + if (_tx_done) { + _tx_done.call(Transfer); + } +} + diff --git a/usb/device/USBAudio/USBAudio.h b/usb/device/USBAudio/USBAudio.h new file mode 100644 index 0000000000..fec17615f1 --- /dev/null +++ b/usb/device/USBAudio/USBAudio.h @@ -0,0 +1,375 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef USBAudio_H +#define USBAudio_H + +/* These headers are included for child class. */ +#include "USBDescriptor.h" +#include "USBDevice_Types.h" + +#include "USBDevice.h" +#include "Callback.h" +#include "OperationList.h" +#include "ByteBuffer.h" +#include "rtos/EventFlags.h" + +/** +* USBAudio example +* +* @code +* #include "mbed.h" +* #include "USBAudio.h" +* +* // Audio loopback example use: +* // 1. Select "Mbed Audio" as your sound device +* // 2. Play a song or audio file +* // 3. Record the output using a program such as Audacity +* +* int main() { +* +* USBAudio audio(true, 44100, 2, 44100, 2); +* +* printf("Looping audio\r\n"); +* static uint8_t buf[128]; +* while (true) { +* if (!audio.read(buf, sizeof(buf))) { +* memset(buf, 0, sizeof(buf)); +* } +* audio.write(buf, sizeof(buf)); +* } +* } +* @endcode +*/ +class USBAudio: protected USBDevice { +public: + + enum AudioEvent { + Start, + Transfer, + End + }; + + /** + * Basic constructor + * + * Construct this object optionally connecting. + * + * @note Do not use this constructor in derived classes. + * + * @param connect Call connect on initialization + * @param frequency_rx frequency in Hz (default: 48000) + * @param channel_count_rx channel number (1 or 2) (default: 1) + * @param frequency_tx frequency in Hz (default: 8000) + * @param channel_count_tx channel number (1 or 2) (default: 1) + * @param buffer_ms time audio can be buffered without overflowing in milliseconds + * @param vendor_id Your vendor_id + * @param product_id Your product_id + * @param product_release Your product_release + */ + USBAudio(bool connect=true, uint32_t frequency_rx = 48000, uint8_t channel_count_rx = 1, uint32_t frequency_tx = 8000, uint8_t channel_count_tx = 1, uint32_t buffer_ms=10, uint16_t vendor_id = 0x7bb8, uint16_t product_id = 0x1111, uint16_t product_release = 0x0100); + + /** + * Fully featured constructor + * + * Construct this object with the supplied USBPhy and parameters. The user + * this object is responsible for calling connect() or init(). + * + * @note Derived classes must use this constructor and call init() or + * connect() themselves. Derived classes should also call deinit() in + * their destructor. This ensures that no interrupts can occur when the + * object is partially constructed or destroyed. + * + * @param phy USB phy to use + * @param frequency_rx frequency in Hz (default: 48000) + * @param channel_count_rx channel number (1 or 2) (default: 1) + * @param frequency_tx frequency in Hz (default: 8000) + * @param channel_count_tx channel number (1 or 2) (default: 1) + * @param buffer_ms time audio can be buffered without overflowing in milliseconds + * @param vendor_id Your vendor_id + * @param product_id Your product_id + * @param product_release Your product_release + */ + USBAudio(USBPhy *phy, uint32_t frequency_rx, uint8_t channel_count_rx, uint32_t frequency_tx, uint8_t channel_count_tx, uint32_t buffer_ms, uint16_t vendor_id, uint16_t product_id, uint16_t product_release); + + /** + * Destroy this object + * + * Any classes which inherit from this class must call deinit + * before this destructor runs. + */ + virtual ~USBAudio(); + + /** + * Connect USBAudio + */ + void connect(); + + /** + * Disconnect USBAudio + * + * This unblocks all calls to read_ready and write_ready. + */ + void disconnect(); + + /** + * Read audio data + * + * @param buf pointer on a buffer which will be filled with audio data + * @param size size to read + * + * @returns true if successful + */ + bool read(uint8_t *buf, uint32_t size); + + /** + * Nonblocking audio data read + * + * Read the available audio data. + * + * @param buf pointer on a buffer which will be filled with audio data + * @param size size to read + * @param actual size actually read + * @note This function is safe to call from USBAudio callbacks. + */ + void read_nb(uint8_t *buf, uint32_t size, uint32_t *actual); + + /** + * Return the number read packets dropped due to overflow + * + * @param clear Reset the overflow count back to 0 + * @return Number of packets dropped due to overflow + */ + uint32_t read_overflows(bool clear=false); + + /** + * Check if the audio read channel is open + * + * @return true if the audio read channel open, false otherwise + */ + bool read_ready(); + + /** + * Wait until the audio read channel is open + */ + void read_wait_ready(); + + /** + * Write audio data + * + * @param buf pointer to audio data to write + * @param size size to write + * + * @returns true if successful + */ + bool write(uint8_t *buf, uint32_t size); + + /** + * Nonblocking audio data write + * + * Write the available audio data. + * + * @param buf pointer to audio data to write + * @param size size to write + * @param actual actual size written + * @note This function is safe to call from USBAudio callbacks. + */ + void write_nb(uint8_t *buf, uint32_t size, uint32_t *actual); + + /** + * Return the number write packets not sent due to underflow + * + * @param clear Reset the underflow count back to 0 + * @return Number of packets that should have been + * sent but weren't due to overflow + */ + uint32_t write_underflows(bool clear=false); + + /** + * Check if the audio write channel is open + * + * @return true if the audio write channel open, false otherwise + */ + bool write_ready(); + + /** + * Wait until the audio write channel is open + */ + void write_wait_ready(); + + /** + * Get current volume between 0.0 and 1.0 + * + * @returns volume + */ + float get_volume(); + + /** Attach a Callback to update the volume + * + * @param cb Callback to attach + * + */ + void attach(Callback &cb); + + /** attach a Callback to Tx Done + * + * @param cb Callback to attach + * + */ + void attach_tx(Callback &cb); + + /** attach a Callback to Rx Done + * + * @param cb Callback to attach + * + */ + void attach_rx(Callback &cb); + +protected: + + virtual void callback_state_change(DeviceState new_state); + virtual void callback_request(const setup_packet_t *setup); + virtual void callback_request_xfer_done(const setup_packet_t *setup, bool aborted); + virtual void callback_set_configuration(uint8_t configuration); + virtual void callback_set_interface(uint16_t interface, uint8_t alternate); + + virtual const uint8_t *string_iproduct_desc(); + virtual const uint8_t *string_iinterface_desc(); + virtual const uint8_t *configuration_desc(uint8_t index); + +private: + + class AsyncWrite; + class AsyncRead; + + enum ChannelState { + Powerdown, + Closed, + Opened + }; + + void _init(uint32_t frequency_rx, uint8_t channel_count_rx, uint32_t frequency_tx, uint8_t channel_count_tx, uint32_t buffer_ms); + + /* + * Call to rebuild the configuration descriptor + * + * This function should be called on creation or when any + * value that is part of the configuration descriptor + * changes. + * @note This function uses ~200 bytes of stack so + * make sure your stack is big enough for it. + */ + void _build_configuration_desc(); + + void _receive_change(ChannelState new_state); + void _receive_isr(usb_ep_t ep); + void _send_change(ChannelState new_state); + void _send_isr_start(); + void _send_isr_next_sync(); + void _send_isr(usb_ep_t ep); + + // has connect been called + bool _connected; + + // audio volume + float _volume; + + // mute state + uint8_t _mute; + + // Volume Current Value + uint16_t _vol_cur; + + // Volume Minimum Value + uint16_t _vol_min; + + // Volume Maximum Value + uint16_t _vol_max; + + // Volume Resolution + uint16_t _vol_res; + + // callback to update volume + Callback _update_vol; + + // callback transmit Done + Callback _tx_done; + + // callback receive Done + Callback _rx_done; + + // Number of times data was dropped due to an overflow + uint32_t _rx_overflow; + + // Number of times data was not sent due to an underflow + uint32_t _tx_underflow; + + // frequency in Hz + uint32_t _tx_freq; + uint32_t _rx_freq; + + // mono, stereo,... + uint8_t _rx_channel_count; + uint8_t _tx_channel_count; + + bool _tx_idle; + uint16_t _tx_frame_fract; + uint16_t _tx_whole_frames_per_xfer; + uint16_t _tx_fract_frames_per_xfer; + + // size of the maximum packet for the isochronous endpoint + uint16_t _tx_packet_size_max; + uint16_t _rx_packet_size_max; + + // Buffer used for the isochronous transfer + uint8_t *_tx_packet_buf; + uint8_t *_rx_packet_buf; + + // Holding buffer + ByteBuffer _tx_queue; + ByteBuffer _rx_queue; + + // State of the audio channels + ChannelState _tx_state; + ChannelState _rx_state; + + + // sample - a single PCM audio sample + // frame - a group of samples from each channel + // packet - a group of frames sent over USB in one transfer + + // Blocking primitives + OperationList _write_list; + OperationList _read_list; + rtos::EventFlags _flags; + + // endpoint numbers + usb_ep_t _episo_out; // rx endpoint + usb_ep_t _episo_in; // tx endpoint + + // channel config in the configuration descriptor: master, left, right + uint8_t _channel_config_rx; + uint8_t _channel_config_tx; + + // configuration descriptor + uint8_t _config_descriptor[183]; + + // buffer for control requests + uint8_t _control_receive[2]; + +}; + +#endif diff --git a/usb/device/USBAudio/USBAudio_Types.h b/usb/device/USBAudio/USBAudio_Types.h new file mode 100644 index 0000000000..bedb0cfe42 --- /dev/null +++ b/usb/device/USBAudio/USBAudio_Types.h @@ -0,0 +1,95 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef USBAUDIO_TYPES_H +#define USBAUDIO_TYPES_H + + +#define DEFAULT_CONFIGURATION (1) + +// Audio Request Codes +#define REQUEST_SET_CUR 0x01 +#define REQUEST_GET_CUR 0x81 +#define REQUEST_SET_MIN 0x02 +#define REQUEST_GET_MIN 0x82 +#define REQUEST_SET_MAX 0x03 +#define REQUEST_GET_MAX 0x83 +#define REQUEST_SET_RES 0x04 +#define REQUEST_GET_RES 0x84 + +#define MUTE_CONTROL 0x01 +#define VOLUME_CONTROL 0x02 + + +// Audio Descriptor Sizes +#define CONTROL_INTERFACE_DESCRIPTOR_LENGTH 0x09 +#define STREAMING_INTERFACE_DESCRIPTOR_LENGTH 0x07 +#define INPUT_TERMINAL_DESCRIPTOR_LENGTH 0x0C +#define OUTPUT_TERMINAL_DESCRIPTOR_LENGTH 0x09 +#define FEATURE_UNIT_DESCRIPTOR_LENGTH 0x09 +#define STREAMING_ENDPOINT_DESCRIPTOR_LENGTH 0x07 + +// Audio Format Type Descriptor Sizes +#define FORMAT_TYPE_I_DESCRIPTOR_LENGTH 0x0b + +#define AUDIO_CLASS 0x01 +#define SUBCLASS_AUDIOCONTROL 0x01 +#define SUBCLASS_AUDIOSTREAMING 0x02 + +// Audio Descriptor Types +#define INTERFACE_DESCRIPTOR_TYPE 0x24 +#define ENDPOINT_DESCRIPTOR_TYPE 0x25 + +// Audio Control Interface Descriptor Subtypes +#define CONTROL_HEADER 0x01 +#define CONTROL_INPUT_TERMINAL 0x02 +#define CONTROL_OUTPUT_TERMINAL 0x03 +#define CONTROL_FEATURE_UNIT 0x06 + +// USB Terminal Types +#define TERMINAL_USB_STREAMING 0x0101 + +// Predefined Audio Channel Configuration Bits +// Mono +#define CHANNEL_M 0x0000 +#define CHANNEL_L 0x0001 /* Left Front */ +#define CHANNEL_R 0x0002 /* Right Front */ + +// Feature Unit Control Bits +#define CONTROL_MUTE 0x0001 +#define CONTROL_VOLUME 0x0002 + +// Input Terminal Types +#define TERMINAL_MICROPHONE 0x0201 + +// Output Terminal Types +#define TERMINAL_SPEAKER 0x0301 +#define TERMINAL_HEADPHONES 0x0302 + +// Audio Streaming Interface Descriptor Subtypes +#define STREAMING_GENERAL 0x01 +#define STREAMING_FORMAT_TYPE 0x02 + +// Audio Data Format Type I Codes +#define FORMAT_PCM 0x0001 + +// Audio Format Types +#define FORMAT_TYPE_I 0x01 + +// Audio Endpoint Descriptor Subtypes +#define ENDPOINT_GENERAL 0x01 + +#endif