SCNAudioPlayer在Xcode Instrument中对SCNNodes的持久性

10

我创建了一个SCNNode的子类。它由几个子节点组成。 我声明了一个方法,即soundCasual(),它向此类的实例添加了一个SCNAudioPlayer。当调用此方法时,一切都按预期工作,并且正在播放音频。每当点击该节点(手势)时,此方法将在该节点上调用。

代码:

class MyNode: SCNNode {
    let wrapperNode = SCNNode()
    let audioSource5 = SCNAudioSource(fileNamed: "audiofile.mp3")

    override init() {
        super.init()

        if let virtualScene = SCNScene(named: "MyNode.scn", inDirectory: "Assets.scnassets/Shapes/MyNode") {
            for child in virtualScene.rootNode.childNodes {
                wrapperNode.addChildNode(child)
            }
        }
    }

   func soundCasual() {
        DispatchQueue.global(qos: .userInteractive).async { [weak self] in
            if let audioSource = self?.audioSource5 {
                let audioPlayer = SCNAudioPlayer(source: audioSource)
                self?.wrapperNode.removeAllAudioPlayers()
                self?.wrapperNode.addAudioPlayer(audioPlayer)
            }
        }
    }
}

仪器问题(分配)

当我分析整个代码库(其中还有其他内容)时,我发现每当我点击该节点时,在Instruments中进行分析时,SCNAudioPlayer的分配计数会增加一次。但所有增长都是持久性的。根据SCNAudioPlayer的定义,我认为播放器在播放后被移除,因此增量应该在瞬态分配中,但它并不像这样工作。这就是为什么我在向节点添加SCNAudioPlayer之前尝试了removeAllAudioPlayers(),如soundCasual()的代码所示。但问题仍然存在。

在拍摄此快照时,我大约点击了该节点17次,并且对于SCNAudioPlayer的Persistent分配,它也显示为17。

注意: SCNAudioSource为10,这是正确的,因为我在应用程序中使用了10个音频源

enter image description here

我的应用程序中所有其他SCNNodes都出现了这种情况。请帮忙,因为我无法理解我到底错过了什么。

编辑

按建议,我将我的init()更改为

let path = Bundle.main.path(forResource: "Keemo", ofType: "scn", inDirectory: "Assets.scnassets/Shapes/Keemo")
if let path = path , let keemo = SCNReferenceNode(url: URL(fileURLWithPath: path)) {
    keemo.load()
}
func soundPlay() {
    DispatchQueue.global(qos: .userInteractive).async { [weak self] in
        if let audioSource = self?.audioSourcePlay {
            audioSource.volume = 0.1
            let audioPlayer = SCNAudioPlayer(source: audioSource)
            self?.removeAllAudioPlayers()
            self?.addAudioPlayer(audioPlayer)
        }
    }
}

尽管在Instruments中分配的资源表明audioPlayers是持久的,但是检查node.audioPlayers后显示在某一时刻只有一个audioPlayer节点被连接。

编辑

即使在使用XCode默认创建的Scenekit应用程序中附带的样板代码中,这个问题也会出现。因此,这个问题已经被提升为苹果公司的一个错误。 https://bugreport.apple.com/web/?problemID=43482539

解决方法

我使用AVAudioPlayer代替SCNAudioPlayer,虽然不完全相同,但至少这种方式不会导致内存崩溃。


请问您能否展示一下 removeAllAudioPlayers() 函数的代码? - ekscrypto
抱歉,这是一个简单的实例方法,请参阅:https://developer.apple.com/documentation/scenekit/scnnode/1523570-removeallaudioplayers - Manganese
否则,它只是“let myNode = MyNode()”; “myNode.soundCasual()”; “myNode.removeAllAudioPlayers()”。 - Manganese
@Manganese,.audioPlayers 的输出是什么? - Stephen Furlani
@StephenFurlani 空数组 [],因为这就是应该发生的事情。AudioNodes一旦播放,就会被技术上移除。但是如果audioNodes被添加到wrapperNode中,而我正在检查self,那么我的答案可能也存在问题。 - Manganese
2个回答

1

我对SceneKit不熟悉,但根据我的UIKit和SpriteKit经验,我怀疑你使用的wrapperNodevirtualScene正在干扰垃圾收集器。

我建议尝试删除wrapperNode并将所有内容添加到self中(因为self是一个SCNNode)。

你的场景中使用哪个节点?是self还是wrapperNode?在你的示例代码中,wrapperNode没有被添加到self中,因此它可能或可能不是场景的一部分。

另外,你应该使用SCNReferenceNode而不是你正在使用的虚拟场景。

!!! 这段代码未经过测试 !!!

class MyNode: SCNReferenceNode {
    let audioSource5 = SCNAudioSource(fileNamed: "audiofile.mp3")

    func soundCasual() {
        DispatchQueue.global(qos: .userInteractive).async { [weak self] in
            if let audioSource = self?.audioSource5 {
                let audioPlayer = SCNAudioPlayer(source: audioSource)
                self?.removeAllAudioPlayers()
                self?.addAudioPlayer(audioPlayer)
            }
        }
    }
}


// If you programmatically create this node, you'll have to call .load() on it
let referenceNode = SCNReferenceNode(URL: referenceURL)
referenceNode.load()

HtH!


对我来说听起来很有趣。让我按照建议进行所有更改,然后我会更新您。由于我正在通勤,可能需要几天时间。抱歉。 - Manganese
是的,包装器(wrapper)和自身(self)之间的区别似乎可能是一个问题。 - Manganese
我对你提到的关于使用 SCNReferenceNode 和通常的 SCNNode 的观点很感兴趣。前者在某些方面是否更有效或更好? - Manganese
1
@Manganese SCNReferenceNode 自动加载场景文件,并且是 Apple 推荐的从场景文件中加载节点的方式。我猜测 Apple 正在优化 SCNNode 的垃圾回收,因为您创建了这个单独的场景,它可能不会被包含在其中。 - Stephen Furlani
我按照你的建议更改了结构,并作为编辑添加了进去。但仍然没有进展。 - Manganese
1
嗨,Stephen,根据我与苹果的沟通,他们已经将此问题作为一个错误接受了。他们提到会建议解决方法,我会及时更新。 - Manganese

1
如果您还没有找到答案,那么在 SCNAudioPlayer 播放完成后,您需要从节点中删除它:
if let audioSource = self?.audioSourcePlay {
    audioSource.volume = 0.1
    let audioPlayer = SCNAudioPlayer(source: audioSource)
    self?.removeAllAudioPlayers()
    self?.addAudioPlayer(audioPlayer)
    self?.audioplayer.didFinishPlayback = {
        self?.removeAudioPlayer(audioPlayer)
    }
}

嘿,我想我已经尝试过这个了。让我再检查一下,然后我会回来的。很抱歉我错过了你的帖子。谢谢。 - Manganese

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