Видео в финальном видео обрезают с помощью AVAssetExportSession - PullRequest
1 голос
/ 24 января 2020

Я пытаюсь объединить несколько видео в одно видео. Видео - результат источника, составленного из смешанных СМИ; видео и картинки. Я использую «множественный» процесс экспорта, другими словами, я экспортирую все изображения в их собственные «видео», а затем использую эти видео для объединения вместе с видео и пытаюсь создать окончательное видео.

Проблема в том, что, когда я смешиваю медиа, видео, которые я создаю из изображений, обрезаются или перезаписываются.

Рабочий процесс выглядит следующим образом

  • начинается с [VideoData]
  • отправьте это в startMixedMediaMerge fun c
  • , если VideoData имеет статус c media (другими словами, это imageURL), затем создайте видео из этого изображения, используя пиксельный буфер, и сохраните это видео и получите URL для файла
  • , если VideoData не является стати c media, чем мы пропускаем URL для этого видео
  • , возьмем все наши URL URL видео и отправим те в другой процесс слияния, который создает окончательное видео

Вот код:

  1. Начните с [VideoData] и передайте его в startMixedMediaMerge fun c
private func startMixedMediaMerge(_ videoData: [VideoData], _ completion: @escaping CompletionWithMedia) {
        reset()
        var totalCount = videoData.count
        var mixDownData: [VideoData] = []

        videoData.enumerated().forEach { (index, data) in
            if data.isStaticMedia {
                shouldOptimiseImageForVideo = true
                fileIndex = String(index)

                mergeImages(from: [data], withAudio: nil) { (localURL, error) in
                    if let error = error {
                        P5Log(andReport: true, error)
                        return
                    } else {
                        let newData = VideoData(index: data.index, isStaticMedia: false, mediaURL: localURL, format: data.format, videoIn: data.videoIn, videoOut: data.videoOut)
                        mixDownData.append(newData)
                    }

                    totalCount -= 1
                    if totalCount <= 0 {
                        mixDownData.sort(by: { $0.videoIn < $1.videoIn })
                        completion(mixDownData)
                    }
                }
            } else {
                mixDownData.append(data)

                totalCount -= 1
                if totalCount <= 0 {
                    mixDownData.sort(by: { $0.videoIn < $1.videoIn })
                    completion(mixDownData)
                }
            }
        }
    }

Сортирует элементы videoData и создает коллекцию новых объектов videoData (все из которых являются файлами .mp4 видео)

1a. если videoData является медиаисточником c (другими словами, медиа-URL заканчивается на .jpg), создайте из него видео

private func mergeImages(from videoData: [VideoData], withAudio audioURL: URL?, _ completion: @escaping Completion) {

        let type: VideoGeneratorType = videoData.count > 1 ? .singleAudioMultipleImage : .single

        queue.async { [weak self] in

            self?.configureFor(videoData: videoData, type: type)

            if let firstImage = self?.videoImages.first?.image {
                self?.minSize = firstImage.size
            }

            let inputSize = self?.minSize ?? .zero
            let outputSize = self?.minSize ?? .zero

            self?.getTempVideoFileUrl { (videoOutputURL) in
                do {
                    try self?.videoWriter = AVAssetWriter(outputURL: videoOutputURL, fileType: .mp4)
                } catch {
                    self?.videoWriter = nil
                    self?.queue.main {
                        completion(.none, error)
                    }
                }

                if let videoWriter = self?.videoWriter {

                    let videoSettings: [String: AnyObject] = [
                        AVVideoCodecKey: (AVVideoCodecType.h264 as AnyObject),
                        AVVideoWidthKey: (outputSize.width as AnyObject),
                        AVVideoHeightKey: (outputSize.height as AnyObject)
                    ]

                    let videoWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)

                    let sourceBufferAttributes: [String: AnyObject] = [
                        (kCVPixelBufferPixelFormatTypeKey as String): (Int(kCVPixelFormatType_32ARGB) as AnyObject),
                        (kCVPixelBufferWidthKey as String): (Float(inputSize.width) as AnyObject),
                        (kCVPixelBufferHeightKey as String): (Float(inputSize.height) as AnyObject),
                        (kCVPixelBufferCGImageCompatibilityKey as String): (NSNumber(value: true)),
                        (kCVPixelBufferCGBitmapContextCompatibilityKey as String): (NSNumber(value: true))
                    ]

                    let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput, sourcePixelBufferAttributes: sourceBufferAttributes)

                    assert(videoWriter.canAdd(videoWriterInput))
                    videoWriter.add(videoWriterInput)

                    if videoWriter.startWriting() {

                        videoWriter.startSession(atSourceTime: .zero)

                        assert(pixelBufferAdaptor.pixelBufferPool != nil)

                        let mediaQueue = DispatchQueue(label: "mediaInputQueue", attributes: [])
                        videoWriterInput.requestMediaDataWhenReady(on: mediaQueue, using: { () -> Void in

                            guard let videoImages = self?.videoImages else {
                                completion(.none, VideoGeneratorError(.kMissingVideoURLs))
                                return
                            }

                            var frameCount = 0
                            var elapsedTime: Double = 0
                            var remainingVideoImages = self?.videoImages
                            var nextStartTimeForFrame: CMTime! = CMTime(seconds: 0, preferredTimescale: 1)
                            var imageForVideo: UIImage!

                            while videoWriterInput.isReadyForMoreMediaData && frameCount < videoImages.count {
                                if self?.generatorType == .single {
                                    guard let videoImage = remainingVideoImages?.remove(at: 0) else { continue }
                                    let duration = videoImage.outTime - videoImage.inTime
                                    let frameDuration = CMTime(seconds: ceil(Double(duration / Double(videoImages.count))), preferredTimescale: 1)

                                    imageForVideo = videoImage.image

                                    nextStartTimeForFrame = frameCount == 0 ? CMTime(seconds: 0, preferredTimescale: 1) : CMTime(seconds: Double(frameCount) * frameDuration.seconds, preferredTimescale: 1)

                                } else {
                                    imageForVideo = self?.videoImages[frameCount].image

                                    nextStartTimeForFrame = frameCount == 0 ? CMTime(seconds: 0, preferredTimescale: 600) : CMTime(seconds: Double(elapsedTime), preferredTimescale: 600)

                                    let audioTime = self?.videoDurationInSeconds ?? 1
                                    let totalImages = videoImages.count
                                    elapsedTime += audioTime / Double(totalImages)
                                }

                                let shouldAppendPixelBufferForImage = self?.appendPixelBufferForImage(imageForVideo, pixelBufferAdaptor: pixelBufferAdaptor, presentationTime: nextStartTimeForFrame)

                                if  shouldAppendPixelBufferForImage == false {
                                    self?.queue.main { [weak self] in
                                        self?.videoWriter = nil
                                        completion(.none, VideoGeneratorError(.kFailedToAppendPixelBufferError))
                                    }
                                }

                                frameCount += 1
                            }

                            videoWriterInput.markAsFinished()

                            if let maxLength = self?.maxVideoLengthInSeconds {
                                videoWriter.endSession(atSourceTime: CMTime(seconds: maxLength, preferredTimescale: 1))
                            }

                            videoWriter.finishWriting { () -> Void in
                                self?.videoWriter = nil

                                if let audioURL = audioURL {
                                    self?.mergeVideoWithAudio(videoUrl: videoOutputURL, audioUrl: audioURL) { (localURL, error) in
                                        completion(localURL, error)
                                    }
                                } else {
                                    completion(videoOutputURL, .none)
                                }
                            }
                        })
                    } else {
                        self?.queue.main { [weak self] in
                            self?.videoWriter = nil
                            completion(.none, VideoGeneratorError(.kFailedToStartAssetWriterError))
                        }
                    }
                } else {
                    self?.queue.main { [weak self] in
                        self?.videoWriter = nil
                        completion(.none, VideoGeneratorError(.kFailedToStartAssetWriterError))
                    }
                }
            }
        }
    }
Обработка выше создает новую коллекцию [VideoData] только с видео и отправляет это в mergeVideos fun c
private func mergeVideos(from videoData: [VideoData], _ completion: @escaping Completion) {
        guard !videoData.compactMap({ $0.mediaURL }).isEmpty else {
            queue.main {
                completion(.none, VideoGeneratorError(.kMissingVideoURLs))
            }
            return
        }

        guard let outputURL = pathForExport() else {
            completion(.none, VideoGeneratorError(.kFailedToFetchDirectory))
            return
        }

        let mixComposition = AVMutableComposition()

        if let videoTrack: AVMutableCompositionTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) {
            var insertTime = CMTime.zero

            videoData.forEach { data in
                guard let videoURL = data.mediaURL else { return }

                let videoAsset = AVURLAsset(url: videoURL)
                let videoDurationSeconds = data.videoOut - data.videoIn
                let duration = CMTime(seconds: videoDurationSeconds, preferredTimescale: 1)

                do {
                    if let assetVideoTrack = videoAsset.tracks(withMediaType: .video).first {
                        let frameRange = CMTimeRange(start: CMTime(seconds: 0, preferredTimescale: 1), duration: duration)
                        try videoTrack.insertTimeRange(frameRange, of: assetVideoTrack, at: insertTime)

                        videoTrack.preferredTransform = assetVideoTrack.preferredTransform
                    }

                    insertTime = CMTimeAdd(insertTime, duration)
                } catch {
                    queue.main {
                        completion(.none, error)
                    }
                }
            }


guard let exportSession = AVAssetExportSession(asset: asset, presetName: preset) else {
         fatalError(VideoGeneratorError(.kFailedToStartAssetExportSession).localizedDescription)
        }

        exportSession.outputFileType = .mov
        exportSession.canPerformMultiplePassesOverSourceMediaData = true
        exportSession.shouldOptimizeForNetworkUse = false
            exportSession.outputURL = outputURL

        exportSession.exportAsynchronously { [weak self] in
                self?.queue.main {
                    if let error = exportSession.error {
                        completion(.none, error)
                    } else {
                        completion(outputURL, .none)
                    }
                }
            }
        }
    } 
Как только окончательный URL возвращается и это видео сохраняется, мы отправляем его нашему проигрывателю. Видео экспортируется без ошибок, и оно воспроизводится. Проблема заключается в том, что первое «видео» (полученное из фактического видеоисточника; ie: URL-адрес .mp4) «перезаписывает» второе «видеоизображение» (которое из забавы mergeImages c. I Посмотрел видео, которое mergeImages экспортирует в файловую систему, и оно правильное, имеет правильную длину и воспроизводится нормально. Но когда я объединяю его со вторым видео, оно обрезается.

Чтобы сделать это более ясным. - Я создаю видео из изображений, и его длина 8 секунд - я объединяю его с видео из .mp4, которое имеет длину 10 секунд - в финальном видео, которое выходит, есть оба видео, но Во-первых, .mp4 теперь воспроизводится в течение 14 секунд, а клип из видеоизображения обрезается до 4 секунд

Я провел некоторые эксперименты, и он всегда обрезает ровно половину продолжительности "images video"

Кроме того, я протестировал только забаву mergeImages c и отправил несколько изображений, в результате получилось правильное видео.

Я протестировал весь этот рабочий процесс с помощью Только изображения и полученное видео также правильные

, и я сделал то же самое для mergeVideos и всего рабочего процесса с видео .mp4, и снова результат верный.

Единственный раз, когда это Кажется, это происходит, когда я создаю смешанное видео и использую видеоизображение в mergeVideos fun c.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...