Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS - Reversing video file (.mov)

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.

  1. Make chunks of video files with specific frame rate using AVAssetExportSession
  2. Merge all these video chunks into single movie file using AVMutableComposition and AVAssetExportSession.
  3. Also merge audio of each file into new video file during the merge process.

Using above steps , I am able to achieve resulting video file in reverse but I am having below concerns.

  1. It takes good amount of time if video is of long duration.
  2. It also consumes huge CPU cycles and memory to accomplish this process.

Does anybody is having any other optimized way to achieve this? Any suggestion will be appreciated.

like image 881
BornCoder Avatar asked Aug 30 '12 09:08

BornCoder


2 Answers

Here is my solution , maybe it can help you. https://github.com/KayWong/VideoReverse

like image 94
KayWong Avatar answered Sep 30 '22 23:09

KayWong


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)
                }
            }
        }
    }
like image 44
xaphod Avatar answered Oct 01 '22 00:10

xaphod