2016-09-29 68 views
8

這裏,如果你願意跑這一個完整的項目自己:https://www.dropbox.com/s/5p384mogjzflvqk/AVPlayerLayerSoundOnlyBug_iOS10.zip?dl=0iOS的10.0 - 10.1:AVPlayerLayer使用AVVideoCompositionCoreAnimationTool後不顯示視頻,只有音頻

這是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() 
      } 
     } 
    } 
} 
+0

AVAssetExportSession似乎是在iOS10上的越野車http://stackoverflow.com/q/39560386/22147 http://stackoverflow.com/a/39746140/22147 –

+0

@RhythmicFistman謝謝!我還沒有遇到過。看起來我可以使用自定義視頻合成器而不是AVVideoCompositionCoreAnimationTool解決此問題。 –

回答

5

在這種情況下,答案對我來說是通過使用自定義視頻合成類實現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會話並查看他們的示例代碼,則可以實現它。我不能發佈我在這裏寫的,但我使用CoreImage來完成AVAsynchronousVideoCompositionRequest的處理,確保使用OpenGL CoreImage上下文以獲得最佳性能(如果在CPU上執行它,它會非常慢)。如果在導出過程中出現內存使用率峯值,您還可能需要自動釋放池。

如果要覆蓋一個CALayer像我這樣的,那麼要確保設置layer.isGeometryFlipped = true當你發送它關閉之前CoreImage渲染層出一個CGImage。並確保在合成器中逐幀緩存呈現的CGImage

+0

感謝您的解決方法。如果有更明確的代碼來清楚說明,那將是非常好的。 – Sam

+0

你好你能否展示一個更明確的代碼來幫助社區?這個問題很煩人,我也無法修復它 – Sam

+1

@Sam我需要爲我的項目構建自定義視頻合成器來解決此問題。準備好之後,本週我會在GitHub上發佈一個鏈接。我的解決方案將允許您在視頻上放置水印。但如果沒有其他的東西,它會給你一個地方開始,如果你需要更復雜的東西。 – kleezy

2

我們在iOS 10和10.1上遇到了同樣的問題。看起來像iOS 10一樣固定。2的Beta 3雖然

+1

優秀!你知道這個修復是否被Apple的某處所提及?或者您是否在使用測試版時觀察了它? – kleezy

+0

偉大的,但這意味着對於iOS 10和10.1的用戶,它將無法正常工作,即使它已經在10.2中修復是正確的嗎? – Sam

+0

這是正確的@Sam – kleezy

1

要在薩米Samhuri的答案擴大,這裏是一個小樣本項目,我工作了使用自定義AVVideoCompositing類實現自定義指令AVVideoCompositionInstructionProtocol

https://github.com/claygarrett/CustomVideoCompositor

該項目允許您將視頻上的水印,但這個想法可以擴展到做你需要的任何東西。這可防止有問題的AVPlayer錯誤出現。

一個單獨的線程的另一個有趣的解決方案,可以幫助:AVPlayer playback fails while AVAssetExportSession is active as of iOS 10

+0

感謝您將它放在一起!這是基本的解決方案,但我真的推薦使用GPU而不是CoreGraphics來提高性能。我會嘗試提交拉請求。 –

+0

我的解決方案非常不同,因爲我使用的是圖層而不是圖片,與CA動畫工具的工作方式類似。這是我的排字和指導的要點。也許有人可以拼湊一個完整的工作示例:https://gist.github.com/samsonjs/71e27c1f500725d3d0c48064af7c1fd3 –

+1

好吧,我終於打包了我的解決方案到一個示例項目中:https://github.com/samsonjs/LayerVideoCompositor –

0

我遇到過這個問題在iOS 10.1和問題被固定在iOS 10.2。 我發現在iOS 10.1上解決這個問題的一種方式是延遲幾秒鐘將playerLayer添加到容器層。

相關問題