iOS 10.0 - 10.1: AVPlayerLayer не показывает видео после использования AVVideoCompositionCoreAnimationTool, только аудио
Вот полный проект, если вы хотите запустить его самостоятельно: https://www.dropbox.com/s/5p384mogjzflvqk/AVPlayerLayerSoundOnlyBug_iOS10.zip?dl=0
Это новая проблема в iOS 10, и она была исправлена в iOS 10.2. После экспорта видео с использованием AVAssetExportSession и AVVideoCompositionCoreAnimationTool для компоновки слоя поверх видео во время экспорта видео, воспроизводимые в AVPlayerLayer, не воспроизводятся. Похоже, что это не вызвано достижением ограничения конвейера AV-кодирования / декодирования, потому что это часто происходит после одного экспорта, который, насколько я знаю, только раскручивает 2 конвейера: один для AVAssetExportSession и другой для AVPlayer. Я также правильно устанавливаю рамку слоя, как вы можете увидеть, запустив приведенный ниже код, который дает слою синий фон, который вы можете видеть.
После экспорта ожидание некоторого времени перед воспроизведением видео, кажется, делает его более надежным, но это не совсем приемлемый обходной путь, чтобы сказать вашим пользователям.
Любые идеи о том, что вызывает это или как я могу исправить или обойти это? Я что-то напутал или пропустил важный шаг или деталь? Любая помощь или указатели на документацию очень ценятся.
import UIKit
import AVFoundation
/* After exporting an AVAsset using AVAssetExportSession with AVVideoCompositionCoreAnimationTool, we
* will attempt to play a video using an AVPlayerLayer with a blue background.
*
* If you see the blue background and hear audio you're experiencing the missing-video bug. Otherwise
* try hitting the button again.
*/
class ViewController: UIViewController {
private var playerLayer: AVPlayerLayer?
private let button = UIButton()
private let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
button.setTitle("Cause Trouble", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.addTarget(self, action: #selector(ViewController.buttonTapped), for: .touchUpInside)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -16),
])
indicator.hidesWhenStopped = true
view.insertSubview(indicator, belowSubview: button)
indicator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
indicator.centerXAnchor.constraint(equalTo: button.centerXAnchor),
indicator.centerYAnchor.constraint(equalTo: button.centerYAnchor),
])
}
func buttonTapped() {
button.isHidden = true
indicator.startAnimating()
playerLayer?.removeFromSuperlayer()
let sourcePath = Bundle.main.path(forResource: "video.mov", ofType: nil)!
let sourceURL = URL(fileURLWithPath: sourcePath)
let sourceAsset = AVURLAsset(url: sourceURL)
//////////////////////////////////////////////////////////////////////
// STEP 1: Export a video using AVVideoCompositionCoreAnimationTool //
//////////////////////////////////////////////////////////////////////
let exportSession = { () -> AVAssetExportSession in
let sourceTrack = sourceAsset.tracks(withMediaType: AVMediaTypeVideo).first!
let parentLayer = CALayer()
parentLayer.frame = CGRect(origin: .zero, size: CGSize(width: 1280, height: 720))
let videoLayer = CALayer()
videoLayer.frame = parentLayer.bounds
parentLayer.addSublayer(videoLayer)
let composition = AVMutableVideoComposition(propertiesOf: sourceAsset)
composition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: parentLayer)
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: sourceTrack)
layerInstruction.setTransform(sourceTrack.preferredTransform, at: kCMTimeZero)
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRange(start: kCMTimeZero, duration: sourceAsset.duration)
instruction.layerInstructions = [layerInstruction]
composition.instructions = [instruction]
let e = AVAssetExportSession(asset: sourceAsset, presetName: AVAssetExportPreset1280x720)!
e.videoComposition = composition
e.outputFileType = AVFileTypeQuickTimeMovie
e.timeRange = CMTimeRange(start: kCMTimeZero, duration: sourceAsset.duration)
let outputURL = URL(fileURLWithPath: NSTemporaryDirectory().appending("/out2.mov"))
_ = try? FileManager.default.removeItem(at: outputURL)
e.outputURL = outputURL
return e
}()
print("Exporting asset...")
exportSession.exportAsynchronously {
assert(exportSession.status == .completed)
//////////////////////////////////////////////
// STEP 2: Play a video in an AVPlayerLayer //
//////////////////////////////////////////////
DispatchQueue.main.async {
// Reuse player layer, shouldn't be hitting the AV pipeline limit
let playerItem = AVPlayerItem(asset: sourceAsset)
let layer = self.playerLayer ?? AVPlayerLayer()
if layer.player == nil {
layer.player = AVPlayer(playerItem: playerItem)
}
else {
layer.player?.replaceCurrentItem(with: playerItem)
}
layer.backgroundColor = UIColor.blue.cgColor
if UIDeviceOrientationIsPortrait(UIDevice.current.orientation) {
layer.frame = self.view.bounds
layer.bounds.size.height = layer.bounds.width * 9.0 / 16.0
}
else {
layer.frame = self.view.bounds.insetBy(dx: 0, dy: 60)
layer.bounds.size.width = layer.bounds.height * 16.0 / 9.0
}
self.view.layer.insertSublayer(layer, at: 0)
self.playerLayer = layer
layer.player?.play()
print("Playing a video in an AVPlayerLayer...")
self.button.isHidden = false
self.indicator.stopAnimating()
}
}
}
}
4 ответа
Ответ для меня в этом случае заключается в том, чтобы обойти проблему с AVVideoCompositionCoreAnimationTool
с помощью пользовательского класса композитинга видео, реализующего AVVideoCompositing
протокол и пользовательские инструкции композиции, реализующие AVVideoCompositionInstruction
протокол. Потому что мне нужно наложить CALayer
поверх видео я включаю этот слой в экземпляр инструкции по композиции.
Вам нужно установить пользовательский композитор для вашей видео композиции следующим образом:
composition.customVideoCompositorClass = CustomVideoCompositor.self
и затем установите свои собственные инструкции на нем:
let instruction = CustomVideoCompositionInstruction(...) // whatever parameters you need and are required by the instruction protocol
composition.instructions = [instruction]
РЕДАКТИРОВАТЬ: Вот рабочий пример того, как использовать пользовательский композитор для наложения слоя на видео с помощью графического процессора: https://github.com/samsonjs/LayerVideoCompositor... оригинальный ответ продолжается ниже
Что касается самого композитора, вы можете реализовать его, если посмотрите соответствующие сессии WWDC и посмотрите их пример кода. Я не могу опубликовать тот, который я написал здесь, но я использую CoreImage, чтобы сделать тяжелую работу при обработке AVAsynchronousVideoCompositionRequest
убедитесь, что вы используете контекст OpenGL CoreImage для лучшей производительности (если вы сделаете это на процессоре, это будет ужасно медленным). Вам также может понадобиться пул с автоматическим выпуском, если во время экспорта вы получаете всплеск использования памяти.
Если вы накладываете CALayer
как я, то обязательно установите layer.isGeometryFlipped = true
когда вы визуализируете этот слой в CGImage
перед отправкой в CoreImage. И убедитесь, что вы кешируете CGImage
от кадра к кадру в вашем композиторе.
У нас была такая же проблема на iOS 10 и 10.1. Выглядит исправлено с iOS 10.2 бета 3, хотя
Чтобы расширить ответ Сами Самхури, вот небольшой пример проекта, который я разработал, который использует пользовательский AVVideoCompositing
класс с пользовательскими инструкциями, которые реализуют AVVideoCompositionInstructionProtocol
https://github.com/claygarrett/CustomVideoCompositor
Проект позволяет размещать водяной знак поверх видео, но идея может распространяться на все, что вам нужно. Это предотвращает появление ошибки, связанной с AVPlayer.
Еще одно интересное решение в отдельном потоке, которое может помочь: воспроизведение AVPlayer завершается неудачно, когда AVAssetExportSession активен с iOS 10
Я встречал эту проблему на iOS 10.1, и проблема была исправлена на iOS 10.2. Один из способов решить эту проблему на iOS 10.1 - отложить несколько секунд, чтобы добавить playerLayer в слой контейнера.