Add latency to remote source api.

Latency corresponds to base minimum delay on NetEq.

Bug: webrtc:10287
Change-Id: I538d202e3e4fe07b779c46bf560e2fde38e0468e
Reviewed-on: https://webrtc-review.googlesource.com/c/121704
Commit-Queue: Ruslan Burakov <kuddai@google.com>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26724}
This commit is contained in:
Ruslan Burakov 2019-02-16 02:07:05 +01:00 committed by Commit Bot
parent 86f09741a7
commit 7ea460593c
13 changed files with 354 additions and 4 deletions

View File

@ -32,4 +32,8 @@ const cricket::AudioOptions AudioSourceInterface::options() const {
return {};
}
double AudioSourceInterface::GetLatency() const {
return 0.0;
}
} // namespace webrtc

View File

@ -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) {}

View File

@ -120,12 +120,14 @@ bool FakeVoiceMediaChannel::AddRecvStream(const StreamParams& sp) {
if (!RtpHelper<VoiceMediaChannel>::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<VoiceMediaChannel>::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<int> 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;
}

View File

@ -349,6 +349,10 @@ class FakeVoiceMediaChannel : public RtpHelper<VoiceMediaChannel> {
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<int> GetBaseMinimumPlayoutDelayMs(
uint32_t ssrc) const override;
bool GetStats(VoiceMediaInfo* info) override;
void SetRawAudioSink(
@ -384,6 +388,7 @@ class FakeVoiceMediaChannel : public RtpHelper<VoiceMediaChannel> {
std::vector<AudioCodec> recv_codecs_;
std::vector<AudioCodec> send_codecs_;
std::map<uint32_t, double> output_scalings_;
std::map<uint32_t, int> output_delays_;
std::vector<DtmfInfo> dtmf_info_queue_;
AudioOptions options_;
std::map<uint32_t, std::unique_ptr<VoiceChannelAudioSink>> local_sinks_;

View File

@ -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<int> 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.

View File

@ -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<webrtc::RtpSource> GetSources() const override {
return std::vector<webrtc::RtpSource>();
@ -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

View File

@ -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<webrtc::RtpSource> 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<uint32_t> 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<int> 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

View File

@ -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<int> 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<webrtc::AudioSinkInterface> default_sink_;
// Default SSRC to use for RTCP receiver reports in case of no signaled

View File

@ -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";

View File

@ -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);
}
}

View File

@ -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<AudioDataProxy>(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<void>(
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<int>(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<int> 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<void>(RTC_FROM_HERE, [&] {
media_channel_->SetBaseMinimumPlayoutDelayMs(ssrc_.value(), delay_ms);
});
return true;
}
absl::optional<int> RemoteAudioSource::GetDelayMs() const {
if (!media_channel_ || !ssrc_) {
return absl::nullopt;
}
return worker_thread_->Invoke<absl::optional<int>>(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));

View File

@ -46,6 +46,8 @@ class RemoteAudioSource : public Notifier<AudioSourceInterface>,
// 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<AudioSourceInterface>,
void OnMessage(rtc::Message* msg) override;
bool SetDelayMs(int delay_ms);
absl::optional<int> GetDelayMs() const;
rtc::Thread* const main_thread_;
rtc::Thread* const worker_thread_;
std::list<AudioObserver*> audio_observers_;
rtc::CriticalSection sink_lock_;
std::list<AudioTrackSinkInterface*> sinks_;
SourceState state_;
// Media channel and ssrc together uniqely identify audio stream.
cricket::VoiceMediaChannel* media_channel_ = nullptr;
absl::optional<uint32_t> ssrc_;
absl::optional<double> cached_latency_;
};
} // namespace webrtc

View File

@ -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<int> delay_ms; // In milliseconds.
double latency_s = 0.5; // In seconds.
rtc::scoped_refptr<RemoteAudioSource> source =
new rtc::RefCountedObject<RemoteAudioSource>(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<RemoteAudioSource> source =
new rtc::RefCountedObject<RemoteAudioSource>(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<int> 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) {