在WebRTC官方SDK中,如果为RTCPeerConnection添加了AudioTrack,WebRTC就会尝试去初始化音频的输入输出。
Audio通道建立成功之后WebRTC会自动完成声音的采集传输播放。
RTCAudioSession
提供了一个useManualAudio
属性,将它设置为true
,那么音频的输入输出开关将由isAudioEnabled
属性控制。
但是,isAudioEnabled
只能同时控制音频的输入输出,无法分开控制。
我们的产品现在需要在静音麦克风的功能,也就是保持输出打开,但是关闭输入。
目前官方没有提供API,底层相关代码还没有实现
// sdk/objc/native/src/audio/audio_device_ios.mm
int32_t AudioDeviceIOS::SetMicrophoneMute(bool enable) {
RTC_NOTREACHED() << "Not implemented";
return -1;
}
分析源码,可以在VoiceProcessingAudioUnit
中找到Audio Unit
的使用。
OnDeliverRecordedData
回调函数拿到音频数据后通过VoiceProcessingAudioUnitObserver
通知给AudioDeviceIOS
// sdk/objc/native/src/audio/voice_processing_audio_unit.mm
OSStatus VoiceProcessingAudioUnit::OnDeliverRecordedData(
void* in_ref_con,
AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* io_data) {
VoiceProcessingAudioUnit* audio_unit =
static_cast<VoiceProcessingAudioUnit*>(in_ref_con);
return audio_unit->NotifyDeliverRecordedData(flags, time_stamp, bus_number,
num_frames, io_data);
}
// sdk/objc/native/src/audio/audio_device_ios.mm
OSStatus AudioDeviceIOS::OnDeliverRecordedData(AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* time_stamp,
UInt32 bus_number,
UInt32 num_frames,
AudioBufferList* /* io_data */) {
// 在处理音频之前,通过一个参数来控制麦克风是否静音,如果静音则直接退出函数,这样就不会有声音被传输
if (is_microphone_mute_) return noErr;
// 处理音频数据,这里WebRTC没有使用传输的`io_data`,而是重新去`render`了一次数据,详情可以看源码
fine_audio_buffer_->DeliverRecordedData(record_audio_buffer_, kFixedRecordDelayEstimate);
return noErr;
}
其实要静音麦克风也很简单,只需要忽略掉输入数据,或者将输入全部替换成0即可,这边采用了忽略数据的方式。
上面代码新增了一个is_microphone_mute_
变量,这个变量将会像之前的isAudioEnabled
属性一样,通过RTCAudioSession
对外提供接口。
下面就是将is_microphone_mute_
变量暴露给外部,我们只要模仿isAudioEnabled
就可以轻松实现目的。
在RTCAudioSession
中实现isMicrophoneMute
属性
// sdk/objc/components/audio/RTCAudioSession.mm
- (void)setIsMicrophoneMute:(BOOL)isMicrophoneMute {
@synchronized(self) {
if (_isMicrophoneMute == isMicrophoneMute) {
return;
}
_isMicrophoneMute = isMicrophoneMute;
}
[self notifyDidChangeMicrophoneMute];
}
- (BOOL)isMicrophoneMute {
@synchronized(self) {
return _isMicrophoneMute;
}
}
- (void)notifyDidChangeMicrophoneMute {
for (auto delegate : self.delegates) {
SEL sel = @selector(audioSession:didChangeMicrophoneMute:);
if ([delegate respondsToSelector:sel]) {
[delegate audioSession:self didChangeMicrophoneMute:self.isMicrophoneMute];
}
}
}
setIsMicrophoneMute
将通过RTCNativeAudioSessionDelegateAdapter
把消息传递给AudioDeviceIOS
// sdk/objc/components/audio/RTCNativeAudioSessionDelegateAdapter.mm
- (void)audioSession:(RTCAudioSession *)session
didChangeMicrophoneMute:(BOOL)isMicrophoneMute {
_observer->OnMicrophoneMuteChange(isMicrophoneMute);
}
在AudioDeviceIOS
中实现具体逻辑,AudioDeviceIOS::OnMicrophoneMuteChange
将消息发送给线程来处理
// sdk/objc/native/src/audio/audio_device_ios.mm
void AudioDeviceIOS::OnMicrophoneMuteChange(bool is_microphone_mute) {
RTC_DCHECK(thread_);
thread_->Post(RTC_FROM_HERE,
this,
kMessageTypeMicrophoneMuteChange,
new rtc::TypedMessageData<bool>(is_microphone_mute));
}
void AudioDeviceIOS::OnMessage(rtc::Message* msg) {
switch (msg->message_id) {
// ...
case kMessageTypeMicrophoneMuteChange: {
rtc::TypedMessageData<bool>* data = static_cast<rtc::TypedMessageData<bool>*>(msg->pdata);
HandleMicrophoneMuteChange(data->data());
delete data;
break;
}
}
}
void AudioDeviceIOS::HandleMicrophoneMuteChange(bool is_microphone_mute) {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTCLog(@"Handling MicrophoneMute change to %d", is_microphone_mute);
is_microphone_mute_ = is_microphone_mute;
}
至此,麦克风的静音就完成了