diff options
author | Android Chromium Automerger <chromium-automerger@android> | 2014-09-25 12:15:39 +0000 |
---|---|---|
committer | Android Chromium Automerger <chromium-automerger@android> | 2014-09-25 12:15:39 +0000 |
commit | db1e40e5746058ca5a1cffbb68150b3e8b3e4339 (patch) | |
tree | 8c54d3753557a2aaa4573d9808ebde41c109243d | |
parent | 8af00ea2e38cd722cdc20c41427cdab7e165591e (diff) | |
parent | 53545bbfc47f2cddb7038395369a0dcd457c8b34 (diff) | |
download | webrtc-db1e40e5746058ca5a1cffbb68150b3e8b3e4339.tar.gz |
Merge third_party/webrtc from https://chromium.googlesource.com/external/webrtc/trunk/webrtc.git at 53545bbfc47f2cddb7038395369a0dcd457c8b34
This commit was generated by merge_from_chromium.py.
Change-Id: I9a04351d8f1bfde313d4565bd7f276b9684920d7
-rw-r--r-- | base/thread.cc | 33 | ||||
-rw-r--r-- | base/thread.h | 19 | ||||
-rw-r--r-- | base/thread_unittest.cc | 72 | ||||
-rw-r--r-- | modules/audio_coding/main/test/RTPFile.cc | 14 | ||||
-rw-r--r-- | modules/audio_processing/audio_buffer.cc | 31 | ||||
-rw-r--r-- | modules/audio_processing/audio_processing_impl.cc | 9 | ||||
-rw-r--r-- | modules/audio_processing/test/audio_processing_unittest.cc | 41 | ||||
-rw-r--r-- | modules/desktop_capture/screen_capturer_mac.mm | 1 | ||||
-rw-r--r-- | modules/modules.gyp | 1 | ||||
-rw-r--r-- | modules/rtp_rtcp/source/rtp_format_h264.h | 2 | ||||
-rw-r--r-- | modules/video_coding/main/source/jitter_buffer.cc | 5 | ||||
-rw-r--r-- | modules/video_coding/main/source/jitter_estimator.cc | 161 | ||||
-rw-r--r-- | modules/video_coding/main/source/jitter_estimator.h | 19 | ||||
-rw-r--r-- | modules/video_coding/main/source/jitter_estimator_tests.cc | 160 |
14 files changed, 465 insertions, 103 deletions
diff --git a/base/thread.cc b/base/thread.cc index 9d2917d9..40257ab8 100644 --- a/base/thread.cc +++ b/base/thread.cc @@ -411,15 +411,12 @@ void Thread::Stop() { } void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) { - AssertBlockingIsAllowedOnCurrentThread(); - if (fStop_) return; // Sent messages are sent to the MessageHandler directly, in the context // of "thread", like Win32 SendMessage. If in the right context, // call the handler directly. - Message msg; msg.phandler = phandler; msg.message_id = id; @@ -429,6 +426,8 @@ void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) { return; } + AssertBlockingIsAllowedOnCurrentThread(); + AutoThread thread; Thread *current_thread = Thread::Current(); ASSERT(current_thread != NULL); // AutoThread ensures this @@ -451,7 +450,9 @@ void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) { crit_.Enter(); while (!ready) { crit_.Leave(); - current_thread->ReceiveSends(); + // We need to limit "ReceiveSends" to |this| thread to avoid an arbitrary + // thread invoking calls on the current thread. + current_thread->ReceiveSendsFromThread(this); current_thread->socketserver()->Wait(kForever, false); waited = true; crit_.Enter(); @@ -475,17 +476,23 @@ void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) { } void Thread::ReceiveSends() { + ReceiveSendsFromThread(NULL); +} + +void Thread::ReceiveSendsFromThread(const Thread* source) { // Receive a sent message. Cleanup scenarios: // - thread sending exits: We don't allow this, since thread can exit // only via Join, so Send must complete. // - thread receiving exits: Wakeup/set ready in Thread::Clear() // - object target cleared: Wakeup/set ready in Thread::Clear() + _SendMessage smsg; + crit_.Enter(); - while (!sendlist_.empty()) { - _SendMessage smsg = sendlist_.front(); - sendlist_.pop_front(); + while (PopSendMessageFromThread(source, &smsg)) { crit_.Leave(); + smsg.msg.phandler->OnMessage(&smsg.msg); + crit_.Enter(); *smsg.ready = true; smsg.thread->socketserver()->WakeUp(); @@ -493,6 +500,18 @@ void Thread::ReceiveSends() { crit_.Leave(); } +bool Thread::PopSendMessageFromThread(const Thread* source, _SendMessage* msg) { + for (std::list<_SendMessage>::iterator it = sendlist_.begin(); + it != sendlist_.end(); ++it) { + if (it->thread == source || source == NULL) { + *msg = *it; + sendlist_.erase(it); + return true; + } + } + return false; +} + void Thread::Clear(MessageHandler *phandler, uint32 id, MessageList* removed) { CritScope cs(&crit_); diff --git a/base/thread.h b/base/thread.h index 25b0f569..34ec45e3 100644 --- a/base/thread.h +++ b/base/thread.h @@ -165,7 +165,6 @@ class Thread : public MessageQueue { // See ScopedDisallowBlockingCalls for details. template <class ReturnT, class FunctorT> ReturnT Invoke(const FunctorT& functor) { - AssertBlockingIsAllowedOnCurrentThread(); FunctorMessageHandler<ReturnT, FunctorT> handler(functor); Send(&handler); return handler.result(); @@ -210,6 +209,10 @@ class Thread : public MessageQueue { // of whatever code is conditionally executing because of the return value! bool RunningForTest() { return running(); } + // Sets the per-thread allow-blocking-calls flag and returns the previous + // value. + bool SetAllowBlockingCalls(bool allow); + protected: // This method should be called when thread is created using non standard // method, like derived implementation of rtc::Thread and it can not be @@ -226,10 +229,6 @@ class Thread : public MessageQueue { // Blocks the calling thread until this thread has terminated. void Join(); - // Sets the per-thread allow-blocking-calls flag and returns the previous - // value. - bool SetAllowBlockingCalls(bool allow); - static void AssertBlockingIsAllowedOnCurrentThread(); friend class ScopedDisallowBlockingCalls; @@ -248,6 +247,16 @@ class Thread : public MessageQueue { // Return true if the thread was started and hasn't yet stopped. bool running() { return running_.Wait(0); } + // Processes received "Send" requests. If |source| is not NULL, only requests + // from |source| are processed, otherwise, all requests are processed. + void ReceiveSendsFromThread(const Thread* source); + + // If |source| is not NULL, pops the first "Send" message from |source| in + // |sendlist_|, otherwise, pops the first "Send" message of |sendlist_|. + // The caller must lock |crit_| before calling. + // Returns true if there is such a message. + bool PopSendMessageFromThread(const Thread* source, _SendMessage* msg); + std::list<_SendMessage> sendlist_; std::string name_; ThreadPriority priority_; diff --git a/base/thread_unittest.cc b/base/thread_unittest.cc index 4229df28..57b6df66 100644 --- a/base/thread_unittest.cc +++ b/base/thread_unittest.cc @@ -276,6 +276,78 @@ TEST(ThreadTest, DISABLED_ON_MAC(Invoke)) { thread.Invoke<void>(&LocalFuncs::Func2); } +// Verifies that two threads calling Invoke on each other at the same time does +// not deadlock. +TEST(ThreadTest, TwoThreadsInvokeNoDeadlock) { + AutoThread thread; + Thread* current_thread = Thread::Current(); + ASSERT_TRUE(current_thread != NULL); + + Thread other_thread; + other_thread.Start(); + + struct LocalFuncs { + static void Set(bool* out) { *out = true; } + static void InvokeSet(Thread* thread, bool* out) { + thread->Invoke<void>(Bind(&Set, out)); + } + }; + + bool called = false; + other_thread.Invoke<void>( + Bind(&LocalFuncs::InvokeSet, current_thread, &called)); + + EXPECT_TRUE(called); +} + +// Verifies that if thread A invokes a call on thread B and thread C is trying +// to invoke A at the same time, thread A does not handle C's invoke while +// invoking B. +TEST(ThreadTest, ThreeThreadsInvoke) { + AutoThread thread; + Thread* thread_a = Thread::Current(); + Thread thread_b, thread_c; + thread_b.Start(); + thread_c.Start(); + + struct LocalFuncs { + static void Set(bool* out) { *out = true; } + static void InvokeSet(Thread* thread, bool* out) { + thread->Invoke<void>(Bind(&Set, out)); + } + + // Set |out| true and call InvokeSet on |thread|. + static void SetAndInvokeSet(bool* out, Thread* thread, bool* out_inner) { + *out = true; + InvokeSet(thread, out_inner); + } + + // Asynchronously invoke SetAndInvokeSet on |thread1| and wait until + // |thread1| starts the call. + static void AsyncInvokeSetAndWait( + Thread* thread1, Thread* thread2, bool* out) { + bool async_invoked = false; + + AsyncInvoker invoker; + invoker.AsyncInvoke<void>( + thread1, Bind(&SetAndInvokeSet, &async_invoked, thread2, out)); + + EXPECT_TRUE_WAIT(async_invoked, 2000); + } + }; + + bool thread_a_called = false; + + // Start the sequence A --(invoke)--> B --(async invoke)--> C --(invoke)--> A. + // Thread B returns when C receives the call and C should be blocked until A + // starts to process messages. + thread_b.Invoke<void>(Bind(&LocalFuncs::AsyncInvokeSetAndWait, + &thread_c, thread_a, &thread_a_called)); + EXPECT_FALSE(thread_a_called); + + EXPECT_TRUE_WAIT(thread_a_called, 2000); +} + class AsyncInvokeTest : public testing::Test { public: void IntCallback(int value) { diff --git a/modules/audio_coding/main/test/RTPFile.cc b/modules/audio_coding/main/test/RTPFile.cc index 4cef5924..b7f587b8 100644 --- a/modules/audio_coding/main/test/RTPFile.cc +++ b/modules/audio_coding/main/test/RTPFile.cc @@ -109,7 +109,7 @@ uint16_t RTPBuffer::Read(WebRtcRTPHeader* rtpInfo, uint8_t* payloadData, if (packet->payloadSize > 0 && payloadSize >= packet->payloadSize) { memcpy(payloadData, packet->payloadData, packet->payloadSize); } else { - return 0u; + return 0; } *offset = (packet->timeStamp / (packet->frequency / 1000)); @@ -216,7 +216,7 @@ uint16_t RTPFile::Read(WebRtcRTPHeader* rtpInfo, uint8_t* payloadData, /* Check if we have reached end of file. */ if ((read_len == 0) && feof(_rtpFile)) { _rtpEOF = true; - return 0u; + return 0; } EXPECT_EQ(1u, fread(&plen, 2, 1, _rtpFile)); EXPECT_EQ(1u, fread(offset, 4, 1, _rtpFile)); @@ -232,13 +232,13 @@ uint16_t RTPFile::Read(WebRtcRTPHeader* rtpInfo, uint8_t* payloadData, EXPECT_EQ(lengthBytes, plen + 8); if (plen == 0) { - return 0u; - } - if (payloadSize < (lengthBytes - 20)) { - return 0u; + return 0; } if (lengthBytes < 20) { - return 0u; + return 0; + } + if (payloadSize < (lengthBytes - 20)) { + return 0; } lengthBytes -= 20; EXPECT_EQ(lengthBytes, fread(payloadData, 1, lengthBytes, _rtpFile)); diff --git a/modules/audio_processing/audio_buffer.cc b/modules/audio_processing/audio_buffer.cc index fb2c200e..2bbd7710 100644 --- a/modules/audio_processing/audio_buffer.cc +++ b/modules/audio_processing/audio_buffer.cc @@ -406,19 +406,32 @@ int AudioBuffer::samples_per_keyboard_channel() const { // TODO(andrew): Do deinterleaving and mixing in one step? void AudioBuffer::DeinterleaveFrom(AudioFrame* frame) { assert(proc_samples_per_channel_ == input_samples_per_channel_); - assert(num_proc_channels_ == num_input_channels_); - assert(frame->num_channels_ == num_proc_channels_); + assert(frame->num_channels_ == num_input_channels_); assert(frame->samples_per_channel_ == proc_samples_per_channel_); InitForNewData(); activity_ = frame->vad_activity_; - int16_t* interleaved = frame->data_; - for (int i = 0; i < num_proc_channels_; i++) { - int16_t* deinterleaved = channels_->ibuf()->channel(i); - int interleaved_idx = i; - for (int j = 0; j < proc_samples_per_channel_; j++) { - deinterleaved[j] = interleaved[interleaved_idx]; - interleaved_idx += num_proc_channels_; + if (num_input_channels_ == 2 && num_proc_channels_ == 1) { + // Downmix directly; no explicit deinterleaving needed. + int16_t* downmixed = channels_->ibuf()->channel(0); + for (int i = 0; i < input_samples_per_channel_; ++i) { + // HACK(ajm): The downmixing in the int16_t path is in practice never + // called from production code. We do this weird scaling to and from float + // to satisfy tests checking for bit-exactness with the float path. + float downmix_float = (ScaleToFloat(frame->data_[i * 2]) + + ScaleToFloat(frame->data_[i * 2 + 1])) / 2; + downmixed[i] = ScaleAndRoundToInt16(downmix_float); + } + } else { + assert(num_proc_channels_ == num_input_channels_); + int16_t* interleaved = frame->data_; + for (int i = 0; i < num_proc_channels_; ++i) { + int16_t* deinterleaved = channels_->ibuf()->channel(i); + int interleaved_idx = i; + for (int j = 0; j < proc_samples_per_channel_; ++j) { + deinterleaved[j] = interleaved[interleaved_idx]; + interleaved_idx += num_proc_channels_; + } } } } diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc index 659b794a..d91cbd2f 100644 --- a/modules/audio_processing/audio_processing_impl.cc +++ b/modules/audio_processing/audio_processing_impl.cc @@ -257,10 +257,9 @@ int AudioProcessingImpl::InitializeLocked(int input_sample_rate_hz, } } - // TODO(ajm): Enable this. - // Always downmix the reverse stream to mono for analysis. - //rev_proc_format_.set(rev_proc_rate, 1); - rev_proc_format_.set(rev_proc_rate, rev_in_format_.num_channels()); + // Always downmix the reverse stream to mono for analysis. This has been + // demonstrated to work well for AEC in most practical scenarios. + rev_proc_format_.set(rev_proc_rate, 1); if (fwd_proc_format_.rate() == kSampleRate32kHz) { split_rate_ = kSampleRate16kHz; @@ -482,12 +481,12 @@ int AudioProcessingImpl::ProcessStreamLocked() { RETURN_ON_ERR(high_pass_filter_->ProcessCaptureAudio(ca)); RETURN_ON_ERR(gain_control_->AnalyzeCaptureAudio(ca)); + RETURN_ON_ERR(noise_suppression_->AnalyzeCaptureAudio(ca)); RETURN_ON_ERR(echo_cancellation_->ProcessCaptureAudio(ca)); if (echo_control_mobile_->is_enabled() && noise_suppression_->is_enabled()) { ca->CopyLowPassToReference(); } - RETURN_ON_ERR(noise_suppression_->AnalyzeCaptureAudio(ca)); RETURN_ON_ERR(noise_suppression_->ProcessCaptureAudio(ca)); RETURN_ON_ERR(echo_control_mobile_->ProcessCaptureAudio(ca)); RETURN_ON_ERR(voice_detection_->ProcessCaptureAudio(ca)); diff --git a/modules/audio_processing/test/audio_processing_unittest.cc b/modules/audio_processing/test/audio_processing_unittest.cc index af63bde3..a0fb303b 100644 --- a/modules/audio_processing/test/audio_processing_unittest.cc +++ b/modules/audio_processing/test/audio_processing_unittest.cc @@ -751,7 +751,8 @@ TEST_F(ApmTest, Channels) { for (int i = 1; i < 3; i++) { TestChangingChannels(i, kNoErr); EXPECT_EQ(i, apm_->num_input_channels()); - EXPECT_EQ(i, apm_->num_reverse_channels()); + // We always force the number of reverse channels used for processing to 1. + EXPECT_EQ(1, apm_->num_reverse_channels()); } } @@ -1671,7 +1672,7 @@ TEST_F(ApmTest, FloatAndIntInterfacesGiveIdenticalResults) { const int num_output_channels = test->num_output_channels(); const int samples_per_channel = test->sample_rate() * AudioProcessing::kChunkSizeMs / 1000; - const int output_length = samples_per_channel * num_output_channels; + const int output_length = samples_per_channel * num_output_channels; Init(test->sample_rate(), test->sample_rate(), test->sample_rate(), num_input_channels, num_output_channels, num_render_channels, true); @@ -2325,25 +2326,25 @@ TEST_P(AudioProcessingTest, Formats) { #if defined(WEBRTC_AUDIOPROC_FLOAT_PROFILE) INSTANTIATE_TEST_CASE_P( CommonFormats, AudioProcessingTest, testing::Values( - std::tr1::make_tuple(48000, 48000, 48000, 25), - std::tr1::make_tuple(48000, 48000, 32000, 25), - std::tr1::make_tuple(48000, 48000, 16000, 25), - std::tr1::make_tuple(48000, 44100, 48000, 20), - std::tr1::make_tuple(48000, 44100, 32000, 20), - std::tr1::make_tuple(48000, 44100, 16000, 20), - std::tr1::make_tuple(48000, 32000, 48000, 25), - std::tr1::make_tuple(48000, 32000, 32000, 25), - std::tr1::make_tuple(48000, 32000, 16000, 25), - std::tr1::make_tuple(48000, 16000, 48000, 25), - std::tr1::make_tuple(48000, 16000, 32000, 25), - std::tr1::make_tuple(48000, 16000, 16000, 25), + std::tr1::make_tuple(48000, 48000, 48000, 20), + std::tr1::make_tuple(48000, 48000, 32000, 20), + std::tr1::make_tuple(48000, 48000, 16000, 20), + std::tr1::make_tuple(48000, 44100, 48000, 15), + std::tr1::make_tuple(48000, 44100, 32000, 15), + std::tr1::make_tuple(48000, 44100, 16000, 15), + std::tr1::make_tuple(48000, 32000, 48000, 20), + std::tr1::make_tuple(48000, 32000, 32000, 20), + std::tr1::make_tuple(48000, 32000, 16000, 20), + std::tr1::make_tuple(48000, 16000, 48000, 20), + std::tr1::make_tuple(48000, 16000, 32000, 20), + std::tr1::make_tuple(48000, 16000, 16000, 20), std::tr1::make_tuple(44100, 48000, 48000, 20), std::tr1::make_tuple(44100, 48000, 32000, 20), std::tr1::make_tuple(44100, 48000, 16000, 20), - std::tr1::make_tuple(44100, 44100, 48000, 20), - std::tr1::make_tuple(44100, 44100, 32000, 20), - std::tr1::make_tuple(44100, 44100, 16000, 20), + std::tr1::make_tuple(44100, 44100, 48000, 15), + std::tr1::make_tuple(44100, 44100, 32000, 15), + std::tr1::make_tuple(44100, 44100, 16000, 15), std::tr1::make_tuple(44100, 32000, 48000, 20), std::tr1::make_tuple(44100, 32000, 32000, 20), std::tr1::make_tuple(44100, 32000, 16000, 20), @@ -2360,9 +2361,9 @@ INSTANTIATE_TEST_CASE_P( std::tr1::make_tuple(32000, 32000, 48000, 30), std::tr1::make_tuple(32000, 32000, 32000, 0), std::tr1::make_tuple(32000, 32000, 16000, 30), - std::tr1::make_tuple(32000, 16000, 48000, 25), - std::tr1::make_tuple(32000, 16000, 32000, 25), - std::tr1::make_tuple(32000, 16000, 16000, 25), + std::tr1::make_tuple(32000, 16000, 48000, 20), + std::tr1::make_tuple(32000, 16000, 32000, 20), + std::tr1::make_tuple(32000, 16000, 16000, 20), std::tr1::make_tuple(16000, 48000, 48000, 25), std::tr1::make_tuple(16000, 48000, 32000, 25), diff --git a/modules/desktop_capture/screen_capturer_mac.mm b/modules/desktop_capture/screen_capturer_mac.mm index be05bd99..effe7ebf 100644 --- a/modules/desktop_capture/screen_capturer_mac.mm +++ b/modules/desktop_capture/screen_capturer_mac.mm @@ -429,6 +429,7 @@ void ScreenCapturerMac::Capture(const DesktopRegion& region_to_capture) { // Lion requires us to use their new APIs for doing screen capture. These // APIS currently crash on 10.6.8 if there is no monitor attached. if (!CgBlitPostLion(*current_frame, region)) { + desktop_config_monitor_->Unlock(); callback_->OnCaptureCompleted(NULL); return; } diff --git a/modules/modules.gyp b/modules/modules.gyp index 777523ab..9650e66d 100644 --- a/modules/modules.gyp +++ b/modules/modules.gyp @@ -237,6 +237,7 @@ 'video_coding/main/interface/mock/mock_vcm_callbacks.h', 'video_coding/main/source/decoding_state_unittest.cc', 'video_coding/main/source/jitter_buffer_unittest.cc', + 'video_coding/main/source/jitter_estimator_tests.cc', 'video_coding/main/source/media_optimization_unittest.cc', 'video_coding/main/source/receiver_unittest.cc', 'video_coding/main/source/session_info_unittest.cc', diff --git a/modules/rtp_rtcp/source/rtp_format_h264.h b/modules/rtp_rtcp/source/rtp_format_h264.h index abd9321f..830d765c 100644 --- a/modules/rtp_rtcp/source/rtp_format_h264.h +++ b/modules/rtp_rtcp/source/rtp_format_h264.h @@ -43,7 +43,7 @@ class RtpPacketizerH264 : public RtpPacketizer { virtual ProtectionType GetProtectionType() OVERRIDE; - virtual StorageType GetStorageType(uint32_t retrasmission_settings) OVERRIDE; + virtual StorageType GetStorageType(uint32_t retransmission_settings) OVERRIDE; virtual std::string ToString() OVERRIDE; diff --git a/modules/video_coding/main/source/jitter_buffer.cc b/modules/video_coding/main/source/jitter_buffer.cc index 9aa34090..d09fccd9 100644 --- a/modules/video_coding/main/source/jitter_buffer.cc +++ b/modules/video_coding/main/source/jitter_buffer.cc @@ -122,8 +122,7 @@ void FrameList::Reset(UnorderedFrameList* free_frames) { } } -VCMJitterBuffer::VCMJitterBuffer(Clock* clock, - EventFactory* event_factory) +VCMJitterBuffer::VCMJitterBuffer(Clock* clock, EventFactory* event_factory) : clock_(clock), running_(false), crit_sect_(CriticalSectionWrapper::CreateCriticalSection()), @@ -145,7 +144,7 @@ VCMJitterBuffer::VCMJitterBuffer(Clock* clock, num_consecutive_old_frames_(0), num_consecutive_old_packets_(0), num_discarded_packets_(0), - jitter_estimate_(), + jitter_estimate_(clock), inter_frame_delay_(clock_->TimeInMilliseconds()), rtt_ms_(kDefaultRtt), nack_mode_(kNoNack), diff --git a/modules/video_coding/main/source/jitter_estimator.cc b/modules/video_coding/main/source/jitter_estimator.cc index 71c54a00..b36775a4 100644 --- a/modules/video_coding/main/source/jitter_estimator.cc +++ b/modules/video_coding/main/source/jitter_estimator.cc @@ -11,6 +11,8 @@ #include "webrtc/modules/video_coding/main/source/internal_defines.h" #include "webrtc/modules/video_coding/main/source/jitter_estimator.h" #include "webrtc/modules/video_coding/main/source/rtt_filter.h" +#include "webrtc/system_wrappers/interface/clock.h" +#include "webrtc/system_wrappers/interface/field_trial.h" #include <assert.h> #include <math.h> @@ -19,7 +21,13 @@ namespace webrtc { -VCMJitterEstimator::VCMJitterEstimator(int32_t vcmId, int32_t receiverId) +enum { kStartupDelaySamples = 30 }; +enum { kFsAccuStartupSamples = 5 }; +enum { kMaxFramerateEstimate = 200 }; + +VCMJitterEstimator::VCMJitterEstimator(const Clock* clock, + int32_t vcmId, + int32_t receiverId) : _vcmId(vcmId), _receiverId(receiverId), _phi(0.97), @@ -32,8 +40,15 @@ VCMJitterEstimator::VCMJitterEstimator(int32_t vcmId, int32_t receiverId) _noiseStdDevs(2.33), // ~Less than 1% chance // (look up in normal distribution table)... _noiseStdDevOffset(30.0), // ...of getting 30 ms freezes - _rttFilter() { - Reset(); + _rttFilter(), + fps_counter_(30), // TODO(sprang): Use an estimator with limit based on + // time, rather than number of samples. + low_rate_experiment_(kInit), + clock_(clock) { + Reset(); +} + +VCMJitterEstimator::~VCMJitterEstimator() { } VCMJitterEstimator& @@ -94,6 +109,7 @@ VCMJitterEstimator::Reset() _fsCount = 0; _startupCount = 0; _rttFilter.Reset(); + fps_counter_.Reset(); } void @@ -297,35 +313,54 @@ VCMJitterEstimator::DeviationFromExpectedDelay(int64_t frameDelayMS, // Estimates the random jitter by calculating the variance of the // sample distance from the line given by theta. -void -VCMJitterEstimator::EstimateRandomJitter(double d_dT, bool incompleteFrame) -{ - double alpha; - if (_alphaCount == 0) - { - assert(_alphaCount > 0); - return; - } - alpha = static_cast<double>(_alphaCount - 1) / static_cast<double>(_alphaCount); - _alphaCount++; - if (_alphaCount > _alphaCountMax) - { - _alphaCount = _alphaCountMax; - } - double avgNoise = alpha * _avgNoise + (1 - alpha) * d_dT; - double varNoise = alpha * _varNoise + - (1 - alpha) * (d_dT - _avgNoise) * (d_dT - _avgNoise); - if (!incompleteFrame || varNoise > _varNoise) - { - _avgNoise = avgNoise; - _varNoise = varNoise; - } - if (_varNoise < 1.0) - { - // The variance should never be zero, since we might get - // stuck and consider all samples as outliers. - _varNoise = 1.0; +void VCMJitterEstimator::EstimateRandomJitter(double d_dT, + bool incompleteFrame) { + uint64_t now = clock_->TimeInMicroseconds(); + if (_lastUpdateT != -1) { + fps_counter_.AddSample(now - _lastUpdateT); + } + _lastUpdateT = now; + + if (_alphaCount == 0) { + assert(false); + return; + } + double alpha = + static_cast<double>(_alphaCount - 1) / static_cast<double>(_alphaCount); + _alphaCount++; + if (_alphaCount > _alphaCountMax) + _alphaCount = _alphaCountMax; + + if (LowRateExperimentEnabled()) { + // In order to avoid a low frame rate stream to react slower to changes, + // scale the alpha weight relative a 30 fps stream. + double fps = GetFrameRate(); + if (fps > 0.0) { + double rate_scale = 30.0 / fps; + // At startup, there can be a lot of noise in the fps estimate. + // Interpolate rate_scale linearly, from 1.0 at sample #1, to 30.0 / fps + // at sample #kStartupDelaySamples. + if (_alphaCount < kStartupDelaySamples) { + rate_scale = + (_alphaCount * rate_scale + (kStartupDelaySamples - _alphaCount)) / + kStartupDelaySamples; + } + alpha = pow(alpha, rate_scale); } + } + + double avgNoise = alpha * _avgNoise + (1 - alpha) * d_dT; + double varNoise = + alpha * _varNoise + (1 - alpha) * (d_dT - _avgNoise) * (d_dT - _avgNoise); + if (!incompleteFrame || varNoise > _varNoise) { + _avgNoise = avgNoise; + _varNoise = varNoise; + } + if (_varNoise < 1.0) { + // The variance should never be zero, since we might get + // stuck and consider all samples as outliers. + _varNoise = 1.0; + } } double @@ -387,19 +422,61 @@ VCMJitterEstimator::UpdateMaxFrameSize(uint32_t frameSizeBytes) // Returns the current filtered estimate if available, // otherwise tries to calculate an estimate. -int -VCMJitterEstimator::GetJitterEstimate(double rttMultiplier) -{ - double jitterMS = CalculateEstimate() + OPERATING_SYSTEM_JITTER; - if (_filterJitterEstimate > jitterMS) - { - jitterMS = _filterJitterEstimate; +int VCMJitterEstimator::GetJitterEstimate(double rttMultiplier) { + double jitterMS = CalculateEstimate() + OPERATING_SYSTEM_JITTER; + if (_filterJitterEstimate > jitterMS) + jitterMS = _filterJitterEstimate; + if (_nackCount >= _nackLimit) + jitterMS += _rttFilter.RttMs() * rttMultiplier; + + if (LowRateExperimentEnabled()) { + static const double kJitterScaleLowThreshold = 5.0; + static const double kJitterScaleHighThreshold = 10.0; + double fps = GetFrameRate(); + // Ignore jitter for very low fps streams. + if (fps < kJitterScaleLowThreshold) { + if (fps == 0.0) { + return jitterMS; + } + return 0; } - if (_nackCount >= _nackLimit) - { - jitterMS += _rttFilter.RttMs() * rttMultiplier; + + // Semi-low frame rate; scale by factor linearly interpolated from 0.0 at + // kJitterScaleLowThreshold to 1.0 at kJitterScaleHighThreshold. + if (fps < kJitterScaleHighThreshold) { + jitterMS = + (1.0 / (kJitterScaleHighThreshold - kJitterScaleLowThreshold)) * + (fps - kJitterScaleLowThreshold) * jitterMS; } - return static_cast<uint32_t>(jitterMS + 0.5); + } + + return static_cast<uint32_t>(jitterMS + 0.5); +} + +bool VCMJitterEstimator::LowRateExperimentEnabled() { + if (low_rate_experiment_ == kInit) { + std::string group = + webrtc::field_trial::FindFullName("WebRTC-ReducedJitterDelay"); + if (group == "Disabled") { + low_rate_experiment_ = kDisabled; + } else { + low_rate_experiment_ = kEnabled; + } + } + return low_rate_experiment_ == kEnabled ? true : false; +} + +double VCMJitterEstimator::GetFrameRate() const { + if (fps_counter_.count() == 0) + return 0; + + double fps = 1000000.0 / fps_counter_.ComputeMean(); + // Sanity check. + assert(fps >= 0.0); + if (fps > kMaxFramerateEstimate) { + fps = kMaxFramerateEstimate; + } + return fps; } } diff --git a/modules/video_coding/main/source/jitter_estimator.h b/modules/video_coding/main/source/jitter_estimator.h index dda8f8da..ec7e35ce 100644 --- a/modules/video_coding/main/source/jitter_estimator.h +++ b/modules/video_coding/main/source/jitter_estimator.h @@ -11,17 +11,22 @@ #ifndef WEBRTC_MODULES_VIDEO_CODING_JITTER_ESTIMATOR_H_ #define WEBRTC_MODULES_VIDEO_CODING_JITTER_ESTIMATOR_H_ +#include "webrtc/base/rollingaccumulator.h" #include "webrtc/modules/video_coding/main/source/rtt_filter.h" #include "webrtc/typedefs.h" namespace webrtc { +class Clock; + class VCMJitterEstimator { public: - VCMJitterEstimator(int32_t vcmId = 0, int32_t receiverId = 0); - + VCMJitterEstimator(const Clock* clock, + int32_t vcmId = 0, + int32_t receiverId = 0); + virtual ~VCMJitterEstimator(); VCMJitterEstimator& operator=(const VCMJitterEstimator& rhs); // Resets the estimate to the initial state @@ -68,6 +73,8 @@ protected: double _theta[2]; // Estimated line parameters (slope, offset) double _varNoise; // Variance of the time-deviation from the line + virtual bool LowRateExperimentEnabled(); + private: // Updates the Kalman filter for the line describing // the frame size dependent jitter. @@ -109,6 +116,8 @@ private: double DeviationFromExpectedDelay(int64_t frameDelayMS, int32_t deltaFSBytes) const; + double GetFrameRate() const; + // Constants, filter parameters int32_t _vcmId; int32_t _receiverId; @@ -145,8 +154,10 @@ private: // but never goes above _nackLimit VCMRttFilter _rttFilter; - enum { kStartupDelaySamples = 30 }; - enum { kFsAccuStartupSamples = 5 }; + rtc::RollingAccumulator<uint64_t> fps_counter_; + enum ExperimentFlag { kInit, kEnabled, kDisabled }; + ExperimentFlag low_rate_experiment_; + const Clock* clock_; }; } // namespace webrtc diff --git a/modules/video_coding/main/source/jitter_estimator_tests.cc b/modules/video_coding/main/source/jitter_estimator_tests.cc new file mode 100644 index 00000000..5f347505 --- /dev/null +++ b/modules/video_coding/main/source/jitter_estimator_tests.cc @@ -0,0 +1,160 @@ +/* Copyright (c) 2014 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/modules/video_coding/main/source/jitter_estimator.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/system_wrappers/interface/clock.h" + +namespace webrtc { + +class TestEstimator : public VCMJitterEstimator { + public: + explicit TestEstimator(bool exp_enabled) + : VCMJitterEstimator(&fake_clock_, 0, 0), + fake_clock_(0), + exp_enabled_(exp_enabled) {} + + virtual bool LowRateExperimentEnabled() { return exp_enabled_; } + + void AdvanceClock(int64_t microseconds) { + fake_clock_.AdvanceTimeMicroseconds(microseconds); + } + + private: + SimulatedClock fake_clock_; + const bool exp_enabled_; +}; + +class TestVCMJitterEstimator : public ::testing::Test { + protected: + TestVCMJitterEstimator() + : regular_estimator_(false), low_rate_estimator_(true) {} + + virtual void SetUp() { regular_estimator_.Reset(); } + + TestEstimator regular_estimator_; + TestEstimator low_rate_estimator_; +}; + +// Generates some simple test data in the form of a sawtooth wave. +class ValueGenerator { + public: + ValueGenerator(int32_t amplitude) : amplitude_(amplitude), counter_(0) {} + virtual ~ValueGenerator() {} + + int64_t Delay() { return ((counter_ % 11) - 5) * amplitude_; } + + uint32_t FrameSize() { return 1000 + Delay(); } + + void Advance() { ++counter_; } + + private: + const int32_t amplitude_; + int64_t counter_; +}; + +// 5 fps, disable jitter delay altogether. +TEST_F(TestVCMJitterEstimator, TestLowRate) { + ValueGenerator gen(10); + uint64_t time_delta = 1000000 / 5; + for (int i = 0; i < 60; ++i) { + regular_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize()); + regular_estimator_.AdvanceClock(time_delta); + low_rate_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize()); + low_rate_estimator_.AdvanceClock(time_delta); + EXPECT_GT(regular_estimator_.GetJitterEstimate(0), 0); + if (i > 2) + EXPECT_EQ(low_rate_estimator_.GetJitterEstimate(0), 0); + gen.Advance(); + } +} + +// 8 fps, steady state estimate should be in interpolated interval between 0 +// and value of previous method. +TEST_F(TestVCMJitterEstimator, TestMidRate) { + ValueGenerator gen(10); + uint64_t time_delta = 1000000 / 8; + for (int i = 0; i < 60; ++i) { + regular_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize()); + regular_estimator_.AdvanceClock(time_delta); + low_rate_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize()); + low_rate_estimator_.AdvanceClock(time_delta); + EXPECT_GT(regular_estimator_.GetJitterEstimate(0), 0); + EXPECT_GT(low_rate_estimator_.GetJitterEstimate(0), 0); + EXPECT_GE(regular_estimator_.GetJitterEstimate(0), + low_rate_estimator_.GetJitterEstimate(0)); + gen.Advance(); + } +} + +// 30 fps, steady state estimate should be same as previous method. +TEST_F(TestVCMJitterEstimator, TestHighRate) { + ValueGenerator gen(10); + uint64_t time_delta = 1000000 / 30; + for (int i = 0; i < 60; ++i) { + regular_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize()); + regular_estimator_.AdvanceClock(time_delta); + low_rate_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize()); + low_rate_estimator_.AdvanceClock(time_delta); + EXPECT_EQ(regular_estimator_.GetJitterEstimate(0), + low_rate_estimator_.GetJitterEstimate(0)); + gen.Advance(); + } +} + +// 10 fps, high jitter then low jitter. Low rate estimator should converge +// faster to low noise estimate. +TEST_F(TestVCMJitterEstimator, TestConvergence) { + // Reach a steady state with high noise. + ValueGenerator gen(50); + uint64_t time_delta = 1000000 / 10; + for (int i = 0; i < 100; ++i) { + regular_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize()); + regular_estimator_.AdvanceClock(time_delta * 2); + low_rate_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize()); + low_rate_estimator_.AdvanceClock(time_delta * 2); + gen.Advance(); + } + + int threshold = regular_estimator_.GetJitterEstimate(0) / 2; + + // New generator with zero noise. + ValueGenerator low_gen(0); + int regular_iterations = 0; + int low_rate_iterations = 0; + for (int i = 0; i < 500; ++i) { + if (regular_iterations == 0) { + regular_estimator_.UpdateEstimate(low_gen.Delay(), low_gen.FrameSize()); + regular_estimator_.AdvanceClock(time_delta); + if (regular_estimator_.GetJitterEstimate(0) < threshold) { + regular_iterations = i; + } + } + + if (low_rate_iterations == 0) { + low_rate_estimator_.UpdateEstimate(low_gen.Delay(), low_gen.FrameSize()); + low_rate_estimator_.AdvanceClock(time_delta); + if (low_rate_estimator_.GetJitterEstimate(0) < threshold) { + low_rate_iterations = i; + } + } + + if (regular_iterations != 0 && low_rate_iterations != 0) { + break; + } + + gen.Advance(); + } + + EXPECT_NE(regular_iterations, 0); + EXPECT_NE(low_rate_iterations, 0); + EXPECT_LE(low_rate_iterations, regular_iterations); +} +} |