【问题标题】:Boost / increase volume of text to speech (AVSpeechUtterance) to make it louder提高/增加文本到语音的音量 (AVSpeechUtterance) 使其更响亮
【发布时间】:2019-07-12 02:03:01
【问题描述】:

我有一个导航应用程序,它使用AVSpeechUtterance 提供方向语音指令(例如“在 200 英尺内左转”)。我已经把音量设置为 1 像这样。 speechUtteranceInstance.volume = 1,但与来自 iPhone 的音乐或播客相比,音量仍然非常低,尤其是当声音通过蓝牙或有线连接(如通过蓝牙连接到汽车)时

有什么办法可以提高音量吗? (我知道之前有人问过这个问题,但到目前为止还没有找到适合我的解决方案。)

【问题讨论】:

    标签: ios swift avaudioplayer avaudiosession avspeechutterance


    【解决方案1】:

    经过大量研究和尝试,我找到了一个很好的解决方法。

    首先,我认为这是一个 iOS 错误。当以下所有条件都成立时,我发现语音指令本身也被闪避(或至少听起来被闪避)导致语音指令以与 DUCKED 音乐相同的音量播放(因此太软而听不清)。

    • 在后台播放音乐
    • 通过隐藏此背景音乐 .duckOtheraudioSessionCategory
    • 通过 AVSpeechSynthesizer 播放语音语音
    • 通过连接的蓝牙播放音频 设备(如蓝牙耳机或蓝牙车载扬声器)

    我找到的解决方法是将 SpeechUtterance 提供给 AVAudioEngine。这只能在 iOS13 或更高版本上完成,因为这会添加 .write method to AVSpeechSynthesizer

    简而言之,我使用AVAudioEngineAVAudioUnitEQAVAudioPlayerNode,将AVAudioUnitEQ 的 globalGain 属性设置为大约 10 dB。这也有一些怪癖,但可以解决它们(参见代码 cmets)。

    完整代码如下:

    import UIKit
    import AVFoundation
    import MediaPlayer
    
    class ViewController: UIViewController {
    
        // MARK: AVAudio properties
        var engine = AVAudioEngine()
        var player = AVAudioPlayerNode()
        var eqEffect = AVAudioUnitEQ()
        var converter = AVAudioConverter(from: AVAudioFormat(commonFormat: AVAudioCommonFormat.pcmFormatInt16, sampleRate: 22050, channels: 1, interleaved: false)!, to: AVAudioFormat(commonFormat: AVAudioCommonFormat.pcmFormatFloat32, sampleRate: 22050, channels: 1, interleaved: false)!)
        let synthesizer = AVSpeechSynthesizer()
        var bufferCounter: Int = 0
    
        let audioSession = AVAudioSession.sharedInstance()
    
    
    
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
    
    
            let outputFormat = AVAudioFormat(commonFormat: AVAudioCommonFormat.pcmFormatFloat32, sampleRate: 22050, channels: 1, interleaved: false)!
            setupAudio(format: outputFormat, globalGain: 0)
    
    
    
        }
    
        func activateAudioSession() {
            do {
                try audioSession.setCategory(.playback, mode: .voicePrompt, options: [.mixWithOthers, .duckOthers])
                try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
            } catch {
                print("An error has occurred while setting the AVAudioSession.")
            }
        }
    
        @IBAction func tappedPlayButton(_ sender: Any) {
    
            eqEffect.globalGain = 0
            play()
    
        }
    
        @IBAction func tappedPlayLoudButton(_ sender: Any) {
            eqEffect.globalGain = 10
            play()
    
        }
    
        func play() {
            let path = Bundle.main.path(forResource: "voiceStart", ofType: "wav")!
            let file = try! AVAudioFile(forReading: URL(fileURLWithPath: path))
            self.player.scheduleFile(file, at: nil, completionHandler: nil)
            let utterance = AVSpeechUtterance(string: "This is to test if iOS is able to boost the voice output above the 100% limit.")
            synthesizer.write(utterance) { buffer in
                guard let pcmBuffer = buffer as? AVAudioPCMBuffer, pcmBuffer.frameLength > 0 else {
                    print("could not create buffer or buffer empty")
                    return
                }
    
                // QUIRCK Need to convert the buffer to different format because AVAudioEngine does not support the format returned from AVSpeechSynthesizer
                let convertedBuffer = AVAudioPCMBuffer(pcmFormat: AVAudioFormat(commonFormat: AVAudioCommonFormat.pcmFormatFloat32, sampleRate: pcmBuffer.format.sampleRate, channels: pcmBuffer.format.channelCount, interleaved: false)!, frameCapacity: pcmBuffer.frameCapacity)!
                do {
                    try self.converter!.convert(to: convertedBuffer, from: pcmBuffer)
                    self.bufferCounter += 1
                    self.player.scheduleBuffer(convertedBuffer, completionCallbackType: .dataPlayedBack, completionHandler: { (type) -> Void in
                        DispatchQueue.main.async {
                            self.bufferCounter -= 1
                            print(self.bufferCounter)
                            if self.bufferCounter == 0 {
                                self.player.stop()
                                self.engine.stop()
                                try! self.audioSession.setActive(false, options: [])
                            }
                        }
    
                    })
    
                    self.converter!.reset()
                    //self.player.prepare(withFrameCount: convertedBuffer.frameLength)
                }
                catch let error {
                    print(error.localizedDescription)
                }
            }
            activateAudioSession()
            if !self.engine.isRunning {
                try! self.engine.start()
            }
            if !self.player.isPlaying {
                self.player.play()
            }
        }
    
        func setupAudio(format: AVAudioFormat, globalGain: Float) {
            // QUIRCK: Connecting the equalizer to the engine somehow starts the shared audioSession, and if that audiosession is not configured with .mixWithOthers and if it's not deactivated afterwards, this will stop any background music that was already playing. So first configure the audio session, then setup the engine and then deactivate the session again.
            try? self.audioSession.setCategory(.playback, options: .mixWithOthers)
    
            eqEffect.globalGain = globalGain
            engine.attach(player)
            engine.attach(eqEffect)
            engine.connect(player, to: eqEffect, format: format)
            engine.connect(eqEffect, to: engine.mainMixerNode, format: format)
            engine.prepare()
    
            try? self.audioSession.setActive(false)
    
        }
    
    }
    

    【讨论】:

    • 非常感谢您分享这个。我一直在努力让它发挥作用!
    • 这是一个很好的解决方案,它帮助很大。谢谢你。它在正常情况下有效,但在 CarPlay 的情况下它似乎不起作用。在互联网上快速搜索 voicePrompt 类别不允许任何音频处理,包括 AVAudioUnitEQ 处理。有同样的想法吗?
    • @nishithSingh 抱歉。我不知道。我没有使用 voicePrompt 类别(也许这就是你的答案……使用不同的类别)并且没有使用 CarPlay 进行测试。
    【解决方案2】:

    文档提到.volume 的默认值为 1.0,这是最响亮的。实际响度基于用户音量设置。如果用户调高音量,我并没有真正遇到语音不够响亮的问题。

    如果用户音量级别低于某个级别,也许您可​​以考虑显示视觉警告。似乎this answer 展示了如何通过 AVAudioSession 做到这一点。

    AVAudioSession 值得探索,因为有些设置确实会影响语音输出...例如,您的应用是否会中断来自其他应用的音频。

    【讨论】:

    • 我的应用已经在闪避其他音频(并暂停语音音频)。问题是用户可能正在播放来自另一个应用程序的音乐,例如音乐应用。因此,简单地让用户调高或调低整体音量不是一种选择,因为这样音乐音量就会太大或太低。与音乐或播客的音量相比,语音的相对音量非常低。
    • 我以前从未注意到这一点,但现在我检查我同意你的看法。此外,背景音乐的缓慢淡入和淡出似乎让事情变得更糟。有没有办法改变淡入淡出设置? TBH 我认为您可能会受到系统的限制。如果您的语音并不总是动态的,您可以输出到音频文件(似乎仅在 macOS 上可用),然后将它们通过音频过滤器以提高音量。
    • 我也可以尝试中断音乐(以更改淡入淡出设置)。不幸的是,语音是非常动态的,所以我认为不可能输出到音频文件,除非那几乎是实时的?不过我会调查一下。我还考虑过在讲话时增加整体音量(通过 MPVolumeView),然后在讲话完成后将其调低。没有把握。如果这些工作中的任何一个,我会在这里发布。 Tha KS 为您提供建议。真的很感激!
    • 别担心!最后一个想法我会尝试,看看是否有任何大型 3rd 方应用程序能够实现你想要的......它会让你知道它是否可能,也许会给你一些提示/灵感;-) 好运气@gudio
    • 我今天开始做一些关于语音的工作,我注意到在 iOS13 中有一些 AVSpeechSynthesizer 的更新可能值得一看,现在有一个 AVAudioSession 属性和一个神秘的 mixToTelephonyUplink 属性
    【解决方案3】:

    试试这个:

    import Speech
    
    try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
    
    let utterance = AVSpeechUtterance(string: "Hello world")        
    utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
    
    let synthesizer = AVSpeechSynthesizer()
    synthesizer.speak(utterance)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-26
      • 2021-10-15
      • 1970-01-01
      • 2012-12-16
      • 1970-01-01
      • 1970-01-01
      • 2023-03-03
      • 2015-06-16
      相关资源
      最近更新 更多