summaryrefslogtreecommitdiffstats
path: root/chromecast
diff options
context:
space:
mode:
authordamienv <damienv@chromium.org>2014-08-29 19:27:59 -0700
committerCommit bot <commit-bot@chromium.org>2014-08-30 02:34:59 +0000
commit63429df7b27fefd8d7af9f73986b3e08b343a430 (patch)
tree7d2901e51391ff75ce82110e482b8527f5cf5632 /chromecast
parent26c2820646e60e9044cc5bddce700772423367b5 (diff)
downloadchromium_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.gyp8
-rw-r--r--chromecast/media/cma/base/buffering_controller.cc194
-rw-r--r--chromecast/media/cma/base/buffering_controller.h108
-rw-r--r--chromecast/media/cma/base/buffering_controller_unittest.cc133
-rw-r--r--chromecast/media/cma/base/buffering_state.cc129
-rw-r--r--chromecast/media/cma/base/buffering_state.h138
-rw-r--r--chromecast/media/cma/base/cma_logging.h23
-rw-r--r--chromecast/media/cma/base/run_all_unittests.cc40
-rw-r--r--chromecast/media/media.gyp50
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',
+ ],
+ },
+ ],
+}