Экспорт видео не сглаживается с помощью AVMutableVideoCompositionLayerInstruction - PullRequest
0 голосов
/ 31 января 2020

Я создал видео из одного изображения и хочу добавить эффект к этому видео с помощью AVMutableVideoCompositionLayerInstruction, но после его экспорта окончательное видео не будет плавным.

Вот ссылка на видео

Может кто-нибудь помочь мне сделать это гладко?

Вот мой код:

private func pixelBuffer(fromImage image: CGImage, size: CGSize) throws -> CVPixelBuffer {
        let options: CFDictionary = [kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true] as CFDictionary
        var pxbuffer: CVPixelBuffer? = nil
        let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(size.width), Int(size.height), kCVPixelFormatType_32ARGB, options, &pxbuffer)
        guard let buffer = pxbuffer, status == kCVReturnSuccess else { throw NSError.throwError() }

        CVPixelBufferLockBaseAddress(buffer, [])
        guard let pxdata = CVPixelBufferGetBaseAddress(buffer) else { throw NSError.throwError() }
        let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer)

        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
        guard let context = CGContext(data: pxdata, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) else { throw NSError.throwError() }
        context.concatenate(CGAffineTransform(rotationAngle: 0))
        context.draw(image, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))

        CVPixelBufferUnlockBaseAddress(buffer, [])

        return buffer
    }

    private func writeSingleImageToMovie(name: String, image: UIImage, movieLength: TimeInterval, outputFileURL: URL, completion: @escaping (Error?) -> ()) {
        do {
            let imageSize = image.size
            let videoWriter = try AVAssetWriter(outputURL: outputFileURL, fileType: AVFileType.mp4)
            let videoSettings: [String: Any] = [AVVideoCodecKey: AVVideoCodecType.h264,
                                                AVVideoWidthKey: imageSize.width,
                                                AVVideoHeightKey: imageSize.height]
            let videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
            let adaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput, sourcePixelBufferAttributes: nil)

            if !videoWriter.canAdd(videoWriterInput) { throw NSError.throwError() }
            videoWriterInput.expectsMediaDataInRealTime = true
            videoWriter.add(videoWriterInput)
            videoWriter.startWriting()

            let timeScale: Int32 = 600 // recommended in CMTime for movies.
            let halfMovieLength = Float64(movieLength/2.0) // videoWriter assumes frame lengths are equal.
            let startFrameTime = CMTimeMake(value: 0, timescale: timeScale)
            let endFrameTime = CMTimeMakeWithSeconds(halfMovieLength, preferredTimescale: timeScale)
            videoWriter.startSession(atSourceTime: startFrameTime)

            guard let cgImage = image.cgImage else { throw NSError.throwError() }
            let buffer: CVPixelBuffer = try pixelBuffer(fromImage: cgImage, size: imageSize)

            while !adaptor.assetWriterInput.isReadyForMoreMediaData { usleep(10) }
            adaptor.append(buffer, withPresentationTime: startFrameTime)
            while !adaptor.assetWriterInput.isReadyForMoreMediaData { usleep(10) }
            adaptor.append(buffer, withPresentationTime: endFrameTime)

            videoWriterInput.markAsFinished()
            videoWriter.finishWriting {
                if videoWriter.error == nil {
                    self.compositeVideo(videoUrl: outputFileURL, size: imageSize, onCompleted: completion)
                } else {
                    completion(videoWriter.error)
                }
            }
        } catch {
            completion(error)
        }
    }

Этот код добавляет эффект масштаба на видео

private func changeFilename(source: URL, toNewName new: URL) {
        do {
            if FileManager.default.fileExists(atPath: new.absoluteString) {
                try FileManager.default.removeItem(at: new)
            }
            try FileManager.default.moveItem(at: source, to: new)
        } catch {
            print(error)
        }
    }

    private func compositeVideo(videoUrl: URL, size: CGSize, onCompleted: @escaping (Error?) -> ()) {
        let tmpUrl = URL(fileURLWithPath: NSHomeDirectory() + "/Documents/" + imageVideosDirName).appendingPathComponent("tmp.mp4")
        changeFilename(source: videoUrl, toNewName: tmpUrl)
        let asset = AVURLAsset(url: tmpUrl)
        let composition = AVMutableComposition()
        let videoDuration = asset.duration
        guard
            let compositionTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid),
            let assetTrack = asset.tracks(withMediaType: .video).first else {
                onCompleted(NSError.throwError(domain: "Cannot create composition track."))
                return
        }

        do {
            let timeRange = CMTimeRange(start: .zero, duration: asset.duration)
            try compositionTrack.insertTimeRange(timeRange, of: assetTrack, at: .zero)
        } catch {
            print(error)
            onCompleted(error)
            return
        }

        let videoComposition = AVMutableVideoComposition()
        videoComposition.renderSize = size
        videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
        videoComposition.renderScale = 1

        let instructionVideo = AVMutableVideoCompositionInstruction()
        instructionVideo.timeRange = CMTimeRange(start: .zero, duration: videoDuration)
        videoComposition.instructions = [instructionVideo]

        let startTransform = CGAffineTransform(scaleX: 1, y: 1)
        let endTransform = CGAffineTransform(scaleX: 1.1, y: 1.1)
//        let startTransform = CGAffineTransform(translationX: 0, y: 0)
//        let endTransform = CGAffineTransform(translationX: 20, y: 0)
        let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack)
        instruction.setTransformRamp(fromStart: startTransform, toEnd: endTransform, timeRange: CMTimeRange(start: .zero, duration: videoDuration))
        instruction.setOpacityRamp(fromStartOpacity: 1, toEndOpacity: 0, timeRange: CMTimeRange(start: CMTime(seconds: 4, preferredTimescale: 600), duration: CMTime(seconds: 1, preferredTimescale: 600)))
        instructionVideo.layerInstructions = [instruction]
        print(composition.duration)
        guard let export = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
            print("Cannot create export session.")
            onCompleted(NSError.throwError(domain: "Cannot create export session."))
            return
        }

        export.videoComposition = videoComposition
        export.outputURL = videoUrl
        export.outputFileType = .mp4
        export.exportAsynchronously {
            switch export.status {
            case .completed:
                onCompleted(nil)
            default:
                onCompleted(NSError.throwError())
            }
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...