Community project

Ethernet Audio Streamer

ESP32
Photo of Ethernet Audio Streamer
Generated with AI

Andre Swartz

Published September 27, 2026

This project builds a network audio streamer that receives RTP audio packets over Ethernet and outputs stereo sound through a 3.5 mm jack. The ESP32 microcontroller connects to a wired network via the W5500 Ethernet module and feeds audio data to a PCM5102A stereo DAC, enabling high-quality audio streaming from compatible sources on the local network.

The guide provides a complete parts list, wiring diagram showing all SPI and I2S connections, step-by-step assembly instructions, and Arduino firmware that handles Ethernet configuration, RTP packet reception, and I2S audio output at 48 kHz. Builders will have a working audio streamer capable of receiving network audio streams and converting them to line-level output.

Wiring diagram

Wiring diagram for Ethernet Audio Streamer

Gather all the parts

QtyComponent
1

W5500 Ethernet Module

Hardwired TCP/IP stack SPI Ethernet module based on the WIZnet W5500 IC with integrated RJ45 connector. Supports full TCP/IP stack in hardware (TCP, UDP, ICMP, IPv4, ARP, IGMP, PPPoE) over SPI. Operates on 3.3V logic (most breakout boards include onboard 3.3V LDO and level-shifting for 5V compatibility). Widely used with ESP32 for Modbus TCP and other TCP/IP protocols over LAN. SPI bus can be shared with other SPI devices (e.g. MicroSD) using separate CS pins.

1

PCM5102A I2S stereo DAC breakout

A small board that turns the received digital stereo audio into left and right line-level audio.

1

3.5 mm stereo TRS audio jack

A panel or breakout connector that carries the stereo line-level output to an amplifier or powered speakers.

Assemble it in 5 steps

1. Keep the board unpowered while wiring

Place the ESP32 DevKit, W5500 Ethernet module, PCM5102A DAC board, and 3.5 mm jack where their printed pin labels are easy to see. Leave the ESP32 USB cable unplugged until every wire is checked.

  • Do not connect or remove signal wires while the board is powered; a loose wire can short two pins together.

2. Wire the Ethernet module

Connect the W5500 VCC pin to ESP32 3V3 (power), W5500 GND to ESP32 GND (ground), MOSI to GPIO23 (data), MISO to GPIO19 (data), SCK to GPIO18 (clock), CS to GPIO4 (device-select), and RST to GPIO13 (reset). Plug the W5500 RJ45 socket into your Ethernet switch or router with a normal Ethernet cable.

  • Keep the SPI wires short, especially the SCK wire, to make the network connection more reliable.
  • Make sure the W5500 module is a 3.3 V-compatible version before connecting VCC; applying the wrong supply voltage can damage it.

3. Wire the audio converter

Connect PCM5102A VCC to ESP32 3V3 (power), PCM5102A GND to ESP32 GND (ground), BCK to GPIO27 (audio timing), LCK to GPIO26 (left/right timing), and DIN to GPIO25 (audio data).

  • The DAC and Ethernet module must share the same ESP32 GND pin or ground rail, otherwise the audio data has no reliable reference.
  • Make sure VCC and GND are not swapped — swapped power can damage the DAC board.

4. Connect the line output jack

Connect the DAC LOUT pin to the 3.5 mm jack TIP contact (left audio), DAC ROUT to the jack RING contact (right audio), and the jack SLEEVE contact to ESP32 GND (audio ground). Connect this jack to powered speakers, an amplifier line input, or other line-level equipment—not directly to passive speakers.

  • This output is line level and cannot safely power an unamplified speaker by itself.

5. Check and power the streamer

Check each wire against its printed label, then plug the ESP32 into USB for power. The board receives RTP/L16, 48 kHz, 16-bit stereo packets sent to 192.168.1.50 on UDP port 5004 and sends them to the audio jack.

  • Set the sending computer or audio source to the same Ethernet network and use a compatible RTP/L16 stereo stream.

Review all connections

1. Connections between "w5500_1" and "ESP32"

Functionw5500_1ESP32
powerVCC3V3
groundGNDGND
spiMOSIGPIO 23
spiMISOGPIO 19
spiSCKGPIO 18
digitalRSTGPIO 13
spiCSGPIO 4

2. Connections between "dac_1" and "ESP32"

Functiondac_1ESP32
powerVCC3V3
groundGNDGND
dataBCKGPIO 27
dataLCKGPIO 26
dataDINGPIO 25
analogLOUT → 3.5 mm stereo TRS audio jack TIPEXT
analogROUT → 3.5 mm stereo TRS audio jack RINGEXT

3. Connections between "line_out_1" and "ESP32"

Functionline_out_1ESP32
groundSLEEVEGND

Deploy the firmware

#include <Arduino.h>
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <driver/i2s.h>


// Forward declarations
void setupI2S();
void setupEthernet();
void playRtpPacket(int packetSize);

constexpr int ETH_CS_PIN = 4;
constexpr int ETH_RST_PIN = 13;
constexpr int ETH_SCK_PIN = 18;
constexpr int ETH_MISO_PIN = 19;
constexpr int ETH_MOSI_PIN = 23;
constexpr int I2S_BCK_PIN = 27;
constexpr int I2S_LRCK_PIN = 26;
constexpr int I2S_DOUT_PIN = 25;
constexpr uint16_t RTP_PORT = 5004;
constexpr size_t MAX_UDP_PACKET = 1472;

byte macAddress[] = {0x02, 0xA0, 0x10, 0x00, 0x00, 0x50};
IPAddress localIp(192, 168, 1, 50);
EthernetUDP rtpUdp;
uint8_t packetBuffer[MAX_UDP_PACKET];
int16_t audioBuffer[MAX_UDP_PACKET / 2];

void setupI2S() {
  i2s_config_t config = {};
  config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
  config.sample_rate = 48000;
  config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
  config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
  config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
  config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1;
  config.dma_buf_count = 8;
  config.dma_buf_len = 256;
  config.use_apll = false;
  config.tx_desc_auto_clear = true;
  config.fixed_mclk = 0;
  i2s_driver_install(I2S_NUM_0, &config, 0, nullptr);

  i2s_pin_config_t pins = {};
  pins.bck_io_num = I2S_BCK_PIN;
  pins.ws_io_num = I2S_LRCK_PIN;
  pins.data_out_num = I2S_DOUT_PIN;
  pins.data_in_num = I2S_PIN_NO_CHANGE;
  i2s_set_pin(I2S_NUM_0, &pins);
  i2s_zero_dma_buffer(I2S_NUM_0);
}

void setupEthernet() {
  pinMode(ETH_RST_PIN, OUTPUT);
  digitalWrite(ETH_RST_PIN, LOW);
  delay(20);
  digitalWrite(ETH_RST_PIN, HIGH);
  delay(100);

  SPI.begin(ETH_SCK_PIN, ETH_MISO_PIN, ETH_MOSI_PIN, ETH_CS_PIN);
  Ethernet.init(ETH_CS_PIN);
  Ethernet.begin(macAddress, localIp);
  rtpUdp.begin(RTP_PORT);
}

void playRtpPacket(int packetSize) {
  if (packetSize < 12 || packetSize > (int)sizeof(packetBuffer)) return;
  int received = rtpUdp.read(packetBuffer, packetSize);
  if (received != packetSize) return;

  const uint8_t version = packetBuffer[0] >> 6;
  const bool hasExtension = (packetBuffer[0] & 0x10) != 0;
  const uint8_t csrcCount = packetBuffer[0] & 0x0F;
  if (version != 2) return;

  size_t offset = 12 + (size_t)csrcCount * 4;
  if (offset > (size_t)received) return;
  if (hasExtension) {
    if (offset + 4 > (size_t)received) return;
    const uint16_t extensionWords = ((uint16_t)packetBuffer[offset + 2] << 8) | packetBuffer[offset + 3];
    offset += 4 + (size_t)extensionWords * 4;
  }
  if (offset >= (size_t)received) return;

  size_t payloadBytes = (size_t)received - offset;
  payloadBytes &= ~((size_t)3); // Whole 16-bit left/right sample pairs only.
  if (payloadBytes == 0 || payloadBytes / 2 > sizeof(audioBuffer) / sizeof(audioBuffer[0])) return;

  for (size_t i = 0; i < payloadBytes / 2; ++i) {
    audioBuffer[i] = (int16_t)(((uint16_t)packetBuffer[offset + i * 2] << 8) |
                               packetBuffer[offset + i * 2 + 1]);
  }

  size_t written = 0;
  i2s_write(I2S_NUM_0, audioBuffer, payloadBytes, &written, portMAX_DELAY);
}

void setup() {
  Serial.begin(115200);
  setupI2S();
  setupEthernet();
  Serial.println("AoIP receiver ready: RTP/L16 stereo, 48 kHz, UDP port 5004, IP 192.168.1.50");
}

void loop() {
  int packetSize = rtpUdp.parsePacket();
  if (packetSize > 0) playRtpPacket(packetSize);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik