3D позиционное аудио - перемещайте SCNAudioPlayer вдоль осей Y и Z
Используя SceneKit, я могу перемещать audioNode слева направо по оси x, но у меня возникают проблемы при перемещении по оси y и z. На мне наушники, поэтому я слышу бинауральные (3d-аудио) эффекты. Также я запускаю это на MacOS.
Мой код тестирования ниже. Может ли кто-нибудь дать мне знать, что мне не хватает? Буду признателен!
import Cocoa
import SceneKit
class ViewController: NSViewController {
@IBOutlet weak var sceneView: SCNView!
override func viewDidLoad() {
super.viewDidLoad()
let path = Bundle.main.path(forResource: "Sounds/Test.mp3",
ofType: nil)
let url = URL(fileURLWithPath: path!)
let source = SCNAudioSource(url:url)!
source.loops = true
source.shouldStream = false
source.isPositional = true
source.load()
let player = SCNAudioPlayer(source: source)
let box = SCNBox(width: 100.0,
height: 100.0,
length: 100.0,
chamferRadius: 100.0)
let boxNode = SCNNode(geometry: box)
let audioNode = SCNNode()
boxNode.addChildNode(audioNode)
let scene = SCNScene()
scene.rootNode.addChildNode(boxNode)
sceneView.scene = scene
audioNode.addAudioPlayer(player)
let avm = player.audioNode as! AVAudioMixing
avm.volume = 1.0
let up = SCNAction.moveBy(x: 0, y: 100, z: 0, duration: 5)
let down = SCNAction.moveBy(x: 0, y: -100, z: 0, duration: 5)
let sequence = SCNAction.sequence([up, down])
let loop = SCNAction.repeatForever(sequence)
boxNode.runAction(loop)
// Do any additional setup after loading the view.
}
}
2 ответа
После исследования и экспериментов в течение некоторого времени, я наконец понял это. Было две вещи, которые мне нужно было исправить.
- Мне пришлось изменить алгоритм рендеринга по умолчанию для SCNAudioPlayer.AVAudioNode с equalPowerPanning на HRTF или HRTFHQ. Однако AVAudioNode не имеет свойства renderAlgorithm. Тем не менее, я смог преобразовать SCNAudioPlayer.AVAudioNode в AVAudioPlayerNode, а AVAudioPlayerNode действительно имеет свойство renderAlgorithm. Вот соответствующий код.
if let apn = player.audioNode as? AVAudioPlayerNode {
apn.renderingAlgorithm = .HRTFHQ
}
- Мне пришлось назначить узел с SCNCamera для pointOfView для SCNView. Также мне пришлось изменить положение узла камеры подальше от audioNode. В противном случае, я услышал резкое движение в начале. Вот соответствующий код.
let cameraNode = SCNNode(geometry: SCNBox(width:1, height:1, length:1, chamferRadius: 0.1))
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: -10)
sceneView.pointOfView = cameraNode
Мой scene.rootNode - это геометрия блока с размером 100x100x100. Внутри scene.rootNode у меня есть boxNode размером 50x5050. Затем внутри boxNode у меня есть audioNode, генерирующий звук размером 1x1x1, а также cameraNode с размером 1x1x1. Начальная позиция AudioNode - 0,0,0, а позиция для CameraNode - 0,0,-20.
Наконец, вот весь рабочий код.
импорт какао импорт AVFoundation импорт SceneKit
Класс ViewController: NSViewController {
@IBOutlet weak var sceneView: SCNView!
override func viewDidLoad() {
super.viewDidLoad()
let path = Bundle.main.path(forResource: "Sounds/Test_mono.mp3", ofType: nil)
let url = URL(fileURLWithPath: path!)
let source = SCNAudioSource(url: url)!
source.loops = true
source.shouldStream = false
source.isPositional = true
source.load()
let player = SCNAudioPlayer(source: source)
if let apn = player.audioNode as? AVAudioPlayerNode {
apn.renderingAlgorithm = .HRTFHQ
}
let audioNode = SCNNode(geometry: SCNBox(width:1, height:1, length:1, chamferRadius: 0.1))
let cameraNode = SCNNode(geometry: SCNBox(width:1, height:1, length:1, chamferRadius: 0.1))
cameraNode.camera = SCNCamera () let boxNode = SCNNode (геометрия: SCNBox(ширина:50, высота:50, длина:50, chamferRadius: 1)) boxNode.addChildNode (audioNode) audioNode.position = SCNVector3 (x: 0, y: 0, z: 0) boxNode.addChildNode (cameraNode) cameraNode.position = SCNVector3 (x: 0, y: 0, z: -10)
let scene = SCNScene()
scene.rootNode.geometry = SCNBox(width:100, height:100, length:100, chamferRadius: 0.1)
scene.rootNode.addChildNode(boxNode)
boxNode.position = SCNVector3 (x: 0, y: 0, z: 0)
sceneView.scene = scene
sceneView.pointOfView = cameraNode
sceneView.audioListener = cameraNode
audioNode.addAudioPlayer(player)
let move = SCNAction.moveBy(x:1, y:0, z:0, duration: 1)
let sequence = SCNAction.sequence([move])
let loop = SCNAction.repeatForever(sequence)
audioNode.runAction(loop)
// Выполнить любую дополнительную настройку после загрузки представления. }
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
Обновлено
Вы кастуете player.audioNode
в AVAudioMixing
протокол:
let avm = player.audioNode as! AVAudioMixing
Но вместо этого вы должны привести его к классу. Код выглядит так:
let avm = player.audioNode as? AVAudioEnvironmentNode
Любой узел, который соответствует
AVAudioMixing
протокол (например,AVAudioPlayerNode
) может выступать источником в этой среде. Окружающая среда имеет неявноеlistener
, Управляя положением и ориентацией слушателя, приложение контролирует, как пользователь воспринимает виртуальный мир. Этот узел также определяет свойства для ослабления расстояния и реверберации, которые помогают характеризовать окружающую среду.
Только входы с одноканальным форматом подключения к узлу среды пространственно распределены.
If the input is stereo, the audio is passed through without being spatialized
, Входы с форматами подключения более двух каналов не поддерживаются.
И, конечно же, вам необходимо реализовать протокол AVAudio3DMixing.
Вот рабочий код:
import SceneKit
import AVFoundation
class ViewController: NSViewController, AVAudio3DMixing {
// YOU NEED MONO AUDIO !!!
var renderingAlgorithm = AVAudio3DMixingRenderingAlgorithm.sphericalHead
var rate: Float = 0.0
var reverbBlend: Float = 0.5
var obstruction: Float = -100.0
var occlusion: Float = -100.0
var position: AVAudio3DPoint = AVAudio3DPoint(x: 0, y: 0, z: -100)
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.camera?.zFar = 200
cameraNode.position = SCNVector3(x: 0, y: 0, z: 40)
scene.rootNode.addChildNode(cameraNode)
let sceneView = self.view as! SCNView
sceneView.scene = scene
sceneView.backgroundColor = NSColor.black
sceneView.autoenablesDefaultLighting = true
let path = Bundle.main.path(forResource: "Test_Mono", ofType: "mp3")
let url = URL(fileURLWithPath: path!)
let source = SCNAudioSource(url: url)!
source.loops = true
source.shouldStream = false // MUST BE FALSE
source.isPositional = true
source.load()
let player = SCNAudioPlayer(source: source)
let audioNode = SCNNode()
let box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0.2)
let boxNode = SCNNode(geometry: box)
boxNode.addChildNode(audioNode)
scene.rootNode.addChildNode(boxNode)
audioNode.addAudioPlayer(player)
let avm = player.audioNode as? AVAudioEnvironmentNode
avm?.reverbBlend = reverbBlend
avm?.renderingAlgorithm = renderingAlgorithm
avm?.occlusion = occlusion
avm?.obstruction = obstruction
let up = SCNAction.moveBy(x: 0, y: 0, z: 70, duration: 5)
let down = SCNAction.moveBy(x: 0, y: 0, z: -70 , duration: 5)
let sequence = SCNAction.sequence([up, down])
let loop = SCNAction.repeatForever(sequence)
boxNode.runAction(loop)
avm?.position = AVAudio3DPoint(
x: Float(boxNode.position.x),
y: Float(boxNode.position.y),
z: Float(boxNode.position.z))
}
}