iOS Swift录屏报错:-[AVAssetWriterInput markAsFinished]状态为0时无法调用
iOS Swift 录制视频偶发
AVAssetWriterInput 报错问题 问题现象
使用AVAssetWriterInput录制视频时,停止录制仅在1/10的概率下出现如下报错:
- [AVAssetWriterInput markAsFinished] Cannot call method when status is 0'
相关代码
停止录制方法
func stopRecording(completion: ((String?)-> Void)? = nil) { sessionQueue.async { [self] in guard let captureSession = self.session, captureSession.isRunning else { print(CameraControllerError.captureSessionIsMissing) completion?(nil) return } if(!isRecording) { completion?(nil) return } isRecording = false do { if (assetWriter?.status == .writing) { videoWriterInput?.markAsFinished() audioWriterInput?.markAsFinished() videoWriterInputPixelBufferAdaptor!.assetWriterInput.markAsFinished() print("video finished") print("audio finished") } else{ print("not writing") } self.assetWriter?.finishWriting() { print("finished writing") } } catch { if let e=self.assetWriter?.error{ print("stop record error:", e) } } if(self.audioCaptureSession!.isRunning) { let inputs = self.audioCaptureSession!.inputs for oldInput:AVCaptureInput in inputs { self.audioCaptureSession!.removeInput(oldInput) } let outputs = self.audioCaptureSession!.outputs for oldOutput:AVCaptureOutput in outputs { self.audioCaptureSession!.removeOutput(oldOutput) } self.audioCaptureSession!.stopRunning() } self.audioCaptureSession = nil let path = assetWriter?.outputURL.path self.videoRecordCompletionBlock?(path) videoWriterInputPixelBufferAdaptor = nil } }
AVAssetWriter 配置方法
func configureWritter(path: String) { do { assetWriter = try AVAssetWriter(outputURL: URL(fileURLWithPath: path), fileType: AVFileType.mp4) let videoSettings = self.recommendedVideoSettings(videoOutput: videoDataOutput, fileType: AVFileType.mp4, videoCodec: AVVideoCodecType.h264) videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings) videoWriterInput!.expectsMediaDataInRealTime = true if (assetWriter?.canAdd(videoWriterInput!))! { assetWriter?.add(videoWriterInput!) print("Added AVAssetWriterInput: Video") } else{ print("Could not add VideoWriterInput to VideoWriter") } videoWriterInputPixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput!, sourcePixelBufferAttributes: [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, kCVPixelBufferWidthKey as String: 1080, kCVPixelBufferHeightKey as String: 1920, kCVPixelFormatOpenGLESCompatibility as String: true, ]) // ------ AUDIO -------- // Start audio session self.audioCaptureSession?.startRunning() //Audio Settings let audioSettings = self.audioDataOutput.recommendedAudioSettingsForAssetWriter(writingTo: .mp4) audioWriterInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: audioSettings) audioWriterInput!.expectsMediaDataInRealTime = true if (assetWriter?.canAdd(audioWriterInput!))! { assetWriter?.add(audioWriterInput!) print("Added AVAssetWriterInput: Audio") } else{ print("Could not add AudioWriterInput to VideoWriter") } let recordingClock = self.session!.masterClock assetWriter?.startWriting() assetWriter?.startSession(atSourceTime: CMClockGetTime(recordingClock!)) switch self.assetWriter?.status { case .writing: print("status writing") case .failed: print("status failed") case .cancelled: print("status cancelled") case .unknown: print("status unknown") default: print("status completed") } } catch { return } }
捕获输出代理方法
func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from _: AVCaptureConnection) { if(!isRecording) { return } guard CMSampleBufferDataIsReady(sampleBuffer) else { print("data are not ready") return } if(self.assetWriter == nil) { return } else if (self.assetWriter?.status != .writing) { return } switch captureOutput { case is AVCaptureVideoDataOutput: guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { print("error to get image buffer") return } let time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) if(videoWriterInputPixelBufferAdaptor != nil) { if(videoWriterInputPixelBufferAdaptor!.assetWriterInput.isReadyForMoreMediaData) { videoWriterInputPixelBufferAdaptor!.append(imageBuffer, withPresentationTime: time) print("append video writer") } else { print("input pixel writer is not ready for media data") } } else { print("input pixel writer was null") } case is AVCaptureAudioDataOutput: audioWriterInput?.append(sampleBuffer) print("append audio input writer") default: break } } }
疑问
请问该报错的含义是什么?为什么会偶发出现此问题?
解答
报错含义
报错里的status is 0对应AVAssetWriterInput.Status的.unknown状态,意思是你在AVAssetWriterInput处于未知状态时调用了markAsFinished()方法,这个状态下该方法不允许被调用。
偶发原因分析
- 竞态条件导致状态变化:你在
stopRecording里先判断assetWriter?.status == .writing,再调用markAsFinished,但这两个操作有时间差。多线程环境下(sessionQueue.async执行,captureOutput可能在其他线程回调),assetWriter的状态可能在检查后、调用方法前发生变化(比如隐性写入错误导致进入.failed状态),此时AVAssetWriterInput状态同步异常。 - 重复调用
markAsFinished:你同时调用了videoWriterInput?.markAsFinished()和videoWriterInputPixelBufferAdaptor!.assetWriterInput.markAsFinished(),这其实是同一个AVAssetWriterInput对象。第一次调用后输入已进入完成状态,第二次调用就会触发报错。 - 音频会话提前终止:在
stopRecording中,你在finishWriting的回调完成前就直接停止并销毁了audioCaptureSession,如果此时音频输入还在处理数据,可能导致assetWriter意外进入错误状态,进而让AVAssetWriterInput状态异常。 - 未监听写入过程中的错误:
assetWriter写入时出现的隐性错误(如磁盘空间不足、编码失败)会悄悄切换到.failed状态,但你只在stopRecording的catch块打印错误,没有在写入过程中监听状态变化,导致后续操作基于错误状态执行。
内容的提问来源于stack exchange,提问作者nicover
相关产品推荐
相关产品推荐

