732 lines
25 KiB
C++
732 lines
25 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
// Original author: ekr@rtfm.com
|
|
|
|
#include "logging.h"
|
|
#include "nss.h"
|
|
#include "ssl.h"
|
|
|
|
#include "api/audio/builtin_audio_processing_builder.h"
|
|
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
|
|
#include "api/environment/environment_factory.h"
|
|
#include "api/scoped_refptr.h"
|
|
#include "AudioSegment.h"
|
|
#include "Canonicals.h"
|
|
#include "modules/audio_device/include/fake_audio_device.h"
|
|
#include "modules/audio_mixer/audio_mixer_impl.h"
|
|
#include "modules/audio_processing/include/audio_processing.h"
|
|
#include "mozilla/Mutex.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/SpinEventLoopUntil.h"
|
|
#include "MediaConduitInterface.h"
|
|
#include "MediaPipeline.h"
|
|
#include "MediaPipelineFilter.h"
|
|
#include "MediaTrackGraph.h"
|
|
#include "MediaTrackListener.h"
|
|
#include "TaskQueueWrapper.h"
|
|
#include "mtransport_test_utils.h"
|
|
#include "SharedBuffer.h"
|
|
#include "MediaTransportHandler.h"
|
|
#include "WebrtcCallWrapper.h"
|
|
#include "WebrtcEnvironmentWrapper.h"
|
|
#include "PeerConnectionCtx.h"
|
|
|
|
#define GTEST_HAS_RTTI 0
|
|
#include "gtest/gtest.h"
|
|
|
|
using namespace mozilla;
|
|
MOZ_MTLOG_MODULE("transportbridge")
|
|
|
|
static MtransportTestUtils* test_utils;
|
|
|
|
namespace {
|
|
class MainAsCurrent : public TaskQueueWrapper<DeletionPolicy::NonBlocking> {
|
|
public:
|
|
MainAsCurrent()
|
|
: TaskQueueWrapper(
|
|
TaskQueue::Create(do_AddRef(GetMainThreadSerialEventTarget()),
|
|
"MainAsCurrentTaskQueue"),
|
|
"MainAsCurrent"_ns),
|
|
mSetter(this) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
}
|
|
|
|
~MainAsCurrent() = default;
|
|
|
|
private:
|
|
CurrentTaskQueueSetter mSetter;
|
|
};
|
|
|
|
class FakeAudioTrack : public ProcessedMediaTrack {
|
|
public:
|
|
FakeAudioTrack()
|
|
: ProcessedMediaTrack(44100, MediaSegment::AUDIO, nullptr),
|
|
mMutex("Fake AudioTrack") {
|
|
NS_NewTimerWithFuncCallback(
|
|
getter_AddRefs(mTimer), FakeAudioTrackGenerateData, this, 20,
|
|
nsITimer::TYPE_REPEATING_SLACK,
|
|
"FakeAudioTrack::FakeAudioTrackGenerateData", test_utils->sts_target());
|
|
}
|
|
|
|
void Destroy() override {
|
|
MutexAutoLock lock(mMutex);
|
|
MOZ_RELEASE_ASSERT(!mMainThreadDestroyed);
|
|
mMainThreadDestroyed = true;
|
|
mTimer->Cancel();
|
|
mTimer = nullptr;
|
|
}
|
|
|
|
void QueueSetAutoend(bool) override {}
|
|
|
|
void AddListener(MediaTrackListener* aListener) override {
|
|
MutexAutoLock lock(mMutex);
|
|
MOZ_RELEASE_ASSERT(!mListener);
|
|
mListener = aListener;
|
|
}
|
|
|
|
RefPtr<GenericPromise> RemoveListener(
|
|
MediaTrackListener* aListener) override {
|
|
MutexAutoLock lock(mMutex);
|
|
MOZ_RELEASE_ASSERT(mListener == aListener);
|
|
mListener = nullptr;
|
|
return GenericPromise::CreateAndResolve(true, __func__);
|
|
}
|
|
|
|
void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override {}
|
|
|
|
uint32_t NumberOfChannels() const override { return NUM_CHANNELS; }
|
|
|
|
private:
|
|
Mutex mMutex MOZ_UNANNOTATED;
|
|
MediaTrackListener* mListener = nullptr;
|
|
nsCOMPtr<nsITimer> mTimer;
|
|
int mCount = 0;
|
|
|
|
static const int AUDIO_BUFFER_SIZE = 1600;
|
|
static const int NUM_CHANNELS = 2;
|
|
static void FakeAudioTrackGenerateData(nsITimer* timer, void* closure) {
|
|
auto t = static_cast<FakeAudioTrack*>(closure);
|
|
MutexAutoLock lock(t->mMutex);
|
|
if (t->mMainThreadDestroyed) {
|
|
return;
|
|
}
|
|
CheckedInt<size_t> bufferSize(sizeof(int16_t));
|
|
bufferSize *= NUM_CHANNELS;
|
|
bufferSize *= AUDIO_BUFFER_SIZE;
|
|
RefPtr<SharedBuffer> samples = SharedBuffer::Create(bufferSize);
|
|
int16_t* data = reinterpret_cast<int16_t*>(samples->Data());
|
|
for (int i = 0; i < (AUDIO_BUFFER_SIZE * NUM_CHANNELS); i++) {
|
|
// saw tooth audio sample
|
|
data[i] = ((t->mCount % 8) * 4000) - (7 * 4000) / 2;
|
|
t->mCount++;
|
|
}
|
|
|
|
AudioSegment segment;
|
|
AutoTArray<const int16_t*, 1> channels;
|
|
channels.AppendElement(data);
|
|
segment.AppendFrames(samples.forget(), channels, AUDIO_BUFFER_SIZE,
|
|
PRINCIPAL_HANDLE_NONE);
|
|
|
|
if (t->mListener) {
|
|
t->mListener->NotifyQueuedChanges(nullptr, 0, segment);
|
|
}
|
|
}
|
|
};
|
|
|
|
template <typename Function>
|
|
void RunOnSts(Function&& aFunction) {
|
|
MOZ_ALWAYS_SUCCEEDS(test_utils->SyncDispatchToSTS(
|
|
NS_NewRunnableFunction(__func__, [&] { aFunction(); })));
|
|
}
|
|
|
|
class LoopbackTransport : public MediaTransportHandler {
|
|
public:
|
|
LoopbackTransport() : MediaTransportHandler(nullptr) {
|
|
RunOnSts([&] {
|
|
SetState("mux", TransportLayer::TS_INIT);
|
|
SetRtcpState("mux", TransportLayer::TS_INIT);
|
|
SetState("non-mux", TransportLayer::TS_INIT);
|
|
SetRtcpState("non-mux", TransportLayer::TS_INIT);
|
|
});
|
|
}
|
|
|
|
static void InitAndConnect(LoopbackTransport& client,
|
|
LoopbackTransport& server) {
|
|
client.Connect(&server);
|
|
server.Connect(&client);
|
|
}
|
|
|
|
void Connect(LoopbackTransport* peer) { peer_ = peer; }
|
|
|
|
void Shutdown() { peer_ = nullptr; }
|
|
|
|
RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) override {
|
|
return nullptr;
|
|
}
|
|
|
|
void ClearIceLog() override {}
|
|
void EnterPrivateMode() override {}
|
|
void ExitPrivateMode() override {}
|
|
|
|
void CreateIceCtx(const std::string& aName) override {}
|
|
|
|
nsresult SetIceConfig(const nsTArray<dom::RTCIceServer>& aIceServers,
|
|
dom::RTCIceTransportPolicy aIcePolicy) override {
|
|
return NS_OK;
|
|
}
|
|
|
|
void Destroy() override {}
|
|
|
|
// We will probably be able to move the proxy lookup stuff into
|
|
// this class once we move mtransport to its own process.
|
|
void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) override {}
|
|
|
|
void EnsureProvisionalTransport(const std::string& aTransportId,
|
|
const std::string& aLocalUfrag,
|
|
const std::string& aLocalPwd,
|
|
int aComponentCount) override {}
|
|
|
|
void SetTargetForDefaultLocalAddressLookup(const std::string& aTargetIp,
|
|
uint16_t aTargetPort) override {}
|
|
|
|
// We set default-route-only as late as possible because it depends on what
|
|
// capture permissions have been granted on the window, which could easily
|
|
// change between Init (ie; when the PC is created) and StartIceGathering
|
|
// (ie; when we set the local description).
|
|
void StartIceGathering(bool aDefaultRouteOnly, bool aObfuscateAddresses,
|
|
// TODO: It probably makes sense to look
|
|
// this up internally
|
|
const nsTArray<NrIceStunAddr>& aStunAddrs) override {}
|
|
|
|
void ActivateTransport(
|
|
const std::string& aTransportId, const std::string& aLocalUfrag,
|
|
const std::string& aLocalPwd, size_t aComponentCount,
|
|
const std::string& aUfrag, const std::string& aPassword,
|
|
const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
|
|
SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
|
|
bool aPrivacyRequested) override {}
|
|
|
|
void RemoveTransportsExcept(
|
|
const std::set<std::string>& aTransportIds) override {}
|
|
|
|
void StartIceChecks(bool aIsControlling,
|
|
const std::vector<std::string>& aIceOptions) override {}
|
|
|
|
void AddIceCandidate(const std::string& aTransportId,
|
|
const std::string& aCandidate, const std::string& aUfrag,
|
|
const std::string& aObfuscatedAddress) override {}
|
|
|
|
void UpdateNetworkState(bool aOnline) override {}
|
|
|
|
RefPtr<dom::RTCStatsPromise> GetIceStats(const std::string& aTransportId,
|
|
DOMHighResTimeStamp aNow) override {
|
|
return nullptr;
|
|
}
|
|
|
|
void SendPacket(const std::string& aTransportId,
|
|
MediaPacket&& aPacket) override {
|
|
peer_->LoopbackPacketReceived(aTransportId, aPacket);
|
|
}
|
|
|
|
void SetState(const std::string& aTransportId, TransportLayer::State aState) {
|
|
MediaTransportHandler::OnStateChange(aTransportId, aState);
|
|
}
|
|
|
|
void SetRtcpState(const std::string& aTransportId,
|
|
TransportLayer::State aState) {
|
|
MediaTransportHandler::OnRtcpStateChange(aTransportId, aState);
|
|
}
|
|
|
|
void LoopbackPacketReceived(const std::string& aTransportId,
|
|
const MediaPacket& aPacket) {
|
|
if (aPacket.len() && aPacket.type() == MediaPacket::RTCP) {
|
|
++rtcp_packets_received_;
|
|
}
|
|
SignalPacketReceived(aTransportId, aPacket);
|
|
}
|
|
|
|
int RtcpPacketsReceived() const { return rtcp_packets_received_; }
|
|
|
|
private:
|
|
RefPtr<LoopbackTransport> peer_;
|
|
std::atomic<int> rtcp_packets_received_{0};
|
|
};
|
|
|
|
class TestAgent {
|
|
public:
|
|
explicit TestAgent(const RefPtr<WebrtcEnvironmentWrapper>& aEnvWrapper,
|
|
const RefPtr<SharedWebrtcState>& aSharedState)
|
|
: control_(aSharedState->mCallWorkerThread),
|
|
audio_config_(109, "opus", 48000, 2, false),
|
|
call_(WebrtcCallWrapper::Create(
|
|
aEnvWrapper, mozilla::dom::RTCStatsTimestampMaker::Create(),
|
|
nullptr, aSharedState)),
|
|
audio_conduit_(
|
|
AudioSessionConduit::Create(call_, test_utils->sts_target())),
|
|
transport_(new LoopbackTransport) {
|
|
Unused << WaitFor(InvokeAsync(call_->mCallThread, __func__, [&] {
|
|
audio_conduit_->InitControl(&control_);
|
|
return GenericPromise::CreateAndResolve(true, "TestAgent()");
|
|
}));
|
|
}
|
|
|
|
static void Connect(TestAgent* client, TestAgent* server) {
|
|
LoopbackTransport::InitAndConnect(*client->transport_, *server->transport_);
|
|
}
|
|
|
|
virtual void CreatePipeline(const std::string& aTransportId) = 0;
|
|
|
|
void SetState_s(const std::string& aTransportId,
|
|
TransportLayer::State aState) {
|
|
transport_->SetState(aTransportId, aState);
|
|
}
|
|
|
|
void SetRtcpState_s(const std::string& aTransportId,
|
|
TransportLayer::State aState) {
|
|
transport_->SetRtcpState(aTransportId, aState);
|
|
}
|
|
|
|
void UpdateTransport_s(const std::string& aTransportId,
|
|
UniquePtr<MediaPipelineFilter>&& aFilter) {
|
|
audio_pipeline_->UpdateTransport_s(aTransportId, std::move(aFilter), false);
|
|
}
|
|
|
|
void Stop() {
|
|
MOZ_MTLOG(ML_DEBUG, "Stopping");
|
|
|
|
control_.Update([](auto& aControl) {
|
|
aControl.mTransmitting = false;
|
|
aControl.mReceiving = false;
|
|
});
|
|
}
|
|
|
|
void Shutdown_s() { transport_->Shutdown(); }
|
|
|
|
void Shutdown() {
|
|
if (audio_pipeline_) {
|
|
audio_pipeline_->Shutdown();
|
|
}
|
|
if (audio_conduit_) {
|
|
Unused << WaitFor(audio_conduit_->Shutdown());
|
|
}
|
|
if (call_) {
|
|
call_->Destroy();
|
|
}
|
|
if (audio_track_) {
|
|
audio_track_->Destroy();
|
|
audio_track_ = nullptr;
|
|
}
|
|
|
|
test_utils->SyncDispatchToSTS(WrapRunnable(this, &TestAgent::Shutdown_s));
|
|
}
|
|
|
|
uint32_t GetRemoteSSRC() {
|
|
return audio_conduit_->GetRemoteSSRC().valueOr(0);
|
|
}
|
|
|
|
uint32_t GetLocalSSRC() {
|
|
std::vector<uint32_t> res;
|
|
res = audio_conduit_->GetLocalSSRCs();
|
|
return res.empty() ? 0 : res[0];
|
|
}
|
|
|
|
int GetAudioRtpCountSent() { return audio_pipeline_->RtpPacketsSent(); }
|
|
|
|
int GetAudioRtpCountReceived() {
|
|
return audio_pipeline_->RtpPacketsReceived();
|
|
}
|
|
|
|
int GetAudioRtcpCountSent() { return audio_pipeline_->RtcpPacketsSent(); }
|
|
|
|
int GetAudioRtcpCountReceived() { return transport_->RtcpPacketsReceived(); }
|
|
|
|
protected:
|
|
ConcreteControl control_;
|
|
AudioCodecConfig audio_config_;
|
|
RefPtr<WebrtcCallWrapper> call_;
|
|
RefPtr<AudioSessionConduit> audio_conduit_;
|
|
RefPtr<FakeAudioTrack> audio_track_;
|
|
// TODO(bcampen@mozilla.com): Right now this does not let us test RTCP in
|
|
// both directions; only the sender's RTCP is sent, but the receiver should
|
|
// be sending it too.
|
|
RefPtr<MediaPipeline> audio_pipeline_;
|
|
RefPtr<LoopbackTransport> transport_;
|
|
};
|
|
|
|
class TestAgentSend : public TestAgent {
|
|
public:
|
|
explicit TestAgentSend(const RefPtr<WebrtcEnvironmentWrapper>& aEnvWrapper,
|
|
const RefPtr<SharedWebrtcState>& aSharedState)
|
|
: TestAgent(aEnvWrapper, aSharedState) {
|
|
control_.Update([&](auto& aControl) {
|
|
aControl.mAudioSendCodec = Some(audio_config_);
|
|
});
|
|
audio_track_ = new FakeAudioTrack();
|
|
}
|
|
|
|
virtual void CreatePipeline(const std::string& aTransportId) {
|
|
std::string test_pc;
|
|
|
|
RefPtr<MediaPipelineTransmit> audio_pipeline =
|
|
MediaPipelineTransmit::Create(
|
|
test_pc, transport_, AbstractThread::MainThread(),
|
|
test_utils->sts_target(), false, audio_conduit_);
|
|
Unused << WaitFor(InvokeAsync(call_->mCallThread, __func__, [&] {
|
|
audio_pipeline->InitControl(&control_);
|
|
return GenericPromise::CreateAndResolve(true, __func__);
|
|
}));
|
|
|
|
audio_pipeline->SetSendTrackOverride(audio_track_);
|
|
control_.Update([](auto& aControl) { aControl.mTransmitting = true; });
|
|
audio_pipeline->UpdateTransport_m(aTransportId, nullptr, true);
|
|
audio_pipeline_ = audio_pipeline;
|
|
}
|
|
};
|
|
|
|
class TestAgentReceive : public TestAgent {
|
|
public:
|
|
explicit TestAgentReceive(const RefPtr<WebrtcEnvironmentWrapper>& aEnvWrapper,
|
|
const RefPtr<SharedWebrtcState>& aSharedState)
|
|
: TestAgent(aEnvWrapper, aSharedState) {
|
|
control_.Update([&](auto& aControl) {
|
|
std::vector<AudioCodecConfig> codecs;
|
|
codecs.push_back(audio_config_);
|
|
aControl.mAudioRecvCodecs = codecs;
|
|
});
|
|
}
|
|
|
|
virtual void CreatePipeline(const std::string& aTransportId) {
|
|
std::string test_pc;
|
|
|
|
auto audio_pipeline = MakeRefPtr<MediaPipelineReceiveAudio>(
|
|
test_pc, transport_, AbstractThread::MainThread(),
|
|
test_utils->sts_target(),
|
|
static_cast<AudioSessionConduit*>(audio_conduit_.get()), nullptr,
|
|
TrackingId(), PRINCIPAL_HANDLE_NONE, PrincipalPrivacy::NonPrivate);
|
|
Unused << WaitFor(InvokeAsync(call_->mCallThread, __func__, [&] {
|
|
audio_pipeline->InitControl(&control_);
|
|
return GenericPromise::CreateAndResolve(true, __func__);
|
|
}));
|
|
|
|
control_.Update([](auto& aControl) { aControl.mReceiving = true; });
|
|
audio_pipeline->UpdateTransport_m(aTransportId, std::move(bundle_filter_),
|
|
true);
|
|
audio_pipeline_ = audio_pipeline;
|
|
}
|
|
|
|
void SetBundleFilter(UniquePtr<MediaPipelineFilter>&& filter) {
|
|
bundle_filter_ = std::move(filter);
|
|
}
|
|
|
|
void UpdateTransport_s(const std::string& aTransportId,
|
|
UniquePtr<MediaPipelineFilter>&& filter) {
|
|
audio_pipeline_->UpdateTransport_s(aTransportId, std::move(filter), false);
|
|
}
|
|
|
|
private:
|
|
UniquePtr<MediaPipelineFilter> bundle_filter_;
|
|
};
|
|
|
|
void WaitFor(TimeDuration aDuration) {
|
|
bool done = false;
|
|
NS_DelayedDispatchToCurrentThread(
|
|
NS_NewRunnableFunction(__func__, [&] { done = true; }),
|
|
aDuration.ToMilliseconds());
|
|
SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
|
|
"WaitFor(TimeDuration aDuration)"_ns, [&] { return done; });
|
|
}
|
|
|
|
webrtc::AudioState::Config CreateAudioStateConfig(
|
|
const webrtc::Environment& aEnv) {
|
|
webrtc::AudioState::Config audio_state_config;
|
|
audio_state_config.audio_mixer = webrtc::AudioMixerImpl::Create();
|
|
|
|
webrtc::BuiltinAudioProcessingBuilder audio_processing_builder;
|
|
audio_state_config.audio_processing = audio_processing_builder.Build(aEnv);
|
|
audio_state_config.audio_device_module = new webrtc::FakeAudioDeviceModule();
|
|
return audio_state_config;
|
|
}
|
|
|
|
class MediaPipelineTest : public ::testing::Test {
|
|
public:
|
|
MediaPipelineTest()
|
|
: main_task_queue_(
|
|
WrapUnique<TaskQueueWrapper<DeletionPolicy::NonBlocking>>(
|
|
new MainAsCurrent())),
|
|
env_wrapper_(WebrtcEnvironmentWrapper::Create(
|
|
mozilla::dom::RTCStatsTimestampMaker::Create())),
|
|
shared_state_(MakeAndAddRef<SharedWebrtcState>(
|
|
AbstractThread::MainThread(),
|
|
CreateAudioStateConfig(env_wrapper_->Environment()),
|
|
already_AddRefed(
|
|
webrtc::CreateBuiltinAudioDecoderFactory().release()),
|
|
WrapUnique(new webrtc::MozTrialsConfig()))),
|
|
p1_(env_wrapper_, shared_state_),
|
|
p2_(env_wrapper_, shared_state_) {}
|
|
|
|
~MediaPipelineTest() {
|
|
p1_.Shutdown();
|
|
p2_.Shutdown();
|
|
}
|
|
|
|
static void SetUpTestCase() {
|
|
test_utils = new MtransportTestUtils();
|
|
NSS_NoDB_Init(nullptr);
|
|
NSS_SetDomesticPolicy();
|
|
}
|
|
|
|
// Setup transport.
|
|
void InitTransports() {
|
|
test_utils->SyncDispatchToSTS(
|
|
WrapRunnableNM(&TestAgent::Connect, &p2_, &p1_));
|
|
}
|
|
|
|
// Verify RTP and RTCP
|
|
void TestAudioSend(bool aIsRtcpMux,
|
|
UniquePtr<MediaPipelineFilter>&& initialFilter = nullptr,
|
|
UniquePtr<MediaPipelineFilter>&& refinedFilter = nullptr,
|
|
unsigned int ms_until_filter_update = 500,
|
|
unsigned int ms_of_traffic_after_answer = 10000) {
|
|
bool bundle = !!(initialFilter);
|
|
// We do not support testing bundle without rtcp mux, since that doesn't
|
|
// make any sense.
|
|
ASSERT_FALSE(!aIsRtcpMux && bundle);
|
|
|
|
p2_.SetBundleFilter(std::move(initialFilter));
|
|
|
|
// Setup transport flows
|
|
InitTransports();
|
|
|
|
std::string transportId = aIsRtcpMux ? "mux" : "non-mux";
|
|
p1_.CreatePipeline(transportId);
|
|
p2_.CreatePipeline(transportId);
|
|
|
|
// Set state of transports to CONNECTING. MediaPipeline doesn't really care
|
|
// about this transition, but we're trying to simluate what happens in a
|
|
// real case.
|
|
RunOnSts([&] {
|
|
p1_.SetState_s(transportId, TransportLayer::TS_CONNECTING);
|
|
p1_.SetRtcpState_s(transportId, TransportLayer::TS_CONNECTING);
|
|
p2_.SetState_s(transportId, TransportLayer::TS_CONNECTING);
|
|
p2_.SetRtcpState_s(transportId, TransportLayer::TS_CONNECTING);
|
|
});
|
|
|
|
WaitFor(TimeDuration::FromMilliseconds(10));
|
|
|
|
// Set state of transports to OPEN (ie; connected). This should result in
|
|
// media flowing.
|
|
RunOnSts([&] {
|
|
p1_.SetState_s(transportId, TransportLayer::TS_OPEN);
|
|
p1_.SetRtcpState_s(transportId, TransportLayer::TS_OPEN);
|
|
p2_.SetState_s(transportId, TransportLayer::TS_OPEN);
|
|
p2_.SetRtcpState_s(transportId, TransportLayer::TS_OPEN);
|
|
});
|
|
|
|
if (bundle) {
|
|
WaitFor(TimeDuration::FromMilliseconds(ms_until_filter_update));
|
|
|
|
// Leaving refinedFilter not set implies we want to just update with
|
|
// the other side's SSRC
|
|
if (!refinedFilter) {
|
|
refinedFilter = MakeUnique<MediaPipelineFilter>();
|
|
// Might not be safe, strictly speaking.
|
|
refinedFilter->AddRemoteSSRC(p1_.GetLocalSSRC());
|
|
}
|
|
|
|
RunOnSts([&] {
|
|
p2_.UpdateTransport_s(transportId, std::move(refinedFilter));
|
|
});
|
|
}
|
|
|
|
// wait for some RTP/RTCP tx and rx to happen
|
|
WaitFor(TimeDuration::FromMilliseconds(ms_of_traffic_after_answer));
|
|
|
|
p1_.Stop();
|
|
p2_.Stop();
|
|
|
|
// wait for any packets in flight to arrive
|
|
WaitFor(TimeDuration::FromMilliseconds(200));
|
|
|
|
p1_.Shutdown();
|
|
p2_.Shutdown();
|
|
|
|
if (!bundle) {
|
|
// If we are filtering, allow the test-case to do this checking.
|
|
ASSERT_GE(p1_.GetAudioRtpCountSent(), 40);
|
|
ASSERT_EQ(p1_.GetAudioRtpCountReceived(), p2_.GetAudioRtpCountSent());
|
|
ASSERT_EQ(p1_.GetAudioRtpCountSent(), p2_.GetAudioRtpCountReceived());
|
|
}
|
|
|
|
// No RTCP packets should have been dropped, because we do not filter them.
|
|
// Calling ShutdownMedia_m on both pipelines does not stop the flow of
|
|
// RTCP. So, we might be off by one here.
|
|
ASSERT_LE(p2_.GetAudioRtcpCountReceived(), p1_.GetAudioRtcpCountSent());
|
|
ASSERT_GE(p2_.GetAudioRtcpCountReceived() + 1, p1_.GetAudioRtcpCountSent());
|
|
}
|
|
|
|
void TestAudioReceiverBundle(
|
|
bool bundle_accepted, UniquePtr<MediaPipelineFilter>&& initialFilter,
|
|
UniquePtr<MediaPipelineFilter>&& refinedFilter = nullptr,
|
|
unsigned int ms_until_answer = 500,
|
|
unsigned int ms_of_traffic_after_answer = 10000) {
|
|
TestAudioSend(true, std::move(initialFilter), std::move(refinedFilter),
|
|
ms_until_answer, ms_of_traffic_after_answer);
|
|
}
|
|
|
|
protected:
|
|
// main_task_queue_ has this type to make sure it goes through Delete() when
|
|
// we're destroyed.
|
|
UniquePtr<TaskQueueWrapper<DeletionPolicy::NonBlocking>> main_task_queue_;
|
|
const RefPtr<WebrtcEnvironmentWrapper> env_wrapper_;
|
|
const RefPtr<SharedWebrtcState> shared_state_;
|
|
TestAgentSend p1_;
|
|
TestAgentReceive p2_;
|
|
};
|
|
|
|
class MediaPipelineFilterTest : public ::testing::Test {
|
|
public:
|
|
bool Filter(MediaPipelineFilter& filter, uint32_t ssrc, uint8_t payload_type,
|
|
const Maybe<std::string>& mid = Nothing()) {
|
|
webrtc::RTPHeader header;
|
|
header.ssrc = ssrc;
|
|
header.payloadType = payload_type;
|
|
mid.apply([&](const auto& mid) { header.extension.mid = mid; });
|
|
return filter.Filter(header);
|
|
}
|
|
};
|
|
|
|
TEST_F(MediaPipelineFilterTest, TestConstruct) { MediaPipelineFilter filter; }
|
|
|
|
TEST_F(MediaPipelineFilterTest, TestDefault) {
|
|
MediaPipelineFilter filter;
|
|
EXPECT_FALSE(Filter(filter, 233, 110));
|
|
}
|
|
|
|
TEST_F(MediaPipelineFilterTest, TestSSRCFilter) {
|
|
MediaPipelineFilter filter;
|
|
filter.AddRemoteSSRC(555);
|
|
EXPECT_TRUE(Filter(filter, 555, 110));
|
|
EXPECT_FALSE(Filter(filter, 556, 110));
|
|
}
|
|
|
|
TEST_F(MediaPipelineFilterTest, TestSSRCFilterOverridesPayloadTypeFilter) {
|
|
MediaPipelineFilter filter;
|
|
filter.AddRemoteSSRC(555);
|
|
filter.AddUniqueReceivePT(110);
|
|
// We have a configured ssrc but still need to allow payload type matching.
|
|
EXPECT_TRUE(Filter(filter, 556, 110));
|
|
EXPECT_TRUE(Filter(filter, 555, 110));
|
|
}
|
|
|
|
#define SSRC(ssrc) \
|
|
((ssrc >> 24) & 0xFF), ((ssrc >> 16) & 0xFF), ((ssrc >> 8) & 0xFF), \
|
|
(ssrc & 0xFF)
|
|
#define REPORT_FRAGMENT(ssrc) \
|
|
SSRC(ssrc), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
|
|
#define RTCP_TYPEINFO(num_rrs, type, size) 0x80 + num_rrs, type, 0, size
|
|
|
|
TEST_F(MediaPipelineFilterTest, TestMidFilter) {
|
|
MediaPipelineFilter filter;
|
|
const auto mid = Some(std::string("mid0"));
|
|
filter.SetRemoteMediaStreamId(mid);
|
|
|
|
EXPECT_FALSE(Filter(filter, 16, 110));
|
|
EXPECT_TRUE(Filter(filter, 16, 110, mid));
|
|
EXPECT_TRUE(Filter(filter, 16, 110));
|
|
EXPECT_FALSE(Filter(filter, 17, 110));
|
|
|
|
// The mid filter maintains a set of SSRCs. Adding a new SSRC should work
|
|
// and still allow previous SSRCs to work. Unrecognized SSRCs should still be
|
|
// filtered out.
|
|
EXPECT_TRUE(Filter(filter, 18, 111, mid));
|
|
EXPECT_TRUE(Filter(filter, 18, 111));
|
|
EXPECT_TRUE(Filter(filter, 16, 110));
|
|
EXPECT_FALSE(Filter(filter, 17, 110));
|
|
}
|
|
|
|
TEST_F(MediaPipelineFilterTest, TestPayloadTypeFilter) {
|
|
MediaPipelineFilter filter;
|
|
filter.AddUniqueReceivePT(110);
|
|
EXPECT_TRUE(Filter(filter, 555, 110));
|
|
EXPECT_FALSE(Filter(filter, 556, 111));
|
|
// Matching based on unique payload type causes us to learn the ssrc.
|
|
EXPECT_TRUE(Filter(filter, 555, 98));
|
|
// Once we have learned an SSRC we still need to learn new ones
|
|
// based on payload type.
|
|
EXPECT_TRUE(Filter(filter, 557, 110));
|
|
}
|
|
|
|
TEST_F(MediaPipelineFilterTest, TestSSRCMovedWithMid) {
|
|
MediaPipelineFilter filter;
|
|
const auto mid0 = Some(std::string("mid0"));
|
|
const auto mid1 = Some(std::string("mid1"));
|
|
filter.SetRemoteMediaStreamId(mid0);
|
|
ASSERT_TRUE(Filter(filter, 555, 110, mid0));
|
|
ASSERT_TRUE(Filter(filter, 555, 110));
|
|
// Present a new MID binding
|
|
ASSERT_FALSE(Filter(filter, 555, 110, mid1));
|
|
ASSERT_FALSE(Filter(filter, 555, 110));
|
|
}
|
|
|
|
TEST_F(MediaPipelineFilterTest, TestRemoteSDPNoSSRCs) {
|
|
// If the remote SDP doesn't have SSRCs, right now this is a no-op and
|
|
// there is no point of even incorporating a filter, but we make the
|
|
// behavior consistent to avoid confusion.
|
|
MediaPipelineFilter filter;
|
|
const auto mid = Some(std::string("mid0"));
|
|
filter.SetRemoteMediaStreamId(mid);
|
|
filter.AddUniqueReceivePT(111);
|
|
EXPECT_TRUE(Filter(filter, 555, 110, mid));
|
|
EXPECT_TRUE(Filter(filter, 555, 110));
|
|
|
|
// Update but remember binding./
|
|
MediaPipelineFilter filter2;
|
|
|
|
filter.Update(filter2, true);
|
|
|
|
// Ensure that the old SSRC still works.
|
|
EXPECT_TRUE(Filter(filter, 555, 110));
|
|
|
|
// Forget the previous binding
|
|
MediaPipelineFilter filter3;
|
|
filter3.SetRemoteMediaStreamId(Some(std::string("mid1")));
|
|
filter.Update(filter3, true);
|
|
|
|
ASSERT_FALSE(Filter(filter, 555, 110));
|
|
}
|
|
|
|
TEST_F(MediaPipelineTest, TestAudioSendNoMux) { TestAudioSend(false); }
|
|
|
|
TEST_F(MediaPipelineTest, TestAudioSendMux) { TestAudioSend(true); }
|
|
|
|
TEST_F(MediaPipelineTest, TestAudioSendBundle) {
|
|
auto filter = MakeUnique<MediaPipelineFilter>();
|
|
// These durations have to be _extremely_ long to have any assurance that
|
|
// some RTCP will be sent at all. This is because the first RTCP packet
|
|
// is sometimes sent before the transports are ready, which causes it to
|
|
// be dropped.
|
|
TestAudioReceiverBundle(
|
|
true, std::move(filter),
|
|
// We do not specify the filter for the remote description, so it will be
|
|
// set to something sane after a short time.
|
|
nullptr, 10000, 10000);
|
|
|
|
// Some packets should have been dropped, but not all
|
|
ASSERT_GT(p1_.GetAudioRtpCountSent(), p2_.GetAudioRtpCountReceived());
|
|
ASSERT_GT(p2_.GetAudioRtpCountReceived(), 40);
|
|
ASSERT_GT(p1_.GetAudioRtcpCountSent(), 1);
|
|
}
|
|
|
|
TEST_F(MediaPipelineTest, TestAudioSendEmptyBundleFilter) {
|
|
auto filter = MakeUnique<MediaPipelineFilter>();
|
|
auto bad_answer_filter = MakeUnique<MediaPipelineFilter>();
|
|
TestAudioReceiverBundle(true, std::move(filter),
|
|
std::move(bad_answer_filter));
|
|
// Filter is empty, so should drop everything.
|
|
ASSERT_EQ(0, p2_.GetAudioRtpCountReceived());
|
|
}
|
|
|
|
} // end namespace
|