iOS快速-录制多个视频片段会在AVQueuePlayer中冻结播放

时间:2018-07-05 04:07:47

标签: swift video avassetwriter avqueueplayer cmsamplebuffer

让我开始说我很久以来一直在寻找这个问题的答案,并且已经阅读了很多SO帖子,但没有一个提供我需要的答案

我正在尝试使用多个AVAssetWriters / AVAssetWriterInputs记录多个视频片段,我将它们实例化并立即排队。

我面临的问题是,尽管正确录制了视频,但是当它们进入要按顺序播放的AVQueuePlayer时,会随机出现其中一个视频冻结但继续的情况。 em>播放音频。

准备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)
    }
}

处理CMSampleBuffers 现在AVAssetWriters及其输入已初始化,我按了一个记录按钮,该按钮将更改Bool值以使AVCaptureVideoDataOutputSampleBufferDelegate知道开始捕获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")
            }
        }

}

从每个AVAssetWriter获取视频片段

关于获取视频片段,我以指定的时间间隔调用此endRecording函数,以便从每个AVAssetWriter的outputURL获取视频:

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)
        }
    }
}

问题

要重述该问题,此实现会创建四个视频,每个视频中几乎没有问题, ,当我尝试使用{{1}依次重播这些视频时,就会出现问题}。这些视频之一仅在开始时会随机冻结,同时继续播放音频直到结束。我可以根据自己的喜好进行多次录制,并得到不同的结果。实际上,在某些情况下,冻结完全不会发生,并且可以播放所有视频而没有任何问题。

但是通常情况下,冻结是在完全随机的时间间隔进行的。这种冻结不会解冻。该应用程序的其余部分仍保持响应(我可以按一个按钮删除所有视频,并允许我从头开始重新录制)。但是再次执行记录将导致在其他一些随机时间间隔内冻结。

我尝试将视频合并为单个视频,然后在“照片”应用中播放,并且随机冻结也在那里发生。 但是,我也分别将视频下载到“照片”应用程序中(将四个短视频和一个长视频下载到“照片”中),并且如果单独播放,每个视频都可以正常播放。

1 个答案:

答案 0 :(得分:1)

我以前没有使用AVQueuePlayer播放多个视频,但是根据我的经验,我建议您尝试使用AVMutableComposition连接视频。并使用AVPlayer播放它。