确定在SceneKit中使用SKVideoNode的视频尺寸/纵横比。

5

如何从AVPlayer获取视频大小以设置节点的几何大小?

例如,我有一个具有宽度和高度的SCNPlane。

let planeGeo = SCNPlane(width: 5, height: 5)

所以现在我实例化我的视频播放器。
let videoURL = NSURL(string: someURL)
let player = AVPlayer(URL: videoURL!)

以及我的SKVideoNode

let spriteKitScene = SKScene(size: CGSize(width: 1920, height: 1080))
spriteKitScene.scaleMode = .AspectFit

videoSpriteKitNode = SKVideoNode(AVPlayer: player)
videoSpriteKitNode.anchorPoint = CGPointMake(0,0)
videoSpriteKitNode.size.width = spriteKitScene.size.width
videoSpriteKitNode.size.height = spriteKitScene.size.height

spriteKitScene.addChild(videoSpriteKitNode)

planeGeo!.firstMaterial.diffuse.contents = spriteKitScene
videoSpriteKitNode.play()

现在我希望视频大小能够调整到正确的纵横比。我已经尝试过使用AVPlayerLayer进行调整,但是始终返回0。

let avLayer = AVPlayerLayer(player: player)
print(avLayer.videoRect.width) //0
print(avLayer.videoRect.height) //0

我在这里也尝试了,但效果不佳。
let avLayer = AVPlayerLayer(player: player)
let layer = avLayer.sublayers![0]
let transformedBounds = CGRectApplyAffineTransform(layer.bounds, CATransform3DGetAffineTransform(layer.sublayerTransform))
print(transformedBounds.width) //0
print(transformedBounds.height) //0

请注意,这个问题涉及到SpriteKit。但是十年后,你不必再使用SpriteKit来解决这个问题了!现在非常容易。https://dev59.com/pVgQ5IYBdhLWcg3wnVV3#74667281 - Fattie
3个回答

2

好的,我明白了,KVO是正确的选择。在viewDidLoad中添加:

player.currentItem?.addObserver(self, forKeyPath: "presentationSize", options: .New, context: nil)

在析构函数中:

player.currentItem?.removeObserver(self, forKeyPath: "presentationSize")

然后添加:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    
    if keyPath == "presentationSize" {
        if let item = object as? AVPlayerItem {
            let size = item.presentationSize
            let width = size.width
            let height = size.height

            //Set size of geometry here
        }
    }
}

2022语法的典型代码:

var py: AVPlayer?
private var pyContext = 0
...

guard let url = URL(string: "https:// .. /test.m4v") else { return }
py = AVPlayer(url: url)
someNode.geometry?.firstMaterial?.diffuse.contents = py

py?.currentItem?.addObserver(self,
   forKeyPath: "presentationSize",
   context: &pyContext)    
...

override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?) {

    if context == &py1Context && keyPath == "presentationSize" {
        print("Found it ...")

        guard let item = object as AVPlayerItem else { return }

        let ps = item.presentationSize
        let aspect: Float = Float(ps.width) / Float(ps.height)
        someNode.geometry?.firstMaterial?
           .diffuse.contentsTransform =
           SCNMatrix4MakeScale( .. , .. , 1)
    }
}

附录. 计算正确的缩放比例很棘手

不幸的是,任何时候当你处理视频时,如果流媒体内容的确切大小并不总是相同的,那么形状调整视频就会非常麻烦。当然你需要考虑许多因素,例如是否有信封式黑边等等。 在某些简单的情况下,计算如下:

// 1. You nave finally received the info on the video from the HLS stream:

let ps = item.presentationSize
let spect: Float = Float(ps.width) / Float(ps.height)

// 2. Over in your 3D code, you need to know the current facts on the mesh:

let planeW = ... width of your mesh
let planeH = ... height of your mesh
let planeAspect = planeW / planeH

今日免费次数已满, 请开通会员/明日再来
var simplePlane = SCNPlane()
simplePlane.width = 2.783
simplePlane.height = 1.8723

无论如何,您都需要知道网格的宽高比。然后,

// 3. In many (not all) cases, the solution is:

let final = vidAspect / planeAspect
print("we'll try this: \(final)")
yourNode.geometry?
  .firstMaterial?.diffuse
  .contentsTransform = SCNMatrix4MakeScale(1.0, final, 1.0)

1
令人难以置信的答案。请注意,现在你必须添加愚蠢的“上下文”参数。 - Fattie

2

正如您所注意到的,SKVideoNodesize 属性是在视频实际呈现后异步加载的,这可能需要一秒钟的时间。如果有人需要同步解决方案,可以尝试这个方法。实例化一个 AVURLAsset,在那里您将可以访问一个视频轨道 (AVAssetTrack),它具有 naturalSize 属性:

let videoURL = NSURL(string: someURL)!
let asset = AVURLAsset(url: videoURL)

let videoSize: CGSize
if let size = asset.tracks(withMediaType: .video).first?.naturalSize {
    videoSize = size
} else {
    // Handle error (shouldn't happen normally if the asset is
    // an actual video.)
    videoSize = .zero
}

let player = AVPlayer(item: AVPlayerItem(asset: asset))
// Alternatively:
// let player = AVPlayer(URL: videoURL!)

videoSpriteKitNode = SKVideoNode(AVPlayer: player)
// continue node setup...

但是这只适用于本地文件,对吧?@kelin - Fattie
@Fattie,可以处理本地文件,远程文件尚未测试。 - kelin
谢谢。我会在有时间的时候进行测试!你知道,由于直到几帧流入后才能获取信息,所以很难看出它如何与远程文件一起工作。但我会进行测试! - Fattie
@Fattie,也许你需要先获取一些元数据。此外,这是一个同步解决方案,这意味着你不需要在这里等待,对于远程视频,Nico S.提供的异步解决方案更适合。 - kelin
嗨@kelin,你说得没错,但是“你将首先获得一些元数据”,这可能需要相当长的时间(通常需要一两秒钟)...你很可能是正确的,它比等待presentationSize更快..我会检查一下,谢谢! - Fattie

1

如果您在使用SpriteKit编码时先前设置了videoNode.sizeskScene.size之间的相等性,您可以使用SceneKit获取以points表示的视频节点大小。有关详细信息,请阅读此文章

import SceneKit
import SpriteKit

let model = scene.rootNode.childNode(withName: "someNode", recursively: true)
    
let skScene = model?.geometry?.firstMaterial?.diffuse.contents as? SKScene
    
let skVideoNode = skScene?.children[0] as? SKVideoNode
    
guard let size = skVideoNode?.scene?.size,
      let position = skVideoNode?.position,
      let frame = skVideoNode?.frame
else { return }
    
print(String(format: "%.1f, %.1f", size.width, size.height))
print(position)
print(frame)
print(skVideoNode?.speed as Any)
print(skVideoNode?.description as Any)

1
虽然这是很棒的信息,但是(1)现在我猜你一般不需要以任何方式使用Sprite Kit来将视频放在节点上,(2)我有一个担忧,当流式传输视频时,无论是3D、Sprite Kit、UIView还是其他任何东西,都绝对不可能知道传入视频的尺寸,直到足够的HLS流到达。唯一的方法是异步的(事实上,我所知道的使用AVPlayer进行observeValueForKeyPath on presentationSize是唯一的机制),因此在调用此之前需要这样做?... IDK - Fattie
总的来说,我同意你的观点,但这个问题是关于SKVideoNode的。 - Andy Jazz
1
啊,糟糕!非常好的观点,谢谢。 - Fattie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接