Skip to content

Commit 1cef977

Browse files
authored
Merge pull request #2 from webrtc-sdk/listen-only-audio-session
allow listen-only mode in AudioUnit, adjust when category changes
2 parents 2d3ba08 + bf37148 commit 1cef977

File tree

9 files changed

+126
-18
lines changed

9 files changed

+126
-18
lines changed

sdk/objc/components/audio/RTCAudioSession+Configuration.mm

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ - (BOOL)setConfiguration:(RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configur
5555
if (![self setCategory:configuration.category
5656
withOptions:configuration.categoryOptions
5757
error:&categoryError]) {
58-
RTCLogError(@"Failed to set category: %@",
58+
RTCLogError(@"Failed to set category to %@: %@",
59+
self.category,
5960
categoryError.localizedDescription);
6061
error = categoryError;
6162
} else {
@@ -66,7 +67,8 @@ - (BOOL)setConfiguration:(RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configur
6667
if (self.mode != configuration.mode) {
6768
NSError *modeError = nil;
6869
if (![self setMode:configuration.mode error:&modeError]) {
69-
RTCLogError(@"Failed to set mode: %@",
70+
RTCLogError(@"Failed to set mode to %@: %@",
71+
self.mode,
7072
modeError.localizedDescription);
7173
error = modeError;
7274
} else {

sdk/objc/components/audio/RTCAudioSession+Private.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ NS_ASSUME_NONNULL_BEGIN
3535
*/
3636
@property(nonatomic, assign) BOOL isInterrupted;
3737

38+
/** if the current category could allow recording */
39+
@property(nonatomic, assign) BOOL isRecordingEnabled;
40+
3841
/** Adds the delegate to the list of delegates, and places it at the front of
3942
* the list. This delegate will be notified before other delegates of
4043
* audio events.

sdk/objc/components/audio/RTCAudioSession.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ RTC_OBJC_EXPORT
9999
failedToSetActive:(BOOL)active
100100
error:(NSError *)error;
101101

102+
/** Called when audio session changed from output-only to input & output */
103+
- (void)audioSessionWillRecord:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession;
104+
102105
@end
103106

104107
/** This is a protocol used to inform RTCAudioSession when the audio session

sdk/objc/components/audio/RTCAudioSession.mm

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ - (instancetype)initWithAudioSession:(id)audioSession {
103103
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
104104
context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class];
105105

106+
self.isRecordingEnabled = [_session.category isEqualToString:AVAudioSessionCategoryPlayAndRecord];
107+
106108
RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): init.", self);
107109
}
108110
return self;
@@ -492,6 +494,13 @@ - (void)handleRouteChangeNotification:(NSNotification *)notification {
492494
case AVAudioSessionRouteChangeReasonCategoryChange:
493495
RTCLog(@"Audio route changed: CategoryChange to :%@",
494496
self.session.category);
497+
if (!self.isRecordingEnabled && [self.session.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) {
498+
self.isRecordingEnabled = true;
499+
[self notifyWillRecord];
500+
}
501+
if (self.isRecordingEnabled && [self.session.category isEqualToString:AVAudioSessionCategoryPlayback]) {
502+
self.isRecordingEnabled = false;
503+
}
495504
break;
496505
case AVAudioSessionRouteChangeReasonOverride:
497506
RTCLog(@"Audio route changed: Override");
@@ -704,6 +713,7 @@ - (BOOL)unconfigureWebRTCSession:(NSError **)outError {
704713
}
705714
RTCLog(@"Unconfiguring audio session for WebRTC.");
706715
[self setActive:NO error:outError];
716+
self.isRecordingEnabled = NO;
707717

708718
return YES;
709719
}
@@ -916,4 +926,14 @@ - (void)notifyFailedToSetActive:(BOOL)active error:(NSError *)error {
916926
}
917927
}
918928

929+
- (void)notifyWillRecord {
930+
for (auto delegate : self.delegates) {
931+
SEL sel = @selector(audioSessionWillRecord:);
932+
if ([delegate respondsToSelector:sel]) {
933+
[delegate audioSessionWillRecord:self];
934+
}
935+
}
936+
}
937+
938+
919939
@end

sdk/objc/components/audio/RTCNativeAudioSessionDelegateAdapter.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,9 @@ - (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession
8686
_observer->OnChangedOutputVolume();
8787
}
8888

89+
- (void)audioSessionWillRecord:(RTC_OBJC_TYPE(RTCAudioSession) *)session {
90+
// re-trigger audio unit init, by using interrupt ended callback
91+
_observer->OnAudioWillRecord();
92+
}
93+
8994
@end

sdk/objc/native/src/audio/audio_device_ios.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
145145
void OnValidRouteChange() override;
146146
void OnCanPlayOrRecordChange(bool can_play_or_record) override;
147147
void OnChangedOutputVolume() override;
148+
void OnAudioWillRecord() override;
148149

149150
// VoiceProcessingAudioUnitObserver methods.
150151
OSStatus OnDeliverRecordedData(AudioUnitRenderActionFlags* flags,
@@ -172,9 +173,10 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
172173
void HandleSampleRateChange(float sample_rate);
173174
void HandlePlayoutGlitchDetected();
174175
void HandleOutputVolumeChange();
176+
void HandleAudioWillRecord();
175177

176-
// Uses current |playout_parameters_| and |record_parameters_| to inform the
177-
// audio device buffer (ADB) about our internal audio parameters.
178+
// Uses current |playout_parameters_| and |record_parameters_| to inform
179+
// the audio device buffer (ADB) about our internal audio parameters.
178180
void UpdateAudioDeviceBuffer();
179181

180182
// Since the preferred audio parameters are only hints to the OS, the actual

sdk/objc/native/src/audio/audio_device_ios.mm

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
kMessageTypeCanPlayOrRecordChange,
6969
kMessageTypePlayoutGlitchDetected,
7070
kMessageOutputVolumeChange,
71+
kMessageTypeAudioWillRecord,
7172
};
7273

7374
using ios::CheckAndLogError;
@@ -367,6 +368,11 @@ static void LogDeviceInfo() {
367368
thread_->Post(RTC_FROM_HERE, this, kMessageOutputVolumeChange);
368369
}
369370

371+
void AudioDeviceIOS::OnAudioWillRecord() {
372+
RTC_DCHECK(thread_);
373+
thread_->Post(RTC_FROM_HERE, this, kMessageTypeAudioWillRecord);
374+
}
375+
370376
OSStatus AudioDeviceIOS::OnDeliverRecordedData(AudioUnitRenderActionFlags* flags,
371377
const AudioTimeStamp* time_stamp,
372378
UInt32 bus_number,
@@ -452,7 +458,7 @@ static void LogDeviceInfo() {
452458
// Exclude extreme delta values since they do most likely not correspond
453459
// to a real glitch. Instead, the most probable cause is that a headset
454460
// has been plugged in or out. There are more direct ways to detect
455-
// audio device changes (see HandleValidRouteChange()) but experiments
461+
// audio device changes (see ValidRouteChange()) but experiments
456462
// show that using it leads to more complex implementations.
457463
// TODO(henrika): more tests might be needed to come up with an even
458464
// better upper limit.
@@ -497,6 +503,8 @@ static void LogDeviceInfo() {
497503
case kMessageOutputVolumeChange:
498504
HandleOutputVolumeChange();
499505
break;
506+
case kMessageTypeAudioWillRecord:
507+
HandleAudioWillRecord();
500508
}
501509
}
502510

@@ -660,6 +668,61 @@ static void LogDeviceInfo() {
660668
last_output_volume_change_time_ = rtc::TimeMillis();
661669
}
662670

671+
void AudioDeviceIOS::HandleAudioWillRecord() {
672+
RTC_DCHECK_RUN_ON(&thread_checker_);
673+
674+
LOGI() << "HandleAudioWillRecord";
675+
676+
// If we don't have an audio unit yet, or the audio unit is uninitialized,
677+
// there is no work to do.
678+
if (!audio_unit_ || audio_unit_->GetState() < VoiceProcessingAudioUnit::kInitialized) {
679+
return;
680+
}
681+
682+
// The audio unit is already initialized or started.
683+
// Check to see if the sample rate or buffer size has changed.
684+
RTC_OBJC_TYPE(RTCAudioSession)* session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
685+
const double session_sample_rate = session.sampleRate;
686+
687+
// Extra sanity check to ensure that the new sample rate is valid.
688+
if (session_sample_rate <= 0.0) {
689+
RTCLogError(@"Sample rate is invalid: %f", session_sample_rate);
690+
LOGI() << "Sample rate is invalid " << session_sample_rate;
691+
return;
692+
}
693+
// We need to adjust our format and buffer sizes.
694+
// The stream format is about to be changed and it requires that we first
695+
// stop and uninitialize the audio unit to deallocate its resources.
696+
RTCLog(@"Stopping and uninitializing audio unit to adjust buffers.");
697+
bool restart_audio_unit = false;
698+
if (audio_unit_->GetState() == VoiceProcessingAudioUnit::kStarted) {
699+
audio_unit_->Stop();
700+
restart_audio_unit = true;
701+
PrepareForNewStart();
702+
}
703+
if (audio_unit_->GetState() == VoiceProcessingAudioUnit::kInitialized) {
704+
audio_unit_->Uninitialize();
705+
}
706+
707+
// Allocate new buffers given the new stream format.
708+
SetupAudioBuffersForActiveAudioSession();
709+
710+
// Initialize the audio unit again with the new sample rate.
711+
RTC_DCHECK_EQ(playout_parameters_.sample_rate(), session_sample_rate);
712+
if (!audio_unit_->Initialize(session_sample_rate)) {
713+
RTCLogError(@"Failed to initialize the audio unit with sample rate: %f", session_sample_rate);
714+
return;
715+
}
716+
717+
// Restart the audio unit if it was already running.
718+
if (restart_audio_unit && !audio_unit_->Start()) {
719+
RTCLogError(@"Failed to start audio unit with sample rate: %f", session_sample_rate);
720+
return;
721+
}
722+
723+
LOGI() << "Successfully enabled audio unit for recording.";
724+
}
725+
663726
void AudioDeviceIOS::UpdateAudioDeviceBuffer() {
664727
LOGI() << "UpdateAudioDevicebuffer";
665728
// AttachAudioBuffer() is called at construction by the main class but check

sdk/objc/native/src/audio/audio_session_observer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class AudioSessionObserver {
3232

3333
virtual void OnChangedOutputVolume() = 0;
3434

35+
virtual void OnAudioWillRecord() = 0;
36+
3537
protected:
3638
virtual ~AudioSessionObserver() {}
3739
};

sdk/objc/native/src/audio/voice_processing_audio_unit.mm

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,6 @@ static OSStatus GetAGCState(AudioUnit audio_unit, UInt32* enabled) {
112112
return false;
113113
}
114114

115-
// Enable input on the input scope of the input element.
116-
UInt32 enable_input = 1;
117-
result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
118-
kAudioUnitScope_Input, kInputBus, &enable_input,
119-
sizeof(enable_input));
120-
if (result != noErr) {
121-
DisposeAudioUnit();
122-
RTCLogError(@"Failed to enable input on input scope of input element. "
123-
"Error=%ld.",
124-
(long)result);
125-
return false;
126-
}
127-
128115
// Enable output on the output scope of the output element.
129116
UInt32 enable_output = 1;
130117
result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
@@ -205,6 +192,27 @@ static OSStatus GetAGCState(AudioUnit audio_unit, UInt32* enabled) {
205192
LogStreamDescription(format);
206193
#endif
207194

195+
// Enable input on the input scope of the input element.
196+
// keep it disabled if audio session is configured for playback only
197+
AVAudioSession* session = [AVAudioSession sharedInstance];
198+
UInt32 enable_input = 0;
199+
if ([session.category isEqualToString: AVAudioSessionCategoryPlayAndRecord] ||
200+
[session.category isEqualToString: AVAudioSessionCategoryRecord]) {
201+
enable_input = 1;
202+
}
203+
RTCLog(@"Initializing AudioUnit, category=%@, enable_input=%d", session.category, enable_input);
204+
// LOGI() << "Initialize" << session.category << ", enable_input=" << enable_input;
205+
result = AudioUnitSetProperty(vpio_unit_, kAudioOutputUnitProperty_EnableIO,
206+
kAudioUnitScope_Input, kInputBus, &enable_input,
207+
sizeof(enable_input));
208+
if (result != noErr) {
209+
DisposeAudioUnit();
210+
RTCLogError(@"Failed to enable input on input scope of input element. "
211+
"Error=%ld.",
212+
(long)result);
213+
return false;
214+
}
215+
208216
// Set the format on the output scope of the input element/bus.
209217
result =
210218
AudioUnitSetProperty(vpio_unit_, kAudioUnitProperty_StreamFormat,

0 commit comments

Comments
 (0)