Я пытаюсь объединить несколько видео в одно видео. Видео - результат источника, составленного из смешанных СМИ; видео и картинки. Я использую «множественный» процесс экспорта, другими словами, я экспортирую все изображения в их собственные «видео», а затем использую эти видео для объединения вместе с видео и пытаюсь создать окончательное видео.
Проблема в том, что, когда я смешиваю медиа, видео, которые я создаю из изображений, обрезаются или перезаписываются.
Рабочий процесс выглядит следующим образом
- начинается с [VideoData]
- отправьте это в startMixedMediaMerge fun c
- , если VideoData имеет статус c media (другими словами, это imageURL), затем создайте видео из этого изображения, используя пиксельный буфер, и сохраните это видео и получите URL для файла
- , если VideoData не является стати c media, чем мы пропускаем URL для этого видео
- , возьмем все наши URL URL видео и отправим те в другой процесс слияния, который создает окончательное видео
Вот код:
- Начните с [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.