diff options
author | damienv <damienv@chromium.org> | 2014-08-29 19:27:59 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-08-30 02:34:59 +0000 |
commit | 63429df7b27fefd8d7af9f73986b3e08b343a430 (patch) | |
tree | 7d2901e51391ff75ce82110e482b8527f5cf5632 /chromecast | |
parent | 26c2820646e60e9044cc5bddce700772423367b5 (diff) | |
download | chromium_src-63429df7b27fefd8d7af9f73986b3e08b343a430.zip chromium_src-63429df7b27fefd8d7af9f73986b3e08b343a430.tar.gz chromium_src-63429df7b27fefd8d7af9f73986b3e08b343a430.tar.bz2 |
Add a buffering controller for Chromecast.
The buffering controller serves two purposes:
- for URL playback, this will smooth playback when network
condition is not good enough. This basically avoids continuous
stuttering in that case.
- for MSE playback, it stops playback when either audio or video
underruns (which is a special case of the low buffer level state).
BUG=408189
Review URL: https://codereview.chromium.org/509213002
Cr-Commit-Position: refs/heads/master@{#292764}
Diffstat (limited to 'chromecast')
-rw-r--r-- | chromecast/chromecast.gyp | 8 | ||||
-rw-r--r-- | chromecast/media/cma/base/buffering_controller.cc | 194 | ||||
-rw-r--r-- | chromecast/media/cma/base/buffering_controller.h | 108 | ||||
-rw-r--r-- | chromecast/media/cma/base/buffering_controller_unittest.cc | 133 | ||||
-rw-r--r-- | chromecast/media/cma/base/buffering_state.cc | 129 | ||||
-rw-r--r-- | chromecast/media/cma/base/buffering_state.h | 138 | ||||
-rw-r--r-- | chromecast/media/cma/base/cma_logging.h | 23 | ||||
-rw-r--r-- | chromecast/media/cma/base/run_all_unittests.cc | 40 | ||||
-rw-r--r-- | chromecast/media/media.gyp | 50 |
9 files changed, 823 insertions, 0 deletions
diff --git a/chromecast/chromecast.gyp b/chromecast/chromecast.gyp index 3c744d6..e4f7cc2 100644 --- a/chromecast/chromecast.gyp +++ b/chromecast/chromecast.gyp @@ -177,6 +177,7 @@ 'cast_version_header', 'chromecast_locales.gyp:chromecast_locales_pak', 'chromecast_locales.gyp:chromecast_settings', + 'media/media.gyp:cast_media', '../components/components.gyp:component_metrics_proto', '../content/content.gyp:content', '../content/content.gyp:content_app_browser', @@ -264,5 +265,12 @@ }, ], }, + { + 'target_name': 'cast_tests', + 'type': 'none', + 'dependencies': [ + 'media/media.gyp:cast_media_unittests', + ], + }, ], # end of targets } diff --git a/chromecast/media/cma/base/buffering_controller.cc b/chromecast/media/cma/base/buffering_controller.cc new file mode 100644 index 0000000..2adaf82 --- /dev/null +++ b/chromecast/media/cma/base/buffering_controller.cc @@ -0,0 +1,194 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/buffering_controller.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "chromecast/media/cma/base/buffering_state.h" +#include "chromecast/media/cma/base/cma_logging.h" +#include "media/base/buffers.h" + +namespace chromecast { +namespace media { + +BufferingController::BufferingController( + const scoped_refptr<BufferingConfig>& config, + const BufferingNotificationCB& buffering_notification_cb) + : config_(config), + buffering_notification_cb_(buffering_notification_cb), + is_buffering_(false), + begin_buffering_time_(base::Time()), + weak_factory_(this) { + weak_this_ = weak_factory_.GetWeakPtr(); + thread_checker_.DetachFromThread(); +} + +BufferingController::~BufferingController() { + // Some weak pointers might possibly be invalidated here. + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void BufferingController::UpdateHighLevelThreshold( + base::TimeDelta high_level_threshold) { + // Can only decrease the high level threshold. + if (high_level_threshold > config_->high_level()) + return; + CMALOG(kLogControl) << "High buffer threshold: " + << high_level_threshold.InMilliseconds(); + config_->set_high_level(high_level_threshold); + + // Make sure the low level threshold is somewhat consistent. + // Currently, we set it to one third of the high level threshold: + // this value could be adjusted in the future. + base::TimeDelta low_level_threshold = high_level_threshold / 3; + if (low_level_threshold <= config_->low_level()) { + CMALOG(kLogControl) << "Low buffer threshold: " + << low_level_threshold.InMilliseconds(); + config_->set_low_level(low_level_threshold); + } + + // Signal all the streams the config has changed. + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + (*it)->OnConfigChanged(); + } + + // Once all the streams have been notified, the buffering state must be + // updated (no notification is received from the streams). + OnBufferingStateChanged(false, false); +} + +scoped_refptr<BufferingState> BufferingController::AddStream() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Add a new stream to the list of streams being monitored. + scoped_refptr<BufferingState> buffering_state(new BufferingState( + config_, + base::Bind(&BufferingController::OnBufferingStateChanged, weak_this_, + false, false), + base::Bind(&BufferingController::UpdateHighLevelThreshold, weak_this_))); + stream_list_.push_back(buffering_state); + + // Update the state and force a notification to the streams. + // TODO(damienv): Should this be a PostTask ? + OnBufferingStateChanged(true, false); + + return buffering_state; +} + +void BufferingController::SetMediaTime(base::TimeDelta time) { + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + (*it)->SetMediaTime(time); + } +} + +base::TimeDelta BufferingController::GetMaxRenderingTime() const { + base::TimeDelta max_rendering_time(::media::kNoTimestamp()); + for (StreamList::const_iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + base::TimeDelta max_stream_rendering_time = + (*it)->GetMaxRenderingTime(); + if (max_stream_rendering_time == ::media::kNoTimestamp()) + return ::media::kNoTimestamp(); + if (max_rendering_time == ::media::kNoTimestamp() || + max_stream_rendering_time < max_rendering_time) { + max_rendering_time = max_stream_rendering_time; + } + } + return max_rendering_time; +} + +void BufferingController::Reset() { + DCHECK(thread_checker_.CalledOnValidThread()); + + is_buffering_ = false; + stream_list_.clear(); +} + +void BufferingController::OnBufferingStateChanged( + bool force_notification, bool buffering_timeout) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Log the state of each stream. + DumpState(); + + bool is_low_buffering = IsLowBufferLevel(); + bool is_high_buffering = !is_low_buffering; + if (!buffering_timeout) { + // Hysteresis: + // - to leave buffering, not only should we leave the low buffer level state + // but we should go to the high buffer level state (medium is not enough). + is_high_buffering = IsHighBufferLevel(); + } + + bool is_buffering_prv = is_buffering_; + if (is_buffering_) { + if (is_high_buffering) + is_buffering_ = false; + } else { + if (is_low_buffering) + is_buffering_ = true; + } + + // Start buffering. + if (is_buffering_ && !is_buffering_prv) { + begin_buffering_time_ = base::Time::Now(); + } + + // End buffering. + if (is_buffering_prv && !is_buffering_) { + // TODO(damienv): |buffering_user_time| could be a UMA histogram. + base::Time current_time = base::Time::Now(); + base::TimeDelta buffering_user_time = current_time - begin_buffering_time_; + CMALOG(kLogControl) + << "Buffering took: " + << buffering_user_time.InMilliseconds() << "ms"; + } + + if (is_buffering_prv != is_buffering_ || force_notification) + buffering_notification_cb_.Run(is_buffering_); +} + +bool BufferingController::IsHighBufferLevel() { + if (stream_list_.empty()) + return true; + + bool is_high_buffering = true; + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + BufferingState::State stream_state = (*it)->GetState(); + is_high_buffering = is_high_buffering && + ((stream_state == BufferingState::kHighLevel) || + (stream_state == BufferingState::kEosReached)); + } + return is_high_buffering; +} + +bool BufferingController::IsLowBufferLevel() { + if (stream_list_.empty()) + return false; + + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + BufferingState::State stream_state = (*it)->GetState(); + if (stream_state == BufferingState::kLowLevel) + return true; + } + + return false; +} + +void BufferingController::DumpState() const { + CMALOG(kLogControl) << __FUNCTION__; + for (StreamList::const_iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + CMALOG(kLogControl) << (*it)->ToString(); + } +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/buffering_controller.h b/chromecast/media/cma/base/buffering_controller.h new file mode 100644 index 0000000..0581651d --- /dev/null +++ b/chromecast/media/cma/base/buffering_controller.h @@ -0,0 +1,108 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_BUFFERING_CONTROLLER_H +#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_CONTROLLER_H + +#include <list> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" + +namespace chromecast { +namespace media { +class BufferingConfig; +class BufferingState; + +class BufferingController { + public: + typedef base::Callback<void(bool)> BufferingNotificationCB; + + // Creates a buffering controller where the conditions to trigger rebuffering + // are given by |config|. The whole point of the buffering controller is to + // derive a single buffering state from the buffering state of various + // streams. + // |buffering_notification_cb| is a callback invoked to inform about possible + // changes of the buffering state. + BufferingController( + const scoped_refptr<BufferingConfig>& config, + const BufferingNotificationCB& buffering_notification_cb); + ~BufferingController(); + + // Creates a buffering state for one stream. This state is added to the list + // of streams monitored by the buffering controller. + scoped_refptr<BufferingState> AddStream(); + + // Sets the playback time. + void SetMediaTime(base::TimeDelta time); + + // Returns the maximum media time available for rendering. + // Return kNoTimestamp() if unknown. + base::TimeDelta GetMaxRenderingTime() const; + + // Returns whether there is an active buffering phase. + bool IsBuffering() const { return is_buffering_; } + + // Resets the buffering controller. This includes removing all the streams + // that were previously added. + void Reset(); + + private: + // Invoked each time the buffering state of one of the streams has changed. + // If |force_notification| is set, |buffering_notification_cb_| is invoked + // regardless whether the buffering state has changed or not. + // If |buffering_timeout| is set, then the condition to leave the buffering + // state is relaxed (we don't want to wait more). + void OnBufferingStateChanged(bool force_notification, + bool buffering_timeout); + + // Updates the high buffer level threshold to |high_level_threshold| + // if needed. + // This condition is triggered when one of the stream reached its maximum + // capacity. In that case, to avoid possible race condition (the buffering + // controller waits for more data to come but the buffer is to small to + // accomodate additional data), the thresholds in |config_| are adjusted + // accordingly. + void UpdateHighLevelThreshold(base::TimeDelta high_level_threshold); + + // Determines the overall buffer level based on the buffer level of each + // stream. + bool IsHighBufferLevel(); + bool IsLowBufferLevel(); + + // Logs the state of the buffering controller. + void DumpState() const; + + base::ThreadChecker thread_checker_; + + // Settings used to determine when to start/stop buffering. + scoped_refptr<BufferingConfig> config_; + + // Callback invoked each time there is a change of the buffering state. + BufferingNotificationCB buffering_notification_cb_; + + // State of the buffering controller. + bool is_buffering_; + + // Start time of a re-buffering phase. + base::Time begin_buffering_time_; + + // Buffering level for each individual stream. + typedef std::list<scoped_refptr<BufferingState> > StreamList; + StreamList stream_list_; + + base::WeakPtrFactory<BufferingController> weak_factory_; + base::WeakPtr<BufferingController> weak_this_; + + DISALLOW_COPY_AND_ASSIGN(BufferingController); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_CONTROLLER_H diff --git a/chromecast/media/cma/base/buffering_controller_unittest.cc b/chromecast/media/cma/base/buffering_controller_unittest.cc new file mode 100644 index 0000000..75eaed9 --- /dev/null +++ b/chromecast/media/cma/base/buffering_controller_unittest.cc @@ -0,0 +1,133 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/buffering_controller.h" +#include "chromecast/media/cma/base/buffering_state.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +class MockBufferingControllerClient { + public: + MockBufferingControllerClient(); + ~MockBufferingControllerClient(); + + MOCK_METHOD1(OnBufferingNotification, void(bool is_buffering)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockBufferingControllerClient); +}; + +MockBufferingControllerClient::MockBufferingControllerClient() { +} + +MockBufferingControllerClient::~MockBufferingControllerClient() { +} + +} // namespace + +class BufferingControllerTest : public testing::Test { + public: + BufferingControllerTest(); + virtual ~BufferingControllerTest(); + + protected: + scoped_ptr<BufferingController> buffering_controller_; + + MockBufferingControllerClient client_; + + // Buffer level under the low level threshold. + base::TimeDelta d1_; + + // Buffer level between the low and the high level. + base::TimeDelta d2_; + + // Buffer level above the high level. + base::TimeDelta d3_; + + private: + DISALLOW_COPY_AND_ASSIGN(BufferingControllerTest); +}; + +BufferingControllerTest::BufferingControllerTest() { + base::TimeDelta low_level_threshold( + base::TimeDelta::FromMilliseconds(2000)); + base::TimeDelta high_level_threshold( + base::TimeDelta::FromMilliseconds(6000)); + + d1_ = low_level_threshold - base::TimeDelta::FromMilliseconds(50); + d2_ = (low_level_threshold + high_level_threshold) / 2; + d3_ = high_level_threshold + base::TimeDelta::FromMilliseconds(50); + + scoped_refptr<BufferingConfig> buffering_config( + new BufferingConfig(low_level_threshold, high_level_threshold)); + buffering_controller_.reset(new BufferingController( + buffering_config, + base::Bind(&MockBufferingControllerClient::OnBufferingNotification, + base::Unretained(&client_)))); +} + +BufferingControllerTest::~BufferingControllerTest() { +} + +TEST_F(BufferingControllerTest, OneStream_Typical) { + EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1); + scoped_refptr<BufferingState> buffering_state = + buffering_controller_->AddStream(); + buffering_state->SetMediaTime(base::TimeDelta()); + + // Simulate pre-buffering. + buffering_state->SetBufferedTime(d2_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kMediumLevel); + + EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1); + buffering_state->SetBufferedTime(d3_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kHighLevel); + + // Simulate some fluctuations of the buffering level. + buffering_state->SetBufferedTime(d2_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kMediumLevel); + + // Simulate an underrun. + EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1); + buffering_state->SetBufferedTime(d1_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kLowLevel); + + EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1); + buffering_state->SetBufferedTime(d3_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kHighLevel); + + // Simulate the end of stream. + buffering_state->NotifyEos(); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); + + buffering_state->SetBufferedTime(d2_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); + + buffering_state->SetBufferedTime(d1_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); +} + +TEST_F(BufferingControllerTest, OneStream_LeaveBufferingOnEos) { + EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1); + scoped_refptr<BufferingState> buffering_state = + buffering_controller_->AddStream(); + buffering_state->SetMediaTime(base::TimeDelta()); + + EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1); + buffering_state->NotifyEos(); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/buffering_state.cc b/chromecast/media/cma/base/buffering_state.cc new file mode 100644 index 0000000..e1fc49f --- /dev/null +++ b/chromecast/media/cma/base/buffering_state.cc @@ -0,0 +1,129 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/buffering_state.h" + +#include <sstream> + +#include "base/logging.h" +#include "media/base/buffers.h" + +namespace chromecast { +namespace media { + +BufferingConfig::BufferingConfig( + base::TimeDelta low_level_threshold, + base::TimeDelta high_level_threshold) + : low_level_threshold_(low_level_threshold), + high_level_threshold_(high_level_threshold) { +} + +BufferingConfig::~BufferingConfig() { +} + + +BufferingState::BufferingState( + const scoped_refptr<BufferingConfig>& config, + const base::Closure& state_changed_cb, + const HighLevelBufferCB& high_level_buffer_cb) + : config_(config), + state_changed_cb_(state_changed_cb), + high_level_buffer_cb_(high_level_buffer_cb), + state_(kLowLevel), + media_time_(::media::kNoTimestamp()), + max_rendering_time_(::media::kNoTimestamp()), + buffered_time_(::media::kNoTimestamp()) { +} + +BufferingState::~BufferingState() { +} + +void BufferingState::OnConfigChanged() { + state_ = GetBufferLevelState(); +} + +void BufferingState::SetMediaTime(base::TimeDelta media_time) { + media_time_ = media_time; + switch (state_) { + case kLowLevel: + case kMediumLevel: + case kHighLevel: + UpdateState(GetBufferLevelState()); + break; + case kEosReached: + break; + } +} + +void BufferingState::SetMaxRenderingTime(base::TimeDelta max_rendering_time) { + max_rendering_time_ = max_rendering_time; +} + +base::TimeDelta BufferingState::GetMaxRenderingTime() const { + return max_rendering_time_; +} + +void BufferingState::SetBufferedTime(base::TimeDelta buffered_time) { + buffered_time_ = buffered_time; + switch (state_) { + case kLowLevel: + case kMediumLevel: + case kHighLevel: + UpdateState(GetBufferLevelState()); + break; + case kEosReached: + break; + } +} + +void BufferingState::NotifyEos() { + UpdateState(kEosReached); +} + +void BufferingState::NotifyMaxCapacity(base::TimeDelta buffered_time) { + if (media_time_ == ::media::kNoTimestamp() || + buffered_time == ::media::kNoTimestamp()) { + LOG(WARNING) << "Max capacity with no timestamp"; + return; + } + base::TimeDelta buffer_duration = buffered_time - media_time_; + if (buffer_duration < config_->high_level()) + high_level_buffer_cb_.Run(buffer_duration); +} + +std::string BufferingState::ToString() const { + std::ostringstream s; + s << "state=" << state_ + << " media_time_ms=" << media_time_.InMilliseconds() + << " buffered_time_ms=" << buffered_time_.InMilliseconds() + << " low_level_ms=" << config_->low_level().InMilliseconds() + << " high_level_ms=" << config_->high_level().InMilliseconds(); + return s.str(); +} + +BufferingState::State BufferingState::GetBufferLevelState() const { + if (media_time_ == ::media::kNoTimestamp() || + buffered_time_ == ::media::kNoTimestamp()) { + return kLowLevel; + } + + base::TimeDelta buffer_duration = buffered_time_ - media_time_; + if (buffer_duration < config_->low_level()) + return kLowLevel; + if (buffer_duration >= config_->high_level()) + return kHighLevel; + return kMediumLevel; +} + +void BufferingState::UpdateState(State new_state) { + if (new_state == state_) + return; + + state_ = new_state; + if (!state_changed_cb_.is_null()) + state_changed_cb_.Run(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/buffering_state.h b/chromecast/media/cma/base/buffering_state.h new file mode 100644 index 0000000..ced8206 --- /dev/null +++ b/chromecast/media/cma/base/buffering_state.h @@ -0,0 +1,138 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_BUFFERING_STATE_H_ +#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_STATE_H_ + +#include <string> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" + +namespace chromecast { +namespace media { + +class BufferingConfig : public base::RefCountedThreadSafe<BufferingConfig> { + public: + BufferingConfig(base::TimeDelta low_level_threshold, + base::TimeDelta high_level_threshold); + + base::TimeDelta low_level() const { return low_level_threshold_; } + base::TimeDelta high_level() const { return high_level_threshold_; } + + void set_low_level(base::TimeDelta low_level) { + low_level_threshold_ = low_level; + } + void set_high_level(base::TimeDelta high_level) { + high_level_threshold_ = high_level; + } + + private: + friend class base::RefCountedThreadSafe<BufferingConfig>; + virtual ~BufferingConfig(); + + base::TimeDelta low_level_threshold_; + base::TimeDelta high_level_threshold_; + + DISALLOW_COPY_AND_ASSIGN(BufferingConfig); +}; + +class BufferingState + : public base::RefCountedThreadSafe<BufferingState> { + public: + typedef base::Callback<void(base::TimeDelta)> HighLevelBufferCB; + + enum State { + kLowLevel, + kMediumLevel, + kHighLevel, + kEosReached, + }; + + // Creates a new buffering state. The initial state is |kLowLevel|. + // |state_changed_cb| is used to notify about possible state changes. + // |high_level_buffer_cb| is used to adjust the high buffer threshold + // when the underlying buffer is not large enough to accomodate + // the current high buffer level. + BufferingState(const scoped_refptr<BufferingConfig>& config, + const base::Closure& state_changed_cb, + const HighLevelBufferCB& high_level_buffer_cb); + + // Returns the buffering state. + State GetState() const { return state_; } + + // Invoked when the buffering configuration has changed. + // Based on the new configuration, the buffering state might change. + // However, |state_changed_cb_| is not triggered in that case. + void OnConfigChanged(); + + // Sets the current rendering time for this stream. + void SetMediaTime(base::TimeDelta media_time); + + // Sets/gets the maximum rendering media time for this stream. + // The maximum rendering time is always lower than the buffered time. + void SetMaxRenderingTime(base::TimeDelta max_rendering_time); + base::TimeDelta GetMaxRenderingTime() const; + + // Sets the buffered time. + void SetBufferedTime(base::TimeDelta buffered_time); + + // Notifies the buffering state that all the frames for this stream have been + // buffered, i.e. the end of stream has been reached. + void NotifyEos(); + + // Notifies the buffering state the underlying buffer has reached + // its maximum capacity. + // The maximum frame timestamp in the buffer is given by |buffered_time|. + // Note: this timestamp can be different from the one provided through + // SetBufferedTime since SetBufferedTime takes the timestamp of a playable + // frame which is not necessarily the case here (e.g. missing key id). + void NotifyMaxCapacity(base::TimeDelta buffered_time); + + // Buffering state as a human readable string, for debugging. + std::string ToString() const; + + private: + friend class base::RefCountedThreadSafe<BufferingState>; + virtual ~BufferingState(); + + // Returns the state solely based on the buffered time. + State GetBufferLevelState() const; + + // Updates the state to |new_state|. + void UpdateState(State new_state); + + scoped_refptr<BufferingConfig> const config_; + + // Callback invoked each time there is a change of state. + base::Closure state_changed_cb_; + + // Callback invoked to adjust the high buffer level. + HighLevelBufferCB high_level_buffer_cb_; + + // State. + State state_; + + // Playback media time. + // Equal to kNoTimestamp() when not known. + base::TimeDelta media_time_; + + // Maximum rendering media time. + // This corresponds to the timestamp of the last frame sent to the hardware + // decoder/renderer. + base::TimeDelta max_rendering_time_; + + // Buffered media time. + // Equal to kNoTimestamp() when not known. + base::TimeDelta buffered_time_; + + DISALLOW_COPY_AND_ASSIGN(BufferingState); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_STATE_H_ diff --git a/chromecast/media/cma/base/cma_logging.h b/chromecast/media/cma/base/cma_logging.h new file mode 100644 index 0000000..1743cea --- /dev/null +++ b/chromecast/media/cma/base/cma_logging.h @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_CMA_LOGGING_H_ +#define CHROMECAST_MEDIA_CMA_BASE_CMA_LOGGING_H_ + +#include "base/logging.h" + +namespace chromecast { +namespace media { + +#define CMALOG(loglevel) VLOG(loglevel) + +enum { + kLogControl = 2, + kLogFrame = 3 +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_CMA_LOGGING_H_ diff --git a/chromecast/media/cma/base/run_all_unittests.cc b/chromecast/media/cma/base/run_all_unittests.cc new file mode 100644 index 0000000..07b9e2b --- /dev/null +++ b/chromecast/media/cma/base/run_all_unittests.cc @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "build/build_config.h" +#include "media/base/media.h" + +#if defined(OS_ANDROID) +#error "CMA not supported on Android" +#endif + +class CmaTestSuite : public base::TestSuite { + public: + // Note: the base class constructor creates an AtExitManager. + CmaTestSuite(int argc, char** argv) : TestSuite(argc, argv) {} + virtual ~CmaTestSuite() {} + + protected: + virtual void Initialize() OVERRIDE; +}; + +void CmaTestSuite::Initialize() { + // Run TestSuite::Initialize first so that logging is initialized. + base::TestSuite::Initialize(); + + // Initialize the FFMpeg library. + // Note: at this time, AtExitManager is already present. + media::InitializeMediaLibraryForTesting(); +} + +int main(int argc, char** argv) { + CmaTestSuite test_suite(argc, argv); + + return base::LaunchUnitTests( + argc, argv, base::Bind(&CmaTestSuite::Run, + base::Unretained(&test_suite))); +} diff --git a/chromecast/media/media.gyp b/chromecast/media/media.gyp new file mode 100644 index 0000000..c6f7030 --- /dev/null +++ b/chromecast/media/media.gyp @@ -0,0 +1,50 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'cma_base', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + '../../media/media.gyp:media', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'cma/base/buffering_controller.cc', + 'cma/base/buffering_controller.h', + 'cma/base/buffering_state.cc', + 'cma/base/buffering_state.h', + 'cma/base/cma_logging.h', + ], + }, + { + 'target_name': 'cast_media', + 'type': 'none', + 'dependencies': [ + 'cma_base', + ], + }, + { + 'target_name': 'cast_media_unittests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'cast_media', + '../../base/base.gyp:base', + '../../base/base.gyp:base_i18n', + '../../base/base.gyp:test_support_base', + '../../testing/gmock.gyp:gmock', + '../../testing/gtest.gyp:gtest', + '../../testing/gtest.gyp:gtest_main', + ], + 'sources': [ + 'cma/base/buffering_controller_unittest.cc', + 'cma/base/run_all_unittests.cc', + ], + }, + ], +} |