Skip to content

fix(player): fall back to ffmpeg for MPEG-2/2.5 low-rate MP3 - #452

Open
cesarho wants to merge 1 commit into
bjarneo:mainfrom
cesarho:fix/mpeg2-lowrate-mp3-decode
Open

fix(player): fall back to ffmpeg for MPEG-2/2.5 low-rate MP3#452
cesarho wants to merge 1 commit into
bjarneo:mainfrom
cesarho:fix/mpeg2-lowrate-mp3-decode

Conversation

@cesarho

@cesarho cesarho commented Sep 9, 2026

Copy link
Copy Markdown

Summary

go-mp3 (github.com/hajimehoshi/go-mp3, the pure-Go decoder beep's mp3 package wraps — used for all native .mp3 HTTP/file decoding in cliamp) parses MPEG-2/2.5 Layer III streams — sample rates below the MPEG-1 floor of 32000Hz (22050/24000/16000Hz for MPEG-2, 11025/12000/8000Hz for MPEG-2.5) — without returning an error, but produces audibly garbled/distorted PCM. This rate range is common on low-bitrate Icecast/Shoutcast radio, which frequently drops to it for bandwidth-constrained stations.

Root-caused this against a real report of "bad output sound" on stm2.rthk.hk/radio2 (RTHK Radio 2, a 22050Hz/32kbps MPEG-2 stream). Methodically isolated it by ear:

  • Captured raw bytes from the live stream.
  • Decoded with go-mp3 straight to its native 22050Hz — no resampling at all in the chain — still garbled. This ruled out resampling, EQ, volume, and the audio device entirely.
  • Decoded the identical bytes with ffmpeg (mp3float) — clean.
  • mpv and a browser playing the same URL directly — both clean (they use platform/ffmpeg decoders, not go-mp3).

So the defect is specifically go-mp3 producing wrong output for this MPEG version/rate combination, silently — no decode error to hang a fallback on.

Fix

This isn't a new code path or a new dependency: ffmpeg is already a hard runtime dependency of cliamp, and needsFFmpeg() already routes .m4a/.aac/.aacp/.m4b/.alac/.wma/.opus/.webm and all HLS through it, plus there's already an existing fallback to ffmpeg when a native decoder (go-mp3/wav/flac/vorbis) returns an outright error. This PR extends that same, already-exercised fallback to trigger in one more well-defined case.

  • player/decode.go: adds isLowRateMP3(ext, sampleRate) — true for .mp3 with a sample rate below 32000Hz.
  • player/pipeline.go: after a successful native decode, if isLowRateMP3 is true, discards the go-mp3 decoder's output and routes through the existing ffmpeg fallback path (previously only reachable via a decode error) instead of trusting output known to be wrong for this stream class.

Testing

  • go build ./..., go vet ./..., full go test ./... — all clean across the whole repo.
  • New regression tests in player/decode_test.go:
    • TestIsLowRateMP3 — table test covering MPEG-1/2/2.5 rate boundaries and the .mp3-only scope.
    • TestBuildPipelineRoutesLowRateMP3ToFFmpeg — builds a real pipeline against a small synthesized MPEG-2 22050Hz MP3 fixture (player/testdata/mpeg2_22050.mp3, a 1-second sine tone generated with ffmpeg — not copyrighted stream audio) and asserts the resulting decoder is *localFFmpegStreamer, not the native go-mp3 path, plus a basic sanity check that it actually produces samples.
  • Manually verified end-to-end against the live RTHK stream: built and ran the patched binary, listened to it live and to captured/isolated clips at each pipeline stage — confirmed by ear that the fix resolves it ("very good").

Test plan

  • go build ./...
  • go vet ./...
  • go test ./...
  • New regression tests for the rate detection and the pipeline routing
  • Manual end-to-end verification against the live stm2.rthk.hk/radio2 stream, confirmed by ear

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed playback of low-sample-rate MP3 files that could previously produce garbled audio.
    • These files now use the FFmpeg fallback decoder for reliable playback.
  • Tests
    • Added coverage for low-rate MP3 detection and fallback playback behavior.

go-mp3 (github.com/hajimehoshi/go-mp3, the pure-Go decoder beep's mp3
package wraps) parses MPEG-2/2.5 Layer III streams — sample rates
below the MPEG-1 floor of 32000Hz, e.g. 22050Hz — without returning an
error, but produces audibly garbled/distorted PCM. This is the actual
cause of "bad output sound" on low-bitrate Icecast/Shoutcast radio
streams that use these rates (common for bandwidth-constrained
stations), independent of the resample-headroom clipping fixed in the
previous commit.

Confirmed by ear against a real 22050Hz stream (RTHK Radio 2,
stm2.rthk.hk/radio2): decoding the identical captured bytes with
ffmpeg sounded correct; decoding with go-mp3 did not, with or without
resampling involved at all (tested decoding straight to the source's
native 22050Hz, no resample step in the chain). ffmpeg's mp3float
decoder handles this stream correctly.

Adds isLowRateMP3() and routes any HTTP/local .mp3 stream detected at
this rate range to the existing ffmpeg fallback path (previously only
used when go-mp3 returned an outright error) instead of trusting its
error-free-but-wrong output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L7ty32fpxBC45NiVTFPAGA
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 81fc117b-6bfe-4f2c-8986-eadebf8fa518

📥 Commits

Reviewing files that changed from the base of the PR and between 93a4ac6 and 604e51d.

⛔ Files ignored due to path filters (1)
  • player/testdata/mpeg2_22050.mp3 is excluded by !**/*.mp3
📒 Files selected for processing (3)
  • player/decode.go
  • player/decode_test.go
  • player/pipeline.go

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The change detects low-rate MPEG-2/2.5 MP3 streams, rejects unreliable native decoding, and routes these streams through the existing ffmpeg fallback. Tests cover detection and decoded output.

Changes

Low-rate MP3 fallback

Layer / File(s) Summary
Low-rate MP3 detection and unit coverage
player/decode.go, player/decode_test.go
Adds errGoMP3LowRateUnreliable and isLowRateMP3. Tests cover supported rates, MPEG-1 rates, zero rates, and non-MP3 extensions.
Pipeline fallback and regression coverage
player/pipeline.go, player/decode_test.go
Routes low-rate MP3 streams from native decoding to localFFmpegStreamer. The integration test verifies that ffmpeg produces samples.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 604e5

Low-rate MP3 streams now use FFmpeg instead of the unreliable native decoder, correcting distorted playback while preserving normal MP3 handling. No current merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant buildPipeline
  participant goMP3Decoder
  participant localFFmpegStreamer
  buildPipeline->>goMP3Decoder: Decode low-rate MP3
  goMP3Decoder-->>buildPipeline: Return sample rate below 32000Hz
  buildPipeline->>localFFmpegStreamer: Use fallback decoder
  localFFmpegStreamer-->>buildPipeline: Produce samples
Loading

Suggested reviewers: bjarneo

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: routing MPEG-2/2.5 low-rate MP3 decoding through the FFmpeg fallback.
  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant