iOS 10.0 - 10.1:AVPlayerLayer 在使用 AVVideoCompositionCoreAnimationTool 后不显示视频,只显示音频
iOS 10.0 - 10.1: AVPlayerLayer doesn't show video after using AVVideoCompositionCoreAnimationTool, only audio
如果您自己喜欢 运行,这里有一个完整的项目:https://www.dropbox.com/s/5p384mogjzflvqk/AVPlayerLayerSoundOnlyBug_iOS10.zip?dl=0
这是 iOS 10 上的一个新问题,已在 iOS 10.2 中修复。使用 AVAssetExportSession 和 AVVideoCompositionCoreAnimationTool 导出视频后,在导出过程中在视频顶部合成图层后,在 AVPlayerLayer 中播放的视频无法播放。这似乎不是由达到 AV encode/decode 管道限制引起的,因为它经常发生在单次导出之后,据我所知,这只会启动 2 个管道:1 个用于 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()
}
}
}
}
在这种情况下,我的答案是通过使用实现 AVVideoCompositing
协议的自定义视频合成 class 和实现 AVVideoCompositing
的自定义合成指令来解决 AVVideoCompositionCoreAnimationTool
的问题AVVideoCompositionInstruction
协议。因为我需要在视频之上叠加一个 CALayer
,所以我将该层包含在合成指令实例中。
您需要像这样在您的视频合成中设置自定义合成器:
composition.customVideoCompositorClass = CustomVideoCompositor.self
然后在上面设置您的自定义说明:
let instruction = CustomVideoCompositionInstruction(...) // whatever parameters you need and are required by the instruction protocol
composition.instructions = [instruction]
编辑:这是一个工作示例,说明如何使用自定义合成器使用 GPU 在视频上叠加图层:https://github.com/samsonjs/LayerVideoCompositor ... 原始答案在下面继续
至于合成器本身,如果您观看相关的 WWDC 会议并查看它们的示例代码,您可以实现一个。我不能 post 我在这里写的那个,但我正在使用 CoreImage 来处理 AVAsynchronousVideoCompositionRequest
的繁重工作,确保使用 OpenGL CoreImage 上下文以获得最佳性能(如果你在CPU 它会非常慢)。如果在导出期间出现内存使用高峰,您可能还需要一个 auto-release 池。
如果您像我一样覆盖 CALayer
,那么请确保在将该层渲染为 CGImage
时设置 layer.isGeometryFlipped = true
,然后再将其发送到 CoreImage。并确保在合成器中逐帧缓存渲染的 CGImage
。
我们在 iOS 10 和 10.1 上遇到了同样的问题。尽管 iOS 10.2 beta 3 看起来已修复
为了扩展 Sami Samhuri 的回答,这是我开发的一个小示例项目,它使用自定义 AVVideoCompositing
class 和实现 AVVideoCompositionInstructionProtocol
的自定义指令
https://github.com/claygarrett/CustomVideoCompositor
该项目允许您在视频上放置水印,但这个想法可以扩展到做任何您需要的事情。这可以防止出现问题的 AVPlayer 错误。
另一个可能有帮助的单独线程上的有趣解决方案:AVPlayer playback fails while AVAssetExportSession is active as of iOS 10
我在 iOS 10.1 上遇到过这个问题,该问题已在 iOS 10.2 上得到解决。
我发现在 iOS 10.1 上解决此问题的一种方法是延迟几秒钟以将 playerLayer 添加到您的容器层。
如果您自己喜欢 运行,这里有一个完整的项目:https://www.dropbox.com/s/5p384mogjzflvqk/AVPlayerLayerSoundOnlyBug_iOS10.zip?dl=0
这是 iOS 10 上的一个新问题,已在 iOS 10.2 中修复。使用 AVAssetExportSession 和 AVVideoCompositionCoreAnimationTool 导出视频后,在导出过程中在视频顶部合成图层后,在 AVPlayerLayer 中播放的视频无法播放。这似乎不是由达到 AV encode/decode 管道限制引起的,因为它经常发生在单次导出之后,据我所知,这只会启动 2 个管道:1 个用于 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()
}
}
}
}
在这种情况下,我的答案是通过使用实现 AVVideoCompositing
协议的自定义视频合成 class 和实现 AVVideoCompositing
的自定义合成指令来解决 AVVideoCompositionCoreAnimationTool
的问题AVVideoCompositionInstruction
协议。因为我需要在视频之上叠加一个 CALayer
,所以我将该层包含在合成指令实例中。
您需要像这样在您的视频合成中设置自定义合成器:
composition.customVideoCompositorClass = CustomVideoCompositor.self
然后在上面设置您的自定义说明:
let instruction = CustomVideoCompositionInstruction(...) // whatever parameters you need and are required by the instruction protocol
composition.instructions = [instruction]
编辑:这是一个工作示例,说明如何使用自定义合成器使用 GPU 在视频上叠加图层:https://github.com/samsonjs/LayerVideoCompositor ... 原始答案在下面继续
至于合成器本身,如果您观看相关的 WWDC 会议并查看它们的示例代码,您可以实现一个。我不能 post 我在这里写的那个,但我正在使用 CoreImage 来处理 AVAsynchronousVideoCompositionRequest
的繁重工作,确保使用 OpenGL CoreImage 上下文以获得最佳性能(如果你在CPU 它会非常慢)。如果在导出期间出现内存使用高峰,您可能还需要一个 auto-release 池。
如果您像我一样覆盖 CALayer
,那么请确保在将该层渲染为 CGImage
时设置 layer.isGeometryFlipped = true
,然后再将其发送到 CoreImage。并确保在合成器中逐帧缓存渲染的 CGImage
。
我们在 iOS 10 和 10.1 上遇到了同样的问题。尽管 iOS 10.2 beta 3 看起来已修复
为了扩展 Sami Samhuri 的回答,这是我开发的一个小示例项目,它使用自定义 AVVideoCompositing
class 和实现 AVVideoCompositionInstructionProtocol
https://github.com/claygarrett/CustomVideoCompositor
该项目允许您在视频上放置水印,但这个想法可以扩展到做任何您需要的事情。这可以防止出现问题的 AVPlayer 错误。
另一个可能有帮助的单独线程上的有趣解决方案:AVPlayer playback fails while AVAssetExportSession is active as of iOS 10
我在 iOS 10.1 上遇到过这个问题,该问题已在 iOS 10.2 上得到解决。 我发现在 iOS 10.1 上解决此问题的一种方法是延迟几秒钟以将 playerLayer 添加到您的容器层。