-
|
Hi, Any tips/ideas where to implement ReplayGain support in the audio stack? I guess I should remove the EQ node before SFBAudioPlayer attempts to switch decoder, but this does not feel right. Maybe there is a better/cleaner way to implement this? Thanks. |
Beta Was this translation helpful? Give feedback.
Replies: 11 comments 2 replies
-
|
What I would recommend is in the player's rendering will start delegate method, schedule a volume parameter change on the main mixer node using the desired replay gain value. You can schedule the parameter to change at the time the track will start if you want to be extra precise. There shouldn't be any need to add additional Audio Units. |
Beta Was this translation helpful? Give feedback.
-
|
Thanks for your help. If I understand well you can only apply attenuation with the main mixer volume. What if you also want some positive preamp gain? I realize now my question is more general since I may also need to do more advanced processing (like n-band EQ): how do you properly insert Audio Units in the audio graph and handle configuration (format) changes? |
Beta Was this translation helpful? Give feedback.
-
|
I added https://github.com/sbooth/SFBAudioEngine/wiki/Adding-an-AVAudioUnit-to-AudioPlayer which I hope explains the process in a bit more detail. |
Beta Was this translation helpful? Give feedback.
-
|
Thank you very much, that's helpful. Sadly it did not work: audioPlayerAVAudioEngineConfigurationChange: is not called when the player node is updated. However I think I finally understand why I get the kAudioUnitErr_FormatNotSupported error. So maybe a hook is needed in configureEngineForGaplessPlaybackOfFormat: to allow users to properly reconfigure the chain? |
Beta Was this translation helpful? Give feedback.
-
|
It's surprising that I think your idea for a hook is a good one because it would let the changes to the processing graph be grouped together. |
Beta Was this translation helpful? Give feedback.
-
|
Please take a look at #401 and let me know if it works for you. |
Beta Was this translation helpful? Give feedback.
-
My interpretation of Apple's documentation is that I tested your patch but there's still an issue: The exception is raised in if(playerNodeOutputConnectionPoint)
[_engine connect:_playerNode to:playerNodeOutputConnectionPoint.node format:format];Not sure how to handle this cleanly. Some ideas:
|
Beta Was this translation helpful? Give feedback.
-
|
Thank you for testing out the proposed PR and providing feedback. I pushed additional changes that I hope will fix the issue. |
Beta Was this translation helpful? Give feedback.
-
|
Thanks, we are nearly there :) So now adding an AudioUnit in the chain may look like this: init() {
self.audioPlayer = AudioPlayer()
self.equalizer = AVAudioUnitEQ()
self.equalizer.globalGain = -6
self.equalizer.bypass = false
self.audioPlayer.delegate = self
self.audioPlayer.withEngine { engine in
guard let mixerCP = engine.inputConnectionPoint(for: engine.mainMixerNode, inputBus: 0) else {
logger.critical("AVAudioEngine missing main mixer node")
return
}
guard let srcNode = mixerCP.node else {
logger.critical("AVAudioEngine missing input node to main mixer node")
return
}
let format = srcNode.outputFormat(forBus: 0)
engine.attach(self.equalizer)
engine.disconnectNodeInput(engine.mainMixerNode)
engine.connect(srcNode, to: self.equalizer, format: format)
engine.connect(self.equalizer, to: engine.mainMixerNode, format: format)
}
}
func audioPlayer(_ audioPlayer: AudioPlayer, reconfigureProcessingGraph engine: AVAudioEngine) -> AVAudioNode {
let format = audioPlayer.playerNode.renderingFormat
engine.disconnectNodeOutput(self.equalizer)
engine.connect(self.equalizer, to: engine.mainMixerNode, format: format)
return self.equalizer
}I suggest providing the new format as an argument to the It would look like this: func audioPlayer(_ audioPlayer: AudioPlayer, reconfigureProcessingGraph engine: AVAudioEngine, withFormat format: AVAudioFormat) -> AVAudioNode {
engine.disconnectNodeOutput(self.equalizer)
engine.connect(self.equalizer, to: engine.mainMixerNode, format: format)
return self.equalizer
} |
Beta Was this translation helpful? Give feedback.
-
|
I'd actually considered that initially but then decided against it, but after seeing your code it definitely makes sense to include the format in the delegate call. I guess the last thing to nail down is whether the delegate method should be called
|
Beta Was this translation helpful? Give feedback.
-
|
Your call :) |
Beta Was this translation helpful? Give feedback.
I think these are good changes. Thank you for raising the issue.