2

This error occurs when capturing video with AVAssetWriter. However, after calling AVAssetWriter's finishWriting inside of endVideoCapture, there isn't another call to start writing again, so why is this occurring?

As you can see in the delegate function, captureOutput, we check the recording state before trying to append to the asset writer. The recording state is set to false in endVideoCapture.

Optional(Error Domain=AVFoundationErrorDomain Code=-11862 "Cannot append media data after ending session" UserInfo={NSLocalizedFailureReason=The application encountered a programming error., NSLocalizedDescription=The operation is not allowed, NSDebugDesc

func startVideoCapture() {
    // Get capture resolution
    let resolution = getCaptureResolution()

    // Return if capture resolution not set
    if resolution.width == 0 || resolution.height == 0 {
        printError("Error starting capture because resolution invalid")
        return
    }

    // If here, start capture
    assetWriter = createAssetWriter(Int(resolution.width), outputHeight: Int(resolution.height))
    let recordingClock = captureSession.masterClock
    assetWriter!.startWriting()
    assetWriter!.startSession(atSourceTime: CMClockGetTime(recordingClock!))

    // Update time stamp
    startTime = CACurrentMediaTime()

    // Update <recording> flag & notify delegate
    recording = true
    delegate?.cameraDidStartVideoCapture()
}


func createAssetWriter(_ outputWidth: Int, outputHeight: Int) -> AVAssetWriter? {
    // Update <outputURL> with temp file to hold video
    let tempPath = gFile.getUniqueTempPath(gFile.MP4File)
    outputURL = URL(fileURLWithPath: tempPath)

    // Return new asset writer or nil
    do {
        // Create asset writer
        let newWriter = try AVAssetWriter(outputURL: outputURL, fileType: AVFileTypeMPEG4)

        // Define video settings
        let videoSettings: [String : AnyObject] = [
            AVVideoCodecKey  : AVVideoCodecH264 as AnyObject,
            AVVideoWidthKey  : outputWidth as AnyObject,
            AVVideoHeightKey : outputHeight as AnyObject,
        ]

        // Add video input to writer
        assetWriterVideoInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings)
        assetWriterVideoInput!.expectsMediaDataInRealTime = true
        newWriter.add(assetWriterVideoInput!)

        // Define audio settings
        let audioSettings : [String : AnyObject] = [
            AVFormatIDKey : NSInteger(kAudioFormatMPEG4AAC) as AnyObject,
            AVNumberOfChannelsKey : 2 as AnyObject,
            AVSampleRateKey : NSNumber(value: 44100.0 as Double)
        ]

        // Add audio input to writer
        assetWriterAudioInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: audioSettings)
        assetWriterAudioInput!.expectsMediaDataInRealTime = true
        newWriter.add(assetWriterAudioInput!)

        // Return writer
        print("Created asset writer for \(outputWidth)x\(outputHeight) video")
        return newWriter
    } catch {
        printError("Error creating asset writer: \(error)")
        return nil
    }
}


func endVideoCapture() {
    // Update flag to stop data capture
    recording = false

    // Return if asset writer undefined
    if assetWriter == nil {
        return
    }

    // If here, end capture
    // -- Mark inputs as done
    assetWriterVideoInput!.markAsFinished()
    assetWriterAudioInput!.markAsFinished()

    // -- Finish writing
    assetWriter!.finishWriting() {
        self.assetWriterDidFinish()
    }
}

func assetWriterDidFinish() {
    print("Asset writer finished with status: \(getAssetWriterStatus())")

    // Return early on error & tell delegate
    if assetWriter!.error != nil {
        printError("Error finishing asset writer: \(assetWriter!.error)")
        delegate?.panabeeCameraDidEndVideoCapture(videoURL: nil, videoDur: 0, error: assetWriter!.error)
        logEvent("Asset Writer Finish Error", userData: ["Error" : assetWriter!.error.debugDescription])
        return
    }

    // If here, no error so extract video properties & tell delegate
    let videoAsset = AVURLAsset(url: outputURL, options: nil)
    let videoDur = CMTimeGetSeconds(videoAsset.duration)
    let videoTrack = videoAsset.tracks(withMediaType: AVMediaTypeVideo)[0]
    print("Camera created video. Duration: \(videoDur). Size: \(videoTrack.naturalSize). Transform: \(videoTrack.preferredTransform). URL: \(outputURL).")

    // Tell delegate
    delegate?.cameraDidEndVideoCapture(videoURL: outputURL.path, videoDur: videoDur, error: assetWriter!.error)

    // Reset <assetWriter> to nil
    assetWriter = nil
}

func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
    // Return if not recording
    if !recording {
        return
    }

    // If here, capture data
    // Write video data?
    if captureOutput == videoOutput && assetWriterVideoInput!.isReadyForMoreMediaData {
        assetWriterVideoQueue!.async {
            self.assetWriterVideoInput!.append(sampleBuffer)
        }
    }

    // No, write audio data?
    if captureOutput == audioOutput && assetWriterAudioInput!.isReadyForMoreMediaData {
        assetWriterAudioQueue!.async {
            self.assetWriterAudioInput!.append(sampleBuffer)
        }
    }
}
Crashalot
  • 33,605
  • 61
  • 269
  • 439
  • Why are you calling `finishWriting`? If you need to pause writing, you could pause the capture session or not write the frames you receive - both approaches may require modifying the sample buffer time stamps. – Rhythmic Fistman Jan 15 '17 at 22:50
  • @RhythmicFistman aren't you supposed to call `finishWriting` when done recording? The class docs are painfully scant on details; where did you see that calling `finishWriting` is not recommend? Also, we do discard frames if they arrive too late. If `finishWriting` is called already (i.e., user wants session to end), the code is supposed to discard future frames, which you can see on the first line of the `captureOutput` function. Thanks for your help! – Crashalot Jan 16 '17 at 05:22
  • 1
    Once you call `finishWriting` you have to create a new `AVAssetWriter` if you want to record something else. – Rhythmic Fistman Jan 16 '17 at 05:24
  • @RhythmicFistman Yup, understood. That's not the issue. There is a sister call to `endVideoCapture` which starts the video capture, and inside this function a new `AVAssetWriter` gets created. Any suggestions? – Crashalot Jan 16 '17 at 05:35
  • Do the writer inputs get reset too? You should show that code. – Rhythmic Fistman Jan 16 '17 at 05:53
  • @RhythmicFistman ok added that code. didn't show it originally since didn't suspect it as an issue and didn't want to bloat the question. thanks for your help! – Crashalot Jan 16 '17 at 06:26
  • Did you find where the problem comes from? I have a similar issue. When calling `finishWriting` to stop the recording, append(sampleBuffer) still get called on audio and video writer inputs and it crashes (NSException). – Marie Dm May 09 '17 at 07:53
  • I re-added `markAsFinished` before `finishWriting` and I just had the same error as you > Cannot append media data after ending session. – Marie Dm May 09 '17 at 14:26
  • @MarieDm does this crash happen consistently? are you using the same code to write video/audio data as in the question? in particular, are you using queues as the code above? – Crashalot Nov 25 '17 at 22:27
  • Hey @Crashalot I'm sorry it's been a while since the question was asked so I don't remember everything. I asked a most recent question where I pasted my code, so it must look like this > https://stackoverflow.com/questions/44135223/record-video-with-avassetwriter-first-frames-are-black I remember having way less crashes after I updated my code to this, but it was still happening sometimes (without much debugging info). I actually also wondered if it could be a queue issue, made some tests... But I don't know. – Marie Dm Nov 26 '17 at 15:44

0 Answers0