AVAssetWriterInput的isReadyForMoreMediaData始终为false问题求助
解决AVAssetWriterInput.isReadyForMoreMediaData始终返回false的问题
我在尝试实时录制CVPixelBuffer时遇到了问题,assetWriterInput.isReadyForMoreMediaData始终返回false,导致无法追加缓冲区。以下是我的代码:
class VideoRecorder{ static var shared = VideoRecorder() var avAssetWriter: AVAssetWriter? var Adaptor: AVAssetWriterInputPixelBufferAdaptor? var Settings: RecorderSetting? struct RecorderSetting{ var videoSetting: [String : Any] var Path: URL } func makeVideoSettings(width: Int, height: Int, BitRate: Double) -> [String : Any]{ let VideoCompressionProperties = [ AVVideoAverageBitRateKey: Double(width * height) * BitRate ] let videoSettings:[String : Any] = [ AVVideoCodecKey: AVVideoCodecType.hevc, AVVideoWidthKey: width, AVVideoHeightKey: height, AVVideoCompressionPropertiesKey: VideoCompressionProperties ] return videoSettings } func makePath(FileName: String) -> URL{ return URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/\(FileName).mp4") } func setup(width: Int, height: Int, BitRate: Double, FileName: String){ let setting = makeVideoSettings(width: width, height: height, BitRate: BitRate) let Path = makePath(FileName: FileName) Settings = RecorderSetting(videoSetting: setting, Path: Path) } func StartSession(FirstFrame: CVPixelBuffer) throws{ let attribute: [String : Any] = [ kCVPixelBufferPixelFormatTypeKey as String: CVPixelBufferGetPixelFormatType(FirstFrame), kCVPixelBufferWidthKey as String: CVPixelBufferGetWidth(FirstFrame), kCVPixelBufferHeightKey as String: CVPixelBufferGetHeight(FirstFrame) ] if (Settings == nil){throw "Settings invalid"} let writerInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: Settings!.videoSetting) Adaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: writerInput, sourcePixelBufferAttributes: attribute) Adaptor?.assetWriterInput.expectsMediaDataInRealTime = true do{ avAssetWriter = try AVAssetWriter(url: Settings!.Path, fileType: AVFileType.mp4) if (avAssetWriter!.canAdd(writerInput)){ let StartTime = Date().timeIntervalSince1970.toCMTime() avAssetWriter?.startWriting() avAssetWriter?.startSession(atSourceTime: StartTime) try? WriteBuffer(Buffer: FirstFrame, time: StartTime) }else{ throw "Add AVWriterInput Error" } }catch{ throw "Initializing Error" } } func StopSession(){ if(Adaptor?.assetWriterInput.isReadyForMoreMediaData == false){return} Adaptor?.assetWriterInput.markAsFinished() avAssetWriter?.finishWriting(completionHandler: { if let outputPath = self.Settings?.Path{ PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputPath) }) { saved, error in if saved { try? FileManager().removeItem(at: outputPath) print("Saved ") /*let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] let fetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions).firstObject // fetchResult is your latest video PHAsset // To fetch latest image replace .video with .image*/ } } } }) } func WriteBuffer(Buffer: CVPixelBuffer, time: CMTime) throws{ if(self.Adaptor != nil){ if (self.Adaptor!.assetWriterInput.isReadyForMoreMediaData){ let whetherPixelBufferAppendedtoAdaptor = self.Adaptor!.append(Buffer, withPresentationTime: time) if(!whetherPixelBufferAppendedtoAdaptor){ print(avAssetWriter?.error as Any) } }else{ throw "Writer Input is not Ready" } }else{ throw "PixelBufferAdaptor invalild" } }
核心原因及修复点
- 未将AVAssetWriterInput添加到AVAssetWriter:这是最关键的错误。你创建了
writerInput,但在调用startWriting()和startSession()之前,没有执行avAssetWriter?.add(writerInput)。未被添加到writer的input永远不会处于就绪状态。 - 调用顺序完全颠倒:原代码先启动writing和session,再判断是否能添加input,逻辑完全错误。正确顺序应为:创建writer → 添加input → 启动writing → 启动session。
- StopSession逻辑缺陷:原代码中如果
isReadyForMoreMediaData为false就直接返回,不执行markAsFinished(),会导致AVAssetWriter无法正常完成写入,甚至造成资源泄漏。不管input是否就绪,停止录制时都需要标记input完成。 - 时间戳生成不可靠:用
Date().timeIntervalSince1970.toCMTime()生成的时间戳可能存在基准不一致的问题,建议使用基于帧率的连续时间戳。
修复后的完整代码
class VideoRecorder{ static var shared = VideoRecorder() var avAssetWriter: AVAssetWriter? var adaptor: AVAssetWriterInputPixelBufferAdaptor? var settings: RecorderSetting? var baseTime: CMTime? // 记录起始时间,用于生成连续时间戳 let fps: Int32 = 30 // 帧率,可根据需求调整 struct RecorderSetting{ var videoSetting: [String : Any] var path: URL } func makeVideoSettings(width: Int, height: Int, bitRate: Double) -> [String : Any]{ let videoCompressionProperties = [ AVVideoAverageBitRateKey: Double(width * height) * bitRate ] let videoSettings:[String : Any] = [ AVVideoCodecKey: AVVideoCodecType.hevc, AVVideoWidthKey: width, AVVideoHeightKey: height, AVVideoCompressionPropertiesKey: videoCompressionProperties ] return videoSettings } func makePath(fileName: String) -> URL{ let docsDir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] return URL(fileURLWithPath: docsDir).appendingPathComponent("\(fileName).mp4") } func setup(width: Int, height: Int, bitRate: Double, fileName: String){ let setting = makeVideoSettings(width: width, height: height, bitRate: bitRate) let path = makePath(fileName: fileName) settings = RecorderSetting(videoSetting: setting, path: path) } func startSession(firstFrame: CVPixelBuffer) throws{ guard let settings = settings else { throw "Settings invalid" } let attribute: [String : Any] = [ kCVPixelBufferPixelFormatTypeKey as String: CVPixelBufferGetPixelFormatType(firstFrame), kCVPixelBufferWidthKey as String: CVPixelBufferGetWidth(firstFrame), kCVPixelBufferHeightKey as String: CVPixelBufferGetHeight(firstFrame) ] let writerInput = AVAssetWriterInput(mediaType: .video, outputSettings: settings.videoSetting) writerInput.expectsMediaDataInRealTime = true adaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: writerInput, sourcePixelBufferAttributes: attribute) do{ avAssetWriter = try AVAssetWriter(url: settings.path, fileType: .mp4) guard avAssetWriter!.canAdd(writerInput) else { throw "Add AVWriterInput Error" } // 关键:先将input添加到writer avAssetWriter!.add(writerInput) // 启动writing和session try avAssetWriter!.startWriting() baseTime = CMTimeMake(value: 0, timescale: fps) avAssetWriter!.startSession(atSourceTime: baseTime!) // 写入第一帧 try writeBuffer(buffer: firstFrame, frameIndex: 0) }catch{ throw "Initializing Error: \(error.localizedDescription)" } } func stopSession(){ guard let adaptor = adaptor, let avAssetWriter = avAssetWriter else { return } // 不管是否就绪,都标记input完成 adaptor.assetWriterInput.markAsFinished() avAssetWriter.finishWriting(completionHandler: { [weak self] in guard let self = self, let outputPath = self.settings?.path else { return } if avAssetWriter.status == .completed { PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputPath) }) { saved, error in if saved { try? FileManager.default.removeItem(at: outputPath) print("视频已保存到相册") } else if let error = error { print("保存失败:\(error.localizedDescription)") } } } else if let error = avAssetWriter.error { print("写入失败:\(error.localizedDescription)") } // 重置状态,避免重复使用时的混乱 self.avAssetWriter = nil self.adaptor = nil self.settings = nil self.baseTime = nil }) } func writeBuffer(buffer: CVPixelBuffer, frameIndex: Int) throws{ guard let adaptor = adaptor, let baseTime = baseTime else { throw "PixelBufferAdaptor invalid" } // 实时场景下适当等待input就绪,避免丢帧 while !adaptor.assetWriterInput.isReadyForMoreMediaData { Thread.sleep(forTimeInterval: 0.01) } // 生成连续的时间戳 let presentationTime = CMTimeAdd(baseTime, CMTimeMake(value: Int64(frameIndex), timescale: fps)) let success = adaptor.append(buffer, withPresentationTime: presentationTime) if !success { if let error = avAssetWriter?.error { throw "追加缓冲区失败:\(error.localizedDescription)" } else { throw "追加缓冲区失败,未知错误" } } } }
额外说明
- 命名规范:修复了原代码中不符合Swift命名规范的变量名(如
Adaptor改为adaptor),提升代码可读性。 - 时间戳优化:使用基于帧率的连续时间戳,避免时间基准不一致导致的录制异常。
- 错误处理增强:增加了更详细的错误信息,便于调试定位问题。
- 资源重置:在stopSession完成后重置相关变量,避免重复使用时的状态混乱。
内容的提问来源于stack exchange,提问作者Shimamura Uzuki
相关产品推荐
相关产品推荐

