diff --git a/Sources/PlayerRecorder.swift b/Sources/PlayerRecorder.swift index df83195..7b30345 100644 --- a/Sources/PlayerRecorder.swift +++ b/Sources/PlayerRecorder.swift @@ -17,6 +17,10 @@ final class PlayerRecorder: NSObject { private var audioReaderOutput: AVAssetReaderTrackOutput? private var audioCaptureQueue: DispatchQueue? private var isRunning = false + /// 线程安全的音频停止标志,后台队列直接读取 + nonisolated(unsafe) private var audioStopped: Bool = false + /// 音频线程读取 firstFrameTime 的镜像 + nonisolated(unsafe) private var audioFirstFrameTime: CMTime? @Published var isRecording = false @Published var durationText = "00:00" @@ -40,6 +44,7 @@ final class PlayerRecorder: NSObject { weakOutput = output firstFrameTime = nil isRunning = false + audioStopped = false // 临时文件 let formatter = DateFormatter() @@ -143,6 +148,7 @@ final class PlayerRecorder: NSObject { // 第一帧:用它的时间作为 session 起点 if firstFrameTime == nil { firstFrameTime = itemTime + audioFirstFrameTime = itemTime // 同步给音频线程 writer?.startSession(atSourceTime: itemTime) isRunning = true } @@ -213,7 +219,8 @@ final class PlayerRecorder: NSObject { let queue = DispatchQueue(label: "recorder.audio.capture") self.audioCaptureQueue = queue queue.async { [weak self] in - self?.readAudioLoop(reader: reader, output: output) + guard let self = self, let aInput = self.audioInput else { return } + self.readAudioLoop(reader: reader, output: output, audioInput: aInput) } } } catch { @@ -222,47 +229,45 @@ final class PlayerRecorder: NSObject { } } - private nonisolated func readAudioLoop(reader: AVAssetReader, output: AVAssetReaderTrackOutput) { - while reader.status == .reading { + private nonisolated func readAudioLoop(reader: AVAssetReader, output: AVAssetReaderTrackOutput, audioInput: AVAssetWriterInput) { + while !audioStopped && reader.status == .reading { guard let sampleBuffer = output.copyNextSampleBuffer() else { break } - // 在 MainActor 上调整时间戳并写入 - Task { @MainActor [weak self] in - guard let self = self, self.isRunning else { return } - guard let firstTime = self.firstFrameTime else { return } - guard let input = self.audioInput, input.isReadyForMoreMediaData else { return } - - let pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) - let relativeTime = CMTimeSubtract(pts, firstTime) - guard relativeTime.seconds >= 0 else { return } - - // 重建 sample buffer with new time - var count: CMItemCount = 0 - CMSampleBufferGetSampleTimingInfoArray(sampleBuffer, entryCount: 0, arrayToFill: nil, entriesNeededOut: &count) - var timingInfo = [CMSampleTimingInfo](repeating: CMSampleTimingInfo(), count: count) - CMSampleBufferGetSampleTimingInfoArray(sampleBuffer, entryCount: count, arrayToFill: &timingInfo, entriesNeededOut: nil) - - for i in 0..= 0 else { continue } + + // 重建 sample buffer with new time + var count: CMItemCount = 0 + CMSampleBufferGetSampleTimingInfoArray(sampleBuffer, entryCount: 0, arrayToFill: nil, entriesNeededOut: &count) + var timingInfo = [CMSampleTimingInfo](repeating: CMSampleTimingInfo(), count: count) + CMSampleBufferGetSampleTimingInfoArray(sampleBuffer, entryCount: count, arrayToFill: &timingInfo, entriesNeededOut: nil) + + for i in 0..