Last active
June 8, 2024 16:16
-
-
Save benagricola/ce97696df9377a9f9c38045b89769a5b to your computer and use it in GitHub Desktop.
Using MicroPython's new pure-python USB driver to send audio data to the host.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import array | |
import math | |
import time | |
import os | |
import usb.device | |
from uac2 import AudioInterface | |
print("Okay, configuring the audio interface now...") | |
# Constants | |
sample_rate = 48000 | |
frequency = 440 # 440Hz | |
num_samples = 10 # number of samples to fit in 60 bytes | |
# Generate the sine wave | |
wave = [0.5 * math.sin(2 * math.pi * frequency * (i / sample_rate)) for i in range(num_samples)] | |
# Convert to 24-bit PCM | |
wave_pcm = [int(sample * (2**23 - 1)) for sample in wave] | |
# Convert to bytes and interleave left and right channels (they are the same because it's mono) | |
byte_array = bytearray() | |
for sample in wave_pcm: | |
# Convert sample to 24-bit little-endian | |
sample_bytes = sample.to_bytes(3, 'little') | |
# Add sample to left channel | |
byte_array.extend(sample_bytes) | |
# Add sample to right channel | |
byte_array.extend(sample_bytes) | |
print("Sample Len: {}".format(len(byte_array))) | |
class AudioTest(AudioInterface): | |
def __init__(self, sample): | |
self.sample = sample | |
super().__init__() | |
def send_sample(self): | |
self._tx.write(self.sample) | |
# Create an AudioInterface instance | |
print("Creating AudioTest instance...") | |
# Replace 'AudioInterface' with your actual AudioInterface class | |
m = AudioTest(sample=byte_array) | |
print("Initializing USB device...") | |
usb.device.get().init(m, builtin_driver=True) | |
while not m.is_open(): | |
print("Waiting for USB host to configure the interface...") | |
time.sleep_ms(100) | |
print("USB host configured the interface") | |
while m.is_open(): | |
m.send_sample() | |
m._tx_xfer() | |
print("Finished") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# MicroPython USB AUDIO INPUT module | |
# MIT license; Copyright (c) 2023 Paul Hamshere, 2023-2024 Angus Gratton | |
from micropython import const | |
import struct, time | |
from usb.device.core import Interface, Buffer | |
_EP_IN_FLAG = const(1 << 7) | |
_INTERFACE_CLASS_AUDIO = const(0x01) | |
_INTERFACE_SUBCLASS_AUDIO_CONTROL = const(0x01) | |
_INTERFACE_SUBCLASS_AUDIO_STREAMING = const(0x02) | |
class AudioInterface(Interface): | |
# Base class to implement a USB Audio input device in Python. | |
def __init__(self, txlen=30): | |
# Arguments are size of transmit and receive buffers in bytes. | |
super().__init__() | |
self.ep_in = None # TX direction (device to host) | |
self._tx = Buffer(txlen) | |
def _tx_xfer(self): | |
# Keep an active IN transfer to send data to the host, whenever | |
# there is data to send. | |
if self.is_open() and not self.xfer_pending(self.ep_in) and self._tx.readable(): | |
self.submit_xfer(self.ep_in, self._tx.pend_read(), self._tx_cb) | |
def _tx_cb(self, ep, res, num_bytes): | |
if res == 0: | |
self._tx.finish_read(num_bytes) | |
def desc_cfg(self, desc, itf_num, ep_num, strs): | |
# By this point, standard device and configuration descriptors have been added. | |
# Append the standard AudioControl interface descriptor | |
desc.interface(itf_num, 0, _INTERFACE_CLASS_AUDIO, _INTERFACE_SUBCLASS_AUDIO_CONTROL) | |
# Append the class-specific AC interface header descriptor | |
desc.pack( | |
"<BBBHHBB", | |
9, # bLength | |
0x24, # bDescriptorType CS_INTERFACE | |
0x01, # bDescriptorSubtype MS_HEADER | |
0x0100, # BcdADC | |
0x0009, # wTotalLength | |
0x01, # bInCollection, | |
itf_num + 1, # baInterfaceNr - points to the Audio Streaming interface | |
) | |
# Append the input terminal descriptor | |
# Append input terminal name | |
idx = len(strs)+1 | |
strs.append("MicroPython Audio Input") | |
desc.pack( | |
"<BBBBHBBHBB", | |
12, # bLength | |
0x24, # bDescriptorType CS_INTERFACE | |
0x02, # bDescriptorSubtype INPUT_TERMINAL | |
0x01, # bTerminalID | |
0x0603,# wTerminalType - USB streaming | |
0x00, # bAssocTerminal - no association | |
0x02, # bNrChannels - 2 channels | |
0x0003, # wChannelConfig - left and right front speakers | |
0x00, # iChannelNames - no string | |
idx, # iTerminal - no string | |
) | |
# Next add the Audio Streaming interface descriptor | |
desc.interface( | |
itf_num + 1, 1, _INTERFACE_CLASS_AUDIO, _INTERFACE_SUBCLASS_AUDIO_STREAMING | |
) | |
# Append the class-specific AS interface descriptor | |
desc.pack( | |
"<BBBBBH", | |
7, # bLength | |
0x24, # bDescriptorType CS_INTERFACE | |
0x01, # bDescriptorSubtype AS_GENERAL | |
0x01, # bTerminalLink - points to the Terminal ID of the Terminal to which the endpoint of this interface is connected | |
0x01, # bDelay - delay introduced by the data path | |
0x0001,# wFormatTag - PCM | |
) | |
# Append the Type I format type descriptor | |
desc.pack( | |
"<BBBBBBBB3B", | |
11, # bLength | |
0x24, # bDescriptorType CS_INTERFACE | |
0x02, # bDescriptorSubtype FORMAT_TYPE | |
0x01, # bFormatType - Type I | |
2, # bNrChannels - 2 channels | |
3, # bSubframeSize - 3 bytes per audio subframe | |
24, # bBitResolution - 24 bits per sample | |
1, # bSamFreqType - 1 discrete sampling frequencies | |
0x80, 0xBB, 0x00, # tSamFreq - 48000 Hz | |
) | |
self.ep_in = ep_num | _EP_IN_FLAG | |
# # rx side, USB "in" endpoint | |
_audio_endpoint(desc, self.ep_in) | |
def num_itfs(self): | |
return 2 | |
def num_eps(self): | |
return 1 | |
def on_open(self): | |
super().on_open() | |
# kick off any transfers that may have queued while the device was not open | |
self._tx_xfer() | |
def _audio_endpoint(desc, bEndpointAddress): | |
# Append the standard AS Isochronous Audio Data Endpoint descriptor | |
desc.pack( | |
"<BBBBHB", | |
7, # bLength | |
0x05, # bDescriptorType ENDPOINT | |
bEndpointAddress, # bEndpointAddress | |
0x01, # bmAttributes - Isochronous | |
0x0120, # wMaxPacketSize | |
0x01, # bInterval | |
) | |
# Append the class-specific AS Isochronous Audio Data Endpoint descriptor | |
desc.pack( | |
"<BBBBBH", | |
7, # bLength | |
0x25, # bDescriptorType CS_ENDPOINT | |
0x01, # bDescriptorSubtype EP_GENERAL | |
0x00, # bmAttributes | |
0x00, # bLockDelayUnits | |
0x0000, # wLockDelay | |
) | |
OK the updated code now is able to send audio data. It's not clean, there's clearly an issue with the sample rate vs buffer size vs transmit rate in USB but... AUDIO!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Currently having trouble with transmitting the actual samples: