0

Let me start by saying I have searched for an answer to this question for a long time and have read through many SO posts, but none have provided the answer I need

I am attempting to record multiple video segments using multiple AVAssetWriters/AVAssetWriterInputs, which I instantiate and queue up immediately.

The problem I am facing is that while the videos are recorded properly, when they enter an AVQueuePlayer to be played back in sequential order, there is a random occurrence by which one of the videos will freeze but continue to play audio.

Preparing the AVAssetWriters/VideoInputs/AudioInputs:

private func setupAssetWriters() {
    guard assetWriterOne == nil else { return }
    // This just returns a URL with ".mov" as the file suffix in the absoluteString.
    outputFileURLOne = createRandomizedURL(withMediaFileType: .mov) 
    (assetWriterOne, videoAssetWriterInputOne, audioAssetWriterInputOne) = configureAssetWriter(withInput: outputFileURLOne)

    guard assetWriterTwo == nil else { return }
    outputFileURLTwo = createRandomizedURL(withMediaFileType: .mov)
    (assetWriterTwo, videoAssetWriterInputTwo, audioAssetWriterInputTwo) = configureAssetWriter(withInput: outputFileURLTwo)

    guard assetWriterThree == nil else { return }
    outputFileURLThree = createRandomizedURL(withMediaFileType: .mov)
    (assetWriterThree, videoAssetWriterInputThree, audioAssetWriterInputThree) = configureAssetWriter(withInput: outputFileURLThree)

    guard assetWriterFour == nil else { return }
    outputFileURLFour = createRandomizedURL(withMediaFileType: .mov)
    (assetWriterFour, videoAssetWriterInputFour, audioAssetWriterInputFour) = configureAssetWriter(withInput: outputFileURLFour)

}


private func configureAssetWriter(withInput url: URL) -> (assetWriter: AVAssetWriter?, videoAssetWriterInput: AVAssetWriterInput?, audioAssetWriterInput: AVAssetWriterInput?){
    do {

        let recommendedVideoSettings: [String: Any]? = videoDataOutput?.recommendedVideoSettingsForAssetWriter(writingTo: AVFileType.mov)
        let assetWriter = try VideoAssetWriter(url: url, fileType: AVFileType.mov)
        guard
            assetWriter.canApply(outputSettings: recommendedVideoSettings, forMediaType: AVMediaType.video),
            assetWriter.canApply(outputSettings: audioSettings, forMediaType: AVMediaType.audio)
            else { return (nil, nil, nil) }

        let videoAssetWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: recommendedVideoSettings, sourceFormatHint: videoFormatDescription)
        let audioAssetWriterInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: audioSettings)

        guard
            assetWriter.canAdd(videoAssetWriterInput),
            assetWriter.canAdd(audioAssetWriterInput)
            else { return (nil, nil, nil) }
        assetWriter.add(videoAssetWriterInput)
        assetWriter.add(audioAssetWriterInput)

        videoAssetWriterInput.expectsMediaDataInRealTime = true
        audioAssetWriterInput.expectsMediaDataInRealTime = true

        return (assetWriter, videoAssetWriterInput, audioAssetWriterInput)

    } catch let error {
        print("error getting AVAssetWriter: \(error.localizedDescription)")
        return (nil, nil, nil)
    }
}

Processing the CMSampleBuffers Now that the AVAssetWriters and their inputs are initialized, I press a record button which changes a Bool value to let the AVCaptureVideoDataOutputSampleBufferDelegate know to begin capturing CMSampleBuffers:

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    if isRecording {
        processVideoSamples(withOutput: output, sampleBuffer: sampleBuffer)
    }
}








private func processVideoSamples(withOutput output: AVCaptureOutput, sampleBuffer: CMSampleBuffer) {

    switch currentVideoInProcess {
    case 1:
        processSamples(usingAssetWriter: assetWriterOne, videoAssetWriterInput: videoAssetWriterInputOne, audioAssetWriterInput: audioAssetWriterInputOne, withOutput: output, sampleBuffer: sampleBuffer)
        break

    case 2:
        processSamples(usingAssetWriter: assetWriterTwo, videoAssetWriterInput: videoAssetWriterInputTwo, audioAssetWriterInput: audioAssetWriterInputTwo, withOutput: output, sampleBuffer: sampleBuffer)
        break

    case 3:
        processSamples(usingAssetWriter: assetWriterThree, videoAssetWriterInput: videoAssetWriterInputThree, audioAssetWriterInput: audioAssetWriterInputThree, withOutput: output, sampleBuffer: sampleBuffer)
        break

    case 4:
        processSamples(usingAssetWriter: assetWriterFour, videoAssetWriterInput: videoAssetWriterInputFour, audioAssetWriterInput: audioAssetWriterInputFour, withOutput: output, sampleBuffer: sampleBuffer)
        break

    default:

        break
    }
}


private func processSamples(usingAssetWriter assetWriter: VideoAssetWriter?, videoAssetWriterInput: AVAssetWriterInput?, audioAssetWriterInput: AVAssetWriterInput?, withOutput output: AVCaptureOutput, sampleBuffer: CMSampleBuffer) {

        guard CMSampleBufferDataIsReady(sampleBuffer) else { return }

        guard
            assetWriter != nil,
            videoAssetWriterInput != nil,
            audioAssetWriterInput != nil
            else { return }

        if assetWriter?.status == .unknown {
            if let _ = output as? AVCaptureVideoDataOutput {
                print("\n STARTED RECORDING")
                assetWriter?.startWriting()
                closingTime = sampleTime
                let startRecordingTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
                assetWriter?.startSession(atSourceTime: startRecordingTime)
            } else {
                print("output type unknown")
                return
            }
        }

        if assetWriter?.status == .failed { return }

        guard assetWriter?.status == .writing else { return }

        if let _ = output as? AVCaptureVideoDataOutput {

            if (videoAssetWriterInput?.isReadyForMoreMediaData)! {
                videoAssetWriterInput?.append(sampleBuffer)   
                if assetWriter?.hasWrittenFirstVideoSample == false {
                    print("added 1st video frame")
                    assetWriter?.hasWrittenFirstVideoSample = true
                }
            } else {
                print("video writer not ready")
            }
        } else if let _ = output as? AVCaptureAudioDataOutput {
            if (audioAssetWriterInput?.isReadyForMoreMediaData)! && assetWriter?.hasWrittenFirstVideoSample == true {
                audioAssetWriterInput?.append(sampleBuffer)
            } else {
                print("audio writer not ready OR video not written yet")
            }
        }

}

Getting the video segments from each AVAssetWriter

As for getting the video segments, I call this endRecording function at specified intervals in order to get a video from the outputURL of each AVAssetWriter:

private func endRecordingOfCurrentWriter(completion: @escaping (_ fileURL: URL?, _ error: Error?) -> ()) {

    // currentVideoInProcess is just an Int to keep track of which AVAssetWriter is current processing CMSampleBuffers
    switch currentVideoInProcess {
    case 1:
        endRecording(atOutputURL: outputFileURLOne, usingAssetWriter: assetWriterOne, videoAssetWriterInput: videoAssetWriterInputOne, audioAssetWriterInput: audioAssetWriterInputOne) { (fileURL, error) in
            completion(fileURL, error)
        }
        break

    case 2:
        endRecording(atOutputURL: outputFileURLTwo, usingAssetWriter: assetWriterTwo, videoAssetWriterInput: videoAssetWriterInputTwo, audioAssetWriterInput: audioAssetWriterInputTwo) { (fileURL, error) in
            completion(fileURL, error)
        }
        break

    case 3:
        endRecording(atOutputURL: outputFileURLThree, usingAssetWriter: assetWriterThree, videoAssetWriterInput: videoAssetWriterInputThree, audioAssetWriterInput: audioAssetWriterInputThree) { (fileURL, error) in
            completion(fileURL, error)
        }
        break

    case 4:
        endRecording(atOutputURL: outputFileURLFour, usingAssetWriter: assetWriterFour, videoAssetWriterInput: videoAssetWriterInputFour, audioAssetWriterInput: audioAssetWriterInputFour) { (fileURL, error) in
            completion(fileURL, error)
        }
        break

    default: break
    }

    currentVideoInProcess += 1

}

private func endRecording(atOutputURL outputURL: URL, usingAssetWriter assetWriter: VideoAssetWriter?, videoAssetWriterInput: AVAssetWriterInput?, audioAssetWriterInput: AVAssetWriterInput?, completion: @escaping (_ fileURL: URL?, _ error: Error?) -> ()) {

    if assetWriter?.status.rawValue == 1 {
        videoAssetWriterInput?.markAsFinished()
        audioAssetWriterInput?.markAsFinished()
    }

    assetWriter?.finishWriting() {
        let status = assetWriter?.status

        guard assetWriter?.error == nil else {
            if let error = assetWriter?.error {
                completion(nil, assetWriter?.error)
            }
            return
        }

        switch status {
        case .completed?:
            completion(assetWriter?.outputURL, nil)
        case .failed?:
            completion(nil, assetWriter?.error)
        case .cancelled?:
            completion(nil, assetWriter?.error)
        default:
            completion(nil, assetWriter?.error)
        }
    }
}

The problem

To restate the problem, this implementation creates four videos with virtually no issue in each video, however, the problem arises when I try to replay these videos in a sequential order using an AVQueuePlayer. One of these videos randomly freezes ONLY at the very start while continuing to play the audio until the end. I could perform this recording as many times as I like and receive different results. In fact, there are some instances where the freezing does NOT happen at all and plays all videos with no issues.

But more often than not, the freezing occurs at completely random intervals. This freezing does not unfreeze. The rest of the app is still responsive (I can press a button to delete all the videos and allow me to record all over again from the beginning). But performing the record again will cause a freeze at some other random time-interval.

I've tried merging the videos into a single-video and playing it in the Photos app, and the random freeze occurs there as well. BUT, I also download the videos separately to the Photos app as well (four short videos and single long video are downloaded to the Photos), and each video plays completely fine if played separately.

Chrishon Wyllie
  • 209
  • 1
  • 2
  • 11

1 Answers1

1

I didn't use AVQueuePlayer to play multiple videos before but from my experience, I suggest you try AVMutableComposition to connect videos. and use AVPlayer to play it.

chenyungui
  • 11
  • 4
  • Yea, I tried that as well. That’s what I meant at the end about merging the videos. This final connected or merged video also freezes – Chrishon Wyllie Jul 05 '18 at 13:56
  • I have some suggestion for you to check. 1. when do recording, best make sure the first sample buffer is video(not audio), that will cause some timeRange issue. 2. when you do composition, also carefully check the timeRanges. If still have problem, you can post some composition code – chenyungui Jul 06 '18 at 05:40