{"id":7040,"date":"2026-06-22T13:52:16","date_gmt":"2026-06-22T11:52:16","guid":{"rendered":"https:\/\/www.pschatzmann.ch\/home\/?p=7040"},"modified":"2026-06-22T13:52:16","modified_gmt":"2026-06-22T11:52:16","slug":"usb-audio-class-2-0-for-arduino","status":"publish","type":"post","link":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/","title":{"rendered":"USB Audio Class 2.0 for Arduino"},"content":{"rendered":"<p>I&#8217;m happy to announce that the <a href=\"https:\/\/github.com\/pschatzmann\/arduino-audio-tools\">Arduino Audio Tools library<\/a> now includes native USB Audio Class 2.0 (UAC2) support. Your ESP32-S3 or RP2040 board can show up as a standard USB microphone, speaker, or both \u2014 no drivers needed on the host side. Linux, macOS, and Windows 10+ recognize it out of the box.<\/p>\n<h2 id=\"why-usb-audio\">Why USB Audio?<\/h2>\n<p>Most Arduino audio projects use I2S to talk to an external DAC or ADC. That works well, but it requires extra hardware and doesn&#8217;t help when you want to stream audio directly between a microphone and a computer, or use your board as a USB sound card.<\/p>\n<p>With UAC2, the microcontroller <em>is<\/em> the sound card. Plug it in, and tools like <code>arecord<\/code>, <code>aplay<\/code>, Audacity, or any DAW can send and receive audio without any custom software on the host.<\/p>\n<h2 id=\"whats-supported\">What&#8217;s Supported<\/h2>\n<ul>\n<li><strong>TX mode<\/strong> (device \u2192 host): The board appears as a USB microphone. Feed it audio from any source \u2014 I2S, a sine generator, an MP3 decoder \u2014 and it streams to the host as a standard capture device.<\/li>\n<li><strong>RX mode<\/strong> (host \u2192 device): The board appears as a USB speaker. The host plays audio, and your sketch reads it with <code>readBytes()<\/code> to forward to a DAC, process, or analyze.<\/li>\n<li><strong>RXTX mode<\/strong>: Both directions at once. The board appears as a composite capture + playback device.<\/li>\n<li><strong>Volume and mute control<\/strong>: The host can adjust per-channel volume and mute through the standard mixer interface (e.g. <code>amixer<\/code>, PulseAudio, Windows volume mixer).<\/li>\n<li><strong>Sample rate flexibility<\/strong>: 16-bit, 24-bit, and 32-bit PCM at any standard rate. An optional multi-rate mode advertises 14 discrete rates from 8 kHz to 192 kHz.<\/li>\n<li><strong>Composite USB<\/strong>: Works alongside USB CDC (serial), so you can debug over the same USB cable.<\/li>\n<li><strong>Cross-platform<\/strong>: Tested on Linux (<code>snd-usb-audio<\/code>), and designed to work with macOS CoreAudio and Windows UAC2 drivers.<\/li>\n<\/ul>\n<h2 id=\"supported-hardware\">Supported Hardware<\/h2>\n<ul>\n<li><strong>ESP32-S2 \/ ESP32-S3<\/strong> \u2014 via the native USB OTG peripheral (DWC2 controller)<\/li>\n<li><strong>Adafruit TinyUSB stack<\/strong> all boards supporting the Adafruit TinyUSB stack (e.g. Raspberry Pi Pico)<\/li>\n<li><strong>Zephyr-based boards<\/strong> \u2014 via Zephyr&#8217;s native <code>usbd_uac2<\/code> driver (nRF5340, STM32, etc.)<\/li>\n<\/ul>\n<h2 id=\"getting-started\">Getting Started<\/h2>\n<h3 id=\"tx-mode--usb-microphone\">TX Mode \u2014 USB Microphone<\/h3>\n<p>A minimal example that streams a sine wave to the host:<\/p>\n<pre><code class=\"language-cpp\">#include \"AudioTools.h\"\r\n#include \"AudioTools\/Communication\/USB\/USBAudioStream.h\"\r\n\r\nAudioInfo info(44100, 2, 16);\r\nSineGenerator&lt;int16_t&gt; sineWave;\r\nGeneratedSoundStream&lt;int16_t&gt; sound(sineWave);\r\nUSBAudioStream out;\r\nStreamCopy copier(out, sound);\r\n\r\nvoid setup() {\r\n  \/\/ Required on cores without built-in TinyUSB support (e.g. mbed RP2040)\r\n  if (!TinyUSBDevice.isInitialized()) {\r\n    TinyUSBDevice.begin(0);\r\n  }\r\n\r\n  sineWave.begin(info, N_B4);\r\n  out.addNotifyAudioChange(sound);\r\n\r\n  auto config = out.defaultConfig(TX_MODE);\r\n  config.copyFrom(info);\r\n  out.begin(config);\r\n\r\n  \/\/ Re-enumerate so the host picks up the new audio interface\r\n  if (TinyUSBDevice.mounted()) {\r\n    TinyUSBDevice.detach();\r\n    delay(10);\r\n    TinyUSBDevice.attach();\r\n  }\r\n}\r\n\r\nvoid loop() {\r\n  copier.copy();\r\n}\r\n<\/code><\/pre>\n<p>On the host, the device appears immediately:<\/p>\n<pre><code class=\"language-bash\">arecord -D hw:Audio -f S16_LE -r 44100 -c 2 -d 5 recording.wav\r\n<\/code><\/pre>\n<h3 id=\"rx-mode--usb-speaker\">RX Mode \u2014 USB Speaker<\/h3>\n<p>The board receives audio from the host:<\/p>\n<pre><code class=\"language-cpp\">#include \"AudioTools.h\"\r\n#include \"AudioTools\/Communication\/USB\/USBAudioStream.h\"\r\n\r\nAudioInfo info(44100, 2, 16);\r\nUSBAudioStream in;\r\nI2SStream i2s;  \/\/ or any output\r\nStreamCopy copier(i2s, in);\r\n\r\nvoid setup() {\r\n  \/\/ Required on cores without built-in TinyUSB support (e.g. mbed RP2040)\r\n  if (!TinyUSBDevice.isInitialized()) {\r\n    TinyUSBDevice.begin(0);\r\n  }\r\n\r\n  auto config = in.defaultConfig(RX_MODE);\r\n  config.copyFrom(info);\r\n  in.begin(config);\r\n\r\n  auto i2s_cfg = i2s.defaultConfig(TX_MODE);\r\n  i2s_cfg.copyFrom(info);\r\n  i2s.begin(i2s_cfg);\r\n\r\n  \/\/ Re-enumerate so the host picks up the new audio interface\r\n  if (TinyUSBDevice.mounted()) {\r\n    TinyUSBDevice.detach();\r\n    delay(10);\r\n    TinyUSBDevice.attach();\r\n  }\r\n}\r\n\r\nvoid loop() {\r\n  copier.copy();\r\n}\r\n<\/code><\/pre>\n<p>Then on the host:<\/p>\n<pre><code class=\"language-bash\">aplay -D hw:Audio my_song.wav\r\n<\/code><\/pre>\n<h2 id=\"configuration\">Configuration<\/h2>\n<p>All USB audio settings live in a single <code>USBAudioConfig<\/code> struct. You get a pre-filled instance from <code>defaultConfig()<\/code> and customize what you need:<\/p>\n<pre><code class=\"language-cpp\">auto config = usb.defaultConfig(TX_MODE);  \/\/ or RX_MODE, RXTX_MODE\r\nconfig.copyFrom(info);                     \/\/ inherit sample_rate, channels, bits_per_sample\r\n<\/code><\/pre>\n<h3 id=\"audio-format\">Audio Format<\/h3>\n<table>\n<thead>\n<tr>\n<th>Field<\/th>\n<th>Default<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>sample_rate<\/code><\/td>\n<td>44100<\/td>\n<td>Sample rate in Hz. Inherited from <code>AudioInfo<\/code>.<\/td>\n<\/tr>\n<tr>\n<td><code>channels<\/code><\/td>\n<td>2<\/td>\n<td>Number of audio channels (1 = mono, 2 = stereo).<\/td>\n<\/tr>\n<tr>\n<td><code>bits_per_sample<\/code><\/td>\n<td>16<\/td>\n<td>Bit depth per sample. Must be 16, 24, or 32. Maps to S16_LE, S24_3LE, S32_LE on the host.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"direction\">Direction<\/h3>\n<table>\n<thead>\n<tr>\n<th>Field<\/th>\n<th>Default<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>enable_ep_in<\/code><\/td>\n<td>true<\/td>\n<td>Enable the IN endpoint (device \u2192 host, capture\/microphone).<\/td>\n<\/tr>\n<tr>\n<td><code>enable_ep_out<\/code><\/td>\n<td>true<\/td>\n<td>Enable the OUT endpoint (host \u2192 device, playback\/speaker).<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><code>defaultConfig(TX_MODE)<\/code> sets <code>enable_ep_in=true, enable_ep_out=false<\/code>. <code>defaultConfig(RX_MODE)<\/code> does the opposite. <code>RXTX_MODE<\/code> enables both.<\/p>\n<h3 id=\"endpoint-addresses\">Endpoint Addresses<\/h3>\n<table>\n<thead>\n<tr>\n<th>Field<\/th>\n<th>Default<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>ep_in<\/code><\/td>\n<td>0x83<\/td>\n<td>ISO IN endpoint address (capture).<\/td>\n<\/tr>\n<tr>\n<td><code>ep_out<\/code><\/td>\n<td>0x03<\/td>\n<td>ISO OUT endpoint address (playback).<\/td>\n<\/tr>\n<tr>\n<td><code>ep_fb<\/code><\/td>\n<td>0x84<\/td>\n<td>ISO IN feedback endpoint (RX-only mode).<\/td>\n<\/tr>\n<tr>\n<td><code>ep_int<\/code><\/td>\n<td>0x85<\/td>\n<td>INT IN endpoint (AC change notifications).<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The defaults are chosen to avoid conflicts with CDC, which uses 0x81, 0x82, and 0x02.<\/p>\n<h3 id=\"buffering\">Buffering<\/h3>\n<table>\n<thead>\n<tr>\n<th>Field<\/th>\n<th>Default<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>fifo_packets<\/code><\/td>\n<td>16<\/td>\n<td>Number of 1 ms USB packets buffered. Higher values reduce the risk of underrun at the cost of latency.<\/td>\n<\/tr>\n<tr>\n<td><code>use_linear_buffer_rx<\/code><\/td>\n<td>true<\/td>\n<td>Use a flat buffer for RX (required for DMA-based downstream drivers).<\/td>\n<\/tr>\n<tr>\n<td><code>use_linear_buffer_tx<\/code><\/td>\n<td>true<\/td>\n<td>Use a flat buffer for TX.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"device-identity\">Device Identity<\/h3>\n<table>\n<thead>\n<tr>\n<th>Field<\/th>\n<th>Default<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>vid<\/code><\/td>\n<td>0xCafe<\/td>\n<td>USB Vendor ID.<\/td>\n<\/tr>\n<tr>\n<td><code>pid<\/code><\/td>\n<td>0x4002<\/td>\n<td>USB Product ID.<\/td>\n<\/tr>\n<tr>\n<td><code>manufacturer<\/code><\/td>\n<td>&#8220;Audio Tools&#8221;<\/td>\n<td>Manufacturer string shown by the host.<\/td>\n<\/tr>\n<tr>\n<td><code>product<\/code><\/td>\n<td>&#8220;USB Audio&#8221;<\/td>\n<td>Product name shown by the host.<\/td>\n<\/tr>\n<tr>\n<td><code>serial<\/code><\/td>\n<td>&#8220;000001&#8221;<\/td>\n<td>Serial number string.<\/td>\n<\/tr>\n<tr>\n<td><code>self_powered<\/code><\/td>\n<td>true<\/td>\n<td>Device is self-powered (not bus-powered).<\/td>\n<\/tr>\n<tr>\n<td><code>max_power_ma<\/code><\/td>\n<td>100<\/td>\n<td>Maximum current draw in mA.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"feature-flags\">Feature Flags<\/h3>\n<table>\n<thead>\n<tr>\n<th>Field<\/th>\n<th>Default<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>enable_feedback_ep<\/code><\/td>\n<td>true<\/td>\n<td>Enable the isochronous feedback endpoint so the host can adjust its clock. Only active in pure RX mode (no IN endpoint).<\/td>\n<\/tr>\n<tr>\n<td><code>enable_multi_sample_rate<\/code><\/td>\n<td>false<\/td>\n<td>When true, the clock source is programmable and GET_RANGE advertises 14 discrete rates (8 kHz \u2013 192 kHz). When false, only the configured rate is reported.<\/td>\n<\/tr>\n<tr>\n<td><code>enable_interrupt_ep<\/code><\/td>\n<td>false<\/td>\n<td>Enable the AC interrupt endpoint for device-initiated volume, mute, and sample-rate change notifications.<\/td>\n<\/tr>\n<tr>\n<td><code>enable_ep_in_flow_control<\/code><\/td>\n<td>true<\/td>\n<td>Vary the per-frame packet size so non-integer rates like 44100 Hz are delivered at the exact average rate.<\/td>\n<\/tr>\n<tr>\n<td><code>volume_active<\/code><\/td>\n<td>false<\/td>\n<td>When true, the library applies volume and mute scaling to the audio samples directly. When false, values are available via <code>volume()<\/code> and <code>isMuted()<\/code> for external processing.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"esp32-specific\">ESP32-Specific<\/h3>\n<table>\n<thead>\n<tr>\n<th>Field<\/th>\n<th>Default<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>begin_usb<\/code><\/td>\n<td>false<\/td>\n<td>When true, <code>beginUSB()<\/code> calls <code>USB.begin()<\/code> automatically. Set to false for composite USB devices where you control the startup order.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"example\">Example<\/h3>\n<pre><code class=\"language-cpp\">auto config = usb.defaultConfig(TX_MODE);\r\nconfig.copyFrom(info);\r\nconfig.product = \"My Synth\";\r\nconfig.vid = 0x1234;\r\nconfig.pid = 0x5678;\r\nconfig.fifo_packets = 32;         \/\/ more buffering\r\nconfig.volume_active = true;      \/\/ let the library handle volume\r\nconfig.enable_interrupt_ep = true; \/\/ push volume\/rate changes to host\r\nusb.begin(config);\r\n<\/code><\/pre>\n<h2 id=\"volume-and-mute\">Volume and Mute<\/h2>\n<p>The UAC2 descriptors advertise per-channel volume and mute controls. The host sees them as standard mixer controls:<\/p>\n<pre><code class=\"language-bash\"># List controls\r\namixer -c Audio scontrols\r\n\r\n# Set volume to 80%\r\namixer -c Audio sset 'Mic',0 80%\r\n\r\n# Mute\r\namixer -c Audio sset 'Mic',0 mute\r\n<\/code><\/pre>\n<p>On the device side, you can react to volume changes with a callback:<\/p>\n<pre><code class=\"language-cpp\">out.setVolumeCallback([](float vol, uint8_t channel) {\r\n  Serial.printf(\"Volume ch%d: %.0f%%\\n\", channel, vol * 100);\r\n});\r\n\r\nout.setMuteCallback([](bool muted, uint8_t channel) {\r\n  Serial.printf(\"Mute ch%d: %s\\n\", channel, muted ? \"on\" : \"off\");\r\n});\r\n<\/code><\/pre>\n<p>Or enable automatic volume processing so the library scales the audio samples directly:<\/p>\n<pre><code class=\"language-cpp\">config.volume_active = true;\r\n<\/code><\/pre>\n<h2 id=\"architecture\">Architecture<\/h2>\n<p>The implementation is split into layers:<\/p>\n<ul>\n<li><strong><code>USBAudioConfig<\/code><\/strong> \u2014 all configuration in one struct: sample rate, channels, bit depth, endpoint addresses, buffering, device identity, and feature flags.<\/li>\n<li><strong><code>USBAudio2DescriptorBuilder<\/code><\/strong> \u2014 generates the complete UAC2 descriptor block at runtime: IAD, Audio Control interface with Clock Source, Input\/Output Terminals, and Feature Units, plus Audio Streaming interfaces with Format Type descriptors and isochronous endpoints.<\/li>\n<li><strong><code>USBAudioDeviceBase<\/code><\/strong> \u2014 the core class (~2200 lines) that implements the TinyUSB class driver interface: descriptor callbacks, control request handling (sample rate, volume, mute), isochronous endpoint management, flow control for accurate sample rates, and the <code>AudioStream<\/code> read\/write API.<\/li>\n<li><strong><code>USBAudioDeviceESP32<\/code><\/strong> \/ <strong><code>USBAudioDeviceTinyUSB<\/code><\/strong> \/ <strong><code>USBAudioDeviceZephyr<\/code><\/strong> \u2014 thin platform subclasses that provide the right buffer implementation and USB stack initialization for each target.<\/li>\n<li><strong><code>USBAudioStream<\/code><\/strong> \u2014 a dispatch header that resolves to the correct platform class automatically.<\/li>\n<\/ul>\n<p>The key design goal was to make USB audio feel like any other AudioTools stream. <code>StreamCopy<\/code> works. <code>addNotifyAudioChange<\/code> works. Volume and mute integrate with the existing <code>VolumeSupport<\/code> interface. You can chain it with any source or sink in the library.<\/p>\n<h2 id=\"flow-control\">Flow Control<\/h2>\n<p>One subtle but important feature is TX flow control. At 44100 Hz with 1 ms USB frames, you need to send 44.1 samples per frame \u2014 not an integer. Without flow control, you&#8217;d send 45 samples every frame, and the effective rate would drift to 45000 Hz.<\/p>\n<p>The flow control implementation uses a fractional accumulator: it alternates between 44-sample and 45-sample packets so the average over time is exactly 44.1 samples per frame. This is enabled by default (<code>enable_ep_in_flow_control = true<\/code>) and is essential for glitch-free audio.<\/p>\n<h2 id=\"buffering-1\">Buffering<\/h2>\n<p>Getting buffer management right on a dual-core chip like the ESP32-S3 was one of the bigger challenges. The USB stack runs on core 0, but the Arduino <code>loop()<\/code> typically runs on core 1. We need lock-free or RTOS-synchronized handoff between the cores.<\/p>\n<p>The ESP32 subclass uses <code>BufferRTOS<\/code> \u2014 a FreeRTOS StreamBuffer wrapper that provides safe cross-core data transfer. The TX side uses a short write timeout (5 ms) so the audio copier blocks briefly when the buffer is full, while the USB callback side never blocks. The RX side is fully non-blocking on both ends.<\/p>\n<p>On the RP2040 (single-core), a simple <code>RingBuffer<\/code> is sufficient since there&#8217;s no cross-core contention.<\/p>\n<h2 id=\"composite-usb-devices\">Composite USB Devices<\/h2>\n<p>You can combine USB Audio with CDC serial for debugging. On ESP32-S3, set <code>begin_usb = false<\/code> in the config, register the CDC interface first, then start audio, and finally call <code>USB.begin()<\/code> yourself:<\/p>\n<pre><code class=\"language-cpp\">USBCDC MySerial;\r\n\r\nvoid setup() {\r\n  MySerial.begin(115200);\r\n\r\n  auto config = out.defaultConfig(TX_MODE);\r\n  config.copyFrom(info);\r\n  config.begin_usb = false;  \/\/ we'll call USB.begin() manually\r\n  out.begin(config);\r\n\r\n  USB.begin();  \/\/ starts both CDC and Audio\r\n}\r\n<\/code><\/pre>\n<p>The default endpoint addresses (0x83, 0x03, 0x84, 0x85) are chosen to avoid conflicts with CDC, which uses 0x81, 0x82, and 0x02.<\/p>\n<h2 id=\"testing\">Testing<\/h2>\n<p>The library includes a <code>QualityAnalysisStream<\/code> that can be wired into the RX path to detect audio problems:<\/p>\n<pre><code class=\"language-cpp\">AudioInfo info(44100, 2, 16);\r\nUSBAudioStream in;\r\nQualityAnalysisStream quality(in);\r\nuint8_t buf[1024];\r\n\r\nvoid setup() {\r\n  \/\/ Required on cores without built-in TinyUSB support (e.g. mbed RP2040)\r\n  if (!TinyUSBDevice.isInitialized()) {\r\n    TinyUSBDevice.begin(0);\r\n  }\r\n\r\n  auto config = in.defaultConfig(RX_MODE);\r\n  config.copyFrom(info);\r\n  in.begin(config);\r\n  quality.setReporting(10000, Serial);\r\n  quality.begin(info);\r\n\r\n  \/\/ Re-enumerate so the host picks up the new audio interface\r\n  if (TinyUSBDevice.mounted()) {\r\n    TinyUSBDevice.detach();\r\n    delay(10);\r\n    TinyUSBDevice.attach();\r\n  }\r\n}\r\n\r\nvoid loop() {\r\n  quality.readBytes(buf, sizeof(buf));\r\n}\r\n<\/code><\/pre>\n<p>On the host, play a test tone and watch Serial for results:<\/p>\n<pre><code class=\"language-bash\">sox -n -r 44100 -c 2 -b 16 test_tone.wav synth 5 sine 440\r\naplay -D hw:Audio test_tone.wav\r\n<\/code><\/pre>\n<p>The code is available in the <a href=\"https:\/\/github.com\/pschatzmann\/arduino-audio-tools\">arduino-audio-tools<\/a> library under <code>src\/AudioTools\/Communication\/USB\/<\/code>. Examples are in <code>examples\/examples-communication\/usb\/<\/code>.<\/p>\n<p>Feedback and bug reports are welcome \u2014 this is a complex protocol stack and real-world testing across different hosts and use cases is invaluable.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m happy to announce that the Arduino Audio Tools library now includes native USB Audio Class 2.0 (UAC2) support. Your ESP32-S3 or RP2040 board can show up as a standard USB microphone, speaker, or both \u2014 no drivers needed on the host side. Linux, macOS, and Windows 10+ recognize it [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":2487,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_import_markdown_pro_load_document_selector":0,"_import_markdown_pro_submit_text_textarea":"","_exactmetrics_skip_tracking":false,"_exactmetrics_sitenote_active":false,"_exactmetrics_sitenote_note":"","_exactmetrics_sitenote_category":0,"footnotes":""},"categories":[20,22],"tags":[],"class_list":["post-7040","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-arduino","category-machine-sound"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>USB Audio Class 2.0 for Arduino - Phil Schatzmann<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"USB Audio Class 2.0 for Arduino - Phil Schatzmann\" \/>\n<meta property=\"og:description\" content=\"I&#8217;m happy to announce that the Arduino Audio Tools library now includes native USB Audio Class 2.0 (UAC2) support. Your ESP32-S3 or RP2040 board can show up as a standard USB microphone, speaker, or both \u2014 no drivers needed on the host side. Linux, macOS, and Windows 10+ recognize it [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/\" \/>\n<meta property=\"og:site_name\" content=\"Phil Schatzmann\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-22T11:52:16+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2021\/02\/USB.png\" \/>\n\t<meta property=\"og:image:width\" content=\"324\" \/>\n\t<meta property=\"og:image:height\" content=\"155\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"pschatzmann\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"pschatzmann\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/\"},\"author\":{\"name\":\"pschatzmann\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\"},\"headline\":\"USB Audio Class 2.0 for Arduino\",\"datePublished\":\"2026-06-22T11:52:16+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/\"},\"wordCount\":1227,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\"},\"image\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2021\\\/02\\\/USB.png\",\"articleSection\":[\"Arduino\",\"Machine Sound\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/\",\"name\":\"USB Audio Class 2.0 for Arduino - Phil Schatzmann\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2021\\\/02\\\/USB.png\",\"datePublished\":\"2026-06-22T11:52:16+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2021\\\/02\\\/USB.png\",\"contentUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2021\\\/02\\\/USB.png\",\"width\":324,\"height\":155},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/2026\\\/06\\\/22\\\/usb-audio-class-2-0-for-arduino\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"USB Audio Class 2.0 for Arduino\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#website\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/\",\"name\":\"Phil Schatzmann Consulting\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/home\\\/#\\\/schema\\\/person\\\/73a53638a4e34e8373405fd737dac9b1\",\"name\":\"pschatzmann\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\",\"url\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\",\"contentUrl\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\",\"width\":305,\"height\":305,\"caption\":\"pschatzmann\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.pschatzmann.ch\\\/wp-content\\\/uploads\\\/2022\\\/08\\\/pschatzmann.png\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"USB Audio Class 2.0 for Arduino - Phil Schatzmann","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/","og_locale":"en_US","og_type":"article","og_title":"USB Audio Class 2.0 for Arduino - Phil Schatzmann","og_description":"I&#8217;m happy to announce that the Arduino Audio Tools library now includes native USB Audio Class 2.0 (UAC2) support. Your ESP32-S3 or RP2040 board can show up as a standard USB microphone, speaker, or both \u2014 no drivers needed on the host side. Linux, macOS, and Windows 10+ recognize it [&hellip;]","og_url":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/","og_site_name":"Phil Schatzmann","article_published_time":"2026-06-22T11:52:16+00:00","og_image":[{"width":324,"height":155,"url":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2021\/02\/USB.png","type":"image\/png"}],"author":"pschatzmann","twitter_card":"summary_large_image","twitter_misc":{"Written by":"pschatzmann","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/#article","isPartOf":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/"},"author":{"name":"pschatzmann","@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1"},"headline":"USB Audio Class 2.0 for Arduino","datePublished":"2026-06-22T11:52:16+00:00","mainEntityOfPage":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/"},"wordCount":1227,"commentCount":0,"publisher":{"@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1"},"image":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/#primaryimage"},"thumbnailUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2021\/02\/USB.png","articleSection":["Arduino","Machine Sound"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/","url":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/","name":"USB Audio Class 2.0 for Arduino - Phil Schatzmann","isPartOf":{"@id":"https:\/\/www.pschatzmann.ch\/home\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/#primaryimage"},"image":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/#primaryimage"},"thumbnailUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2021\/02\/USB.png","datePublished":"2026-06-22T11:52:16+00:00","breadcrumb":{"@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/#primaryimage","url":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2021\/02\/USB.png","contentUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2021\/02\/USB.png","width":324,"height":155},{"@type":"BreadcrumbList","@id":"https:\/\/www.pschatzmann.ch\/home\/2026\/06\/22\/usb-audio-class-2-0-for-arduino\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.pschatzmann.ch\/home\/"},{"@type":"ListItem","position":2,"name":"USB Audio Class 2.0 for Arduino"}]},{"@type":"WebSite","@id":"https:\/\/www.pschatzmann.ch\/home\/#website","url":"https:\/\/www.pschatzmann.ch\/home\/","name":"Phil Schatzmann Consulting","description":"","publisher":{"@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.pschatzmann.ch\/home\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.pschatzmann.ch\/home\/#\/schema\/person\/73a53638a4e34e8373405fd737dac9b1","name":"pschatzmann","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png","url":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png","contentUrl":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png","width":305,"height":305,"caption":"pschatzmann"},"logo":{"@id":"https:\/\/www.pschatzmann.ch\/wp-content\/uploads\/2022\/08\/pschatzmann.png"}}]}},"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts\/7040","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/comments?post=7040"}],"version-history":[{"count":2,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts\/7040\/revisions"}],"predecessor-version":[{"id":7042,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/posts\/7040\/revisions\/7042"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/media\/2487"}],"wp:attachment":[{"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/media?parent=7040"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/categories?post=7040"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.pschatzmann.ch\/home\/wp-json\/wp\/v2\/tags?post=7040"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}