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