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

Side by Side Diff: chrome/browser/extensions/api/cast_streaming/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: Fix LoggingImplTest + REBASE 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
(Empty)
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
3 // found in the LICENSE file.
4
5 #include <algorithm>
6 #include <cmath>
7
8 #include "base/command_line.h"
9 #include "base/float_util.h"
10 #include "base/run_loop.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/synchronization/lock.h"
13 #include "base/time/time.h"
14 #include "chrome/browser/extensions/extension_apitest.h"
15 #include "chrome/common/chrome_switches.h"
16 #include "content/public/common/content_switches.h"
17 #include "media/base/bind_to_current_loop.h"
18 #include "media/base/video_frame.h"
19 #include "media/cast/cast_config.h"
20 #include "media/cast/cast_environment.h"
21 #include "media/cast/test/utility/audio_utility.h"
22 #include "media/cast/test/utility/default_config.h"
23 #include "media/cast/test/utility/in_process_receiver.h"
24 #include "media/cast/test/utility/standalone_cast_environment.h"
25 #include "net/base/net_errors.h"
26 #include "net/base/net_util.h"
27 #include "net/base/rand_callback.h"
28 #include "net/udp/udp_socket.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30
31 namespace extensions {
32
33 class CastStreamingApiTest : public ExtensionApiTest {
34 public:
35 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
36 ExtensionApiTest::SetUpCommandLine(command_line);
37 command_line->AppendSwitchASCII(switches::kWhitelistedExtensionID,
38 "ddchlicdkolnonkihahngkmmmjnjlkkf");
39 }
40 };
41
42 // Test running the test extension for Cast Mirroring API.
43 IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, Basics) {
44 ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "basics.html")) << message_;
45 }
46
47 IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, Stats) {
48 ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "stats.html")) << message_;
49 }
50
51 IN_PROC_BROWSER_TEST_F(CastStreamingApiTest, BadLogging) {
52 ASSERT_TRUE(RunExtensionSubtest("cast_streaming", "bad_logging.html"))
53 << message_;
54 }
55
56 namespace {
57
58 // An in-process Cast receiver that examines the audio/video frames being
59 // received for expected colors and tones. Used in
60 // CastStreamingApiTest.EndToEnd, below.
61 class TestPatternReceiver : public media::cast::InProcessReceiver {
62 public:
63 explicit TestPatternReceiver(
64 const scoped_refptr<media::cast::CastEnvironment>& cast_environment,
65 const net::IPEndPoint& local_end_point)
66 : InProcessReceiver(cast_environment,
67 local_end_point,
68 net::IPEndPoint(),
69 media::cast::GetDefaultAudioReceiverConfig(),
70 media::cast::GetDefaultVideoReceiverConfig()),
71 target_tone_frequency_(0),
72 current_tone_frequency_(0.0f) {
73 memset(&target_color_, 0, sizeof(target_color_));
74 memset(&current_color_, 0, sizeof(current_color_));
75 }
76
77 virtual ~TestPatternReceiver() {}
78
79 // Blocks the caller until this receiver has seen both |yuv_color| and
80 // |tone_frequency| consistently for the given |duration|.
81 void WaitForColorAndTone(const uint8 yuv_color[3],
82 int tone_frequency,
83 base::TimeDelta duration) {
84 LOG(INFO) << "Waiting for test pattern: color=yuv("
85 << static_cast<int>(yuv_color[0]) << ", "
86 << static_cast<int>(yuv_color[1]) << ", "
87 << static_cast<int>(yuv_color[2])
88 << "), tone_frequency=" << tone_frequency << " Hz";
89
90 base::RunLoop run_loop;
91 cast_env()->PostTask(
92 media::cast::CastEnvironment::MAIN,
93 FROM_HERE,
94 base::Bind(&TestPatternReceiver::NotifyOnceMatched,
95 base::Unretained(this),
96 yuv_color,
97 tone_frequency,
98 duration,
99 media::BindToCurrentLoop(run_loop.QuitClosure())));
100 run_loop.Run();
101 }
102
103 private:
104 // Resets tracking data and sets the match duration and callback.
105 void NotifyOnceMatched(const uint8 yuv_color[3],
106 int tone_frequency,
107 base::TimeDelta match_duration,
108 const base::Closure& matched_callback) {
109 DCHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
110
111 match_duration_ = match_duration;
112 matched_callback_ = matched_callback;
113 target_color_[0] = yuv_color[0];
114 target_color_[1] = yuv_color[1];
115 target_color_[2] = yuv_color[2];
116 target_tone_frequency_ = tone_frequency;
117 first_time_near_target_color_ = base::TimeTicks();
118 first_time_near_target_tone_ = base::TimeTicks();
119 }
120
121 // Runs |matched_callback_| once both color and tone have been matched for the
122 // required |match_duration_|.
123 void NotifyIfMatched() {
124 DCHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
125
126 // TODO(miu): Check audio tone too, once audio is fixed in the library.
127 // http://crbug.com/349295
128 if (first_time_near_target_color_.is_null() ||
129 /*first_time_near_target_tone_.is_null()*/ false)
130 return;
131 const base::TimeTicks now = cast_env()->Clock()->NowTicks();
132 if ((now - first_time_near_target_color_) >= match_duration_ &&
133 /*(now - first_time_near_target_tone_) >= match_duration_*/ true) {
134 matched_callback_.Run();
135 }
136 }
137
138 // Invoked by InProcessReceiver for each received audio frame.
139 virtual void OnAudioFrame(scoped_ptr<media::cast::PcmAudioFrame> audio_frame,
140 const base::TimeTicks& playout_time) OVERRIDE {
141 DCHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
142
143 if (audio_frame->samples.empty()) {
144 NOTREACHED() << "OnAudioFrame called with no samples?!?";
145 return;
146 }
147
148 // Assume the audio signal is a single sine wave (it can have some
149 // low-amplitude noise). Count zero crossings, and extrapolate the
150 // frequency of the sine wave in |audio_frame|.
151 const int crossings = media::cast::CountZeroCrossings(audio_frame->samples);
152 const float seconds_per_frame = audio_frame->samples.size() /
153 static_cast<float>(audio_frame->frequency);
154 const float frequency_in_frame = crossings / seconds_per_frame;
155
156 const float kAveragingWeight = 0.1f;
157 UpdateExponentialMovingAverage(
158 kAveragingWeight, frequency_in_frame, &current_tone_frequency_);
159 VLOG(1) << "Current audio tone frequency: " << current_tone_frequency_;
160
161 const float kTargetWindowHz = 20;
162 // Update the time at which the current tone started falling within
163 // kTargetWindowHz of the target tone.
164 if (fabsf(current_tone_frequency_ - target_tone_frequency_) <
165 kTargetWindowHz) {
166 if (first_time_near_target_tone_.is_null())
167 first_time_near_target_tone_ = cast_env()->Clock()->NowTicks();
168 NotifyIfMatched();
169 } else {
170 first_time_near_target_tone_ = base::TimeTicks();
171 }
172 }
173
174 virtual void OnVideoFrame(const scoped_refptr<media::VideoFrame>& video_frame,
175 const base::TimeTicks& render_time) OVERRIDE {
176 DCHECK(cast_env()->CurrentlyOn(media::cast::CastEnvironment::MAIN));
177
178 CHECK(video_frame->format() == media::VideoFrame::YV12 ||
179 video_frame->format() == media::VideoFrame::I420 ||
180 video_frame->format() == media::VideoFrame::YV12A);
181
182 // Note: We take the median value of each plane because the test image will
183 // contain mostly a solid color plus some "cruft" which is the "Testing..."
184 // text in the upper-left corner of the video frame. In other words, we
185 // want to read "the most common color."
186 const int kPlanes[] = {media::VideoFrame::kYPlane,
187 media::VideoFrame::kUPlane,
188 media::VideoFrame::kVPlane};
189 for (size_t i = 0; i < arraysize(kPlanes); ++i) {
190 current_color_[i] =
191 ComputeMedianIntensityInPlane(video_frame->row_bytes(kPlanes[i]),
192 video_frame->rows(kPlanes[i]),
193 video_frame->stride(kPlanes[i]),
194 video_frame->data(kPlanes[i]));
195 }
196
197 VLOG(1) << "Current video color: yuv(" << current_color_[0] << ", "
198 << current_color_[1] << ", " << current_color_[2] << ')';
199
200 const float kTargetWindow = 10.0f;
201 // Update the time at which all color channels started falling within
202 // kTargetWindow of the target.
203 if (fabsf(current_color_[0] - target_color_[0]) < kTargetWindow &&
204 fabsf(current_color_[1] - target_color_[1]) < kTargetWindow &&
205 fabsf(current_color_[2] - target_color_[2]) < kTargetWindow) {
206 if (first_time_near_target_color_.is_null())
207 first_time_near_target_color_ = cast_env()->Clock()->NowTicks();
208 NotifyIfMatched();
209 } else {
210 first_time_near_target_color_ = base::TimeTicks();
211 }
212 }
213
214 static void UpdateExponentialMovingAverage(float weight,
215 float sample_value,
216 float* average) {
217 *average = weight * sample_value + (1.0f - weight) * (*average);
218 CHECK(base::IsFinite(*average));
219 }
220
221 static uint8 ComputeMedianIntensityInPlane(int width,
222 int height,
223 int stride,
224 uint8* data) {
225 const int num_pixels = width * height;
226 if (num_pixels <= 0)
227 return 0;
228 // If necessary, re-pack the pixels such that the stride is equal to the
229 // width.
230 if (width < stride) {
231 for (int y = 1; y < height; ++y) {
232 uint8* const src = data + y * stride;
233 uint8* const dest = data + y * width;
234 memmove(dest, src, width);
235 }
236 }
237 const size_t middle_idx = num_pixels / 2;
238 std::nth_element(data, data + middle_idx, data + num_pixels);
239 return data[middle_idx];
240 }
241
242 base::TimeDelta match_duration_;
243 base::Closure matched_callback_;
244
245 float target_color_[3]; // Y, U, V
246 float target_tone_frequency_;
247
248 float current_color_[3]; // Y, U, V
249 base::TimeTicks first_time_near_target_color_;
250 float current_tone_frequency_;
251 base::TimeTicks first_time_near_target_tone_;
252
253 DISALLOW_COPY_AND_ASSIGN(TestPatternReceiver);
254 };
255
256 } // namespace
257
258 class CastStreamingApiTestWithPixelOutput : public CastStreamingApiTest {
259 virtual void SetUp() OVERRIDE {
260 EnablePixelOutput();
261 CastStreamingApiTest::SetUp();
262 }
263
264 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
265 command_line->AppendSwitchASCII(switches::kWindowSize, "128,128");
266 CastStreamingApiTest::SetUpCommandLine(command_line);
267 }
268 };
269
270 // Tests the Cast streaming API and its basic functionality end-to-end. An
271 // extension subtest is run to generate test content, capture that content, and
272 // use the API to send it out. At the same time, this test launches an
273 // in-process Cast receiver, listening on a localhost UDP socket, to receive the
274 // content and check whether it matches expectations.
275 //
276 // Note: This test is disabled until outstanding bugs are fixed and the
277 // media/cast library has achieved sufficient stability.
278 // http://crbug.com/349599
279 IN_PROC_BROWSER_TEST_F(CastStreamingApiTestWithPixelOutput, DISABLED_EndToEnd) {
280 // Determine a unused UDP port for the in-process receiver to listen on.
281 // Method: Bind a UDP socket on port 0, and then check which port the
282 // operating system assigned to it.
283 net::IPAddressNumber localhost;
284 localhost.push_back(127);
285 localhost.push_back(0);
286 localhost.push_back(0);
287 localhost.push_back(1);
288 scoped_ptr<net::UDPSocket> receive_socket(
289 new net::UDPSocket(net::DatagramSocket::DEFAULT_BIND,
290 net::RandIntCallback(),
291 NULL,
292 net::NetLog::Source()));
293 receive_socket->AllowAddressReuse();
294 ASSERT_EQ(net::OK, receive_socket->Bind(net::IPEndPoint(localhost, 0)));
295 net::IPEndPoint receiver_end_point;
296 ASSERT_EQ(net::OK, receive_socket->GetLocalAddress(&receiver_end_point));
297 receive_socket.reset();
298
299 // Start the in-process receiver that examines audio/video for the expected
300 // test patterns.
301 const scoped_refptr<media::cast::StandaloneCastEnvironment> cast_environment(
302 new media::cast::StandaloneCastEnvironment(
303 media::cast::CastLoggingConfig()));
304 TestPatternReceiver* const receiver =
305 new TestPatternReceiver(cast_environment, receiver_end_point);
306 receiver->Start();
307
308 // Launch the page that: 1) renders the source content; 2) uses the
309 // chrome.tabCapture and chrome.cast.streaming APIs to capture its content and
310 // stream using Cast; and 3) calls chrome.test.succeed() once it is
311 // operational.
312 const std::string page_url = base::StringPrintf(
313 "end_to_end_sender.html?port=%d", receiver_end_point.port());
314 ASSERT_TRUE(RunExtensionSubtest("cast_streaming", page_url)) << message_;
315
316 // Examine the Cast receiver for expected audio/video test patterns. The
317 // colors and tones specified here must match those in end_to_end_sender.js.
318 const uint8 kRedInYUV[3] = {82, 90, 240}; // rgb(255, 0, 0)
319 const uint8 kGreenInYUV[3] = {145, 54, 34}; // rgb(0, 255, 0)
320 const uint8 kBlueInYUV[3] = {41, 240, 110}; // rgb(0, 0, 255)
321 const base::TimeDelta kOneHalfSecond = base::TimeDelta::FromMilliseconds(500);
322 receiver->WaitForColorAndTone(kRedInYUV, 200 /* Hz */, kOneHalfSecond);
323 receiver->WaitForColorAndTone(kGreenInYUV, 500 /* Hz */, kOneHalfSecond);
324 receiver->WaitForColorAndTone(kBlueInYUV, 1800 /* Hz */, kOneHalfSecond);
325
326 // TODO(miu): Uncomment once GetWeakPtr() NULL crash in PacedSender is fixed
327 // (see http://crbug.com/349786):
328 // receiver->DestroySoon();
329 cast_environment->Shutdown();
330 }
331
332 } // namespace extensions
OLDNEW
« no previous file with comments | « chrome/browser/extensions/api/cast_streaming/OWNERS ('k') | chrome/browser/extensions/cast_streaming_apitest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698