iOS 10.0 – 10.1:使用AVVideoCompositionCoreAnimationTool后,AVPlayerLayer不显示video,只有audio

这里是一个完整的项目,如果你关心自己运行这个: https : //www.dropbox.com/s/5p384mogjzflvqk/AVPlayerLayerSoundOnlyBug_iOS10.zip?dl = 0

这是iOS 10上的一个新问题,从iOS 10.2开始已经修复。 在使用AVAssetExportSession和AVVideoCompositionCoreAnimationTool导出video以在导出期间合成video顶层的video之后,在AVPlayerLayer中播放的video将无法播放。 这似乎不是由于AVaudio编码/解码stream水线限制造成的,因为它经常发生在一次导出之后,据我所知只有2个stream水线:1个用于AVAssetExportSession,另一个用于AVPlayer。 我也正确地设置图层的框架,正如你可以看到通过运行下面的代码给图层一个蓝色的背景,你可以清楚地看到。

导出之后,在播放video之前等待一段时间似乎使它更可靠,但这不是一个可以接受的解决方法告诉你的用户。

任何想法是什么导致这个或如何我可以解决或解决它? 我是否搞砸了一些重要的步骤或细节? 任何帮助或指向文档的指针非常感谢。

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() } } } } 

在这种情况下,我的答案是通过使用实现AVVideoCompositing协议的自定义video合成类和实现AVVideoCompositing协议的自定义组合指令来解决AVVideoCompositionInstruction 。 因为我需要在video的顶部覆盖一个CALayer ,所以在构图指令实例中包含该图层。

您需要在您的video合成上设置自定义合成器,如下所示:

 composition.customVideoCompositorClass = CustomVideoCompositor.self 

然后在其上设置您的自定义说明:

 let instruction = CustomVideoCompositionInstruction(...) // whatever parameters you need and are required by the instruction protocol composition.instructions = [instruction] 

编辑:这是一个工作的例子,如何使用自定义合成器覆盖一层使用GPU的video: https : //github.com/samsonjs/LayerVideoCompositor …原来的答案继续下面

至于合成器本身,如果你观看相关的WWDC会议并查看他们的示例代码,你可以实现一个。 我不能发布我在这里写的,但我使用CoreImage在处理AVAsynchronousVideoCompositionRequest中做了繁重的AVAsynchronousVideoCompositionRequest ,确保使用OpenGL CoreImage上下文以获得最佳性能(如果在CPU上执行,它会非常慢)。 如果在导出过程中出现内存使用率峰值,则可能还需要一个自动释放池。

如果你覆盖像我这样的CALayer那么当你将这个图层渲染到CGImage之前,确保将它设置为layer.isGeometryFlipped = true ,然后将其发送到CoreImage。 并确保您在CGImage中逐帧caching呈现的CGImage

我们在iOS 10和10.1上遇到同样的问题。 看起来像iOS 10.2 beta 3虽然固定

为了扩大萨米Samhuri的答案,这里是一个小样本项目,我使用自定义AVVideoCompositing类与定制指令,实现AVVideoCompositionInstructionProtocol

https://github.com/claygarrett/CustomVideoCompositor

该项目允许你在video上放置水印,但是这个想法可以扩展到做你需要的任何东西。 这可防止出现问题的AVPlayer错误。

另一个有趣的解决scheme在单独的线程可能会有所帮助: AVPlayer播放失败,而AVAssetExportSession是从iOS 10开始

我在iOS 10.1上遇到了这个问题,并且在iOS 10.2上修复了这个问题。 我发现在iOS 10.1上解决这个问题的一个方法是延迟几秒钟将playerLayer添加到容器层。