Fix iOS voice capture and screen sharing
This commit is contained in:
@@ -61,6 +61,11 @@ internal sealed class IosAudioRouter
|
||||
BluetoothMode = preset == IosAudioPreset.VoiceChat ? IosBluetoothMode.HfpVoice : IosBluetoothMode.BuiltInMicA2dp;
|
||||
if (preset is IosAudioPreset.StereoMicrophone or IosAudioPreset.MonoMicrophone)
|
||||
SelectedInputId = AVAudioSession.SharedInstance().AvailableInputs?.FirstOrDefault(value => value.PortType == AVAudioSession.PortBuiltInMic)?.UID;
|
||||
else SelectedInputId = null;
|
||||
// Named presets never carry an Advanced capsule selection across transitions.
|
||||
// Stereo derives its data source below; mono/voice chat must clear a stale stereo one.
|
||||
SelectedDataSourceId = null; SelectedPolarPattern = AVAudioDataSourcePolarPattern.Unknown;
|
||||
if (preset == IosAudioPreset.VoiceChat) ForceSpeaker = true;
|
||||
}
|
||||
SaveAndReconfigure();
|
||||
}
|
||||
@@ -103,38 +108,52 @@ internal sealed class IosAudioRouter
|
||||
: BluetoothMode == IosBluetoothMode.BuiltInMicA2dp ? AVAudioSessionMode.VideoRecording : AVAudioSessionMode.VoiceChat;
|
||||
if (!session.SetCategory(AVAudioSessionCategory.PlayAndRecord, mode, options, out NSError? categoryError)) throw new InvalidOperationException(categoryError.LocalizedDescription);
|
||||
session.SetPreferredSampleRate(48_000, out _); session.SetPreferredIOBufferDuration(0.02, out _);
|
||||
if (configureInput) ApplyInputSelection(session);
|
||||
if (!session.SetActive(true, AVAudioSessionSetActiveOptions.NotifyOthersOnDeactivation, out NSError? activeError)) throw new InvalidOperationException(activeError.LocalizedDescription);
|
||||
if (configureInput) ApplyInput(session);
|
||||
Console.Error.WriteLine($"VC_ROUTE preset={Preset} requestedCh={CaptureChannels} sessionCh={session.InputNumberOfChannels} " +
|
||||
$"preferred={session.PreferredInput?.PortName ?? "default"} dataSource={session.InputDataSource?.DataSourceName ?? "default"} " +
|
||||
$"pattern={session.InputDataSource?.SelectedPolarPattern.ToString() ?? "default"}");
|
||||
session.OverrideOutputAudioPort(ForceSpeaker && BluetoothMode != IosBluetoothMode.BuiltInMicA2dp ? AVAudioSessionPortOverride.Speaker : AVAudioSessionPortOverride.None, out _); RefreshRoutes();
|
||||
}
|
||||
finally { applying = false; }
|
||||
}
|
||||
|
||||
private void ApplyInput(AVAudioSession session)
|
||||
private void ApplyInputSelection(AVAudioSession session)
|
||||
{
|
||||
AVAudioSessionPortDescription? port = session.AvailableInputs?.FirstOrDefault(value => value.UID == SelectedInputId);
|
||||
if (CaptureChannels == 2) port ??= session.AvailableInputs?.FirstOrDefault(value => value.PortType == AVAudioSession.PortBuiltInMic);
|
||||
if (port is null) return; session.SetPreferredInput(port, out _);
|
||||
AVAudioSessionDataSourceDescription? source = port.DataSources?.FirstOrDefault(value => value.DataSourceID.ToString() == SelectedDataSourceId);
|
||||
if (CaptureChannels == 2) source ??= port.DataSources?.FirstOrDefault(value => value.SupportedPolarPatterns?.Any(pattern => pattern.ToString().Contains("Stereo", StringComparison.OrdinalIgnoreCase)) == true);
|
||||
if (port is null) { if (CaptureChannels == 1) ClearStereoPolarPattern(session); return; }
|
||||
AVAudioSessionDataSourceDescription? source = CaptureChannels == 2
|
||||
? port.DataSources?.FirstOrDefault(value => value.SupportedPolarPatterns?.Any(pattern => pattern.ToString().Contains("Stereo", StringComparison.OrdinalIgnoreCase)) == true)
|
||||
: port.DataSources?.FirstOrDefault(value => value.DataSourceID.ToString() == SelectedDataSourceId);
|
||||
if (CaptureChannels == 1 && source is null) ClearStereoPolarPattern(session);
|
||||
if (source is not null)
|
||||
{
|
||||
if (!port.SetPreferredDataSource(source, out NSError? portError)) throw new InvalidOperationException(portError.LocalizedDescription);
|
||||
if (!session.SetInputDataSource(source, out NSError? sourceError)) throw new InvalidOperationException(sourceError.LocalizedDescription);
|
||||
}
|
||||
if (session.MaximumInputNumberOfChannels < CaptureChannels)
|
||||
throw new InvalidOperationException($"The selected input supports at most {session.MaximumInputNumberOfChannels} channel(s), not {CaptureChannels}.");
|
||||
if (!session.SetPreferredInputNumberOfChannels(CaptureChannels, out NSError? channelError)) throw new InvalidOperationException(channelError.LocalizedDescription);
|
||||
if (source is not null)
|
||||
{
|
||||
AVAudioDataSourcePolarPattern pattern = CaptureChannels == 2
|
||||
? source.SupportedPolarPatterns?.FirstOrDefault(value => value.ToString().Contains("Stereo", StringComparison.OrdinalIgnoreCase)) ?? AVAudioDataSourcePolarPattern.Unknown
|
||||
: SelectedPolarPattern;
|
||||
if (pattern != AVAudioDataSourcePolarPattern.Unknown && !source.SetPreferredPolarPattern(pattern, out NSError? patternError))
|
||||
throw new InvalidOperationException(patternError.LocalizedDescription);
|
||||
}
|
||||
if (session.InputNumberOfChannels != CaptureChannels)
|
||||
throw new InvalidOperationException($"The selected input activated with {session.InputNumberOfChannels} channel(s), not {CaptureChannels}.");
|
||||
if (!session.SetPreferredInput(port, out NSError? inputError)) throw new InvalidOperationException(inputError.LocalizedDescription);
|
||||
// The working Swift client deliberately does not call
|
||||
// SetPreferredInputNumberOfChannels: doing so disrupts stereo + A2DP routing. The stereo
|
||||
// capsule and polar pattern above cause the input node to expose its two-channel format.
|
||||
if (CaptureChannels == 2 && source is not null &&
|
||||
!session.SetInputDataSource(source, out NSError? sourceError))
|
||||
throw new InvalidOperationException(sourceError.LocalizedDescription);
|
||||
Console.Error.WriteLine($"VC_ROUTE_SELECT port={port.PortName} source={source?.DataSourceName ?? "none"} " +
|
||||
$"patterns={string.Join(',', source?.SupportedPolarPatterns?.Select(value => value.ToString()) ?? [])}");
|
||||
}
|
||||
|
||||
private static void ClearStereoPolarPattern(AVAudioSession session)
|
||||
{
|
||||
AVAudioSessionPortDescription? builtIn = session.AvailableInputs?.FirstOrDefault(value => value.PortType == AVAudioSession.PortBuiltInMic);
|
||||
if (builtIn is null) return;
|
||||
foreach (AVAudioSessionDataSourceDescription source in builtIn.DataSources ?? [])
|
||||
if (source.SelectedPolarPattern.ToString().Contains("Stereo", StringComparison.OrdinalIgnoreCase))
|
||||
source.SetPreferredPolarPattern(AVAudioDataSourcePolarPattern.Unknown, out _);
|
||||
}
|
||||
|
||||
private void SaveAndReconfigure()
|
||||
|
||||
Reference in New Issue
Block a user