fix: 🐛 Decode 8-bit, 16-bit and float PCM samples correctly on Android - #513
Open
victorrss wants to merge 1 commit into
Open
fix: 🐛 Decode 8-bit, 16-bit and float PCM samples correctly on Android#513victorrss wants to merge 1 commit into
victorrss wants to merge 1 commit into
Conversation
The three PCM paths in WaveformExtractor mis-read the decoder output, so extracted waveforms did not match the audio. - 8-bit: ENCODING_PCM_8BIT is unsigned with 128 as silence, but the byte was read as a signed Kotlin Byte, so silence decoded as -1.0. - 16-bit: the low byte was not masked, so any byte above 0x7F widened to a negative Int whose sign bits swallowed the high byte through the `or`. Full-scale +32767 decoded as -0.00003. - 32-bit: a pcmEncodingBit of 32 is only set for ENCODING_PCM_FLOAT, but the bytes were assembled as an integer and divided by 2^31. Both +1.0 and -1.0 decoded as -0.0039.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
WaveformExtractormis-reads the decoder's PCM output in all three of its bit-depth paths, so the extracted waveform does not match the audio. Nothing crashes — the waveform simply has the wrong shape — which is why this has been easy to miss. Full analysis and a value-by-value comparison are in #512.16-bit (
handle16bit), the default and most common path:buf.get()returns a signedByte, so a low byte above0x7Fwidens to a negativeIntwhose sign bits then swallow the high byte through theor. Full-scale+32767decodes as-0.00003. Since about half of all samples have a low byte ≥0x80, roughly half collapse toward zero, making the waveform attenuated and noisy rather than visibly broken. Fixed by masking the low byte withand 0xFF; the high byte stays signed, because that is the sample's own sign.8-bit (
handle8bit):AudioFormat.ENCODING_PCM_8BITis unsigned with 128 as silence, but the byte was read as a signedByteand never centered, so silence decoded as full-scale negative. Fixed by widening withand 0xFFand subtracting 128.32-bit (
handle32bit):pcmEncodingBitis only ever set to 32 forAudioFormat.ENCODING_PCM_FLOAT(inonOutputFormatChanged), so these samples are IEEE-754 floats already in[-1.0, 1.0]. They were assembled byte-by-byte as an integer and divided by2^31, which made+1.0and-1.0both decode as-0.0039. Fixed by reading them as floats viaasFloatBuffer(), which also simplifies the stereo channel skip.No public API changes, and no behavioral change beyond the decoded values being correct.
Checklist
fix:,feat:,docs:etc).docsand added dartdoc comments with///.examplesordocs.On tests: the Android source set has no test harness today (
android/src/contains onlymain, and there are no JUnit dependencies ortestOptionsinandroid/build.gradle), and these three functions are private and read instance state, so there is nowhere to hang a unit test without first introducing the JVM test setup. I did not want to bundle that decision into a bug fix. The issue instead documents the exact input/output pairs for each path, and I am happy to add a proper test — extracting the arithmetic into testable helpers, plus the JUnit wiring — if you would like it in this PR or a follow-up.Docs and examples are not applicable: this is an internal native decoding change with no surface in the Dart API.
Verified with
./gradlew :audio_waveforms:compileDebugKotlin(clean; the two remaining warnings are pre-existing, inAudioPlayer.kt) and by decoding known sample values through both the old and the new arithmetic.Breaking Change?
Waveforms extracted on Android will change shape, but only to become correct — they will now match what iOS produces for the same file.
Related Issues
Fixes #512
Made with Cursor