【问题标题】:iOS 10.0 - 10.1: AVPlayerLayer doesn't show video after using AVVideoCompositionCoreAnimationTool, only audioiOS 10.0 - 10.1:AVPlayerLayer 使用 AVVideoCompositionCoreAnimationTool 后不显示视频,只显示音频
【发布时间】:2016-09-29 01:50:53
【问题描述】:

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

这是 iOS 10 上的一个新问题,自 iOS 10.2 起已修复。使用 AVAssetExportSession 和 AVVideoCompositionCoreAnimationTool 导出视频后,在导出过程中在视频顶部合成图层后,在 AVPlayerLayer 中播放的视频无法播放。这似乎不是由达到 AV 编码/解码管道限制引起的,因为它通常发生在单个导出之后,据我所知,这只会启动 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()
            }
        }
    }
}

【问题讨论】:

标签: ios video avfoundation ios10


【解决方案1】:

在这种情况下,我的答案是通过使用实现AVVideoCompositing 协议的自定义视频合成类和实现AVVideoCompositionInstruction 协议的自定义合成指令来解决AVVideoCompositionCoreAnimationTool 的问题。因为我需要在视频顶部覆盖一个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 会议并查看其示例代码,您可以实现一个。我不能发布我在这里写的那个,但是我正在使用 CoreImage 来处理AVAsynchronousVideoCompositionRequest 的繁重工作,确保使用 OpenGL CoreImage 上下文以获得最佳性能(如果你在 CPU 上执行它会非常慢)。如果您在导出期间遇到内存使用高峰,您可能还需要一个自动释放池。

如果您像我一样覆盖CALayer,请确保在将该层渲染到CGImage 时设置layer.isGeometryFlipped = true,然后再将其发送到CoreImage。并确保在合成器中逐帧缓存渲染的CGImage

【讨论】:

  • 感谢您的解决方法。如果有更明确的代码来说明这一点,那就太好了。
  • 您好,您可以显示更明确的代码来帮助社区吗?这个问题很烦人,我无法解决它
  • @Sam 我需要为我的项目构建一个自定义视频合成器来解决这个问题。本周准备好后,我将在 GitHub 上提供一个链接。我的解决方案只允许您在视频上放置水印。但如果不出意外,如果您需要更复杂的东西,它会给您一个起点。
  • @kleezy 非常感谢你的摇滚!!感谢您的帮助:)
  • 见下文@Sam。刚刚发布了一个链接。
【解决方案2】:

我们在 iOS 10 和 10.1 上遇到了同样的问题。不过从 iOS 10.2 beta 3 开始看起来已经修复

【讨论】:

  • 太棒了!您知道 Apple 是否在某处提到了该修复程序吗?还是您刚刚在使用测试版时观察到它?
  • 很好,但这意味着对于 iOS 10 和 10.1 上的用户,即使它已在 10.2 中修复,它也无法正常工作,对吗?
  • @kleezy 我在发行说明中没有看到任何内容。我们通过人为地延迟 exportAsynchronously 解决了 10.0 和 10.1 的问题。这降低了问题发生的可能性。我们还在播放器视图后面放置错误消息,这样如果问题确实发生,用户不会只看到黑屏。如果用户运行的是 10.2 或更高版本,则会删除延迟和错误消息。
【解决方案3】:

为了扩展 Sami Samhuri 的答案,这是我开发的一个小示例项目,它使用自定义 AVVideoCompositing 类和实现 AVVideoCompositionInstructionProtocol 的自定义指令

https://github.com/claygarrett/CustomVideoCompositor

该项目允许您在视频上放置水印,但这个想法可以扩展到做任何您需要的事情。这可以防止有问题的 AVPlayer 错误浮出水面。

另一个可能有帮助的单独线程上的有趣解决方案:AVPlayer playback fails while AVAssetExportSession is active as of iOS 10

【讨论】:

  • 感谢您将这些放在一起!这是基本的解决方案,但我真的建议使用 GPU 而不是 CoreGraphics 来提高性能。我会尝试提交一个拉取请求。
  • 我的解决方案完全不同,因为我使用的是图层而不是图像,类似于 CA 动画工具的工作方式。这是我的合成器和指导的要点。也许有人可以拼凑出一个完整的工作示例:gist.github.com/samsonjs/71e27c1f500725d3d0c48064af7c1fd3
  • 好的,我终于将我的解决方案打包到一个示例项目中:github.com/samsonjs/LayerVideoCompositor
  • 太棒了@SamiSamhuri!谢谢你这样做。我一定会看看的。肯定对 GPU 优化感兴趣。
【解决方案4】:

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

【讨论】:

    猜你喜欢
    • 2015-10-02
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多