Conversation
Instead of accumulating all decoded PCM data in a ByteArrayOutputStream before writing, use a RandomAccessFile to write each decoded chunk directly to disk. A placeholder WAV header is written first and updated with the actual data size after decoding completes. This reduces peak memory usage from O(file_size) to O(buffer_size) for the common case (no resampling). When resampling is requested, the original buffered approach is used as a fallback since the linear interpolation algorithm requires the full PCM buffer. Closes #14
- Add AudioTrackInfo data class and extractAudioTrack() to deduplicate track discovery logic between performConversion and performConversionBuffered - Wrap streaming path in try/catch/finally to delete partial output files on failure and guarantee codec/extractor release - Document Int/WAV 2 GB size field limitation on totalPcmBytes - Restore clarifying comments in performConversionBuffered
- Reuse existing mime variable in extractAudioTrack instead of re-fetching - Pass AudioTrackInfo directly to performConversionBuffered to avoid double extraction when resampling is requested - Add try/catch/finally to performConversionBuffered for consistent error handling and resource cleanup - Replace mutable chunk variable with idiomatic let-chain - Replace outdated getPlatformVersion test with argument validation tests covering all method channel endpoints
Use Long for totalPcmBytes and validate against the WAV format's ~4 GB data limit (uint32 RIFF size field) so that oversized files produce a clear error instead of a corrupt header.
Prevents stale bytes from a previous larger file remaining at the end of the output when RandomAccessFile opens in read-write mode.
Cover channel conversion, bit depth conversion, and combined channel + bit depth without resampling. Verify that the WAV header data and RIFF chunk sizes match the actual PCM payload.
There was a problem hiding this comment.
Pull request overview
This pull request implements streaming PCM output to disk during Android WAV conversion to reduce memory usage from O(file_size) to O(buffer_size). The implementation writes decoded PCM chunks directly to disk via RandomAccessFile with a placeholder WAV header that is updated after decoding completes. When resampling is requested, the code falls back to the original buffered approach since the linear interpolation algorithm requires the full PCM buffer.
Changes:
- Introduced streaming WAV conversion that writes PCM data incrementally to disk instead of buffering in memory
- Added fallback to buffered conversion when resampling is needed
- Added comprehensive integration tests for the streaming path and unit tests for error handling
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| android/src/main/kotlin/nl/silversoft/audio_decoder/AudioDecoderPlugin.kt | Implements streaming PCM output with RandomAccessFile, adds extractAudioTrack helper, splits conversion into streaming and buffered paths, adds MAX_WAV_DATA_SIZE validation |
| example/integration_test/plugin_integration_test.dart | Adds tests for streaming path with channel/bit-depth conversion and WAV header validation |
| android/src/test/kotlin/nl/silversoft/audio_decoder/AudioDecoderPluginTest.kt | Adds comprehensive unit tests for argument validation across all method calls, removes outdated comments |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
android/src/main/kotlin/nl/silversoft/audio_decoder/AudioDecoderPlugin.kt
Outdated
Show resolved
Hide resolved
android/src/main/kotlin/nl/silversoft/audio_decoder/AudioDecoderPlugin.kt
Outdated
Show resolved
Hide resolved
android/src/main/kotlin/nl/silversoft/audio_decoder/AudioDecoderPlugin.kt
Outdated
Show resolved
Hide resolved
android/src/main/kotlin/nl/silversoft/audio_decoder/AudioDecoderPlugin.kt
Outdated
Show resolved
Hide resolved
android/src/main/kotlin/nl/silversoft/audio_decoder/AudioDecoderPlugin.kt
Outdated
Show resolved
Hide resolved
android/src/main/kotlin/nl/silversoft/audio_decoder/AudioDecoderPlugin.kt
Outdated
Show resolved
Hide resolved
Move codec.configure/start inside try block so failures are caught by the finally clause. Wrap codec.stop() in try-catch to prevent IllegalStateException from blocking release of remaining resources. Lift extractor lifecycle to an outer try-finally in performConversion so it is always released regardless of where an error occurs, and remove redundant extractor release from performConversionBuffered.
Include a benchmark integration test that measures conversion times for large audio files. The test assets are gitignored (~146 MB) and must be provided locally — see the doc comment in benchmark_test.dart for setup instructions.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
android/src/main/kotlin/nl/silversoft/audio_decoder/AudioDecoderPlugin.kt
Show resolved
Hide resolved
android/src/main/kotlin/nl/silversoft/audio_decoder/AudioDecoderPlugin.kt
Show resolved
Hide resolved
Set ByteBuffer position/limit from bufferInfo before reading decoded PCM in both performConversion and performConversionBuffered. This prevents potential audio corruption on codecs that use non-zero offsets. Also guard benchmark tests so they skip when large test assets are not bundled, avoiding failures on clean checkouts.
Summary
RandomAccessFileinstead of accumulating all data in aByteArrayOutputStreambefore writingMemory impact
Benchmark results
Tested on Android emulator (Medium_Phone, API 36, arm64-v8a, ~192 MB heap limit) with a ~10 min MP3 (16 MB compressed → ~119 MB PCM).
mainbranch (buffered)The old code buffers all decoded PCM (~195 MB) in a
ByteArrayOutputStreambefore writing to disk, exceeding the heap limit.This branch (streaming)
All 5 conversions completed successfully with no memory issues. The streaming approach writes PCM chunks directly to disk, keeping memory usage independent of file size.
Test plan
flutter test)Closes #14