diff --git a/api/media_stream_interface.cc b/api/media_stream_interface.cc index 73566c4a2f..b55a8403c6 100644 --- a/api/media_stream_interface.cc +++ b/api/media_stream_interface.cc @@ -32,4 +32,8 @@ const cricket::AudioOptions AudioSourceInterface::options() const { return {}; } +double AudioSourceInterface::GetLatency() const { + return 0.0; +} + } // namespace webrtc diff --git a/api/media_stream_interface.h b/api/media_stream_interface.h index b0774809e4..e5203611ad 100644 --- a/api/media_stream_interface.h +++ b/api/media_stream_interface.h @@ -201,6 +201,12 @@ class AudioSourceInterface : public MediaSourceInterface { // be applied in the track in a way that does not affect clones of the track. virtual void SetVolume(double volume) {} + // Sets the minimum latency of the remote source until audio playout. Actual + // observered latency may differ depending on the source. |latency| is in the + // range of [0.0, 10.0] seconds. + virtual void SetLatency(double latency) {} + virtual double GetLatency() const; + // Registers/unregisters observers to the audio source. virtual void RegisterAudioObserver(AudioObserver* observer) {} virtual void UnregisterAudioObserver(AudioObserver* observer) {} diff --git a/media/base/fake_media_engine.cc b/media/base/fake_media_engine.cc index 70fabc53cd..829790278a 100644 --- a/media/base/fake_media_engine.cc +++ b/media/base/fake_media_engine.cc @@ -120,12 +120,14 @@ bool FakeVoiceMediaChannel::AddRecvStream(const StreamParams& sp) { if (!RtpHelper::AddRecvStream(sp)) return false; output_scalings_[sp.first_ssrc()] = 1.0; + output_delays_[sp.first_ssrc()] = 0; return true; } bool FakeVoiceMediaChannel::RemoveRecvStream(uint32_t ssrc) { if (!RtpHelper::RemoveRecvStream(ssrc)) return false; output_scalings_.erase(ssrc); + output_delays_.erase(ssrc); return true; } bool FakeVoiceMediaChannel::CanInsertDtmf() { @@ -163,6 +165,23 @@ bool FakeVoiceMediaChannel::GetOutputVolume(uint32_t ssrc, double* volume) { *volume = output_scalings_[ssrc]; return true; } +bool FakeVoiceMediaChannel::SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, + int delay_ms) { + if (output_delays_.find(ssrc) == output_delays_.end()) { + return false; + } else { + output_delays_[ssrc] = delay_ms; + return true; + } +} +absl::optional FakeVoiceMediaChannel::GetBaseMinimumPlayoutDelayMs( + uint32_t ssrc) const { + const auto it = output_delays_.find(ssrc); + if (it != output_delays_.end()) { + return it->second; + } + return absl::nullopt; +} bool FakeVoiceMediaChannel::GetStats(VoiceMediaInfo* info) { return false; } diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h index a41d4e4226..f5865806f0 100644 --- a/media/base/fake_media_engine.h +++ b/media/base/fake_media_engine.h @@ -349,6 +349,10 @@ class FakeVoiceMediaChannel : public RtpHelper { bool SetOutputVolume(uint32_t ssrc, double volume) override; bool GetOutputVolume(uint32_t ssrc, double* volume); + bool SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, int delay_ms) override; + absl::optional GetBaseMinimumPlayoutDelayMs( + uint32_t ssrc) const override; + bool GetStats(VoiceMediaInfo* info) override; void SetRawAudioSink( @@ -384,6 +388,7 @@ class FakeVoiceMediaChannel : public RtpHelper { std::vector recv_codecs_; std::vector send_codecs_; std::map output_scalings_; + std::map output_delays_; std::vector dtmf_info_queue_; AudioOptions options_; std::map> local_sinks_; diff --git a/media/base/media_channel.h b/media/base/media_channel.h index d20de7571f..5f5f32dbfc 100644 --- a/media/base/media_channel.h +++ b/media/base/media_channel.h @@ -737,6 +737,13 @@ class VoiceMediaChannel : public MediaChannel { AudioSource* source) = 0; // Set speaker output volume of the specified ssrc. virtual bool SetOutputVolume(uint32_t ssrc, double volume) = 0; + // Set base minimum delay of the receive stream with specified ssrc. + // Base minimum delay sets lower bound on minimum delay value which + // determines minimum delay until audio playout. + // Returns false if there is no stream with given ssrc. + virtual bool SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, int delay_ms) = 0; + virtual absl::optional GetBaseMinimumPlayoutDelayMs( + uint32_t ssrc) const = 0; // Returns if the telephone-event has been negotiated. virtual bool CanInsertDtmf() = 0; // Send a DTMF |event|. The DTMF out-of-band signal will be used. diff --git a/media/engine/fake_webrtc_call.h b/media/engine/fake_webrtc_call.h index a0147e9ee0..7df6b52919 100644 --- a/media/engine/fake_webrtc_call.h +++ b/media/engine/fake_webrtc_call.h @@ -94,6 +94,9 @@ class FakeAudioReceiveStream final : public webrtc::AudioReceiveStream { float gain() const { return gain_; } bool DeliverRtp(const uint8_t* packet, size_t length, int64_t packet_time_us); bool started() const { return started_; } + int base_mininum_playout_delay_ms() const { + return base_mininum_playout_delay_ms_; + } private: // webrtc::AudioReceiveStream implementation. @@ -105,11 +108,11 @@ class FakeAudioReceiveStream final : public webrtc::AudioReceiveStream { void SetSink(webrtc::AudioSinkInterface* sink) override; void SetGain(float gain) override; bool SetBaseMinimumPlayoutDelayMs(int delay_ms) override { - base_minimum_playout_delay_ms_ = delay_ms; + base_mininum_playout_delay_ms_ = delay_ms; return true; } int GetBaseMinimumPlayoutDelayMs() const override { - return base_minimum_playout_delay_ms_; + return base_mininum_playout_delay_ms_; } std::vector GetSources() const override { return std::vector(); @@ -123,7 +126,7 @@ class FakeAudioReceiveStream final : public webrtc::AudioReceiveStream { float gain_ = 1.0f; rtc::Buffer last_packet_; bool started_ = false; - int base_minimum_playout_delay_ms_ = 0; + int base_mininum_playout_delay_ms_ = 0; }; class FakeVideoSendStream final diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc index a055ae9ed3..1f1b585cd7 100644 --- a/media/engine/webrtc_voice_engine.cc +++ b/media/engine/webrtc_voice_engine.cc @@ -1173,6 +1173,29 @@ class WebRtcVoiceMediaChannel::WebRtcAudioReceiveStream { playout_ = playout; } + bool SetBaseMinimumPlayoutDelayMs(int delay_ms) { + RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); + RTC_DCHECK(stream_); + if (stream_->SetBaseMinimumPlayoutDelayMs(delay_ms)) { + // Memorize only valid delay because during stream recreation it will be + // passed to the constructor and it must be valid value. + config_.jitter_buffer_min_delay_ms = delay_ms; + return true; + } else { + RTC_LOG(LS_ERROR) << "Failed to SetBaseMinimumPlayoutDelayMs" + << " on AudioReceiveStream on SSRC=" + << config_.rtp.remote_ssrc + << " with delay_ms=" << delay_ms; + return false; + } + } + + int GetBaseMinimumPlayoutDelayMs() const { + RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); + RTC_DCHECK(stream_); + return stream_->GetBaseMinimumPlayoutDelayMs(); + } + std::vector GetSources() { RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); RTC_DCHECK(stream_); @@ -1952,6 +1975,44 @@ bool WebRtcVoiceMediaChannel::SetOutputVolume(uint32_t ssrc, double volume) { return true; } +bool WebRtcVoiceMediaChannel::SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, + int delay_ms) { + RTC_DCHECK(worker_thread_checker_.CalledOnValidThread()); + std::vector ssrcs(1, ssrc); + // SSRC of 0 represents the default receive stream. + if (ssrc == 0) { + default_recv_base_minimum_delay_ms_ = delay_ms; + ssrcs = unsignaled_recv_ssrcs_; + } + for (uint32_t ssrc : ssrcs) { + const auto it = recv_streams_.find(ssrc); + if (it == recv_streams_.end()) { + RTC_LOG(LS_WARNING) << "SetBaseMinimumPlayoutDelayMs: no recv stream " + << ssrc; + return false; + } + it->second->SetBaseMinimumPlayoutDelayMs(delay_ms); + RTC_LOG(LS_INFO) << "SetBaseMinimumPlayoutDelayMs() to " << delay_ms + << " for recv stream with ssrc " << ssrc; + } + return true; +} + +absl::optional WebRtcVoiceMediaChannel::GetBaseMinimumPlayoutDelayMs( + uint32_t ssrc) const { + // SSRC of 0 represents the default receive stream. + if (ssrc == 0) { + return default_recv_base_minimum_delay_ms_; + } + + const auto it = recv_streams_.find(ssrc); + + if (it != recv_streams_.end()) { + return it->second->GetBaseMinimumPlayoutDelayMs(); + } + return absl::nullopt; +} + bool WebRtcVoiceMediaChannel::CanInsertDtmf() { return dtmf_payload_type_.has_value() && send_; } @@ -2047,6 +2108,7 @@ void WebRtcVoiceMediaChannel::OnPacketReceived(rtc::CopyOnWriteBuffer* packet, RTC_DCHECK_GE(kMaxUnsignaledRecvStreams, unsignaled_recv_ssrcs_.size()); SetOutputVolume(ssrc, default_recv_volume_); + SetBaseMinimumPlayoutDelayMs(ssrc, default_recv_base_minimum_delay_ms_); // The default sink can only be attached to one stream at a time, so we hook // it up to the *latest* unsignaled stream we've seen, in order to support the diff --git a/media/engine/webrtc_voice_engine.h b/media/engine/webrtc_voice_engine.h index 4ee61a16a7..830887d1b1 100644 --- a/media/engine/webrtc_voice_engine.h +++ b/media/engine/webrtc_voice_engine.h @@ -196,6 +196,10 @@ class WebRtcVoiceMediaChannel final : public VoiceMediaChannel, // SSRC=0 will apply the new volume to current and future unsignaled streams. bool SetOutputVolume(uint32_t ssrc, double volume) override; + bool SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, int delay_ms) override; + absl::optional GetBaseMinimumPlayoutDelayMs( + uint32_t ssrc) const override; + bool CanInsertDtmf() override; bool InsertDtmf(uint32_t ssrc, int event, int duration) override; @@ -295,6 +299,10 @@ class WebRtcVoiceMediaChannel final : public VoiceMediaChannel, // Volume for unsignaled streams, which may be set before the stream exists. double default_recv_volume_ = 1.0; + + // Delay for unsignaled streams, which may be set before the stream exists. + int default_recv_base_minimum_delay_ms_ = 0; + // Sink for latest unsignaled stream - may be set before the stream exists. std::unique_ptr default_sink_; // Default SSRC to use for RTCP receiver reports in case of no signaled diff --git a/media/engine/webrtc_voice_engine_unittest.cc b/media/engine/webrtc_voice_engine_unittest.cc index a3ccee94b9..4b1a5287b6 100644 --- a/media/engine/webrtc_voice_engine_unittest.cc +++ b/media/engine/webrtc_voice_engine_unittest.cc @@ -3168,6 +3168,65 @@ TEST_F(WebRtcVoiceEngineTestFake, SetOutputVolumeUnsignaledRecvStream) { EXPECT_DOUBLE_EQ(4, GetRecvStream(kSsrcX).gain()); } +TEST_F(WebRtcVoiceEngineTestFake, BaseMinimumPlayoutDelayMs) { + EXPECT_TRUE(SetupChannel()); + EXPECT_FALSE(channel_->SetBaseMinimumPlayoutDelayMs(kSsrcY, 200)); + EXPECT_FALSE(channel_->GetBaseMinimumPlayoutDelayMs(kSsrcY).has_value()); + + cricket::StreamParams stream; + stream.ssrcs.push_back(kSsrcY); + EXPECT_TRUE(channel_->AddRecvStream(stream)); + EXPECT_EQ(0, GetRecvStream(kSsrcY).base_mininum_playout_delay_ms()); + EXPECT_TRUE(channel_->SetBaseMinimumPlayoutDelayMs(kSsrcY, 300)); + EXPECT_EQ(300, GetRecvStream(kSsrcY).base_mininum_playout_delay_ms()); +} + +TEST_F(WebRtcVoiceEngineTestFake, + BaseMinimumPlayoutDelayMsUnsignaledRecvStream) { + // Here base minimum delay is abbreviated to delay in comments for shortness. + EXPECT_TRUE(SetupChannel()); + + // Spawn an unsignaled stream by sending a packet - delay should be 0. + DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame)); + EXPECT_EQ(0, channel_->GetBaseMinimumPlayoutDelayMs(kSsrc1).value_or(-1)); + // Check that it doesn't provide default values for unknown ssrc. + EXPECT_FALSE(channel_->GetBaseMinimumPlayoutDelayMs(kSsrcY).has_value()); + + // Check that default value for unsignaled streams is 0. + EXPECT_EQ(0, channel_->GetBaseMinimumPlayoutDelayMs(kSsrc0).value_or(-1)); + + // Should remember the delay 100 which will be set on new unsignaled streams, + // and also set the delay to 100 on existing unsignaled streams. + EXPECT_TRUE(channel_->SetBaseMinimumPlayoutDelayMs(kSsrc0, 100)); + EXPECT_EQ(100, channel_->GetBaseMinimumPlayoutDelayMs(kSsrc0).value_or(-1)); + // Check that it doesn't provide default values for unknown ssrc. + EXPECT_FALSE(channel_->GetBaseMinimumPlayoutDelayMs(kSsrcY).has_value()); + + // Spawn an unsignaled stream by sending a packet - delay should be 100. + unsigned char pcmuFrame2[sizeof(kPcmuFrame)]; + memcpy(pcmuFrame2, kPcmuFrame, sizeof(kPcmuFrame)); + rtc::SetBE32(&pcmuFrame2[8], kSsrcX); + DeliverPacket(pcmuFrame2, sizeof(pcmuFrame2)); + EXPECT_EQ(100, channel_->GetBaseMinimumPlayoutDelayMs(kSsrcX).value_or(-1)); + + // Setting delay with SSRC=0 should affect all unsignaled streams. + EXPECT_TRUE(channel_->SetBaseMinimumPlayoutDelayMs(kSsrc0, 300)); + if (kMaxUnsignaledRecvStreams > 1) { + EXPECT_EQ(300, channel_->GetBaseMinimumPlayoutDelayMs(kSsrc1).value_or(-1)); + } + EXPECT_EQ(300, channel_->GetBaseMinimumPlayoutDelayMs(kSsrcX).value_or(-1)); + + // Setting delay on an individual stream affects only that. + EXPECT_TRUE(channel_->SetBaseMinimumPlayoutDelayMs(kSsrcX, 400)); + if (kMaxUnsignaledRecvStreams > 1) { + EXPECT_EQ(300, channel_->GetBaseMinimumPlayoutDelayMs(kSsrc1).value_or(-1)); + } + EXPECT_EQ(400, channel_->GetBaseMinimumPlayoutDelayMs(kSsrcX).value_or(-1)); + EXPECT_EQ(300, channel_->GetBaseMinimumPlayoutDelayMs(kSsrc0).value_or(-1)); + // Check that it doesn't provide default values for unknown ssrc. + EXPECT_FALSE(channel_->GetBaseMinimumPlayoutDelayMs(kSsrcY).has_value()); +} + TEST_F(WebRtcVoiceEngineTestFake, SetsSyncGroupFromStreamId) { const uint32_t kAudioSsrc = 123; const std::string kStreamId = "AvSyncLabel"; diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc index af2e55483c..385c3d09eb 100644 --- a/pc/peer_connection.cc +++ b/pc/peer_connection.cc @@ -4670,7 +4670,7 @@ void PeerConnection::UpdateRemoteSendersList( FindSenderInfo(*current_senders, kDefaultStreamId, default_sender_id); if (!default_sender_info) { current_senders->push_back( - RtpSenderInfo(kDefaultStreamId, default_sender_id, 0)); + RtpSenderInfo(kDefaultStreamId, default_sender_id, /*ssrc=*/0)); OnRemoteSenderAdded(current_senders->back(), media_type); } } diff --git a/pc/remote_audio_source.cc b/pc/remote_audio_source.cc index 0155ec614a..63944c6d83 100644 --- a/pc/remote_audio_source.cc +++ b/pc/remote_audio_source.cc @@ -20,11 +20,17 @@ #include "rtc_base/constructor_magic.h" #include "rtc_base/location.h" #include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" #include "rtc_base/thread.h" #include "rtc_base/thread_checker.h" namespace webrtc { +namespace { +constexpr int kDefaultLatency = 0; +constexpr int kRoundToZeroThresholdMs = 10; +} // namespace + // This proxy is passed to the underlying media engine to receive audio data as // they come in. The data will then be passed back up to the RemoteAudioSource // which will fan it out to all the sinks that have been added to it. @@ -64,6 +70,13 @@ void RemoteAudioSource::Start(cricket::VoiceMediaChannel* media_channel, uint32_t ssrc) { RTC_DCHECK_RUN_ON(main_thread_); RTC_DCHECK(media_channel); + // Check that there are no consecutive start calls. + RTC_DCHECK(!media_channel_ && !ssrc_); + + // Remember media channel ssrc pair for latency calls. + media_channel_ = media_channel; + ssrc_ = ssrc; + // Register for callbacks immediately before AddSink so that we always get // notified when a channel goes out of scope (signaled when "AudioDataProxy" // is destroyed). @@ -71,12 +84,22 @@ void RemoteAudioSource::Start(cricket::VoiceMediaChannel* media_channel, media_channel->SetRawAudioSink(ssrc, absl::make_unique(this)); }); + + // Trying to apply cached latency for the audio stream. + if (cached_latency_) { + SetLatency(cached_latency_.value()); + } } void RemoteAudioSource::Stop(cricket::VoiceMediaChannel* media_channel, uint32_t ssrc) { RTC_DCHECK_RUN_ON(main_thread_); RTC_DCHECK(media_channel); + + // Assume that audio stream is no longer present for latency calls. + media_channel_ = nullptr; + ssrc_ = absl::nullopt; + worker_thread_->Invoke( RTC_FROM_HERE, [&] { media_channel->SetRawAudioSink(ssrc, nullptr); }); } @@ -99,6 +122,53 @@ void RemoteAudioSource::SetVolume(double volume) { } } +void RemoteAudioSource::SetLatency(double latency) { + RTC_DCHECK_GE(latency, 0); + RTC_DCHECK_LE(latency, 10); + + int delay_ms = rtc::dchecked_cast(latency * 1000); + // In NetEq 0 delay has special meaning of being unconstrained value that is + // why we round delay to 0 if it is small enough during conversion from + // latency. + if (delay_ms <= kRoundToZeroThresholdMs) { + delay_ms = 0; + } + + cached_latency_ = latency; + SetDelayMs(delay_ms); +} + +double RemoteAudioSource::GetLatency() const { + absl::optional delay_ms = GetDelayMs(); + + if (delay_ms) { + return delay_ms.value() / 1000.0; + } else { + return cached_latency_.value_or(kDefaultLatency); + } +} + +bool RemoteAudioSource::SetDelayMs(int delay_ms) { + if (!media_channel_ || !ssrc_) { + return false; + } + + worker_thread_->Invoke(RTC_FROM_HERE, [&] { + media_channel_->SetBaseMinimumPlayoutDelayMs(ssrc_.value(), delay_ms); + }); + return true; +} + +absl::optional RemoteAudioSource::GetDelayMs() const { + if (!media_channel_ || !ssrc_) { + return absl::nullopt; + } + + return worker_thread_->Invoke>(RTC_FROM_HERE, [&] { + return media_channel_->GetBaseMinimumPlayoutDelayMs(ssrc_.value()); + }); +} + void RemoteAudioSource::RegisterAudioObserver(AudioObserver* observer) { RTC_DCHECK(observer != NULL); RTC_DCHECK(!absl::c_linear_search(audio_observers_, observer)); diff --git a/pc/remote_audio_source.h b/pc/remote_audio_source.h index 399e7e3a44..0773c38dcf 100644 --- a/pc/remote_audio_source.h +++ b/pc/remote_audio_source.h @@ -46,6 +46,8 @@ class RemoteAudioSource : public Notifier, // AudioSourceInterface implementation. void SetVolume(double volume) override; + void SetLatency(double latency) override; + double GetLatency() const override; void RegisterAudioObserver(AudioObserver* observer) override; void UnregisterAudioObserver(AudioObserver* observer) override; @@ -63,12 +65,19 @@ class RemoteAudioSource : public Notifier, void OnMessage(rtc::Message* msg) override; + bool SetDelayMs(int delay_ms); + absl::optional GetDelayMs() const; + rtc::Thread* const main_thread_; rtc::Thread* const worker_thread_; std::list audio_observers_; rtc::CriticalSection sink_lock_; std::list sinks_; SourceState state_; + // Media channel and ssrc together uniqely identify audio stream. + cricket::VoiceMediaChannel* media_channel_ = nullptr; + absl::optional ssrc_; + absl::optional cached_latency_; }; } // namespace webrtc diff --git a/pc/rtp_sender_receiver_unittest.cc b/pc/rtp_sender_receiver_unittest.cc index b17246049a..1f015f6699 100644 --- a/pc/rtp_sender_receiver_unittest.cc +++ b/pc/rtp_sender_receiver_unittest.cc @@ -48,6 +48,7 @@ #include "pc/dtls_srtp_transport.h" #include "pc/local_audio_source.h" #include "pc/media_stream.h" +#include "pc/remote_audio_source.h" #include "pc/rtp_receiver.h" #include "pc/rtp_sender.h" #include "pc/rtp_transport_internal.h" @@ -522,6 +523,103 @@ TEST_F(RtpSenderReceiverTest, RemoteAudioTrackSetVolume) { DestroyAudioRtpReceiver(); } +TEST_F(RtpSenderReceiverTest, RemoteAudioSourceLatencyCaching) { + absl::optional delay_ms; // In milliseconds. + double latency_s = 0.5; // In seconds. + rtc::scoped_refptr source = + new rtc::RefCountedObject(rtc::Thread::Current()); + + // Check default value. + EXPECT_DOUBLE_EQ(source->GetLatency(), 0.0); + + // Check caching behaviour. + source->SetLatency(latency_s); + EXPECT_DOUBLE_EQ(source->GetLatency(), latency_s); + + // Check that cached value applied on the start. + source->Start(voice_media_channel_, kAudioSsrc); + delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc); + EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0); + + // Check that setting latency changes delay. + latency_s = 0.8; + source->SetLatency(latency_s); + delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc); + EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0); + EXPECT_DOUBLE_EQ(latency_s, source->GetLatency()); + + // Check that if underlying delay is changed then remote source will reflect + // it. + delay_ms = 300; + voice_media_channel_->SetBaseMinimumPlayoutDelayMs(kAudioSsrc, + delay_ms.value()); + EXPECT_DOUBLE_EQ(source->GetLatency(), delay_ms.value() / 1000.0); + + // Check that after stop we get last cached value. + source->Stop(voice_media_channel_, kAudioSsrc); + EXPECT_DOUBLE_EQ(latency_s, source->GetLatency()); + + // Check that if we start source again with new ssrc then cached value is + // applied. + source->Start(voice_media_channel_, kAudioSsrc2); + delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc2); + EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0); + + // Check rounding behavior. + source->SetLatency(2 / 1000.0); + delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc2); + EXPECT_EQ(0, delay_ms.value_or(-1)); + EXPECT_DOUBLE_EQ(0, source->GetLatency()); +} + +TEST_F(RtpSenderReceiverTest, RemoteAudioSourceLatencyNoCaching) { + int delay_ms = 300; // In milliseconds. + rtc::scoped_refptr source = + new rtc::RefCountedObject(rtc::Thread::Current()); + + // Set it to value different from default zero. + voice_media_channel_->SetBaseMinimumPlayoutDelayMs(kAudioSsrc, delay_ms); + + // Check that calling GetLatency on the source that hasn't been started yet + // won't trigger caching. + EXPECT_DOUBLE_EQ(source->GetLatency(), 0); + source->Start(voice_media_channel_, kAudioSsrc); + EXPECT_DOUBLE_EQ(source->GetLatency(), delay_ms / 1000.0); +} + +TEST_F(RtpSenderReceiverTest, RemoteAudioTrackSetLatency) { + CreateAudioRtpReceiver(); + + absl::optional delay_ms; // In milliseconds. + double latency_s = 0.5; // In seconds. + audio_track_->GetSource()->SetLatency(latency_s); + delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc); + EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0); + + // Disabling the track should take no effect on previously set value. + audio_track_->set_enabled(false); + delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc); + EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0); + + // When the track is disabled, we still should be able to set latency. + latency_s = 0.3; + audio_track_->GetSource()->SetLatency(latency_s); + delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc); + EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0); + + // Enabling the track should take no effect on previously set value. + audio_track_->set_enabled(true); + delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc); + EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0); + + // We still should be able to change latency. + latency_s = 0.0; + audio_track_->GetSource()->SetLatency(latency_s); + delay_ms = voice_media_channel_->GetBaseMinimumPlayoutDelayMs(kAudioSsrc); + EXPECT_EQ(0, delay_ms.value_or(-1)); + EXPECT_DOUBLE_EQ(latency_s, delay_ms.value_or(0) / 1000.0); +} + // Test that the media channel isn't enabled for sending if the audio sender // doesn't have both a track and SSRC. TEST_F(RtpSenderReceiverTest, AudioSenderWithoutTrackAndSsrc) {