You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在AVFoundation中正确修改采样率?采样率切换无声音问题排查

问题分析与解决方案

你遇到的核心问题是音频格式不匹配以及未正确设置音频会话的首选采样率,导致低采样率下音频无法正常播放。下面一步步拆解原因和解决方法:

主要问题根源

  • AudioSession采样率是只读属性,无法通过category直接修改:你打印的audioSession.sampleRate是当前硬件的默认采样率,要使用自定义采样率(比如16000Hz/8000Hz),必须主动调用setPreferredSampleRate(_:)方法,否则硬件会沿用自身默认值。
  • Tap格式与输入节点格式不兼容:你在installTap时强制指定了44100Hz格式,但输入节点的实际格式可能和硬件原生格式一致(比如蓝牙耳机的16000Hz),这种格式不匹配会导致音频数据转换出错,要么无声要么产生噪音。
  • Player连接格式错误:你把player连接到mainMixer时用了input.inputFormat(forBus: 0),但player实际要播放的是tap输出的缓冲区格式,两者不一致会导致播放失败。

修正后的代码

import UIKit
import AVFoundation

class ViewController: UIViewController {
    var engine = AVAudioEngine()
    let player = AVAudioPlayerNode()
    let audioSession = AVAudioSession.sharedInstance()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 1. 配置音频会话并设置首选采样率
        do {
            try audioSession.setCategory(.playAndRecord, options: .allowBluetooth)
            try audioSession.setMode(.default)
            // 设置目标采样率:可替换为8000.0或其他需要的值
            try audioSession.setPreferredSampleRate(16000.0)
            try audioSession.setActive(true)
        } catch {
            print("音频会话配置失败:\(error.localizedDescription)")
        }
        
        print("当前会话采样率:\(audioSession.sampleRate)")
        
        let input = engine.inputNode
        let bus = 0
        
        // 2. 获取输入节点的实际输出格式(与会话采样率同步)
        let inputOutputFormat = input.outputFormat(forBus: bus)
        print("输入节点输出格式:\(inputOutputFormat)")
        
        engine.attach(player)
        
        // 3. 确保Player连接格式与输入输出格式一致
        engine.connect(player, to: engine.mainMixerNode, format: inputOutputFormat)
        
        // 4. 使用输入节点的实际格式安装Tap,避免格式不匹配
        input.installTap(onBus: bus, bufferSize: 1024, format: inputOutputFormat) { [weak self] buffer, time in
            guard let self = self else { return }
            
            print("缓冲区格式:\(buffer.format)")
            print("缓冲区数据地址:\(buffer.floatChannelData)")
            
            // 调度缓冲区播放,确保播放状态正确
            self.player.scheduleBuffer(buffer, at: nil, options: .interruptsAtLoopStart, completionHandler: nil)
            
            if !self.player.isPlaying {
                self.player.play()
            }
        }
        
        do {
            try engine.start()
        } catch {
            print("引擎启动失败:\(error.localizedDescription)")
        }
    }
}

关键改动说明

  • 主动设置首选采样率:通过audioSession.setPreferredSampleRate(_:)指定你需要的采样率,硬件会尽量适配该值(注意部分设备可能不支持极低采样率,需在catch块中做降级处理)。
  • 统一全程音频格式:全程使用输入节点的实际输出格式(与会话采样率同步),避免tap、player、mixer之间的格式冲突。
  • 移除冗余Mixer节点:你的场景不需要额外的mixer,直接在输入节点上安装tap即可,减少格式转换环节的出错概率。
  • 安全控制Player播放:在tap回调中检查player状态,仅在未播放时调用play(),避免重复触发播放逻辑。

额外注意事项

  • 不同硬件支持的采样率范围不同,比如部分内置麦克风最低仅支持44100Hz,设置8000Hz会失败,需在setPreferredSampleRate的错误处理中 fallback到设备支持的采样率。
  • 可通过audioSession.preferredSampleRate确认设置的采样率是否生效。
  • 如果需要在不同格式间转换音频数据(比如输入16000Hz转44100Hz播放),可以使用AVAudioConverter处理缓冲区数据,避免强制格式导致的错误。

内容的提问来源于stack exchange,提问作者Satizh J

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:16:11