| /* |
| * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "audio/voip/voip_core.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "api/audio_codecs/audio_format.h" |
| #include "rtc_base/logging.h" |
| |
| namespace webrtc { |
| |
| namespace { |
| |
| // For Windows, use specific enum type to initialize default audio device as |
| // defined in AudioDeviceModule::WindowsDeviceType. |
| #if defined(WEBRTC_WIN) |
| constexpr AudioDeviceModule::WindowsDeviceType kAudioDeviceId = |
| AudioDeviceModule::WindowsDeviceType::kDefaultCommunicationDevice; |
| #else |
| constexpr uint16_t kAudioDeviceId = 0; |
| #endif // defined(WEBRTC_WIN) |
| |
| // Maximum value range limit on ChannelId. This can be increased without any |
| // side effect and only set at this moderate value for better readability for |
| // logging. |
| static constexpr int kMaxChannelId = 100000; |
| |
| } // namespace |
| |
| VoipCore::VoipCore(const Environment& env, |
| rtc::scoped_refptr<AudioEncoderFactory> encoder_factory, |
| rtc::scoped_refptr<AudioDecoderFactory> decoder_factory, |
| rtc::scoped_refptr<AudioDeviceModule> audio_device_module, |
| rtc::scoped_refptr<AudioProcessing> audio_processing) |
| : env_(env), |
| encoder_factory_(std::move(encoder_factory)), |
| decoder_factory_(std::move(decoder_factory)), |
| audio_processing_(std::move(audio_processing)), |
| audio_device_module_(std::move(audio_device_module)) { |
| audio_mixer_ = AudioMixerImpl::Create(); |
| |
| // AudioTransportImpl depends on audio mixer and audio processing instances. |
| audio_transport_ = std::make_unique<AudioTransportImpl>( |
| audio_mixer_.get(), audio_processing_.get(), nullptr); |
| } |
| |
| bool VoipCore::InitializeIfNeeded() { |
| // `audio_device_module_` internally owns a lock and the whole logic here |
| // needs to be executed atomically once using another lock in VoipCore. |
| // Further changes in this method will need to make sure that no deadlock is |
| // introduced in the future. |
| MutexLock lock(&lock_); |
| |
| if (initialized_) { |
| return true; |
| } |
| |
| // Initialize ADM. |
| if (audio_device_module_->Init() != 0) { |
| RTC_LOG(LS_ERROR) << "Failed to initialize the ADM."; |
| return false; |
| } |
| |
| // Note that failures on initializing default recording/speaker devices are |
| // not considered to be fatal here. In certain case, caller may not care about |
| // recording device functioning (e.g webinar where only speaker is available). |
| // It's also possible that there are other audio devices available that may |
| // work. |
| |
| // Initialize default speaker device. |
| if (audio_device_module_->SetPlayoutDevice(kAudioDeviceId) != 0) { |
| RTC_LOG(LS_WARNING) << "Unable to set playout device."; |
| } |
| if (audio_device_module_->InitSpeaker() != 0) { |
| RTC_LOG(LS_WARNING) << "Unable to access speaker."; |
| } |
| |
| // Initialize default recording device. |
| if (audio_device_module_->SetRecordingDevice(kAudioDeviceId) != 0) { |
| RTC_LOG(LS_WARNING) << "Unable to set recording device."; |
| } |
| if (audio_device_module_->InitMicrophone() != 0) { |
| RTC_LOG(LS_WARNING) << "Unable to access microphone."; |
| } |
| |
| // Set number of channels on speaker device. |
| bool available = false; |
| if (audio_device_module_->StereoPlayoutIsAvailable(&available) != 0) { |
| RTC_LOG(LS_WARNING) << "Unable to query stereo playout."; |
| } |
| if (audio_device_module_->SetStereoPlayout(available) != 0) { |
| RTC_LOG(LS_WARNING) << "Unable to set mono/stereo playout mode."; |
| } |
| |
| // Set number of channels on recording device. |
| available = false; |
| if (audio_device_module_->StereoRecordingIsAvailable(&available) != 0) { |
| RTC_LOG(LS_WARNING) << "Unable to query stereo recording."; |
| } |
| if (audio_device_module_->SetStereoRecording(available) != 0) { |
| RTC_LOG(LS_WARNING) << "Unable to set stereo recording mode."; |
| } |
| |
| if (audio_device_module_->RegisterAudioCallback(audio_transport_.get()) != |
| 0) { |
| RTC_LOG(LS_WARNING) << "Unable to register audio callback."; |
| } |
| |
| initialized_ = true; |
| |
| return true; |
| } |
| |
| ChannelId VoipCore::CreateChannel(Transport* transport, |
| std::optional<uint32_t> local_ssrc) { |
| ChannelId channel_id; |
| |
| // Set local ssrc to random if not set by caller. |
| if (!local_ssrc) { |
| Random random(rtc::TimeMicros()); |
| local_ssrc = random.Rand<uint32_t>(); |
| } |
| |
| rtc::scoped_refptr<AudioChannel> channel = |
| rtc::make_ref_counted<AudioChannel>(env_, transport, local_ssrc.value(), |
| audio_mixer_.get(), decoder_factory_); |
| |
| { |
| MutexLock lock(&lock_); |
| |
| channel_id = static_cast<ChannelId>(next_channel_id_); |
| channels_[channel_id] = channel; |
| next_channel_id_++; |
| if (next_channel_id_ >= kMaxChannelId) { |
| next_channel_id_ = 0; |
| } |
| } |
| |
| // Set ChannelId in audio channel for logging/debugging purpose. |
| channel->SetId(channel_id); |
| |
| return channel_id; |
| } |
| |
| VoipResult VoipCore::ReleaseChannel(ChannelId channel_id) { |
| // Destroy channel outside of the lock. |
| rtc::scoped_refptr<AudioChannel> channel; |
| |
| bool no_channels_after_release = false; |
| |
| { |
| MutexLock lock(&lock_); |
| |
| auto iter = channels_.find(channel_id); |
| if (iter != channels_.end()) { |
| channel = std::move(iter->second); |
| channels_.erase(iter); |
| } |
| |
| no_channels_after_release = channels_.empty(); |
| } |
| |
| VoipResult status_code = VoipResult::kOk; |
| if (!channel) { |
| RTC_LOG(LS_WARNING) << "Channel " << channel_id << " not found"; |
| status_code = VoipResult::kInvalidArgument; |
| } |
| |
| if (no_channels_after_release) { |
| // TODO(bugs.webrtc.org/11581): unclear if we still need to clear `channel` |
| // here. |
| channel = nullptr; |
| |
| // Make sure to stop playout on ADM if it is playing. |
| if (audio_device_module_->Playing()) { |
| if (audio_device_module_->StopPlayout() != 0) { |
| RTC_LOG(LS_WARNING) << "StopPlayout failed"; |
| status_code = VoipResult::kInternal; |
| } |
| } |
| } |
| |
| return status_code; |
| } |
| |
| rtc::scoped_refptr<AudioChannel> VoipCore::GetChannel(ChannelId channel_id) { |
| rtc::scoped_refptr<AudioChannel> channel; |
| { |
| MutexLock lock(&lock_); |
| auto iter = channels_.find(channel_id); |
| if (iter != channels_.end()) { |
| channel = iter->second; |
| } |
| } |
| if (!channel) { |
| RTC_LOG(LS_ERROR) << "Channel " << channel_id << " not found"; |
| } |
| return channel; |
| } |
| |
| bool VoipCore::UpdateAudioTransportWithSenders() { |
| std::vector<AudioSender*> audio_senders; |
| |
| // Gather a list of audio channel that are currently sending along with |
| // highest sampling rate and channel numbers to configure into audio |
| // transport. |
| int max_sampling_rate = 8000; |
| size_t max_num_channels = 1; |
| { |
| MutexLock lock(&lock_); |
| // Reserve to prevent run time vector re-allocation. |
| audio_senders.reserve(channels_.size()); |
| for (auto kv : channels_) { |
| rtc::scoped_refptr<AudioChannel>& channel = kv.second; |
| if (channel->IsSendingMedia()) { |
| auto encoder_format = channel->GetEncoderFormat(); |
| if (!encoder_format) { |
| RTC_LOG(LS_ERROR) |
| << "channel " << channel->GetId() << " encoder is not set"; |
| continue; |
| } |
| audio_senders.push_back(channel->GetAudioSender()); |
| max_sampling_rate = |
| std::max(max_sampling_rate, encoder_format->clockrate_hz); |
| max_num_channels = |
| std::max(max_num_channels, encoder_format->num_channels); |
| } |
| } |
| } |
| |
| audio_transport_->UpdateAudioSenders(audio_senders, max_sampling_rate, |
| max_num_channels); |
| |
| // Depending on availability of senders, turn on or off ADM recording. |
| if (!audio_senders.empty()) { |
| // Initialize audio device module and default device if needed. |
| if (!InitializeIfNeeded()) { |
| return false; |
| } |
| |
| if (!audio_device_module_->Recording()) { |
| if (audio_device_module_->InitRecording() != 0) { |
| RTC_LOG(LS_ERROR) << "InitRecording failed"; |
| return false; |
| } |
| if (audio_device_module_->StartRecording() != 0) { |
| RTC_LOG(LS_ERROR) << "StartRecording failed"; |
| return false; |
| } |
| } |
| } else { |
| if (audio_device_module_->Recording() && |
| audio_device_module_->StopRecording() != 0) { |
| RTC_LOG(LS_ERROR) << "StopRecording failed"; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| VoipResult VoipCore::StartSend(ChannelId channel_id) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| if (!channel->StartSend()) { |
| return VoipResult::kFailedPrecondition; |
| } |
| |
| return UpdateAudioTransportWithSenders() ? VoipResult::kOk |
| : VoipResult::kInternal; |
| } |
| |
| VoipResult VoipCore::StopSend(ChannelId channel_id) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| channel->StopSend(); |
| |
| return UpdateAudioTransportWithSenders() ? VoipResult::kOk |
| : VoipResult::kInternal; |
| } |
| |
| VoipResult VoipCore::StartPlayout(ChannelId channel_id) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| if (channel->IsPlaying()) { |
| return VoipResult::kOk; |
| } |
| |
| if (!channel->StartPlay()) { |
| return VoipResult::kFailedPrecondition; |
| } |
| |
| // Initialize audio device module and default device if needed. |
| if (!InitializeIfNeeded()) { |
| return VoipResult::kInternal; |
| } |
| |
| if (!audio_device_module_->Playing()) { |
| if (audio_device_module_->InitPlayout() != 0) { |
| RTC_LOG(LS_ERROR) << "InitPlayout failed"; |
| return VoipResult::kInternal; |
| } |
| if (audio_device_module_->StartPlayout() != 0) { |
| RTC_LOG(LS_ERROR) << "StartPlayout failed"; |
| return VoipResult::kInternal; |
| } |
| } |
| |
| return VoipResult::kOk; |
| } |
| |
| VoipResult VoipCore::StopPlayout(ChannelId channel_id) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| channel->StopPlay(); |
| |
| return VoipResult::kOk; |
| } |
| |
| VoipResult VoipCore::ReceivedRTPPacket( |
| ChannelId channel_id, |
| rtc::ArrayView<const uint8_t> rtp_packet) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| channel->ReceivedRTPPacket(rtp_packet); |
| |
| return VoipResult::kOk; |
| } |
| |
| VoipResult VoipCore::ReceivedRTCPPacket( |
| ChannelId channel_id, |
| rtc::ArrayView<const uint8_t> rtcp_packet) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| channel->ReceivedRTCPPacket(rtcp_packet); |
| |
| return VoipResult::kOk; |
| } |
| |
| VoipResult VoipCore::SetSendCodec(ChannelId channel_id, |
| int payload_type, |
| const SdpAudioFormat& encoder_format) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| auto encoder = encoder_factory_->Create(env_, encoder_format, |
| {.payload_type = payload_type}); |
| channel->SetEncoder(payload_type, encoder_format, std::move(encoder)); |
| |
| return VoipResult::kOk; |
| } |
| |
| VoipResult VoipCore::SetReceiveCodecs( |
| ChannelId channel_id, |
| const std::map<int, SdpAudioFormat>& decoder_specs) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| channel->SetReceiveCodecs(decoder_specs); |
| |
| return VoipResult::kOk; |
| } |
| |
| VoipResult VoipCore::RegisterTelephoneEventType(ChannelId channel_id, |
| int rtp_payload_type, |
| int sample_rate_hz) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| channel->RegisterTelephoneEventType(rtp_payload_type, sample_rate_hz); |
| |
| return VoipResult::kOk; |
| } |
| |
| VoipResult VoipCore::SendDtmfEvent(ChannelId channel_id, |
| DtmfEvent dtmf_event, |
| int duration_ms) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| return (channel->SendTelephoneEvent(static_cast<int>(dtmf_event), duration_ms) |
| ? VoipResult::kOk |
| : VoipResult::kFailedPrecondition); |
| } |
| |
| VoipResult VoipCore::GetIngressStatistics(ChannelId channel_id, |
| IngressStatistics& ingress_stats) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| ingress_stats = channel->GetIngressStatistics(); |
| |
| return VoipResult::kOk; |
| } |
| |
| VoipResult VoipCore::GetChannelStatistics(ChannelId channel_id, |
| ChannelStatistics& channel_stats) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| channel_stats = channel->GetChannelStatistics(); |
| |
| return VoipResult::kOk; |
| } |
| |
| VoipResult VoipCore::SetInputMuted(ChannelId channel_id, bool enable) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| channel->SetMute(enable); |
| |
| return VoipResult::kOk; |
| } |
| |
| VoipResult VoipCore::GetInputVolumeInfo(ChannelId channel_id, |
| VolumeInfo& input_volume) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| input_volume.audio_level = channel->GetInputAudioLevel(); |
| input_volume.total_energy = channel->GetInputTotalEnergy(); |
| input_volume.total_duration = channel->GetInputTotalDuration(); |
| |
| return VoipResult::kOk; |
| } |
| |
| VoipResult VoipCore::GetOutputVolumeInfo(ChannelId channel_id, |
| VolumeInfo& output_volume) { |
| rtc::scoped_refptr<AudioChannel> channel = GetChannel(channel_id); |
| |
| if (!channel) { |
| return VoipResult::kInvalidArgument; |
| } |
| |
| output_volume.audio_level = channel->GetOutputAudioLevel(); |
| output_volume.total_energy = channel->GetOutputTotalEnergy(); |
| output_volume.total_duration = channel->GetOutputTotalDuration(); |
| |
| return VoipResult::kOk; |
| } |
| |
| } // namespace webrtc |