audio_decode 1.0.1
audio_decode: ^1.0.1 copied to clipboard
Decode MP3 and Ogg Vorbis to raw PCM in Dart, over FFI. Compiles stb_vorbis and minimp3 from source: self-contained, pure-Dart friendly, no platform plugins or prebuilt binaries.
1.0.1 #
Two bugs found by exercising the package against its own pub.dev description on a real device and a real file sweep, rather than against its own tests.
Android crashed at runtime, and the build was green #
flutter build apk succeeded for all three ABIs, and then on device:
dlopen failed: cannot locate symbol "log" referenced by libaudio_decode.so
stb_vorbis and minimp3 call log, pow, sin, exp and ldexp. Android
keeps those in a separate libm, and its linker will not resolve a symbol from
a library that is not in DT_NEEDED. The build hook never linked it, so the
shipped .so listed only libdl.so and libc.so.
Every other target hid the omission: macOS and iOS get math from libSystem,
and glibc 2.34 folded libm into libc. Android was the one platform where it
was fatal, and the one platform a Dart test/ directory cannot reach.
hook/build.dart now links m on Android and Linux. Verified by reading
DT_NEEDED out of the built .so: it was [libdl.so, libc.so] and is now
[libm.so, libdl.so, libc.so].
If you shipped 1.0.0 to Android, it did not work. Sorry.
A truncated file could decode to nothing and report success #
Truncating an Ogg part way through its first audio page returned a PcmAudio
with frameCount == 0 and threw nothing. Any caller validating an upload by
catching an exception accepted a file that produced no audio at all. A decode
that yields no frames now throws AudioDecodeException.
The README's claim that truncated Ogg files "fail and throw" was also wrong, and is corrected rather than defended. Measured behaviour: a cut inside the header throws, a cut after the header decodes what arrived and returns it, and a cut yielding nothing now throws. Detecting a missing tail needs the end-of-stream page flag, which this package does not check yet, and the README now says so.
New test/truncation_test.dart sweeps truncation points instead of pinning one
percentage, because which offset produces the empty decode depends on where the
page boundaries fall. Confirmed red before the fix and green after.
1.0.0 #
The API is stable. No behaviour changes; this freezes the surface after an adversarial pass over the FFI boundary, and documents one thing that pass turned up.
- Documented what a truncated file does, which differs by format. The README
said undecodable bytes throw, which is true, but a cut-off file is a separate
case. Ogg is a checksummed container, so a truncated one throws. MP3 is a bare
frame sequence carrying no total length, so a truncated one is
indistinguishable from a shorter recording: it decodes the frames that arrived
and returns them silently — measured, a third of a file decodes to about a
third of the audio. Nothing can detect that from the bytes, so the README now
says to check
PcmAudio.durationagainst what you expected when the input may be a partial download.
Verified by execution and now covered by test/native_safety_test.dart: empty
input raises ArgumentError and garbage raises AudioDecodeException; a
truncated Ogg throws while a truncated MP3 returns fewer frames at the same
sample rate; repeated decode/encode does not leak (growth across batches
flattens instead of staying linear, which is the shape a leak would have);
encodeWav writes exactly a 44-byte header plus the samples; and toMono
leaves already-mono audio unchanged.
native_toolchain_c is pre-1.0 but is a build-time dependency and does not
reach the public API frozen here.
0.5.1 #
- Add
example/README.mdfor pub.dev's Example tab (it was empty). It walks through the command-line example — detect the format, decode to PCM, print an ASCII waveform, write a WAV — with the real output against the repo's sine fixture. Docs only.
0.5.0 #
Two things that had to be settled before a 1.0.0 freezes them.
- Correct a false claim. Every release up to 0.4.1 said this package "decodes the same bytes to the same samples on every platform", and the library documentation said it "decodes deterministically across platforms". That is not true, and it was never true. Compiling this package's own C for arm64 and for x86-64 with the same clang and the same flags and decoding the six test fixtures, five of them come out with different samples: about 0.03% of samples differ, always by one least-significant bit. Both decoders work in floating point and a compiler may fuse a multiply and an add on one architecture and not on another, which moves the last rounding. What is true, and is what the documentation now says, is that decoding is deterministic for a given build and that the reported geometry (channels, sample rate, frame count) matches everywhere. The practical consequence is narrow but real: a checksum of decoded PCM does not survive a move between architectures. The reason this went unnoticed is that the test suite runs on three operating systems but each runner only ever compares against itself.
- Mark
PcmAudio,AudioInfoandAudioDecodeExceptionasfinal. They are leaf types: none of them is designed to be extended or implemented, and nothing needs to. Sealing them now is what makes the rest of 1.x safe, since adding a method to an open class breaks anyone who implemented it, andAudioInfo's==would otherwise be open to an asymmetric subclass. This is the breaking part of this release:finalcannot be added after 1.0.0 without a 2.0.0, while removing it later would not break anyone.
0.4.1 #
- Answer the question the README kept skipping: why decode in process rather
than run
ffmpeg.bench/vs_ffmpeg.dartmeasures both paths on the same file at four clip lengths, and the README now carries the result. The point is not that the codec is faster. Starting ffmpeg costs about 24.8 ms on an Apple M-series laptop before it decodes anything, measured by giving it a 0.05-second clip where there is nothing to decode, and that cost is paid per file: at one second of audio, 98% of the subprocess time is not decoding. The decoding is in the same class either way, about 15.7 ms against 12.9 ms over thirty seconds. - The bench compares the two decoders' output before it reports any timing, so a timing number cannot come from decoding less audio. Running it turned up one real difference, which it now reports rather than hiding: on a one-second clip ffmpeg's raw PCM pipe stops 128 frames early, while this package returns the full 44100 frames the container declares. The two agree exactly at five, fifteen and thirty seconds.
0.4.0 #
- Add value equality to
AudioInfo. It now overrides==andhashCodeover itssampleRate,channelsandframeCountfields, so two values with the same geometry compare equal, share ahashCode, deduplicate in aSetand work asMapkeys. This lands before the 1.0.0 freeze: giving a released value type value equality afterwards would change how existing==,SetandMapuses behave, which would be a breaking change.PcmAudiokeeps identity equality by design, since itssamplesbuffer is large and mutable and a structural==would scan every sample and rehash whenever the buffer is written. No existing fields or methods change.
0.3.2 #
- Fix the int32 overflow from 0.3.1 on the Ogg Vorbis path as well.
ad_info_vorbisread the stream length from the final page's granule position as anunsigned intand narrowed it into the caller'sintout parameter with no range check, so a stream whose granule passed 2^31 (about 13.5 hours at 44.1 kHz) wrapped to a negative value thatoggInfoandaudioInfosurfaced as a negativeframeCountanddurationwith no error. The Vorbis info path now rejects the overflow with anAudioDecodeException, the same guard the MP3 path already carried.
0.3.1 #
- Fix an int32 overflow: the MP3 decoder accumulated the per-channel sample
count in a native
size_tbut narrowed it into the caller'sintout parameter with no overflow check. A stream whose per-channel sample count passed 2^31 (about 13.5 hours at 44.1 kHz) wrapped to a negative value, whichmp3Info/audioInfosurfaced as a negativeframeCountanddurationwith no error, and whichdecodeMp3could turn into an unfreed native buffer surfaced as an unrelatedArgumentError. Both paths now reject the overflow explicitly (AudioDecodeException) instead of wrapping, and_decodefrees the native buffer in afinallyeven if the copy into Dart memory throws.
0.3.0 #
- Add
PcmAudio.toFloat32(),PcmAudio.channel(int)andPcmAudio.toMono(). The samples were only exposed as raw interleavedInt16List, so waveform, analysis and machine-learning callers all had to write the same divide-by-32768 loop and manual channel split.toFloat32returns the normalized[-1.0, 1.0]floats,channeldeinterleaves one channel into aFloat32List, andtoMonoaverages the channels into a monoPcmAudio. All additive; existing fields and methods are unchanged.
0.2.3 #
- Widen the native-toolchain constraints so the package can be installed in a
Flutter app at all.
hooks2.1.0 andnative_toolchain_c0.19.3 raised theirmetafloor to ^1.19.0, and Flutter's SDK pinsmetato 1.17.0, soflutter pub addfailed at version solving with "flutter from sdk is incompatible". Allowinghooks >=2.0.2andnative_toolchain_c >=0.19.2lets the solver pick a version that works with the pinnedmeta, while a pure-Dart project still resolves to the newest. No API or behaviour change.
0.2.2 #
- Shorten the screenshot description. pub.dev accepts up to 200 characters but scores only those under 160, so the previous release published cleanly and quietly gave up the documentation points it was meant to earn.
0.2.1 #
- Declare the diagram in
pubspec.yamlso pub.dev renders it on the package page. It was already in the repository and the README, but pub.dev shows only what thescreenshots:field points at.
0.2.0 #
- Add
audioInfo,oggInfoandmp3Info, which return anAudioInfowithsampleRate,channels,frameCountanddurationwithout decoding to PCM. Reading a track's length used to mean decoding the whole file, which for four minutes of 44.1 kHz stereo materializes 40 MB of samples you then throw away. The new calls allocate no PCM: Vorbis answers from the container, and MP3 walks its frame headers with the decoder's synthesis step skipped. Measured on a one-second stereo fixture (Apple M-series, warmed up): Ogg 107 µs against 511 µs for a full decode, MP3 0.9 µs against 217 µs. The reported geometry is checked againstdecodeAudiofor every test fixture.
0.1.3 #
- Example: show what to do with the decoded PCM, not just how to re-encode it. It now reduces the samples to a one-line waveform (peak amplitude per column, scaled to the loudest column), the primitive a waveform view or a silence detector is built on.
0.1.2 #
- Docs: sharpen the pub.dev description to lead with the value and the terms people search.
0.1.1 #
- Move the stb_vorbis and minimp3 attributions out of
LICENSEintoTHIRD_PARTY_NOTICES.md, soLICENSEis the plain MIT text that automated license detection recognises. The attributions themselves are unchanged and still ship with the package.
0.1.0 #
- Initial release.
- Ogg Vorbis decoding via stb_vorbis and MP3 decoding via minimp3, compiled from source with Dart build hooks.
decodeAudio,decodeOgg,decodeMp3,detectFormatandPcmAudio.encodeWavwrites decoded PCM to a 16-bit WAV.
