Применение фильтра CIFilter к видеофайлу и его сохранение

Есть ли какой-нибудь быстрый и максимально легкий способ применить CIFilter к видео? Перед тем как это упомянуть, я просмотрел GPUImage - он выглядит очень мощным < s> magic, но это действительно излишне для того, что я пытаюсь сделать.

По сути, я бы хотел

  1. Возьмите видеофайл, скажем, хранящийся в /tmp/myVideoFile.mp4
  2. Примените CIFilter к этому видеофайлу
  3. Сохраните видеофайл в другом (или том же) месте, например /tmp/anotherVideoFile.mp4

Мне удалось применить CIFilter к видео, которое воспроизводится очень легко и быстро, используя AVPlayerItemVideoOutput

let player = AVPlayer(playerItem: AVPlayerItem(asset: video))
let output = AVPlayerItemVideoOutput(pixelBufferAttributes: nil)
player.currentItem?.addOutput(self.output)
player.play()

let displayLink = CADisplayLink(target: self, selector: #selector(self.displayLinkDidRefresh(_:)))
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)

func displayLinkDidRefresh(link: CADisplayLink){
    let itemTime = output.itemTimeForHostTime(CACurrentMediaTime())
    if output.hasNewPixelBufferForItemTime(itemTime){
        if let pixelBuffer = output.copyPixelBufferForItemTime(itemTime, itemTimeForDisplay: nil){
            let image = CIImage(CVPixelBuffer: pixelBuffer)
            // apply filters to image
            // display image
        }
    }
}

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

В случае чрезмерно упрощенного кода я ищу что-то вроде этого:

var newVideo = AVMutableAsset() // We'll just pretend like this is a thing

var originalVideo = AVAsset(url: NSURL(urlString: "/example/location.mp4"))
originalVideo.getAllFrames(){(pixelBuffer: CVPixelBuffer) -> Void in
    let image = CIImage(CVPixelBuffer: pixelBuffer)
        .imageByApplyingFilter("Filter", withInputParameters: [:])

    newVideo.addFrame(image)
}

newVideo.exportTo(url: NSURL(urlString: "/this/isAnother/example.mp4"))

Есть ли какой-нибудь быстрый (опять же, без использования GPUImage и в идеале работающий в iOS 7) способ применить фильтр к видеофайлу, а затем сохранить его? Например, для этого нужно взять сохраненное видео, загрузить его в AVAsset, применить CIFilter, а затем сохранить новое видео в другом месте.


person Jojodmo    schedule 24.08.2016    source источник
comment
Мне удалось заставить это работать, и код теперь доступен на GitHub   -  person Jojodmo    schedule 07.09.2016


Ответы (1)


В iOS 9 / OS X 10.11 / tvOS есть удобный метод применения CIFilters к видео. Он работает с AVVideoComposition, поэтому вы можете использовать его как для воспроизведения, так и для импорта / экспорта из файла в файл. См. AVVideoComposition.init(asset:applyingCIFiltersWithHandler:) для документации по методам.

Вот пример также в Руководстве по программированию Core Image от Apple:

let filter = CIFilter(name: "CIGaussianBlur")!
let composition = AVVideoComposition(asset: asset, applyingCIFiltersWithHandler: { request in

    // Clamp to avoid blurring transparent pixels at the image edges
    let source = request.sourceImage.clampingToExtent()
    filter.setValue(source, forKey: kCIInputImageKey)

    // Vary filter parameters based on video timing
    let seconds = CMTimeGetSeconds(request.compositionTime)
    filter.setValue(seconds * 10.0, forKey: kCIInputRadiusKey)

    // Crop the blurred output to the bounds of the original image
    let output = filter.outputImage!.cropping(to: request.sourceImage.extent)

    // Provide the filter output to the composition
    request.finish(with: output, context: nil)
})

Эта часть устанавливает композицию. После того, как вы это сделаете, вы можете воспроизвести его, назначив AVPlayer, или записать его в файл с помощью AVAssetExportSession. Поскольку вы ищете последнее, вот пример этого:

let export = AVAssetExportSession(asset: asset, presetName: AVAssetExportPreset1920x1200)
export.outputFileType = AVFileTypeQuickTimeMovie
export.outputURL = outURL
export.videoComposition = composition

export.exportAsynchronouslyWithCompletionHandler(/*...*/)

Подробнее об этом в сеансе WWDC15 на Core Image, начиная с 20 минут в.


Если вам нужно решение, работающее на более ранних версиях ОС, это немного сложнее.

В сторону: подумайте о том, как далеко вам действительно нужно поддерживать. По состоянию на 15 августа 2016 г. 87% устройств используют iOS 9.0 или новее, и 97% - на iOS 8.0 или новее. Прилагаемые большие усилия для поддержки небольшой части вашей потенциальной клиентской базы - а она станет еще меньше к тому времени, когда вы завершите свой проект и будете готовы к развертыванию, - возможно, не окупятся.

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

  1. Используйте AVAssetReader и AVAssetWriter для чтения и записи буферов пикселей. Примеры того, как это сделать (часть чтения и записи; вам все еще нужно выполнить фильтрацию между ними) в Экспорт Руководства по программированию Apple AVFoundation.

  2. Используйте AVVideoComposition с настраиваемым классом композитора. Вашему пользовательскому композитору предоставляются объекты AVAsynchronousVideoCompositionRequest, которые обеспечивают доступ к пиксельным буферам и возможность предоставить буферы обработанных пикселей. У Apple есть проект образца кода под названием AVCustomEdit, который показывает, как это сделать (опять же, только часть буферов получения и возврата; вы бы хотели обрабатывать с помощью Core Image вместо использования их средств визуализации GL).

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

person rickster    schedule 24.08.2016
comment
Спасибо за Ваш ответ! Я много сделал с AVVideoComposition, но, похоже, не могу получить _ 2_, которая запускает работу CVPixelBuffer. Он вызывается, но не имеет идентификаторов дорожек (и, следовательно, пиксельных буферов), когда я устанавливаю passthroughTrackID в своем классе AVVideoCompositionInstruction. Однако он не вызывается, если я не устанавливаю идентификатор трека. - person Jojodmo; 25.08.2016
comment
Вы хоть представляете, что может быть причиной этого? Если это сложнее, чем просто это (что, вероятно, так), я опубликую еще один вопрос об этом - я просто подумал, что сначала спрошу вас здесь, если это что-то действительно очевидное - person Jojodmo; 25.08.2016
comment
Трудно сказать, не увидев некоторого (вероятно, нетривиального) кода, поэтому, вероятно, лучше, как его собственный вопрос. - person rickster; 25.08.2016
comment
Кто-нибудь еще получает от этого нечеткие результаты? - person Marcos Griselli; 14.12.2016
comment
Я в точности скопировал, вставил этот код, но видео получилось полностью черным и без звука. Есть предположения? @rickster - person Faruk; 19.07.2017
comment
Красиво составленное резюме. - person matt; 05.08.2018
comment
Никогда не пробовал, но выглядит круто. Попробую, если это быстрее. - person Deepak Sharma; 24.09.2018
comment
привет @rickster, дайте мне любое предложение относительно моей проблемы stackoverflow.com/questions/54652637/ - person Deep; 06.08.2019