Added A/V sync tests with drifting clocks.
adding 30% drift to media generator (e.g. audio frame generated every 7ms instead of promised 10ms) works fine adding 2% drift to video ntp-timestamp-stamper makes A/V sync fail. BUG=webrtc:5504 R=pbos@webrtc.org,stefan@webrtc.org Review URL: https://codereview.webrtc.org/1674413004 Cr-Commit-Position: refs/heads/master@{#11556}
This commit is contained in:
@ -26,6 +26,7 @@
|
||||
#include "webrtc/system_wrappers/include/rtp_to_ntp.h"
|
||||
#include "webrtc/test/call_test.h"
|
||||
#include "webrtc/test/direct_transport.h"
|
||||
#include "webrtc/test/drifting_clock.h"
|
||||
#include "webrtc/test/encoder_settings.h"
|
||||
#include "webrtc/test/fake_audio_device.h"
|
||||
#include "webrtc/test/fake_decoder.h"
|
||||
@ -41,11 +42,18 @@
|
||||
#include "webrtc/voice_engine/include/voe_rtp_rtcp.h"
|
||||
#include "webrtc/voice_engine/include/voe_video_sync.h"
|
||||
|
||||
using webrtc::test::DriftingClock;
|
||||
using webrtc::test::FakeAudioDevice;
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class CallPerfTest : public test::CallTest {
|
||||
protected:
|
||||
void TestAudioVideoSync(bool fec, bool create_audio_first);
|
||||
void TestAudioVideoSync(bool fec,
|
||||
bool create_audio_first,
|
||||
float video_ntp_speed,
|
||||
float video_rtp_speed,
|
||||
float audio_rtp_speed);
|
||||
|
||||
void TestCpuOveruse(LoadObserver::Load tested_load, int encode_delay_ms);
|
||||
|
||||
@ -188,7 +196,11 @@ class VideoRtcpAndSyncObserver : public SyncRtcpObserver, public VideoRenderer {
|
||||
int64_t first_time_in_sync_;
|
||||
};
|
||||
|
||||
void CallPerfTest::TestAudioVideoSync(bool fec, bool create_audio_first) {
|
||||
void CallPerfTest::TestAudioVideoSync(bool fec,
|
||||
bool create_audio_first,
|
||||
float video_ntp_speed,
|
||||
float video_rtp_speed,
|
||||
float audio_rtp_speed) {
|
||||
const char* kSyncGroup = "av_sync";
|
||||
const uint32_t kAudioSendSsrc = 1234;
|
||||
const uint32_t kAudioRecvSsrc = 5678;
|
||||
@ -228,8 +240,8 @@ void CallPerfTest::TestAudioVideoSync(bool fec, bool create_audio_first) {
|
||||
const std::string audio_filename =
|
||||
test::ResourcePath("voice_engine/audio_long16", "pcm");
|
||||
ASSERT_STRNE("", audio_filename.c_str());
|
||||
test::FakeAudioDevice fake_audio_device(Clock::GetRealTimeClock(),
|
||||
audio_filename);
|
||||
FakeAudioDevice fake_audio_device(Clock::GetRealTimeClock(), audio_filename,
|
||||
audio_rtp_speed);
|
||||
EXPECT_EQ(0, voe_base->Init(&fake_audio_device, nullptr));
|
||||
Config voe_config;
|
||||
voe_config.Set<VoicePacing>(new VoicePacing(true));
|
||||
@ -324,7 +336,8 @@ void CallPerfTest::TestAudioVideoSync(bool fec, bool create_audio_first) {
|
||||
receiver_call_->CreateAudioReceiveStream(audio_recv_config);
|
||||
}
|
||||
|
||||
CreateFrameGeneratorCapturer();
|
||||
DriftingClock drifting_clock(clock_, video_ntp_speed);
|
||||
CreateFrameGeneratorCapturerWithDrift(&drifting_clock, video_rtp_speed);
|
||||
|
||||
Start();
|
||||
|
||||
@ -365,15 +378,49 @@ void CallPerfTest::TestAudioVideoSync(bool fec, bool create_audio_first) {
|
||||
}
|
||||
|
||||
TEST_F(CallPerfTest, PlaysOutAudioAndVideoInSyncWithAudioCreatedFirst) {
|
||||
TestAudioVideoSync(false, true);
|
||||
TestAudioVideoSync(false, true, DriftingClock::kNoDrift,
|
||||
DriftingClock::kNoDrift, DriftingClock::kNoDrift);
|
||||
}
|
||||
|
||||
TEST_F(CallPerfTest, PlaysOutAudioAndVideoInSyncWithVideoCreatedFirst) {
|
||||
TestAudioVideoSync(false, false);
|
||||
TestAudioVideoSync(false, false, DriftingClock::kNoDrift,
|
||||
DriftingClock::kNoDrift, DriftingClock::kNoDrift);
|
||||
}
|
||||
|
||||
TEST_F(CallPerfTest, PlaysOutAudioAndVideoInSyncWithFec) {
|
||||
TestAudioVideoSync(true, false);
|
||||
TestAudioVideoSync(true, false, DriftingClock::kNoDrift,
|
||||
DriftingClock::kNoDrift, DriftingClock::kNoDrift);
|
||||
}
|
||||
|
||||
// TODO(danilchap): Reenable after adding support for frame capture clock
|
||||
// that is not in sync with local TickTime clock.
|
||||
TEST_F(CallPerfTest, DISABLED_PlaysOutAudioAndVideoInSyncWithVideoNtpDrift) {
|
||||
TestAudioVideoSync(false, true, DriftingClock::PercentsFaster(10.0f),
|
||||
DriftingClock::kNoDrift, DriftingClock::kNoDrift);
|
||||
}
|
||||
|
||||
TEST_F(CallPerfTest, PlaysOutAudioAndVideoInSyncWithAudioRtpDrift) {
|
||||
TestAudioVideoSync(false, true, DriftingClock::kNoDrift,
|
||||
DriftingClock::kNoDrift,
|
||||
DriftingClock::PercentsFaster(30.0f));
|
||||
}
|
||||
|
||||
TEST_F(CallPerfTest, PlaysOutAudioAndVideoInSyncWithVideoRtpDrift) {
|
||||
TestAudioVideoSync(false, true, DriftingClock::kNoDrift,
|
||||
DriftingClock::PercentsFaster(30.0f),
|
||||
DriftingClock::kNoDrift);
|
||||
}
|
||||
|
||||
TEST_F(CallPerfTest, PlaysOutAudioAndVideoInSyncWithAudioFasterThanVideoDrift) {
|
||||
TestAudioVideoSync(false, true, DriftingClock::kNoDrift,
|
||||
DriftingClock::PercentsSlower(30.0f),
|
||||
DriftingClock::PercentsFaster(30.0f));
|
||||
}
|
||||
|
||||
TEST_F(CallPerfTest, PlaysOutAudioAndVideoInSyncWithVideoFasterThanAudioDrift) {
|
||||
TestAudioVideoSync(false, true, DriftingClock::kNoDrift,
|
||||
DriftingClock::PercentsFaster(30.0f),
|
||||
DriftingClock::PercentsSlower(30.0f));
|
||||
}
|
||||
|
||||
void CallPerfTest::TestCaptureNtpTime(const FakeNetworkPipe::Config& net_config,
|
||||
|
||||
@ -236,6 +236,14 @@ void CallTest::CreateMatchingReceiveConfigs(Transport* rtcp_send_transport) {
|
||||
}
|
||||
}
|
||||
|
||||
void CallTest::CreateFrameGeneratorCapturerWithDrift(Clock* clock,
|
||||
float speed) {
|
||||
VideoStream stream = video_encoder_config_.streams.back();
|
||||
frame_generator_capturer_.reset(test::FrameGeneratorCapturer::Create(
|
||||
video_send_stream_->Input(), stream.width, stream.height,
|
||||
stream.max_framerate * speed, clock));
|
||||
}
|
||||
|
||||
void CallTest::CreateFrameGeneratorCapturer() {
|
||||
VideoStream stream = video_encoder_config_.streams.back();
|
||||
frame_generator_capturer_.reset(test::FrameGeneratorCapturer::Create(
|
||||
@ -245,9 +253,11 @@ void CallTest::CreateFrameGeneratorCapturer() {
|
||||
|
||||
void CallTest::CreateFakeAudioDevices() {
|
||||
fake_send_audio_device_.reset(new FakeAudioDevice(
|
||||
clock_, test::ResourcePath("voice_engine/audio_long16", "pcm")));
|
||||
clock_, test::ResourcePath("voice_engine/audio_long16", "pcm"),
|
||||
DriftingClock::kNoDrift));
|
||||
fake_recv_audio_device_.reset(new FakeAudioDevice(
|
||||
clock_, test::ResourcePath("voice_engine/audio_long16", "pcm")));
|
||||
clock_, test::ResourcePath("voice_engine/audio_long16", "pcm"),
|
||||
DriftingClock::kNoDrift));
|
||||
}
|
||||
|
||||
void CallTest::CreateVideoStreams() {
|
||||
|
||||
@ -71,6 +71,7 @@ class CallTest : public ::testing::Test {
|
||||
Transport* send_transport);
|
||||
void CreateMatchingReceiveConfigs(Transport* rtcp_send_transport);
|
||||
|
||||
void CreateFrameGeneratorCapturerWithDrift(Clock* drift_clock, float speed);
|
||||
void CreateFrameGeneratorCapturer();
|
||||
void CreateFakeAudioDevices();
|
||||
|
||||
|
||||
57
webrtc/test/drifting_clock.cc
Normal file
57
webrtc/test/drifting_clock.cc
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2016 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 "webrtc/test/drifting_clock.h"
|
||||
#include "webrtc/base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
const float DriftingClock::kDoubleSpeed = 2.0f;
|
||||
const float DriftingClock::kNoDrift = 1.0f;
|
||||
const float DriftingClock::kHalfSpeed = 0.5f;
|
||||
|
||||
DriftingClock::DriftingClock(Clock* clock, float speed)
|
||||
: clock_(clock),
|
||||
drift_(speed - 1.0f),
|
||||
start_time_(clock_->TimeInMicroseconds()) {
|
||||
RTC_CHECK(clock);
|
||||
RTC_CHECK_GT(speed, 0.0f);
|
||||
}
|
||||
|
||||
float DriftingClock::Drift() const {
|
||||
int64_t now = clock_->TimeInMicroseconds();
|
||||
RTC_DCHECK_GE(now, start_time_);
|
||||
return (now - start_time_) * drift_;
|
||||
}
|
||||
|
||||
int64_t DriftingClock::TimeInMilliseconds() const {
|
||||
return clock_->TimeInMilliseconds() + Drift() / 1000.;
|
||||
}
|
||||
|
||||
int64_t DriftingClock::TimeInMicroseconds() const {
|
||||
return clock_->TimeInMicroseconds() + Drift();
|
||||
}
|
||||
|
||||
void DriftingClock::CurrentNtp(uint32_t& seconds, uint32_t& fractions) const {
|
||||
// NTP precision is 1/2^32 seconds, i.e. 2^32 ntp fractions = 1 second.
|
||||
const double kNtpFracPerMicroSecond = 4294.967296; // = 2^32 / 10^6
|
||||
|
||||
clock_->CurrentNtp(seconds, fractions);
|
||||
uint64_t total_fractions = (static_cast<uint64_t>(seconds) << 32) | fractions;
|
||||
total_fractions += Drift() * kNtpFracPerMicroSecond;
|
||||
seconds = total_fractions >> 32;
|
||||
fractions = static_cast<uint32_t>(total_fractions);
|
||||
}
|
||||
|
||||
int64_t DriftingClock::CurrentNtpInMilliseconds() const {
|
||||
return clock_->CurrentNtpInMilliseconds() + Drift() / 1000.;
|
||||
}
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
45
webrtc/test/drifting_clock.h
Normal file
45
webrtc/test/drifting_clock.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*/
|
||||
#ifndef WEBRTC_TEST_DRIFTING_CLOCK_H_
|
||||
#define WEBRTC_TEST_DRIFTING_CLOCK_H_
|
||||
|
||||
#include "webrtc/system_wrappers/include/clock.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
class DriftingClock : public Clock {
|
||||
public:
|
||||
// TODO(danilchap): Make this constants constexpr when it would be supported.
|
||||
static const float kDoubleSpeed; // 2.0f;
|
||||
static const float kNoDrift; // 1.0f;
|
||||
static const float kHalfSpeed; // 0.5f;
|
||||
|
||||
DriftingClock(Clock* clock, float speed);
|
||||
|
||||
// TODO(danilchap): Make this functions constexpr when it would be supported.
|
||||
static float PercentsFaster(float percent) { return 1.0f + percent / 100.0f; }
|
||||
static float PercentsSlower(float percent) { return 1.0f - percent / 100.0f; }
|
||||
|
||||
int64_t TimeInMilliseconds() const override;
|
||||
int64_t TimeInMicroseconds() const override;
|
||||
void CurrentNtp(uint32_t& seconds, uint32_t& fractions) const override;
|
||||
int64_t CurrentNtpInMilliseconds() const override;
|
||||
|
||||
private:
|
||||
float Drift() const;
|
||||
|
||||
Clock* const clock_;
|
||||
const float drift_;
|
||||
const int64_t start_time_;
|
||||
};
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_TEST_DRIFTING_CLOCK_H_
|
||||
@ -22,13 +22,16 @@
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
FakeAudioDevice::FakeAudioDevice(Clock* clock, const std::string& filename)
|
||||
FakeAudioDevice::FakeAudioDevice(Clock* clock,
|
||||
const std::string& filename,
|
||||
float speed)
|
||||
: audio_callback_(NULL),
|
||||
capturing_(false),
|
||||
captured_audio_(),
|
||||
playout_buffer_(),
|
||||
speed_(speed),
|
||||
last_playout_ms_(-1),
|
||||
clock_(clock),
|
||||
clock_(clock, speed),
|
||||
tick_(EventTimerWrapper::Create()),
|
||||
thread_(FakeAudioDevice::Run, this, "FakeAudioDevice"),
|
||||
file_utility_(new ModuleFileUtility(0)),
|
||||
@ -51,7 +54,7 @@ int32_t FakeAudioDevice::Init() {
|
||||
if (file_utility_->InitPCMReading(*input_stream_.get()) != 0)
|
||||
return -1;
|
||||
|
||||
if (!tick_->StartTimer(true, 10))
|
||||
if (!tick_->StartTimer(true, 10 / speed_))
|
||||
return -1;
|
||||
thread_.Start();
|
||||
thread_.SetPriority(rtc::kHighPriority);
|
||||
@ -107,7 +110,7 @@ void FakeAudioDevice::CaptureAudio() {
|
||||
false,
|
||||
new_mic_level));
|
||||
size_t samples_needed = kFrequencyHz / 100;
|
||||
int64_t now_ms = clock_->TimeInMilliseconds();
|
||||
int64_t now_ms = clock_.TimeInMilliseconds();
|
||||
uint32_t time_since_last_playout_ms = now_ms - last_playout_ms_;
|
||||
if (last_playout_ms_ > 0 && time_since_last_playout_ms > 0) {
|
||||
samples_needed = std::min(
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include "webrtc/base/platform_thread.h"
|
||||
#include "webrtc/base/scoped_ptr.h"
|
||||
#include "webrtc/modules/audio_device/include/fake_audio_device.h"
|
||||
#include "webrtc/test/drifting_clock.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
@ -29,7 +30,7 @@ namespace test {
|
||||
|
||||
class FakeAudioDevice : public FakeAudioDeviceModule {
|
||||
public:
|
||||
FakeAudioDevice(Clock* clock, const std::string& filename);
|
||||
FakeAudioDevice(Clock* clock, const std::string& filename, float speed);
|
||||
|
||||
virtual ~FakeAudioDevice();
|
||||
|
||||
@ -54,9 +55,10 @@ class FakeAudioDevice : public FakeAudioDeviceModule {
|
||||
bool capturing_;
|
||||
int8_t captured_audio_[kBufferSizeBytes];
|
||||
int8_t playout_buffer_[kBufferSizeBytes];
|
||||
const float speed_;
|
||||
int64_t last_playout_ms_;
|
||||
|
||||
Clock* clock_;
|
||||
DriftingClock clock_;
|
||||
rtc::scoped_ptr<EventTimerWrapper> tick_;
|
||||
rtc::CriticalSection lock_;
|
||||
rtc::PlatformThread thread_;
|
||||
|
||||
@ -22,6 +22,8 @@
|
||||
'constants.h',
|
||||
'direct_transport.cc',
|
||||
'direct_transport.h',
|
||||
'drifting_clock.cc',
|
||||
'drifting_clock.h',
|
||||
'encoder_settings.cc',
|
||||
'encoder_settings.h',
|
||||
'fake_audio_device.cc',
|
||||
|
||||
Reference in New Issue
Block a user