Skip to content

Commit 40e3fc5

Browse files
cloudwebrtcdavidliu
authored andcommitted
Merge pull request #2 from webrtc-sdk/listen-only-audio-session
allow listen-only mode in AudioUnit, adjust when category changes
1 parent 2ce9e01 commit 40e3fc5

9 files changed

+124
-16
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
@@ -102,6 +102,9 @@ RTC_OBJC_EXPORT
102102
- (void)audioSession:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession
103103
audioUnitStartFailedWithError:(NSError *)error;
104104

105+
/** Called when audio session changed from output-only to input & output */
106+
- (void)audioSessionWillRecord:(RTC_OBJC_TYPE(RTCAudioSession) *)audioSession;
107+
105108
@end
106109

107110
/** 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
@@ -114,6 +114,8 @@ - (instancetype)initWithAudioSession:(id)audioSession {
114114
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
115115
context:(__bridge void *)RTC_OBJC_TYPE(RTCAudioSession).class];
116116

117+
self.isRecordingEnabled = [_session.category isEqualToString:AVAudioSessionCategoryPlayAndRecord];
118+
117119
RTCLog(@"RTC_OBJC_TYPE(RTCAudioSession) (%p): init.", self);
118120
}
119121
return self;
@@ -542,6 +544,13 @@ - (void)handleRouteChangeNotification:(NSNotification *)notification {
542544
case AVAudioSessionRouteChangeReasonCategoryChange:
543545
RTCLog(@"Audio route changed: CategoryChange to :%@",
544546
self.session.category);
547+
if (!self.isRecordingEnabled && [self.session.category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) {
548+
self.isRecordingEnabled = true;
549+
[self notifyWillRecord];
550+
}
551+
if (self.isRecordingEnabled && [self.session.category isEqualToString:AVAudioSessionCategoryPlayback]) {
552+
self.isRecordingEnabled = false;
553+
}
545554
break;
546555
case AVAudioSessionRouteChangeReasonOverride:
547556
RTCLog(@"Audio route changed: Override");
@@ -773,6 +782,7 @@ - (BOOL)unconfigureWebRTCSession:(NSError **)outError {
773782
}
774783
RTCLog(@"Unconfiguring audio session for WebRTC.");
775784
[self setActive:NO error:outError];
785+
self.isRecordingEnabled = NO;
776786

777787
return YES;
778788
}
@@ -997,4 +1007,14 @@ - (void)notifyFailedToSetActive:(BOOL)active error:(NSError *)error {
9971007
}
9981008
}
9991009

1010+
- (void)notifyWillRecord {
1011+
for (auto delegate : self.delegates) {
1012+
SEL sel = @selector(audioSessionWillRecord:);
1013+
if ([delegate respondsToSelector:sel]) {
1014+
[delegate audioSessionWillRecord:self];
1015+
}
1016+
}
1017+
}
1018+
1019+
10001020
@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: 2 additions & 0 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,6 +173,7 @@ class AudioDeviceIOS : public AudioDeviceGeneric,
172173
void HandleSampleRateChange();
173174
void HandlePlayoutGlitchDetected();
174175
void HandleOutputVolumeChange();
176+
void HandleAudioWillRecord();
175177

176178
// Uses current `playout_parameters_` and `record_parameters_` to inform the
177179
// audio device buffer (ADB) about our internal audio parameters.

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;
@@ -373,6 +374,11 @@ static void LogDeviceInfo() {
373374
thread_->Post(RTC_FROM_HERE, this, kMessageOutputVolumeChange);
374375
}
375376

377+
void AudioDeviceIOS::OnAudioWillRecord() {
378+
RTC_DCHECK(thread_);
379+
thread_->Post(RTC_FROM_HERE, this, kMessageTypeAudioWillRecord);
380+
}
381+
376382
OSStatus AudioDeviceIOS::OnDeliverRecordedData(AudioUnitRenderActionFlags* flags,
377383
const AudioTimeStamp* time_stamp,
378384
UInt32 bus_number,
@@ -458,7 +464,7 @@ static void LogDeviceInfo() {
458464
// Exclude extreme delta values since they do most likely not correspond
459465
// to a real glitch. Instead, the most probable cause is that a headset
460466
// has been plugged in or out. There are more direct ways to detect
461-
// audio device changes (see HandleValidRouteChange()) but experiments
467+
// audio device changes (see ValidRouteChange()) but experiments
462468
// show that using it leads to more complex implementations.
463469
// TODO(henrika): more tests might be needed to come up with an even
464470
// better upper limit.
@@ -503,6 +509,8 @@ static void LogDeviceInfo() {
503509
case kMessageOutputVolumeChange:
504510
HandleOutputVolumeChange();
505511
break;
512+
case kMessageTypeAudioWillRecord:
513+
HandleAudioWillRecord();
506514
}
507515
}
508516

@@ -672,6 +680,61 @@ static void LogDeviceInfo() {
672680
last_output_volume_change_time_ = rtc::TimeMillis();
673681
}
674682

683+
void AudioDeviceIOS::HandleAudioWillRecord() {
684+
RTC_DCHECK_RUN_ON(&thread_checker_);
685+
686+
LOGI() << "HandleAudioWillRecord";
687+
688+
// If we don't have an audio unit yet, or the audio unit is uninitialized,
689+
// there is no work to do.
690+
if (!audio_unit_ || audio_unit_->GetState() < VoiceProcessingAudioUnit::kInitialized) {
691+
return;
692+
}
693+
694+
// The audio unit is already initialized or started.
695+
// Check to see if the sample rate or buffer size has changed.
696+
RTC_OBJC_TYPE(RTCAudioSession)* session = [RTC_OBJC_TYPE(RTCAudioSession) sharedInstance];
697+
const double session_sample_rate = session.sampleRate;
698+
699+
// Extra sanity check to ensure that the new sample rate is valid.
700+
if (session_sample_rate <= 0.0) {
701+
RTCLogError(@"Sample rate is invalid: %f", session_sample_rate);
702+
LOGI() << "Sample rate is invalid " << session_sample_rate;
703+
return;
704+
}
705+
// We need to adjust our format and buffer sizes.
706+
// The stream format is about to be changed and it requires that we first
707+
// stop and uninitialize the audio unit to deallocate its resources.
708+
RTCLog(@"Stopping and uninitializing audio unit to adjust buffers.");
709+
bool restart_audio_unit = false;
710+
if (audio_unit_->GetState() == VoiceProcessingAudioUnit::kStarted) {
711+
audio_unit_->Stop();
712+
restart_audio_unit = true;
713+
PrepareForNewStart();
714+
}
715+
if (audio_unit_->GetState() == VoiceProcessingAudioUnit::kInitialized) {
716+
audio_unit_->Uninitialize();
717+
}
718+
719+
// Allocate new buffers given the new stream format.
720+
SetupAudioBuffersForActiveAudioSession();
721+
722+
// Initialize the audio unit again with the new sample rate.
723+
RTC_DCHECK_EQ(playout_parameters_.sample_rate(), session_sample_rate);
724+
if (!audio_unit_->Initialize(session_sample_rate)) {
725+
RTCLogError(@"Failed to initialize the audio unit with sample rate: %f", session_sample_rate);
726+
return;
727+
}
728+
729+
// Restart the audio unit if it was already running.
730+
if (restart_audio_unit && !audio_unit_->Start()) {
731+
RTCLogError(@"Failed to start audio unit with sample rate: %f", session_sample_rate);
732+
return;
733+
}
734+
735+
LOGI() << "Successfully enabled audio unit for recording.";
736+
}
737+
675738
void AudioDeviceIOS::UpdateAudioDeviceBuffer() {
676739
LOGI() << "UpdateAudioDevicebuffer";
677740
// 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
@@ -111,19 +111,6 @@ static OSStatus GetAGCState(AudioUnit audio_unit, UInt32* enabled) {
111111
return false;
112112
}
113113

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

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

0 commit comments

Comments
 (0)