让我开始说我很久以来一直在寻找这个问题的答案,并且已经阅读了很多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}依次重播这些视频时,就会出现问题}。这些视频之一仅在开始时会随机冻结,同时继续播放音频直到结束。我可以根据自己的喜好进行多次录制,并得到不同的结果。实际上,在某些情况下,冻结完全不会不发生,并且可以播放所有视频而没有任何问题。
但是通常情况下,冻结是在完全随机的时间间隔进行的。这种冻结不会解冻。该应用程序的其余部分仍保持响应(我可以按一个按钮删除所有视频,并允许我从头开始重新录制)。但是再次执行记录将导致在其他一些随机时间间隔内冻结。
我尝试将视频合并为单个视频,然后在“照片”应用中播放,并且随机冻结也在那里发生。 但是,我也分别将视频下载到“照片”应用程序中(将四个短视频和一个长视频下载到“照片”中),并且如果单独播放,每个视频都可以正常播放。
答案 0 :(得分:1)
我以前没有使用AVQueuePlayer播放多个视频,但是根据我的经验,我建议您尝试使用AVMutableComposition连接视频。并使用AVPlayer播放它。