10
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AVAssetWriterで動画の出力

Posted at

AVAssetExportSessionで失敗するAVAssetの書き出しもAVAssetReaderで読み込んで(バッファリングして)、AVAssetWriterで書き出すと上手く保存できたので忘れないようにメモ。

///
// MARK: ------------------------------ AVAsset
///
///
///
extension AVAsset {
    ///
    // MARK: ------------------------------ func
    ///
    /// Readerで読み込んでWriterで書き出し
    ///
    /// ```
    /// asset.wroteTempDir { (err: NSError?, wroteUrl: URL?) in
    ///     if let err = err {
    ///         print("\(err.localizedDescription)")
    ///     } else {
    ///         /// success
    ///     }
    /// }
    /// ```
    ///
    func wroteTempDir(_ cbWrote: ((_ err: NSError?, _ wroteUrl: URL?) -> Void)?) {
        let err: NSError = NSError.init(domain: "hoge", code: 999, userInfo: [NSLocalizedDescriptionKey: "エラー説明"])
        let wroteUrl: URL = NSHomeDirectory() + "/tmp/wrote.MOV"
        ///
        /// setup reader & writer 
        ///
        guard let reader: AVAssetReader = try? AVAssetReader.init(asset: self) else {
            cbWrote?(err, nil)
            return
        }
        guard let writer: AVAssetWriter = try? AVAssetWriter(outputURL: wroteUrl, fileType: AVFileType.mov) else {
            cbWrote?(err, nil)
            return
        }
        ///
        /// setup finish closure
        ///
        var audioFinished: Bool = false
        var videoFinished: Bool = false
        let finishClosure: (() -> Void) = {
            if audioFinished == true && videoFinished == true {
                writer.finishWriting {
                    cbWrote?(nil, wroteUrl)
                }
                reader.cancelReading()
            }
        }
        ///
        /// prepare reader for video
        ///
        let readerVideoOutput: AVAssetReaderTrackOutput = AVAssetReaderTrackOutput(
            track: self.tracks(withMediaType: AVMediaType.video)[0],
            outputSettings: [
                kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange),
            ]
        )
        reader.add(readerVideoOutput)
        ///
        /// prepare reader for audio
        ///
        var readerAudioOutput: AVAssetReaderTrackOutput!
        if self.tracks(withMediaType: AVMediaType.audio).count <= 0 {
            /// 無音も有り得るので考慮
            audioFinished = true
        } else {
            readerAudioOutput = AVAssetReaderTrackOutput.init(
                track: self.tracks(withMediaType: AVMediaType.audio)[0],
                outputSettings: [
                    AVSampleRateKey: 44100,
                    AVFormatIDKey:   kAudioFormatLinearPCM,
                ]
            )
            if reader.canAdd(readerAudioOutput) {
                reader.add(readerAudioOutput)
            } else {
                print("Cannot add audio output reader")
                audioFinished = true
            }
        }
        ///
        /// prepare writer input for video
        ///
        let writerVideoInput: AVAssetWriterInput = AVAssetWriterInput.init(
            mediaType: AVMediaType.video,
            outputSettings: [
                AVVideoCodecKey:                 AVVideoCodecType.h264,
                AVVideoWidthKey:                 self.tracks(withMediaType: AVMediaType.video)[0].naturalSize.width,
                AVVideoHeightKey:                self.tracks(withMediaType: AVMediaType.video)[0].naturalSize.height,
                AVVideoCompressionPropertiesKey: [
                    AVVideoAverageBitRateKey: self.tracks(withMediaType: AVMediaType.video)[0].estimatedDataRate,
                ],
            ]
        )
        writerVideoInput.expectsMediaDataInRealTime = false
        writerVideoInput.transform = self.tracks(withMediaType: AVMediaType.video)[0].preferredTransform
        writer.add(writerVideoInput)
        ///
        /// prepare writer input for audio
        ///
        var writerAudioInput: AVAssetWriterInput! = nil
        if self.tracks(withMediaType: AVMediaType.audio).count > 0 {
            let formatDesc: [Any] = self.tracks(withMediaType: AVMediaType.audio)[0].formatDescriptions
            var channels: UInt32 = 1
            var sampleRate: Float64 = 44100.000000
            for i in 0 ..< formatDesc.count {
                guard let bobTheDesc: UnsafePointer<AudioStreamBasicDescription> = CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc[i] as! CMAudioFormatDescription) else {
                    continue
                }
                channels = bobTheDesc.pointee.mChannelsPerFrame
                sampleRate = bobTheDesc.pointee.mSampleRate
                break
            }
            writerAudioInput = AVAssetWriterInput.init(
                mediaType: AVMediaType.audio,
                outputSettings: [
                    AVFormatIDKey:         kAudioFormatMPEG4AAC,
                    AVNumberOfChannelsKey: channels,
                    AVSampleRateKey:       sampleRate,
                    AVEncoderBitRateKey:   128000,
                ]
            )
            writerAudioInput.expectsMediaDataInRealTime = true
            writer.add(writerAudioInput)
        }
        ///
        /// write
        ///
        let videoQueue = DispatchQueue.init(label: "videoQueue")
        let audioQueue = DispatchQueue.init(label: "audioQueue")
        writer.startWriting()
        reader.startReading()
        writer.startSession(atSourceTime: CMTime.zero)
        ///
        /// write for video
        ///
        writerVideoInput.requestMediaDataWhenReady(on: videoQueue) {
            while writerVideoInput.isReadyForMoreMediaData {
                autoreleasepool {
                    let buffer = readerVideoOutput.copyNextSampleBuffer()
                    if buffer != nil {
                        writerVideoInput.append(buffer!)
                    } else {
                        writerVideoInput.markAsFinished()
                        DispatchQueue.main.async {
                            videoFinished = true
                            finishClosure()
                        }
                    }
                }
            }
        }
        if writerAudioInput != nil {
            writerAudioInput.requestMediaDataWhenReady(on: audioQueue) {
                while writerAudioInput.isReadyForMoreMediaData {
                    autoreleasepool {
                        let buffer = readerAudioOutput.copyNextSampleBuffer()
                        if buffer != nil {
                            writerAudioInput.append(buffer!)
                        } else {
                            writerAudioInput.markAsFinished()
                            DispatchQueue.main.async {
                                audioFinished = true
                                finishClosure()
                            }
                        }
                    }
                }
            }
        }
    }

こちらの記事が大変参考になりました🙇‍♂️

https://samkirkiles.svbtle.com/swift-using-avassetwriter-to-compress-video-files-for-network-transfer
https://qiita.com/takecian/items/f66942df058695b46f5f

動画のマスク処理もできそうなので、自分のアプリ「ShapingMovie」もバージョンアップしようと思います✨

10
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?