Проблема с маршрутом AVAudioSession в iOS 16 — вход всегда MicrophoneBuiltIn. метод setPreferredInput не работает
AVAudioSessionIOS16InpitIssue
О чем это:
У меня есть приложение iOS "Guitar Effect", которое получает аудиосигнал с входа, обрабатывает его и воспроизводит полученный звук обратно пользователю через выход. Приложение не работает со встроенным микрофоном iOS-устройства (из-за обратной связи) - пользователям необходимо подключать гитару через специальное устройство: либо аналоговое, например iRig , либо цифровое, например iRig HD.
Вкратце: начиная с iOS 16 я сталкиваюсь со странным поведением AVAudioSession, которое ломает мое приложение. В iOS 16 вход AVAudioSession Route всегда MicrophoneBuiltIn — независимо от того, подключаю ли я какие-либо внешние микрофоны, такие как устройство iRig или наушники с микрофоном. Даже если я попытаюсь вручную переключиться на внешний микрофон, назначив предпочтительный вход для AVAudioSession, это не изменит маршрут — вход всегда MicrophoneBuiltIn. В iOS 15 и более ранних версиях iOS автоматически меняет ввод маршрута на любой внешний микрофон, подключенный к устройству iOS. И вы можете управлять вводом, назначив свойство selectedInput для AVAudioSession.
Структура проекта:
Это самый маленький пример проекта для воспроизведения проблемы.Это очень небольшой проект, созданный для воспроизведения проблемы.Весь код находится в классе ViewController.
- Я создаю 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)>"
)>
Как видите, MicrophoneWired появляется в списке доступных входов, но вход маршрута по-прежнему MicrophoneBuiltIn. Затем я попытался изменить предпочтительный вход AVAudioSession сначала на MicrophoneWired, затем на MicrophoneBuiltIn, а затем снова на MicrophoneWired:
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)>"
)>
Здесь два основных отличия:
- routeChangeNotification вызывался два раза
- ввод маршрута 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. Любой совет высоко ценится.
1 ответ
Apple выпустила iOS 16.1, и похоже, что эта проблема там исправлена