Позвольте мне начать с того, что я долго искал ответ на этот вопрос и прочитал много SO-сообщений, но ни один не дал ответ, который мне нужен
Я пытаюсь записать несколько сегментов видео, используя несколько AVAssetWriters / AVAssetWriterInputs, которые я создаю и немедленно ставлю в очередь.
Проблема, с которой я сталкиваюсь, заключается в том, что хотя видео записываются правильно, когда они вводят AVQueuePlayer для воспроизведения в последовательном порядке, возникает случайное явление, при котором одно из видео останавливается, но продолжается для воспроизведения аудио.
Подготовка 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
через определенные интервалы, чтобы получить видео из outputURL каждого 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)
}
}
}
Проблема
Чтобы сформулировать проблему, эта реализация создает четыре видео практически без проблем в каждом видео, , однако , проблема возникает, когда я пытаюсь воспроизвести эти видео в последовательном порядке, используя AVQueuePlayer
. Одно из этих видео случайным образом зависает ТОЛЬКО в самом начале , продолжая при этом воспроизводить звук до конца. Я мог выполнять эту запись столько раз, сколько мне нравится, и получать разные результаты. Фактически, есть некоторые случаи, когда замораживание НЕ происходит вообще и воспроизводит все видео без проблем.
Но чаще всего замораживание происходит с совершенно случайными интервалами. Эта заморозка не разморозить . Остальная часть приложения по-прежнему отзывчива (я могу нажать кнопку, чтобы удалить все видео и позволить мне начать все заново с самого начала). Но повторное выполнение записи приведет к остановке через некоторый другой случайный интервал времени.
Я пытался объединить видео в однокадровое видео и воспроизвести его в приложении "Фотографии", и там также происходит случайное замораживание. НО , я также загружаю видео отдельно в приложение «Фотографии» (четыре коротких видео и одно длинное видео загружаются в фотографии), и каждое видео воспроизводится совершенно нормально, если воспроизводится отдельно.