Подтвердить что ты не робот

Пользовательский класс AVVideoCompositing не работает должным образом

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

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

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

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

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

Что-то интересное, что я обнаружил, заключается в том, что функция 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 AVCustomEdit для руководства с этим, но я не могу найти в нем никаких причин, почему это происходит.

Как я могу получить функцию request.sourceFrameByTrackID: для правильного вызова и предоставить допустимый CVPixelBuffer для каждого кадра?

4b9b3361

Ответ 1

Оказывается, переменная requiredSourceTrackIDs в пользовательском AVVideoCompositionInstruction class (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")
    }
}

Весь код этой утилиты также находится в GitHub

Ответ 2

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

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

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

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

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