How the RPi_Pico_WAV_Player Handles WAV File Formats Up to 192KHz/24bit

The RPi_Pico_WAV_Player parses WAV headers to detect PCM or IEEE-float formats at sample rates up to 192kHz and bit depths up to 24-bit, dynamically reconfigures the I²S peripheral on-the-fly, and converts all audio samples to the 32-bit signed PCM format required by the Raspberry Pi Pico's audio driver.

The RPi_Pico_WAV_Player is an open-source project that enables high-resolution audio playback on the Raspberry Pi Pico microcontroller. Unlike fixed-format players, it analyzes each WAV file's RIFF header to extract channel configuration, sample rate, and bit depth, then adjusts its hardware configuration accordingly. This architecture supports mono or stereo PCM streams from standard 44.1kHz/16-bit CD quality up to audiophile-grade 192kHz/24-bit WAV files.

Header Parsing and Format Detection

When playback begins, the PlayWav::skipToDataChunk() method scans the file's RIFF structure to locate the "fmt " chunk. According to lib/PlayAudio/PlayWav.cpp, the parser reads the following fields using little-endian byte helpers:

// lib/PlayAudio/PlayWav.cpp lines 44-66
if (memcmp(chunk_id, "fmt ", 4) == 0) {
    format        = getU16LE(buf + ofs + 8);      // Audio format (1 = PCM, 3 = IEEE-float)
    channels      = getU16LE(buf + ofs + 10);     // 1 = mono, 2 = stereo
    sf            = getU32LE(buf + ofs + 12);     // Sample frequency (44100, 96000, 192000)
    bitRateKbps   = getU32LE(buf + ofs + 16) * 8 / 1000;
    blockBytes    = getU16LE(buf + ofs + 20);     // Bytes per sample frame
    bitsPerSample = getU16LE(buf + ofs + 22);     // 16, 24, or 32
    reinitI2s = (sampFreq != sf);                 // Flag I²S re-init if rate changed
    sampFreq = sf;
}

The player recognizes PCM format (format == 1) and IEEE-float format (format == 3), supporting bit depths of 16, 24, and 32 bits. After parsing the header, the code locates the "data" chunk, calculates the payload size, and aligns the read buffer so that the first audio sample starts at byte zero.

Dynamic I²S Reconfiguration for Sample Rate Changes

If the newly detected sampFreq differs from the currently configured rate, the reinitI2s flag triggers a hardware reset before the next buffer fill. The i2s_setup() function in lib/PlayAudio/i2s_audio_init.cpp handles this transition:

// lib/PlayAudio/i2s_audio_init.cpp lines 34-43
void i2s_setup(uint32_t samp_freq, audio_buffer_pool_t*& ap)
{
    if (_producer_pool != nullptr) {
        ap = nullptr;
        i2s_audio_deinit();   // Clean previous I²S instance
    }
    i2s_audio_init(samp_freq);
    ap = _producer_pool;
}

This ensures the Pico's audio_format.sample_freq matches the source file exactly, allowing glitch-free playback when switching between 44.1kHz tracks and high-resolution 192kHz files.

Sample Conversion Pipeline

During decoding, PlayWav::decode() converts raw file data into the 32-bit signed integer format (AUDIO_PCM_FORMAT_S32) that the Pico's audio driver requires. The conversion logic uses a switch statement keyed by format and bit depth:

// lib/PlayAudio/PlayWav.cpp lines 101-115
int32_t* samples = reinterpret_cast<int32_t*>(buffer->buffer->bytes);
for (int i = 0; i < buffer->sample_count; i++, buf += blockBytes) {
    for (int j = 0; j < 2; j++) {
        int base = (channels == 2) ? j * bitsPerSample / 8 : 0;
        int32_t buf_s32;
        switch ((format << 8) | bitsPerSample) {
            case ((FMT_PCM << 8) | 16): 
                buf_s32 = (buf[base+1] << 24) | (buf[base+0] << 16); 
                break;
            case ((FMT_PCM << 8) | 24): 
                buf_s32 = (buf[base+2] << 24) | (buf[base+1] << 16) | (buf[base+0] << 8); 
                break;
            case ((FMT_PCM << 8) | 32): 
                buf_s32 = (buf[base+3] << 24) | (buf[base+2] << 16) | 
                          (buf[base+1] << 8) | (buf[base+0] << 0); 
                break;
            case ((FMT_FLOAT << 8) | 32): 
                buf_s32 = 0;   // Float implementation pending
                break;
            default: 
                buf_s32 = 0;   // Unsupported format yields silence
        }
        samples[i*2+j] = (int32_t)((int64_t)buf_s32 * vol_table[volume] / 65536) + DAC_ZERO;
    }
}

16-bit PCM samples are shifted left 16 bits to occupy the high word, 24-bit PCM is shifted left 8 bits, and 32-bit PCM maps directly. The resulting value is scaled by a volume table and offset by DAC_ZERO (the hardware midpoint) before being written to the output buffer.

Supported WAV Specifications

The RPi_Pico_WAV_Player supports the following configurations according to the source code analysis:

  • Sample Rates: 44.1kHz, 48kHz, 88.2kHz, 96kHz, 176.4kHz, and 192kHz
  • Bit Depths: 16-bit, 24-bit, and 32-bit integer PCM
  • Channel Modes: Mono (1 channel) and Stereo (2 channels)
  • Format Codes: PCM (0x0001) and IEEE-float (0x0003) — though float playback currently outputs silence as the conversion is not yet implemented
  • Container: Standard RIFF/WAV with fmt and data chunks

Unsupported formats like ADPCM or μ-law fall through to the default case, producing zeroed output.

Buffer Management and Playback Flow

The architecture uses a dual-buffer system to maintain continuous playback. The ReadBuffer class provides a lock-free circular buffer for raw file data, while i2s_audio_init() creates an audio_buffer_pool_t for DMA transfer to the I²S peripheral.

The PlayAudio::decode() method operates in a background task, repeatedly executing this sequence:

  1. Check rdbuf->getLeft() against RDBUF_THRESHOLD to ensure sufficient data is pre-loaded
  2. Calculate available samples as min(buffer_size, rdbuf->getLeft() / blockBytes)
  3. Convert samples using the format-specific logic above
  4. Call give_audio_buffer() to hand the filled buffer to the I²S driver
  5. If reinitI2s is true, invoke i2s_setup() before processing the next block

When the file's data chunk is exhausted, stop() is called automatically to terminate the playback loop.

Summary

  • The RPi_Pico_WAV_Player reads WAV headers in PlayWav::skipToDataChunk() to detect sample rates up to 192kHz and bit depths up to 24-bit.
  • Dynamic I²S reconfiguration occurs via i2s_setup() whenever a new file's sample rate differs from the current hardware setting.
  • All audio is converted to 32-bit signed PCM in PlayWav::decode(), with specific bit-shifting logic for 16-bit and 24-bit source material.
  • The player supports mono and stereo PCM formats; 32-bit float files are recognized but currently render as silence.
  • A lock-free buffer pool architecture ensures continuous playback of high-resolution audio streams.

Frequently Asked Questions

Does the RPi_Pico_WAV_Player support 32-bit floating-point WAV files?

While the header parser recognizes IEEE-float format (format == 3), the conversion logic in PlayWav::decode() contains a stub that sets buf_s32 = 0 for float samples. As of the current implementation, 32-bit float WAV files will play as silence, though the infrastructure exists to add float-to-integer conversion in future updates.

What is the maximum audio quality supported by the RPi_Pico_WAV_Player?

The player supports stereo PCM WAV files at 192kHz sample rate with 24-bit depth. The hardware can accept 32-bit PCM files as well, but 24-bit is the highest resolution commonly used for high-fidelity audio distribution that the player handles natively.

How does the player handle mono WAV files?

When channels == 1, the conversion loop sets base = 0 for both left and right output channels. This duplicates the single mono sample into both stereo output channels, effectively creating a dual-mono playback from the mono source file.

Can the RPi_Pico_WAV_Player play compressed WAV formats like ADPCM?

No. The switch statement in PlayWav::decode() only handles PCM (format 1) and IEEE-float (format 3). Any other format code falls through to the default case, which assigns zero to the output buffer, resulting in silent playback for ADPCM, μ-law, or other compressed WAV variants.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →