// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
#include "base/debug/trace_event_impl.h"
#include "base/json/json_reader.h"
#include "base/strings/stringprintf.h"
#include "base/test/trace_event_analyzer.h"
#include "base/values.h"
#include "content/browser/media/webrtc_internals.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/webrtc_content_browsertest_base.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/perf/perf_test.h"
#if defined(OS_WIN)
#include "base/win/windows_version.h"
#endif
using trace_analyzer::TraceAnalyzer;
using trace_analyzer::Query;
using trace_analyzer::TraceEventVector;
namespace {
static const char kGetUserMediaAndStop[] = "getUserMediaAndStop";
static const char kGetUserMediaAndGetStreamUp[] = "getUserMediaAndGetStreamUp";
static const char kGetUserMediaAndAnalyseAndStop[] =
"getUserMediaAndAnalyseAndStop";
static const char kGetUserMediaAndExpectFailure[] =
"getUserMediaAndExpectFailure";
static const char kRenderSameTrackMediastreamAndStop[] =
"renderSameTrackMediastreamAndStop";
static const char kRenderClonedMediastreamAndStop[] =
"renderClonedMediastreamAndStop";
static const char kRenderClonedTrackMediastreamAndStop[] =
"renderClonedTrackMediastreamAndStop";
static const char kRenderDuplicatedMediastreamAndStop[] =
"renderDuplicatedMediastreamAndStop";
// Results returned by JS.
static const char kOK[] = "OK";
std::string GenerateGetUserMediaWithMandatorySourceID(
const std::string& function_name,
const std::string& audio_source_id,
const std::string& video_source_id) {
const std::string audio_constraint =
"audio: {mandatory: { sourceId:\"" + audio_source_id + "\"}}, ";
const std::string video_constraint =
"video: {mandatory: { sourceId:\"" + video_source_id + "\"}}";
return function_name + "({" + audio_constraint + video_constraint + "});";
}
std::string GenerateGetUserMediaWithOptionalSourceID(
const std::string& function_name,
const std::string& audio_source_id,
const std::string& video_source_id) {
const std::string audio_constraint =
"audio: {optional: [{sourceId:\"" + audio_source_id + "\"}]}, ";
const std::string video_constraint =
"video: {optional: [{ sourceId:\"" + video_source_id + "\"}]}";
return function_name + "({" + audio_constraint + video_constraint + "});";
}
} // namespace
namespace content {
class WebRtcGetUserMediaBrowserTest: public WebRtcContentBrowserTest,
public testing::WithParamInterface<bool> {
public:
WebRtcGetUserMediaBrowserTest() : trace_log_(NULL) {}
virtual ~WebRtcGetUserMediaBrowserTest() {}
virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
WebRtcContentBrowserTest::SetUpCommandLine(command_line);
bool enable_audio_track_processing = GetParam();
if (!enable_audio_track_processing)
command_line->AppendSwitch(switches::kDisableAudioTrackProcessing);
}
void StartTracing() {
CHECK(trace_log_ == NULL) << "Can only can start tracing once";
trace_log_ = base::debug::TraceLog::GetInstance();
trace_log_->SetEnabled(base::debug::CategoryFilter("video"),
base::debug::TraceLog::RECORDING_MODE,
base::debug::TraceLog::ENABLE_SAMPLING);
// Check that we are indeed recording.
EXPECT_EQ(trace_log_->GetNumTracesRecorded(), 1);
}
void StopTracing() {
CHECK(message_loop_runner_ == NULL) << "Calling StopTracing more than once";
trace_log_->SetDisabled();
message_loop_runner_ = new MessageLoopRunner;
trace_log_->Flush(base::Bind(
&WebRtcGetUserMediaBrowserTest::OnTraceDataCollected,
base::Unretained(this)));
message_loop_runner_->Run();
}
void OnTraceDataCollected(
const scoped_refptr<base::RefCountedString>& events_str_ptr,
bool has_more_events) {
CHECK(!has_more_events);
recorded_trace_data_ = events_str_ptr;
message_loop_runner_->Quit();
}
TraceAnalyzer* CreateTraceAnalyzer() {
return TraceAnalyzer::Create("[" + recorded_trace_data_->data() + "]");
}
void RunGetUserMediaAndCollectMeasures(const int time_to_sample_secs,
const std::string& measure_filter,
const std::string& graph_name) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
// Put getUserMedia to work and let it run for a couple of seconds.
DCHECK(time_to_sample_secs);
ExecuteJavascriptAndWaitForOk(
base::StringPrintf("%s({video: true});",
kGetUserMediaAndGetStreamUp));
// Now the stream is up and running, start collecting traces.
StartTracing();
// Let the stream run for a while in javascript.
ExecuteJavascriptAndWaitForOk(
base::StringPrintf("waitAndStopVideoTrack(%d);", time_to_sample_secs));
// Wait until the page title changes to "OK". Do not sleep() here since that
// would stop both this code and the browser underneath.
StopTracing();
scoped_ptr<TraceAnalyzer> analyzer(CreateTraceAnalyzer());
analyzer->AssociateBeginEndEvents();
trace_analyzer::TraceEventVector events;
DCHECK(measure_filter.size());
analyzer->FindEvents(
Query::EventNameIs(measure_filter),
&events);
ASSERT_GT(events.size(), 0u)
<< "Could not collect any samples during test, this is bad";
std::string duration_us;
std::string interarrival_us;
for (size_t i = 0; i != events.size(); ++i) {
duration_us.append(
base::StringPrintf("%d,", static_cast<int>(events[i]->duration)));
}
for (size_t i = 1; i < events.size(); ++i) {
// The event |timestamp| comes in ns, divide to get us like |duration|.
interarrival_us.append(base::StringPrintf("%d,",
static_cast<int>((events[i]->timestamp - events[i - 1]->timestamp) /
base::Time::kNanosecondsPerMicrosecond)));
}
perf_test::PrintResultList(
graph_name, "", "sample_duration", duration_us, "us", true);
perf_test::PrintResultList(
graph_name, "", "interarrival_time", interarrival_us, "us", true);
}
void RunTwoGetTwoGetUserMediaWithDifferentContraints(
const std::string& constraints1,
const std::string& constraints2,
const std::string& expected_result) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
std::string command = "twoGetUserMedia(" + constraints1 + ',' +
constraints2 + ')';
EXPECT_EQ(expected_result, ExecuteJavascriptAndReturnResult(command));
}
void GetInputDevices(std::vector<std::string>* audio_ids,
std::vector<std::string>* video_ids) {
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
std::string devices_as_json = ExecuteJavascriptAndReturnResult(
"getSources()");
EXPECT_FALSE(devices_as_json.empty());
int error_code;
std::string error_message;
scoped_ptr<base::Value> value(
base::JSONReader::ReadAndReturnError(devices_as_json,
base::JSON_ALLOW_TRAILING_COMMAS,
&error_code,
&error_message));
ASSERT_TRUE(value.get() != NULL) << error_message;
EXPECT_EQ(value->GetType(), base::Value::TYPE_LIST);
base::ListValue* values;
ASSERT_TRUE(value->GetAsList(&values));
for (base::ListValue::iterator it = values->begin();
it != values->end(); ++it) {
const base::DictionaryValue* dict;
std::string kind;
std::string device_id;
ASSERT_TRUE((*it)->GetAsDictionary(&dict));
ASSERT_TRUE(dict->GetString("kind", &kind));
ASSERT_TRUE(dict->GetString("id", &device_id));
ASSERT_FALSE(device_id.empty());
EXPECT_TRUE(kind == "audio" || kind == "video");
if (kind == "audio") {
audio_ids->push_back(device_id);
} else if (kind == "video") {
video_ids->push_back(device_id);
}
}
ASSERT_FALSE(audio_ids->empty());
ASSERT_FALSE(video_ids->empty());
}
private:
base::debug::TraceLog* trace_log_;
scoped_refptr<base::RefCountedString> recorded_trace_data_;
scoped_refptr<MessageLoopRunner> message_loop_runner_;
};
static const bool kRunTestsWithFlag[] = { false, true };
INSTANTIATE_TEST_CASE_P(WebRtcGetUserMediaBrowserTests,
WebRtcGetUserMediaBrowserTest,
testing::ValuesIn(kRunTestsWithFlag));
// These tests will all make a getUserMedia call with different constraints and
// see that the success callback is called. If the error callback is called or
// none of the callbacks are called the tests will simply time out and fail.
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, GetVideoStreamAndStop) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
ExecuteJavascriptAndWaitForOk(
base::StringPrintf("%s({video: true});", kGetUserMediaAndStop));
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
RenderSameTrackMediastreamAndStop) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
ExecuteJavascriptAndWaitForOk(
base::StringPrintf("%s({video: true});",
kRenderSameTrackMediastreamAndStop));
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
RenderClonedMediastreamAndStop) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
ExecuteJavascriptAndWaitForOk(
base::StringPrintf("%s({video: true});",
kRenderClonedMediastreamAndStop));
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
kRenderClonedTrackMediastreamAndStop) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
ExecuteJavascriptAndWaitForOk(
base::StringPrintf("%s({video: true});",
kRenderClonedTrackMediastreamAndStop));
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
kRenderDuplicatedMediastreamAndStop) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
ExecuteJavascriptAndWaitForOk(
base::StringPrintf("%s({video: true});",
kRenderDuplicatedMediastreamAndStop));
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
GetAudioAndVideoStreamAndStop) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
ExecuteJavascriptAndWaitForOk(base::StringPrintf(
"%s({video: true, audio: true});", kGetUserMediaAndStop));
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
GetAudioAndVideoStreamAndClone) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
ExecuteJavascriptAndWaitForOk("getUserMediaAndClone();");
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
RenderVideoTrackInMultipleTagsAndPause) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
ExecuteJavascriptAndWaitForOk("getUserMediaAndRenderInSeveralVideoTags();");
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
GetUserMediaWithMandatorySourceID) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
std::vector<std::string> audio_ids;
std::vector<std::string> video_ids;
GetInputDevices(&audio_ids, &video_ids);
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
// Test all combinations of mandatory sourceID;
for (std::vector<std::string>::const_iterator video_it = video_ids.begin();
video_it != video_ids.end(); ++video_it) {
for (std::vector<std::string>::const_iterator audio_it = audio_ids.begin();
audio_it != audio_ids.end(); ++audio_it) {
NavigateToURL(shell(), url);
EXPECT_EQ(kOK, ExecuteJavascriptAndReturnResult(
GenerateGetUserMediaWithMandatorySourceID(
kGetUserMediaAndStop,
*audio_it,
*video_it)));
}
}
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
GetUserMediaWithInvalidMandatorySourceID) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
std::vector<std::string> audio_ids;
std::vector<std::string> video_ids;
GetInputDevices(&audio_ids, &video_ids);
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
// Test with invalid mandatory audio sourceID.
NavigateToURL(shell(), url);
EXPECT_EQ("DevicesNotFoundError", ExecuteJavascriptAndReturnResult(
GenerateGetUserMediaWithMandatorySourceID(
kGetUserMediaAndExpectFailure,
"something invalid",
video_ids[0])));
// Test with invalid mandatory video sourceID.
EXPECT_EQ("DevicesNotFoundError", ExecuteJavascriptAndReturnResult(
GenerateGetUserMediaWithMandatorySourceID(
kGetUserMediaAndExpectFailure,
audio_ids[0],
"something invalid")));
// Test with empty mandatory audio sourceID.
EXPECT_EQ("DevicesNotFoundError", ExecuteJavascriptAndReturnResult(
GenerateGetUserMediaWithMandatorySourceID(
kGetUserMediaAndExpectFailure,
"",
video_ids[0])));
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
GetUserMediaWithInvalidOptionalSourceID) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
std::vector<std::string> audio_ids;
std::vector<std::string> video_ids;
GetInputDevices(&audio_ids, &video_ids);
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
// Test with invalid optional audio sourceID.
NavigateToURL(shell(), url);
EXPECT_EQ(kOK, ExecuteJavascriptAndReturnResult(
GenerateGetUserMediaWithOptionalSourceID(
kGetUserMediaAndStop,
"something invalid",
video_ids[0])));
// Test with invalid optional video sourceID.
EXPECT_EQ(kOK, ExecuteJavascriptAndReturnResult(
GenerateGetUserMediaWithOptionalSourceID(
kGetUserMediaAndStop,
audio_ids[0],
"something invalid")));
// Test with empty optional audio sourceID.
EXPECT_EQ(kOK, ExecuteJavascriptAndReturnResult(
GenerateGetUserMediaWithOptionalSourceID(
kGetUserMediaAndStop,
"",
video_ids[0])));
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest, TwoGetUserMediaAndStop) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
NavigateToURL(shell(), url);
ExecuteJavascriptAndWaitForOk(
"twoGetUserMediaAndStop({video: true, audio: true});");
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
TwoGetUserMediaWithEqualConstraints) {
std::string constraints1 = "{video: true, audio: true}";
const std::string& constraints2 = constraints1;
std::string expected_result = "w=640:h=480-w=640:h=480";
RunTwoGetTwoGetUserMediaWithDifferentContraints(constraints1, constraints2,
expected_result);
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
TwoGetUserMediaWithSecondVideoCropped) {
std::string constraints1 = "{video: true}";
std::string constraints2 = "{video: {mandatory: {maxHeight: 360}}}";
std::string expected_result = "w=640:h=480-w=640:h=360";
RunTwoGetTwoGetUserMediaWithDifferentContraints(constraints1, constraints2,
expected_result);
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
TwoGetUserMediaWithFirstHdSecondVga) {
std::string constraints1 =
"{video: {mandatory: {minWidth:1280 , minHeight: 720}}}";
std::string constraints2 =
"{video: {mandatory: {maxWidth:640 , maxHeight: 480}}}";
std::string expected_result = "w=1280:h=720-w=640:h=480";
RunTwoGetTwoGetUserMediaWithDifferentContraints(constraints1, constraints2,
expected_result);
}
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
GetUserMediaWithTooHighVideoConstraintsValues) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
int large_value = 99999;
std::string call = GenerateGetUserMediaCall(kGetUserMediaAndExpectFailure,
large_value,
large_value,
large_value,
large_value,
large_value,
large_value);
NavigateToURL(shell(), url);
// TODO(perkj): A proper error code should be returned by gUM.
EXPECT_EQ("TrackStartError", ExecuteJavascriptAndReturnResult(call));
}
// This test will make a simple getUserMedia page, verify that video is playing
// in a simple local <video>, and for a couple of seconds, collect some
// performance traces from VideoCaptureController colorspace conversion and
// potential resizing.
IN_PROC_BROWSER_TEST_P(
WebRtcGetUserMediaBrowserTest,
TraceVideoCaptureControllerPerformanceDuringGetUserMedia) {
RunGetUserMediaAndCollectMeasures(
10,
"VideoCaptureController::OnIncomingCapturedData",
"VideoCaptureController");
}
// This test calls getUserMedia and checks for aspect ratio behavior.
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
TestGetUserMediaAspectRatio4To3) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
std::string constraints_4_3 = GenerateGetUserMediaCall(
kGetUserMediaAndAnalyseAndStop, 640, 640, 480, 480, 10, 30);
NavigateToURL(shell(), url);
ASSERT_EQ("w=640:h=480",
ExecuteJavascriptAndReturnResult(constraints_4_3));
}
// This test calls getUserMedia and checks for aspect ratio behavior.
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
TestGetUserMediaAspectRatio16To9) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
std::string constraints_16_9 = GenerateGetUserMediaCall(
kGetUserMediaAndAnalyseAndStop, 640, 640, 360, 360, 10, 30);
NavigateToURL(shell(), url);
ASSERT_EQ("w=640:h=360",
ExecuteJavascriptAndReturnResult(constraints_16_9));
}
// This test calls getUserMedia and checks for aspect ratio behavior.
IN_PROC_BROWSER_TEST_P(WebRtcGetUserMediaBrowserTest,
TestGetUserMediaAspectRatio1To1) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
std::string constraints_1_1 = GenerateGetUserMediaCall(
kGetUserMediaAndAnalyseAndStop, 320, 320, 320, 320, 10, 30);
NavigateToURL(shell(), url);
ASSERT_EQ("w=320:h=320",
ExecuteJavascriptAndReturnResult(constraints_1_1));
}
namespace {
struct UserMediaSizes {
int min_width;
int max_width;
int min_height;
int max_height;
int min_frame_rate;
int max_frame_rate;
};
} // namespace
class WebRtcConstraintsBrowserTest
: public WebRtcContentBrowserTest,
public testing::WithParamInterface<UserMediaSizes> {
public:
WebRtcConstraintsBrowserTest() : user_media_(GetParam()) {}
const UserMediaSizes& user_media() const { return user_media_; }
private:
UserMediaSizes user_media_;
};
// This test calls getUserMedia in sequence with different constraints.
IN_PROC_BROWSER_TEST_P(WebRtcConstraintsBrowserTest, GetUserMediaConstraints) {
ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
std::string call = GenerateGetUserMediaCall(kGetUserMediaAndStop,
user_media().min_width,
user_media().max_width,
user_media().min_height,
user_media().max_height,
user_media().min_frame_rate,
user_media().max_frame_rate);
DVLOG(1) << "Calling getUserMedia: " << call;
NavigateToURL(shell(), url);
ExecuteJavascriptAndWaitForOk(call);
}
static const UserMediaSizes kAllUserMediaSizes[] = {
{320, 320, 180, 180, 10, 30},
{320, 320, 240, 240, 10, 30},
{640, 640, 360, 360, 10, 30},
{640, 640, 480, 480, 10, 30},
{960, 960, 720, 720, 10, 30},
{1280, 1280, 720, 720, 10, 30}};
INSTANTIATE_TEST_CASE_P(UserMedia,
WebRtcConstraintsBrowserTest,
testing::ValuesIn(kAllUserMediaSizes));
} // namespace content