Benchmarks Opus encoding performance across a matrix of settings using two 30-second audio clips. Tests speech encoding (SILK codec, low bitrates) and music encoding (CELT codec, high bitrates), reporting per-frame timing statistics and actual vs target bitrate.
- Two embedded 30-second test audio clips (public domain):
- SPEECH (SILK): 16kHz mono, tests low-bitrate encoding (10-32 kbit/s)
- MUSIC (CELT): 48kHz stereo, tests high-bitrate encoding (64-192 kbit/s)
- Full test matrix: complexity levels (0, 2, 5, 8, 10) x application modes (VOIP, AUDIO) x bitrates
- Per-frame encoding timing with statistical analysis (min/max/avg/stddev)
- Only encodes are timed (decode time excluded from measurements)
- Actual vs target bitrate comparison
- Auto-skip: stops test series when encoding becomes slower than real-time
- Pre-configured for maximum performance (240MHz, PSRAM, fixed-point)
| Mode | Complexity | Bitrates |
|---|---|---|
| VOIP | 0, 2, 5, 8, 10 | 10k, 16k, 24k, 32k |
| AUDIO | 0, 2, 5, 8, 10 | 10k, 16k, 24k, 32k |
| Mode | Complexity | Bitrates |
|---|---|---|
| AUDIO | 0, 2, 5, 8, 10 | 64k, 96k, 128k, 192k |
- PlatformIO (recommended) OR ESP-IDF v5.0 or later
- ESP32-S3 development board with PSRAM
PlatformIO provides a simplified build process with automatic dependency management.
cd examples/encode_benchmark
# Build the project
pio run
# Upload and monitor
pio run -t upload -t monitorThe PlatformIO configuration uses the parent microOpus repository as a component, so no additional setup is required.
cd examples/encode_benchmark
idf.py set-target esp32s3
idf.py build
idf.py flash monitorThe default configuration is optimized for maximum performance. To customize:
- Edit
sdkconfig.defaultsto change Opus-specific settings - Use
pio run -t menuconfigfor full ESP-IDF configuration
idf.py menuconfigNavigate to Component config → Opus Audio Codec to adjust:
- Memory allocation mode (THREADSAFE_PSEUDOSTACK, NONTHREADSAFE_PSEUDOSTACK, USE_ALLOCA)
- Floating-point vs fixed-point implementation
- Memory preferences (PSRAM vs internal RAM for state/pseudostack)
- Pseudostack size
Each iteration runs through all encoder configurations for both audio types:
I (1019) ENCODE_BENCH: === ESP32-S3 Opus Encode Benchmark ===
I (1019) ENCODE_BENCH: Audio sources:
I (1029) ENCODE_BENCH: SPEECH (SILK): 38196 bytes, 16 kHz, 1 channel
I (1029) ENCODE_BENCH: MUSIC (CELT): 497933 bytes, 48 kHz, 2 channels
I (1039) ENCODE_BENCH: Processing: decode packet -> encode packet (timing encode only)
I (1049) ENCODE_BENCH: Test matrix: 40 speech configs + 20 music configs = 60 total
I (1049) ENCODE_BENCH: Free heap: 17070164 bytes
I (1059) ENCODE_BENCH: Free PSRAM: 16774624 bytes
I (1059) ENCODE_BENCH: Free Internal: 295540 bytes
I (1069) ENCODE_BENCH: ========== Iteration 1 ==========
I (1079) ENCODE_BENCH: === SPEECH Encoding Tests (40 configurations) ===
I (1079) ENCODE_BENCH: --- SPEECH: VOIP, complexity=0, bitrate=10000 ---
I (9149) ENCODE_BENCH: Frame (us): min=3370 max=4684 avg=4082.5 sd=463.9 (n=1500)
I (9149) ENCODE_BENCH: Total: 6123 ms (30.0s audio), RTF: 0.204 (4.9x real-time)
I (9149) ENCODE_BENCH: Encoded: 34542 bytes (9211 bps actual, target 10000 bps)
...
I (589889) ENCODE_BENCH: --- SPEECH: AUDIO, complexity=10, bitrate=32000 ---
I (602159) ENCODE_BENCH: Frame (us): min=4251 max=8040 avg=6725.8 sd=594.7 (n=1500)
I (602159) ENCODE_BENCH: Total: 10088 ms (30.0s audio), RTF: 0.336 (3.0x real-time)
I (602159) ENCODE_BENCH: Encoded: 121566 bytes (32418 bps actual, target 32000 bps)
I (602169) ENCODE_BENCH: === MUSIC Encoding Tests (20 configurations) ===
I (602179) ENCODE_BENCH: --- MUSIC: AUDIO, complexity=0, bitrate=64000 ---
I (618689) ENCODE_BENCH: Frame (us): min=5530 max=6602 avg=6330.5 sd=98.6 (n=1500)
I (618689) ENCODE_BENCH: Total: 9495 ms (30.0s audio), RTF: 0.317 (3.2x real-time)
I (618699) ENCODE_BENCH: Encoded: 241654 bytes (64441 bps actual, target 64000 bps)
...
I (1041739) ENCODE_BENCH: --- MUSIC: AUDIO, complexity=10, bitrate=192000 ---
I (1041739) ENCODE_BENCH: Frame (us): min=8468 max=16662 avg=12890.2 sd=1985.3 (n=1500)
I (1041749) ENCODE_BENCH: Total: 19335 ms (30.0s audio), RTF: 0.645 (1.6x real-time)
I (1041749) ENCODE_BENCH: Encoded: 721980 bytes (192528 bps actual, target 192000 bps)
I (1041759) ENCODE_BENCH: === Iteration 1 Summary ===
I (1041759) ENCODE_BENCH: All encodes successful: YES
I (1041769) ENCODE_BENCH: Free heap: 16949064 bytes
- Frame (us): Per-frame encode time statistics (min/max/avg/sd in microseconds, n = frame count)
- Total: Wall-clock time spent encoding all frames
- RTF: Real-Time Factor (encode_time / audio_duration). RTF < 1 means faster than real-time
- Nx real-time: How many times faster than real-time encoding (1/RTF)
- Encoded: Compressed size and actual bitrate vs target bitrate
When encoding becomes slower than real-time (RTF > 1.0), the benchmark skips remaining configurations in that audio type since higher complexity/bitrate settings will be even slower:
W (95000) ENCODE_BENCH: RTF > 1.0, skipping remaining MUSIC tests (higher settings will be slower)
Results from ESP32-S3 at 240MHz using fixed-point arithmetic (CONFIG_OPUS_FLOATING_POINT=n). Fixed-point encoding is significantly faster than floating-point on ESP32-S3, especially for SILK at higher complexity levels where floating-point fails to encode in real-time.
Values show real-time multiplier with RTF in parentheses.
| Complexity | 10 kbit/s | 16 kbit/s | 24 kbit/s | 32 kbit/s |
|---|---|---|---|---|
| 0 | 4.9x (0.20) | 3.9x (0.26) | 3.8x (0.26) | 3.8x (0.26) |
| 2 | 2.9x (0.34) | 2.9x (0.34) | 2.9x (0.34) | 2.9x (0.35) |
| 5 | 2.1x (0.48) | 2.1x (0.49) | 2.1x (0.49) | 2.0x (0.49) |
| 8 | 1.5x (0.68) | 1.5x (0.68) | 1.5x (0.68) | 1.5x (0.68) |
| 10 | 1.5x (0.68) | 1.5x (0.68) | 1.5x (0.68) | 1.5x (0.68) |
| Complexity | 10 kbit/s | 16 kbit/s | 24 kbit/s | 32 kbit/s |
|---|---|---|---|---|
| 0 | 5.0x (0.20) | 3.9x (0.26) | 5.5x (0.18) | 5.4x (0.18) |
| 2 | 3.0x (0.34) | 2.9x (0.34) | 4.6x (0.22) | 4.6x (0.22) |
| 5 | 2.1x (0.48) | 2.1x (0.48) | 3.0x (0.33) | 3.0x (0.33) |
| 8 | 1.5x (0.68) | 1.5x (0.67) | 3.0x (0.34) | 3.0x (0.34) |
| 10 | 1.5x (0.68) | 1.5x (0.67) | 3.0x (0.34) | 3.0x (0.34) |
Bold = CELT/SILK hybrid (faster than SILK at same bitrate)
| Complexity | 64 kbit/s | 96 kbit/s | 128 kbit/s | 192 kbit/s |
|---|---|---|---|---|
| 0 | 3.2x (0.32) | 3.0x (0.34) | 2.8x (0.35) | 2.6x (0.38) |
| 2 | 2.6x (0.38) | 2.5x (0.41) | 2.4x (0.42) | 2.3x (0.44) |
| 5 | 2.0x (0.51) | 1.9x (0.53) | 1.8x (0.55) | 1.8x (0.57) |
| 8 | 1.8x (0.54) | 1.7x (0.59) | 1.6x (0.62) | 1.6x (0.65) |
| 10 | 1.8x (0.54) | 1.7x (0.59) | 1.6x (0.62) | 1.6x (0.65) |
| Finding | Details |
|---|---|
| Complexity 8 = 10 | No performance difference between complexity 8 and 10 |
| CELT faster than SILK | At 24+ kbit/s in AUDIO mode, encoder switches to CELT (~2x faster) |
| All configs real-time capable | Worst case 1.5x real-time (complexity 8/10 VOIP speech) |
| Bitrate effect (SILK) | Minimal impact on encode time |
| Bitrate effect (CELT) | Higher bitrates slightly slower (more data to process) |
| Codec | Best Case | Worst Case | Notes |
|---|---|---|---|
| SILK (speech) | 4.9x @ c=0 | 1.5x @ c=8+ | Bitrate has little effect on speed |
| CELT (speech) | 5.5x @ c=0 | 3.0x @ c=8+ | ~2x faster than SILK at same complexity |
| CELT (music) | 3.2x @ c=0 | 1.6x @ c=8+ | Stereo 48kHz more demanding than mono 16kHz |
Results with CONFIG_OPUS_FLOATING_POINT=y for comparison. Floating-point is significantly slower for SILK encoding.
| Mode | Complexity | 10 kbit/s | 16 kbit/s | Higher |
|---|---|---|---|---|
| VOIP | 0 | 1.1x (0.87) | 0.7x (1.53) | skipped |
| AUDIO | 0 | 1.1x (0.87) | 0.7x (1.53) | skipped |
Bold = Slower than real-time (RTF > 1.0). Higher complexity levels were not tested because complexity 0 already fails at 16 kbit/s.
| Complexity | 64 kbit/s | 96 kbit/s | 128 kbit/s | 192 kbit/s |
|---|---|---|---|---|
| 0 | 2.9x (0.34) | 2.7x (0.37) | 2.5x (0.40) | 2.2x (0.46) |
| 2 | 2.5x (0.41) | 2.3x (0.44) | 2.1x (0.47) | 1.9x (0.52) |
| 5 | 2.0x (0.49) | 1.9x (0.52) | 1.8x (0.55) | 1.7x (0.61) |
| 8 | 1.4x (0.73) | 1.3x (0.79) | 1.2x (0.84) | 1.1x (0.91) |
| 10 | 1.4x (0.73) | 1.3x (0.79) | 1.2x (0.84) | 1.1x (0.91) |
| Codec | Fixed-Point | Floating-Point | Speedup |
|---|---|---|---|
| SILK (c=0, 10k) | 4.9x real-time | 1.1x real-time | 4x faster |
| SILK (c=0, 16k) | 3.9x real-time | 0.7x (fails) | 6x faster |
| CELT music (c=0, 64k) | 3.2x real-time | 2.9x real-time | 1.1x faster |
| CELT music (c=8, 192k) | 1.6x real-time | 1.1x real-time | 1.5x faster |
Recommendation: Use fixed-point (CONFIG_OPUS_FLOATING_POINT=n) for encoding on ESP32-S3. SILK encoding with floating-point is not viable for real-time applications.
Opus complexity ranges from 0 (fastest) to 10 (best quality):
| Complexity | Trade-off |
|---|---|
| 0-2 | Fastest encoding, lower quality |
| 5 | Balanced (default in most applications) |
| 8-10 | Best quality, slowest encoding |
Higher complexity uses more CPU cycles for analysis and psychoacoustic modeling. Note that complexity 8 and 10 show identical performance on ESP32-S3.
- VOIP: Optimized for speech, prefers SILK codec even at higher bitrates
- AUDIO: Optimized for music, prefers CELT codec, uses SILK only at very low bitrates
Higher bitrates generally:
- Increase encoding time (more data to process)
- Improve audio quality
- Result in larger compressed output
The default configuration uses 240MHz, fixed-point, THREADSAFE_PSEUDOSTACK, and pseudostack in PSRAM.
Key settings in sdkconfig.defaults:
# Fixed-point (currently configured, can change to floating-point)
CONFIG_OPUS_FLOATING_POINT=n| Type | Size | Notes |
|---|---|---|
| Flash | ~640KB | 100KB code + 498KB music + 38KB speech |
| Pseudostack | 120KB | Shared between encoder and decoder |
| Problem | Solution |
|---|---|
| Watchdog timeout | Already disabled in default config |
| Stack overflow | Increase CONFIG_ESP_MAIN_TASK_STACK_SIZE |
| Allocation failures | Check PSRAM is enabled, reduce pseudostack size, or set state to prefer PSRAM |
| All tests skip | RTF > 1.0 on first test; consider lowering complexity or switching to floating-point |
Processing Flow: For each encoder configuration, the benchmark:
- Decodes the embedded Opus file packet by packet using
OggOpusDecoder - Accumulates PCM samples until a full 20ms frame is ready
- Encodes the frame using the raw Opus encoder API (
opus_encode) - Times only the encode step (decode time is excluded)
- Reports statistics after processing all audio
Music Audio (CELT): Beethoven Symphony No. 3 "Eroica", Op. 55, Movement I, 30s extract.
- Performer: Czech National Symphony Orchestra
- Source: Musopen Collection on Archive.org
- License: Public Domain
- Format: Ogg Opus 48kHz stereo ~128kbit/s VBR (CELT codec)
Speech Audio (SILK): The Art of War, Chapters 1-2, 30s extract.
- Author: Sun Tzu
- Reader: Moira Fogarty (October 2006)
- Source: LibriVox on Archive.org
- License: Public Domain
- Format: Ogg Opus 16kHz mono ~10kbit/s (SILK codec)
Timing: Uses esp_timer_get_time() for microsecond precision. Only measures opus_encode() calls.