esp32-audio-player 0.1.0
Audio player engine for ESP32
Loading...
Searching...
No Matches
esp32-audio-player

Audio player engine for the ESP32 in one library: FLAC, MP3, Ogg Vorbis, WAV, AIFF and DSD from an SD card, sample-exact gapless playback, CUE sheets, ReplayGain, a 10-band parametric EQ that imports AutoEQ presets, crossfeed, a queue with shuffle and resume, and output to an I2S DAC or to Bluetooth headphones.

CI Component Registry [License](LICENSE)

Русская версия · API reference · Changelog

The host build of the engine plays two halves of a sine as one album and reproduces the uncut file sample for sample

The engine is portable C17 and also builds on a PC. The recording above is the host command-line player from tools/player_cli: two FLAC files cut at sample 22383, which falls inside a FLAC frame, play back to back and match the uncut reference in all 44100 samples. The same check runs in CI (ctest -R gapless_cli).

Status

Version 0.1.0 has not been run on an ESP32 yet; a photo of the test board and the first measurements (CPU load per format, RAM) will be added after the hardware test.

What is verified:

  • 16 host test programs with 84981 checks pass on Windows with zig cc; CI runs them on Linux with AddressSanitizer and UndefinedBehaviorSanitizer and on macOS. They cover every decoder against reference CRCs of generated test files (29 files), a fuzz pass with 200 mutated copies of each file, gapless joins of FLAC and MP3, CUE splitting, resampling, the DSP chain, tag reading for ID3, APE, Vorbis comments, RIFF, AIFF, DSF, DFF, WavPack and MP4, the library index on 5000 generated files, the queue and the resume files.
  • CI builds the ESP-IDF examples for the ESP32 with ESP-IDF 5.3, 5.5, 6.0 and 6.1, and the I2S example also for the ESP32-S3. Warnings in the library fail the build.
  • CI builds the Arduino examples with the Arduino core 3.1.3 (ESP-IDF 5.3) and the latest core, and with PlatformIO. With the core 3.3.12 the SD-to-I2S sketch takes 613 KB of flash and 25.8 KB of static RAM.

Comparison

esp32-audio-player ESP32-audioI2S
FLAC, MP3, Ogg Vorbis, WAV Yes Yes
AAC, M4A, Opus No Yes
AIFF, DSF and DFF (as DoP) Yes Not listed
Internet radio No Yes
Output sample rate 8 to 192 kHz, follows the file 16 to 48 kHz
Sample-exact gapless Yes, with LAME delay and padding for MP3 Not documented
Equalizer 10-band parametric, AutoEQ import 3-band tone control
Crossfeed, ReplayGain, CUE Yes No
Bluetooth headphones Yes, through esp32-a2dp-xq No
Music library index, resume, scrobble log Yes No
Tested on hardware Not yet Yes

ESP32-audioI2S is the library for web radio and AAC. This one is for music stored on an SD card.

Quick start

Arduino, a PCM5102A on BCK 26, LRCK 25, DIN 22 and an SD card on the default SPI pins with CS 5:

#include <SD.h>
#include <audio_player.h>
void setup() {
SD.begin(5); // mounted at /sd
i2s.bck_gpio = 26; i2s.ws_gpio = 25; i2s.dout_gpio = 22;
player_t *player = audio_player_start(&cfg); // runs in its own task
player_cmd_play_folder(player, "/sd/music", 0, false);
}
void loop() {}
esp32-audio-player: one include for the whole library.
player_t * audio_player_start(const audio_player_esp32_config_t *cfg)
Initialise the port, create the player and start its audio task.
#define AUDIO_PLAYER_ESP32_CONFIG_DEFAULT()
The audio task runs on the second core of dual-core chips, away from Wi-Fi and Bluetooth.
Definition esp32.h:48
audio_sink_t * audio_i2s_sink_create(const audio_i2s_config_t *cfg)
Create the sink.
#define AUDIO_I2S_CONFIG_DEFAULT()
Definition i2s_sink.h:47
struct player player_t
Definition player.h:111
int player_cmd_play_folder(player_t *p, const char *dir, uint32_t start, bool shuffle)
Definition i2s_sink.h:29
int dout_gpio
data to the DAC (DIN)
Definition i2s_sink.h:33
int bck_gpio
bit clock (BCK, SCK on some boards)
Definition i2s_sink.h:31
int ws_gpio
word select (LRCK, LCK)
Definition i2s_sink.h:32
Definition esp32.h:30
audio_sink_t * sink
output, may be NULL and set later with player_cmd_set_sink()
Definition esp32.h:31

The ESP-IDF version of the same program is examples/idf/sd_i2s_player.

Install

ESP-IDF 5.3 or newer:

idf.py add-dependency "olerast67/esp32-audio-player^0.1.0"

PlatformIO (platformio.ini):

lib_deps = https://github.com/olerast67/esp32-audio-player.git#v0.1.0

Arduino IDE with the ESP32 core 3.1 or newer: download esp32-audio-player-0.1.0.zip from Releases and add it with Sketch > Include Library > Add .ZIP Library. For Bluetooth output also install esp32-a2dp-xq.

A board with PSRAM (ESP32-WROVER, ESP32-S3 N8R2 or N16R8) is recommended. Decoder state, read buffers and the 200 ms output FIFO go to PSRAM when it is present; without PSRAM the FIFO takes 100 ms at up to 48 kHz (38 KB of internal RAM).

Features

Formats

Format Details
FLAC 4 to 32 bit, mono and stereo, native and Ogg FLAC, FLAC behind an ID3v2 tag, sample-exact seeking (dr_flac)
MP3 MPEG-1, 2 and 2.5 Layer I, II, III; Xing, Info and VBRI headers; LAME encoder delay and padding removed for gapless (minimp3)
Ogg Vorbis Sample-exact seeking; comment packets of any size are skipped (stb_vorbis)
WAV PCM 8 to 32 bit, IEEE float 32 and 64 bit, WAVE_FORMAT_EXTENSIBLE, RF64 and BW64
AIFF, AIFF-C Big-endian PCM, sowt, fl32, fl64
DSF, DFF DSD64 as DoP at 176.4 kHz and DSD128 as DoP at 352.8 kHz; DST-compressed DFF is not supported

Every sample inside the engine is a 32-bit integer with the audio in the top bits, so 16, 24 and 32-bit files reach an I2S DAC unchanged while all DSP stages are off. The player status reports this case as bitperfect.

Playback

  • Gapless: the next file is opened about 2 s before the current one ends. When both negotiate the same output format, samples continue without a gap, and the DSP and resampler keep their state across the join. A change of format plays out the tail and reopens the output.
  • CUE sheets (FILE, TRACK, INDEX 01, TITLE, PERFORMER, REM GAIN), CP1251 or UTF-8. Consecutive tracks of one image share one decoder.
  • Queue of up to 20000 entries with shuffle, repeat one and repeat all, "play next", jump and remove. Shuffle keeps the current track first and switching it off restores the original order.
  • Resume: player_save_state() writes the queue as UTF-8 queue.m3u8 and the position, volume and modes to player.ini, both replaced atomically. player_restore_state() continues paused or playing.
  • Sleep timer with a 30 s fade-out, volume limit, Rockbox-format .scrobbler.log.

Sound processing

The chain runs in float at the output rate: ReplayGain, preamp, parametric EQ, crossfeed, software volume, limiter, dither.

  • ReplayGain from tags: track, album, or album gain only while an album plays in order; preamp, fallback gain and clipping prevention by peak.
  • Parametric EQ with up to 10 bands of peak, low shelf, high shelf, low pass and high pass (RBJ biquads). eq_preset_parse_autoeq() reads the ParametricEQ.txt files of AutoEQ, and eq_preset_format_autoeq() writes the same format. Built-in presets: Flat, Bass, Treble, Vocal, Loudness.
  • Crossfeed for headphones, cutoff 300 to 2000 Hz and level 1 to 15 dB.
  • Limiter with about 1 ms of lookahead, TPDF dither to the resolution of the output.
  • Resampling for outputs with fixed rates: cascaded half-band decimators for 2:1 and 4:1, a polyphase windowed-sinc filter for other ratios such as 48 to 44.1 kHz. The number of output samples depends only on the number of input samples, so gapless joins stay exact after resampling.

When every stage is off and the volume is at 0 dB, the samples pass untouched.

Library and metadata

  • Tags: ID3v2.2, 2.3 and 2.4, ID3v1, APEv2, Vorbis comments with FLAC pictures, RIFF INFO and id3 chunks, MP4 and M4A atoms. Single-byte text in unknown encodings is detected as UTF-8, CP1251 or Latin-1, so CP1251 tags in old Russian MP3 files read correctly.
  • Folder browsing without an index (fs_list_dir), sorted with collation for English and Russian.
  • Music library index on the SD card: artists, albums, tracks, genres, recently added and audiobooks, built by an incremental scan into new files that replace the old ones atomically. The tests index 5000 generated files.
  • M3U and M3U8 playlists, read and write.

Outputs

  • I2S DAC (audio_i2s_sink_create) without a control bus, for example PCM5102A, UDA1334A, ES9023 or MAX98357A. 32-bit slots, 8 to 192 kHz following each file, optional MCLK, mute and amplifier-enable pins. Mute comes before the clocks stop and after they start. DoP passes to DACs that detect it when accept_dop is set.
  • Bluetooth headphones through esp32-a2dp-xq (audio_player/a2dp_sink.h): SBC-XQ at 452 kbps with ESP-IDF 6.0+, standard SBC otherwise. The player resamples to 44.1 kHz and the headphone buttons control it.
  • Outputs of your own through the audio_sink_t interface of audio_player/sink.h.

Examples

Example ESP-IDF Arduino
Folder on the SD card to an I2S DAC, resume after reset sd_i2s_player SdI2sPlayer
Folder on the SD card to Bluetooth headphones bluetooth_headphones BluetoothHeadphones
AutoEQ preset and crossfeed AutoEQ
Any file to a WAV on the PC tools/player_cli

Architecture

The core never creates threads. Every task may send commands (player_cmd_*); they only go into a queue. One audio task calls player_run_once() in a loop; it decodes about 1024 frames, processes them and writes them to the output, and the output write sets the pace. audio_player_start() creates that task on the second core with priority 18. Memory, logging, locks and the clock come from hooks in audio_player/base.h, which audio_player/esp32.h connects to ESP-IDF; the host tests use the C library defaults.

Host build and tests

cmake -S test -B build/test
cmake --build build/test
ctest --test-dir build/test --output-on-failure

The build gives player_cli, which plays a file, a folder, an M3U playlist or a CUE sheet through the engine into a WAV file (player_cli album/ out.wav --gapless-folder --eq autoeq.txt --rate 44100). On Windows without a C compiler, python -m pip install ziglang cmake ninja and add -G Ninja -DCMAKE_TOOLCHAIN_FILE=<absolute path>/cmake/zig-toolchain.cmake. -DAUDIO_PLAYER_SANITIZE=ON adds AddressSanitizer and UndefinedBehaviorSanitizer.

License

Apache License 2.0, see [LICENSE](LICENSE). Vendored decoders: dr_flac (Unlicense or MIT-0), minimp3 (CC0-1.0), stb_vorbis (MIT or Unlicense); details in [NOTICE](NOTICE) and src/third_party. The test files in test/data are generated by tools/gen_test_vectors.py and released under CC0-1.0.

The engine comes from an ESP32 music player I am building; the player will be published after its own hardware tests. Most of the code was written by Claude (Anthropic) on my instructions; I set the tasks and checked the results with the tests and builds listed under Status.