Requirement :
It sounds like litte different but this is what I want to achieve. I want to make movie (.mov) file in reverse. Just like how we rewind the movie file. I also want maintain same frame rate as my video is containing.
NOTE: I do not just want to play video file in reverse order. I want to generate new movie file playing in reverse order.
My Exploration :
I thought of below steps to perform to do the same.
AVAssetExportSession
AVMutableComposition
and AVAssetExportSession
.Using above steps , I am able to achieve resulting video file in reverse but I am having below concerns.
Does anybody is having any other optimized way to achieve this? Any suggestion will be appreciated.
Here is my solution , maybe it can help you. https://github.com/KayWong/VideoReverse
Swift 5, credit to Andy Hin as I based this on http://www.andyhin.com/post/5/reverse-video-avfoundation
class func reverseVideo(inURL: URL, outURL: URL, queue: DispatchQueue, _ completionBlock: ((Bool)->Void)?) {
let asset = AVAsset.init(url: inURL)
guard
let reader = try? AVAssetReader.init(asset: asset),
let videoTrack = asset.tracks(withMediaType: .video).first
else {
assert(false)
completionBlock?(false)
return
}
let width = videoTrack.naturalSize.width
let height = videoTrack.naturalSize.height
let readerSettings: [String : Any] = [
String(kCVPixelBufferPixelFormatTypeKey) : kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
]
let readerOutput = AVAssetReaderTrackOutput.init(track: videoTrack, outputSettings: readerSettings)
reader.add(readerOutput)
reader.startReading()
var buffers = [CMSampleBuffer]()
while let nextBuffer = readerOutput.copyNextSampleBuffer() {
buffers.append(nextBuffer)
}
let status = reader.status
reader.cancelReading()
guard status == .completed, let firstBuffer = buffers.first else {
assert(false)
completionBlock?(false)
return
}
let sessionStartTime = CMSampleBufferGetPresentationTimeStamp(firstBuffer)
let writerSettings: [String:Any] = [
AVVideoCodecKey : AVVideoCodecType.h264,
AVVideoWidthKey : width,
AVVideoHeightKey: height,
]
let writerInput: AVAssetWriterInput
if let formatDescription = videoTrack.formatDescriptions.last {
writerInput = AVAssetWriterInput.init(mediaType: .video, outputSettings: writerSettings, sourceFormatHint: (formatDescription as! CMFormatDescription))
} else {
writerInput = AVAssetWriterInput.init(mediaType: .video, outputSettings: writerSettings)
}
writerInput.transform = videoTrack.preferredTransform
writerInput.expectsMediaDataInRealTime = false
guard
let writer = try? AVAssetWriter.init(url: outURL, fileType: .mp4),
writer.canAdd(writerInput)
else {
assert(false)
completionBlock?(false)
return
}
let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor.init(assetWriterInput: writerInput, sourcePixelBufferAttributes: nil)
let group = DispatchGroup.init()
group.enter()
writer.add(writerInput)
writer.startWriting()
writer.startSession(atSourceTime: sessionStartTime)
var currentSample = 0
writerInput.requestMediaDataWhenReady(on: queue) {
for i in currentSample..<buffers.count {
currentSample = i
if !writerInput.isReadyForMoreMediaData {
return
}
let presentationTime = CMSampleBufferGetPresentationTimeStamp(buffers[i])
guard let imageBuffer = CMSampleBufferGetImageBuffer(buffers[buffers.count - i - 1]) else {
WLog("VideoWriter reverseVideo: warning, could not get imageBuffer from SampleBuffer...")
continue
}
if !pixelBufferAdaptor.append(imageBuffer, withPresentationTime: presentationTime) {
WLog("VideoWriter reverseVideo: warning, could not append imageBuffer...")
}
}
// finish
writerInput.markAsFinished()
group.leave()
}
group.notify(queue: queue) {
writer.finishWriting {
if writer.status != .completed {
WLog("VideoWriter reverseVideo: error - \(String(describing: writer.error))")
completionBlock?(false)
} else {
completionBlock?(true)
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With