RPi Pico WAV Player Audio File Format Limitations: A Complete Technical Guide

The RPi Pico WAV Player only supports uncompressed PCM WAV files with 16, 24, or 32-bit integer depth, mono or stereo channels, and standard RIFF headers up to 192 kHz sample rates.

The elehobica/rpi_pico_wav_player repository implements a lightweight audio player for Raspberry Pi Pico that deliberately restricts format support to standard WAV containers. Understanding these limitations is essential for preparing compatible audio libraries and troubleshooting playback failures.

What Audio Formats Are Supported?

The player implements a narrow compatibility window optimized for the RP2040's constrained resources.

Supported WAV Specifications

The decoder recognizes only uncompressed PCM audio with the following parameters:

  • Container: Standard RIFF/WAV (not RF64 or BWF)
  • Format Tag: FMT_PCM (value 1) for integer PCM; FMT_FLOAT (value 3) is parsed but not decoded
  • Bit Depth: 16-bit, 24-bit, or 32-bit integer samples
  • Channels: Mono (1) or Stereo (2) only
  • Sample Rates: Up to 192 kHz (hardware limited by I²S/PCM5102 DAC configuration)

Any deviation from these specifications results in the file being excluded from the UI or decoded as silence.

How Format Restrictions Are Enforced in Code

The limitation is not merely documentation—it is hardcoded across multiple layers of the architecture.

File Extension Filtering in file_menu_FatFs.c

Before any audio processing occurs, the file browser filters directory contents. In lib/file_menu/file_menu_FatFs.c at lines 99-107, the function file_menu_match_ext() implements a strict string comparison:

// From lib/file_menu/file_menu_FatFs.c
bool file_menu_match_ext(const char *filename) {
    const char *ext = strrchr(filename, '.');
    if (ext == NULL) return false;
    // Only returns true for .wav or .WAV extensions
    return (strcasecmp(ext, ".wav") == 0);
}

Consequently, MP3, FLAC, OGG, AAC, and other formats never appear in the player's file menu.

Header Parsing in PlayWav.cpp

Once a .wav file is selected, PlayWav::skipToDataChunk() in lib/PlayAudio/PlayWav.cpp (lines 44-66) parses the RIFF structure. This function extracts critical metadata from the fmt chunk:

  • Format tag (format): Must be 1 (PCM) or 3 (Float) to proceed
  • Channels (channels): Stored but assumed ≤ 2 in downstream processing
  • Sample rate (sampleRate): Validated against hardware capabilities
  • Bits per sample (bitsPerSample): Determines decoder branch
  • Block alignment (blockBytes): Used for buffer calculations

The parser does not implement extended fmt sub-chunks or WAVE_FORMAT_EXTENSIBLE GUIDs. If the header contains compression codes like ADPCM (0x11) or MPEG (0x50), the player treats them as unknown and will likely produce silence or fail to initialize the audio stream.

Decoder Limitations in the Audio Pipeline

The actual audio decoding occurs in PlayWav::decode(), where a switch statement (lines 104-112) handles sample conversion:

// Simplified from lib/PlayAudio/PlayWav.cpp
int32_t PlayWav::decode(uint8_t *in_buf, int32_t *out_buf, size_t samples) {
    switch (bitsPerSample) {
        case 16: // Convert int16 to int32
        case 24: // Convert packed 24-bit to int32
        case 32: // Pass through int32
            // ... PCM processing ...
            break;
        default:
            return 0; // Silence for unsupported bit depths
    }
    // Channel handling: assumes max 2 channels
    for (int j = 0; j < 2; j++) {
        out_buf[j] = (j < channels) ? sample : sample; // Duplicate mono
    }
}

Critical constraints visible in this code:

  • No floating-point decoding: While FMT_FLOAT (value 3) is recognized in the header, the decode switch lacks a case for float conversion, resulting in zeroed output buffers.
  • Hardcoded stereo output: The channel loop for (int j = 0; j < 2; j++) always writes two samples. Mono files work because the sample is duplicated, but multi-channel files (5.1 surround, etc.) will have channels truncated or misaligned.
  • Integer PCM only: Only 16, 24, and 32-bit integer samples reach the DAC. Other bit depths (8-bit, 20-bit) return silence.

Unsupported Formats and Why They Fail

Understanding the failure modes helps diagnose why specific files produce no sound.

Compressed Audio (MP3, FLAC, AAC)

Lossy and lossless compressed formats require decompression algorithms (codecs) that would exceed the RP2040's limited RAM and CPU budget. The player architecture delegates format support to the UI layer's extension filter, so these files never reach the decoder. Attempting to rename a .mp3 to .wav will result in a RIFF header parse failure in skipToDataChunk().

Floating-Point PCM

Professional audio workflows often use 32-bit float WAVs for headroom. While the header parser recognizes format tag 3 (WAVE_FORMAT_IEEE_FLOAT), the decode() function lacks the conversion logic to normalize float values to the integer range expected by the PCM5102 DAC. The result is a valid playback session with silent output.

Multi-Channel Audio Beyond Stereo

Surround sound files (4.0, 5.1, 7.1) contain more than two channels. The decoder's fixed for (int j = 0; j < 2; j++) loop in PlayWav::decode() assumes a maximum of two interleaved samples per frame. Additional channels in the file stream cause channel data to be misinterpreted as audio samples, resulting in white noise or decoding errors.

Extended WAV Variants (RF64, BWF)

The Broadcast Wave Format (BWF) and RF64 extensions support files larger than 4GB and embed metadata chunks. The parser in skipToDataChunk() searches for the data chunk offset using standard RIFF structure assumptions. It does not handle the ds64 chunk required for RF64 or parse the bext chunk. Large files will not have their data pointers correctly resolved, causing playback failure or crashes.

Code Examples: Working Within the Limitations

Playing a Compatible WAV File

The following snippet demonstrates proper initialization of a WAV player with a verified compatible file:

#include "PlayAudio/PlayAudio.h"
#include "PlayAudio/PlayWav.h"

int main() {
    // Initialize the audio subsystem and I2S interface
    PlayAudio::initialize();

    // Instantiate WAV player - only accepts standard PCM files
    PlayWav player;
    
    // This will succeed if the file meets all constraints:
    // - Extension: .wav
    // - Format: PCM (1) or Float (3) header
    // - Bit depth: 16, 24, or 32-bit integer
    // - Channels: 1 or 2
    player.play("music/01_Track.wav");
}

The play() method internally calls skipToDataChunk() to validate the RIFF header before streaming begins.

Handling Unsupported File Types

When integrating file selection logic, you can verify extension compatibility before attempting playback:

#include "file_menu/file_menu_FatFs.h"

void processFile(const char* filename) {
    // Check if file matches supported extension
    if (!file_menu_match_ext(filename)) {
        // This filters out MP3, FLAC, OGG, etc.
        printf("Skipping unsupported file: %s\n", filename);
        return;
    }
    
    // Proceed with WAV playback
    PlayWav player;
    player.play(filename);
}

This pattern prevents the decoder from attempting to parse non-WAV headers, saving processing cycles and avoiding undefined behavior.

Summary

The RPi Pico WAV Player enforces strict audio format constraints at multiple architectural levels:

  • File Extension: Only .wav and .WAV files appear in the UI, filtered by file_menu_match_ext() in lib/file_menu/file_menu_FatFs.c
  • Container Format: Standard RIFF headers only; RF64 and BWF extensions are unsupported
  • Codec: Uncompressed PCM integer data only; compressed codecs and IEEE float formats are parsed but decode to silence
  • Bit Depth: 16-bit, 24-bit, or 32-bit integer PCM
  • Channels: Mono or stereo only; multi-channel surround files are not handled
  • Sample Rate: Up to 192 kHz, limited by I²S hardware configuration

Any deviation from these specifications results in the file being hidden from the file browser or producing silent/invalid output.

Frequently Asked Questions

Can the RPi Pico WAV Player play MP3 files?

No. The player cannot decode MP3 or any compressed audio format. The file menu explicitly filters for .wav extensions in file_menu_match_ext(), preventing MP3 files from appearing in the browser. Even if renamed to .wav, the RIFF header parser would reject the invalid structure.

What is the maximum bit depth supported by the RPi Pico WAV Player?

The decoder supports 16-bit, 24-bit, and 32-bit integer PCM. These are handled in PlayWav::decode() where samples are zero-padded or sign-extended to 32-bit integers for the DAC. 8-bit PCM and 32-bit floating-point formats are not supported and will play as silence.

Why does my 32-bit float WAV file play silence?

While the header parser recognizes the WAVE_FORMAT_IEEE_FLOAT (format tag 3) in skipToDataChunk(), the decode() function lacks a conversion case for floating-point samples. The switch statement only handles integer bit depths (16, 24, 32), causing float files to fall through to the default case that returns zeroed buffers.

Is there a file size limit for WAV files on the RPi Pico WAV Player?

The player uses standard RIFF structure parsing that assumes 32-bit chunk sizes, limiting files to approximately 4 GB. The parser does not implement the RF64 extension required for larger files. Additionally, practical limits depend on SD card read speeds; high-bitrate files (24-bit/192kHz stereo) require sustained throughput that may cause buffer underruns on slower cards.

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 →