You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 23:45:40