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

> Learn how the RPi Pico WAV Player supports up to 192KHz/24bit WAV files. Discover its dynamic I2S reconfiguration and on-the-fly audio sample conversion.

- Repository: [Elehobica/rpi_pico_wav_player](https://github.com/elehobica/rpi_pico_wav_player)
- Tags: deep-dive
- Published: 2026-03-01

---

**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`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/lib/PlayAudio/PlayWav.cpp), the parser reads the following fields using little-endian byte helpers:

```cpp
// 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`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/lib/PlayAudio/i2s_audio_init.cpp) handles this transition:

```cpp
// 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:

```cpp
// 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.