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(¤t_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, ¤t_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