Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(283)

Side by Side Diff: chrome/browser/extensions/cast_streaming_apitest.cc

Issue 184813009: Cast Streaming API end-to-end browser_test. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <algorithm>
6 #include <cmath>
7
5 #include "base/command_line.h" 8 #include "base/command_line.h"
9 #include "base/float_util.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/synchronization/condition_variable.h"
12 #include "base/synchronization/lock.h"
13 #include "base/time/time.h"
6 #include "chrome/browser/extensions/extension_apitest.h" 14 #include "chrome/browser/extensions/extension_apitest.h"
7 #include "chrome/common/chrome_switches.h" 15 #include "chrome/common/chrome_switches.h"
8 #include "content/public/common/content_switches.h" 16 #include "content/public/common/content_switches.h"
17 #include "media/base/video_frame.h"
18 #include "media/cast/cast_config.h"
19 #include "media/cast/cast_environment.h"
20 #include "media/cast/test/utility/audio_utility.h"
21 #include "media/cast/test/utility/in_process_receiver.h"
22 #include "net/base/net_util.h"
9 #include "testing/gtest/include/gtest/gtest.h" 23 #include "testing/gtest/include/gtest/gtest.h"
10 24
11 namespace extensions { 25 namespace extensions {
12 26
13 class CastStreamingApiTest : public ExtensionApiTest { 27 class CastStreamingApiTest : public ExtensionApiTest {
14 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 28 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
15 ExtensionApiTest::SetUpCommandLine(command_line); 29 ExtensionApiTest::SetUpCommandLine(command_line);
16 command_line->AppendSwitchASCII( 30 command_line->AppendSwitchASCII(
17 switches::kWhitelistedExtensionID, 31 switches::kWhitelistedExtensionID,
18 "ddchlicdkolnonkihahngkmmmjnjlkkf"); 32 "ddchlicdkolnonkihahngkmmmjnjlkkf");
19 } 33 }
20 }; 34 };
21 35
22 // Test running the test extension for Cast Mirroring API. 36 // Test running the test extension for Cast Mirroring API.
23 IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, Basics) { 37 IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, Basics) {
24 ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "basics.html")); 38 ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "basics.html")) << message_;
25 } 39 }
26 40
27 IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, BadLogging) { 41 IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, BadLogging) {
28 ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "bad_logging.html")); 42 ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "bad_logging.html"))
43 << message_;
29 } 44 }
30 45
46 namespace {
47
48 // An in-process Cast receiver that examines the audio/video frames being
49 // received for expected colors and tones. Used in
50 // CastStreamingApiTest.EndToEnd, below.
51 class TestPatternReceiver : public media::cast::InProcessReceiver {
52 public:
53 explicit TestPatternReceiver(const net::IPEndPoint& local_end_point)
54 : InProcessReceiver(
55 make_scoped_refptr(new media::cast::CastEnvironment(
56 media::cast::CastLoggingConfig())),
57 local_end_point,
58 net::IPEndPoint(),
59 media::cast::InProcessReceiver::GetDefaultAudioConfig(),
60 media::cast::InProcessReceiver::GetDefaultVideoConfig()),
61 cond_(&lock_),
62 target_color_y_(0),
63 target_color_u_(0),
64 target_color_v_(0),
65 target_tone_frequency_(0),
66 current_color_y_(0.0f),
67 current_color_u_(0.0f),
68 current_color_v_(0.0f),
69 current_tone_frequency_(0.0f) {}
70 virtual ~TestPatternReceiver() {}
71
72 // Blocks the caller until this receiver has seen both |yuv_color| and
73 // |tone_frequency| consistently for the given |duration|.
74 bool WaitForColorAndTone(const uint8 yuv_color[3],
75 int tone_frequency,
76 base::TimeDelta duration) {
77 DCHECK(!cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
78
79 DVLOG(1) << "Waiting for test pattern: color=yuv("
80 << static_cast<int>(yuv_color[0]) << ", "
81 << static_cast<int>(yuv_color[1]) << ", "
82 << static_cast<int>(yuv_color[2])
83 << "), tone_frequency=" << tone_frequency << " Hz";
84
85 const base::TimeTicks start_time = cast_env()->Clock()->NowTicks();
86
87 // Reset target values and counters.
88 base::AutoLock auto_lock(lock_); // Released by |cond_| in the loop below.
89 target_color_y_ = yuv_color[0];
90 target_color_u_ = yuv_color[1];
91 target_color_v_ = yuv_color[2];
92 target_tone_frequency_ = tone_frequency;
93 first_time_near_target_color_ = base::TimeTicks();
94 first_time_near_target_tone_ = base::TimeTicks();
95
96 // Wait until both the color and tone have matched, subject to a timeout.
97 const int kMaxTotalWaitSeconds = 20;
98 do {
99 const base::TimeDelta remaining_wait_time =
100 base::TimeDelta::FromSeconds(kMaxTotalWaitSeconds) -
101 (cast_env()->Clock()->NowTicks() - start_time);
102 if (remaining_wait_time <= base::TimeDelta())
103 return false; // Failed to match test pattern within total wait time.
104 cond_.TimedWait(remaining_wait_time);
105
106 if (!first_time_near_target_color_.is_null() &&
107 !first_time_near_target_tone_.is_null()) {
108 const base::TimeTicks now = cast_env()->Clock()->NowTicks();
109 if ((now - first_time_near_target_color_) >= duration &&
110 (now - first_time_near_target_tone_) >= duration) {
111 return true; // Successfully matched for sufficient duration.
112 }
113 }
114 } while (true);
115 }
116
117 private:
118 // Invoked by InProcessReceiver for each received audio frame.
119 virtual void OnAudioFrame(scoped_ptr<media::cast::PcmAudioFrame> audio_frame,
120 const base::TimeTicks& playout_time) OVERRIDE {
121 DCHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
122
123 if (audio_frame->samples.empty()) {
124 NOTREACHED() << "OnAudioFrame called with no samples?!?";
125 return;
126 }
127
128 // Assume the audio signal is a single sine wave (it can have some
129 // low-amplitude noise). Count zero crossings, and extrapolate the
130 // frequency of the sine wave in |audio_frame|.
131 const int crossings = media::cast::CountZeroCrossings(audio_frame->samples);
132 const float seconds_per_frame = audio_frame->samples.size() /
133 static_cast<float>(audio_frame->frequency);
134 const float frequency_in_frame = crossings / seconds_per_frame;
135
136 const float kAveragingWeight = 0.1f;
137 UpdateExponentialMovingAverage(
138 kAveragingWeight, frequency_in_frame, &current_tone_frequency_);
139 DVLOG(1) << "Current audio tone frequency: " << current_tone_frequency_;
140
141 {
142 base::AutoLock auto_lock(lock_);
hubbe 2014/03/04 22:42:26 What is this lock for? Doesn't all of these functi
miu 2014/03/06 06:09:15 No. All CastEnvironment threads are different fro
hubbe 2014/03/06 19:54:43 I missed the exclamation mark in the DCHECK in Wai
143 const float kTargetWindowHz = 20;
144 // Trigger the waiting thread while the current tone is within
145 // kTargetWindowHz of the target tone.
146 if (fabsf(current_tone_frequency_ - target_tone_frequency_) <
147 kTargetWindowHz) {
148 if (first_time_near_target_tone_.is_null())
149 first_time_near_target_tone_ = cast_env()->Clock()->NowTicks();
150 cond_.Broadcast();
151 } else {
152 first_time_near_target_tone_ = base::TimeTicks();
153 }
154 }
155 }
156
157 virtual void OnVideoFrame(const scoped_refptr<media::VideoFrame>& video_frame,
158 const base::TimeTicks& render_time) OVERRIDE {
159 DCHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
160
161 CHECK(video_frame->format() == media::VideoFrame::YV12 ||
162 video_frame->format() == media::VideoFrame::I420 ||
163 video_frame->format() == media::VideoFrame::YV12A);
164
165 // Note: We take the median value of each plane because the test image will
166 // contain mostly a solid color plus some "cruft" which is the "Testing..."
167 // text in the upper-left corner of the video frame. In other words, we
168 // want to read "the most common color."
169 const float kAveragingWeight = 0.2f;
170 #define UPDATE_FOR_PLANE(which, kXPlane) \
171 const uint8 median_##which = ComputeMedianIntensityInPlane( \
172 video_frame->row_bytes(media::VideoFrame::kXPlane), \
173 video_frame->rows(media::VideoFrame::kXPlane), \
174 video_frame->stride(media::VideoFrame::kXPlane), \
175 video_frame->data(media::VideoFrame::kXPlane)); \
176 UpdateExponentialMovingAverage( \
hubbe 2014/03/04 22:42:26 Why do we need a moving average? A moving average
miu 2014/03/06 06:09:15 Good point. Done.
177 kAveragingWeight, median_##which, &current_color_##which##_)
178
179 UPDATE_FOR_PLANE(y, kYPlane);
hubbe 2014/03/04 22:42:26 I think an uint8 medians[3], a const int planes[]
miu 2014/03/06 06:09:15 Done.
180 UPDATE_FOR_PLANE(u, kUPlane);
181 UPDATE_FOR_PLANE(v, kVPlane);
182
183 #undef UPDATE_FOR_PLANE
184
185 DVLOG(1) << "Current video color: yuv(" << current_color_y_ << ", "
186 << current_color_u_ << ", " << current_color_v_ << ')';
187
188 {
189 base::AutoLock auto_lock(lock_);
190 const float kTargetWindow = 10.0f;
191 // Trigger the waiting thread while all color channels are within
192 // kTargetWindow of the target.
193 if (fabsf(current_color_y_ - target_color_y_) < kTargetWindow &&
194 fabsf(current_color_u_ - target_color_u_) < kTargetWindow &&
195 fabsf(current_color_v_ - target_color_v_) < kTargetWindow) {
196 if (first_time_near_target_color_.is_null())
197 first_time_near_target_color_ = cast_env()->Clock()->NowTicks();
198 cond_.Broadcast();
199 } else {
200 first_time_near_target_color_ = base::TimeTicks();
201 }
202 }
203 }
204
205 static void UpdateExponentialMovingAverage(float weight,
206 float sample_value,
207 float* average) {
208 *average += weight * sample_value - weight * (*average);
hubbe 2014/03/04 22:42:26 Shouldn't this be: *average += weight * sample_val
miu 2014/03/06 06:09:15 Note: I'm using "*average += ..." and not "*averag
hubbe 2014/03/06 19:54:43 Ah tricky. Seems harder to read that way though.
miu 2014/03/07 22:40:29 Changed.
209 CHECK(base::IsFinite(*average));
210 }
211
212 static uint8 ComputeMedianIntensityInPlane(int width,
213 int height,
214 int stride,
215 uint8* data) {
216 const int num_pixels = width * height;
217 if (num_pixels <= 0)
218 return 0;
219 // If necessary, re-pack the pixels such that the stride is equal to the
220 // width.
221 if (width < stride) {
222 for (int y = 1; y < height; ++y) {
223 uint8* const src = data + y * stride;
224 uint8* const dest = data + y * width;
225 memmove(dest, src, width);
hubbe 2014/03/04 22:42:26 Are you sure we're actually allowed to modify the
miu 2014/03/06 06:09:15 Yes. There is no other reader of the frame.
hubbe 2014/03/06 19:54:43 I was more concerned about the vp8 library using t
226 }
227 }
228 const size_t middle_idx = num_pixels / 2;
229 std::nth_element(data, data + middle_idx, data + num_pixels);
230 return data[middle_idx];
231 }
232
233 base::Lock lock_;
234 base::ConditionVariable cond_;
235
236 float target_color_y_;
237 float target_color_u_;
238 float target_color_v_;
239 float target_tone_frequency_;
240
241 float current_color_y_;
242 float current_color_u_;
243 float current_color_v_;
244 base::TimeTicks first_time_near_target_color_;
245 float current_tone_frequency_;
246 base::TimeTicks first_time_near_target_tone_;
247
248 DISALLOW_COPY_AND_ASSIGN(TestPatternReceiver);
249 };
250
251 } // namespace
252
253 class CastStreamingApiTestWithPixelOutput : public CastStreamingApiTest {
254 virtual void SetUp() OVERRIDE {
255 if (!UsingOSMesa())
256 EnablePixelOutput();
257 CastStreamingApiTest::SetUp();
258 }
259 };
260
261 // Tests the Cast streaming API and its basic functionality end-to-end. An
262 // extension subtest is run to generate test content, capture that content, and
263 // use the API to send it out. At the same time, this test launches an
264 // in-process Cast receiver, listening on a localhost UDP socket, to receive the
265 // content and check whether it matches expectations.
266 //
267 // Note: This test is disabled until outstanding bugs are fixed and the
268 // media/cast library has achieved sufficient stability.
hubbe 2014/03/04 22:42:26 Maybe file a bug too?
miu 2014/03/06 06:09:15 Done.
269 IN_PROC_BROWSER_TEST_F(CastStreamingApiTestWithPixelOutput, DISABLED_EndToEnd) {
270 // This test is too slow to succeed with OSMesa on the bots.
271 if (UsingOSMesa()) {
272 LOG(WARNING) << "Skipping this test since OSMesa is too slow on the bots.";
273 return;
274 }
275
276 // Determine a unused UDP port for the in-process receiver to listen on, in
277 // the range [2300,2345]. We utilize the hero super-power known as "crossing
278 // our fingers" to find an unused port. Note: As of this writing, the cast
279 // sender runs on port 2346.
hubbe 2014/03/04 22:42:26 This is not a good way to do it. We should create
miu 2014/03/06 06:09:15 Done. I had looked several weeks ago for the help
280 net::IPAddressNumber localhost;
281 localhost.push_back(127);
282 localhost.push_back(0);
283 localhost.push_back(0);
284 localhost.push_back(1);
285 const int64 random_temporal_offset =
286 (base::TimeTicks::Now() - base::TimeTicks::UnixEpoch()).InMilliseconds() /
287 10;
288 const int hopefully_unused_receiver_port = 2300 + random_temporal_offset % 46;
289
290 // Start the in-process receiver that examines audio/video for the expected
291 // test patterns.
292 TestPatternReceiver receiver(
293 net::IPEndPoint(localhost, hopefully_unused_receiver_port));
294 receiver.Start();
295
296 // Launch the page that: 1) renders the source content; 2) uses the
297 // chrome.tabCapture and chrome.cast.streaming APIs to capture its content and
298 // stream using Cast; and 3) calls chrome.test.succeed() once it is
299 // operational.
300 const std::string& page_url = base::StringPrintf(
301 "end_to_end_sender.html?port=%d", hopefully_unused_receiver_port);
302 ASSERT_TRUE(RunExtensionSubtest("cast_streaming", page_url)) << message_;
303
304 // Examine the Cast receiver for expected audio/video test patterns. The
305 // colors and tones specified here must match those in end_to_end_sender.js.
306 const uint8 kRedInYUV[3] = {82, 90, 240}; // rgb(255, 0, 0)
307 const uint8 kGreenInYUV[3] = {145, 54, 34}; // rgb(0, 255, 0)
308 const uint8 kBlueInYUV[3] = {41, 240, 110}; // rgb(0, 0, 255)
309 const base::TimeDelta kOneHalfSecond = base::TimeDelta::FromMilliseconds(500);
310 EXPECT_TRUE(
311 receiver.WaitForColorAndTone(kRedInYUV, 200 /* Hz */, kOneHalfSecond));
312 EXPECT_TRUE(
313 receiver.WaitForColorAndTone(kGreenInYUV, 500 /* Hz */, kOneHalfSecond));
314 EXPECT_TRUE(
315 receiver.WaitForColorAndTone(kBlueInYUV, 1800 /* Hz */, kOneHalfSecond));
316 }
317
31 } // namespace extensions 318 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698