【问题标题】:setting gain of mp3 files (aka normalize) for playback为播放设置 mp3 文件的增益(又名标准化)
【发布时间】:2020-09-26 20:55:44
【问题描述】:

有没有人成功编写过本地 XCode/Swift 代码来实现 ReplayGain 或类似功能,或者知道我在哪里可以找到一些示例代码/教程?

我正在尝试将我的 python 代码转换为原生 macOS 应用程序的 XCode。我在 python 中使用 pydub 库,并通过 apply_gain 方法应用增益调整以将我的音频文件标准化为指定的 dBFS,但我已经搜索了高和低的示例代码,例如如何在 XCode 中执行此操作,我来了几乎两手空空。我正在使用 Catalina 10.15.4 和 XCode 11.5。

我找到了下面的代码here,并对其进行了修改以删除均衡器频段并添加了 globalGain 选项。但是,我现在需要弄清楚如何分析文件以确定峰值分贝/峰值增益并相应地调整 globalGain 以确保每个 mp3 的音量几乎相同,而不必一直摆弄音量。

import AVFoundation

var audioEngine: AVAudioEngine = AVAudioEngine()
var equalizer: AVAudioUnitEQ!
var audioPlayerNode: AVAudioPlayerNode = AVAudioPlayerNode()
var audioFile: AVAudioFile!

// in viewDidLoad():
equalizer = AVAudioUnitEQ(numberOfBands: 0)
audioEngine.attach(audioPlayerNode)
audioEngine.attach(equalizer)
let bands = equalizer.bands
//let globalGain = equalizer.globalGain
let freqs = [60, 230, 910, 4000, 14000]
audioEngine.connect(audioPlayerNode, to: equalizer, format: nil)
audioEngine.connect(equalizer, to: audioEngine.outputNode, format: nil)
equalizer.globalGain = 12
var filePath = "some_music_file.mp3"

do {
        let filePathURL = NSURL.fileURL(withPath: filePath)
        audioFile = try AVAudioFile(forReading: filePathURL)
        audioEngine.prepare()
        try audioEngine.start()
        audioPlayerNode.scheduleFile(audioFile, at: nil, completionHandler: nil)
        audioPlayerNode.play()
} catch {
    print ("An error occured.")
}

【问题讨论】:

    标签: xcode avfoundation


    【解决方案1】:

    经过数小时的研究和挖掘,我想我终于找到了在轨道之间“标准化”我的音频所需的解决方案。

    其实很短。我正在向该方法发送一个 AVAudioPCMBuffer,并使用 Accelerate API 进行数学运算,首先计算 RMS(功率),然后将其转换为分贝(dB)级别。

    我设置了一个默认音量级别,然后从 dB 的绝对值中减去该音量级别,并将该值传递给 AVAudioEQ 的.globalGain 属性。

    至少在我看来,这似乎是为了实现我想要从 Python 迁移过来的东西。我已经用许多 MP3 对其进行了测试,这些 MP3 从一首曲目到另一首曲目的声音明显更大或更小,而且它似乎运行良好。

    如果您打算在非常大的音频文件上使用它,我建议将它放在它自己的线程中,这样它就不会阻塞。对于我的应用程序来说,它似乎已经足够快了。

    如果您知道更好、更有效的方法,请在下方评论。

    Swift 5.3

    import Accelerate
    import AVFoundation
    
    func getDecibles1(buffer: AVAudioPCMBuffer) -> Float {
       // This method will compute the Real Mean Squared (RMS) value of an audio
       // PCM buffer that will return the Decibile (dB) level of an audio signal.
       // RMS will be calculated for all channels with a signal and averaged
       // which then provides true dB of the audio for all channels.
       let frameLength = UInt(buffer.frameLength)
       let channels = Int(buffer.format.channelCount)
       var channelsWithSignal = Float(channels)
       var rms:Float = 0
       var rmsSumOfChannels: Float = 0
       for channel in 0..<channels {
           guard let channelData = buffer.floatChannelData?[channel] else { return 0 }
           vDSP_measqv(channelData, 1, &rms, frameLength)
           if rms > 0 {
               rmsSumOfChannels += rms
           } else {
               channelsWithSignal -= 1
           }
        }
        // If you need the average power, uncomment the line below
        //let avgPower = 20 * log10(rmsSumOfChannels/channelsWithSignal)
        let dB = 10 * log10(rmsSumOfChannels/channelsWithSignal)
        return dB
    }
    

    【讨论】:

    • vDSP_measqv 的文档说它“计算单精度向量中的平方均值”。您是否需要取其平方根才能获得 rms?另外,如果将通道值加在一起是正确的方法吗?也许平均通道值以获得对总输出有意义的值?因为我的理解水平可能比你低。
    • 我停止使用这种方法,因为它在生产环境中不够好。我现在求助于通过 ffmpeg 的 loud_norm 程序运行命令行进程,将每个音频文件的 LUFS 设置为相同的值。我可以在 Xcode/Swift 中找到任何原生的东西。
    • 我很好奇是什么让您断定这种方法“在生产环境中不够好”。我正在尝试在仅供个人使用的应用程序中执行此操作。我希望避免遍历我收藏中的所有歌曲来手动让它们对我的耳朵具有大致相同的响度(不一定完全相同的 LUFS 值)。我主要是在开车时听,所以无论如何精确的精度是没用的。由于我不想规范化库中的每首歌曲,因此对使用命令行 ffmpeg 方法的歌曲进行排序将是一场噩梦。
    • 我在仔细查看代码后意识到,这就是平均通道值。呸!太晚了,无法编辑我的原始评论 - 抱歉。
    • 只是为了跟进,我已经放弃了这种准即时标准化方法,我现在使用方法启动一个外部进程,该进程调用 ffprobe 以获取音频文件详细信息,然后运行 ​​ffmpeg 进程从我的音频文件的开头和结尾修剪静音以及随后的 ffmpeg 进程以将音频标准化为 LUFS (-23)。在我的演出期间,我不再需要弄乱音量/增益。我设置它并离开它。这是我针对我的情况(DJ 类型的工作)提出的最佳解决方案。感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 2017-06-29
    • 1970-01-01
    • 2019-07-31
    • 2014-05-26
    • 2010-12-20
    • 2012-04-11
    • 1970-01-01
    相关资源
    最近更新 更多