From 281dbf5993de230269de5302e6215bcd742b9e33 Mon Sep 17 00:00:00 2001 From: Roman Sorochak Date: Tue, 13 Oct 2026 23:42:08 +0200 Subject: [PATCH 1/2] Reset the audio converter before raising a codec error A failed AudioConverterFillComplexBuffer can leave the converter holding a span of its input, which points into memory AudioFileStream owns only for the duration of the packets callback. The stream keeps delivering data after .codecError, so the next fill consolidates that stale span with new input and reads freed memory (ACSpanningInputBuffer::consolidateInput -> memmove, SIGSEGV/SIGBUS). Call AudioConverterReset before each .codecError raise in AudioFileStreamProcessor, as processSeek() and createAudioConverter already do. In a harness that unmaps each chunk after AudioFileStreamParseBytes returns, MP3 input switching format mid-stream crashed on 10-11 of 40 seeds without the reset and on 0 of 40 with it. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit d5acc82f665105152813fe06b297251ce271ed90) --- .../Processors/AudioFileStreamProcessor.swift | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift b/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift index 77d560e..b24f426 100644 --- a/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift +++ b/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift @@ -587,7 +587,7 @@ final class AudioFileStreamProcessor { fillUsedFrames(framesCount: framesAdded) return } else if status != 0 { - fileStreamCallback?(.raiseError(.codecError)) + raiseCodecError(resetting: converter) return } @@ -620,7 +620,7 @@ final class AudioFileStreamProcessor { fillUsedFrames(framesCount: framesAdded) continue packetProcess } else if status != 0 { - fileStreamCallback?(.raiseError(.codecError)) + raiseCodecError(resetting: converter) return } } else { @@ -651,13 +651,26 @@ final class AudioFileStreamProcessor { fillUsedFrames(framesCount: framesAdded) continue packetProcess } else if status != 0 { - fileStreamCallback?(.raiseError(.codecError)) + raiseCodecError(resetting: converter) return } } } } + /// Raises `.codecError` after resetting the converter. + /// + /// A failed `AudioConverterFillComplexBuffer` can leave the converter holding a span of the + /// input it was given, which points into `inInputData` — memory owned by `AudioFileStream` that + /// is gone once this packets proc returns. The source keeps delivering data after the error, + /// so the next call would have the converter consolidate that stale span with new input and + /// read freed memory (`ACSpanningInputBuffer::consolidateInput` → `memmove`, SIGSEGV/SIGBUS). + /// Resetting drops the stale span, as `processSeek()` and `createAudioConverter` already do. + private func raiseCodecError(resetting converter: AudioConverterRef) { + AudioConverterReset(converter) + fileStreamCallback?(.raiseError(.codecError)) + } + /// Fills the `AudioBuffer` with data as required /// /// - parameter list: An `UnsafeMutableAudioBufferListPointer` object representing the buffer list be filled with data From cf6ee783065203b19e8dc575247c073daa7444ae Mon Sep 17 00:00:00 2001 From: Roman Sorochak Date: Wed, 14 Oct 2026 01:22:00 +0200 Subject: [PATCH 2/2] Shorten the raiseCodecError doc comment to the file's style --- .../Processors/AudioFileStreamProcessor.swift | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift b/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift index b24f426..49060b7 100644 --- a/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift +++ b/AudioStreaming/Streaming/AudioPlayer/Processors/AudioFileStreamProcessor.swift @@ -658,14 +658,12 @@ final class AudioFileStreamProcessor { } } - /// Raises `.codecError` after resetting the converter. + /// Resets the converter and raises `.codecError`. /// - /// A failed `AudioConverterFillComplexBuffer` can leave the converter holding a span of the - /// input it was given, which points into `inInputData` — memory owned by `AudioFileStream` that - /// is gone once this packets proc returns. The source keeps delivering data after the error, - /// so the next call would have the converter consolidate that stale span with new input and - /// read freed memory (`ACSpanningInputBuffer::consolidateInput` → `memmove`, SIGSEGV/SIGBUS). - /// Resetting drops the stale span, as `processSeek()` and `createAudioConverter` already do. + /// A failed fill can leave the converter holding a span of `inInputData`, which is freed once the + /// packets proc returns; resetting drops it, as `processSeek()` does. + /// + /// - parameter converter: The `AudioConverterRef` that failed. private func raiseCodecError(resetting converter: AudioConverterRef) { AudioConverterReset(converter) fileStreamCallback?(.raiseError(.codecError))