iOS 16中AVAudioSession路由问题 - 输入始终为MicrophoneBuiltIn。setPreferredInput方法无效。

3

AVAudioSessionIOS16InpitIssue

关于这个问题:

我有一个iOS的“吉他效果”应用程序,它从输入中获取音频信号,处理它并通过输出向用户播放结果音频。该应用程序不适用于iOS设备的内置麦克风(因为会出现反馈)-用户必须通过特殊设备连接吉他:模拟设备如iRig或数字设备如iRig HD

简而言之:从iOS 16开始,我遇到了AVAudioSession的奇怪行为,导致我的应用程序崩溃。在iOS 16中,AVAudioSession路由的输入始终是MicrophoneBuiltIn-无论我是否连接任何外部麦克风,如iRig设备或带麦克风的耳机。即使我尝试通过为AVAudioSession分配preferredInput来手动切换到外部麦克风,它也不会改变路线-输入始终是MicrophoneBuiltIn。在iOS 15和更早版本的iOS中,iOS会自动将路由的输入更改为您连接到iOS设备的任何外部麦克风。您可以通过为AVAudioSession分配preferredInput属性来控制输入。

这是一个最小的示例项目,用于重现此问题。

项目结构:

这是一个非常小的项目,用于重现此问题。所有代码都在ViewController类中。

  1. 我创建了一个playAndRecord AVAudioSession,并订阅routeChangeNotification通知:
NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChange), name: AVAudioSession.routeChangeNotification, object: nil)
let audioSession = AVAudioSession.sharedInstance()
do {
  try audioSession.setCategory(AVAudioSession.Category.playAndRecord, options: .mixWithOthers)
  try audioSession.setActive(true, options: [])
} catch {
  print("AVAudioSession init error: \(error)")
}

当我收到通知时,我会打印可用音频输入列表、首选输入和当前的音频路由:
@objc func handleRouteChange(notification: Notification) {
  print("\nHANDLE ROUTE CHANGE")
  print("AVAILABLE INPUTS: \(AVAudioSession.sharedInstance().availableInputs ?? [])")
  print("PREFERRED INPUT: \(String(describing: AVAudioSession.sharedInstance().preferredInput))")
  print("CURRENT ROUTE: \(AVAudioSession.sharedInstance().currentRoute)\n")
}

我有一个按钮,点击后会显示所有可用音频输入的列表,并提供将每个输入设置为首选项的方法。
@IBAction func selectPreferredInputClick(_ sender: UIButton) {
  let inputs = AVAudioSession.sharedInstance().availableInputs ?? []
  let title = "Select Preferred Input"
  let message = "Current Preferred Input: \(String(describing: AVAudioSession.sharedInstance().preferredInput?.portName))\nCurrent Route Input \(String(describing: AVAudioSession.sharedInstance().currentRoute.inputs.first?.portName))"
  let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
  for input in inputs {
    alert.addAction(UIAlertAction(title: input.portName, style: .default) {_ in
      print("\n\(title)")
      print("\(message) New Preferred Input: \(input.portName)\n")
      do {
        try AVAudioSession.sharedInstance().setPreferredInput(input)
     } catch {
        print("Set Preferred Input Error: \(error)")
     }
    })
  }
  alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
  present(alert, animated: true)
}

iOS 16行为:

当我启动应用程序而没有连接任何外部麦克风并初始化AVAudioSession时,我会得到以下日志:

HANDLE ROUTE CHANGE
AVAILABLE INPUTS: [<AVAudioSessionPortDescription: 0x2837101e0, type = MicrophoneBuiltIn; name = iPhone Microphone; UID = Built-In Microphone; selectedDataSource = Bottom>]
PREFERRED INPUT: nil
CURRENT ROUTE: <AVAudioSessionRouteDescription: 0x283710a80, 
inputs = (
    "<AVAudioSessionPortDescription: 0x283710a50, type = MicrophoneBuiltIn; name = iPhone Microphone; UID = Built-In Microphone; selectedDataSource = Bottom>"
); 
outputs = (
    "<AVAudioSessionPortDescription: 0x283710600, type = Receiver; name = Receiver; UID = Built-In Receiver; selectedDataSource = (null)>"
)>

这很好。然后我连接了iRig设备(基本上是外部麦克风),我看到了以下日志:

HANDLE ROUTE CHANGE
AVAILABLE INPUTS: [<AVAudioSessionPortDescription: 0x283718630, type = MicrophoneBuiltIn; name = iPhone Microphone; UID = Built-In Microphone; selectedDataSource = Bottom>, <AVAudioSessionPortDescription: 0x283718500, type = MicrophoneWired; name = Headset Microphone; UID = Wired Microphone; selectedDataSource = (null)>]
PREFERRED INPUT: nil
CURRENT ROUTE: <AVAudioSessionRouteDescription: 0x283700140, 
inputs = (
    "<AVAudioSessionPortDescription: 0x283700160, type = MicrophoneBuiltIn; name = iPhone Microphone; UID = Built-In Microphone; selectedDataSource = Bottom>"
); 
outputs = (
    "<AVAudioSessionPortDescription: 0x2837001f0, type = Headphones; name = Headphones; UID = Wired Headphones; selectedDataSource = (null)>"
)>

你会发现- 有线麦克风出现在可用输入列表中,但路由的输入仍然是内置麦克风。 然后我尝试先将AVAudioSession的preferredInput更改为有线麦克风,然后改为内置麦克风,再改回有线麦克风:

Select Preferred Input
Current Preferred Input: nil
Current Route Input Optional("iPhone Microphone") New Preferred Input: Headset Microphone


Select Preferred Input
Current Preferred Input: Optional("Headset Microphone")
Current Route Input Optional("iPhone Microphone") New Preferred Input: iPhone Microphone


HANDLE ROUTE CHANGE
AVAILABLE INPUTS: [<AVAudioSessionPortDescription: 0x28299da70, type = MicrophoneBuiltIn; name = iPhone Microphone; UID = Built-In Microphone; selectedDataSource = Bottom>, <AVAudioSessionPortDescription: 0x28299d930, type = MicrophoneWired; name = Headset Microphone; UID = Wired Microphone; selectedDataSource = (null)>]
PREFERRED INPUT: Optional(<AVAudioSessionPortDescription: 0x282994330, type = MicrophoneBuiltIn; name = iPhone Microphone; UID = Built-In Microphone; selectedDataSource = Bottom>)
CURRENT ROUTE: <AVAudioSessionRouteDescription: 0x2829912d0, 
inputs = (
    "<AVAudioSessionPortDescription: 0x282991820, type = MicrophoneBuiltIn; name = iPhone Microphone; UID = Built-In Microphone; selectedDataSource = Bottom>"
); 
outputs = (
    "<AVAudioSessionPortDescription: 0x282991740, type = Headphones; name = Headphones; UID = Wired Headphones; selectedDataSource = (null)>"
)>


Select Preferred Input
Current Preferred Input: Optional("iPhone Microphone")
Current Route Input Optional("iPhone Microphone") New Preferred Input: Headset Microphone


HANDLE ROUTE CHANGE
AVAILABLE INPUTS: [<AVAudioSessionPortDescription: 0x28299d7c0, type = MicrophoneBuiltIn; name = iPhone Microphone; UID = Built-In Microphone; selectedDataSource = Bottom>, <AVAudioSessionPortDescription: 0x28299d8c0, type = MicrophoneWired; name = Headset Microphone; UID = Wired Microphone; selectedDataSource = (null)>]
PREFERRED INPUT: Optional(<AVAudioSessionPortDescription: 0x2829918e0, type = MicrophoneWired; name = Headset Microphone; UID = Wired Microphone; selectedDataSource = (null)>)
CURRENT ROUTE: <AVAudioSessionRouteDescription: 0x28299d530, 
inputs = (
    "<AVAudioSessionPortDescription: 0x28299d510, type = MicrophoneBuiltIn; name = iPhone Microphone; UID = Built-In Microphone; selectedDataSource = Bottom>"
); 
outputs = (
    "<AVAudioSessionPortDescription: 0x28299d6d0, type = Headphones; name = Headphones; UID = Wired Headphones; selectedDataSource = (null)>"
)>

无论选择什么输入设备,AudioSession路由的输入设备都是MicrophoneBuiltIn。
iOS 15行为:
在iOS 15中,一切都不同(而且更好)。当我启动应用程序并初始化AVAudioSession时,如果没有连接外部麦克风,我会得到与iOS 16相同的日志记录。
HANDLE ROUTE CHANGE
AVAILABLE INPUTS: [<AVAudioSessionPortDescription: 0x2813cc930, type = MicrophoneBuiltIn; name = iPad Microphone; UID = Built-In Microphone; selectedDataSource = Top>]
PREFERRED INPUT: nil
CURRENT ROUTE: <AVAudioSessionRouteDescription: 0x2813cc9c0, 
inputs = (
    "<AVAudioSessionPortDescription: 0x2813cc9b0, type = MicrophoneBuiltIn; name = iPad Microphone; UID = Built-In Microphone; selectedDataSource = Top>"
); 
outputs = (
    "<AVAudioSessionPortDescription: 0x2813cc6b0, type = Speaker; name = Speaker; UID = Speaker; selectedDataSource = (null)>"
)>

然后我连接iRig设备(基本上是外部麦克风),我看到以下日志:

HANDLE ROUTE CHANGE
AVAILABLE INPUTS: [<AVAudioSessionPortDescription: 0x2813d0450, type = MicrophoneBuiltIn; name = iPad Microphone; UID = Built-In Microphone; selectedDataSource = Top>, <AVAudioSessionPortDescription: 0x2813d04a0, type = MicrophoneWired; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:2; selectedDataSource = (null)>]
PREFERRED INPUT: nil
CURRENT ROUTE: <AVAudioSessionRouteDescription: 0x2813e40f0, 
inputs = (
    "<AVAudioSessionPortDescription: 0x2813e4110, type = MicrophoneWired; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:2; selectedDataSource = (null)>"
); 
outputs = (
    "<AVAudioSessionPortDescription: 0x2813e4150, type = Headphones; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:1; selectedDataSource = (null)>"
)>


HANDLE ROUTE CHANGE
AVAILABLE INPUTS: [<AVAudioSessionPortDescription: 0x2813e40e0, type = MicrophoneBuiltIn; name = iPad Microphone; UID = Built-In Microphone; selectedDataSource = Top>, <AVAudioSessionPortDescription: 0x2813e4160, type = MicrophoneWired; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:2; selectedDataSource = (null)>]
PREFERRED INPUT: nil
CURRENT ROUTE: <AVAudioSessionRouteDescription: 0x2813dc1c0, 
inputs = (
    "<AVAudioSessionPortDescription: 0x2813dc1e0, type = MicrophoneWired; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:2; selectedDataSource = (null)>"
); 
outputs = (
    "<AVAudioSessionPortDescription: 0x2813dc220, type = Headphones; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:1; selectedDataSource = (null)>"
)>

这里有两个主要的区别:

  1. routeChangeNotification被调用了两次
  2. AVAudioSession路由的输入是MicrophoneWired 然后我尝试改变AVAudioSession的首选输入,并得到以下日志:
Select Preferred Input
Current Preferred Input: nil
Current Route Input Optional("YC136 USB AUDIO") New Preferred Input: iPad Microphone


HANDLE ROUTE CHANGE
AVAILABLE INPUTS: [<AVAudioSessionPortDescription: 0x2813c8db0, type = MicrophoneBuiltIn; name = iPad Microphone; UID = Built-In Microphone; selectedDataSource = Top>, <AVAudioSessionPortDescription: 0x2813c8e00, type = MicrophoneWired; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:2; selectedDataSource = (null)>]
PREFERRED INPUT: Optional(<AVAudioSessionPortDescription: 0x2813d8ad0, type = MicrophoneBuiltIn; name = iPad Microphone; UID = Built-In Microphone; selectedDataSource = Top>)
CURRENT ROUTE: <AVAudioSessionRouteDescription: 0x2813c0c40, 
inputs = (
    "<AVAudioSessionPortDescription: 0x2813c1300, type = MicrophoneBuiltIn; name = iPad Microphone; UID = Built-In Microphone; selectedDataSource = Top>"
); 
outputs = (
    "<AVAudioSessionPortDescription: 0x2813c10b0, type = Headphones; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:1; selectedDataSource = (null)>"
)>


Select Preferred Input
Current Preferred Input: Optional("iPad Microphone")
Current Route Input Optional("iPad Microphone") New Preferred Input: YC136 USB AUDIO


HANDLE ROUTE CHANGE
AVAILABLE INPUTS: [<AVAudioSessionPortDescription: 0x2813c0d50, type = MicrophoneBuiltIn; name = iPad Microphone; UID = Built-In Microphone; selectedDataSource = Top>, <AVAudioSessionPortDescription: 0x2813c0a20, type = MicrophoneWired; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:2; selectedDataSource = (null)>]
PREFERRED INPUT: Optional(<AVAudioSessionPortDescription: 0x2813e4140, type = MicrophoneWired; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:2; selectedDataSource = (null)>)
CURRENT ROUTE: <AVAudioSessionRouteDescription: 0x2813cdaa0, 
inputs = (
    "<AVAudioSessionPortDescription: 0x2813cdad0, type = MicrophoneWired; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:2; selectedDataSource = (null)>"
); 
outputs = (
    "<AVAudioSessionPortDescription: 0x2813cdeb0, type = Headphones; name = YC136 USB AUDIO; UID = AppleUSBAudioEngine:Generic:YC136 USB AUDIO:20170726905926:1; selectedDataSource = (null)>"
)>

如您所见,路由的输入与AVAudioSession的首选输入匹配。

结论:

请告知是否有任何方法使iOS 16的行为与iOS 15及以下版本相同。我搜索了iOS 16的发布说明,没有找到提到AVAudioSession的任何信息。如果没有办法,请告知管理AVAudioSession路由输入源的正确方法。非常感谢任何建议。


有任何更新吗? - Mehmet Baykar
@MehmetBaykar 不,我不得不做一个丑陋的解决方法 - 而不是检查路由的当前输入,我正在检查 AVAudioSession 的可用输入数量。 - Gibadu
@MehmetBaykar,看起来苹果在iOS 16.1中修复了这个问题。 - Gibadu
1个回答

2

苹果发布了iOS 16.1,看起来这个问题在那里得到了解决。


1
嘿,当使用Airpods连接时,我仍然可以通过AVAudioSession.sharedInstance().currentRoute.inputs获取到MicrophoneBuiltIn。但是它会在AVAudioSession.routeChangeNotification中正确地更改。你有任何想法为什么吗? - Akila Wasala

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接