13

CIFilterをAVAssetに適用しようとしていて、フィルタを適用して保存しようとしています。私がこれをやっている方法は、オブジェクトに設定されたvideoCompositionAVVideoCompositingクラスに設定してAVAssetExportSessionを使用することです。カスタムAVVideoCompositingクラスが期待どおりに機能しない

私のAVMutableVideoCompositionオブジェクトのinstructionsも、カスタム合成命令クラス(AVMutableVideoCompositionInstructionに準拠)に設定しています。このクラスにはトラックIDが渡され、他の重要でない変数がいくつか渡されます。

残念ながら、カスタムビデオコンポジタークラス(AVVideoCompositingに準拠)のstartVideoCompositionRequest:関数が正しく呼び出されないという問題が発生しました。

私のカスタム命令クラスの変数をトラックIDに設定すると、AVVideoCompositingstartVideoCompositionRequest(request)関数が呼び出されません。

しかし、私は私のカスタム命令クラスのpassthroughTrackID変数を設定していない、startVideoCompositionRequest(request)と呼ばれるですが、正しくない - 空の配列でrequest.sourceTrackIDs結果、およびnilの値でrequest.sourceFrameByTrackID(trackID)結果を印刷します。

私が見つけた興味深い点は、フィルタを使用してビデオを書き出すときに、cancelAllPendingVideoCompositionRequests:関数が常に2回呼び出されるということでした。 startVideoCompositionRequest:の前に一度だけ呼び出されます。startVideoCompositionRequest:が呼び出されない場合は、一度呼び出されます。

フィルタを使用して動画を書き出すための3つのクラスを作成しました。ここでは基本的にはexport機能を備えており、必要なすべてのコードを呼び出すユーティリティクラスは、だ

class VideoFilterExport{ 

    let asset: AVAsset 
    init(asset: AVAsset){ 
     self.asset = asset 
    } 

    func export(toURL url: NSURL, callback: (url: NSURL?) -> Void){ 
     guard let track: AVAssetTrack = self.asset.tracksWithMediaType(AVMediaTypeVideo).first else{callback(url: nil); return} 

     let composition = AVMutableComposition() 
     let compositionTrack = composition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid) 

     do{ 
      try compositionTrack.insertTimeRange(track.timeRange, ofTrack: track, atTime: kCMTimeZero) 
     } 
     catch _{callback(url: nil); return} 

     let videoComposition = AVMutableVideoComposition(propertiesOfAsset: composition) 
     videoComposition.customVideoCompositorClass = VideoFilterCompositor.self 
     videoComposition.frameDuration = CMTimeMake(1, 30) 
     videoComposition.renderSize = compositionTrack.naturalSize 

     let instruction = VideoFilterCompositionInstruction(trackID: compositionTrack.trackID) 
     instruction.timeRange = CMTimeRangeMake(kCMTimeZero, self.asset.duration) 
     videoComposition.instructions = [instruction] 

     let session: AVAssetExportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetMediumQuality)! 
     session.videoComposition = videoComposition 
     session.outputURL = url 
     session.outputFileType = AVFileTypeMPEG4 

     session.exportAsynchronouslyWithCompletionHandler(){ 
      callback(url: url) 
     } 
    } 
} 

はここで他の二つのクラスだ - 私は、これはポストにするために一つのコードブロックにそれらの両方を出してあげる短い

// Video Filter Composition Instruction Class - from what I gather, 
// AVVideoCompositionInstruction is used only to pass values to 
// the AVVideoCompositing class 

class VideoFilterCompositionInstruction : AVMutableVideoCompositionInstruction{ 

    let trackID: CMPersistentTrackID 
    let filters: ImageFilterGroup 
    let context: CIContext 


    // When I leave this line as-is, startVideoCompositionRequest: isn't called. 
    // When commented out, startVideoCompositionRequest(request) is called, but there 
    // are no valid CVPixelBuffers provided by request.sourceFrameByTrackID(below value) 
    override var passthroughTrackID: CMPersistentTrackID{get{return self.trackID}} 
    override var requiredSourceTrackIDs: [NSValue]{get{return []}} 
    override var containsTweening: Bool{get{return false}} 


    init(trackID: CMPersistentTrackID, filters: ImageFilterGroup, context: CIContext){ 
     self.trackID = trackID 
     self.filters = filters 
     self.context = context 

     super.init() 

     //self.timeRange = timeRange 
     self.enablePostProcessing = true 
    } 

    required init?(coder aDecoder: NSCoder) { 
     fatalError("init(coder:) has not been implemented") 
    } 

} 


// My custom AVVideoCompositing class. This is where the problem lies - 
// although I don't know if this is the root of the problem 

class VideoFilterCompositor : NSObject, AVVideoCompositing{ 

    var requiredPixelBufferAttributesForRenderContext: [String : AnyObject] = [ 
     kCVPixelBufferPixelFormatTypeKey as String : NSNumber(unsignedInt: kCVPixelFormatType_32BGRA), // The video is in 32 BGRA 
     kCVPixelBufferOpenGLESCompatibilityKey as String : NSNumber(bool: true), 
     kCVPixelBufferOpenGLCompatibilityKey as String : NSNumber(bool: true) 
    ] 
    var sourcePixelBufferAttributes: [String : AnyObject]? = [ 
     kCVPixelBufferPixelFormatTypeKey as String : NSNumber(unsignedInt: kCVPixelFormatType_32BGRA), 
     kCVPixelBufferOpenGLESCompatibilityKey as String : NSNumber(bool: true), 
     kCVPixelBufferOpenGLCompatibilityKey as String : NSNumber(bool: true) 
    ] 

    let renderQueue = dispatch_queue_create("co.getblix.videofiltercompositor.renderingqueue", DISPATCH_QUEUE_SERIAL) 

    override init(){ 
     super.init() 
    } 

    func startVideoCompositionRequest(request: AVAsynchronousVideoCompositionRequest){ 
     // This code block is never executed when the 
     // passthroughTrackID variable is in the above class 

     autoreleasepool(){ 
      dispatch_async(self.renderQueue){ 
       guard let instruction = request.videoCompositionInstruction as? VideoFilterCompositionInstruction else{ 
        request.finishWithError(NSError(domain: "getblix.co", code: 760, userInfo: nil)) 
        return 
       } 
       guard let pixels = request.sourceFrameByTrackID(instruction.passthroughTrackID) else{ 
        // This code block is executed when I comment out the 
        // passthroughTrackID variable in the above class    

        request.finishWithError(NSError(domain: "getblix.co", code: 761, userInfo: nil)) 
        return 
       } 
       // I have not been able to get the code to reach this point 
       // This function is either not called, or the guard 
       // statement above executes 

       let image = CIImage(CVPixelBuffer: pixels) 
       let filtered: CIImage = //apply the filter here 

       let width = CVPixelBufferGetWidth(pixels) 
       let height = CVPixelBufferGetHeight(pixels) 
       let format = CVPixelBufferGetPixelFormatType(pixels) 

       var newBuffer: CVPixelBuffer? 
       CVPixelBufferCreate(kCFAllocatorDefault, width, height, format, nil, &newBuffer) 

       if let buffer = newBuffer{ 
        instruction.context.render(filtered, toCVPixelBuffer: buffer) 
        request.finishWithComposedVideoFrame(buffer) 
       } 
       else{ 
        request.finishWithComposedVideoFrame(pixels) 
       } 
      } 
     } 
    } 

    func renderContextChanged(newRenderContext: AVVideoCompositionRenderContext){ 
     // I don't have any code in this block 
    } 

    // This is interesting - this is called twice, 
    // Once before startVideoCompositionRequest is called, 
    // And once after. In the case when startVideoCompositionRequest 
    // Is not called, this is simply called twice in a row 
    func cancelAllPendingVideoCompositionRequests(){ 
     dispatch_barrier_async(self.renderQueue){ 
      print("Cancelled") 
     } 
    } 
} 

私はApple's AVCustomEdit sample projectを参考にしていますが、これが起こっている理由は何も見つかりません。

request.sourceFrameByTrackID:関数を正しく呼び出して、フレームごとに有効なCVPixelBufferを提供するにはどうすればよいですか?

答えて

6

それはカスタムAVVideoCompositionInstructionクラス(問題のVideoFilterCompositionInstruction)でrequiredSourceTrackIDs変数だから、最終的なカスタム構成命令クラスは

あるトラックIDに

override var requiredSourceTrackIDs: [NSValue]{ 
    get{ 
    return [ 
     NSNumber(value: Int(self.trackID)) 
    ] 
    } 
} 

を含む配列に設定する必要があることが判明

class VideoFilterCompositionInstruction : AVMutableVideoCompositionInstruction{ 
    let trackID: CMPersistentTrackID 
    let filters: [CIFilter] 
    let context: CIContext 

    override var passthroughTrackID: CMPersistentTrackID{get{return self.trackID}} 
    override var requiredSourceTrackIDs: [NSValue]{get{return [NSNumber(value: Int(self.trackID))]}} 
    override var containsTweening: Bool{get{return false}} 

    init(trackID: CMPersistentTrackID, filters: [CIFilter], context: CIContext){ 
     self.trackID = trackID 
     self.filters = filters 
     self.context = context 

     super.init() 

     self.enablePostProcessing = true 
    } 

    required init?(coder aDecoder: NSCoder){ 
     fatalError("init(coder:) has not been implemented") 
    } 
} 

このユーティリティのコードis also on GitHub

+0

こんにちは私はあなたのサンプルプロジェクトをgithubで試しましたが、私の側ではうまくいきません。 AVVideoCompositingでfunc startRequest(_ asyncVideoCompositionRequest:AVAsynchronousVideoCompositionRequest)が呼び出されない – Sam

+0

何が間違っているのか分かりますか? – Sam

+0

Swift 3でこれを試しましたか? – Sam

3

のすべてとして、あなたは、passthroughTrackIDがフィルタリングしたいトラックを返すことが正しいアプローチではないことを指摘しました。代わりに、フィルタリングするトラックをrequiredSourceTrackIDsから返す必要があります。 (それをしたら、それはあなたがでもの場合はpassthroughTrackIDから返されます。)それはこのように動作なぜの残りの質問に答えるために... passthroughTrackIDrequiredSourceTrackIDsため

ドキュメントは確かにこれまでAppleの明確な書き込みはありません。 (File a bug about itと彼らが向上する場合があります。)しかし、あなたは、元の記述に密接に見れば、ヒントがあります(強調は追加)...

命令の期間、映像合成結果が1である場合このプロパティは、対応するトラックIDを返します。 命令の実行中はコンポジタントは実行されず、適切なソースフレームが代わりに使用されます。だから、

あなたがを処理せずにを通じてシングルトラックを渡す命令クラスを作っているとき、あなたはpassthroughTrackIDのみを使用しています。

イメージ処理を実行する予定がある場合は、1つのトラックにコンポジットがない場合でも、代わりにrequiredSourceTrackIDsにそのトラックを指定してください。

+0

こんにちは、詳しく教えてください。 swift3でgithubプロジェクトを動作させるには? – Sam

関連する問題