I2S DAC/Using it/08. Internet radio
Using it · 08 of 9

Internet radio

An MP3 station over Wi-Fi, decoded on the ESP32-S3 and played in stereo on the TK103. Two libraries do the work: arduino-audio-tools moves the audio from the network to the DAC, and arduino-libhelix decodes the MP3. Type a number from 0 to 1 in the Serial Monitor to set the volume.

Install the two libraries

Neither library is in the ESP32 core. From GitHub, download each as a ZIP:

  1. pschatzmann/arduino-audio-tools, which moves audio between streams.
  2. pschatzmann/arduino-libhelix, the MP3 decoder.

Then in the Arduino IDE, Sketch > Include Library > Add .ZIP Library, once for each file. The sketch was compiled with arduino-audio-tools 1.2.6 and arduino-libhelix 0.9.4; a much newer release may have moved a header.

Wire it

The same six wires as Left, then right: GND, 3V3, BCK to GPIO 42, DIN to 41, LRCK to 40, and XSMT to 39 if you like. Headphones or a powered speaker in the jack.

Your network and the station

Change your-network and your-password to your Wi-Fi's name and password. STATION is Radio Swiss Jazz's public MP3 stream, the one the library uses in its own example; it answered on 2026-09-24. Any http MP3 stream works in its place. An https one needs more memory, and is not where to start.

How the sketch moves sound

From a station to the jack
press run
The station's sample rate
Samples
16-bit stereo
BCK
1.4 MHz
Starting volume
0.3
Follow one buffer through. Nothing in the chain waits for the whole song: each call moves a little, and the DAC plays while the next buffer is fetched.

Each object hands its output to the next. copier.copy() moves one buffer through the whole chain, and loop() calls it as fast as it can, so the DAC keeps playing while the next buffer arrives.

The DAC starts at 44.1 kHz, and mp3.begin() lets the decoder correct that to the station's real rate once it has read the stream. Most MP3 streams are 44.1 kHz stereo; at 16-bit that is a bit clock of 1.4112 MHz, 32 fs, inside TI's table.

Volume

The sketch starts at 0.3. Open the Serial Monitor at 115200, leave its line ending on Newline, and type a number from 0 to 1. 0 is silent and 1 is full scale; VolumeStream spaces the steps between like a volume knob rather than in a straight line. The sketch prints the new volume back.

The sketch reads the whole line and trims the line ending off it, so a blank line, or a number outside 0 to 1, leaves the volume where it was.

The code

tk103_radio.ino

Put your network's name and password in SSID and PASSWORD. URLStream fetches the station, the MP3 decoder turns it into 16-bit stereo samples, VolumeStream scales them, and I2SStream sends them to the TK103. loop() moves one buffer per call and reads a volume from the Serial Monitor.

// TK103 PCM5102 DAC: internet radio in stereo, from an MP3 stream.
//
// Wiring, TK103 to ESP32-S3:
//   GND -> GND, 3V3 -> 3V3
//   BCK -> GPIO 42, DIN -> GPIO 41, LRCK -> GPIO 40, XSMT -> GPIO 39
//   Headphones or powered speakers in the jack.
//
// Libraries (Sketch > Include Library > Add .ZIP Library), from GitHub:
//   pschatzmann/arduino-audio-tools and pschatzmann/arduino-libhelix
// Arduino IDE: Tools > Board > "ESP32S3 Dev Module" (esp32 core 3.x),
// USB CDC On Boot: Enabled.
// Type a number from 0 to 1 in the Serial Monitor (115200) for volume.

#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
#include "AudioTools/Communication/AudioHttp.h"

const char *SSID = "your-network";
const char *PASSWORD = "your-password";
const char *STATION = "http://stream.srg-ssr.ch/m/rsj/mp3_128";

const int PIN_BCK = 42, PIN_DIN = 41, PIN_LRCK = 40, PIN_XSMT = 39;

URLStream url(SSID, PASSWORD);          // bytes from the station
I2SStream dac;                          // samples to the TK103
VolumeStream volume(dac);               // scales every sample
EncodedAudioStream mp3(&volume, new MP3DecoderHelix());
StreamCopy copier(mp3, url);            // moves one buffer per call

void setup() {
  Serial.begin(115200);
  AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
  pinMode(PIN_XSMT, OUTPUT);
  digitalWrite(PIN_XSMT, HIGH);

  auto cfg = dac.defaultConfig(TX_MODE);
  cfg.pin_bck = PIN_BCK;
  cfg.pin_ws = PIN_LRCK;
  cfg.pin_data = PIN_DIN;
  cfg.sample_rate = 44100;              // the decoder corrects this
  cfg.channels = 2;
  cfg.bits_per_sample = 16;
  dac.begin(cfg);

  volume.begin(cfg);
  volume.setVolume(0.3);
  mp3.begin();                          // tells the DAC the real rate
  url.begin(STATION, "audio/mp3");
}

void loop() {
  copier.copy();

  if (Serial.available()) {
    String line = Serial.readStringUntil('\n');
    line.trim();                        // drops the line ending
    float v = line.toFloat();
    if (line.length() > 0 && v >= 0 && v <= 1) {
      volume.setVolume(v);
      Serial.printf("volume %.2f\n", v);
    }
  }
}

Compiled, not run, with esp32 core 3.3.10, arduino-audio-tools 1.2.6 and arduino-libhelix 0.9.4: 1,111,497 bytes, 84 % of the default app partition, so no partition change is needed. Its tested ancestor is the owner's bench player, which played an MP3 from flash through the same two libraries at 44.1 kHz on a TK103. Leave the Serial Monitor's line ending on Newline, its default, when typing a volume.

When it does not work

Typing a volume pauses the sound for a second.

The Serial Monitor is set to No line ending, so the sketch waits a second for the end of the line before it gives up and reads the number. Set the line ending to Newline, the default, and the change is immediate.

Compiling fails: AudioTools.h: No such file or directory.

The libraries are not installed, or only one is. Add both ZIPs with Sketch > Include Library > Add .ZIP Library: arduino-audio-tools and arduino-libhelix, from pschatzmann's GitHub pages. Then restart the IDE and compile again.

Nothing plays and the Serial Monitor shows warnings.

Usually the network: check SSID and PASSWORD, and that the board is in range. If it connects and still plays nothing, the station may be down: try it in a browser on the same network, or put another http MP3 stream in STATION.

The sound stutters now and then.

The DAC plays what the network delivered, and a weak Wi-Fi signal delivers it late. Move the board nearer the router.

An https station does not play.

The sketch is written for http. An https stream needs the ESP32-S3 to run TLS as well, which needs more memory; start with an http stream, such as the one in the sketch.

Where this goes next

Six causes, the red LED to sort them, and where to look first.

When it stays silent

Edit this page — content/books/i2s-dac/internet-radio.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

I2S DAC

Loading discussions…

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse Modules and blocks on the forum