13

Я пытаюсь применить CIFilter к AVAsset, а затем сохраните его с применением фильтра. Способ, которым я это делаю, - использовать AVAssetExportSession с videoComposition, установленным для объекта AVMutableVideoComposition с пользовательским классом AVVideoCompositing.Пользовательский класс AVVideoCompositing не работает должным образом

Я также устанавливаю instructions моего объекта AVMutableVideoComposition в соответствии с классом инструкций по составу (соответствует AVMutableVideoCompositionInstruction). Этот класс передается идентификатором дорожки, а также несколькими другими несущественными переменными.

К сожалению, у меня возникла проблема - функция startVideoCompositionRequest: в моем пользовательском классе видеокомпилятора (соответствующая AVVideoCompositing) неправильно называется.

Когда я установил переменную passthroughTrackID моего пользовательского класса команд в идентификатор дорожки, функция startVideoCompositionRequest(request) в моем AVVideoCompositing не вызывается.

Тем не менее, когда я не установить passthroughTrackID переменных мой класс пользовательских команд, то startVideoCompositionRequest(request)это называется, но не правильно - печать request.sourceTrackIDs результатов в пустом массиве и request.sourceFrameByTrackID(trackID) результатов в нулевой стоимости.

Что-то интересное, что я обнаружил, что функция cancelAllPendingVideoCompositionRequests: всегда вызывается дважды при попытке экспортировать видео с фильтрами. Он либо вызывается один раз до startVideoCompositionRequest: и один раз после, либо дважды в строке в случае, когда startVideoCompositionRequest: не вызывается.

Я создал три класса для экспорта видео с фильтрами. Вот класс утилита, которая в основном только включает в себя 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

Оказывается, что requiredSourceTrackIDs переменные в пользовательском AVVideoCompositionInstruction классе (VideoFilterCompositionInstruction в вопросе) должно быть установлено на массив, содержащих идентификаторы трека

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, но он не работает на моей стороне. func startRequest (_ asyncVideoCompositionRequest: AVAsynchronousVideoCompositionRequest) не вызывается в AVVideoCompositing – Sam

+0

Знаете ли вы, что не так, я реализовал все, как было предложено. – Sam

+0

Вы пробовали это на Swift 3? – Sam

3

Как вы отметили, что passthroughTrackID возврат трека, который вы хотите отфильтровать, не подходит - вам нужно вернуть трек, который будет отфильтрован от requiredSourceTrackIDs. (И похоже, как только вы это сделаете, неважно, если вы тоже верните его с passthroughTrackID.) Для того, чтобы ответить на оставшийся вопрос о том, почему это работает таким образом ...

Документы для passthroughTrackID и requiredSourceTrackIDs конечно, не ясное писания от Apple когда-либо. (File a bug about it и они могли бы улучшить.) Но если вы внимательно посмотрите в описании первого, есть намек (курсив мой) ...

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

Таким образом, вы используете passthroughTrackIDтолько когда вы делаете класс команд, который проходит один трек через без обработки.

Если вы планируете выполнять любую обработку изображений, даже если это только одна дорожка без компоновки, укажите ее в requiredSourceTrackIDs.

+0

Привет, не могли бы вы подробно остановиться. Как сделать проект github работать с swift3? – Sam

Смежные вопросы