Commit 63429df7 authored by damienv's avatar damienv Committed by Commit bot

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}
parent 26c28206
......@@ -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
}
// 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
// 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
// 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
// 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
// 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_
// 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_
// 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)));
}
# 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',
],
},
],
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment