Рендеринг видео в иерархии CALayer с использованием CIFilters - PullRequest
0 голосов
/ 24 октября 2019

В пользовательском интерфейсе моего приложения для iOS отображается сложная иерархия CALayer с. Один из этих слоев - AVPlayerLayer, который отображает видео с CIFilter s, примененным в реальном времени (используя AVVideoComposition(asset:, applyingCIFiltersWithHandler:)).

Теперь я хочу экспортировать эту композицию слоев в видеофайл. В AVFoundation есть два инструмента, которые кажутся полезными:

A : AVVideoCompositionCoreAnimationTool, который позволяет визуализировать видео внутри (возможно анимированной) CALayer иерархии

B : AVVideoComposition(asset:, applyingCIFiltersWithHandler:), который я также использую в пользовательском интерфейсе, чтобы применить CIFilter s к видеоустройству.

Однако эти два инструмента нельзя использовать одновременно: если я запускаюAVAssetExportSession, который объединяет эти инструменты, AVFoundation выдает NSInvalidArgumentException:

Ожидается, что композиция видео будет содержать только AVCoreImageFilterVideoCompositionInstruction

Я попытался обойти этоограничение следующим образом:

Обходной путь 1

1) Настройте экспорт, используя AVAssetReader и AVAssetWriter

2) Получите буферы образцов изсчитыватель ресурсов и примените CIFilter, сохраните результат в CGImage.

3) Установите CGImage в качестве content видеослоя в иерархии слоев. Теперь иерархия слоев «выглядит как» один кадр конечного видео.

4) Получите данные CVPixelBuffer для каждого кадра от средства записи ресурсов, используя CVPixelBufferGetBaseAddress, и создайте CGContext с этимdata.

5) Выполните рендеринг моего слоя в этот контекст, используя CALayer.render(in ctx: CGContext).

Эта настройка работает, но очень медленная - экспорт 5-секундного видео иногда занимает минуту. Похоже, что вызовы CoreGraphics являются здесь узким местом (наверное, потому, что при таком подходе композиция происходит на процессоре?)

Обходной путь 2

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

Резюме

Что является хорошим подходом для экспорта этого видео вфайл, в идеале за один проход? Как я могу использовать CIFilter s и AVVideoCompositionCoreAnimationTool одновременно? Есть ли в AVFoundation собственный способ настройки «конвейера», объединяющего эти инструменты?

1 Ответ

1 голос
/ 31 октября 2019

Способ достижения этого заключается в использовании пользовательского AVVideoCompositing. Этот объект позволяет вам создавать (в данном случае применять CIFilter) каждый видеокадр.

Вот пример реализации, которая применяет эффект CIPhotoEffectNoir ко всему видео:

class VideoFilterCompositor: NSObject, AVVideoCompositing {

    var sourcePixelBufferAttributes: [String : Any]? = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
    var requiredPixelBufferAttributesForRenderContext: [String : Any] = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
    private var renderContext: AVVideoCompositionRenderContext?

    func renderContextChanged(_ newRenderContext: AVVideoCompositionRenderContext) {
        renderContext = newRenderContext
    }

    func cancelAllPendingVideoCompositionRequests() {
    }

    private let filter = CIFilter(name: "CIPhotoEffectNoir")!
    private let context = CIContext()
    func startRequest(_ asyncVideoCompositionRequest: AVAsynchronousVideoCompositionRequest) {
        guard let track = asyncVideoCompositionRequest.sourceTrackIDs.first?.int32Value, let frame = asyncVideoCompositionRequest.sourceFrame(byTrackID: track) else {
            asyncVideoCompositionRequest.finish(with: NSError(domain: "VideoFilterCompositor", code: 0, userInfo: nil))
            return
        }
        filter.setValue(CIImage(cvPixelBuffer: frame), forKey: kCIInputImageKey)
        if let outputImage = filter.outputImage, let outBuffer = renderContext?.newPixelBuffer() {
            context.render(outputImage, to: outBuffer)
            asyncVideoCompositionRequest.finish(withComposedVideoFrame: outBuffer)
        } else {
            asyncVideoCompositionRequest.finish(with: NSError(domain: "VideoFilterCompositor", code: 0, userInfo: nil))
        }
    }

}

Есливам нужно иметь разные фильтры в разное время, вы можете использовать пользовательские AVVideoCompositionInstructionProtocol, которые вы можете получить из AVAsynchronousVideoCompositionRequest

Далее, вам нужно использовать это с вашим AVMutableVideoComposition, поэтому:

let videoComposition = AVMutableVideoComposition()
videoComposition.customVideoCompositorClass = VideoFilterCompositor.self
//Add your animator tool as usual
let animator = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: v, in: p)
videoComposition.animationTool = animator
//Finish setting up the composition

При этом вы сможете экспортировать видео, используя обычный AVAssetExportSession, установив его videoComposition

...