esphome/micro-mp3

0.1.0

Latest
uploaded 5 hours ago
OpenCore MP3 decoder library for ESP32 - MP3 audio decoding with ESP-IDF support

readme

# microMP3 - Embedded MP3 Decoder Wrapper

[![CI](https://github.com/esphome-libs/micro-mp3/actions/workflows/ci.yml/badge.svg)](https://github.com/esphome-libs/micro-mp3/actions/workflows/ci.yml)

Streaming MP3 decoder for embedded devices. Fixed-point decoder forked from OpenCore with frame synchronization, PSRAM-aware allocation, and lazy initialization. Supports MPEG 1, 2, and 2.5 Layer III.

[![OHF - Open Home Foundation](https://img.shields.io/badge/OHF-Open_Home_Foundation-blue)](https://www.openhomefoundation.org/)

## Features

- **Streaming decode**: Decodes directly from the caller's buffer when a complete MP3 frame is available, avoiding an intermediate copy. Falls back to internal buffering only when frames span chunk boundaries.
- **MP3 frame synchronization**: Built-in frame header parsing handles sync-word detection and frame-boundary alignment. No external demuxer needed.
- **ID3v2 tag skipping**: Automatically detects and skips ID3v2 metadata tags, even when they span chunk boundaries.
- **PSRAM-aware allocation**: Configurable memory placement with automatic fallback.
- **MPEG version support**: MPEG 1 (44.1/48/32kHz), MPEG 2 (22.05/24/16kHz), and MPEG 2.5 (11.025/12/8kHz) Layer III
- **VBR compatible**: Bitrate reported per-frame from decoded header data
- **Probe on first frame**: Stream format (sample rate, channel count, bitrate) determined automatically from the first decoded frame before any PCM is written to the caller's buffer
- **Built-in equalizer**: 7 preset EQ modes (flat, bass boost, rock, pop, jazz, classical, talk) applied in the frequency domain. Switchable per-frame.

## Usage Example

### Embedded

```cpp
#include "micro_mp3/mp3_decoder.h"

micro_mp3::Mp3Decoder decoder;  // Constructor always succeeds (lazy init)

// Heap-allocate on embedded targets to avoid stack overflow
int16_t* pcm_buffer = new int16_t[micro_mp3::MP3_MAX_SAMPLES_PER_FRAME *
                                   micro_mp3::MP3_MAX_OUTPUT_CHANNELS];  // 4608 bytes

while (have_data) {
    size_t consumed = 0, samples = 0;
    micro_mp3::Mp3Result result = decoder.decode(
        input_ptr, input_len,
        reinterpret_cast<uint8_t*>(pcm_buffer),
        micro_mp3::MP3_MIN_OUTPUT_BUFFER_BYTES,
        consumed, samples
    );

    input_ptr += consumed;
    input_len -= consumed;

    if (result == micro_mp3::MP3_STREAM_INFO_READY) {
        // Stream format parsed from header, no PCM yet
        setup_pipeline(decoder.get_sample_rate(), decoder.get_channels());
        continue;
    }
    if (result == micro_mp3::MP3_DECODE_ERROR) {
        continue;  // Skip corrupt frame, recoverable
    }
    if (result < 0) {
        break;  // Fatal error (allocation failure, invalid input, etc.)
    }

    if (samples > 0) {
        // samples is per-channel; total int16_t values = samples * channels
        process_audio(pcm_buffer, samples, decoder.get_channels());
    }
}

delete[] pcm_buffer;
```

See the [decode benchmark example](examples/decode_benchmark) for a complete working example.

### Host Tool

A standalone `mp3_to_wav` converter is included for testing on macOS/Linux:

```bash
cd host_examples/mp3_to_wav
mkdir build && cd build && cmake .. && make
./mp3_to_wav input.mp3 output.wav
```

## API Reference

### `Mp3Decoder`

| Member | Description |
| ------ | ----------- |
| `Mp3Decoder()` | Constructor, always succeeds, no allocations |
| `~Mp3Decoder()` | Destructor, frees all resources |
| `decode(input, input_len, output, output_size, bytes_consumed, samples_decoded)` | Decode one MP3 frame; see result codes below |
| `get_sample_rate()` | Sample rate in Hz (0 until first successful decode) |
| `get_channels()` | Output channel count: 1 (mono) or 2 (stereo); 0 until first decode |
| `get_bit_depth()` | Always 16 |
| `get_bytes_per_sample()` | Always 2 |
| `get_bitrate()` | Bitrate in kbps (e.g., 128); may vary frame-to-frame for VBR |
| `get_version()` | MPEG version: `MP3_MPEG1`, `MP3_MPEG2`, or `MP3_MPEG2_5` |
| `get_samples_per_frame()` | PCM samples per channel per frame (1152 for MPEG1, 576 for MPEG2/2.5; 0 until first decode) |
| `get_min_output_buffer_bytes()` | Always `MP3_MIN_OUTPUT_BUFFER_BYTES` (4608) |
| `set_equalizer(eq)` | Set equalizer preset; takes effect on next `decode()` call |
| `get_equalizer()` | Current `Mp3Equalizer` preset |
| `is_initialized()` | True once decoder memory has been allocated |
| `reset()` | Free all state; next `decode()` call re-initializes |

### Result Codes (`Mp3Result`)

| Code | Value | Meaning |
| ---- | ----- | ------- |
| `MP3_OK` | 0 | Success; check `samples_decoded` |
| `MP3_NEED_MORE_DATA` | 1 | Partial frame buffered; feed more data and call again |
| `MP3_STREAM_INFO_READY` | 2 | Stream format parsed from header; no PCM yet; advance by `bytes_consumed` |
| `MP3_INPUT_INVALID` | -1 | Null pointer or bad input |
| `MP3_ALLOCATION_FAILED` | -2 | Memory allocation failed |
| `MP3_OUTPUT_BUFFER_TOO_SMALL` | -3 | Output buffer smaller than `MP3_MIN_OUTPUT_BUFFER_BYTES` |
| `MP3_DECODE_ERROR` | -4 | Corrupt/invalid frame (recoverable; advance by `bytes_consumed`) |

Use `result < 0` to check for any error. Use `result >= 0` for non-error (success or informational).

### Constants

| Constant | Value | Description |
| -------- | ----- | ----------- |
| `MP3_MAX_SAMPLES_PER_FRAME` | 1152 | Max PCM samples per channel per frame (MPEG1) |
| `MP3_MAX_OUTPUT_CHANNELS` | 2 | Max output channels |
| `MP3_MIN_OUTPUT_BUFFER_BYTES` | 4608 | Min output buffer size (1152 x 2ch x 2 bytes) |
| `MP3_INPUT_BUFFER_SIZE` | 8192 | Internal input buffer size |

### Equalizer Presets (`Mp3Equalizer`)

| Preset | Description |
| ------ | ----------- |
| `MP3_EQ_FLAT` | No equalization (default) |
| `MP3_EQ_BASS_BOOST` | Boost low frequencies relative to high |
| `MP3_EQ_ROCK` | Bass + mid emphasis |
| `MP3_EQ_POP` | Mid-high cut |
| `MP3_EQ_JAZZ` | Low-mid emphasis |
| `MP3_EQ_CLASSICAL` | Low emphasis |
| `MP3_EQ_TALK` | Mid emphasis, high cut |

The equalizer operates on 32 subbands in the frequency domain during decode. All non-flat presets only attenuate (gains ≤ 0 dB), so they will not introduce clipping but may reduce overall volume. The preset can be changed between `decode()` calls and takes effect on the next `decode()` call:

```cpp
decoder.set_equalizer(micro_mp3::MP3_EQ_BASS_BOOST);
// ... subsequent decode() calls use bass boost
decoder.set_equalizer(micro_mp3::MP3_EQ_FLAT);
// ... back to flat
```

## Configuration

```bash
idf.py menuconfig
# Navigate to: Component config → MP3 Decoder
```

### Memory Placement

Decoder state memory can be configured with four placement options:

| Option | Default | Description |
| ------ | ------- | ----------- |
| `CONFIG_MP3_DECODER_PREFER_PSRAM` | y | Try PSRAM first, fall back to internal RAM |
| `CONFIG_MP3_DECODER_PREFER_INTERNAL` | | Try internal RAM first, fall back to PSRAM |
| `CONFIG_MP3_DECODER_PSRAM_ONLY` | | Strict PSRAM; fails if unavailable |
| `CONFIG_MP3_DECODER_INTERNAL_ONLY` | | Never use PSRAM |

Prefer PSRAM (the default) conserves internal RAM at a slight performance cost. Prefer internal RAM for better decode throughput when RAM is plentiful.

## Memory Usage

| Allocation | Size | Notes |
| ---------- | ---- | ----- |
| Decoder state | `sizeof(tmp3dec_file)` | Allocated via `pvmp3_decoderMemRequirements()`; PSRAM preferred by default |
| Internal input buffer | 8KB | MP3 frame accumulation (`MP3_INPUT_BUFFER_SIZE`) |
| Probe output buffer | 4.5KB | Scratch buffer for first-frame probe; freed after `reset()` |
| PCM output buffer | up to 4.5KB | User-provided; `MP3_MIN_OUTPUT_BUFFER_BYTES` (4608 bytes) |

## License

[Apache License 2.0](LICENSE)

## Links

- [OpenCore MP3 Decoder](https://android.googlesource.com/platform/external/opencore/)
- [ISO 11172-3 (MP3 specification)](https://www.iso.org/standard/22412.html)
- [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/index.html)

Links

Supports all targets

Maintainer

  • Kevin Ahrendt <kevin.ahrendt@openhomefoundation.org>

License: Apache-2.0

To add this component to your project, run:

idf.py add-dependency "esphome/micro-mp3^0.1.0"

download archive

Stats

  • Archive size
    Archive size ~ 3.19 MB
  • Downloaded in total
    Downloaded in total 0 times
  • Downloaded this version
    This version: 0 times

Badge

esphome/micro-mp3 version: 0.1.0
|