[PATCH] libmp3lame: fix float->double type mismatch in analyzer xr copy
"Maya R. Odinezenko via Lame-dev" <[email protected]> Thu, 16 Jul 2026 15:50:40 -0400
| Newsgroups | gmane.comp.audio.mp3.lame |
|---|---|
| Message-ID | <CAFCgL3njcp3EBrXLQ=3XHeQaoUJdSostPr=kzBfRuqJRxyr9hQ@mail.gmail.com> |
Hello, While porting the frame-analyzer UI to macOS on top of libmp3lame's plotting hooks, the encoder-side MDCT data arrived as garbage. The cause is a type mismatch in the analyzer copy in lame_encode_mp3_frame() (encoder.c:472): pinfo->xr is double[576] (lame-analysis.h) while tt[].xr uses FLOAT, normally float (machine.h). memcpy copies bytes and performs no conversion, so in the default build it moves sizeof(FLOAT)*576 = 2304 bytes into the 4608-byte double destination: the first half holds reinterpreted bit patterns and the second half is never written by the copy. The bundled gtk mp3x renders its MDCT panes from the same arrays. The decoder-side mpg123xr path already produces doubles and is fine, and FLOAT=double builds are accidentally unaffected, which may be why this went unnoticed. The patch converts element by element, which performs the proper float-to-double conversion and is correct for either FLOAT width. Testing, all on the 4.0 release (Apple silicon): - Normal-encoding regression checks (analysis disabled): pristine vs patched builds produce byte-identical MP3s on the bundled testcase.wav (cmp) at -V2 and at -m s -b 320 -q 0 --strictly-enforce-ISO --noreplaygain. - Analyzer-enabled harness (lame_set_analysis(1), caller-owned pinfo attached, deterministic noise input): the changed branch executes in both builds. Pristine, last frame, xr[0][0]: only the first 288 of 576 values are ever written, nonzero magnitudes between 2.1e-38 and 5.2e-9, mostly not representable as float. Patched: bins populated up to the encoder's lowpass, all 576 values are exact float-to-double conversions, magnitudes between 3.7e-5 and 0.18. The analyzer-enabled encodes of the two builds are byte-identical. - The full analyzer path was also exercised through the macOS analyzer port: before the fix the encoder-side MDCT pane shows garbage, after it the display is correct. The patch below is against current trunk (r6586); it applies unchanged to the 4.0 release. Maya -- >8 -- From: Maya <[email protected]> Date: Thu, 16 Jul 2026 15:28:13 -0400 Subject: [PATCH] libmp3lame: fix float->double type mismatch in analyzer xr copy lame_encode_mp3_frame() fills the frame-analyzer data with memcpy(gfc->pinfo->xr[gr][ch], &gfc->l3_side.tt[gr][ch].xr[0], sizeof(FLOAT) * 576); but pinfo->xr is double[576] (lame-analysis.h) while tt[].xr uses FLOAT, normally float (machine.h). memcpy copies bytes and performs no conversion: in the default build it moves 2304 bytes of float bit patterns into the 4608-byte double destination, so the first half holds reinterpreted values and the second half is never written by the copy. The encoder-side MDCT display in the analyzer shows garbage as a result. The decoder-side mpg123xr path already produces doubles and is unaffected, and builds configured with FLOAT=double are accidentally unaffected. Convert element by element instead, which performs the proper float-to-double conversion and is correct for either FLOAT width. --- libmp3lame/encoder.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libmp3lame/encoder.c b/libmp3lame/encoder.c index d123d69..8ba6ac9 100644 --- a/libmp3lame/encoder.c +++ b/libmp3lame/encoder.c @@ -469,7 +469,13 @@ lame_encode_mp3_frame( /* Output */ gfc->pinfo->ms_ener_ratio[gr] = ms_ener_ratio[gr]; gfc->pinfo->blocktype[gr][ch] = gfc->l3_side.tt[gr][ch].block_type; gfc->pinfo->pe[gr][ch] = pe_use[gr][ch]; - memcpy(gfc->pinfo->xr[gr][ch], &gfc->l3_side.tt[gr][ch].xr[0], sizeof(FLOAT) * 576); + /* pinfo->xr is double but tt[].xr is FLOAT: convert + element-wise, a raw copy would reinterpret the bits */ + { + int i; + for (i = 0; i < 576; ++i) + gfc->pinfo->xr[gr][ch][i] = gfc->l3_side.tt[gr][ch].xr[i]; + } /* in psymodel, LR and MS data was stored in pinfo. switch to MS data: */ if (gfc->ov_enc.mode_ext == MPG_MD_MS_LR) {