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