summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormiu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-09 01:41:55 +0000
committermiu@chromium.org <miu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-09 01:41:55 +0000
commite81fe1a173230947493bdfd885c8e518a343a90f (patch)
treee6f0d9dad174fbe82f1e0d7b55022a159939c721
parent963a91b1fda682b844b9ece88d2070862f51893f (diff)
downloadchromium_src-e81fe1a173230947493bdfd885c8e518a343a90f.zip
chromium_src-e81fe1a173230947493bdfd885c8e518a343a90f.tar.gz
chromium_src-e81fe1a173230947493bdfd885c8e518a343a90f.tar.bz2
Cast Streaming API end-to-end browser_test.
Similar to the TabCaptureApi.EndToEnd test: An extension is run that generates a rotating cycle of colors and audio tones, and the result is captured and streamed to an in-process Cast receiver. In addition: 1. Refactored existing code out of the cast_receiver_app into a shared InProcessReceiver class. 2. Added a convenient StandaloneCastEnvironment for test apps. 3. Got rid of the cast_receiver_app prompts since the defaults are used >99% of the time. Using the --enable-prompts command line arg will show them again. 4. Minor clean-ups in files touched. NOTE: The new EndToEnd test is being submitted as disabled while outstanding bugs are worked by the team. Will re-enable once the implementation is sufficiently stable for the bots. R=hubbe@chromium.org, kalman@chromium.org Review URL: https://codereview.chromium.org/184813009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@255805 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/api/cast_streaming/DEPS3
-rw-r--r--chrome/browser/extensions/api/cast_streaming/OWNERS2
-rw-r--r--chrome/browser/extensions/api/cast_streaming/cast_streaming_apitest.cc332
-rw-r--r--chrome/browser/extensions/cast_streaming_apitest.cc35
-rw-r--r--chrome/chrome_tests.gypi3
-rw-r--r--chrome/test/data/extensions/api_test/cast_streaming/end_to_end_sender.html8
-rw-r--r--chrome/test/data/extensions/api_test/cast_streaming/end_to_end_sender.js143
-rw-r--r--chrome/test/data/extensions/api_test/cast_streaming/manifest.json2
-rw-r--r--media/cast/cast_environment.cc55
-rw-r--r--media/cast/cast_environment.h23
-rw-r--r--media/cast/cast_receiver_impl.cc3
-rw-r--r--media/cast/logging/logging_impl.cc35
-rw-r--r--media/cast/logging/logging_impl.h12
-rw-r--r--media/cast/logging/logging_impl_unittest.cc5
-rw-r--r--media/cast/test/linux_output_window.cc9
-rw-r--r--media/cast/test/linux_output_window.h1
-rw-r--r--media/cast/test/receiver.cc144
-rw-r--r--media/cast/test/utility/default_config.cc37
-rw-r--r--media/cast/test/utility/default_config.h26
-rw-r--r--media/cast/test/utility/in_process_receiver.cc126
-rw-r--r--media/cast/test/utility/in_process_receiver.h113
-rw-r--r--media/cast/test/utility/input_builder.cc6
-rw-r--r--media/cast/test/utility/standalone_cast_environment.cc59
-rw-r--r--media/cast/test/utility/standalone_cast_environment.h42
-rw-r--r--media/cast/test/utility/utility.gyp14
-rw-r--r--media/cast/transport/cast_transport_sender_impl.cc2
26 files changed, 1018 insertions, 222 deletions
diff --git a/chrome/browser/extensions/api/cast_streaming/DEPS b/chrome/browser/extensions/api/cast_streaming/DEPS
new file mode 100644
index 0000000..85f9d1f
--- /dev/null
+++ b/chrome/browser/extensions/api/cast_streaming/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+media/cast",
+]
diff --git a/chrome/browser/extensions/api/cast_streaming/OWNERS b/chrome/browser/extensions/api/cast_streaming/OWNERS
new file mode 100644
index 0000000..586a2f5
--- /dev/null
+++ b/chrome/browser/extensions/api/cast_streaming/OWNERS
@@ -0,0 +1,2 @@
+hubbe@chromium.org
+miu@chromium.org
diff --git a/chrome/browser/extensions/api/cast_streaming/cast_streaming_apitest.cc b/chrome/browser/extensions/api/cast_streaming/cast_streaming_apitest.cc
new file mode 100644
index 0000000..4eb1c6f
--- /dev/null
+++ b/chrome/browser/extensions/api/cast_streaming/cast_streaming_apitest.cc
@@ -0,0 +1,332 @@
+// Copyright 2013 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 <algorithm>
+#include <cmath>
+
+#include "base/command_line.h"
+#include "base/float_util.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/common/content_switches.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/video_frame.h"
+#include "media/cast/cast_config.h"
+#include "media/cast/cast_environment.h"
+#include "media/cast/test/utility/audio_utility.h"
+#include "media/cast/test/utility/default_config.h"
+#include "media/cast/test/utility/in_process_receiver.h"
+#include "media/cast/test/utility/standalone_cast_environment.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/rand_callback.h"
+#include "net/udp/udp_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class CastStreamingApiTest : public ExtensionApiTest {
+ public:
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ ExtensionApiTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitchASCII(switches::kWhitelistedExtensionID,
+ "ddchlicdkolnonkihahngkmmmjnjlkkf");
+ }
+};
+
+// Test running the test extension for Cast Mirroring API.
+IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, Basics) {
+ ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "basics.html")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, Stats) {
+ ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "stats.html")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, BadLogging) {
+ ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "bad_logging.html"))
+ << message_;
+}
+
+namespace {
+
+// An in-process Cast receiver that examines the audio/video frames being
+// received for expected colors and tones. Used in
+// CastStreamingApiTest.EndToEnd, below.
+class TestPatternReceiver : public media::cast::InProcessReceiver {
+ public:
+ explicit TestPatternReceiver(
+ const scoped_refptr<media::cast::CastEnvironment>& cast_environment,
+ const net::IPEndPoint& local_end_point)
+ : InProcessReceiver(cast_environment,
+ local_end_point,
+ net::IPEndPoint(),
+ media::cast::GetDefaultAudioReceiverConfig(),
+ media::cast::GetDefaultVideoReceiverConfig()),
+ target_tone_frequency_(0),
+ current_tone_frequency_(0.0f) {
+ memset(&target_color_, 0, sizeof(target_color_));
+ memset(&current_color_, 0, sizeof(current_color_));
+ }
+
+ virtual ~TestPatternReceiver() {}
+
+ // Blocks the caller until this receiver has seen both |yuv_color| and
+ // |tone_frequency| consistently for the given |duration|.
+ void WaitForColorAndTone(const uint8 yuv_color[3],
+ int tone_frequency,
+ base::TimeDelta duration) {
+ LOG(INFO) << "Waiting for test pattern: color=yuv("
+ << static_cast<int>(yuv_color[0]) << ", "
+ << static_cast<int>(yuv_color[1]) << ", "
+ << static_cast<int>(yuv_color[2])
+ << "), tone_frequency=" << tone_frequency << " Hz";
+
+ base::RunLoop run_loop;
+ cast_env()->PostTask(
+ media::cast::CastEnvironment::MAIN,
+ FROM_HERE,
+ base::Bind(&TestPatternReceiver::NotifyOnceMatched,
+ base::Unretained(this),
+ yuv_color,
+ tone_frequency,
+ duration,
+ media::BindToCurrentLoop(run_loop.QuitClosure())));
+ run_loop.Run();
+ }
+
+ private:
+ // Resets tracking data and sets the match duration and callback.
+ void NotifyOnceMatched(const uint8 yuv_color[3],
+ int tone_frequency,
+ base::TimeDelta match_duration,
+ const base::Closure& matched_callback) {
+ DCHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
+
+ match_duration_ = match_duration;
+ matched_callback_ = matched_callback;
+ target_color_[0] = yuv_color[0];
+ target_color_[1] = yuv_color[1];
+ target_color_[2] = yuv_color[2];
+ target_tone_frequency_ = tone_frequency;
+ first_time_near_target_color_ = base::TimeTicks();
+ first_time_near_target_tone_ = base::TimeTicks();
+ }
+
+ // Runs |matched_callback_| once both color and tone have been matched for the
+ // required |match_duration_|.
+ void NotifyIfMatched() {
+ DCHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
+
+ // TODO(miu): Check audio tone too, once audio is fixed in the library.
+ // http://crbug.com/349295
+ if (first_time_near_target_color_.is_null() ||
+ /*first_time_near_target_tone_.is_null()*/ false)
+ return;
+ const base::TimeTicks now = cast_env()->Clock()->NowTicks();
+ if ((now - first_time_near_target_color_) >= match_duration_ &&
+ /*(now - first_time_near_target_tone_) >= match_duration_*/ true) {
+ matched_callback_.Run();
+ }
+ }
+
+ // Invoked by InProcessReceiver for each received audio frame.
+ virtual void OnAudioFrame(scoped_ptr<media::cast::PcmAudioFrame> audio_frame,
+ const base::TimeTicks& playout_time) OVERRIDE {
+ DCHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
+
+ if (audio_frame->samples.empty()) {
+ NOTREACHED() << "OnAudioFrame called with no samples?!?";
+ return;
+ }
+
+ // Assume the audio signal is a single sine wave (it can have some
+ // low-amplitude noise). Count zero crossings, and extrapolate the
+ // frequency of the sine wave in |audio_frame|.
+ const int crossings = media::cast::CountZeroCrossings(audio_frame->samples);
+ const float seconds_per_frame = audio_frame->samples.size() /
+ static_cast<float>(audio_frame->frequency);
+ const float frequency_in_frame = crossings / seconds_per_frame;
+
+ const float kAveragingWeight = 0.1f;
+ UpdateExponentialMovingAverage(
+ kAveragingWeight, frequency_in_frame, &current_tone_frequency_);
+ VLOG(1) << "Current audio tone frequency: " << current_tone_frequency_;
+
+ const float kTargetWindowHz = 20;
+ // Update the time at which the current tone started falling within
+ // kTargetWindowHz of the target tone.
+ if (fabsf(current_tone_frequency_ - target_tone_frequency_) <
+ kTargetWindowHz) {
+ if (first_time_near_target_tone_.is_null())
+ first_time_near_target_tone_ = cast_env()->Clock()->NowTicks();
+ NotifyIfMatched();
+ } else {
+ first_time_near_target_tone_ = base::TimeTicks();
+ }
+ }
+
+ virtual void OnVideoFrame(const scoped_refptr<media::VideoFrame>& video_frame,
+ const base::TimeTicks& render_time) OVERRIDE {
+ DCHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
+
+ CHECK(video_frame->format() == media::VideoFrame::YV12 ||
+ video_frame->format() == media::VideoFrame::I420 ||
+ video_frame->format() == media::VideoFrame::YV12A);
+
+ // Note: We take the median value of each plane because the test image will
+ // contain mostly a solid color plus some "cruft" which is the "Testing..."
+ // text in the upper-left corner of the video frame. In other words, we
+ // want to read "the most common color."
+ const int kPlanes[] = {media::VideoFrame::kYPlane,
+ media::VideoFrame::kUPlane,
+ media::VideoFrame::kVPlane};
+ for (size_t i = 0; i < arraysize(kPlanes); ++i) {
+ current_color_[i] =
+ ComputeMedianIntensityInPlane(video_frame->row_bytes(kPlanes[i]),
+ video_frame->rows(kPlanes[i]),
+ video_frame->stride(kPlanes[i]),
+ video_frame->data(kPlanes[i]));
+ }
+
+ VLOG(1) << "Current video color: yuv(" << current_color_[0] << ", "
+ << current_color_[1] << ", " << current_color_[2] << ')';
+
+ const float kTargetWindow = 10.0f;
+ // Update the time at which all color channels started falling within
+ // kTargetWindow of the target.
+ if (fabsf(current_color_[0] - target_color_[0]) < kTargetWindow &&
+ fabsf(current_color_[1] - target_color_[1]) < kTargetWindow &&
+ fabsf(current_color_[2] - target_color_[2]) < kTargetWindow) {
+ if (first_time_near_target_color_.is_null())
+ first_time_near_target_color_ = cast_env()->Clock()->NowTicks();
+ NotifyIfMatched();
+ } else {
+ first_time_near_target_color_ = base::TimeTicks();
+ }
+ }
+
+ static void UpdateExponentialMovingAverage(float weight,
+ float sample_value,
+ float* average) {
+ *average = weight * sample_value + (1.0f - weight) * (*average);
+ CHECK(base::IsFinite(*average));
+ }
+
+ static uint8 ComputeMedianIntensityInPlane(int width,
+ int height,
+ int stride,
+ uint8* data) {
+ const int num_pixels = width * height;
+ if (num_pixels <= 0)
+ return 0;
+ // If necessary, re-pack the pixels such that the stride is equal to the
+ // width.
+ if (width < stride) {
+ for (int y = 1; y < height; ++y) {
+ uint8* const src = data + y * stride;
+ uint8* const dest = data + y * width;
+ memmove(dest, src, width);
+ }
+ }
+ const size_t middle_idx = num_pixels / 2;
+ std::nth_element(data, data + middle_idx, data + num_pixels);
+ return data[middle_idx];
+ }
+
+ base::TimeDelta match_duration_;
+ base::Closure matched_callback_;
+
+ float target_color_[3]; // Y, U, V
+ float target_tone_frequency_;
+
+ float current_color_[3]; // Y, U, V
+ base::TimeTicks first_time_near_target_color_;
+ float current_tone_frequency_;
+ base::TimeTicks first_time_near_target_tone_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestPatternReceiver);
+};
+
+} // namespace
+
+class CastStreamingApiTestWithPixelOutput : public CastStreamingApiTest {
+ virtual void SetUp() OVERRIDE {
+ EnablePixelOutput();
+ CastStreamingApiTest::SetUp();
+ }
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ command_line->AppendSwitchASCII(switches::kWindowSize, "128,128");
+ CastStreamingApiTest::SetUpCommandLine(command_line);
+ }
+};
+
+// Tests the Cast streaming API and its basic functionality end-to-end. An
+// extension subtest is run to generate test content, capture that content, and
+// use the API to send it out. At the same time, this test launches an
+// in-process Cast receiver, listening on a localhost UDP socket, to receive the
+// content and check whether it matches expectations.
+//
+// Note: This test is disabled until outstanding bugs are fixed and the
+// media/cast library has achieved sufficient stability.
+// http://crbug.com/349599
+IN_PROC_BROWSER_TEST_F(CastStreamingApiTestWithPixelOutput, DISABLED_EndToEnd) {
+ // Determine a unused UDP port for the in-process receiver to listen on.
+ // Method: Bind a UDP socket on port 0, and then check which port the
+ // operating system assigned to it.
+ net::IPAddressNumber localhost;
+ localhost.push_back(127);
+ localhost.push_back(0);
+ localhost.push_back(0);
+ localhost.push_back(1);
+ scoped_ptr<net::UDPSocket> receive_socket(
+ new net::UDPSocket(net::DatagramSocket::DEFAULT_BIND,
+ net::RandIntCallback(),
+ NULL,
+ net::NetLog::Source()));
+ receive_socket->AllowAddressReuse();
+ ASSERT_EQ(net::OK, receive_socket->Bind(net::IPEndPoint(localhost, 0)));
+ net::IPEndPoint receiver_end_point;
+ ASSERT_EQ(net::OK, receive_socket->GetLocalAddress(&receiver_end_point));
+ receive_socket.reset();
+
+ // Start the in-process receiver that examines audio/video for the expected
+ // test patterns.
+ const scoped_refptr<media::cast::StandaloneCastEnvironment> cast_environment(
+ new media::cast::StandaloneCastEnvironment(
+ media::cast::CastLoggingConfig()));
+ TestPatternReceiver* const receiver =
+ new TestPatternReceiver(cast_environment, receiver_end_point);
+ receiver->Start();
+
+ // Launch the page that: 1) renders the source content; 2) uses the
+ // chrome.tabCapture and chrome.cast.streaming APIs to capture its content and
+ // stream using Cast; and 3) calls chrome.test.succeed() once it is
+ // operational.
+ const std::string page_url = base::StringPrintf(
+ "end_to_end_sender.html?port=%d", receiver_end_point.port());
+ ASSERT_TRUE(RunExtensionSubtest("cast_streaming", page_url)) << message_;
+
+ // Examine the Cast receiver for expected audio/video test patterns. The
+ // colors and tones specified here must match those in end_to_end_sender.js.
+ const uint8 kRedInYUV[3] = {82, 90, 240}; // rgb(255, 0, 0)
+ const uint8 kGreenInYUV[3] = {145, 54, 34}; // rgb(0, 255, 0)
+ const uint8 kBlueInYUV[3] = {41, 240, 110}; // rgb(0, 0, 255)
+ const base::TimeDelta kOneHalfSecond = base::TimeDelta::FromMilliseconds(500);
+ receiver->WaitForColorAndTone(kRedInYUV, 200 /* Hz */, kOneHalfSecond);
+ receiver->WaitForColorAndTone(kGreenInYUV, 500 /* Hz */, kOneHalfSecond);
+ receiver->WaitForColorAndTone(kBlueInYUV, 1800 /* Hz */, kOneHalfSecond);
+
+ // TODO(miu): Uncomment once GetWeakPtr() NULL crash in PacedSender is fixed
+ // (see http://crbug.com/349786):
+ // receiver->DestroySoon();
+ cast_environment->Shutdown();
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/cast_streaming_apitest.cc b/chrome/browser/extensions/cast_streaming_apitest.cc
deleted file mode 100644
index 842b571..0000000
--- a/chrome/browser/extensions/cast_streaming_apitest.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2013 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/command_line.h"
-#include "chrome/browser/extensions/extension_apitest.h"
-#include "chrome/common/chrome_switches.h"
-#include "content/public/common/content_switches.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace extensions {
-
-class CastStreamingApiTest : public ExtensionApiTest {
- virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
- ExtensionApiTest::SetUpCommandLine(command_line);
- command_line->AppendSwitchASCII(
- switches::kWhitelistedExtensionID,
- "ddchlicdkolnonkihahngkmmmjnjlkkf");
- }
-};
-
-// Test running the test extension for Cast Mirroring API.
-IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, Basics) {
- ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "basics.html"));
-}
-
-IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, Stats) {
- ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "stats.html"));
-}
-
-IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, BadLogging) {
- ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "bad_logging.html"));
-}
-
-} // namespace extensions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 042786f..a86e29f 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -895,6 +895,7 @@
'../components/component_strings.gyp:component_strings',
'../device/bluetooth/bluetooth.gyp:device_bluetooth_mocks',
'../google_apis/google_apis.gyp:google_apis_test_support',
+ '../media/cast/test/utility/utility.gyp:cast_test_utility',
'../net/net.gyp:net',
'../net/net.gyp:net_test_support',
'../skia/skia.gyp:skia',
@@ -1101,6 +1102,7 @@
'browser/extensions/api/bookmarks/bookmark_apitest.cc',
'browser/extensions/api/browsing_data/browsing_data_test.cc',
'browser/extensions/api/cast_channel/cast_channel_apitest.cc',
+ 'browser/extensions/api/cast_streaming/cast_streaming_apitest.cc',
'browser/extensions/api/cloud_print_private/cloud_print_private_apitest.cc',
'browser/extensions/api/command_line_private/command_line_private_apitest.cc',
'browser/extensions/api/commands/command_service_browsertest.cc',
@@ -1197,7 +1199,6 @@
'browser/extensions/browsertest_util.cc',
'browser/extensions/browsertest_util.h',
'browser/extensions/browsertest_util_browsertest.cc',
- 'browser/extensions/cast_streaming_apitest.cc',
'browser/extensions/chrome_app_api_browsertest.cc',
'browser/extensions/content_script_apitest.cc',
'browser/extensions/content_security_policy_apitest.cc',
diff --git a/chrome/test/data/extensions/api_test/cast_streaming/end_to_end_sender.html b/chrome/test/data/extensions/api_test/cast_streaming/end_to_end_sender.html
new file mode 100644
index 0000000..8ed56af
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/cast_streaming/end_to_end_sender.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<script src="end_to_end_sender.js"></script>
+</head>
+<body>
+<h1 id="message">Test not started yet.</h1>
+</body>
+</html>
diff --git a/chrome/test/data/extensions/api_test/cast_streaming/end_to_end_sender.js b/chrome/test/data/extensions/api_test/cast_streaming/end_to_end_sender.js
new file mode 100644
index 0000000..aac11da
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/cast_streaming/end_to_end_sender.js
@@ -0,0 +1,143 @@
+// Copyright (c) 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.
+
+// This code uses the tab capture and Cast streaming APIs to capture the content
+// and send it to a Cast receiver end-point controlled by
+// CastStreamingApiTestcode. It generates audio/video test patterns that rotate
+// cyclicly, and these test patterns are checked for by an in-process Cast
+// receiver to confirm the correct end-to-end functionality of the Cast
+// streaming API.
+//
+// Once everything is set up and fully operational, chrome.test.succeed() is
+// invoked as a signal for the end-to-end testing to proceed. If any step in
+// the setup process fails, chrome.test.fail() is invoked.
+
+// The test pattern cycles as a color fill of red, then green, then blue; paired
+// with successively higher-frequency tones.
+var colors = [ [ 255, 0, 0 ], [ 0, 255, 0 ], [ 0, 0, 255 ] ];
+var freqs = [ 200, 500, 1800 ];
+var curTestIdx = 0;
+
+function updateTestPattern() {
+ if (document.body && document.body.style) // Check that page is loaded.
+ document.body.style.backgroundColor = "rgb(" + colors[curTestIdx] + ")";
+
+ // Important: Blink the testing message so that the capture pipeline will
+ // observe drawing updates and continue to produce video frames.
+ var message = document.getElementById("message");
+ if (message && !message.blinkInterval) {
+ message.innerHTML = "Testing...";
+ message.blinkInterval = setInterval(
+ function toggleVisibility() {
+ message.style.visibility =
+ message.style.visibility == "hidden" ? "visible" : "hidden";
+ },
+ 125);
+ }
+
+ if (!this.audioContext) {
+ this.audioContext = new webkitAudioContext();
+ this.gainNode = this.audioContext.createGainNode();
+ this.gainNode.gain.value = 0.5;
+ this.gainNode.connect(this.audioContext.destination);
+ } else {
+ this.oscillator.disconnect();
+ }
+ // Note: We recreate the oscillator each time because this switches the audio
+ // frequency immediately. Re-using the same oscillator tends to take several
+ // hundred milliseconds to ramp-up/down the frequency.
+ this.oscillator = audioContext.createOscillator();
+ this.oscillator.type = "sine";
+ this.oscillator.frequency.value = freqs[curTestIdx];
+ this.oscillator.connect(gainNode);
+ this.oscillator.noteOn(0);
+}
+
+// Calls updateTestPattern(), then waits and calls itself again to advance to
+// the next one.
+function runTestPatternLoop() {
+ updateTestPattern();
+ if (!this.curAdvanceWaitTimeMillis) {
+ this.curAdvanceWaitTimeMillis = 750;
+ }
+ setTimeout(
+ function advanceTestPattern() {
+ ++curTestIdx;
+ if (curTestIdx >= colors.length) { // Completed a cycle.
+ curTestIdx = 0;
+ // Increase the wait time between switching test patterns for
+ // overloaded bots that aren't capturing all the frames of video.
+ this.curAdvanceWaitTimeMillis *= 1.25;
+ }
+ runTestPatternLoop();
+ },
+ this.curAdvanceWaitTimeMillis);
+}
+
+chrome.test.runTests([
+ function sendTestPatterns() {
+ // The receive port changes between browser_test invocations, and is passed
+ // as an query parameter in the URL.
+ var recvPort;
+ try {
+ recvPort = parseInt(window.location.search.substring("?port=".length));
+ chrome.test.assertTrue(recvPort > 0);
+ } catch (err) {
+ chrome.test.fail("Error parsing ?port=### -- " + err.message);
+ return;
+ }
+
+ // Set to true if you want to confirm the sender color/tone changes are
+ // working, without starting tab capture and Cast sending.
+ if (false) {
+ setTimeout(runTestPatternLoop, 0);
+ return;
+ }
+
+ var width = 128;
+ var height = 128;
+ var frameRate = 15;
+
+ chrome.tabCapture.capture(
+ { video: true,
+ audio: true,
+ videoConstraints: {
+ mandatory: {
+ minWidth: width,
+ minHeight: height,
+ maxWidth: width,
+ maxHeight: height,
+ maxFrameRate: frameRate,
+ }
+ }
+ },
+ function startStreamingTestPatterns(captureStream) {
+ chrome.test.assertTrue(!!captureStream);
+ chrome.cast.streaming.session.create(
+ captureStream.getAudioTracks()[0],
+ captureStream.getVideoTracks()[0],
+ function (audioId, videoId, udpId) {
+ chrome.cast.streaming.udpTransport.setDestination(
+ udpId, { address: "127.0.0.1", port: recvPort } );
+ var rtpStream = chrome.cast.streaming.rtpStream;
+ rtpStream.start(audioId,
+ rtpStream.getSupportedParams(audioId)[0]);
+ var videoParams = rtpStream.getSupportedParams(videoId)[0];
+ videoParams.payload.width = width;
+ videoParams.payload.height = height;
+ videoParams.payload.clockRate = frameRate;
+ rtpStream.start(videoId, videoParams);
+ setTimeout(runTestPatternLoop, 0);
+ if (window.innerWidth > 2 * width ||
+ window.innerHeight > 2 & height) {
+ console.warn("***TIMEOUT HAZARD*** Tab size is " +
+ window.innerWidth + "x" + window.innerHeight +
+ ", which is much larger than the expected " +
+ width + "x" + height);
+ }
+ chrome.test.succeed();
+ });
+ });
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/cast_streaming/manifest.json b/chrome/test/data/extensions/api_test/cast_streaming/manifest.json
index a42cb9c..65a9923 100644
--- a/chrome/test/data/extensions/api_test/cast_streaming/manifest.json
+++ b/chrome/test/data/extensions/api_test/cast_streaming/manifest.json
@@ -1,6 +1,6 @@
{
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8xv6iO+j4kzj1HiBL93+XVJH/CRyAQMUHS/Z0l8nCAzaAFkW/JsNwxJqQhrZspnxLqbQxNncXs6g6bsXAwKHiEs+LSs+bIv0Gc/2ycZdhXJ8GhEsSMakog5dpQd1681c2gLK/8CrAoewE/0GIKhaFcp7a2iZlGh4Am6fgMKy0iQIDAQAB",
- "name": "chrome.webrtc.cast",
+ "name": "chrome.cast.streaming",
"version": "0.1",
"manifest_version": 2,
"description": "Tests Cast Mirroring Extensions API.",
diff --git a/media/cast/cast_environment.cc b/media/cast/cast_environment.cc
index 9b1a569..9077b52 100644
--- a/media/cast/cast_environment.cc
+++ b/media/cast/cast_environment.cc
@@ -29,23 +29,19 @@ CastEnvironment::CastEnvironment(
scoped_refptr<SingleThreadTaskRunner> video_encode_thread_proxy,
scoped_refptr<SingleThreadTaskRunner> video_decode_thread_proxy,
scoped_refptr<SingleThreadTaskRunner> transport_thread_proxy,
- const CastLoggingConfig& config)
- : clock_(clock.Pass()),
- main_thread_proxy_(main_thread_proxy),
+ const CastLoggingConfig& logging_config)
+ : main_thread_proxy_(main_thread_proxy),
audio_encode_thread_proxy_(audio_encode_thread_proxy),
audio_decode_thread_proxy_(audio_decode_thread_proxy),
video_encode_thread_proxy_(video_encode_thread_proxy),
video_decode_thread_proxy_(video_decode_thread_proxy),
transport_thread_proxy_(transport_thread_proxy),
- logging_(new LoggingImpl(main_thread_proxy, config)) {
- DCHECK(main_thread_proxy);
-}
+ clock_(clock.Pass()),
+ logging_(new LoggingImpl(logging_config)) {}
CastEnvironment::~CastEnvironment() {
// Logging must be deleted on the main thread.
- if (main_thread_proxy_->RunsTasksOnCurrentThread()) {
- logging_.reset();
- } else {
+ if (main_thread_proxy_ && !main_thread_proxy_->RunsTasksOnCurrentThread()) {
main_thread_proxy_->PostTask(
FROM_HERE,
base::Bind(&DeleteLoggingOnMainThread, base::Passed(&logging_)));
@@ -55,10 +51,7 @@ CastEnvironment::~CastEnvironment() {
bool CastEnvironment::PostTask(ThreadId identifier,
const tracked_objects::Location& from_here,
const base::Closure& task) {
- scoped_refptr<SingleThreadTaskRunner> task_runner =
- GetMessageSingleThreadTaskRunnerForThread(identifier);
-
- return task_runner->PostTask(from_here, task);
+ return GetTaskRunner(identifier)->PostTask(from_here, task);
}
bool CastEnvironment::PostDelayedTask(
@@ -66,15 +59,11 @@ bool CastEnvironment::PostDelayedTask(
const tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay) {
- scoped_refptr<SingleThreadTaskRunner> task_runner =
- GetMessageSingleThreadTaskRunnerForThread(identifier);
-
- return task_runner->PostDelayedTask(from_here, task, delay);
+ return GetTaskRunner(identifier)->PostDelayedTask(from_here, task, delay);
}
-scoped_refptr<SingleThreadTaskRunner>
-CastEnvironment::GetMessageSingleThreadTaskRunnerForThread(
- ThreadId identifier) {
+scoped_refptr<SingleThreadTaskRunner> CastEnvironment::GetTaskRunner(
+ ThreadId identifier) const {
switch (identifier) {
case CastEnvironment::MAIN:
return main_thread_proxy_;
@@ -97,30 +86,28 @@ CastEnvironment::GetMessageSingleThreadTaskRunnerForThread(
bool CastEnvironment::CurrentlyOn(ThreadId identifier) {
switch (identifier) {
case CastEnvironment::MAIN:
- return main_thread_proxy_->RunsTasksOnCurrentThread();
+ return main_thread_proxy_ &&
+ main_thread_proxy_->RunsTasksOnCurrentThread();
case CastEnvironment::AUDIO_ENCODER:
- return audio_encode_thread_proxy_->RunsTasksOnCurrentThread();
+ return audio_encode_thread_proxy_ &&
+ audio_encode_thread_proxy_->RunsTasksOnCurrentThread();
case CastEnvironment::AUDIO_DECODER:
- return audio_decode_thread_proxy_->RunsTasksOnCurrentThread();
+ return audio_decode_thread_proxy_ &&
+ audio_decode_thread_proxy_->RunsTasksOnCurrentThread();
case CastEnvironment::VIDEO_ENCODER:
- return video_encode_thread_proxy_->RunsTasksOnCurrentThread();
+ return video_encode_thread_proxy_ &&
+ video_encode_thread_proxy_->RunsTasksOnCurrentThread();
case CastEnvironment::VIDEO_DECODER:
- return video_decode_thread_proxy_->RunsTasksOnCurrentThread();
+ return video_decode_thread_proxy_ &&
+ video_decode_thread_proxy_->RunsTasksOnCurrentThread();
case CastEnvironment::TRANSPORT:
- return transport_thread_proxy_->RunsTasksOnCurrentThread();
+ return transport_thread_proxy_ &&
+ transport_thread_proxy_->RunsTasksOnCurrentThread();
default:
NOTREACHED() << "Invalid thread identifier";
return false;
}
}
-base::TickClock* CastEnvironment::Clock() const { return clock_.get(); }
-
-LoggingImpl* CastEnvironment::Logging() {
- DCHECK(CurrentlyOn(CastEnvironment::MAIN))
- << "Must be called from main thread";
- return logging_.get();
-}
-
} // namespace cast
} // namespace media
diff --git a/media/cast/cast_environment.h b/media/cast/cast_environment.h
index 5abb5c7..95d4ccb 100644
--- a/media/cast/cast_environment.h
+++ b/media/cast/cast_environment.h
@@ -46,7 +46,7 @@ class CastEnvironment : public base::RefCountedThreadSafe<CastEnvironment> {
scoped_refptr<base::SingleThreadTaskRunner> video_encode_thread_proxy,
scoped_refptr<base::SingleThreadTaskRunner> video_decode_thread_proxy,
scoped_refptr<base::SingleThreadTaskRunner> transport_thread_proxy,
- const CastLoggingConfig& config);
+ const CastLoggingConfig& logging_config);
// These are the same methods in message_loop.h, but are guaranteed to either
// get posted to the MessageLoop if it's still alive, or be deleted otherwise.
@@ -64,13 +64,15 @@ class CastEnvironment : public base::RefCountedThreadSafe<CastEnvironment> {
bool CurrentlyOn(ThreadId identifier);
- base::TickClock* Clock() const;
+ // All of the media::cast implementation must use this TickClock.
+ base::TickClock* Clock() const { return clock_.get(); }
- // Logging is not thread safe. Should always be called from the main thread.
- LoggingImpl* Logging();
+ // Logging is not thread safe. Its methods should always be called from the
+ // main thread.
+ LoggingImpl* Logging() const { return logging_.get(); }
- scoped_refptr<base::SingleThreadTaskRunner>
- GetMessageSingleThreadTaskRunnerForThread(ThreadId identifier);
+ scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner(
+ ThreadId identifier) const;
bool HasAudioEncoderThread() {
return audio_encode_thread_proxy_ ? true : false;
@@ -83,10 +85,7 @@ class CastEnvironment : public base::RefCountedThreadSafe<CastEnvironment> {
protected:
virtual ~CastEnvironment();
- private:
- friend class base::RefCountedThreadSafe<CastEnvironment>;
-
- scoped_ptr<base::TickClock> clock_;
+ // Subclasses may override these.
scoped_refptr<base::SingleThreadTaskRunner> main_thread_proxy_;
scoped_refptr<base::SingleThreadTaskRunner> audio_encode_thread_proxy_;
scoped_refptr<base::SingleThreadTaskRunner> audio_decode_thread_proxy_;
@@ -94,6 +93,10 @@ class CastEnvironment : public base::RefCountedThreadSafe<CastEnvironment> {
scoped_refptr<base::SingleThreadTaskRunner> video_decode_thread_proxy_;
scoped_refptr<base::SingleThreadTaskRunner> transport_thread_proxy_;
+ private:
+ friend class base::RefCountedThreadSafe<CastEnvironment>;
+
+ scoped_ptr<base::TickClock> clock_;
scoped_ptr<LoggingImpl> logging_;
DISALLOW_COPY_AND_ASSIGN(CastEnvironment);
diff --git a/media/cast/cast_receiver_impl.cc b/media/cast/cast_receiver_impl.cc
index cca6492..f9fc5a6 100644
--- a/media/cast/cast_receiver_impl.cc
+++ b/media/cast/cast_receiver_impl.cc
@@ -98,8 +98,7 @@ CastReceiverImpl::CastReceiverImpl(
transport::PacketSender* const packet_sender)
: pacer_(cast_environment->Clock(),
packet_sender,
- cast_environment->GetMessageSingleThreadTaskRunnerForThread(
- CastEnvironment::TRANSPORT)),
+ cast_environment->GetTaskRunner(CastEnvironment::TRANSPORT)),
audio_receiver_(cast_environment, audio_config, &pacer_),
video_receiver_(cast_environment, video_config, &pacer_),
frame_receiver_(new LocalFrameReceiver(cast_environment,
diff --git a/media/cast/logging/logging_impl.cc b/media/cast/logging/logging_impl.cc
index ec0099c..7bfc474 100644
--- a/media/cast/logging/logging_impl.cc
+++ b/media/cast/logging/logging_impl.cc
@@ -9,20 +9,19 @@
namespace media {
namespace cast {
-LoggingImpl::LoggingImpl(
- scoped_refptr<base::SingleThreadTaskRunner> main_thread_proxy,
- const CastLoggingConfig& config)
- : main_thread_proxy_(main_thread_proxy),
- config_(config),
- raw_(),
- stats_() {}
+LoggingImpl::LoggingImpl(const CastLoggingConfig& config)
+ : config_(config), raw_(), stats_() {
+ // LoggingImpl can be constructed on any thread, but its methods should all be
+ // called on the same thread.
+ thread_checker_.DetachFromThread();
+}
LoggingImpl::~LoggingImpl() {}
void LoggingImpl::InsertFrameEvent(const base::TimeTicks& time_of_event,
CastLoggingEvent event, uint32 rtp_timestamp,
uint32 frame_id) {
- DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread());
+ DCHECK(thread_checker_.CalledOnValidThread());
if (config_.enable_raw_data_collection) {
raw_.InsertFrameEvent(time_of_event, event, rtp_timestamp, frame_id);
}
@@ -40,7 +39,7 @@ void LoggingImpl::InsertFrameEventWithSize(const base::TimeTicks& time_of_event,
CastLoggingEvent event,
uint32 rtp_timestamp,
uint32 frame_id, int frame_size) {
- DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread());
+ DCHECK(thread_checker_.CalledOnValidThread());
if (config_.enable_raw_data_collection) {
raw_.InsertFrameEventWithSize(time_of_event, event, rtp_timestamp, frame_id,
frame_size);
@@ -61,7 +60,7 @@ void LoggingImpl::InsertFrameEventWithSize(const base::TimeTicks& time_of_event,
void LoggingImpl::InsertFrameEventWithDelay(
const base::TimeTicks& time_of_event, CastLoggingEvent event,
uint32 rtp_timestamp, uint32 frame_id, base::TimeDelta delay) {
- DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread());
+ DCHECK(thread_checker_.CalledOnValidThread());
if (config_.enable_raw_data_collection) {
raw_.InsertFrameEventWithDelay(time_of_event, event, rtp_timestamp,
frame_id, delay);
@@ -82,7 +81,7 @@ void LoggingImpl::InsertFrameEventWithDelay(
void LoggingImpl::InsertPacketListEvent(const base::TimeTicks& time_of_event,
CastLoggingEvent event,
const PacketList& packets) {
- DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread());
+ DCHECK(thread_checker_.CalledOnValidThread());
for (unsigned int i = 0; i < packets.size(); ++i) {
const Packet& packet = packets[i];
// Parse basic properties.
@@ -107,7 +106,7 @@ void LoggingImpl::InsertPacketEvent(const base::TimeTicks& time_of_event,
uint32 rtp_timestamp, uint32 frame_id,
uint16 packet_id, uint16 max_packet_id,
size_t size) {
- DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread());
+ DCHECK(thread_checker_.CalledOnValidThread());
if (config_.enable_raw_data_collection) {
raw_.InsertPacketEvent(time_of_event, event, rtp_timestamp, frame_id,
packet_id, max_packet_id, size);
@@ -126,7 +125,7 @@ void LoggingImpl::InsertPacketEvent(const base::TimeTicks& time_of_event,
void LoggingImpl::InsertGenericEvent(const base::TimeTicks& time_of_event,
CastLoggingEvent event, int value) {
- DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread());
+ DCHECK(thread_checker_.CalledOnValidThread());
if (config_.enable_raw_data_collection) {
raw_.InsertGenericEvent(time_of_event, event, value);
}
@@ -142,31 +141,33 @@ void LoggingImpl::InsertGenericEvent(const base::TimeTicks& time_of_event,
}
void LoggingImpl::AddRawEventSubscriber(RawEventSubscriber* subscriber) {
+ DCHECK(thread_checker_.CalledOnValidThread());
raw_.AddSubscriber(subscriber);
}
void LoggingImpl::RemoveRawEventSubscriber(RawEventSubscriber* subscriber) {
+ DCHECK(thread_checker_.CalledOnValidThread());
raw_.RemoveSubscriber(subscriber);
}
FrameStatsMap LoggingImpl::GetFrameStatsData(EventMediaType media_type) const {
- DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread());
+ DCHECK(thread_checker_.CalledOnValidThread());
return stats_.GetFrameStatsData(media_type);
}
PacketStatsMap LoggingImpl::GetPacketStatsData(
EventMediaType media_type) const {
- DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread());
+ DCHECK(thread_checker_.CalledOnValidThread());
return stats_.GetPacketStatsData(media_type);
}
GenericStatsMap LoggingImpl::GetGenericStatsData() const {
- DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread());
+ DCHECK(thread_checker_.CalledOnValidThread());
return stats_.GetGenericStatsData();
}
void LoggingImpl::ResetStats() {
- DCHECK(main_thread_proxy_->RunsTasksOnCurrentThread());
+ DCHECK(thread_checker_.CalledOnValidThread());
stats_.Reset();
}
diff --git a/media/cast/logging/logging_impl.h b/media/cast/logging/logging_impl.h
index e51b114..d8fb292 100644
--- a/media/cast/logging/logging_impl.h
+++ b/media/cast/logging/logging_impl.h
@@ -10,7 +10,7 @@
// 2. Tracing of raw events.
#include "base/memory/ref_counted.h"
-#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_checker.h"
#include "media/cast/cast_config.h"
#include "media/cast/logging/logging_defines.h"
#include "media/cast/logging/logging_raw.h"
@@ -19,14 +19,14 @@
namespace media {
namespace cast {
-// Should only be called from the main thread.
-class LoggingImpl : public base::NonThreadSafe {
+class LoggingImpl {
public:
- LoggingImpl(scoped_refptr<base::SingleThreadTaskRunner> main_thread_proxy,
- const CastLoggingConfig& config);
+ explicit LoggingImpl(const CastLoggingConfig& config);
~LoggingImpl();
+ // Note: All methods below should be called from the same thread.
+
void InsertFrameEvent(const base::TimeTicks& time_of_event,
CastLoggingEvent event, uint32 rtp_timestamp,
uint32 frame_id);
@@ -66,7 +66,7 @@ class LoggingImpl : public base::NonThreadSafe {
void ResetStats();
private:
- scoped_refptr<base::SingleThreadTaskRunner> main_thread_proxy_;
+ base::ThreadChecker thread_checker_;
const CastLoggingConfig config_;
LoggingRaw raw_;
LoggingStats stats_;
diff --git a/media/cast/logging/logging_impl_unittest.cc b/media/cast/logging/logging_impl_unittest.cc
index 05d7056..92da7c1 100644
--- a/media/cast/logging/logging_impl_unittest.cc
+++ b/media/cast/logging/logging_impl_unittest.cc
@@ -11,7 +11,6 @@
#include "media/cast/logging/logging_defines.h"
#include "media/cast/logging/logging_impl.h"
#include "media/cast/logging/simple_event_subscriber.h"
-#include "media/cast/test/fake_single_thread_task_runner.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
@@ -34,8 +33,7 @@ class LoggingImplTest : public ::testing::Test {
testing_clock_.Advance(
base::TimeDelta::FromMilliseconds(kStartMillisecond));
- task_runner_ = new test::FakeSingleThreadTaskRunner(&testing_clock_);
- logging_.reset(new LoggingImpl(task_runner_, config_));
+ logging_.reset(new LoggingImpl(config_));
logging_->AddRawEventSubscriber(&event_subscriber_);
}
@@ -44,7 +42,6 @@ class LoggingImplTest : public ::testing::Test {
}
CastLoggingConfig config_;
- scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_;
scoped_ptr<LoggingImpl> logging_;
base::SimpleTestTickClock testing_clock_;
SimpleEventSubscriber event_subscriber_;
diff --git a/media/cast/test/linux_output_window.cc b/media/cast/test/linux_output_window.cc
index 735e0dd..389bb0f 100644
--- a/media/cast/test/linux_output_window.cc
+++ b/media/cast/test/linux_output_window.cc
@@ -106,7 +106,6 @@ void LinuxOutputWindow::CreateWindow(int x_pos,
VLOG(1) << "XShmCreateImage failed";
NOTREACHED();
}
- render_buffer_ = reinterpret_cast<uint8_t*>(image_->data);
shminfo_.readOnly = false;
// Attach image to display.
@@ -119,14 +118,16 @@ void LinuxOutputWindow::CreateWindow(int x_pos,
void LinuxOutputWindow::RenderFrame(
const scoped_refptr<media::VideoFrame>& video_frame) {
+ CHECK_LE(video_frame->coded_size().width(), image_->width);
+ CHECK_LE(video_frame->coded_size().height(), image_->height);
libyuv::I420ToARGB(video_frame->data(VideoFrame::kYPlane),
video_frame->stride(VideoFrame::kYPlane),
video_frame->data(VideoFrame::kUPlane),
video_frame->stride(VideoFrame::kUPlane),
video_frame->data(VideoFrame::kVPlane),
video_frame->stride(VideoFrame::kVPlane),
- render_buffer_,
- video_frame->stride(VideoFrame::kYPlane) * 4,
+ reinterpret_cast<uint8_t*>(image_->data),
+ image_->bytes_per_line,
video_frame->coded_size().width(),
video_frame->coded_size().height());
@@ -149,4 +150,4 @@ void LinuxOutputWindow::RenderFrame(
} // namespace test
} // namespace cast
-} // namespace media \ No newline at end of file
+} // namespace media
diff --git a/media/cast/test/linux_output_window.h b/media/cast/test/linux_output_window.h
index 9a68bff8..5907da20 100644
--- a/media/cast/test/linux_output_window.h
+++ b/media/cast/test/linux_output_window.h
@@ -41,7 +41,6 @@ class LinuxOutputWindow {
int width,
int height,
const std::string& name);
- uint8* render_buffer_;
Display* display_;
Window window_;
XShmSegmentInfo shminfo_;
diff --git a/media/cast/test/receiver.cc b/media/cast/test/receiver.cc
index 6c3097f..213d8aa 100644
--- a/media/cast/test/receiver.cc
+++ b/media/cast/test/receiver.cc
@@ -21,7 +21,10 @@
#include "media/cast/cast_environment.h"
#include "media/cast/cast_receiver.h"
#include "media/cast/logging/logging_defines.h"
+#include "media/cast/test/utility/default_config.h"
+#include "media/cast/test/utility/in_process_receiver.h"
#include "media/cast/test/utility/input_builder.h"
+#include "media/cast/test/utility/standalone_cast_environment.h"
#include "media/cast/transport/transport/udp_transport.h"
#include "net/base/net_util.h"
@@ -31,6 +34,7 @@
namespace media {
namespace cast {
+
// Settings chosen to match default sender settings.
#define DEFAULT_SEND_PORT "0"
#define DEFAULT_RECEIVE_PORT "2344"
@@ -46,12 +50,10 @@ namespace cast {
#define DEFAULT_VIDEO_CODEC_HEIGHT "480"
#define DEFAULT_VIDEO_CODEC_BITRATE "2000"
-static const int kAudioSamplingFrequency = 48000;
#if defined(OS_LINUX)
const int kVideoWindowWidth = 1280;
const int kVideoWindowHeight = 720;
#endif // OS_LINUX
-static const int kFrameTimerMs = 33;
void GetPorts(int* tx_port, int* rx_port) {
test::InputBuilder tx_input(
@@ -103,18 +105,9 @@ void GetPayloadtype(AudioReceiverConfig* audio_config) {
}
AudioReceiverConfig GetAudioReceiverConfig() {
- AudioReceiverConfig audio_config;
-
+ AudioReceiverConfig audio_config = GetDefaultAudioReceiverConfig();
GetSsrcs(&audio_config);
GetPayloadtype(&audio_config);
-
- audio_config.rtcp_c_name = "audio_receiver@a.b.c.d";
-
- VLOG(1) << "Using OPUS 48Khz stereo";
- audio_config.use_external_decoder = false;
- audio_config.frequency = 48000;
- audio_config.channels = 2;
- audio_config.codec = transport::kOpus;
return audio_config;
}
@@ -127,28 +120,26 @@ void GetPayloadtype(VideoReceiverConfig* video_config) {
}
VideoReceiverConfig GetVideoReceiverConfig() {
- VideoReceiverConfig video_config;
-
+ VideoReceiverConfig video_config = GetDefaultVideoReceiverConfig();
GetSsrcs(&video_config);
GetPayloadtype(&video_config);
-
- video_config.rtcp_c_name = "video_receiver@a.b.c.d";
-
- video_config.use_external_decoder = false;
-
- VLOG(1) << "Using VP8";
- video_config.codec = transport::kVp8;
return video_config;
}
-static void UpdateCastTransportStatus(transport::CastTransportStatus status) {
- VLOG(1) << "CastTransportStatus = " << status;
-}
-
-class ReceiveProcess : public base::RefCountedThreadSafe<ReceiveProcess> {
+// An InProcessReceiver that renders video frames to a LinuxOutputWindow. While
+// it does receive audio frames, it does not play them.
+class ReceiverDisplay : public InProcessReceiver {
public:
- explicit ReceiveProcess(scoped_refptr<FrameReceiver> frame_receiver)
- : frame_receiver_(frame_receiver),
+ ReceiverDisplay(const scoped_refptr<CastEnvironment>& cast_environment,
+ const net::IPEndPoint& local_end_point,
+ const net::IPEndPoint& remote_end_point,
+ const AudioReceiverConfig& audio_config,
+ const VideoReceiverConfig& video_config)
+ : InProcessReceiver(cast_environment,
+ local_end_point,
+ remote_end_point,
+ audio_config,
+ video_config),
#if defined(OS_LINUX)
render_(0, 0, kVideoWindowWidth, kVideoWindowHeight, "Cast_receiver"),
#endif // OS_LINUX
@@ -156,62 +147,34 @@ class ReceiveProcess : public base::RefCountedThreadSafe<ReceiveProcess> {
last_render_time_() {
}
- void Start() {
- GetAudioFrame(base::TimeDelta::FromMilliseconds(kFrameTimerMs));
- GetVideoFrame();
- }
+ virtual ~ReceiverDisplay() {}
protected:
- virtual ~ReceiveProcess() {}
-
- private:
- friend class base::RefCountedThreadSafe<ReceiveProcess>;
-
- void DisplayFrame(const scoped_refptr<media::VideoFrame>& video_frame,
- const base::TimeTicks& render_time) {
+ virtual void OnVideoFrame(const scoped_refptr<media::VideoFrame>& video_frame,
+ const base::TimeTicks& render_time) OVERRIDE {
#ifdef OS_LINUX
render_.RenderFrame(video_frame);
#endif // OS_LINUX
// Print out the delta between frames.
if (!last_render_time_.is_null()) {
base::TimeDelta time_diff = render_time - last_render_time_;
- VLOG(1) << " RenderDelay[mS] = " << time_diff.InMilliseconds();
+ VLOG(1) << "Size = " << video_frame->coded_size().ToString()
+ << "; RenderDelay[mS] = " << time_diff.InMilliseconds();
}
last_render_time_ = render_time;
- GetVideoFrame();
}
- void ReceiveAudioFrame(scoped_ptr<PcmAudioFrame> audio_frame,
- const base::TimeTicks& playout_time) {
+ virtual void OnAudioFrame(scoped_ptr<PcmAudioFrame> audio_frame,
+ const base::TimeTicks& playout_time) OVERRIDE {
// For audio just print the playout delta between audio frames.
- // Default diff time is kFrameTimerMs.
- base::TimeDelta time_diff =
- base::TimeDelta::FromMilliseconds(kFrameTimerMs);
if (!last_playout_time_.is_null()) {
- time_diff = playout_time - last_playout_time_;
- VLOG(1) << " ***PlayoutDelay[mS] = " << time_diff.InMilliseconds();
+ base::TimeDelta time_diff = playout_time - last_playout_time_;
+ VLOG(1) << "SampleRate = " << audio_frame->frequency
+ << "; PlayoutDelay[mS] = " << time_diff.InMilliseconds();
}
last_playout_time_ = playout_time;
}
- void GetAudioFrame(base::TimeDelta playout_diff) {
- int num_10ms_blocks = playout_diff.InMilliseconds() / 10;
- frame_receiver_->GetRawAudioFrame(
- num_10ms_blocks,
- kAudioSamplingFrequency,
- base::Bind(&ReceiveProcess::ReceiveAudioFrame, this));
- base::MessageLoop::current()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&ReceiveProcess::GetAudioFrame, this, playout_diff),
- playout_diff);
- }
-
- void GetVideoFrame() {
- frame_receiver_->GetRawVideoFrame(
- base::Bind(&ReceiveProcess::DisplayFrame, this));
- }
-
- scoped_refptr<FrameReceiver> frame_receiver_;
#ifdef OS_LINUX
test::LinuxOutputWindow render_;
#endif // OS_LINUX
@@ -224,32 +187,15 @@ class ReceiveProcess : public base::RefCountedThreadSafe<ReceiveProcess> {
int main(int argc, char** argv) {
base::AtExitManager at_exit;
- base::MessageLoopForIO main_message_loop;
CommandLine::Init(argc, argv);
InitLogging(logging::LoggingSettings());
- VLOG(1) << "Cast Receiver";
- base::Thread audio_thread("Cast audio decoder thread");
- base::Thread video_thread("Cast video decoder thread");
- audio_thread.Start();
- video_thread.Start();
-
- scoped_ptr<base::TickClock> clock(new base::DefaultTickClock());
-
- // Enable main and receiver side threads only. Enable raw event logging.
- // Running transport on the main thread.
+ // Enable raw event logging only.
media::cast::CastLoggingConfig logging_config;
logging_config.enable_raw_data_collection = true;
scoped_refptr<media::cast::CastEnvironment> cast_environment(
- new media::cast::CastEnvironment(clock.Pass(),
- main_message_loop.message_loop_proxy(),
- NULL,
- audio_thread.message_loop_proxy(),
- NULL,
- video_thread.message_loop_proxy(),
- main_message_loop.message_loop_proxy(),
- logging_config));
+ new media::cast::StandaloneCastEnvironment(logging_config));
media::cast::AudioReceiverConfig audio_config =
media::cast::GetAudioReceiverConfig();
@@ -281,23 +227,15 @@ int main(int argc, char** argv) {
net::IPEndPoint remote_end_point(remote_ip_number, remote_port);
net::IPEndPoint local_end_point(local_ip_number, local_port);
- scoped_ptr<media::cast::transport::UdpTransport> transport(
- new media::cast::transport::UdpTransport(
- NULL,
- main_message_loop.message_loop_proxy(),
- local_end_point,
- remote_end_point,
- base::Bind(&media::cast::UpdateCastTransportStatus)));
- scoped_ptr<media::cast::CastReceiver> cast_receiver(
- media::cast::CastReceiver::CreateCastReceiver(
- cast_environment, audio_config, video_config, transport.get()));
-
- // TODO(hubbe): Make the cast receiver do this automatically.
- transport->StartReceiving(cast_receiver->packet_receiver());
-
- scoped_refptr<media::cast::ReceiveProcess> receive_process(
- new media::cast::ReceiveProcess(cast_receiver->frame_receiver()));
- receive_process->Start();
- main_message_loop.Run();
+ media::cast::ReceiverDisplay* const receiver_display =
+ new media::cast::ReceiverDisplay(cast_environment,
+ local_end_point,
+ remote_end_point,
+ audio_config,
+ video_config);
+ receiver_display->Start();
+
+ base::MessageLoop().Run(); // Run forever (i.e., until SIGTERM).
+ NOTREACHED();
return 0;
}
diff --git a/media/cast/test/utility/default_config.cc b/media/cast/test/utility/default_config.cc
new file mode 100644
index 0000000..350bd517
--- /dev/null
+++ b/media/cast/test/utility/default_config.cc
@@ -0,0 +1,37 @@
+// 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 "media/cast/test/utility/default_config.h"
+
+#include "media/cast/transport/cast_transport_config.h"
+
+namespace media {
+namespace cast {
+
+AudioReceiverConfig GetDefaultAudioReceiverConfig() {
+ AudioReceiverConfig config;
+ config.feedback_ssrc = 1;
+ config.incoming_ssrc = 2;
+ config.rtp_payload_type = 127;
+ config.rtcp_c_name = "audio_receiver@a.b.c.d";
+ config.use_external_decoder = false;
+ config.frequency = 48000;
+ config.channels = 2;
+ config.codec = media::cast::transport::kOpus;
+ return config;
+}
+
+VideoReceiverConfig GetDefaultVideoReceiverConfig() {
+ VideoReceiverConfig config;
+ config.feedback_ssrc = 12;
+ config.incoming_ssrc = 11;
+ config.rtp_payload_type = 96;
+ config.rtcp_c_name = "video_receiver@a.b.c.d";
+ config.use_external_decoder = false;
+ config.codec = media::cast::transport::kVp8;
+ return config;
+}
+
+} // namespace cast
+} // namespace media
diff --git a/media/cast/test/utility/default_config.h b/media/cast/test/utility/default_config.h
new file mode 100644
index 0000000..ef7c79a
--- /dev/null
+++ b/media/cast/test/utility/default_config.h
@@ -0,0 +1,26 @@
+// 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 MEDIA_CAST_TEST_UTILITY_DEFAULT_CONFIG_H_
+#define MEDIA_CAST_TEST_UTILITY_DEFAULT_CONFIG_H_
+
+#include "media/cast/cast_config.h"
+
+namespace media {
+namespace cast {
+
+// Returns an AudioReceiverConfig initialized to "good-to-go" values. This
+// specifies 48 kHz, 2-channel Opus-coded audio, with standard ssrc's, payload
+// type, and a dummy name.
+AudioReceiverConfig GetDefaultAudioReceiverConfig();
+
+// Returns a VideoReceiverConfig initialized to "good-to-go" values. This
+// specifies VP8-coded video, with standard ssrc's, payload type, and a dummy
+// name.
+VideoReceiverConfig GetDefaultVideoReceiverConfig();
+
+} // namespace cast
+} // namespace media
+
+#endif // MEDIA_CAST_TEST_UTILITY_DEFAULT_CONFIG_H_
diff --git a/media/cast/test/utility/in_process_receiver.cc b/media/cast/test/utility/in_process_receiver.cc
new file mode 100644
index 0000000..f916dbb
--- /dev/null
+++ b/media/cast/test/utility/in_process_receiver.cc
@@ -0,0 +1,126 @@
+// 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 "media/cast/test/utility/in_process_receiver.h"
+
+#include "base/bind_helpers.h"
+#include "base/time/time.h"
+#include "media/base/video_frame.h"
+#include "media/cast/cast_config.h"
+#include "media/cast/cast_environment.h"
+#include "media/cast/cast_receiver.h"
+#include "media/cast/transport/cast_transport_config.h"
+#include "media/cast/transport/transport/udp_transport.h"
+
+using media::cast::transport::CastTransportStatus;
+using media::cast::transport::UdpTransport;
+
+namespace media {
+namespace cast {
+
+InProcessReceiver::InProcessReceiver(
+ const scoped_refptr<CastEnvironment>& cast_environment,
+ const net::IPEndPoint& local_end_point,
+ const net::IPEndPoint& remote_end_point,
+ const AudioReceiverConfig& audio_config,
+ const VideoReceiverConfig& video_config)
+ : cast_environment_(cast_environment),
+ local_end_point_(local_end_point),
+ remote_end_point_(remote_end_point),
+ audio_config_(audio_config),
+ video_config_(video_config),
+ weak_factory_(this) {}
+
+InProcessReceiver::~InProcessReceiver() {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+}
+
+void InProcessReceiver::Start() {
+ cast_environment_->PostTask(CastEnvironment::MAIN,
+ FROM_HERE,
+ base::Bind(&InProcessReceiver::StartOnMainThread,
+ base::Unretained(this)));
+}
+
+void InProcessReceiver::DestroySoon() {
+ cast_environment_->PostTask(
+ CastEnvironment::MAIN,
+ FROM_HERE,
+ base::Bind(&InProcessReceiver::WillDestroyReceiver, base::Owned(this)));
+}
+
+void InProcessReceiver::UpdateCastTransportStatus(CastTransportStatus status) {
+ LOG_IF(ERROR, status == media::cast::transport::TRANSPORT_SOCKET_ERROR)
+ << "Transport socket error occurred. InProcessReceiver is likely dead.";
+ VLOG(1) << "CastTransportStatus is now " << status;
+}
+
+void InProcessReceiver::StartOnMainThread() {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+
+ DCHECK(!transport_ && !cast_receiver_);
+ transport_.reset(
+ new UdpTransport(NULL,
+ cast_environment_->GetTaskRunner(CastEnvironment::MAIN),
+ local_end_point_,
+ remote_end_point_,
+ base::Bind(&InProcessReceiver::UpdateCastTransportStatus,
+ base::Unretained(this))));
+ cast_receiver_.reset(CastReceiver::CreateCastReceiver(
+ cast_environment_, audio_config_, video_config_, transport_.get()));
+
+ // TODO(hubbe): Make the cast receiver do this automatically.
+ transport_->StartReceiving(cast_receiver_->packet_receiver());
+
+ PullNextAudioFrame();
+ PullNextVideoFrame();
+}
+
+void InProcessReceiver::GotAudioFrame(scoped_ptr<PcmAudioFrame> audio_frame,
+ const base::TimeTicks& playout_time) {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ OnAudioFrame(audio_frame.Pass(), playout_time);
+ // TODO(miu): Put this back here: PullNextAudioFrame();
+}
+
+void InProcessReceiver::GotVideoFrame(
+ const scoped_refptr<VideoFrame>& video_frame,
+ const base::TimeTicks& render_time) {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ OnVideoFrame(video_frame, render_time);
+ PullNextVideoFrame();
+}
+
+void InProcessReceiver::PullNextAudioFrame() {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ cast_receiver_->frame_receiver()->GetRawAudioFrame(
+ 1 /* 10 ms of samples */,
+ audio_config_.frequency,
+ base::Bind(&InProcessReceiver::GotAudioFrame,
+ weak_factory_.GetWeakPtr()));
+ // TODO(miu): Fix audio decoder so that it never drops a request for the next
+ // frame of audio. Once fixed, remove this, and add PullNextAudioFrame() to
+ // the end of GotAudioFrame(), so that it behaves just like GotVideoFrame().
+ // http://crbug.com/347361
+ cast_environment_->PostDelayedTask(
+ CastEnvironment::MAIN,
+ FROM_HERE,
+ base::Bind(&InProcessReceiver::PullNextAudioFrame,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(10));
+}
+
+void InProcessReceiver::PullNextVideoFrame() {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ cast_receiver_->frame_receiver()->GetRawVideoFrame(base::Bind(
+ &InProcessReceiver::GotVideoFrame, weak_factory_.GetWeakPtr()));
+}
+
+// static
+void InProcessReceiver::WillDestroyReceiver(InProcessReceiver* receiver) {
+ DCHECK(receiver->cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+}
+
+} // namespace cast
+} // namespace media
diff --git a/media/cast/test/utility/in_process_receiver.h b/media/cast/test/utility/in_process_receiver.h
new file mode 100644
index 0000000..57d8a86
--- /dev/null
+++ b/media/cast/test/utility/in_process_receiver.h
@@ -0,0 +1,113 @@
+// 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 MEDIA_CAST_TEST_IN_PROCESS_RECEIVER_H_
+#define MEDIA_CAST_TEST_IN_PROCESS_RECEIVER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "media/cast/cast_config.h"
+#include "media/cast/transport/cast_transport_config.h"
+
+namespace base {
+class TimeTicks;
+} // namespace base
+
+namespace net {
+class IPEndPoint;
+} // namespace net
+
+namespace media {
+
+class VideoFrame;
+
+namespace cast {
+
+class CastEnvironment;
+class CastReceiver;
+
+namespace transport {
+class UdpTransport;
+} // namespace transport
+
+// Common base functionality for an in-process Cast receiver. This is meant to
+// be subclassed with the OnAudioFrame() and OnVideoFrame() methods implemented,
+// so that the implementor can focus on what is to be done with the frames,
+// rather than on the boilerplate "glue" code.
+class InProcessReceiver {
+ public:
+ // Construct a receiver with the given configuration. |remote_end_point| can
+ // be left empty, if the transport should automatically mate with the first
+ // remote sender it encounters.
+ InProcessReceiver(const scoped_refptr<CastEnvironment>& cast_environment,
+ const net::IPEndPoint& local_end_point,
+ const net::IPEndPoint& remote_end_point,
+ const AudioReceiverConfig& audio_config,
+ const VideoReceiverConfig& video_config);
+
+ // Must be destroyed on the cast MAIN thread. See DestroySoon().
+ virtual ~InProcessReceiver();
+
+ // Convenience accessor to CastEnvironment.
+ scoped_refptr<CastEnvironment> cast_env() const { return cast_environment_; }
+
+ // Begin delivering any received audio/video frames to the OnXXXFrame()
+ // methods.
+ void Start();
+
+ // Schedules destruction on the cast MAIN thread. Any external references to
+ // the InProcessReceiver instance become invalid.
+ void DestroySoon();
+
+ protected:
+ // To be implemented by subclasses. These are called on the Cast MAIN thread
+ // as each frame is received.
+ virtual void OnAudioFrame(scoped_ptr<PcmAudioFrame> audio_frame,
+ const base::TimeTicks& playout_time) = 0;
+ virtual void OnVideoFrame(const scoped_refptr<VideoFrame>& video_frame,
+ const base::TimeTicks& render_time) = 0;
+
+ // Helper method that creates |transport_| and |cast_receiver_|, starts
+ // |transport_| receiving, and requests the first audio/video frame.
+ // Subclasses may override to provide additional start-up functionality.
+ virtual void StartOnMainThread();
+
+ // Callback for the transport to notify of status changes. A default
+ // implementation is provided here that simply logs socket errors.
+ virtual void UpdateCastTransportStatus(transport::CastTransportStatus status);
+
+ private:
+ friend class base::RefCountedThreadSafe<InProcessReceiver>;
+
+ // CastReceiver callbacks that receive a frame and then request another.
+ void GotAudioFrame(scoped_ptr<PcmAudioFrame> audio_frame,
+ const base::TimeTicks& playout_time);
+ void GotVideoFrame(const scoped_refptr<VideoFrame>& video_frame,
+ const base::TimeTicks& render_time);
+ void PullNextAudioFrame();
+ void PullNextVideoFrame();
+
+ // Invoked just before the destruction of |receiver| on the cast MAIN thread.
+ static void WillDestroyReceiver(InProcessReceiver* receiver);
+
+ const scoped_refptr<CastEnvironment> cast_environment_;
+ const net::IPEndPoint local_end_point_;
+ const net::IPEndPoint remote_end_point_;
+ const AudioReceiverConfig audio_config_;
+ const VideoReceiverConfig video_config_;
+
+ scoped_ptr<transport::UdpTransport> transport_;
+ scoped_ptr<CastReceiver> cast_receiver_;
+
+ // For shutdown safety, this member must be last:
+ base::WeakPtrFactory<InProcessReceiver> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(InProcessReceiver);
+};
+
+} // namespace cast
+} // namespace media
+
+#endif // MEDIA_CAST_TEST_IN_PROCESS_RECEIVER_H_
diff --git a/media/cast/test/utility/input_builder.cc b/media/cast/test/utility/input_builder.cc
index 08b7405..73dfb3f 100644
--- a/media/cast/test/utility/input_builder.cc
+++ b/media/cast/test/utility/input_builder.cc
@@ -7,6 +7,7 @@
#include <stdlib.h>
#include <cstdio>
+#include "base/command_line.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
@@ -14,6 +15,8 @@ namespace media {
namespace cast {
namespace test {
+static const char kEnablePromptsSwitch[] = "enable-prompts";
+
InputBuilder::InputBuilder(const std::string& title,
const std::string& default_value,
int low_range,
@@ -26,6 +29,9 @@ InputBuilder::InputBuilder(const std::string& title,
InputBuilder::~InputBuilder() {}
std::string InputBuilder::GetStringInput() const {
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(kEnablePromptsSwitch))
+ return default_value_;
+
printf("\n%s\n", title_.c_str());
if (!default_value_.empty())
printf("Hit enter for default (%s):\n", default_value_.c_str());
diff --git a/media/cast/test/utility/standalone_cast_environment.cc b/media/cast/test/utility/standalone_cast_environment.cc
new file mode 100644
index 0000000..7e82620
--- /dev/null
+++ b/media/cast/test/utility/standalone_cast_environment.cc
@@ -0,0 +1,59 @@
+// 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 "media/cast/test/utility/standalone_cast_environment.h"
+
+#include "base/time/default_tick_clock.h"
+
+namespace media {
+namespace cast {
+
+StandaloneCastEnvironment::StandaloneCastEnvironment(
+ const CastLoggingConfig& logging_config)
+ : CastEnvironment(
+ make_scoped_ptr<base::TickClock>(new base::DefaultTickClock()),
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ logging_config),
+ main_thread_("StandaloneCastEnvironment Main"),
+ audio_encode_thread_("StandaloneCastEnvironment Audio Encode"),
+ audio_decode_thread_("StandaloneCastEnvironment Audio Decode"),
+ video_encode_thread_("StandaloneCastEnvironment Video Encode"),
+ video_decode_thread_("StandaloneCastEnvironment Video Decode"),
+ transport_thread_("StandaloneCastEnvironment Transport") {
+#define CREATE_TASK_RUNNER(name, options) \
+ name##_thread_.StartWithOptions(options); \
+ CastEnvironment::name##_thread_proxy_ = name##_thread_.message_loop_proxy()
+
+ CREATE_TASK_RUNNER(main,
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
+ CREATE_TASK_RUNNER(audio_encode, base::Thread::Options());
+ CREATE_TASK_RUNNER(audio_decode, base::Thread::Options());
+ CREATE_TASK_RUNNER(video_encode, base::Thread::Options());
+ CREATE_TASK_RUNNER(video_decode, base::Thread::Options());
+ CREATE_TASK_RUNNER(transport, base::Thread::Options());
+
+#undef CREATE_TASK_RUNNER
+}
+
+StandaloneCastEnvironment::~StandaloneCastEnvironment() {
+ DCHECK(CalledOnValidThread());
+}
+
+void StandaloneCastEnvironment::Shutdown() {
+ DCHECK(CalledOnValidThread());
+ main_thread_.Stop();
+ audio_encode_thread_.Stop();
+ audio_decode_thread_.Stop();
+ video_encode_thread_.Stop();
+ video_decode_thread_.Stop();
+ transport_thread_.Stop();
+}
+
+} // namespace cast
+} // namespace media
diff --git a/media/cast/test/utility/standalone_cast_environment.h b/media/cast/test/utility/standalone_cast_environment.h
new file mode 100644
index 0000000..39c6e7c
--- /dev/null
+++ b/media/cast/test/utility/standalone_cast_environment.h
@@ -0,0 +1,42 @@
+// 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 MEDIA_CAST_TEST_UTILITY_STANDALONE_CAST_ENVIRONMENT_H_
+#define MEDIA_CAST_TEST_UTILITY_STANDALONE_CAST_ENVIRONMENT_H_
+
+#include "base/threading/thread.h"
+#include "base/threading/thread_checker.h"
+#include "media/cast/cast_environment.h"
+
+namespace media {
+namespace cast {
+
+// A complete CastEnvironment where all task runners are spurned from
+// internally-owned threads. Uses base::DefaultTickClock as a clock.
+class StandaloneCastEnvironment : public CastEnvironment,
+ public base::ThreadChecker {
+ public:
+ explicit StandaloneCastEnvironment(const CastLoggingConfig& logging_config);
+
+ // Stops all threads backing the task runners, blocking the caller until
+ // complete.
+ void Shutdown();
+
+ private:
+ virtual ~StandaloneCastEnvironment();
+
+ base::Thread main_thread_;
+ base::Thread audio_encode_thread_;
+ base::Thread audio_decode_thread_;
+ base::Thread video_encode_thread_;
+ base::Thread video_decode_thread_;
+ base::Thread transport_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(StandaloneCastEnvironment);
+};
+
+} // namespace cast
+} // namespace media
+
+#endif // MEDIA_CAST_TEST_UTILITY_STANDALONE_CAST_ENVIRONMENT_H_
diff --git a/media/cast/test/utility/utility.gyp b/media/cast/test/utility/utility.gyp
index 93e2f14..c1dc644 100644
--- a/media/cast/test/utility/utility.gyp
+++ b/media/cast/test/utility/utility.gyp
@@ -11,6 +11,8 @@
'<(DEPTH)/',
],
'dependencies': [
+ '../../cast_receiver.gyp:cast_receiver',
+ '../../transport/cast_transport.gyp:cast_transport',
'<(DEPTH)/ui/gfx/gfx.gyp:gfx',
'<(DEPTH)/ui/gfx/gfx.gyp:gfx_geometry',
'<(DEPTH)/testing/gtest.gyp:gtest',
@@ -20,13 +22,19 @@
'sources': [
'<(DEPTH)/media/cast/test/fake_single_thread_task_runner.cc',
'<(DEPTH)/media/cast/test/fake_single_thread_task_runner.h',
- 'input_builder.cc',
- 'input_builder.h',
'audio_utility.cc',
'audio_utility.h',
+ 'default_config.cc',
+ 'default_config.h',
+ 'in_process_receiver.cc',
+ 'in_process_receiver.h',
+ 'input_builder.cc',
+ 'input_builder.h',
+ 'standalone_cast_environment.cc',
+ 'standalone_cast_environment.h',
'video_utility.cc',
'video_utility.h',
], # source
},
],
-} \ No newline at end of file
+}
diff --git a/media/cast/transport/cast_transport_sender_impl.cc b/media/cast/transport/cast_transport_sender_impl.cc
index 38f10f8..7754e54 100644
--- a/media/cast/transport/cast_transport_sender_impl.cc
+++ b/media/cast/transport/cast_transport_sender_impl.cc
@@ -59,7 +59,7 @@ CastTransportSenderImpl::CastTransportSenderImpl(
external_transport ? external_transport : transport_.get(),
transport_task_runner),
rtcp_builder_(&pacer_),
- logging_(transport_task_runner, logging_config),
+ logging_(logging_config),
raw_events_callback_(raw_events_callback) {
if (!raw_events_callback_.is_null()) {
DCHECK(logging_config.enable_raw_data_collection);