summaryrefslogtreecommitdiff
path: root/chromium/components/mirroring/service
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-08-24 12:15:48 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-08-28 13:30:04 +0000
commitb014812705fc80bff0a5c120dfcef88f349816dc (patch)
tree25a2e2d9fa285f1add86aa333389a839f81a39ae /chromium/components/mirroring/service
parent9f4560b1027ae06fdb497023cdcaf91b8511fa74 (diff)
downloadqtwebengine-chromium-b014812705fc80bff0a5c120dfcef88f349816dc.tar.gz
BASELINE: Update Chromium to 68.0.3440.125
Change-Id: I23f19369e01f688e496f5bf179abb521ad73874f Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/mirroring/service')
-rw-r--r--chromium/components/mirroring/service/BUILD.gn23
-rw-r--r--chromium/components/mirroring/service/DEPS7
-rw-r--r--chromium/components/mirroring/service/fake_network_service.cc10
-rw-r--r--chromium/components/mirroring/service/fake_network_service.h52
-rw-r--r--chromium/components/mirroring/service/fake_video_capture_host.cc8
-rw-r--r--chromium/components/mirroring/service/interface.cc15
-rw-r--r--chromium/components/mirroring/service/interface.h74
-rw-r--r--chromium/components/mirroring/service/message_dispatcher.cc157
-rw-r--r--chromium/components/mirroring/service/message_dispatcher.h73
-rw-r--r--chromium/components/mirroring/service/message_dispatcher_unittest.cc299
-rw-r--r--chromium/components/mirroring/service/mirror_settings.cc148
-rw-r--r--chromium/components/mirroring/service/mirror_settings.h57
-rw-r--r--chromium/components/mirroring/service/receiver_response.cc199
-rw-r--r--chromium/components/mirroring/service/receiver_response.h134
-rw-r--r--chromium/components/mirroring/service/receiver_response_unittest.cc263
-rw-r--r--chromium/components/mirroring/service/rtp_stream.cc99
-rw-r--r--chromium/components/mirroring/service/rtp_stream.h9
-rw-r--r--chromium/components/mirroring/service/rtp_stream_unittest.cc4
-rw-r--r--chromium/components/mirroring/service/session.cc551
-rw-r--r--chromium/components/mirroring/service/session.h75
-rw-r--r--chromium/components/mirroring/service/session_monitor.cc412
-rw-r--r--chromium/components/mirroring/service/session_monitor.h142
-rw-r--r--chromium/components/mirroring/service/session_monitor_unittest.cc397
-rw-r--r--chromium/components/mirroring/service/session_unittest.cc118
-rw-r--r--chromium/components/mirroring/service/udp_socket_client.cc8
-rw-r--r--chromium/components/mirroring/service/udp_socket_client.h4
-rw-r--r--chromium/components/mirroring/service/udp_socket_client_unittest.cc6
-rw-r--r--chromium/components/mirroring/service/value_util.cc95
-rw-r--r--chromium/components/mirroring/service/value_util.h39
-rw-r--r--chromium/components/mirroring/service/video_capture_client.cc31
-rw-r--r--chromium/components/mirroring/service/video_capture_client.h9
-rw-r--r--chromium/components/mirroring/service/video_capture_client_unittest.cc15
-rw-r--r--chromium/components/mirroring/service/wifi_status_monitor.cc80
-rw-r--r--chromium/components/mirroring/service/wifi_status_monitor.h61
-rw-r--r--chromium/components/mirroring/service/wifi_status_monitor_unittest.cc173
35 files changed, 3455 insertions, 392 deletions
diff --git a/chromium/components/mirroring/service/BUILD.gn b/chromium/components/mirroring/service/BUILD.gn
index a8f92d0058e..a5b3ef6d2f4 100644
--- a/chromium/components/mirroring/service/BUILD.gn
+++ b/chromium/components/mirroring/service/BUILD.gn
@@ -6,6 +6,7 @@ import("//testing/test.gni")
source_set("interface") {
sources = [
+ "interface.cc",
"interface.h",
]
@@ -24,14 +25,26 @@ source_set("interface") {
source_set("service") {
sources = [
+ "message_dispatcher.cc",
+ "message_dispatcher.h",
+ "mirror_settings.cc",
+ "mirror_settings.h",
+ "receiver_response.cc",
+ "receiver_response.h",
"rtp_stream.cc",
"rtp_stream.h",
"session.cc",
"session.h",
+ "session_monitor.cc",
+ "session_monitor.h",
"udp_socket_client.cc",
"udp_socket_client.h",
+ "value_util.cc",
+ "value_util.h",
"video_capture_client.cc",
"video_capture_client.h",
+ "wifi_status_monitor.cc",
+ "wifi_status_monitor.h",
]
public_deps = [
@@ -40,7 +53,10 @@ source_set("service") {
deps = [
":interface",
+ "//components/version_info",
+ "//crypto",
"//media",
+ "//media/capture:capture_base",
"//media/capture/mojom:video_capture",
"//media/cast:common",
"//media/cast:net",
@@ -49,6 +65,7 @@ source_set("service") {
"//mojo/public/cpp/bindings",
"//mojo/public/cpp/system",
"//net",
+ "//services/network/public/cpp",
"//services/network/public/mojom",
"//ui/gfx",
]
@@ -61,10 +78,14 @@ source_set("unittests") {
"fake_network_service.h",
"fake_video_capture_host.cc",
"fake_video_capture_host.h",
+ "message_dispatcher_unittest.cc",
+ "receiver_response_unittest.cc",
"rtp_stream_unittest.cc",
+ "session_monitor_unittest.cc",
"session_unittest.cc",
"udp_socket_client_unittest.cc",
"video_capture_client_unittest.cc",
+ "wifi_status_monitor_unittest.cc",
]
deps = [
@@ -81,6 +102,8 @@ source_set("unittests") {
"//media/cast:test_support",
"//mojo/public/cpp/bindings",
"//net",
+ "//services/network:test_support",
+ "//services/network/public/cpp",
"//services/network/public/mojom",
"//testing/gmock",
"//testing/gtest",
diff --git a/chromium/components/mirroring/service/DEPS b/chromium/components/mirroring/service/DEPS
index d924f835b89..ddfc4b1cd47 100644
--- a/chromium/components/mirroring/service/DEPS
+++ b/chromium/components/mirroring/service/DEPS
@@ -1,4 +1,9 @@
include_rules = [
+ "-components",
+ "+components/mirroring/service",
+ "+components/version_info",
+ "+crypto",
"+net",
- "+services/network/public/mojom",
+ "+services/network/public",
+ "+services/network/test",
]
diff --git a/chromium/components/mirroring/service/fake_network_service.cc b/chromium/components/mirroring/service/fake_network_service.cc
index ca32b975005..971a350aef8 100644
--- a/chromium/components/mirroring/service/fake_network_service.cc
+++ b/chromium/components/mirroring/service/fake_network_service.cc
@@ -5,6 +5,8 @@
#include "components/mirroring/service/fake_network_service.h"
#include "media/cast/test/utility/net_utility.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "services/network/test/test_url_loader_factory.h"
namespace mirroring {
@@ -63,4 +65,12 @@ void MockNetworkContext::CreateUDPSocket(
OnUDPSocketCreated();
}
+void MockNetworkContext::CreateURLLoaderFactory(
+ network::mojom::URLLoaderFactoryRequest request,
+ network::mojom::URLLoaderFactoryParamsPtr params) {
+ ASSERT_TRUE(params);
+ mojo::MakeStrongBinding(std::make_unique<network::TestURLLoaderFactory>(),
+ std::move(request));
+}
+
} // namespace mirroring
diff --git a/chromium/components/mirroring/service/fake_network_service.h b/chromium/components/mirroring/service/fake_network_service.h
index 8237befae49..3612147a52b 100644
--- a/chromium/components/mirroring/service/fake_network_service.h
+++ b/chromium/components/mirroring/service/fake_network_service.h
@@ -8,8 +8,8 @@
#include "base/callback.h"
#include "media/cast/net/cast_transport_config.h"
#include "mojo/public/cpp/bindings/binding.h"
-#include "services/network/public/mojom/network_service.mojom.h"
#include "services/network/public/mojom/udp_socket.mojom.h"
+#include "services/network/test/test_network_context.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -61,7 +61,7 @@ class MockUdpSocket final : public network::mojom::UDPSocket {
DISALLOW_COPY_AND_ASSIGN(MockUdpSocket);
};
-class MockNetworkContext final : public network::mojom::NetworkContext {
+class MockNetworkContext final : public network::TestNetworkContext {
public:
explicit MockNetworkContext(network::mojom::NetworkContextRequest request);
~MockNetworkContext() override;
@@ -69,53 +69,11 @@ class MockNetworkContext final : public network::mojom::NetworkContext {
MOCK_METHOD0(OnUDPSocketCreated, void());
// network::mojom::NetworkContext implementation:
- void CreateURLLoaderFactory(network::mojom::URLLoaderFactoryRequest request,
- uint32_t process_id) override {}
- void GetCookieManager(network::mojom::CookieManagerRequest request) override {
- }
- void GetRestrictedCookieManager(
- network::mojom::RestrictedCookieManagerRequest request,
- int32_t render_process_id,
- int32_t render_frame_id) override {}
- void ClearNetworkingHistorySince(
- base::Time time,
- base::OnceClosure completion_callback) override {}
- void ClearHttpCache(base::Time start_time,
- base::Time end_time,
- network::mojom::ClearCacheUrlFilterPtr filter,
- ClearHttpCacheCallback callback) override {}
- void SetNetworkConditions(
- const std::string& profile_id,
- network::mojom::NetworkConditionsPtr conditions) override {}
- void SetAcceptLanguage(const std::string& new_accept_language) override {}
- void SetCTPolicy(
- const std::vector<std::string>& required_hosts,
- const std::vector<std::string>& excluded_hosts,
- const std::vector<std::string>& excluded_spkis,
- const std::vector<std::string>& excluded_legacy_spkis) override {}
- void AddHSTSForTesting(const std::string& host,
- base::Time expiry,
- bool include_subdomains,
- AddHSTSForTestingCallback callback) override {}
void CreateUDPSocket(network::mojom::UDPSocketRequest request,
network::mojom::UDPSocketReceiverPtr receiver) override;
- void CreateTCPServerSocket(
- const net::IPEndPoint& local_addr,
- uint32_t backlog,
- const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
- network::mojom::TCPServerSocketRequest request,
- CreateTCPServerSocketCallback callback) override {}
- void CreateTCPConnectedSocket(
- const base::Optional<net::IPEndPoint>& local_addr,
- const net::AddressList& remote_addr_list,
- const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
- network::mojom::TCPConnectedSocketRequest request,
- network::mojom::TCPConnectedSocketObserverPtr observer,
- CreateTCPConnectedSocketCallback callback) override {}
- void CreateWebSocket(network::mojom::WebSocketRequest request,
- int process_id,
- int render_frame_id,
- const url::Origin& origin) override {}
+ void CreateURLLoaderFactory(
+ network::mojom::URLLoaderFactoryRequest request,
+ network::mojom::URLLoaderFactoryParamsPtr params) override;
MockUdpSocket* udp_socket() const { return udp_socket_.get(); }
diff --git a/chromium/components/mirroring/service/fake_video_capture_host.cc b/chromium/components/mirroring/service/fake_video_capture_host.cc
index 7e61d2da7d6..56a1574a3ba 100644
--- a/chromium/components/mirroring/service/fake_video_capture_host.cc
+++ b/chromium/components/mirroring/service/fake_video_capture_host.cc
@@ -41,7 +41,10 @@ void FakeVideoCaptureHost::SendOneFrame(const gfx::Size& size,
mojo::ScopedSharedBufferHandle buffer =
mojo::SharedBufferHandle::Create(5000);
memset(buffer->Map(5000).get(), 125, 5000);
- observer_->OnBufferCreated(0, std::move(buffer));
+ media::mojom::VideoBufferHandlePtr buffer_handle =
+ media::mojom::VideoBufferHandle::New();
+ buffer_handle->set_shared_buffer_handle(std::move(buffer));
+ observer_->OnNewBuffer(0, std::move(buffer_handle));
media::VideoFrameMetadata metadata;
metadata.SetDouble(media::VideoFrameMetadata::FRAME_RATE, 30);
metadata.SetTimeTicks(media::VideoFrameMetadata::REFERENCE_TIME,
@@ -49,8 +52,7 @@ void FakeVideoCaptureHost::SendOneFrame(const gfx::Size& size,
observer_->OnBufferReady(
0, media::mojom::VideoFrameInfo::New(
base::TimeDelta(), metadata.GetInternalValues().Clone(),
- media::PIXEL_FORMAT_I420, media::VideoPixelStorage::CPU, size,
- gfx::Rect(size)));
+ media::PIXEL_FORMAT_I420, size, gfx::Rect(size)));
}
} // namespace mirroring
diff --git a/chromium/components/mirroring/service/interface.cc b/chromium/components/mirroring/service/interface.cc
new file mode 100644
index 00000000000..6781ef77eac
--- /dev/null
+++ b/chromium/components/mirroring/service/interface.cc
@@ -0,0 +1,15 @@
+// Copyright 2018 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 "components/mirroring/service/interface.h"
+
+namespace mirroring {
+
+CastSinkInfo::CastSinkInfo() {}
+
+CastSinkInfo::~CastSinkInfo() {}
+
+CastSinkInfo::CastSinkInfo(const CastSinkInfo& sink_info) = default;
+
+} // namespace mirroring
diff --git a/chromium/components/mirroring/service/interface.h b/chromium/components/mirroring/service/interface.h
index 501280efa0f..3feac5c3edb 100644
--- a/chromium/components/mirroring/service/interface.h
+++ b/chromium/components/mirroring/service/interface.h
@@ -5,13 +5,10 @@
#ifndef COMPONENTS_MIRRORING_SERVICE_INTERFACE_H_
#define COMPONENTS_MIRRORING_SERVICE_INTERFACE_H_
-#include <vector>
+#include <string>
-#include "base/callback.h"
-#include "base/values.h"
#include "media/capture/mojom/video_capture.mojom.h"
-#include "media/cast/cast_config.h"
-#include "net/base/ip_endpoint.h"
+#include "net/base/ip_address.h"
#include "services/network/public/mojom/network_service.mojom.h"
namespace mirroring {
@@ -21,22 +18,55 @@ namespace mirroring {
// Errors occurred in a mirroring session.
enum SessionError {
- SESSION_START_ERROR, // Error occurred while starting.
- AUDIO_CAPTURE_ERROR, // Error occurred in audio capturing.
- VIDEO_CAPTURE_ERROR, // Error occurred in video capturing.
- CAST_STREAMING_ERROR, // Error occurred in cast streaming.
- CAST_TRANSPORT_ERROR, // Error occurred in cast transport.
+ ANSWER_TIME_OUT, // ANSWER timeout.
+ ANSWER_NOT_OK, // Not OK answer response.
+ ANSWER_MISMATCHED_CAST_MODE, // ANSWER cast mode mismatched.
+ ANSWER_MISMATCHED_SSRC_LENGTH, // ANSWER ssrc length mismatched with indexes.
+ ANSWER_SELECT_MULTIPLE_AUDIO, // Multiple audio streams selected by ANSWER.
+ ANSWER_SELECT_MULTIPLE_VIDEO, // Multiple video streams selected by ANSWER.
+ ANSWER_SELECT_INVALID_INDEX, // Invalid index was selected.
+ ANSWER_NO_AUDIO_OR_VIDEO, // ANSWER not select audio or video.
+ AUDIO_CAPTURE_ERROR, // Error occurred in audio capturing.
+ VIDEO_CAPTURE_ERROR, // Error occurred in video capturing.
+ RTP_STREAM_ERROR, // Error reported by RtpStream.
+ ENCODING_ERROR, // Error occurred in encoding.
+ CAST_TRANSPORT_ERROR, // Error occurred in cast transport.
};
-enum SessionType {
+enum DeviceCapability {
AUDIO_ONLY,
VIDEO_ONLY,
AUDIO_AND_VIDEO,
};
-class SessionClient {
+constexpr char kRemotingNamespace[] = "urn:x-cast:com.google.cast.remoting";
+constexpr char kWebRtcNamespace[] = "urn:x-cast:com.google.cast.webrtc";
+
+struct CastMessage {
+ std::string message_namespace;
+ std::string json_format_data; // The content of the message.
+};
+
+class CastMessageChannel {
public:
- virtual ~SessionClient() {}
+ virtual ~CastMessageChannel() {}
+ virtual void Send(const CastMessage& message) = 0;
+};
+
+struct CastSinkInfo {
+ CastSinkInfo();
+ ~CastSinkInfo();
+ CastSinkInfo(const CastSinkInfo& sink_info);
+
+ net::IPAddress ip_address;
+ std::string model_name;
+ std::string friendly_name;
+ DeviceCapability capability;
+};
+
+class SessionObserver {
+ public:
+ virtual ~SessionObserver() {}
// Called when error occurred. The session will be stopped.
virtual void OnError(SessionError error) = 0;
@@ -46,25 +76,19 @@ class SessionClient {
// Called when the session is stopped.
virtual void DidStop() = 0;
+};
+
+class ResourceProvider {
+ public:
+ virtual ~ResourceProvider() {}
virtual void GetVideoCaptureHost(
media::mojom::VideoCaptureHostRequest request) = 0;
- virtual void GetNewWorkContext(
+ virtual void GetNetworkContext(
network::mojom::NetworkContextRequest request) = 0;
// TODO(xjz): Add interface to get AudioCaptureHost.
// TODO(xjz): Add interface for HW encoder profiles query and VEA create
// support.
-
- // TODO(xjz): Change this with an interface to send/receive messages to/from
- // receiver through cast channel, and generate/parse the OFFER/ANSWER message
- // in Mirroing service.
- using GetAnswerCallback = base::OnceCallback<void(
- const media::cast::FrameSenderConfig& audio_config,
- const media::cast::FrameSenderConfig& video_config)>;
- virtual void DoOfferAnswerExchange(
- const std::vector<media::cast::FrameSenderConfig>& audio_configs,
- const std::vector<media::cast::FrameSenderConfig>& video_configs,
- GetAnswerCallback callback) = 0;
};
} // namespace mirroring
diff --git a/chromium/components/mirroring/service/message_dispatcher.cc b/chromium/components/mirroring/service/message_dispatcher.cc
new file mode 100644
index 00000000000..aa5ad4a4069
--- /dev/null
+++ b/chromium/components/mirroring/service/message_dispatcher.cc
@@ -0,0 +1,157 @@
+// Copyright 2018 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 "components/mirroring/service/message_dispatcher.h"
+
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+
+namespace mirroring {
+
+// Holds a request until |timeout| elapses or an acceptable response is
+// received. When timeout, |response_callback| runs with an UNKNOWN type
+// response.
+class MessageDispatcher::RequestHolder {
+ public:
+ RequestHolder() {}
+
+ ~RequestHolder() {
+ if (!response_callback_.is_null())
+ std::move(response_callback_).Run(ReceiverResponse());
+ }
+
+ void Start(const base::TimeDelta& timeout,
+ int32_t sequence_number,
+ OnceResponseCallback response_callback) {
+ response_callback_ = std::move(response_callback);
+ sequence_number_ = sequence_number;
+ DCHECK(!response_callback_.is_null());
+ timer_.Start(
+ FROM_HERE, timeout,
+ base::BindRepeating(&RequestHolder::SendResponse,
+ base::Unretained(this), ReceiverResponse()));
+ }
+
+ // Send |response| if the sequence number matches, or if the request times
+ // out, in which case the |response| is UNKNOWN type.
+ void SendResponse(const ReceiverResponse& response) {
+ if (!timer_.IsRunning() || response.sequence_number == sequence_number_)
+ std::move(response_callback_).Run(response);
+ // Ignore the response with mismatched sequence number.
+ }
+
+ private:
+ OnceResponseCallback response_callback_;
+ base::OneShotTimer timer_;
+ int32_t sequence_number_ = -1;
+
+ DISALLOW_COPY_AND_ASSIGN(RequestHolder);
+};
+
+MessageDispatcher::MessageDispatcher(CastMessageChannel* outbound_channel,
+ ErrorCallback error_callback)
+ : outbound_channel_(outbound_channel),
+ error_callback_(std::move(error_callback)),
+ last_sequence_number_(base::RandInt(0, 1e9)) {
+ DCHECK(outbound_channel_);
+ DCHECK(!error_callback_.is_null());
+}
+
+MessageDispatcher::~MessageDispatcher() {
+ // Prevent the re-entrant operations on |callback_map_|.
+ decltype(callback_map_) subscriptions;
+ subscriptions.swap(callback_map_);
+ subscriptions.clear();
+}
+
+void MessageDispatcher::Send(const CastMessage& message) {
+ if (message.message_namespace != kWebRtcNamespace &&
+ message.message_namespace != kRemotingNamespace) {
+ DVLOG(2) << "Ignore message with unknown namespace = "
+ << message.message_namespace;
+ return; // Ignore message with wrong namespace.
+ }
+ if (message.json_format_data.empty())
+ return; // Ignore null message.
+
+ ReceiverResponse response;
+ if (!response.Parse(message.json_format_data)) {
+ error_callback_.Run("Response parsing error. message=" +
+ message.json_format_data);
+ return;
+ }
+
+#if DCHECK_IS_ON()
+ if (response.type == ResponseType::RPC)
+ DCHECK_EQ(kRemotingNamespace, message.message_namespace);
+ else
+ DCHECK_EQ(kWebRtcNamespace, message.message_namespace);
+#endif // DCHECK_IS_ON()
+
+ const auto callback_iter = callback_map_.find(response.type);
+ if (callback_iter == callback_map_.end()) {
+ error_callback_.Run("No callback subscribed. message=" +
+ message.json_format_data);
+ return;
+ }
+ callback_iter->second.Run(response);
+}
+
+void MessageDispatcher::Subscribe(ResponseType type,
+ ResponseCallback callback) {
+ DCHECK(type != ResponseType::UNKNOWN);
+ DCHECK(!callback.is_null());
+
+ const auto insert_result =
+ callback_map_.emplace(std::make_pair(type, std::move(callback)));
+ DCHECK(insert_result.second);
+}
+
+void MessageDispatcher::Unsubscribe(ResponseType type) {
+ auto iter = callback_map_.find(type);
+ if (iter != callback_map_.end())
+ callback_map_.erase(iter);
+}
+
+int32_t MessageDispatcher::GetNextSeqNumber() {
+ // Skip 0, which is used by Cast receiver to indicate that the broadcast
+ // status message is not coming from a specific sender (it is an autonomous
+ // status change, not triggered by a command from any sender). Strange usage
+ // of 0 though; could be a null / optional field.
+ return ++last_sequence_number_;
+}
+
+void MessageDispatcher::SendOutboundMessage(const CastMessage& message) {
+ outbound_channel_->Send(message);
+}
+
+void MessageDispatcher::RequestReply(const CastMessage& message,
+ ResponseType response_type,
+ int32_t sequence_number,
+ const base::TimeDelta& timeout,
+ OnceResponseCallback callback) {
+ DCHECK(!callback.is_null());
+ DCHECK(timeout > base::TimeDelta());
+ RequestHolder* const request_holder = new RequestHolder();
+ request_holder->Start(
+ timeout, sequence_number,
+ base::BindOnce(
+ [](MessageDispatcher* dispatcher, ResponseType response_type,
+ OnceResponseCallback callback, const ReceiverResponse& response) {
+ dispatcher->Unsubscribe(response_type);
+ std::move(callback).Run(response);
+ },
+ this, response_type, std::move(callback)));
+ // |request_holder| keeps alive until the callback is unsubscribed.
+ Subscribe(response_type, base::BindRepeating(
+ [](RequestHolder* request_holder,
+ const ReceiverResponse& response) {
+ request_holder->SendResponse(response);
+ },
+ base::Owned(request_holder)));
+ SendOutboundMessage(message);
+}
+
+} // namespace mirroring
diff --git a/chromium/components/mirroring/service/message_dispatcher.h b/chromium/components/mirroring/service/message_dispatcher.h
new file mode 100644
index 00000000000..7b2ee9bfb62
--- /dev/null
+++ b/chromium/components/mirroring/service/message_dispatcher.h
@@ -0,0 +1,73 @@
+// Copyright 2018 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.
+
+#ifndef COMPONENTS_MIRRORING_SERVICE_MESSAGE_DISPATCHER_H_
+#define COMPONENTS_MIRRORING_SERVICE_MESSAGE_DISPATCHER_H_
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "components/mirroring/service/interface.h"
+#include "components/mirroring/service/receiver_response.h"
+
+namespace mirroring {
+
+// Dispatches inbound/outbound messages. The outbound messages are sent out
+// through |outbound_channel|, and the inbound messages are handled by this
+// class.
+class MessageDispatcher final : public CastMessageChannel {
+ public:
+ using ErrorCallback = base::RepeatingCallback<void(const std::string&)>;
+ // TODO(xjz): Also pass a CastMessageChannel interface request for inbound
+ // message channel.
+ MessageDispatcher(CastMessageChannel* outbound_channel,
+ ErrorCallback error_callback);
+ ~MessageDispatcher() override;
+
+ using ResponseCallback =
+ base::RepeatingCallback<void(const ReceiverResponse& response)>;
+ // Registers/Unregisters callback for a certain type of responses.
+ void Subscribe(ResponseType type, ResponseCallback callback);
+ void Unsubscribe(ResponseType type);
+
+ using OnceResponseCallback =
+ base::OnceCallback<void(const ReceiverResponse& response)>;
+ // Sends the given message and subscribes to replies until an acceptable one
+ // is received or a timeout elapses. Message of the given response type is
+ // delivered to the supplied callback if the sequence number of the response
+ // matches |sequence_number|. If the timeout period elapses, the callback will
+ // be run once with an unknown type of |response|.
+ void RequestReply(const CastMessage& message,
+ ResponseType response_type,
+ int32_t sequence_number,
+ const base::TimeDelta& timeout,
+ OnceResponseCallback callback);
+
+ // Get the sequence number for the next outbound message. Never returns 0.
+ int32_t GetNextSeqNumber();
+
+ // Requests to send outbound |message|.
+ void SendOutboundMessage(const CastMessage& message);
+
+ private:
+ class RequestHolder;
+
+ // CastMessageChannel implementation. Handles inbound messages.
+ void Send(const CastMessage& message) override;
+
+ // Takes care of sending outbound messages.
+ CastMessageChannel* const outbound_channel_;
+ const ErrorCallback error_callback_;
+
+ int32_t last_sequence_number_;
+
+ // Holds callbacks for different types of responses.
+ base::flat_map<ResponseType, ResponseCallback> callback_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageDispatcher);
+};
+
+} // namespace mirroring
+
+#endif // COMPONENTS_MIRRORING_SERVICE_MESSAGE_DISPATCHER_H_
diff --git a/chromium/components/mirroring/service/message_dispatcher_unittest.cc b/chromium/components/mirroring/service/message_dispatcher_unittest.cc
new file mode 100644
index 00000000000..32cb7328ecc
--- /dev/null
+++ b/chromium/components/mirroring/service/message_dispatcher_unittest.cc
@@ -0,0 +1,299 @@
+// Copyright 2018 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 "components/mirroring/service/message_dispatcher.h"
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/mock_callback.h"
+#include "base/test/scoped_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::InvokeWithoutArgs;
+using ::testing::_;
+
+namespace mirroring {
+
+namespace {
+
+bool IsEqual(const CastMessage& message1, const CastMessage& message2) {
+ return message1.message_namespace == message2.message_namespace &&
+ message1.json_format_data == message2.json_format_data;
+}
+
+void CloneResponse(const ReceiverResponse& response,
+ ReceiverResponse* cloned_response) {
+ cloned_response->type = response.type;
+ cloned_response->session_id = response.session_id;
+ cloned_response->sequence_number = response.sequence_number;
+ cloned_response->result = response.result;
+ if (response.answer)
+ cloned_response->answer = std::make_unique<Answer>(*response.answer);
+ if (response.status)
+ cloned_response->status =
+ std::make_unique<ReceiverStatus>(*response.status);
+ if (response.capabilities) {
+ cloned_response->capabilities =
+ std::make_unique<ReceiverCapability>(*response.capabilities);
+ }
+ cloned_response->rpc = response.rpc;
+ if (response.error) {
+ cloned_response->error = std::make_unique<ReceiverError>();
+ cloned_response->error->code = response.error->code;
+ cloned_response->error->description = response.error->description;
+ cloned_response->error->details = response.error->details;
+ }
+}
+
+} // namespace
+
+class MessageDispatcherTest : public CastMessageChannel,
+ public ::testing::Test {
+ public:
+ MessageDispatcherTest() {
+ message_dispatcher_ = std::make_unique<MessageDispatcher>(
+ this, base::BindRepeating(&MessageDispatcherTest::OnParsingError,
+ base::Unretained(this)));
+ message_dispatcher_->Subscribe(
+ ResponseType::ANSWER,
+ base::BindRepeating(&MessageDispatcherTest::OnAnswerResponse,
+ base::Unretained(this)));
+ message_dispatcher_->Subscribe(
+ ResponseType::STATUS_RESPONSE,
+ base::BindRepeating(&MessageDispatcherTest::OnStatusResponse,
+ base::Unretained(this)));
+ }
+ ~MessageDispatcherTest() override { scoped_task_environment_.RunUntilIdle(); }
+
+ void OnParsingError(const std::string& error_message) {
+ last_error_message_ = error_message;
+ }
+
+ void OnAnswerResponse(const ReceiverResponse& response) {
+ if (!last_answer_response_)
+ last_answer_response_ = std::make_unique<ReceiverResponse>();
+ CloneResponse(response, last_answer_response_.get());
+ }
+
+ void OnStatusResponse(const ReceiverResponse& response) {
+ if (!last_status_response_)
+ last_status_response_ = std::make_unique<ReceiverResponse>();
+ CloneResponse(response, last_status_response_.get());
+ }
+
+ protected:
+ // CastMessageChannel implementation. Handles outbound message.
+ void Send(const CastMessage& message) override {
+ last_outbound_message_.message_namespace = message.message_namespace;
+ last_outbound_message_.json_format_data = message.json_format_data;
+ }
+
+ // Simulates receiving an inbound message from receiver.
+ void SendInboundMessage(const CastMessage& message) {
+ CastMessageChannel* inbound_message_channel = message_dispatcher_.get();
+ inbound_message_channel->Send(message);
+ }
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ std::unique_ptr<MessageDispatcher> message_dispatcher_;
+ CastMessage last_outbound_message_;
+ std::string last_error_message_;
+ std::unique_ptr<ReceiverResponse> last_answer_response_;
+ std::unique_ptr<ReceiverResponse> last_status_response_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MessageDispatcherTest);
+};
+
+TEST_F(MessageDispatcherTest, SendsOutboundMessage) {
+ const std::string test1 = "{\"a\": 1, \"b\": 2}";
+ const CastMessage message1 = CastMessage{kWebRtcNamespace, test1};
+ message_dispatcher_->SendOutboundMessage(message1);
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_TRUE(IsEqual(message1, last_outbound_message_));
+ EXPECT_TRUE(last_error_message_.empty());
+
+ const std::string test2 = "{\"m\": 99, \"i\": 98, \"u\": 97}";
+ const CastMessage message2 = CastMessage{kWebRtcNamespace, test2};
+ message_dispatcher_->SendOutboundMessage(message2);
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_TRUE(IsEqual(message2, last_outbound_message_));
+ EXPECT_TRUE(last_error_message_.empty());
+}
+
+TEST_F(MessageDispatcherTest, DispatchMessageToSubscriber) {
+ // Simulate a receiver ANSWER response and expect that just the ANSWER
+ // subscriber processes the message.
+ const std::string answer_response =
+ "{\"type\":\"ANSWER\",\"seqNum\":12345,\"result\":\"ok\","
+ "\"answer\":{\"udpPort\":50691}}";
+ const CastMessage answer_message =
+ CastMessage{kWebRtcNamespace, answer_response};
+ SendInboundMessage(answer_message);
+ scoped_task_environment_.RunUntilIdle();
+ ASSERT_TRUE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+ EXPECT_EQ(12345, last_answer_response_->sequence_number);
+ EXPECT_EQ(ResponseType::ANSWER, last_answer_response_->type);
+ EXPECT_EQ("ok", last_answer_response_->result);
+ EXPECT_EQ(50691, last_answer_response_->answer->udp_port);
+ EXPECT_FALSE(last_answer_response_->status);
+ last_answer_response_.reset();
+ EXPECT_TRUE(last_error_message_.empty());
+
+ // Simulate a receiver STATUS_RESPONSE and expect that just the
+ // STATUS_RESPONSE subscriber processes the message.
+ const std::string status_response =
+ "{\"type\":\"STATUS_RESPONSE\",\"seqNum\":12345,\"result\":\"ok\","
+ "\"status\":{\"wifiSnr\":42}}";
+ const CastMessage status_message =
+ CastMessage{kWebRtcNamespace, status_response};
+ SendInboundMessage(status_message);
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_FALSE(last_answer_response_);
+ ASSERT_TRUE(last_status_response_);
+ EXPECT_EQ(12345, last_status_response_->sequence_number);
+ EXPECT_EQ(ResponseType::STATUS_RESPONSE, last_status_response_->type);
+ EXPECT_EQ("ok", last_status_response_->result);
+ EXPECT_EQ(42, last_status_response_->status->wifi_snr);
+ last_status_response_.reset();
+ EXPECT_TRUE(last_error_message_.empty());
+
+ // Unsubscribe from ANSWER messages, and when feeding-in an ANSWER message,
+ // nothing should happen.
+ message_dispatcher_->Unsubscribe(ResponseType::ANSWER);
+ SendInboundMessage(answer_message);
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_FALSE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+ EXPECT_FALSE(last_error_message_.empty()); // Expect an error reported.
+ last_error_message_.clear();
+
+ // However, STATUS_RESPONSE messages should still be dispatcher to the
+ // remaining subscriber.
+ SendInboundMessage(status_message);
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_FALSE(last_answer_response_);
+ EXPECT_TRUE(last_status_response_);
+ last_status_response_.reset();
+ EXPECT_TRUE(last_error_message_.empty());
+
+ // Finally, unsubscribe from STATUS_RESPONSE messages, and when feeding-in
+ // either an ANSWER or a STATUS_RESPONSE message, nothing should happen.
+ message_dispatcher_->Unsubscribe(ResponseType::STATUS_RESPONSE);
+ SendInboundMessage(answer_message);
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_FALSE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+ EXPECT_FALSE(last_error_message_.empty());
+ last_error_message_.clear();
+ SendInboundMessage(status_message);
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_FALSE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+ EXPECT_FALSE(last_error_message_.empty());
+}
+
+TEST_F(MessageDispatcherTest, IgnoreMalformedMessage) {
+ const CastMessage message =
+ CastMessage{kWebRtcNamespace, "MUAHAHAHAHAHAHAHA!"};
+ SendInboundMessage(message);
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_FALSE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+ EXPECT_FALSE(last_error_message_.empty());
+}
+
+TEST_F(MessageDispatcherTest, IgnoreMessageWithWrongNamespace) {
+ const std::string answer_response =
+ "{\"type\":\"ANSWER\",\"seqNum\":12345,\"result\":\"ok\","
+ "\"answer\":{\"udpPort\":50691}}";
+ const CastMessage answer_message =
+ CastMessage{"Wrong_namespace", answer_response};
+ SendInboundMessage(answer_message);
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_FALSE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+ // Messages with different namespace are ignored with no error reported.
+ EXPECT_TRUE(last_error_message_.empty());
+}
+
+TEST_F(MessageDispatcherTest, RequestReply) {
+ EXPECT_FALSE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+ message_dispatcher_->Unsubscribe(ResponseType::ANSWER);
+ scoped_task_environment_.RunUntilIdle();
+ const std::string fake_offer = "{\"type\":\"OFFER\",\"seqNum\":45623}";
+ const CastMessage offer_message = CastMessage{kWebRtcNamespace, fake_offer};
+ message_dispatcher_->RequestReply(
+ offer_message, ResponseType::ANSWER, 45623,
+ base::TimeDelta::FromMilliseconds(100),
+ base::BindRepeating(&MessageDispatcherTest::OnAnswerResponse,
+ base::Unretained(this)));
+ scoped_task_environment_.RunUntilIdle();
+ // Received the request to send the outbound message.
+ EXPECT_TRUE(IsEqual(offer_message, last_outbound_message_));
+
+ std::string answer_response =
+ "{\"type\":\"ANSWER\",\"seqNum\":12345,\"result\":\"ok\","
+ "\"answer\":{\"udpPort\":50691}}";
+ CastMessage answer_message = CastMessage{kWebRtcNamespace, answer_response};
+ SendInboundMessage(answer_message);
+ scoped_task_environment_.RunUntilIdle();
+ // The answer message with mismatched sequence number is ignored.
+ EXPECT_FALSE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+ EXPECT_TRUE(last_error_message_.empty());
+
+ answer_response =
+ "{\"type\":\"ANSWER\",\"seqNum\":45623,\"result\":\"ok\","
+ "\"answer\":{\"udpPort\":50691}}";
+ answer_message = CastMessage{kWebRtcNamespace, answer_response};
+ SendInboundMessage(answer_message);
+ scoped_task_environment_.RunUntilIdle();
+ ASSERT_TRUE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+ EXPECT_TRUE(last_error_message_.empty());
+ EXPECT_EQ(45623, last_answer_response_->sequence_number);
+ EXPECT_EQ(ResponseType::ANSWER, last_answer_response_->type);
+ EXPECT_EQ("ok", last_answer_response_->result);
+ EXPECT_EQ(50691, last_answer_response_->answer->udp_port);
+ last_answer_response_.reset();
+
+ // Expect that the callback for ANSWER message was already unsubscribed.
+ SendInboundMessage(answer_message);
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_FALSE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+ EXPECT_FALSE(last_error_message_.empty());
+ last_error_message_.clear();
+
+ const CastMessage fake_message =
+ CastMessage{kWebRtcNamespace, "{\"type\":\"OFFER\",\"seqNum\":12345}"};
+ message_dispatcher_->RequestReply(
+ fake_message, ResponseType::ANSWER, 12345,
+ base::TimeDelta::FromMilliseconds(100),
+ base::BindRepeating(&MessageDispatcherTest::OnAnswerResponse,
+ base::Unretained(this)));
+ scoped_task_environment_.RunUntilIdle();
+ // Received the request to send the outbound message.
+ EXPECT_TRUE(IsEqual(fake_message, last_outbound_message_));
+ EXPECT_FALSE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+
+ // Destroy the dispatcher. Expect to receive an unknown type response.
+ message_dispatcher_.reset();
+ scoped_task_environment_.RunUntilIdle();
+ ASSERT_TRUE(last_answer_response_);
+ EXPECT_FALSE(last_status_response_);
+ EXPECT_TRUE(last_error_message_.empty());
+ EXPECT_EQ(ResponseType::UNKNOWN, last_answer_response_->type);
+ EXPECT_EQ(-1, last_answer_response_->sequence_number);
+}
+
+} // namespace mirroring
diff --git a/chromium/components/mirroring/service/mirror_settings.cc b/chromium/components/mirroring/service/mirror_settings.cc
new file mode 100644
index 00000000000..a14799757a4
--- /dev/null
+++ b/chromium/components/mirroring/service/mirror_settings.cc
@@ -0,0 +1,148 @@
+// Copyright 2018 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 "components/mirroring/service/mirror_settings.h"
+
+#include <algorithm>
+
+using media::cast::FrameSenderConfig;
+using media::cast::Codec;
+using media::cast::RtpPayloadType;
+using media::ResolutionChangePolicy;
+
+namespace mirroring {
+
+namespace {
+
+// Starting end-to-end latency for animated content.
+constexpr base::TimeDelta kAnimatedPlayoutDelay =
+ base::TimeDelta::FromMilliseconds(400);
+
+// Minimum end-to-end latency. This allows cast streaming to adaptively lower
+// latency in interactive streaming scenarios.
+// TODO(miu): This was 120 before stable launch, but we got user feedback that
+// this was causing audio drop-outs. So, we need to fix the Cast Streaming
+// implementation before lowering this setting.
+constexpr base::TimeDelta kMinPlayoutDelay =
+ base::TimeDelta::FromMilliseconds(400);
+
+// Maximum end-to-end latency.
+constexpr base::TimeDelta kMaxPlayoutDelay =
+ base::TimeDelta::FromMilliseconds(800);
+
+constexpr int kAudioTimebase = 48000;
+constexpr int kVidoTimebase = 90000;
+constexpr int kAudioChannels = 2;
+constexpr int kAudioFramerate = 100; // 100 FPS for 10ms packets.
+constexpr int kMinVideoBitrate = 300000;
+constexpr int kMaxVideoBitrate = 5000000;
+constexpr int kAudioBitrate = 0; // 0 means automatic.
+constexpr int kMaxFrameRate = 30; // The maximum frame rate for captures.
+constexpr int kMaxWidth = 1920; // Maximum video width in pixels.
+constexpr int kMaxHeight = 1080; // Maximum video height in pixels.
+constexpr int kMinWidth = 180; // Minimum video frame width in pixels.
+constexpr int kMinHeight = 180; // Minimum video frame height in pixels.
+
+} // namespace
+
+MirrorSettings::MirrorSettings()
+ : min_width_(kMinWidth),
+ min_height_(kMinHeight),
+ max_width_(kMaxWidth),
+ max_height_(kMaxHeight) {}
+
+MirrorSettings::~MirrorSettings() {}
+
+// static
+FrameSenderConfig MirrorSettings::GetDefaultAudioConfig(
+ RtpPayloadType payload_type,
+ Codec codec) {
+ FrameSenderConfig config;
+ config.sender_ssrc = 1;
+ config.receiver_ssrc = 2;
+ config.min_playout_delay = kMinPlayoutDelay;
+ config.max_playout_delay = kMaxPlayoutDelay;
+ config.animated_playout_delay = kAnimatedPlayoutDelay;
+ config.rtp_payload_type = payload_type;
+ config.rtp_timebase = kAudioTimebase;
+ config.channels = kAudioChannels;
+ config.min_bitrate = config.max_bitrate = config.start_bitrate =
+ kAudioBitrate;
+ config.max_frame_rate = kAudioFramerate; // 10 ms audio frames
+ config.codec = codec;
+ return config;
+}
+
+// static
+FrameSenderConfig MirrorSettings::GetDefaultVideoConfig(
+ RtpPayloadType payload_type,
+ Codec codec) {
+ FrameSenderConfig config;
+ config.sender_ssrc = 11;
+ config.receiver_ssrc = 12;
+ config.min_playout_delay = kMinPlayoutDelay;
+ config.max_playout_delay = kMaxPlayoutDelay;
+ config.animated_playout_delay = kAnimatedPlayoutDelay;
+ config.rtp_payload_type = payload_type;
+ config.rtp_timebase = kVidoTimebase;
+ config.channels = 1;
+ config.min_bitrate = kMinVideoBitrate;
+ config.max_bitrate = kMaxVideoBitrate;
+ config.start_bitrate = kMinVideoBitrate;
+ config.max_frame_rate = kMaxFrameRate;
+ config.codec = codec;
+ return config;
+}
+
+void MirrorSettings::SetResolutionContraints(int max_width, int max_height) {
+ max_width_ = std::max(max_width, min_width_);
+ max_height_ = std::max(max_height, min_height_);
+}
+
+media::VideoCaptureParams MirrorSettings::GetVideoCaptureParams() {
+ media::VideoCaptureParams params;
+ params.requested_format =
+ media::VideoCaptureFormat(gfx::Size(max_width_, max_height_),
+ kMaxFrameRate, media::PIXEL_FORMAT_I420);
+ if (max_height_ == min_height_ && max_width_ == min_width_) {
+ params.resolution_change_policy = ResolutionChangePolicy::FIXED_RESOLUTION;
+ } else if ((100 * min_width_ / min_height_) ==
+ (100 * max_width_ / max_height_)) {
+ params.resolution_change_policy =
+ ResolutionChangePolicy::FIXED_ASPECT_RATIO;
+ } else {
+ params.resolution_change_policy = ResolutionChangePolicy::ANY_WITHIN_LIMIT;
+ }
+ DCHECK(params.IsValid());
+ return params;
+}
+
+base::Value MirrorSettings::ToDictionaryValue() {
+ base::Value settings(base::Value::Type::DICTIONARY);
+ settings.SetKey("maxWidth", base::Value(max_width_));
+ settings.SetKey("maxHeight", base::Value(max_height_));
+ settings.SetKey("minWidth", base::Value(min_width_));
+ settings.SetKey("minHeight", base::Value(min_height_));
+ settings.SetKey("senderSideLetterboxing", base::Value(true));
+ settings.SetKey("minFrameRate", base::Value(0));
+ settings.SetKey("maxFrameRate", base::Value(kMaxFrameRate));
+ settings.SetKey("minVideoBitrate", base::Value(kMinVideoBitrate));
+ settings.SetKey("maxVideoBitrate", base::Value(kMaxVideoBitrate));
+ settings.SetKey("audioBitrate", base::Value(kAudioBitrate));
+ settings.SetKey(
+ "maxLatencyMillis",
+ base::Value(static_cast<int32_t>(kMaxPlayoutDelay.InMilliseconds())));
+ settings.SetKey(
+ "minLatencyMillis",
+ base::Value(static_cast<int32_t>(kMinPlayoutDelay.InMilliseconds())));
+ settings.SetKey("animatedLatencyMillis",
+ base::Value(static_cast<int32_t>(
+ kAnimatedPlayoutDelay.InMilliseconds())));
+ settings.SetKey("dscpEnabled", base::Value(false));
+ settings.SetKey("enableLogging", base::Value(true));
+ settings.SetKey("useTdls", base::Value(false));
+ return settings;
+}
+
+} // namespace mirroring
diff --git a/chromium/components/mirroring/service/mirror_settings.h b/chromium/components/mirroring/service/mirror_settings.h
new file mode 100644
index 00000000000..937602d3e36
--- /dev/null
+++ b/chromium/components/mirroring/service/mirror_settings.h
@@ -0,0 +1,57 @@
+// Copyright 2018 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.
+
+#ifndef COMPONENTS_MIRRORING_SERVICE_MIRROR_SETTINGS_H_
+#define COMPONENTS_MIRRORING_SERVICE_MIRROR_SETTINGS_H_
+
+#include "base/time/time.h"
+#include "base/values.h"
+#include "media/capture/video_capture_types.h"
+#include "media/cast/cast_config.h"
+
+namespace mirroring {
+
+// Holds the default settings for a mirroring session. This class provides the
+// audio/video configs that this sender supports. And also provides the
+// audio/video constraints used for capturing.
+// TODO(xjz): Add the function to generate the audio capture contraints.
+// TODO(xjz): Add setters to the settings that might be overriden by integration
+// tests.
+class MirrorSettings {
+ public:
+ MirrorSettings();
+ ~MirrorSettings();
+
+ // Get the audio/video config with given codec.
+ static media::cast::FrameSenderConfig GetDefaultAudioConfig(
+ media::cast::RtpPayloadType payload_type,
+ media::cast::Codec codec);
+ static media::cast::FrameSenderConfig GetDefaultVideoConfig(
+ media::cast::RtpPayloadType payload_type,
+ media::cast::Codec codec);
+
+ // Call to override the default resolution settings.
+ void SetResolutionContraints(int max_width, int max_height);
+
+ // Get video capture constraints with the current settings.
+ media::VideoCaptureParams GetVideoCaptureParams();
+
+ int max_width() const { return max_width_; }
+ int max_height() const { return max_height_; }
+
+ // Returns a dictionary value of the current settings.
+ base::Value ToDictionaryValue();
+
+ private:
+ const int min_width_;
+ const int min_height_;
+ int max_width_;
+ int max_height_;
+
+ DISALLOW_COPY_AND_ASSIGN(MirrorSettings);
+};
+
+} // namespace mirroring
+
+#endif // COMPONENTS_MIRRORING_SERVICE_MIRROR_SETTINGS_H_
diff --git a/chromium/components/mirroring/service/receiver_response.cc b/chromium/components/mirroring/service/receiver_response.cc
new file mode 100644
index 00000000000..6feff42172c
--- /dev/null
+++ b/chromium/components/mirroring/service/receiver_response.cc
@@ -0,0 +1,199 @@
+// Copyright 2018 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 "components/mirroring/service/receiver_response.h"
+
+#include "base/base64.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "components/mirroring/service/value_util.h"
+
+namespace mirroring {
+
+namespace {
+
+// Get the response type from the type string value in the JSON message.
+ResponseType GetResponseType(const std::string& type) {
+ if (type == "ANSWER")
+ return ResponseType::ANSWER;
+ if (type == "STATUS_RESPONSE")
+ return ResponseType::STATUS_RESPONSE;
+ if (type == "CAPABILITIES_RESPONSE")
+ return ResponseType::CAPABILITIES_RESPONSE;
+ if (type == "RPC")
+ return ResponseType::RPC;
+ return ResponseType::UNKNOWN;
+}
+
+} // namespace
+
+Answer::Answer()
+ : udp_port(-1), supports_get_status(false), cast_mode("mirroring") {}
+
+Answer::~Answer() {}
+
+Answer::Answer(const Answer& answer) = default;
+
+bool Answer::Parse(const base::Value& raw_value) {
+ return (raw_value.is_dict() && GetInt(raw_value, "udpPort", &udp_port) &&
+ GetIntArray(raw_value, "ssrcs", &ssrcs) &&
+ GetIntArray(raw_value, "sendIndexes", &send_indexes) &&
+ GetString(raw_value, "IV", &iv) &&
+ GetBool(raw_value, "receiverGetStatus", &supports_get_status) &&
+ GetString(raw_value, "castMode", &cast_mode));
+}
+
+// ----------------------------------------------------------------------------
+
+ReceiverStatus::ReceiverStatus() : wifi_snr(0) {}
+
+ReceiverStatus::~ReceiverStatus() {}
+
+ReceiverStatus::ReceiverStatus(const ReceiverStatus& status) = default;
+
+bool ReceiverStatus::Parse(const base::Value& raw_value) {
+ return (raw_value.is_dict() && GetDouble(raw_value, "wifiSnr", &wifi_snr) &&
+ GetIntArray(raw_value, "wifiSpeed", &wifi_speed));
+}
+
+// ----------------------------------------------------------------------------
+
+ReceiverKeySystem::ReceiverKeySystem() {}
+
+ReceiverKeySystem::~ReceiverKeySystem() {}
+
+ReceiverKeySystem::ReceiverKeySystem(
+ const ReceiverKeySystem& receiver_key_system) = default;
+
+bool ReceiverKeySystem::Parse(const base::Value& raw_value) {
+ return (raw_value.is_dict() && GetString(raw_value, "keySystemName", &name) &&
+ GetStringArray(raw_value, "initDataTypes", &init_data_types) &&
+ GetStringArray(raw_value, "codecs", &codecs) &&
+ GetStringArray(raw_value, "secureCodecs", &secure_codecs) &&
+ GetStringArray(raw_value, "audioRobustness", &audio_robustness) &&
+ GetStringArray(raw_value, "videoRobustness", &video_robustness) &&
+ GetString(raw_value, "persistentLicenseSessionSupport",
+ &persistent_license_session_support) &&
+ GetString(raw_value, "persistentReleaseMessageSessionSupport",
+ &persistent_release_message_session_support) &&
+ GetString(raw_value, "persistentStateSupport",
+ &persistent_state_support) &&
+ GetString(raw_value, "distinctiveIdentifierSupport",
+ &distinctive_identifier_support));
+}
+
+// ----------------------------------------------------------------------------
+
+ReceiverCapability::ReceiverCapability() {}
+
+ReceiverCapability::~ReceiverCapability() {}
+
+ReceiverCapability::ReceiverCapability(const ReceiverCapability& capabilities) =
+ default;
+
+bool ReceiverCapability::Parse(const base::Value& raw_value) {
+ if (!raw_value.is_dict() ||
+ !GetStringArray(raw_value, "mediaCaps", &media_caps))
+ return false;
+ auto* found = raw_value.FindKey("keySystems");
+ if (!found)
+ return true;
+ for (const auto& key_system_value : found->GetList()) {
+ ReceiverKeySystem key_system;
+ if (!key_system.Parse(key_system_value))
+ return false;
+ key_systems.emplace_back(key_system);
+ }
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+
+ReceiverError::ReceiverError() : code(-1) {}
+
+ReceiverError::~ReceiverError() {}
+
+bool ReceiverError::Parse(const base::Value& raw_value) {
+ if (!raw_value.is_dict() || !GetInt(raw_value, "code", &code) ||
+ !GetString(raw_value, "description", &description))
+ return false;
+ auto* found = raw_value.FindKey("details");
+ return found && base::JSONWriter::Write(*found, &details);
+}
+
+// ----------------------------------------------------------------------------
+
+ReceiverResponse::ReceiverResponse()
+ : type(ResponseType::UNKNOWN), session_id(-1), sequence_number(-1) {}
+
+ReceiverResponse::~ReceiverResponse() {}
+
+ReceiverResponse::ReceiverResponse(ReceiverResponse&& receiver_response) =
+ default;
+
+ReceiverResponse& ReceiverResponse::operator=(
+ ReceiverResponse&& receiver_response) = default;
+
+bool ReceiverResponse::Parse(const std::string& message_data) {
+ std::unique_ptr<base::Value> raw_value = base::JSONReader::Read(message_data);
+ if (!raw_value || !raw_value->is_dict() ||
+ !GetInt(*raw_value, "sessionId", &session_id) ||
+ !GetInt(*raw_value, "seqNum", &sequence_number) ||
+ !GetString(*raw_value, "result", &result))
+ return false;
+
+ if (result == "error") {
+ auto* found = raw_value->FindKey("error");
+ if (found) {
+ error = std::make_unique<ReceiverError>();
+ if (!error->Parse(*found))
+ return false;
+ }
+ }
+
+ std::string message_type;
+ if (!GetString(*raw_value, "type", &message_type))
+ return false;
+ // Convert |message_type| to uppercase.
+ message_type = base::ToUpperASCII(message_type);
+ type = GetResponseType(message_type);
+ if (type == ResponseType::UNKNOWN) {
+ DVLOG(2) << "Unknown response message type= " << message_type;
+ return false;
+ }
+
+ auto* found = raw_value->FindKey("answer");
+ if (found && !found->is_none()) {
+ answer = std::make_unique<Answer>();
+ if (!answer->Parse(*found))
+ return false;
+ }
+
+ found = raw_value->FindKey("status");
+ if (found && !found->is_none()) {
+ status = std::make_unique<ReceiverStatus>();
+ if (!status->Parse(*found))
+ return false;
+ }
+
+ found = raw_value->FindKey("capabilities");
+ if (found && !found->is_none()) {
+ capabilities = std::make_unique<ReceiverCapability>();
+ if (!capabilities->Parse(*found))
+ return false;
+ }
+
+ found = raw_value->FindKey("rpc");
+ if (found && !found->is_none()) {
+ // Decode the base64-encoded string.
+ if (!found->is_string() || !base::Base64Decode(found->GetString(), &rpc))
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace mirroring
diff --git a/chromium/components/mirroring/service/receiver_response.h b/chromium/components/mirroring/service/receiver_response.h
new file mode 100644
index 00000000000..0429e2fc6e9
--- /dev/null
+++ b/chromium/components/mirroring/service/receiver_response.h
@@ -0,0 +1,134 @@
+// Copyright 2018 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.
+
+#ifndef COMPONENTS_MIRRORING_SERVICE_RECEIVER_RESPONSE_H_
+#define COMPONENTS_MIRRORING_SERVICE_RECEIVER_RESPONSE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/values.h"
+
+namespace mirroring {
+
+// Receiver response message type.
+enum ResponseType {
+ UNKNOWN,
+ ANSWER, // Response to OFFER message.
+ STATUS_RESPONSE, // Response to GET_STATUS message.
+ CAPABILITIES_RESPONSE, // Response to GET_CAPABILITIES message.
+ RPC, // Rpc binary messages. The payload is base64 encoded.
+};
+
+struct Answer {
+ Answer();
+ ~Answer();
+ Answer(const Answer& answer);
+ bool Parse(const base::Value& raw_value);
+
+ // The UDP port used for all streams in this session.
+ int32_t udp_port;
+ // The indexes chosen from the OFFER message.
+ std::vector<int32_t> send_indexes;
+ // The RTP SSRC used to send the RTCP feedback of the stream, indicated by
+ // the |send_indexes| above.
+ std::vector<int32_t> ssrcs;
+ // A 128bit hex number containing the initialization vector for the crypto.
+ std::string iv;
+ // Indicates whether receiver supports the GET_STATUS command.
+ bool supports_get_status;
+ // "mirroring" for screen mirroring, or "remoting" for media remoting.
+ std::string cast_mode;
+};
+
+struct ReceiverStatus {
+ ReceiverStatus();
+ ~ReceiverStatus();
+ ReceiverStatus(const ReceiverStatus& status);
+ bool Parse(const base::Value& raw_value);
+
+ // Current WiFi signal to noise ratio in decibels.
+ double wifi_snr;
+ // Min, max, average, and current bandwidth in bps in order of the WiFi link.
+ // Example: [1200, 1300, 1250, 1230].
+ std::vector<int32_t> wifi_speed;
+};
+
+struct ReceiverKeySystem {
+ ReceiverKeySystem();
+ ~ReceiverKeySystem();
+ ReceiverKeySystem(const ReceiverKeySystem& receiver_key_system);
+ bool Parse(const base::Value& raw_value);
+
+ // Reverse URI (e.g. com.widevine.alpha).
+ std::string name;
+ // EME init data types (e.g. cenc).
+ std::vector<std::string> init_data_types;
+ // Codecs supported by key system. This will include AVC and VP8 on all
+ // Chromecasts.
+ std::vector<std::string> codecs;
+ // Codecs that are also hardware-secure.
+ std::vector<std::string> secure_codecs;
+ // Support levels for audio encryption robustness.
+ std::vector<std::string> audio_robustness;
+ // Support levels for video encryption robustness.
+ std::vector<std::string> video_robustness;
+
+ std::string persistent_license_session_support;
+ std::string persistent_release_message_session_support;
+ std::string persistent_state_support;
+ std::string distinctive_identifier_support;
+};
+
+struct ReceiverCapability {
+ ReceiverCapability();
+ ~ReceiverCapability();
+ ReceiverCapability(const ReceiverCapability& capabilities);
+ bool Parse(const base::Value& raw_value);
+
+ // Set of capabilities (e.g., ac3, 4k, hevc, vp9, dolby_vision, etc.).
+ std::vector<std::string> media_caps;
+ std::vector<ReceiverKeySystem> key_systems;
+};
+
+struct ReceiverError {
+ ReceiverError();
+ ~ReceiverError();
+ bool Parse(const base::Value& raw_value);
+
+ int32_t code;
+ std::string description;
+ std::string details; // In JSON format.
+};
+
+struct ReceiverResponse {
+ ReceiverResponse();
+ ~ReceiverResponse();
+ ReceiverResponse(ReceiverResponse&& receiver_response);
+ ReceiverResponse& operator=(ReceiverResponse&& receiver_response);
+ bool Parse(const std::string& message_data);
+
+ ResponseType type;
+ // All messages have same |session_id| for each mirroring session. This value
+ // is provided by the media router provider.
+ int32_t session_id;
+ // This should be same as the value in the corresponding query/OFFER messages
+ // for non-rpc messages.
+ int32_t sequence_number;
+
+ std::string result; // "ok" or "error".
+
+ // Only one of the following has value, according to |type|.
+ std::unique_ptr<Answer> answer;
+ std::string rpc;
+ std::unique_ptr<ReceiverStatus> status;
+ std::unique_ptr<ReceiverCapability> capabilities;
+ // Can only be non-null when result is "error".
+ std::unique_ptr<ReceiverError> error;
+};
+
+} // namespace mirroring
+
+#endif // COMPONENTS_MIRRORING_SERVICE_RECEIVER_RESPONSE_H_
diff --git a/chromium/components/mirroring/service/receiver_response_unittest.cc b/chromium/components/mirroring/service/receiver_response_unittest.cc
new file mode 100644
index 00000000000..807a683f48d
--- /dev/null
+++ b/chromium/components/mirroring/service/receiver_response_unittest.cc
@@ -0,0 +1,263 @@
+// Copyright 2018 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 "components/mirroring/service/receiver_response.h"
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/json/json_reader.h"
+#include "base/macros.h"
+#include "base/test/mock_callback.h"
+#include "components/mirroring/service/value_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::InvokeWithoutArgs;
+using ::testing::_;
+
+namespace mirroring {
+
+class ReceiverResponseTest : public ::testing::Test {
+ public:
+ ReceiverResponseTest() {}
+ ~ReceiverResponseTest() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReceiverResponseTest);
+};
+
+TEST_F(ReceiverResponseTest, ParseValidJson) {
+ const std::string response_string = "{\"type\":\"ANSWER\",\"result\":\"ok\"}";
+ ReceiverResponse response;
+ ASSERT_TRUE(response.Parse(response_string));
+ EXPECT_EQ(-1, response.session_id);
+ EXPECT_EQ(-1, response.sequence_number);
+ EXPECT_EQ(ResponseType::ANSWER, response.type);
+ EXPECT_EQ("ok", response.result);
+ EXPECT_FALSE(response.answer);
+ EXPECT_FALSE(response.status);
+ EXPECT_FALSE(response.capabilities);
+ EXPECT_FALSE(response.error);
+ EXPECT_TRUE(response.rpc.empty());
+}
+
+TEST_F(ReceiverResponseTest, ParseInvalidValueType) {
+ const std::string response_string =
+ "{\"sessionId\":123, \"seqNum\":\"one-two-three\"}";
+ ReceiverResponse response;
+ EXPECT_FALSE(response.Parse(response_string));
+}
+
+TEST_F(ReceiverResponseTest, ParseNonJsonString) {
+ const std::string response_string = "This is not JSON.";
+ ReceiverResponse response;
+ EXPECT_FALSE(response.Parse(response_string));
+}
+
+TEST_F(ReceiverResponseTest, ParseRealWorldAnswerMessage) {
+ const std::string response_string =
+ "{\"answer\":{\"receiverRtcpEventLog\":[0,1],\"rtpExtensions\":"
+ "[[\"adaptive_playout_delay\"],[\"adaptive_playout_delay\"]],"
+ "\"sendIndexes\":[0,1],\"ssrcs\":[40863,759293],\"udpPort\":50691,"
+ "\"castMode\":\"mirroring\"},\"result\":\"ok\",\"seqNum\":989031000,"
+ "\"type\":\"ANSWER\"}";
+ ReceiverResponse response;
+ ASSERT_TRUE(response.Parse(response_string));
+ EXPECT_EQ(-1, response.session_id);
+ EXPECT_EQ(989031000, response.sequence_number);
+ EXPECT_EQ(ResponseType::ANSWER, response.type);
+ EXPECT_EQ("ok", response.result);
+ ASSERT_TRUE(response.answer);
+ EXPECT_EQ(50691, response.answer->udp_port);
+ EXPECT_EQ(std::vector<int32_t>({0, 1}), response.answer->send_indexes);
+ EXPECT_EQ(std::vector<int32_t>({40863, 759293}), response.answer->ssrcs);
+ EXPECT_TRUE(response.answer->iv.empty());
+ EXPECT_EQ(false, response.answer->supports_get_status);
+ EXPECT_EQ("mirroring", response.answer->cast_mode);
+ EXPECT_FALSE(response.status);
+ EXPECT_FALSE(response.capabilities);
+ EXPECT_FALSE(response.error);
+}
+
+TEST_F(ReceiverResponseTest, ParseErrorMessage) {
+ const std::string response_string =
+ "{\"sessionId\": 123,"
+ "\"seqNum\": 999,"
+ "\"type\": \"ANSWER\","
+ "\"result\": \"error\","
+ "\"error\": {"
+ "\"code\": 42,"
+ "\"description\": \"it is broke\","
+ "\"details\": {\"foo\": -1, \"bar\": 88}"
+ "}"
+ "}";
+ ReceiverResponse response;
+ ASSERT_TRUE(response.Parse(response_string));
+ EXPECT_EQ(123, response.session_id);
+ EXPECT_EQ(999, response.sequence_number);
+ EXPECT_EQ(ResponseType::ANSWER, response.type);
+ EXPECT_EQ("error", response.result);
+ EXPECT_FALSE(response.answer);
+ EXPECT_FALSE(response.status);
+ EXPECT_FALSE(response.capabilities);
+ ASSERT_TRUE(response.error);
+ EXPECT_EQ(42, response.error->code);
+ EXPECT_EQ("it is broke", response.error->description);
+ std::unique_ptr<base::Value> parsed_details =
+ base::JSONReader::Read(response.error->details);
+ ASSERT_TRUE(parsed_details && parsed_details->is_dict());
+ EXPECT_EQ(2u, parsed_details->DictSize());
+ int fool_value = 0;
+ EXPECT_TRUE(GetInt(*parsed_details, "foo", &fool_value));
+ EXPECT_EQ(-1, fool_value);
+ int bar_value = 0;
+ EXPECT_TRUE(GetInt(*parsed_details, "bar", &bar_value));
+ EXPECT_EQ(88, bar_value);
+}
+
+TEST_F(ReceiverResponseTest, ParseStatusMessage) {
+ const std::string response_string =
+ "{\"seqNum\": 777,"
+ "\"type\": \"STATUS_RESPONSE\","
+ "\"result\": \"ok\","
+ "\"status\": {"
+ "\"wifiSnr\": 36.7,"
+ "\"wifiSpeed\": [1234, 5678, 3000, 3001],"
+ "\"wifiFcsError\": [12, 13, 12, 12]}" // This will be ignored.
+ "}";
+ ReceiverResponse response;
+ ASSERT_TRUE(response.Parse(response_string));
+ EXPECT_EQ(777, response.sequence_number);
+ EXPECT_EQ(ResponseType::STATUS_RESPONSE, response.type);
+ EXPECT_EQ("ok", response.result);
+ EXPECT_FALSE(response.error);
+ EXPECT_FALSE(response.answer);
+ ASSERT_TRUE(response.status);
+ EXPECT_EQ(36.7, response.status->wifi_snr);
+ const std::vector<int32_t> expect_speed({1234, 5678, 3000, 3001});
+ EXPECT_EQ(expect_speed, response.status->wifi_speed);
+ EXPECT_FALSE(response.capabilities);
+}
+
+TEST_F(ReceiverResponseTest, ParseCapabilityMessage) {
+ const std::string response_string =
+ "{\"sessionId\": 999888777,"
+ "\"seqNum\": 5551212,"
+ "\"type\": \"CAPABILITIES_RESPONSE\","
+ "\"result\": \"ok\","
+ "\"capabilities\": {"
+ "\"mediaCaps\": [\"audio\", \"video\", \"vp9\"],"
+ "\"keySystems\": ["
+ "{"
+ "\"keySystemName\": \"com.w3c.clearkey\""
+ "},"
+ "{"
+ "\"keySystemName\": \"com.widevine.alpha\","
+ "\"initDataTypes\": [\"a\", \"b\", \"c\", \"1\", \"2\", \"3\"],"
+ "\"codecs\": [\"vp8\", \"h264\"],"
+ "\"secureCodecs\": [\"h264\", \"vp8\"],"
+ "\"audioRobustness\": [\"nope\"],"
+ "\"videoRobustness\": [\"yep\"],"
+ "\"persistentLicenseSessionSupport\": \"SUPPORTED\","
+ "\"persistentReleaseMessageSessionSupport\": \"SUPPORTED_WITH_ID\","
+ "\"persistentStateSupport\": \"REQUESTABLE\","
+ "\"distinctiveIdentifierSupport\": \"ALWAYS_ENABLED\""
+ "}"
+ "]}}";
+ ReceiverResponse response;
+ ASSERT_TRUE(response.Parse(response_string));
+ EXPECT_EQ(999888777, response.session_id);
+ EXPECT_EQ(5551212, response.sequence_number);
+ EXPECT_EQ(ResponseType::CAPABILITIES_RESPONSE, response.type);
+ EXPECT_EQ("ok", response.result);
+ EXPECT_FALSE(response.error);
+ EXPECT_FALSE(response.answer);
+ EXPECT_FALSE(response.status);
+ ASSERT_TRUE(response.capabilities);
+ EXPECT_EQ(std::vector<std::string>({"audio", "video", "vp9"}),
+ response.capabilities->media_caps);
+ const ReceiverKeySystem& first_key_system =
+ response.capabilities->key_systems[0];
+ EXPECT_EQ("com.w3c.clearkey", first_key_system.name);
+ EXPECT_TRUE(first_key_system.init_data_types.empty());
+ EXPECT_TRUE(first_key_system.codecs.empty());
+ EXPECT_TRUE(first_key_system.secure_codecs.empty());
+ EXPECT_TRUE(first_key_system.audio_robustness.empty());
+ EXPECT_TRUE(first_key_system.video_robustness.empty());
+ EXPECT_TRUE(first_key_system.persistent_license_session_support.empty());
+ EXPECT_TRUE(
+ first_key_system.persistent_release_message_session_support.empty());
+ EXPECT_TRUE(first_key_system.persistent_state_support.empty());
+ EXPECT_TRUE(first_key_system.distinctive_identifier_support.empty());
+ const ReceiverKeySystem& second_key_system =
+ response.capabilities->key_systems[1];
+ EXPECT_EQ("com.widevine.alpha", second_key_system.name);
+ EXPECT_EQ(std::vector<std::string>({"a", "b", "c", "1", "2", "3"}),
+ second_key_system.init_data_types);
+ EXPECT_EQ(std::vector<std::string>({"vp8", "h264"}),
+ second_key_system.codecs);
+ EXPECT_EQ(std::vector<std::string>({"h264", "vp8"}),
+ second_key_system.secure_codecs);
+ EXPECT_EQ(std::vector<std::string>({"nope"}),
+ second_key_system.audio_robustness);
+ EXPECT_EQ(std::vector<std::string>({"yep"}),
+ second_key_system.video_robustness);
+ EXPECT_EQ("SUPPORTED", second_key_system.persistent_license_session_support);
+ EXPECT_EQ("SUPPORTED_WITH_ID",
+ second_key_system.persistent_release_message_session_support);
+ EXPECT_EQ("REQUESTABLE", second_key_system.persistent_state_support);
+ EXPECT_EQ("ALWAYS_ENABLED", second_key_system.distinctive_identifier_support);
+}
+
+TEST_F(ReceiverResponseTest, ParseRpcMessage) {
+ const std::string message = "Hello from the Cast Receiver!";
+ std::string rpc_base64;
+ base::Base64Encode(message, &rpc_base64);
+ std::string response_string =
+ "{\"sessionId\": 735189,"
+ "\"seqNum\": 6789,"
+ "\"type\": \"RPC\","
+ "\"result\": \"ok\","
+ "\"rpc\": \"" +
+ rpc_base64 + "\"}";
+ ReceiverResponse response;
+ ASSERT_TRUE(response.Parse(response_string));
+ EXPECT_EQ(735189, response.session_id);
+ EXPECT_EQ(6789, response.sequence_number);
+ EXPECT_EQ("ok", response.result);
+ EXPECT_EQ(ResponseType::RPC, response.type);
+ EXPECT_EQ(message, response.rpc);
+ EXPECT_FALSE(response.error);
+ EXPECT_FALSE(response.answer);
+ EXPECT_FALSE(response.status);
+ EXPECT_FALSE(response.capabilities);
+}
+
+TEST_F(ReceiverResponseTest, ParseResponseWithNullField) {
+ const std::string response_string =
+ "{\"sessionId\":null,\"seqNum\":808907000,\"type\":\"ANSWER\","
+ "\"result\":\"ok\",\"rpc\":null,\"error\":null,"
+ "\"answer\":{\"udpPort\":51706,\"sendIndexes\":[0,1],"
+ "\"ssrcs\":[152818,556029],\"IV\":null,\"receiverGetStatus\":true,"
+ "\"castMode\":\"mirroring\"},\"status\":null,\"capabilities\":null}";
+ ReceiverResponse response;
+ ASSERT_TRUE(response.Parse(response_string));
+ EXPECT_EQ(808907000, response.sequence_number);
+ EXPECT_EQ("ok", response.result);
+ EXPECT_FALSE(response.error);
+ EXPECT_FALSE(response.status);
+ EXPECT_FALSE(response.capabilities);
+ EXPECT_TRUE(response.rpc.empty());
+ EXPECT_EQ(ResponseType::ANSWER, response.type);
+ ASSERT_TRUE(response.answer);
+ EXPECT_EQ(51706, response.answer->udp_port);
+ EXPECT_EQ(std::vector<int32_t>({0, 1}), response.answer->send_indexes);
+ EXPECT_EQ(std::vector<int32_t>({152818, 556029}), response.answer->ssrcs);
+ EXPECT_TRUE(response.answer->iv.empty());
+ EXPECT_EQ(true, response.answer->supports_get_status);
+ EXPECT_EQ("mirroring", response.answer->cast_mode);
+}
+
+} // namespace mirroring
diff --git a/chromium/components/mirroring/service/rtp_stream.cc b/chromium/components/mirroring/service/rtp_stream.cc
index 1051557731c..d9a95d39b28 100644
--- a/chromium/components/mirroring/service/rtp_stream.cc
+++ b/chromium/components/mirroring/service/rtp_stream.cc
@@ -8,7 +8,6 @@
#include "base/logging.h"
#include "base/macros.h"
#include "base/values.h"
-#include "build/build_config.h"
#include "media/base/video_frame.h"
#include "media/cast/cast_config.h"
#include "media/cast/sender/audio_sender.h"
@@ -30,81 +29,6 @@ constexpr base::TimeDelta kRefreshInterval =
// limit (60 * 250ms = 15 seconds), refresh frame requests will stop being made.
constexpr int kMaxConsecutiveRefreshFrames = 60;
-FrameSenderConfig DefaultOpusConfig() {
- FrameSenderConfig config;
- config.rtp_payload_type = RtpPayloadType::AUDIO_OPUS;
- config.sender_ssrc = 1;
- config.receiver_ssrc = 2;
- config.rtp_timebase = media::cast::kDefaultAudioSamplingRate;
- config.channels = 2;
- config.min_bitrate = config.max_bitrate = config.start_bitrate =
- media::cast::kDefaultAudioEncoderBitrate;
- config.max_frame_rate = 100; // 10 ms audio frames
- config.codec = media::cast::CODEC_AUDIO_OPUS;
- return config;
-}
-
-FrameSenderConfig DefaultVp8Config() {
- FrameSenderConfig config;
- config.rtp_payload_type = RtpPayloadType::VIDEO_VP8;
- config.sender_ssrc = 11;
- config.receiver_ssrc = 12;
- config.rtp_timebase = media::cast::kVideoFrequency;
- config.channels = 1;
- config.max_bitrate = media::cast::kDefaultMaxVideoBitrate;
- config.min_bitrate = media::cast::kDefaultMinVideoBitrate;
- config.max_frame_rate = media::cast::kDefaultMaxFrameRate;
- config.codec = media::cast::CODEC_VIDEO_VP8;
- return config;
-}
-
-FrameSenderConfig DefaultH264Config() {
- FrameSenderConfig config;
- config.rtp_payload_type = RtpPayloadType::VIDEO_H264;
- config.sender_ssrc = 11;
- config.receiver_ssrc = 12;
- config.rtp_timebase = media::cast::kVideoFrequency;
- config.channels = 1;
- config.max_bitrate = media::cast::kDefaultMaxVideoBitrate;
- config.min_bitrate = media::cast::kDefaultMinVideoBitrate;
- config.max_frame_rate = media::cast::kDefaultMaxFrameRate;
- config.codec = media::cast::CODEC_VIDEO_H264;
- return config;
-}
-
-bool IsHardwareVP8EncodingSupported(RtpStreamClient* client) {
- // Query for hardware VP8 encoder support.
- const std::vector<media::VideoEncodeAccelerator::SupportedProfile>
- vea_profiles = client->GetSupportedVideoEncodeAcceleratorProfiles();
- for (const auto& vea_profile : vea_profiles) {
- if (vea_profile.profile >= media::VP8PROFILE_MIN &&
- vea_profile.profile <= media::VP8PROFILE_MAX) {
- return true;
- }
- }
- return false;
-}
-
-bool IsHardwareH264EncodingSupported(RtpStreamClient* client) {
-// Query for hardware H.264 encoder support.
-//
-// TODO(miu): Look into why H.264 hardware encoder on MacOS is broken.
-// http://crbug.com/596674
-// TODO(emircan): Look into HW encoder initialization issues on Win.
-// https://crbug.com/636064
-#if !defined(OS_MACOSX) && !defined(OS_WIN)
- const std::vector<media::VideoEncodeAccelerator::SupportedProfile>
- vea_profiles = client->GetSupportedVideoEncodeAcceleratorProfiles();
- for (const auto& vea_profile : vea_profiles) {
- if (vea_profile.profile >= media::H264PROFILE_MIN &&
- vea_profile.profile <= media::H264PROFILE_MAX) {
- return true;
- }
- }
-#endif // !defined(OS_MACOSX) && !defined(OS_WIN)
- return false;
-}
-
} // namespace
VideoRtpStream::VideoRtpStream(
@@ -125,24 +49,6 @@ VideoRtpStream::VideoRtpStream(
VideoRtpStream::~VideoRtpStream() {}
-// static
-std::vector<FrameSenderConfig> VideoRtpStream::GetSupportedConfigs(
- RtpStreamClient* client) {
- std::vector<FrameSenderConfig> supported_configs;
- // Prefer VP8 over H.264 for hardware encoder.
- if (IsHardwareVP8EncodingSupported(client))
- supported_configs.push_back(DefaultVp8Config());
- if (IsHardwareH264EncodingSupported(client))
- supported_configs.push_back(DefaultH264Config());
-
- // Propose the default software VP8 encoder, if no hardware encoders are
- // available.
- if (supported_configs.empty())
- supported_configs.push_back(DefaultVp8Config());
-
- return supported_configs;
-}
-
void VideoRtpStream::InsertVideoFrame(
scoped_refptr<media::VideoFrame> video_frame) {
DCHECK(client_);
@@ -205,11 +111,6 @@ AudioRtpStream::AudioRtpStream(
AudioRtpStream::~AudioRtpStream() {}
-// static
-std::vector<FrameSenderConfig> AudioRtpStream::GetSupportedConfigs() {
- return {DefaultOpusConfig()};
-}
-
void AudioRtpStream::InsertAudio(std::unique_ptr<media::AudioBus> audio_bus,
base::TimeTicks capture_time) {
audio_sender_->InsertAudio(std::move(audio_bus), capture_time);
diff --git a/chromium/components/mirroring/service/rtp_stream.h b/chromium/components/mirroring/service/rtp_stream.h
index ef5788d953c..9bffae1f60d 100644
--- a/chromium/components/mirroring/service/rtp_stream.h
+++ b/chromium/components/mirroring/service/rtp_stream.h
@@ -44,10 +44,6 @@ class RtpStreamClient {
// The following are for hardware video encoding.
- // Query the supported hardware encoding profiles.
- virtual media::VideoEncodeAccelerator::SupportedProfiles
- GetSupportedVideoEncodeAcceleratorProfiles() = 0;
-
virtual void CreateVideoEncodeAccelerator(
const media::cast::ReceiveVideoEncodeAcceleratorCallback& callback) = 0;
@@ -71,9 +67,6 @@ class VideoRtpStream {
base::WeakPtr<RtpStreamClient> client);
~VideoRtpStream();
- static std::vector<media::cast::FrameSenderConfig> GetSupportedConfigs(
- RtpStreamClient* client);
-
// Called by VideoCaptureClient when a video frame is received.
// |video_frame| is required to provide REFERENCE_TIME in the metadata.
void InsertVideoFrame(scoped_refptr<media::VideoFrame> video_frame);
@@ -115,8 +108,6 @@ class AudioRtpStream {
base::WeakPtr<RtpStreamClient> client);
~AudioRtpStream();
- static std::vector<media::cast::FrameSenderConfig> GetSupportedConfigs();
-
// Called by AudioCaptureClient when new audio data is available.
void InsertAudio(std::unique_ptr<media::AudioBus> audio_bus,
base::TimeTicks estimated_capture_time);
diff --git a/chromium/components/mirroring/service/rtp_stream_unittest.cc b/chromium/components/mirroring/service/rtp_stream_unittest.cc
index 1a2d2283643..b0a0ae26b04 100644
--- a/chromium/components/mirroring/service/rtp_stream_unittest.cc
+++ b/chromium/components/mirroring/service/rtp_stream_unittest.cc
@@ -43,10 +43,6 @@ class DummyClient final : public RtpStreamClient {
void CreateVideoEncodeMemory(
size_t size,
const media::cast::ReceiveVideoEncodeMemoryCallback& callback) override {}
- media::VideoEncodeAccelerator::SupportedProfiles
- GetSupportedVideoEncodeAcceleratorProfiles() override {
- return media::VideoEncodeAccelerator::SupportedProfiles();
- }
base::WeakPtr<RtpStreamClient> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
diff --git a/chromium/components/mirroring/service/session.cc b/chromium/components/mirroring/service/session.cc
index 1d97af99207..58047f8db81 100644
--- a/chromium/components/mirroring/service/session.cc
+++ b/chromium/components/mirroring/service/session.cc
@@ -4,23 +4,34 @@
#include "components/mirroring/service/session.h"
+#include "base/json/json_writer.h"
#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/sys_info.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
#include "components/mirroring/service/udp_socket_client.h"
#include "components/mirroring/service/video_capture_client.h"
+#include "crypto/random.h"
#include "media/cast/net/cast_transport.h"
#include "media/cast/sender/audio_sender.h"
#include "media/cast/sender/video_sender.h"
#include "media/video/video_encode_accelerator.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/system/platform_handle.h"
+#include "net/base/ip_endpoint.h"
using media::cast::FrameSenderConfig;
using media::cast::RtpPayloadType;
using media::cast::CastTransportStatus;
+using media::cast::Codec;
using media::cast::FrameEvent;
using media::cast::PacketEvent;
using media::cast::OperationalStatus;
@@ -39,6 +50,23 @@ constexpr base::TimeDelta kSendEventsInterval = base::TimeDelta::FromSeconds(1);
constexpr base::TimeDelta kOfferAnswerExchangeTimeout =
base::TimeDelta::FromSeconds(15);
+// Used for OFFER/ANSWER message exchange. Some receivers will error out on
+// payloadType values other than the ones hard-coded here.
+constexpr int kAudioPayloadType = 127;
+constexpr int kVideoPayloadType = 96;
+
+constexpr int kAudioSsrcMin = 1;
+constexpr int kAudioSsrcMax = 5e5;
+constexpr int kVideoSsrcMin = 5e5 + 1;
+constexpr int kVideoSsrcMax = 10e5;
+
+// Maximum number of bytes of file data allowed in a single Crash report. As of
+// this writing, the total report upload size is capped at 20 MB.
+//
+// 2 KB of "overhead bytes" are subtracted to account for all of the non-file
+// data in a report upload, including HTTP headers/requests and form data.
+constexpr int kMaxCrashReportBytes = (20 * 1024 - 2) * 1024;
+
class TransportClient final : public media::cast::CastTransport::Client {
public:
explicit TransportClient(Session* session) : session_(session) {}
@@ -67,142 +95,192 @@ class TransportClient final : public media::cast::CastTransport::Client {
DISALLOW_COPY_AND_ASSIGN(TransportClient);
};
-} // namespace
-
-Session::Session(SessionType session_type,
- const net::IPEndPoint& receiver_endpoint,
- SessionClient* client)
- : client_(client), weak_factory_(this) {
- DCHECK(client_);
-
- std::vector<FrameSenderConfig> audio_configs;
- std::vector<FrameSenderConfig> video_configs;
- if (session_type != SessionType::VIDEO_ONLY)
- audio_configs = AudioRtpStream::GetSupportedConfigs();
- if (session_type != SessionType::AUDIO_ONLY)
- video_configs = VideoRtpStream::GetSupportedConfigs(this);
- start_timeout_timer_.Start(
- FROM_HERE, kOfferAnswerExchangeTimeout,
- base::BindRepeating(&Session::OnOfferAnswerExchangeTimeout,
- weak_factory_.GetWeakPtr()));
- client_->DoOfferAnswerExchange(
- audio_configs, video_configs,
- base::BindOnce(&Session::StartInternal, weak_factory_.GetWeakPtr(),
- receiver_endpoint));
+// Generates a string with cryptographically secure random bytes.
+std::string MakeRandomString(size_t length) {
+ std::string result(length, ' ');
+ crypto::RandBytes(base::data(result), length);
+ return result;
}
-Session::~Session() {
- StopSession();
+int NumberOfEncodeThreads() {
+ // Do not saturate CPU utilization just for encoding. On a lower-end system
+ // with only 1 or 2 cores, use only one thread for encoding. On systems with
+ // more cores, allow half of the cores to be used for encoding.
+ return std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2);
}
-void Session::StartInternal(const net::IPEndPoint& receiver_endpoint,
- const FrameSenderConfig& audio_config,
- const FrameSenderConfig& video_config) {
- DVLOG(1) << __func__;
- start_timeout_timer_.Stop();
-
- DCHECK(!video_capture_client_);
- DCHECK(!cast_transport_);
- DCHECK(!audio_stream_);
- DCHECK(!video_stream_);
- DCHECK(!cast_environment_);
- DCHECK(client_);
-
- if (audio_config.rtp_payload_type == RtpPayloadType::REMOTE_AUDIO ||
- video_config.rtp_payload_type == RtpPayloadType::REMOTE_VIDEO) {
- NOTIMPLEMENTED(); // TODO(xjz): Add support for media remoting.
- return;
+// Scan profiles for hardware VP8 encoder support.
+bool IsHardwareVP8EncodingSupported(
+ const std::vector<media::VideoEncodeAccelerator::SupportedProfile>&
+ profiles) {
+ for (const auto& vea_profile : profiles) {
+ if (vea_profile.profile >= media::VP8PROFILE_MIN &&
+ vea_profile.profile <= media::VP8PROFILE_MAX) {
+ return true;
+ }
}
+ return false;
+}
- const bool has_audio =
- (audio_config.rtp_payload_type < RtpPayloadType::AUDIO_LAST) &&
- (audio_config.rtp_payload_type >= RtpPayloadType::FIRST);
- const bool has_video =
- (video_config.rtp_payload_type > RtpPayloadType::AUDIO_LAST) &&
- (video_config.rtp_payload_type < RtpPayloadType::LAST);
- if (!has_audio && !has_video) {
- VLOG(1) << "Incorrect ANSWER message: No audio or Video.";
- client_->OnError(SESSION_START_ERROR);
- return;
+// Scan profiles for hardware H.264 encoder support.
+bool IsHardwareH264EncodingSupported(
+ const std::vector<media::VideoEncodeAccelerator::SupportedProfile>&
+ profiles) {
+// TODO(miu): Look into why H.264 hardware encoder on MacOS is broken.
+// http://crbug.com/596674
+// TODO(emircan): Look into HW encoder initialization issues on Win.
+// https://crbug.com/636064
+#if !defined(OS_MACOSX) && !defined(OS_WIN)
+ for (const auto& vea_profile : profiles) {
+ if (vea_profile.profile >= media::H264PROFILE_MIN &&
+ vea_profile.profile <= media::H264PROFILE_MAX) {
+ return true;
+ }
}
+#endif // !defined(OS_MACOSX) && !defined(OS_WIN)
+ return false;
+}
- audio_encode_thread_ = base::CreateSingleThreadTaskRunnerWithTraits(
- {base::TaskPriority::USER_BLOCKING,
- base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
- base::SingleThreadTaskRunnerThreadMode::DEDICATED);
- video_encode_thread_ = base::CreateSingleThreadTaskRunnerWithTraits(
- {base::TaskPriority::USER_BLOCKING,
- base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
- base::SingleThreadTaskRunnerThreadMode::DEDICATED);
- cast_environment_ = new media::cast::CastEnvironment(
- base::DefaultTickClock::GetInstance(),
- base::ThreadTaskRunnerHandle::Get(), audio_encode_thread_,
- video_encode_thread_);
- network::mojom::NetworkContextPtr network_context;
- client_->GetNewWorkContext(mojo::MakeRequest(&network_context));
- auto udp_client = std::make_unique<UdpSocketClient>(
- receiver_endpoint, std::move(network_context),
- base::BindOnce(&Session::ReportError, weak_factory_.GetWeakPtr(),
- SessionError::CAST_TRANSPORT_ERROR));
- cast_transport_ = media::cast::CastTransport::Create(
- cast_environment_->Clock(), kSendEventsInterval,
- std::make_unique<TransportClient>(this), std::move(udp_client),
- base::ThreadTaskRunnerHandle::Get());
+// Helper to add |config| to |config_list| with given |aes_key|.
+void AddSenderConfig(int32_t sender_ssrc,
+ FrameSenderConfig config,
+ const std::string& aes_key,
+ const std::string& aes_iv,
+ std::vector<FrameSenderConfig>* config_list) {
+ config.aes_key = aes_key;
+ config.aes_iv_mask = aes_iv;
+ config.sender_ssrc = sender_ssrc;
+ config_list->emplace_back(config);
+}
- if (has_audio) {
- auto audio_sender = std::make_unique<media::cast::AudioSender>(
- cast_environment_, audio_config,
- base::BindRepeating(&Session::OnEncoderStatusChange,
- weak_factory_.GetWeakPtr()),
- cast_transport_.get());
- audio_stream_ = std::make_unique<AudioRtpStream>(
- std::move(audio_sender), weak_factory_.GetWeakPtr());
- // TODO(xjz): Start audio capturing.
- NOTIMPLEMENTED();
+// Generate the stream object from |config| and add it to |stream_list|.
+void AddStreamObject(int stream_index,
+ const std::string& codec_name,
+ const FrameSenderConfig& config,
+ const MirrorSettings& mirror_settings,
+ base::Value::ListStorage* stream_list) {
+ base::Value stream(base::Value::Type::DICTIONARY);
+ stream.SetKey("index", base::Value(stream_index));
+ stream.SetKey("codecName", base::Value(base::ToLowerASCII(codec_name)));
+ stream.SetKey("rtpProfile", base::Value("cast"));
+ const bool is_audio =
+ (config.rtp_payload_type <= media::cast::RtpPayloadType::AUDIO_LAST);
+ stream.SetKey("rtpPayloadType",
+ base::Value(is_audio ? kAudioPayloadType : kVideoPayloadType));
+ stream.SetKey("ssrc", base::Value(int(config.sender_ssrc)));
+ stream.SetKey(
+ "targetDelay",
+ base::Value(int(config.animated_playout_delay.InMilliseconds())));
+ stream.SetKey("aesKey", base::Value(base::HexEncode(config.aes_key.data(),
+ config.aes_key.size())));
+ stream.SetKey("aesIvMask",
+ base::Value(base::HexEncode(config.aes_iv_mask.data(),
+ config.aes_iv_mask.size())));
+ stream.SetKey("timeBase",
+ base::Value("1/" + std::to_string(config.rtp_timebase)));
+ stream.SetKey("receiverRtcpEventLog", base::Value(true));
+ stream.SetKey("rtpExtensions", base::Value("adaptive_playout_delay"));
+ if (is_audio) {
+ // Note on "AUTO" bitrate calculation: This is based on libopus source
+ // at the time of this writing. Internally, it uses the following math:
+ //
+ // packet_overhead_bps = 60 bits * num_packets_in_one_second
+ // approx_encoded_signal_bps = frequency * channels
+ // estimated_bps = packet_overhead_bps + approx_encoded_signal_bps
+ //
+ // For 100 packets/sec at 48 kHz and 2 channels, this is 102kbps.
+ const int bitrate = config.max_bitrate > 0
+ ? config.max_bitrate
+ : (60 * config.max_frame_rate +
+ config.rtp_timebase * config.channels);
+ stream.SetKey("type", base::Value("audio_source"));
+ stream.SetKey("bitRate", base::Value(bitrate));
+ stream.SetKey("sampleRate", base::Value(config.rtp_timebase));
+ stream.SetKey("channels", base::Value(config.channels));
+ } else /* is video */ {
+ stream.SetKey("type", base::Value("video_source"));
+ stream.SetKey("renderMode", base::Value("video"));
+ stream.SetKey("maxFrameRate",
+ base::Value(std::to_string(static_cast<int>(
+ config.max_frame_rate * 1000)) +
+ "/1000"));
+ stream.SetKey("maxBitRate", base::Value(config.max_bitrate));
+ base::Value::ListStorage resolutions;
+ base::Value resolution(base::Value::Type::DICTIONARY);
+ resolution.SetKey("width", base::Value(mirror_settings.max_width()));
+ resolution.SetKey("height", base::Value(mirror_settings.max_height()));
+ resolutions.emplace_back(std::move(resolution));
+ stream.SetKey("resolutions", base::Value(resolutions));
}
+ stream_list->emplace_back(std::move(stream));
+}
- if (has_video) {
- auto video_sender = std::make_unique<media::cast::VideoSender>(
- cast_environment_, video_config,
- base::BindRepeating(&Session::OnEncoderStatusChange,
- weak_factory_.GetWeakPtr()),
- base::BindRepeating(&Session::CreateVideoEncodeAccelerator,
- weak_factory_.GetWeakPtr()),
- base::BindRepeating(&Session::CreateVideoEncodeMemory,
- weak_factory_.GetWeakPtr()),
- cast_transport_.get(),
- base::BindRepeating(&Session::SetTargetPlayoutDelay,
- weak_factory_.GetWeakPtr()));
- video_stream_ = std::make_unique<VideoRtpStream>(
- std::move(video_sender), weak_factory_.GetWeakPtr());
- media::mojom::VideoCaptureHostPtr video_host;
- client_->GetVideoCaptureHost(mojo::MakeRequest(&video_host));
- video_capture_client_ =
- std::make_unique<VideoCaptureClient>(std::move(video_host));
- video_capture_client_->Start(
- base::BindRepeating(&VideoRtpStream::InsertVideoFrame,
- video_stream_->AsWeakPtr()),
- base::BindOnce(&Session::ReportError, weak_factory_.GetWeakPtr(),
- SessionError::VIDEO_CAPTURE_ERROR));
- }
+} // namespace
+
+Session::Session(int32_t session_id,
+ const CastSinkInfo& sink_info,
+ const gfx::Size& max_resolution,
+ SessionObserver* observer,
+ ResourceProvider* resource_provider,
+ CastMessageChannel* outbound_channel)
+ : session_id_(session_id),
+ sink_info_(sink_info),
+ observer_(observer),
+ resource_provider_(resource_provider),
+ message_dispatcher_(outbound_channel,
+ base::BindRepeating(&Session::OnResponseParsingError,
+ base::Unretained(this))),
+ weak_factory_(this) {
+ DCHECK(resource_provider_);
+ mirror_settings_.SetResolutionContraints(max_resolution.width(),
+ max_resolution.height());
+ resource_provider_->GetNetworkContext(mojo::MakeRequest(&network_context_));
+
+ auto wifi_status_monitor =
+ std::make_unique<WifiStatusMonitor>(session_id_, &message_dispatcher_);
+ network::mojom::URLLoaderFactoryPtr url_loader_factory;
+ network_context_->CreateURLLoaderFactory(
+ mojo::MakeRequest(&url_loader_factory),
+ network::mojom::URLLoaderFactoryParams::New(
+ network::mojom::kBrowserProcessId, false, std::string()));
+
+ // Generate session level tags.
+ base::Value session_tags(base::Value::Type::DICTIONARY);
+ session_tags.SetKey("mirrorSettings", mirror_settings_.ToDictionaryValue());
+ session_tags.SetKey("shouldCaptureAudio",
+ base::Value(sink_info_.capability != VIDEO_ONLY));
+ session_tags.SetKey("shouldCaptureVideo",
+ base::Value(sink_info_.capability != AUDIO_ONLY));
+ session_tags.SetKey("receiverProductName",
+ base::Value(sink_info_.model_name));
+
+ session_monitor_.emplace(
+ kMaxCrashReportBytes, sink_info_.ip_address, std::move(session_tags),
+ std::move(url_loader_factory), std::move(wifi_status_monitor));
+
+ CreateAndSendOffer();
+}
- client_->DidStart();
+Session::~Session() {
+ StopSession();
}
void Session::ReportError(SessionError error) {
- DVLOG(1) << __func__ << ": error=" << error;
- if (client_)
- client_->OnError(error);
+ if (session_monitor_.has_value())
+ session_monitor_->OnStreamingError(error);
+ if (observer_)
+ observer_->OnError(error);
StopSession();
}
void Session::StopSession() {
DVLOG(1) << __func__;
- if (!client_)
+ if (!resource_provider_)
return;
+ session_monitor_->StopStreamingSession();
+ session_monitor_.reset();
weak_factory_.InvalidateWeakPtrs();
- start_timeout_timer_.Stop();
audio_encode_thread_ = nullptr;
video_encode_thread_ = nullptr;
video_capture_client_.reset();
@@ -210,13 +288,15 @@ void Session::StopSession() {
video_stream_.reset();
cast_transport_.reset();
cast_environment_ = nullptr;
- client_->DidStop();
- client_ = nullptr;
+ resource_provider_ = nullptr;
+ if (observer_) {
+ observer_->DidStop();
+ observer_ = nullptr;
+ }
}
void Session::OnError(const std::string& message) {
- VLOG(1) << message;
- ReportError(SessionError::CAST_STREAMING_ERROR);
+ ReportError(SessionError::RTP_STREAM_ERROR);
}
void Session::RequestRefreshFrame() {
@@ -238,14 +318,13 @@ void Session::OnEncoderStatusChange(OperationalStatus status) {
case OperationalStatus::STATUS_UNSUPPORTED_CODEC:
case OperationalStatus::STATUS_CODEC_INIT_FAILED:
case OperationalStatus::STATUS_CODEC_RUNTIME_ERROR:
- DVLOG(1) << "OperationalStatus error.";
- ReportError(SessionError::CAST_STREAMING_ERROR);
+ ReportError(SessionError::ENCODING_ERROR);
break;
}
}
media::VideoEncodeAccelerator::SupportedProfiles
-Session::GetSupportedVideoEncodeAcceleratorProfiles() {
+Session::GetSupportedVeaProfiles() {
// TODO(xjz): Establish GPU channel and query for the supported profiles.
return media::VideoEncodeAccelerator::SupportedProfiles();
}
@@ -290,13 +369,7 @@ void Session::OnTransportStatusChanged(CastTransportStatus status) {
case CastTransportStatus::TRANSPORT_STREAM_INITIALIZED:
return; // Not errors, do nothing.
case CastTransportStatus::TRANSPORT_INVALID_CRYPTO_CONFIG:
- DVLOG(1) << "Warning: unexpected status: "
- << "TRANSPORT_INVALID_CRYPTO_CONFIG";
- ReportError(SessionError::CAST_TRANSPORT_ERROR);
- break;
case CastTransportStatus::TRANSPORT_SOCKET_ERROR:
- DVLOG(1) << "Warning: unexpected status: "
- << "TRANSPORT_SOCKET_ERROR";
ReportError(SessionError::CAST_TRANSPORT_ERROR);
break;
}
@@ -310,6 +383,156 @@ void Session::OnLoggingEventsReceived(
std::move(packet_events));
}
+void Session::OnAnswer(const std::string& cast_mode,
+ const std::vector<FrameSenderConfig>& audio_configs,
+ const std::vector<FrameSenderConfig>& video_configs,
+ const ReceiverResponse& response) {
+ if (!response.answer || response.type == ResponseType::UNKNOWN) {
+ ReportError(ANSWER_TIME_OUT);
+ return;
+ }
+
+ DCHECK_EQ(ResponseType::ANSWER, response.type);
+
+ if (response.result != "ok") {
+ ReportError(ANSWER_NOT_OK);
+ return;
+ }
+
+ const Answer& answer = *response.answer;
+ if (answer.cast_mode != cast_mode) {
+ ReportError(ANSWER_MISMATCHED_CAST_MODE);
+ return;
+ }
+
+ if (answer.send_indexes.size() != answer.ssrcs.size()) {
+ ReportError(ANSWER_MISMATCHED_SSRC_LENGTH);
+ return;
+ }
+
+ // Select Audio/Video config from ANSWER.
+ bool has_audio = false;
+ bool has_video = false;
+ FrameSenderConfig audio_config;
+ FrameSenderConfig video_config;
+ const int video_start_idx = audio_configs.size();
+ const int video_idx_bound = video_configs.size() + video_start_idx;
+ for (size_t i = 0; i < answer.send_indexes.size(); ++i) {
+ if (answer.send_indexes[i] < 0 ||
+ answer.send_indexes[i] >= video_idx_bound) {
+ ReportError(ANSWER_SELECT_INVALID_INDEX);
+ return;
+ }
+ if (answer.send_indexes[i] < video_start_idx) {
+ // Audio
+ if (has_audio) {
+ ReportError(ANSWER_SELECT_MULTIPLE_AUDIO);
+ return;
+ }
+ audio_config = audio_configs[answer.send_indexes[i]];
+ audio_config.receiver_ssrc = answer.ssrcs[i];
+ has_audio = true;
+ } else {
+ // Video
+ if (has_video) {
+ ReportError(ANSWER_SELECT_MULTIPLE_VIDEO);
+ return;
+ }
+ video_config = video_configs[answer.send_indexes[i] - video_start_idx];
+ video_config.receiver_ssrc = answer.ssrcs[i];
+ video_config.video_codec_params.number_of_encode_threads =
+ NumberOfEncodeThreads();
+ has_video = true;
+ }
+ }
+ if (!has_audio && !has_video) {
+ ReportError(ANSWER_NO_AUDIO_OR_VIDEO);
+ return;
+ }
+
+ if ((has_audio &&
+ audio_config.rtp_payload_type == RtpPayloadType::REMOTE_AUDIO) ||
+ (has_video &&
+ video_config.rtp_payload_type == RtpPayloadType::REMOTE_VIDEO)) {
+ NOTIMPLEMENTED(); // TODO(xjz): Add support for media remoting.
+ return;
+ }
+
+ // Start streaming.
+ audio_encode_thread_ = base::CreateSingleThreadTaskRunnerWithTraits(
+ {base::TaskPriority::USER_BLOCKING,
+ base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
+ base::SingleThreadTaskRunnerThreadMode::DEDICATED);
+ video_encode_thread_ = base::CreateSingleThreadTaskRunnerWithTraits(
+ {base::TaskPriority::USER_BLOCKING,
+ base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
+ base::SingleThreadTaskRunnerThreadMode::DEDICATED);
+ cast_environment_ = new media::cast::CastEnvironment(
+ base::DefaultTickClock::GetInstance(),
+ base::ThreadTaskRunnerHandle::Get(), audio_encode_thread_,
+ video_encode_thread_);
+ auto udp_client = std::make_unique<UdpSocketClient>(
+ net::IPEndPoint(sink_info_.ip_address, answer.udp_port),
+ network_context_.get(),
+ base::BindOnce(&Session::ReportError, weak_factory_.GetWeakPtr(),
+ SessionError::CAST_TRANSPORT_ERROR));
+ cast_transport_ = media::cast::CastTransport::Create(
+ cast_environment_->Clock(), kSendEventsInterval,
+ std::make_unique<TransportClient>(this), std::move(udp_client),
+ base::ThreadTaskRunnerHandle::Get());
+
+ if (has_audio) {
+ auto audio_sender = std::make_unique<media::cast::AudioSender>(
+ cast_environment_, audio_config,
+ base::BindRepeating(&Session::OnEncoderStatusChange,
+ weak_factory_.GetWeakPtr()),
+ cast_transport_.get());
+ audio_stream_ = std::make_unique<AudioRtpStream>(
+ std::move(audio_sender), weak_factory_.GetWeakPtr());
+ // TODO(xjz): Start audio capturing.
+ NOTIMPLEMENTED();
+ }
+
+ if (has_video) {
+ auto video_sender = std::make_unique<media::cast::VideoSender>(
+ cast_environment_, video_config,
+ base::BindRepeating(&Session::OnEncoderStatusChange,
+ weak_factory_.GetWeakPtr()),
+ base::BindRepeating(&Session::CreateVideoEncodeAccelerator,
+ weak_factory_.GetWeakPtr()),
+ base::BindRepeating(&Session::CreateVideoEncodeMemory,
+ weak_factory_.GetWeakPtr()),
+ cast_transport_.get(),
+ base::BindRepeating(&Session::SetTargetPlayoutDelay,
+ weak_factory_.GetWeakPtr()));
+ video_stream_ = std::make_unique<VideoRtpStream>(
+ std::move(video_sender), weak_factory_.GetWeakPtr());
+ media::mojom::VideoCaptureHostPtr video_host;
+ resource_provider_->GetVideoCaptureHost(mojo::MakeRequest(&video_host));
+ video_capture_client_ = std::make_unique<VideoCaptureClient>(
+ mirror_settings_.GetVideoCaptureParams(), std::move(video_host));
+ video_capture_client_->Start(
+ base::BindRepeating(&VideoRtpStream::InsertVideoFrame,
+ video_stream_->AsWeakPtr()),
+ base::BindOnce(&Session::ReportError, weak_factory_.GetWeakPtr(),
+ SessionError::VIDEO_CAPTURE_ERROR));
+ }
+
+ const SessionMonitor::SessionType session_type =
+ (has_audio && has_video)
+ ? SessionMonitor::AUDIO_AND_VIDEO
+ : has_audio ? SessionMonitor::AUDIO_ONLY : SessionMonitor::VIDEO_ONLY;
+ session_monitor_->StartStreamingSession(cast_environment_, session_type,
+ false /* is_remoting */);
+
+ if (observer_)
+ observer_->DidStart();
+}
+
+void Session::OnResponseParsingError(const std::string& error_message) {
+ // TODO(xjz): Log the |error_message| in the mirroring logs.
+}
+
void Session::SetTargetPlayoutDelay(base::TimeDelta playout_delay) {
if (audio_stream_)
audio_stream_->SetTargetPlayoutDelay(playout_delay);
@@ -317,10 +540,78 @@ void Session::SetTargetPlayoutDelay(base::TimeDelta playout_delay) {
video_stream_->SetTargetPlayoutDelay(playout_delay);
}
-void Session::OnOfferAnswerExchangeTimeout() {
- VLOG(1) << "OFFER/ANSWER exchange timed out.";
- DCHECK(client_);
- client_->OnError(SESSION_START_ERROR);
+void Session::CreateAndSendOffer() {
+ // The random AES key and initialization vector pair used by all streams in
+ // this session.
+ const std::string aes_key = MakeRandomString(16); // AES-128.
+ const std::string aes_iv = MakeRandomString(16); // AES has 128-bit blocks.
+ std::vector<FrameSenderConfig> audio_configs;
+ std::vector<FrameSenderConfig> video_configs;
+
+ // Generate stream list with supported audio / video configs.
+ base::Value::ListStorage stream_list;
+ int stream_index = 0;
+ if (sink_info_.capability != DeviceCapability::VIDEO_ONLY) {
+ FrameSenderConfig config = MirrorSettings::GetDefaultAudioConfig(
+ RtpPayloadType::AUDIO_OPUS, Codec::CODEC_AUDIO_OPUS);
+ AddSenderConfig(base::RandInt(kAudioSsrcMin, kAudioSsrcMax), config,
+ aes_key, aes_iv, &audio_configs);
+ AddStreamObject(stream_index++, "OPUS", audio_configs.back(),
+ mirror_settings_, &stream_list);
+ }
+ if (sink_info_.capability != DeviceCapability::AUDIO_ONLY) {
+ const int32_t video_ssrc = base::RandInt(kVideoSsrcMin, kVideoSsrcMax);
+ if (IsHardwareVP8EncodingSupported(GetSupportedVeaProfiles())) {
+ FrameSenderConfig config = MirrorSettings::GetDefaultVideoConfig(
+ RtpPayloadType::VIDEO_VP8, Codec::CODEC_VIDEO_VP8);
+ config.use_external_encoder = true;
+ AddSenderConfig(video_ssrc, config, aes_key, aes_iv, &video_configs);
+ AddStreamObject(stream_index++, "VP8", video_configs.back(),
+ mirror_settings_, &stream_list);
+ }
+ if (IsHardwareH264EncodingSupported(GetSupportedVeaProfiles())) {
+ FrameSenderConfig config = MirrorSettings::GetDefaultVideoConfig(
+ RtpPayloadType::VIDEO_H264, Codec::CODEC_VIDEO_H264);
+ config.use_external_encoder = true;
+ AddSenderConfig(video_ssrc, config, aes_key, aes_iv, &video_configs);
+ AddStreamObject(stream_index++, "H264", video_configs.back(),
+ mirror_settings_, &stream_list);
+ }
+ if (video_configs.empty()) {
+ FrameSenderConfig config = MirrorSettings::GetDefaultVideoConfig(
+ RtpPayloadType::VIDEO_VP8, Codec::CODEC_VIDEO_VP8);
+ AddSenderConfig(video_ssrc, config, aes_key, aes_iv, &video_configs);
+ AddStreamObject(stream_index++, "VP8", video_configs.back(),
+ mirror_settings_, &stream_list);
+ }
+ }
+ DCHECK(!audio_configs.empty() || !video_configs.empty());
+
+ // Assemble the OFFER message.
+ const std::string cast_mode = "mirroring";
+ base::Value offer(base::Value::Type::DICTIONARY);
+ offer.SetKey("castMode", base::Value(cast_mode));
+ offer.SetKey("receiverGetStatus", base::Value("true"));
+ offer.SetKey("supportedStreams", base::Value(stream_list));
+
+ const int32_t sequence_number = message_dispatcher_.GetNextSeqNumber();
+ base::Value offer_message(base::Value::Type::DICTIONARY);
+ offer_message.SetKey("type", base::Value("OFFER"));
+ offer_message.SetKey("sessionId", base::Value(session_id_));
+ offer_message.SetKey("seqNum", base::Value(sequence_number));
+ offer_message.SetKey("offer", std::move(offer));
+
+ CastMessage message_to_receiver;
+ message_to_receiver.message_namespace = kWebRtcNamespace;
+ const bool did_serialize_offer = base::JSONWriter::Write(
+ offer_message, &message_to_receiver.json_format_data);
+ DCHECK(did_serialize_offer);
+
+ message_dispatcher_.RequestReply(
+ message_to_receiver, ResponseType::ANSWER, sequence_number,
+ kOfferAnswerExchangeTimeout,
+ base::BindOnce(&Session::OnAnswer, base::Unretained(this), cast_mode,
+ audio_configs, video_configs));
}
} // namespace mirroring
diff --git a/chromium/components/mirroring/service/session.h b/chromium/components/mirroring/service/session.h
index 00cf32b7a22..efc7d8b125b 100644
--- a/chromium/components/mirroring/service/session.h
+++ b/chromium/components/mirroring/service/session.h
@@ -6,9 +6,14 @@
#define COMPONENTS_MIRRORING_SERVICE_SESSION_H_
#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
#include "base/single_thread_task_runner.h"
#include "components/mirroring/service/interface.h"
+#include "components/mirroring/service/message_dispatcher.h"
+#include "components/mirroring/service/mirror_settings.h"
#include "components/mirroring/service/rtp_stream.h"
+#include "components/mirroring/service/session_monitor.h"
+#include "components/mirroring/service/wifi_status_monitor.h"
#include "media/cast/cast_environment.h"
#include "media/cast/net/cast_transport_defines.h"
@@ -22,20 +27,32 @@ class CastTransport;
namespace mirroring {
+struct ReceiverResponse;
class VideoCaptureClient;
-
+class SessionMonitor;
+
+// Controls a mirroring session, including audio/video capturing and Cast
+// Streaming. When constructed, it does OFFER/ANSWER exchange with the mirroring
+// receiver. Mirroring starts when the exchange succeeds and stops when this
+// class is destructed or error occurs. |observer| will get notified when status
+// changes. |outbound_channel| is responsible for sending messages to the
+// mirroring receiver through Cast Channel.
class Session final : public RtpStreamClient {
public:
- Session(SessionType session_type,
- const net::IPEndPoint& receiver_endpoint,
- SessionClient* client);
+ Session(int32_t session_id,
+ const CastSinkInfo& sink_info,
+ const gfx::Size& max_resolution,
+ SessionObserver* observer,
+ ResourceProvider* resource_provider,
+ CastMessageChannel* outbound_channel);
+ // TODO(xjz): Add mojom::CastMessageChannelRequest |inbound_channel| to
+ // receive inbound messages.
+
~Session() override;
// RtpStreamClient implemenation.
void OnError(const std::string& message) override;
void RequestRefreshFrame() override;
- media::VideoEncodeAccelerator::SupportedProfiles
- GetSupportedVideoEncodeAcceleratorProfiles() override;
void CreateVideoEncodeAccelerator(
const media::cast::ReceiveVideoEncodeAcceleratorCallback& callback)
override;
@@ -49,16 +66,23 @@ class Session final : public RtpStreamClient {
std::unique_ptr<std::vector<media::cast::FrameEvent>> frame_events,
std::unique_ptr<std::vector<media::cast::PacketEvent>> packet_events);
- private:
- // Callback when OFFER/ANSWER message exchange finishes. Starts a mirroing
- // session.
- void StartInternal(const net::IPEndPoint& receiver_endpoint,
- const media::cast::FrameSenderConfig& audio_config,
- const media::cast::FrameSenderConfig& video_config);
+ // Callback for ANSWER response. If the ANSWER is invalid, |observer_| will
+ // get notified with error, and session is stopped. Otherwise, capturing and
+ // streaming are started with the selected configs.
+ void OnAnswer(
+ const std::string& cast_mode,
+ const std::vector<media::cast::FrameSenderConfig>& audio_configs,
+ const std::vector<media::cast::FrameSenderConfig>& video_configs,
+ const ReceiverResponse& response);
+ // Called by |message_dispatcher_| when error occurs while parsing the
+ // responses.
+ void OnResponseParsingError(const std::string& error_message);
+
+ private:
void StopSession();
- // Notify |client_| that error occurred and close the session.
+ // Notify |observer_| that error occurred and close the session.
void ReportError(SessionError error);
// Callback by Audio/VideoSender to indicate encoder status change.
@@ -67,12 +91,26 @@ class Session final : public RtpStreamClient {
// Callback by media::cast::VideoSender to set a new target playout delay.
void SetTargetPlayoutDelay(base::TimeDelta playout_delay);
- // Callback by |start_timeout_timer_|.
- void OnOfferAnswerExchangeTimeout();
+ media::VideoEncodeAccelerator::SupportedProfiles GetSupportedVeaProfiles();
+
+ // Create and send OFFER message.
+ void CreateAndSendOffer();
- SessionClient* client_ = nullptr;
+ // Provided by Cast Media Route Provider (MRP).
+ const int32_t session_id_;
+ const CastSinkInfo sink_info_;
- // Create on StartInternal().
+ SessionObserver* observer_ = nullptr;
+ ResourceProvider* resource_provider_ = nullptr;
+ MirrorSettings mirror_settings_;
+
+ MessageDispatcher message_dispatcher_;
+
+ network::mojom::NetworkContextPtr network_context_;
+
+ base::Optional<SessionMonitor> session_monitor_;
+
+ // Created after OFFER/ANSWER exchange succeeds.
std::unique_ptr<AudioRtpStream> audio_stream_;
std::unique_ptr<VideoRtpStream> video_stream_;
std::unique_ptr<VideoCaptureClient> video_capture_client_;
@@ -81,9 +119,6 @@ class Session final : public RtpStreamClient {
scoped_refptr<base::SingleThreadTaskRunner> audio_encode_thread_ = nullptr;
scoped_refptr<base::SingleThreadTaskRunner> video_encode_thread_ = nullptr;
- // Fire if the OFFER/ANSWER exchange times out.
- base::OneShotTimer start_timeout_timer_;
-
base::WeakPtrFactory<Session> weak_factory_;
};
diff --git a/chromium/components/mirroring/service/session_monitor.cc b/chromium/components/mirroring/service/session_monitor.cc
new file mode 100644
index 00000000000..94e6d77438a
--- /dev/null
+++ b/chromium/components/mirroring/service/session_monitor.cc
@@ -0,0 +1,412 @@
+// Copyright 2018 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 "components/mirroring/service/session_monitor.h"
+
+#include <string>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/stl_util.h"
+#include "components/mirroring/service/value_util.h"
+#include "components/mirroring/service/wifi_status_monitor.h"
+#include "components/version_info/version_info.h"
+#include "media/cast/cast_environment.h"
+#include "media/cast/logging/log_serializer.h"
+#include "media/cast/logging/logging_defines.h"
+#include "media/cast/logging/proto/raw_events.pb.h"
+#include "media/cast/logging/raw_event_subscriber_bundle.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+
+namespace mirroring {
+
+namespace {
+
+// Interval between snapshots of Cast Streaming events/stats.
+constexpr base::TimeDelta kSnapshotInterval =
+ base::TimeDelta::FromMinutes(15); // Typical: 15 min → ~3 MB
+
+// The maximum number of bytes for receiver's setup info. 256kb should be more
+// than sufficient.
+constexpr int kMaxSetupResponseSizeBytes = 262144;
+
+// Returns the number of milliseconds elapsed since epoch.
+int32_t ToEpochTime(const base::Time& time) {
+ return (time - base::Time::UnixEpoch()).InMilliseconds();
+}
+
+// Helper to parse the response for receiver setup info and update the tags.
+bool AddReceiverSetupInfoTags(const std::string& response, base::Value* tags) {
+ DCHECK(tags);
+ std::unique_ptr<base::Value> value = base::JSONReader::Read(response);
+
+ std::string build_version;
+ bool is_connected = false;
+ bool is_on_ethernet = false;
+ bool has_update = false;
+ int32_t uptime_seconds = 0;
+
+ const bool result =
+ value && value->is_dict() &&
+ GetString(*value, "cast_build_revision", &build_version) &&
+ GetBool(*value, "connected", &is_connected) &&
+ GetBool(*value, "ethernet_connected", &is_on_ethernet) &&
+ GetBool(*value, "has_update", &has_update) &&
+ GetInt(*value, "uptime", &uptime_seconds);
+ if (result) {
+ tags->SetKey("receiverVersion", base::Value(build_version));
+ tags->SetKey("receiverConnected", base::Value(is_connected));
+ tags->SetKey("receiverOnEthernet", base::Value(is_on_ethernet));
+ tags->SetKey("receiverHasUpdatePending", base::Value(has_update));
+ tags->SetKey("receiverUptimeSeconds", base::Value(uptime_seconds));
+ }
+ return result;
+}
+
+const char* ToErrorMessage(SessionError error) {
+ switch (error) {
+ case ANSWER_TIME_OUT:
+ return "ANSWER response time out";
+ case ANSWER_NOT_OK:
+ return "Received an error ANSWER response";
+ case ANSWER_MISMATCHED_CAST_MODE:
+ return "Unexpected cast mode in ANSWER response.";
+ case ANSWER_MISMATCHED_SSRC_LENGTH:
+ return "sendIndexes.length != ssrcs.length in ANSWER";
+ case ANSWER_SELECT_MULTIPLE_AUDIO:
+ return "Receiver selected audio RTP stream twice in ANSWER";
+ case ANSWER_SELECT_MULTIPLE_VIDEO:
+ return "Receiver selected video RTP stream twice in ANSWER";
+ case ANSWER_SELECT_INVALID_INDEX:
+ return "Invalid indexes selected in ANSWER";
+ case ANSWER_NO_AUDIO_OR_VIDEO:
+ return "Incorrect ANSWER message: No audio or Video.";
+ case AUDIO_CAPTURE_ERROR:
+ return "Audio capture error";
+ case VIDEO_CAPTURE_ERROR:
+ return "Video capture error";
+ case RTP_STREAM_ERROR:
+ return "RTP stream error";
+ case ENCODING_ERROR:
+ return "Encoding status error";
+ case CAST_TRANSPORT_ERROR:
+ return "Transport error";
+ }
+ return "";
+}
+
+} // namespace
+
+SessionMonitor::SessionMonitor(
+ int max_retention_bytes,
+ const net::IPAddress& receiver_address,
+ base::Value session_tags,
+ network::mojom::URLLoaderFactoryPtr loader_factory,
+ std::unique_ptr<WifiStatusMonitor> wifi_status_monitor)
+ : max_retention_bytes_(max_retention_bytes),
+ receiver_address_(receiver_address),
+ session_tags_(std::move(session_tags)),
+ url_loader_factory_(std::move(loader_factory)),
+ wifi_status_monitor_(std::move(wifi_status_monitor)),
+ stored_snapshots_bytes_(0),
+ weak_factory_(this) {}
+
+SessionMonitor::~SessionMonitor() {}
+
+void SessionMonitor::StartStreamingSession(
+ scoped_refptr<media::cast::CastEnvironment> cast_environment,
+ SessionType session_type,
+ bool is_remoting) {
+ DCHECK(!event_subscribers_);
+ DCHECK(!snapshot_timer_.IsRunning());
+
+ std::string session_activity =
+ session_type == AUDIO_AND_VIDEO
+ ? "audio+video"
+ : session_type == AUDIO_ONLY ? "audio-only" : "video-only";
+ session_activity += is_remoting ? " remoting" : " streaming";
+ session_tags_.SetKey("activity", base::Value(session_activity));
+
+ // Query the receiver setup info at the beginning of each streaming session.
+ QueryReceiverSetupInfo();
+
+ // Start collecting Cast Streaming events/stats.
+ event_subscribers_ = std::make_unique<media::cast::RawEventSubscriberBundle>(
+ std::move(cast_environment));
+ if (session_type != VIDEO_ONLY)
+ event_subscribers_->AddEventSubscribers(true /* is_audio */);
+ if (session_type != AUDIO_ONLY)
+ event_subscribers_->AddEventSubscribers(false /* is_audio */);
+
+ // Start periodically snapshotting Cast Streaming events/stats.
+ snapshot_timer_.Start(FROM_HERE, kSnapshotInterval,
+ base::BindRepeating(&SessionMonitor::TakeSnapshot,
+ base::Unretained(this)));
+
+ start_time_ = base::Time::Now();
+}
+
+void SessionMonitor::StopStreamingSession() {
+ if (snapshot_timer_.IsRunning()) {
+ snapshot_timer_.Stop();
+ TakeSnapshot(); // Final snapshot of this streaming session.
+ }
+ event_subscribers_.reset();
+}
+
+void SessionMonitor::OnStreamingError(SessionError error) {
+ DVLOG(2) << error;
+
+ if (!snapshot_timer_.IsRunning())
+ return; // Ignore errors before streaming starts.
+ // If the error has already been recorded, do not overwrite it with another
+ // since the first will usually be the most indicative of the problem.
+ if (error_.has_value())
+ return;
+ error_time_ = base::Time::Now();
+ error_.emplace(error);
+}
+
+std::vector<SessionMonitor::EventsAndStats>
+SessionMonitor::AssembleBundlesAndClear(
+ const std::vector<int32_t>& bundle_sizes) {
+ std::vector<EventsAndStats> bundles;
+ // If a streaming session is currently active, take a snapshot now so that all
+ // data collected since the last automatic periodic snapshot is included in
+ // the bundle.
+ if (snapshot_timer_.IsRunning()) {
+ TakeSnapshot();
+ snapshot_timer_.Reset();
+ }
+
+ for (int32_t max_bytes : bundle_sizes)
+ bundles.emplace_back(MakeSliceOfSnapshots(max_bytes));
+ snapshots_.clear();
+ stored_snapshots_bytes_ = 0;
+ return bundles;
+}
+
+SessionMonitor::EventsAndStats SessionMonitor::MakeSliceOfSnapshots(
+ int32_t max_bytes) {
+ // Immediately subtract two bytes for array brackets ("[]") since
+ // AssembleSnapshotsAndClear() will produce a JSON array of each snapshot's
+ // stats JSON.
+ max_bytes -= 2;
+ base::circular_deque<EventsAndStats> slice;
+ for (int i = snapshots_.size() - 1; i >= 0; --i) {
+ max_bytes -= snapshots_[i].second.length() + 1 /* size of the comma */;
+ // If insufficient bytes remain to retain the current stats JSON, stop
+ // adding more Snapshots to the slice.
+ if (max_bytes < 0)
+ break;
+ slice.emplace_front(std::make_pair("", snapshots_[i].second));
+ // If sufficient bytes remain to include the current events Blob, add it to
+ // the slice.
+ if (!snapshots_[i].first.empty()) {
+ const int32_t events_size = snapshots_[i].first.length();
+ if (max_bytes >= events_size) {
+ slice[0].first = snapshots_[i].first;
+ max_bytes -= events_size;
+ }
+ }
+ }
+
+ EventsAndStats bundle;
+ if (slice.empty())
+ return bundle;
+
+ bundle.second = "[";
+ for (size_t i = 0; i < slice.size(); i++) {
+ // To produce a single events gzipped-data Blob, simply concatenate the
+ // individual gzipped-data Blobs. The spec for gzip explicitly allows for
+ // this. :-)
+ bundle.first += slice[i].first;
+ // To produce the JSON stats array, concatenate the mix of string and Blob
+ // objects to produce a single UTF-8 encoded string.
+ if (i > 0)
+ bundle.second += ",";
+ bundle.second += slice[i].second;
+ }
+ bundle.second += "]";
+
+ return bundle;
+}
+
+void SessionMonitor::TakeSnapshot() {
+ // Session-level tags.
+ base::Value tags = session_tags_.Clone();
+
+ // Add snapshot-level tags.
+ tags.SetKey("startTime", base::Value(ToEpochTime(start_time_)));
+ const base::Time end_time = base::Time::Now();
+ tags.SetKey("endTime", base::Value(ToEpochTime(end_time)));
+ start_time_ = end_time;
+
+ if (wifi_status_monitor_) {
+ const std::vector<WifiStatus> wifi_status =
+ wifi_status_monitor_->GetRecentValues();
+ base::Value::ListStorage wifi_status_list;
+ for (const auto& status : wifi_status) {
+ base::Value status_value(base::Value::Type::DICTIONARY);
+ status_value.SetKey("wifiSnr", base::Value(status.snr));
+ status_value.SetKey("wifiSpeed", base::Value(status.speed));
+ status_value.SetKey("timestamp",
+ base::Value(ToEpochTime(status.timestamp)));
+ wifi_status_list.emplace_back(std::move(status_value));
+ }
+ tags.SetKey("receiverWifiStatus", base::Value(wifi_status_list));
+ }
+
+ // Streaming error tags (if any).
+ if (error_.has_value()) {
+ tags.SetKey("streamingErrorTime", base::Value(ToEpochTime(error_time_)));
+ tags.SetKey("streamingErrorMessage",
+ base::Value(ToErrorMessage(error_.value())));
+ error_.reset();
+ }
+
+ std::string tags_string;
+ base::JSONWriter::Write(tags, &tags_string);
+
+ // Collect raw events.
+ std::string events = GetEventLogsAndReset(true, tags_string) +
+ GetEventLogsAndReset(false, tags_string);
+
+ // Collect stats.
+ std::unique_ptr<base::DictionaryValue> audio_stats =
+ base::DictionaryValue::From(GetStatsAndReset(true));
+ std::unique_ptr<base::DictionaryValue> video_stats =
+ base::DictionaryValue::From(GetStatsAndReset(false));
+ base::DictionaryValue stats;
+ if (audio_stats)
+ stats.MergeDictionary(audio_stats.get());
+ if (video_stats)
+ stats.MergeDictionary(video_stats.get());
+ stats.SetKey("tags", std::move(tags));
+ std::string stats_string;
+ base::JSONWriter::Write(stats, &stats_string);
+
+ int snapshots_bytes =
+ stored_snapshots_bytes_ + events.size() + stats_string.size();
+ // Prune |snapshots_| if necessary.
+ while (snapshots_bytes > max_retention_bytes_) {
+ snapshots_bytes -= snapshots_[0].first.size();
+ snapshots_[0].first = std::string();
+ if (snapshots_bytes <= max_retention_bytes_)
+ break;
+ snapshots_bytes -= snapshots_[0].second.size();
+ snapshots_.pop_front();
+ }
+ snapshots_.emplace_back(std::make_pair(events, stats_string));
+ stored_snapshots_bytes_ = snapshots_bytes;
+}
+
+std::string SessionMonitor::GetEventLogsAndReset(
+ bool is_audio,
+ const std::string& extra_data) {
+ std::string result;
+ if (!event_subscribers_.get())
+ return result;
+
+ media::cast::EncodingEventSubscriber* subscriber =
+ event_subscribers_->GetEncodingEventSubscriber(is_audio);
+ if (!subscriber)
+ return result;
+
+ media::cast::proto::LogMetadata metadata;
+ media::cast::FrameEventList frame_events;
+ media::cast::PacketEventList packet_events;
+
+ subscriber->GetEventsAndReset(&metadata, &frame_events, &packet_events);
+
+ if (!extra_data.empty())
+ metadata.set_extra_data(extra_data);
+ media::cast::proto::GeneralDescription* gen_desc =
+ metadata.mutable_general_description();
+ gen_desc->set_product(version_info::GetProductName());
+ gen_desc->set_product_version(version_info::GetVersionNumber());
+ gen_desc->set_os(version_info::GetOSType());
+
+ result.resize(media::cast::kMaxSerializedBytes);
+ int output_bytes;
+ // TODO(xjz): media::cast::SerializeEvents() shouldn't require the caller to
+ // pre-allocate the memory. It should return a string result.
+ if (media::cast::SerializeEvents(metadata, frame_events, packet_events,
+ true /* compress */,
+ media::cast::kMaxSerializedBytes,
+ base::data(result), &output_bytes)) {
+ result.resize(output_bytes);
+ } else {
+ result.clear();
+ }
+ return result;
+}
+
+std::unique_ptr<base::Value> SessionMonitor::GetStatsAndReset(bool is_audio) {
+ if (!event_subscribers_.get())
+ return nullptr;
+
+ media::cast::StatsEventSubscriber* subscriber =
+ event_subscribers_->GetStatsEventSubscriber(is_audio);
+ if (!subscriber)
+ return nullptr;
+
+ std::unique_ptr<base::Value> stats = subscriber->GetStats();
+ subscriber->Reset();
+ return stats;
+}
+
+void SessionMonitor::QueryReceiverSetupInfo() {
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->method = "GET";
+ resource_request->url = GURL("http://" + receiver_address_.ToString() +
+ ":8008/setup/eureka_info");
+ net::NetworkTrafficAnnotationTag traffic_annotation =
+ net::DefineNetworkTrafficAnnotation("mirroring_get_setup_info", R"(
+ semantics {
+ sender: "Mirroring Service"
+ description:
+ "Mirroring Service sends a request to the receiver to obtain its "
+ "setup info such as the build version, the model name, etc. The "
+ "data is included in mirroring feedback for analysis."
+ trigger:
+ "A tab/desktop mirroring session starts."
+ data: "An HTTP GET request."
+ destination: OTHER
+ destination_other:
+ "A mirroring receiver, such as a ChromeCast device."
+ }
+ policy {
+ cookies_allowed: NO
+ setting: "This feature cannot be disabled in settings."
+ chrome_policy {
+ EnableMediaRouter {
+ EnableMediaRouter: false
+ }
+ }
+ })");
+ std::unique_ptr<network::SimpleURLLoader> url_loader =
+ network::SimpleURLLoader::Create(std::move(resource_request),
+ traffic_annotation);
+ network::SimpleURLLoader* url_loader_ptr = url_loader.get();
+ url_loader_ptr->DownloadToString(
+ url_loader_factory_.get(),
+ base::BindOnce(
+ [](base::WeakPtr<SessionMonitor> monitor,
+ std::unique_ptr<network::SimpleURLLoader> url_loader,
+ std::unique_ptr<std::string> response) {
+ if (monitor) {
+ if (url_loader->NetError() != net::OK ||
+ !AddReceiverSetupInfoTags(*response, &monitor->session_tags_))
+ VLOG(2) << "Unable to fetch/parse receiver setup info.";
+ }
+ },
+ weak_factory_.GetWeakPtr(), std::move(url_loader)),
+ kMaxSetupResponseSizeBytes);
+}
+
+} // namespace mirroring
diff --git a/chromium/components/mirroring/service/session_monitor.h b/chromium/components/mirroring/service/session_monitor.h
new file mode 100644
index 00000000000..ba4892345c2
--- /dev/null
+++ b/chromium/components/mirroring/service/session_monitor.h
@@ -0,0 +1,142 @@
+// Copyright 2018 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.
+
+#ifndef COMPONENTS_MIRRORING_SERVICE_SESSION_MONITOR_H_
+#define COMPONENTS_MIRRORING_SERVICE_SESSION_MONITOR_H_
+
+#include "components/mirroring/service/interface.h"
+
+#include <memory>
+#include <string>
+
+#include "base/containers/circular_deque.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/timer/timer.h"
+#include "base/values.h"
+#include "components/mirroring/service/interface.h"
+#include "services/network/public/mojom/url_loader_factory.mojom.h"
+
+namespace media {
+namespace cast {
+class CastEnvironment;
+class RawEventSubscriberBundle;
+} // namespace cast
+} // namespace media
+
+namespace mirroring {
+
+class WifiStatusMonitor;
+
+// Monitors a single mirroring session's multiple Cast Streaming "subsessions",
+// collecting and managing the following information:
+//
+// 1. WiFi signal strength, SNR, etc. on the connection between sender and
+// receiver.
+// 2. Extra "tags": Information about both the sender and receiver, such as
+// software versions, mirroring settings, and network configuration.
+// 3. Snapshots of Cast Streaming event logs (frame- and packet-level events
+// that will allow an analysis of the data flows and a diagnosing of any
+// issues occurring in-the-wild). Start/StopStreamingSession() are called
+// to notify this monitor whenever each (of multiple) Cast Streaming
+// sessions starts and ends.
+//
+// Either during or at the end of a session, AssembleBundlesAndClear() is called
+// to re-package all of the information across multiple snapshots together into
+// one bundle, whose blobs can then be included in user feedback report uploads.
+//
+// To avoid unbounded memory use, older data is discarded automatically if too
+// much is accumulating.
+class SessionMonitor {
+ public:
+ using EventsAndStats =
+ std::pair<std::string /* events */, std::string /* stats */>;
+ SessionMonitor(int max_retention_bytes,
+ const net::IPAddress& receiver_address,
+ base::Value session_tags,
+ network::mojom::URLLoaderFactoryPtr loader_factory,
+ std::unique_ptr<WifiStatusMonitor> wifi_status_monitor);
+
+ ~SessionMonitor();
+
+ enum SessionType {
+ AUDIO_ONLY,
+ VIDEO_ONLY,
+ AUDIO_AND_VIDEO,
+ };
+
+ // Notifies this monitor that it may now start/stop monitoring Cast Streaming
+ // events/stats.
+ void StartStreamingSession(
+ scoped_refptr<media::cast::CastEnvironment> cast_environment,
+ SessionType session_type,
+ bool is_remoting);
+ void StopStreamingSession();
+
+ // Called when error occurs. Only records the first error since last snapshot.
+ void OnStreamingError(SessionError error);
+
+ // Assembles one or more bundles of data, for inclusion in user feedback
+ // reports. The snapshot history is cleared each time this method is called,
+ // and so no two calls to this method will return the same data. The caller
+ // may request that multiple bundles be produced. This is used, for example,
+ // to get one bundle that meets the upload size maximum in addition to
+ // another. The total data size in bytes is strictly less-than-or-equal to
+ // |bundle_sizes|.
+ std::vector<EventsAndStats> AssembleBundlesAndClear(
+ const std::vector<int32_t>& bundle_sizes);
+
+ // Takes a snapshot of recent Cast Streaming events and statistics.
+ void TakeSnapshot();
+
+ private:
+ // Query the receiver for its current setup and uptime.
+ void QueryReceiverSetupInfo();
+
+ // Get Cast Streaming events/stats.
+ std::string GetEventLogsAndReset(bool is_audio,
+ const std::string& extra_data);
+ std::unique_ptr<base::Value> GetStatsAndReset(bool is_audio);
+
+ // Assemble the most-recent events+stats snapshots to a bundle of a byte size
+ // less than or equal to |max_bytes|.
+ EventsAndStats MakeSliceOfSnapshots(int32_t max_bytes);
+
+ const int max_retention_bytes_; // Maximum number of bytes to keep.
+
+ const net::IPAddress receiver_address_;
+
+ base::Value session_tags_; // Streaming session-level tags.
+
+ network::mojom::URLLoaderFactoryPtr url_loader_factory_;
+
+ // Monitors the WiFi status if not null.
+ const std::unique_ptr<WifiStatusMonitor> wifi_status_monitor_;
+
+ std::unique_ptr<media::cast::RawEventSubscriberBundle> event_subscribers_;
+
+ base::RepeatingTimer snapshot_timer_;
+
+ // The time that the current snapshot starts.
+ base::Time start_time_;
+
+ // The most recent snapshots, from oldest to newest. The total size of the
+ // data stored in this list is bounded by |max_retention_bytes_|.
+ base::circular_deque<EventsAndStats> snapshots_;
+
+ // The number of bytes currently stored in |snapshots_|.
+ int stored_snapshots_bytes_;
+
+ base::Time error_time_;
+ base::Optional<SessionError> error_;
+
+ base::WeakPtrFactory<SessionMonitor> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionMonitor);
+};
+
+} // namespace mirroring
+
+#endif // COMPONENTS_MIRRORING_SERVICE_SESSION_MONITOR_H_
diff --git a/chromium/components/mirroring/service/session_monitor_unittest.cc b/chromium/components/mirroring/service/session_monitor_unittest.cc
new file mode 100644
index 00000000000..c887eaa0a9f
--- /dev/null
+++ b/chromium/components/mirroring/service/session_monitor_unittest.cc
@@ -0,0 +1,397 @@
+// Copyright 2018 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 "components/mirroring/service/session_monitor.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/macros.h"
+#include "base/test/mock_callback.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/time/default_tick_clock.h"
+#include "components/mirroring/service/message_dispatcher.h"
+#include "components/mirroring/service/mirror_settings.h"
+#include "components/mirroring/service/value_util.h"
+#include "components/mirroring/service/wifi_status_monitor.h"
+#include "media/cast/cast_environment.h"
+#include "media/cast/test/utility/net_utility.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "net/base/ip_endpoint.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mirroring {
+
+namespace {
+
+constexpr int kRetentionBytes = 512 * 1024; // 512k.
+
+void VerifyStringValue(const base::Value& raw_value,
+ const std::string& key,
+ const std::string& expected_value) {
+ std::string data;
+ EXPECT_TRUE(GetString(raw_value, key, &data));
+ EXPECT_EQ(expected_value, data);
+}
+
+void VerifyBoolValue(const base::Value& raw_value,
+ const std::string& key,
+ bool expected_value) {
+ bool data;
+ EXPECT_TRUE(GetBool(raw_value, key, &data));
+ EXPECT_EQ(expected_value, data);
+}
+
+void VerifyIntValue(const base::Value& raw_value,
+ const std::string& key,
+ int32_t expected_value) {
+ int32_t data;
+ EXPECT_TRUE(GetInt(raw_value, key, &data));
+ EXPECT_EQ(expected_value, data);
+}
+
+void VerifyWifiStatus(const base::Value& raw_value,
+ double starting_snr,
+ int starting_speed,
+ int num_of_status) {
+ EXPECT_TRUE(raw_value.is_dict());
+ auto* found = raw_value.FindKey("tags");
+ EXPECT_TRUE(found && found->is_dict());
+ auto* wifi_status = found->FindKey("receiverWifiStatus");
+ EXPECT_TRUE(wifi_status && wifi_status->is_list());
+ const base::Value::ListStorage& status_list = wifi_status->GetList();
+ EXPECT_EQ(num_of_status, static_cast<int>(status_list.size()));
+ for (int i = 0; i < num_of_status; ++i) {
+ double snr = -1;
+ int32_t speed = -1;
+ int32_t timestamp = 0;
+ EXPECT_TRUE(GetDouble(status_list[i], "wifiSnr", &snr));
+ EXPECT_EQ(starting_snr + i, snr);
+ EXPECT_TRUE(GetInt(status_list[i], "wifiSpeed", &speed));
+ EXPECT_EQ(starting_speed + i, speed);
+ EXPECT_TRUE(GetInt(status_list[i], "timestamp", &timestamp));
+ }
+}
+
+} // namespace
+
+class SessionMonitorTest : public CastMessageChannel, public ::testing::Test {
+ public:
+ SessionMonitorTest()
+ : receiver_address_(media::cast::test::GetFreeLocalPort().address()),
+ message_dispatcher_(this, error_callback_.Get()) {}
+ ~SessionMonitorTest() override {}
+
+ protected:
+ // CastMessageChannel implementation.
+ MOCK_METHOD1(Send, void(const CastMessage&));
+
+ void CreateSessionMonitor(int max_bytes, std::string* expected_settings) {
+ EXPECT_CALL(*this, Send(::testing::_)).Times(::testing::AtLeast(1));
+ auto wifi_status_monitor =
+ std::make_unique<WifiStatusMonitor>(123, &message_dispatcher_);
+ network::mojom::URLLoaderFactoryPtr url_loader_factory;
+ auto test_url_loader_factory =
+ std::make_unique<network::TestURLLoaderFactory>();
+ url_loader_factory_ = test_url_loader_factory.get();
+ mojo::MakeStrongBinding(std::move(test_url_loader_factory),
+ mojo::MakeRequest(&url_loader_factory));
+ MirrorSettings mirror_settings;
+ base::Value session_tags(base::Value::Type::DICTIONARY);
+ base::Value settings = mirror_settings.ToDictionaryValue();
+ if (expected_settings)
+ EXPECT_TRUE(base::JSONWriter::Write(settings, expected_settings));
+ session_tags.SetKey("mirrorSettings", std::move(settings));
+ session_tags.SetKey("receiverProductName", base::Value("ChromeCast"));
+ session_tags.SetKey("shouldCaptureAudio", base::Value(true));
+ session_tags.SetKey("shouldCaptureVideo", base::Value(true));
+ session_monitor_ = std::make_unique<SessionMonitor>(
+ max_bytes, receiver_address_, std::move(session_tags),
+ std::move(url_loader_factory), std::move(wifi_status_monitor));
+ }
+
+ // Generates and sends |num_of_responses| WiFi status.
+ void SendWifiStatus(double starting_snr,
+ int starting_speed,
+ int num_of_responses) {
+ for (int i = 0; i < num_of_responses; ++i) {
+ CastMessage message;
+ message.message_namespace = kWebRtcNamespace;
+ message.json_format_data =
+ "{\"seqNum\":" +
+ std::to_string(message_dispatcher_.GetNextSeqNumber()) +
+ ","
+ "\"type\": \"STATUS_RESPONSE\","
+ "\"result\": \"ok\","
+ "\"status\": {"
+ "\"wifiSnr\":" +
+ std::to_string(starting_snr + i) +
+ ","
+ "\"wifiSpeed\": [1234, 5678, 3000, " +
+ std::to_string(starting_speed + i) +
+ "],"
+ "\"wifiFcsError\": [12, 13, 12, 12]}" // This will be ignored.
+ "}";
+ static_cast<CastMessageChannel*>(&message_dispatcher_)->Send(message);
+ scoped_task_environment_.RunUntilIdle();
+ }
+ }
+
+ void StartStreamingSession() {
+ cast_environment_ = new media::cast::CastEnvironment(
+ base::DefaultTickClock::GetInstance(),
+ scoped_task_environment_.GetMainThreadTaskRunner(),
+ scoped_task_environment_.GetMainThreadTaskRunner(),
+ scoped_task_environment_.GetMainThreadTaskRunner());
+ EXPECT_TRUE(session_monitor_);
+ session_monitor_->StartStreamingSession(cast_environment_,
+ SessionMonitor::AUDIO_AND_VIDEO,
+ false /* is_remoting */);
+ scoped_task_environment_.RunUntilIdle();
+ }
+
+ void StopStreamingSession() {
+ EXPECT_TRUE(session_monitor_);
+ session_monitor_->StopStreamingSession();
+ cast_environment_ = nullptr;
+ scoped_task_environment_.RunUntilIdle();
+ }
+
+ std::vector<SessionMonitor::EventsAndStats> AssembleBundleAndVerify(
+ const std::vector<int32_t>& bundle_sizes) {
+ std::vector<SessionMonitor::EventsAndStats> bundles =
+ session_monitor_->AssembleBundlesAndClear(bundle_sizes);
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_EQ(bundle_sizes.size(), bundles.size());
+ for (size_t i = 0; i < bundles.size(); ++i) {
+ EXPECT_FALSE(bundles[i].first.empty());
+ EXPECT_FALSE(bundles[i].second.empty());
+ EXPECT_LE(
+ static_cast<int>(bundles[i].first.size() + bundles[i].second.size()),
+ bundle_sizes[i]);
+ }
+ return bundles;
+ }
+
+ base::Value ReadStats(const std::string& stats_string) {
+ std::unique_ptr<base::Value> stats_ptr =
+ base::JSONReader::Read(stats_string);
+ EXPECT_TRUE(stats_ptr);
+ base::Value stats = base::Value::FromUniquePtrValue(std::move(stats_ptr));
+ EXPECT_TRUE(stats.is_list());
+ return stats;
+ }
+
+ void SendReceiverSetupInfo(const std::string& setup_info) {
+ url_loader_factory_->AddResponse(
+ "http://" + receiver_address_.ToString() + ":8008/setup/eureka_info",
+ setup_info);
+ scoped_task_environment_.RunUntilIdle();
+ }
+
+ void TakeSnapshot() {
+ ASSERT_TRUE(session_monitor_);
+ session_monitor_->TakeSnapshot();
+ scoped_task_environment_.RunUntilIdle();
+ }
+
+ void ReportError(SessionError error) {
+ ASSERT_TRUE(session_monitor_);
+ session_monitor_->OnStreamingError(error);
+ scoped_task_environment_.RunUntilIdle();
+ }
+
+ private:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ const net::IPAddress receiver_address_;
+ base::MockCallback<MessageDispatcher::ErrorCallback> error_callback_;
+ MessageDispatcher message_dispatcher_;
+ network::TestURLLoaderFactory* url_loader_factory_ = nullptr;
+ std::unique_ptr<SessionMonitor> session_monitor_;
+ scoped_refptr<media::cast::CastEnvironment> cast_environment_ = nullptr;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionMonitorTest);
+};
+
+TEST_F(SessionMonitorTest, ProvidesExpectedTags) {
+ std::string expected_settings;
+ CreateSessionMonitor(kRetentionBytes, &expected_settings);
+ SendWifiStatus(34, 2000, 5);
+ StartStreamingSession();
+ std::vector<int32_t> bundle_sizes({kRetentionBytes});
+ std::vector<SessionMonitor::EventsAndStats> bundles =
+ AssembleBundleAndVerify(bundle_sizes);
+
+ base::Value stats = ReadStats(bundles[0].second);
+ const base::Value::ListStorage& stats_list = stats.GetList();
+ ASSERT_EQ(1u, stats_list.size());
+ // Verify tags.
+ EXPECT_TRUE(stats_list[0].is_dict());
+ auto* found = stats_list[0].FindKey("video");
+ EXPECT_TRUE(found && found->is_dict());
+ found = stats_list[0].FindKey("audio");
+ EXPECT_TRUE(found && found->is_dict());
+ found = stats_list[0].FindKey("tags");
+ EXPECT_TRUE(found && found->is_dict());
+ // Verify session tags.
+ VerifyStringValue(*found, "activity", "audio+video streaming");
+ VerifyStringValue(*found, "receiverProductName", "ChromeCast");
+ VerifyBoolValue(*found, "shouldCaptureAudio", true);
+ VerifyBoolValue(*found, "shouldCaptureVideo", true);
+ auto* settings = found->FindKey("mirrorSettings");
+ EXPECT_TRUE(settings && settings->is_dict());
+ std::string settings_string;
+ EXPECT_TRUE(base::JSONWriter::Write(*settings, &settings_string));
+ EXPECT_EQ(expected_settings, settings_string);
+ VerifyWifiStatus(stats_list[0], 34, 2000, 5);
+}
+
+// Test for multiple streaming sessions.
+TEST_F(SessionMonitorTest, MultipleSessions) {
+ CreateSessionMonitor(kRetentionBytes, nullptr);
+ StartStreamingSession();
+ StopStreamingSession();
+ // Starts the second streaming session.
+ StartStreamingSession();
+ StopStreamingSession();
+ std::vector<int32_t> bundle_sizes({kRetentionBytes});
+ std::vector<SessionMonitor::EventsAndStats> bundles =
+ AssembleBundleAndVerify(bundle_sizes);
+ base::Value stats = ReadStats(bundles[0].second);
+ const base::Value::ListStorage& stats_list = stats.GetList();
+ // There should be two sessions in the recorded stats.
+ EXPECT_EQ(2u, stats_list.size());
+}
+
+TEST_F(SessionMonitorTest, ConfigureMaxRetentionBytes) {
+ // 2500 is an estimate number of bytes for a snapshot that includes tags and
+ // five WiFi status records.
+ CreateSessionMonitor(2500, nullptr);
+ SendWifiStatus(34, 2000, 5);
+ StartStreamingSession();
+ StopStreamingSession();
+ SendWifiStatus(54, 3000, 5);
+ StartStreamingSession();
+ StopStreamingSession();
+ std::vector<int32_t> bundle_sizes({kRetentionBytes});
+ std::vector<SessionMonitor::EventsAndStats> bundles =
+ AssembleBundleAndVerify(bundle_sizes);
+ base::Value stats = ReadStats(bundles[0].second);
+ const base::Value::ListStorage& stats_list = stats.GetList();
+ // Expect to only record the second session.
+ ASSERT_EQ(1u, stats_list.size());
+ VerifyWifiStatus(stats_list[0], 54, 3000, 5);
+}
+
+TEST_F(SessionMonitorTest, AssembleBundlesWithVaryingSizes) {
+ CreateSessionMonitor(kRetentionBytes, nullptr);
+ SendWifiStatus(34, 2000, 5);
+ StartStreamingSession();
+ StopStreamingSession();
+ SendWifiStatus(54, 3000, 5);
+ StartStreamingSession();
+ StopStreamingSession();
+ std::vector<int32_t> bundle_sizes({2500, kRetentionBytes});
+ std::vector<SessionMonitor::EventsAndStats> bundles =
+ AssembleBundleAndVerify(bundle_sizes);
+
+ // Expect the first bundle has only one session.
+ base::Value stats = ReadStats(bundles[0].second);
+ const base::Value::ListStorage& stats_list = stats.GetList();
+ // Expect to only record the second session.
+ ASSERT_EQ(1u, stats_list.size());
+ VerifyWifiStatus(stats_list[0], 54, 3000, 5);
+
+ // Expect the second bundle has both sessions.
+ stats = ReadStats(bundles[1].second);
+ const base::Value::ListStorage& stats_list2 = stats.GetList();
+ ASSERT_EQ(2u, stats_list2.size());
+ VerifyWifiStatus(stats_list2[0], 34, 2000, 5);
+ VerifyWifiStatus(stats_list2[1], 54, 3000, 5);
+}
+
+TEST_F(SessionMonitorTest, ErrorTags) {
+ CreateSessionMonitor(kRetentionBytes, nullptr);
+ StartStreamingSession();
+ TakeSnapshot(); // Take the first snapshot.
+ ReportError(VIDEO_CAPTURE_ERROR);
+ ReportError(RTP_STREAM_ERROR);
+ TakeSnapshot(); // Take the second snapshot.
+ StopStreamingSession(); // Take the third snapshot.
+
+ std::vector<int32_t> bundle_sizes({kRetentionBytes});
+ std::vector<SessionMonitor::EventsAndStats> bundles =
+ AssembleBundleAndVerify(bundle_sizes);
+ base::Value stats = ReadStats(bundles[0].second);
+ const base::Value::ListStorage& stats_list = stats.GetList();
+ // There should be three snapshots in the bundle.
+ ASSERT_EQ(3u, stats_list.size());
+
+ // The first and the third snapshots should have no error tags.
+ auto* tags = stats_list[0].FindKey("tags");
+ ASSERT_TRUE(tags);
+ EXPECT_FALSE(tags->FindKey("streamingErrorTime"));
+ EXPECT_FALSE(tags->FindKey("streamingErrorMessage"));
+ tags = stats_list[2].FindKey("tags");
+ ASSERT_TRUE(tags);
+ EXPECT_FALSE(tags->FindKey("streamingErrorTime"));
+ EXPECT_FALSE(tags->FindKey("streamingErrorMessage"));
+
+ // The second snapshot should have the error tags. Only the first error is
+ // recorded.
+ tags = stats_list[1].FindKey("tags");
+ ASSERT_TRUE(tags && tags->FindKey("streamingErrorTime"));
+ VerifyStringValue(*tags, "streamingErrorMessage", "Video capture error");
+}
+
+TEST_F(SessionMonitorTest, ReceiverSetupInfo) {
+ CreateSessionMonitor(kRetentionBytes, nullptr);
+ StartStreamingSession();
+ // This snapshot should have no receiver setup info tags.
+ TakeSnapshot();
+
+ const std::string receiver_setup_info =
+ "{"
+ "\"cast_build_revision\": \"1.26.0.1\","
+ "\"connected\": true,"
+ "\"ethernet_connected\": false,"
+ "\"has_update\": false,"
+ "\"uptime\": 132536 }";
+
+ SendReceiverSetupInfo(receiver_setup_info);
+
+ // A final snapshot is taken and should have receiver setup info tags.
+ StopStreamingSession();
+
+ std::vector<int32_t> bundle_sizes({kRetentionBytes});
+ std::vector<SessionMonitor::EventsAndStats> bundles =
+ AssembleBundleAndVerify(bundle_sizes);
+ base::Value stats = ReadStats(bundles[0].second);
+ const base::Value::ListStorage& stats_list = stats.GetList();
+ // There should be two snapshots in the bundle.
+ EXPECT_EQ(2u, stats_list.size());
+
+ // The first snapshot should have no receiver setup info tags.
+ auto* tags = stats_list[0].FindKey("tags");
+ ASSERT_TRUE(tags);
+ EXPECT_FALSE(tags->FindKey("receiverVersion"));
+ EXPECT_FALSE(tags->FindKey("receiverConnected"));
+ EXPECT_FALSE(tags->FindKey("receiverOnEthernet"));
+ EXPECT_FALSE(tags->FindKey("receiverHasUpdatePending"));
+ EXPECT_FALSE(tags->FindKey("receiverUptimeSeconds"));
+
+ // The second snapshot should have the receiver setup info tags.
+ tags = stats_list[1].FindKey("tags");
+ ASSERT_TRUE(tags);
+ VerifyStringValue(*tags, "receiverVersion", "1.26.0.1");
+ VerifyBoolValue(*tags, "receiverConnected", true);
+ VerifyBoolValue(*tags, "receiverOnEthernet", false);
+ VerifyBoolValue(*tags, "receiverHasUpdatePending", false);
+ VerifyIntValue(*tags, "receiverUptimeSeconds", 132536);
+}
+
+} // namespace mirroring
diff --git a/chromium/components/mirroring/service/session_unittest.cc b/chromium/components/mirroring/service/session_unittest.cc
index 751fa584225..973c0daed8a 100644
--- a/chromium/components/mirroring/service/session_unittest.cc
+++ b/chromium/components/mirroring/service/session_unittest.cc
@@ -6,17 +6,22 @@
#include "base/bind.h"
#include "base/callback.h"
+#include "base/json/json_reader.h"
#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/simple_test_tick_clock.h"
+#include "base/values.h"
#include "components/mirroring/service/fake_network_service.h"
#include "components/mirroring/service/fake_video_capture_host.h"
#include "components/mirroring/service/interface.h"
+#include "components/mirroring/service/mirror_settings.h"
+#include "components/mirroring/service/receiver_response.h"
+#include "components/mirroring/service/value_util.h"
#include "media/cast/test/utility/default_config.h"
#include "media/cast/test/utility/net_utility.h"
#include "mojo/public/cpp/bindings/binding.h"
+#include "net/base/ip_address.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -27,74 +32,125 @@ using media::cast::Packet;
namespace mirroring {
-class SessionTest : public SessionClient, public ::testing::Test {
+const int kSessionId = 5;
+
+class SessionTest : public ResourceProvider,
+ public SessionObserver,
+ public CastMessageChannel,
+ public ::testing::Test {
public:
- SessionTest() : weak_factory_(this) {
+ SessionTest() : receiver_endpoint_(media::cast::test::GetFreeLocalPort()) {
testing_clock_.Advance(base::TimeTicks::Now() - base::TimeTicks());
}
~SessionTest() override { scoped_task_environment_.RunUntilIdle(); }
- // SessionClient implemenation.
+ // SessionObserver implemenation.
MOCK_METHOD1(OnError, void(SessionError));
MOCK_METHOD0(DidStart, void());
MOCK_METHOD0(DidStop, void());
- MOCK_METHOD0(OnOfferAnswerExchange, void());
+
+ // ResourceProvider implemenation.
MOCK_METHOD0(OnGetVideoCaptureHost, void());
MOCK_METHOD0(OnGetNetworkContext, void());
+ // Called when sends OFFER message.
+ MOCK_METHOD0(OnOffer, void());
+
+ // CastMessageHandler implementation. For outbound messages.
+ void Send(const CastMessage& message) {
+ EXPECT_TRUE(message.message_namespace == kWebRtcNamespace ||
+ message.message_namespace == kRemotingNamespace);
+ std::unique_ptr<base::Value> value =
+ base::JSONReader::Read(message.json_format_data);
+ ASSERT_TRUE(value);
+ std::string message_type;
+ EXPECT_TRUE(GetString(*value, "type", &message_type));
+ if (message_type == "OFFER") {
+ EXPECT_TRUE(GetInt(*value, "seqNum", &offer_sequence_number_));
+ OnOffer();
+ }
+ }
+
void GetVideoCaptureHost(
media::mojom::VideoCaptureHostRequest request) override {
video_host_ = std::make_unique<FakeVideoCaptureHost>(std::move(request));
OnGetVideoCaptureHost();
}
- void GetNewWorkContext(
+ void GetNetworkContext(
network::mojom::NetworkContextRequest request) override {
network_context_ = std::make_unique<MockNetworkContext>(std::move(request));
OnGetNetworkContext();
}
- void DoOfferAnswerExchange(
- const std::vector<FrameSenderConfig>& audio_configs,
- const std::vector<FrameSenderConfig>& video_configs,
- GetAnswerCallback callback) override {
- OnOfferAnswerExchange();
- std::move(callback).Run(FrameSenderConfig(),
- media::cast::GetDefaultVideoSenderConfig());
+ void SendAnswer() {
+ FrameSenderConfig config = MirrorSettings::GetDefaultVideoConfig(
+ media::cast::RtpPayloadType::VIDEO_VP8,
+ media::cast::Codec::CODEC_VIDEO_VP8);
+ std::vector<FrameSenderConfig> video_configs;
+ video_configs.emplace_back(config);
+
+ auto answer = std::make_unique<Answer>();
+ answer->udp_port = receiver_endpoint_.port();
+ answer->send_indexes.push_back(0);
+ answer->ssrcs.push_back(32);
+ answer->cast_mode = "mirroring";
+
+ ReceiverResponse response;
+ response.result = "ok";
+ response.type = ResponseType::ANSWER;
+ response.sequence_number = offer_sequence_number_;
+ response.answer = std::move(answer);
+
+ session_->OnAnswer("mirroring", std::vector<FrameSenderConfig>(),
+ video_configs, response);
}
protected:
+ void CreateSession() {
+ CastSinkInfo sink_info;
+ sink_info.ip_address = receiver_endpoint_.address();
+ sink_info.capability = DeviceCapability::AUDIO_AND_VIDEO;
+ // Expect to receive OFFER message when session is created.
+ base::RunLoop run_loop;
+ EXPECT_CALL(*this, OnGetNetworkContext()).Times(1);
+ EXPECT_CALL(*this, OnError(_)).Times(0);
+ EXPECT_CALL(*this, OnOffer())
+ .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+ session_ = std::make_unique<Session>(
+ kSessionId, sink_info, gfx::Size(1920, 1080), this, this, this);
+ run_loop.Run();
+ }
+
base::test::ScopedTaskEnvironment scoped_task_environment_;
+ const net::IPEndPoint receiver_endpoint_;
base::SimpleTestTickClock testing_clock_;
std::unique_ptr<Session> session_;
std::unique_ptr<FakeVideoCaptureHost> video_host_;
std::unique_ptr<MockNetworkContext> network_context_;
- base::WeakPtrFactory<SessionTest> weak_factory_;
+ int32_t offer_sequence_number_ = -1;
private:
DISALLOW_COPY_AND_ASSIGN(SessionTest);
};
TEST_F(SessionTest, Mirroring) {
- // Start a mirroring session.
+ CreateSession();
+ scoped_task_environment_.RunUntilIdle();
{
+ // Except mirroing session starts after receiving ANSWER message.
base::RunLoop run_loop;
EXPECT_CALL(*this, OnGetVideoCaptureHost()).Times(1);
- EXPECT_CALL(*this, OnGetNetworkContext()).Times(1);
- EXPECT_CALL(*this, OnOfferAnswerExchange()).Times(1);
+ EXPECT_CALL(*this, OnError(_)).Times(0);
EXPECT_CALL(*this, DidStart())
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
- session_ =
- std::make_unique<Session>(SessionType::AUDIO_AND_VIDEO,
- media::cast::test::GetFreeLocalPort(), this);
+ SendAnswer();
run_loop.Run();
}
-
scoped_task_environment_.RunUntilIdle();
-
{
base::RunLoop run_loop;
// Expect to send out some UDP packets.
@@ -106,7 +162,6 @@ TEST_F(SessionTest, Mirroring) {
video_host_->SendOneFrame(gfx::Size(64, 32), testing_clock_.NowTicks());
run_loop.Run();
}
-
scoped_task_environment_.RunUntilIdle();
// Stop the session.
@@ -121,4 +176,21 @@ TEST_F(SessionTest, Mirroring) {
scoped_task_environment_.RunUntilIdle();
}
+TEST_F(SessionTest, AnswerTimeout) {
+ CreateSession();
+ scoped_task_environment_.RunUntilIdle();
+ {
+ // Expect error.
+ base::RunLoop run_loop;
+ EXPECT_CALL(*this, OnGetVideoCaptureHost()).Times(0);
+ EXPECT_CALL(*this, DidStop()).Times(1);
+ EXPECT_CALL(*this, OnError(ANSWER_TIME_OUT))
+ .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+ session_->OnAnswer("mirroring", std::vector<FrameSenderConfig>(),
+ std::vector<FrameSenderConfig>(), ReceiverResponse());
+ run_loop.Run();
+ }
+ scoped_task_environment_.RunUntilIdle();
+}
+
} // namespace mirroring
diff --git a/chromium/components/mirroring/service/udp_socket_client.cc b/chromium/components/mirroring/service/udp_socket_client.cc
index f7677692753..96923f88ee8 100644
--- a/chromium/components/mirroring/service/udp_socket_client.cc
+++ b/chromium/components/mirroring/service/udp_socket_client.cc
@@ -47,16 +47,18 @@ net::NetworkTrafficAnnotationTag GetNetworkTrafficAnnotationTag() {
} // namespace
UdpSocketClient::UdpSocketClient(const net::IPEndPoint& remote_endpoint,
- network::mojom::NetworkContextPtr context,
+ network::mojom::NetworkContext* context,
base::OnceClosure error_callback)
: remote_endpoint_(remote_endpoint),
- network_context_(std::move(context)),
+ network_context_(context),
error_callback_(std::move(error_callback)),
binding_(this),
bytes_sent_(0),
allow_sending_(false),
num_packets_pending_receive_(0),
- weak_factory_(this) {}
+ weak_factory_(this) {
+ DCHECK(network_context_);
+}
UdpSocketClient::~UdpSocketClient() {}
diff --git a/chromium/components/mirroring/service/udp_socket_client.h b/chromium/components/mirroring/service/udp_socket_client.h
index 2eda92f8d4d..c58a7ee3987 100644
--- a/chromium/components/mirroring/service/udp_socket_client.h
+++ b/chromium/components/mirroring/service/udp_socket_client.h
@@ -24,7 +24,7 @@ class UdpSocketClient final : public media::cast::PacketTransport,
public network::mojom::UDPSocketReceiver {
public:
UdpSocketClient(const net::IPEndPoint& remote_endpoint,
- network::mojom::NetworkContextPtr context,
+ network::mojom::NetworkContext* context,
base::OnceClosure error_callback);
~UdpSocketClient() override;
@@ -54,7 +54,7 @@ class UdpSocketClient final : public media::cast::PacketTransport,
const base::Optional<net::IPEndPoint>& addr);
const net::IPEndPoint remote_endpoint_;
- const network::mojom::NetworkContextPtr network_context_;
+ network::mojom::NetworkContext* const network_context_;
base::OnceClosure error_callback_;
mojo::Binding<network::mojom::UDPSocketReceiver> binding_;
diff --git a/chromium/components/mirroring/service/udp_socket_client_unittest.cc b/chromium/components/mirroring/service/udp_socket_client_unittest.cc
index 9ec51e25aa2..b88f2c7adfd 100644
--- a/chromium/components/mirroring/service/udp_socket_client_unittest.cc
+++ b/chromium/components/mirroring/service/udp_socket_client_unittest.cc
@@ -30,11 +30,10 @@ namespace mirroring {
class UdpSocketClientTest : public ::testing::Test {
public:
UdpSocketClientTest() {
- network::mojom::NetworkContextPtr network_context_ptr;
network_context_ = std::make_unique<MockNetworkContext>(
- mojo::MakeRequest(&network_context_ptr));
+ mojo::MakeRequest(&network_context_ptr_));
udp_transport_client_ = std::make_unique<UdpSocketClient>(
- media::cast::test::GetFreeLocalPort(), std::move(network_context_ptr),
+ media::cast::test::GetFreeLocalPort(), network_context_ptr_.get(),
base::OnceClosure());
}
@@ -49,6 +48,7 @@ class UdpSocketClientTest : public ::testing::Test {
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
+ network::mojom::NetworkContextPtr network_context_ptr_;
std::unique_ptr<MockNetworkContext> network_context_;
std::unique_ptr<UdpSocketClient> udp_transport_client_;
std::unique_ptr<Packet> received_packet_;
diff --git a/chromium/components/mirroring/service/value_util.cc b/chromium/components/mirroring/service/value_util.cc
new file mode 100644
index 00000000000..63eec3d9999
--- /dev/null
+++ b/chromium/components/mirroring/service/value_util.cc
@@ -0,0 +1,95 @@
+// Copyright 2018 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 "components/mirroring/service/value_util.h"
+
+namespace mirroring {
+
+bool GetInt(const base::Value& value, const std::string& key, int32_t* result) {
+ auto* found = value.FindKey(key);
+ if (!found || found->is_none())
+ return true;
+ if (found->is_int()) {
+ *result = found->GetInt();
+ return true;
+ }
+ return false;
+}
+
+bool GetDouble(const base::Value& value,
+ const std::string& key,
+ double* result) {
+ auto* found = value.FindKey(key);
+ if (!found || found->is_none())
+ return true;
+ if (found->is_double()) {
+ *result = found->GetDouble();
+ return true;
+ }
+ if (found->is_int()) {
+ *result = found->GetInt();
+ return true;
+ }
+ return false;
+}
+
+bool GetString(const base::Value& value,
+ const std::string& key,
+ std::string* result) {
+ auto* found = value.FindKey(key);
+ if (!found || found->is_none())
+ return true;
+ if (found->is_string()) {
+ *result = found->GetString();
+ return true;
+ }
+ return false;
+}
+
+bool GetBool(const base::Value& value, const std::string& key, bool* result) {
+ auto* found = value.FindKey(key);
+ if (!found || found->is_none())
+ return true;
+ if (found->is_bool()) {
+ *result = found->GetBool();
+ return true;
+ }
+ return false;
+}
+
+bool GetIntArray(const base::Value& value,
+ const std::string& key,
+ std::vector<int32_t>* result) {
+ auto* found = value.FindKey(key);
+ if (!found || found->is_none())
+ return true;
+ if (!found->is_list())
+ return false;
+ for (const auto& number_value : found->GetList()) {
+ if (number_value.is_int())
+ result->emplace_back(number_value.GetInt());
+ else
+ return false;
+ }
+ return true;
+}
+
+bool GetStringArray(const base::Value& value,
+ const std::string& key,
+ std::vector<std::string>* result) {
+ auto* found = value.FindKey(key);
+ if (!found || found->is_none())
+ return true;
+ if (!found->is_list())
+ return false;
+ for (const auto& string_value : found->GetList()) {
+ if (string_value.is_string())
+ result->emplace_back(string_value.GetString());
+ else
+ return false;
+ }
+ return true;
+}
+
+} // namespace mirroring
diff --git a/chromium/components/mirroring/service/value_util.h b/chromium/components/mirroring/service/value_util.h
new file mode 100644
index 00000000000..d3247747999
--- /dev/null
+++ b/chromium/components/mirroring/service/value_util.h
@@ -0,0 +1,39 @@
+// Copyright 2018 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.
+
+#ifndef COMPONENTS_MIRRORING_SERVICE_VALUE_UTIL_H_
+#define COMPONENTS_MIRRORING_SERVICE_VALUE_UTIL_H_
+
+#include <string>
+
+#include "base/values.h"
+
+namespace mirroring {
+
+// Read certain type of data from dictionary |value| if |key| exits. Return
+// false if |key| exists and the type of the data mismatches. Return true
+// otherwise.
+
+bool GetInt(const base::Value& value, const std::string& key, int32_t* result);
+
+bool GetDouble(const base::Value& value,
+ const std::string& key,
+ double* result);
+
+bool GetString(const base::Value& value,
+ const std::string& key,
+ std::string* result);
+
+bool GetBool(const base::Value& value, const std::string& key, bool* result);
+
+bool GetIntArray(const base::Value& value,
+ const std::string& key,
+ std::vector<int32_t>* result);
+
+bool GetStringArray(const base::Value& value,
+ const std::string& key,
+ std::vector<std::string>* result);
+} // namespace mirroring
+
+#endif // COMPONENTS_MIRRORING_SERVICE_VALUE_UTIL_H_
diff --git a/chromium/components/mirroring/service/video_capture_client.cc b/chromium/components/mirroring/service/video_capture_client.cc
index bbbd088f87f..24ef5467d77 100644
--- a/chromium/components/mirroring/service/video_capture_client.cc
+++ b/chromium/components/mirroring/service/video_capture_client.cc
@@ -14,8 +14,10 @@ namespace {
constexpr int kDeviceId = 0;
} // namespace
-VideoCaptureClient::VideoCaptureClient(media::mojom::VideoCaptureHostPtr host)
- : video_capture_host_(std::move(host)),
+VideoCaptureClient::VideoCaptureClient(const media::VideoCaptureParams& params,
+ media::mojom::VideoCaptureHostPtr host)
+ : params_(params),
+ video_capture_host_(std::move(host)),
binding_(this),
weak_factory_(this) {
DCHECK(video_capture_host_);
@@ -36,8 +38,7 @@ void VideoCaptureClient::Start(FrameDeliverCallback deliver_callback,
media::mojom::VideoCaptureObserverPtr observer;
binding_.Bind(mojo::MakeRequest(&observer));
- video_capture_host_->Start(kDeviceId, 0, media::VideoCaptureParams(),
- std::move(observer));
+ video_capture_host_->Start(kDeviceId, 0, params_, std::move(observer));
}
void VideoCaptureClient::Stop() {
@@ -63,7 +64,7 @@ void VideoCaptureClient::Resume(FrameDeliverCallback deliver_callback) {
return;
}
frame_deliver_callback_ = std::move(deliver_callback);
- video_capture_host_->Resume(kDeviceId, 0, media::VideoCaptureParams());
+ video_capture_host_->Resume(kDeviceId, 0, params_);
}
void VideoCaptureClient::RequestRefreshFrame() {
@@ -100,15 +101,15 @@ void VideoCaptureClient::OnStateChanged(media::mojom::VideoCaptureState state) {
}
}
-void VideoCaptureClient::OnBufferCreated(
+void VideoCaptureClient::OnNewBuffer(
int32_t buffer_id,
- mojo::ScopedSharedBufferHandle handle) {
+ media::mojom::VideoBufferHandlePtr buffer_handle) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK(handle.is_valid());
DVLOG(3) << __func__ << ": buffer_id=" << buffer_id;
+ DCHECK(buffer_handle->is_shared_buffer_handle());
- const auto insert_result =
- client_buffers_.emplace(std::make_pair(buffer_id, std::move(handle)));
+ const auto insert_result = client_buffers_.emplace(std::make_pair(
+ buffer_id, std::move(buffer_handle->get_shared_buffer_handle())));
DCHECK(insert_result.second);
}
@@ -118,13 +119,11 @@ void VideoCaptureClient::OnBufferReady(int32_t buffer_id,
DVLOG(3) << __func__ << ": buffer_id=" << buffer_id;
bool consume_buffer = !frame_deliver_callback_.is_null();
- if ((info->pixel_format != media::PIXEL_FORMAT_I420 &&
- info->pixel_format != media::PIXEL_FORMAT_Y16) ||
- info->storage_type != media::VideoPixelStorage::CPU) {
+ if (info->pixel_format != media::PIXEL_FORMAT_I420 &&
+ info->pixel_format != media::PIXEL_FORMAT_Y16) {
consume_buffer = false;
- LOG(DFATAL) << "Wrong pixel format or storage, got pixel format:"
- << VideoPixelFormatToString(info->pixel_format)
- << ", storage:" << static_cast<int>(info->storage_type);
+ LOG(DFATAL) << "Wrong pixel format, got pixel format:"
+ << VideoPixelFormatToString(info->pixel_format);
}
if (!consume_buffer) {
video_capture_host_->ReleaseBuffer(kDeviceId, buffer_id, -1.0);
diff --git a/chromium/components/mirroring/service/video_capture_client.h b/chromium/components/mirroring/service/video_capture_client.h
index 3c43cd0bb4e..a417dcdef2f 100644
--- a/chromium/components/mirroring/service/video_capture_client.h
+++ b/chromium/components/mirroring/service/video_capture_client.h
@@ -16,6 +16,7 @@
namespace media {
class VideoFrame;
+class VideoFrameMetadata;
} // namespace media
namespace mirroring {
@@ -26,7 +27,8 @@ namespace mirroring {
// received through the media::mojom::VideoCaptureObserver interface.
class VideoCaptureClient : public media::mojom::VideoCaptureObserver {
public:
- explicit VideoCaptureClient(media::mojom::VideoCaptureHostPtr host);
+ VideoCaptureClient(const media::VideoCaptureParams& params,
+ media::mojom::VideoCaptureHostPtr host);
~VideoCaptureClient() override;
using FrameDeliverCallback = base::RepeatingCallback<void(
@@ -47,8 +49,8 @@ class VideoCaptureClient : public media::mojom::VideoCaptureObserver {
// media::mojom::VideoCaptureObserver implementations.
void OnStateChanged(media::mojom::VideoCaptureState state) override;
- void OnBufferCreated(int32_t buffer_id,
- mojo::ScopedSharedBufferHandle handle) override;
+ void OnNewBuffer(int32_t buffer_id,
+ media::mojom::VideoBufferHandlePtr buffer_handle) override;
void OnBufferReady(int32_t buffer_id,
media::mojom::VideoFrameInfoPtr info) override;
void OnBufferDestroyed(int32_t buffer_id) override;
@@ -64,6 +66,7 @@ class VideoCaptureClient : public media::mojom::VideoCaptureObserver {
void OnClientBufferFinished(int buffer_id,
double consumer_resource_utilization);
+ const media::VideoCaptureParams params_;
const media::mojom::VideoCaptureHostPtr video_capture_host_;
// Called when capturing failed to start.
diff --git a/chromium/components/mirroring/service/video_capture_client_unittest.cc b/chromium/components/mirroring/service/video_capture_client_unittest.cc
index a903d086508..0e8637d84b7 100644
--- a/chromium/components/mirroring/service/video_capture_client_unittest.cc
+++ b/chromium/components/mirroring/service/video_capture_client_unittest.cc
@@ -8,6 +8,8 @@
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "components/mirroring/service/fake_video_capture_host.h"
+#include "media/base/video_frame.h"
+#include "media/base/video_frame_metadata.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -26,8 +28,7 @@ media::mojom::VideoFrameInfoPtr GetVideoFrameInfo(const gfx::Size& size) {
base::TimeTicks());
return media::mojom::VideoFrameInfo::New(
base::TimeDelta(), metadata.GetInternalValues().Clone(),
- media::PIXEL_FORMAT_I420, media::VideoPixelStorage::CPU, size,
- gfx::Rect(size));
+ media::PIXEL_FORMAT_I420, size, gfx::Rect(size));
}
} // namespace
@@ -38,7 +39,8 @@ class VideoCaptureClientTest : public ::testing::Test {
media::mojom::VideoCaptureHostPtr host;
host_impl_ =
std::make_unique<FakeVideoCaptureHost>(mojo::MakeRequest(&host));
- client_ = std::make_unique<VideoCaptureClient>(std::move(host));
+ client_ = std::make_unique<VideoCaptureClient>(media::VideoCaptureParams(),
+ std::move(host));
}
~VideoCaptureClientTest() override {
@@ -79,7 +81,12 @@ TEST_F(VideoCaptureClientTest, Basic) {
run_loop.Run();
}
scoped_task_environment_.RunUntilIdle();
- client_->OnBufferCreated(0, mojo::SharedBufferHandle::Create(100000));
+
+ media::mojom::VideoBufferHandlePtr buffer_handle =
+ media::mojom::VideoBufferHandle::New();
+ buffer_handle->set_shared_buffer_handle(
+ mojo::SharedBufferHandle::Create(100000));
+ client_->OnNewBuffer(0, std::move(buffer_handle));
scoped_task_environment_.RunUntilIdle();
{
base::RunLoop run_loop;
diff --git a/chromium/components/mirroring/service/wifi_status_monitor.cc b/chromium/components/mirroring/service/wifi_status_monitor.cc
new file mode 100644
index 00000000000..fa676b4a87d
--- /dev/null
+++ b/chromium/components/mirroring/service/wifi_status_monitor.cc
@@ -0,0 +1,80 @@
+// Copyright 2018 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 "components/mirroring/service/wifi_status_monitor.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "components/mirroring/service/message_dispatcher.h"
+
+namespace mirroring {
+
+namespace {
+
+// The interval to query the status.
+constexpr base::TimeDelta kQueryInterval = base::TimeDelta::FromMinutes(2);
+
+// The maximum number of recent status to be kept.
+constexpr int kMaxRecords = 30;
+
+} // namespace
+
+WifiStatusMonitor::WifiStatusMonitor(int32_t session_id,
+ MessageDispatcher* message_dispatcher)
+ : session_id_(session_id), message_dispatcher_(message_dispatcher) {
+ DCHECK(message_dispatcher_);
+ message_dispatcher_->Subscribe(
+ ResponseType::STATUS_RESPONSE,
+ base::BindRepeating(&WifiStatusMonitor::RecordStatus,
+ base::Unretained(this)));
+ query_timer_.Start(FROM_HERE, kQueryInterval,
+ base::BindRepeating(&WifiStatusMonitor::QueryStatus,
+ base::Unretained(this)));
+ QueryStatus();
+}
+
+WifiStatusMonitor::~WifiStatusMonitor() {
+ message_dispatcher_->Unsubscribe(ResponseType::STATUS_RESPONSE);
+}
+
+std::vector<WifiStatus> WifiStatusMonitor::GetRecentValues() {
+ std::vector<WifiStatus> recent_status(recent_status_.begin(),
+ recent_status_.end());
+ recent_status_.clear();
+ return recent_status;
+}
+
+void WifiStatusMonitor::QueryStatus() {
+ base::Value query(base::Value::Type::DICTIONARY);
+ query.SetKey("type", base::Value("GET_STATUS"));
+ query.SetKey("sessionId", base::Value(session_id_));
+ query.SetKey("seqNum", base::Value(message_dispatcher_->GetNextSeqNumber()));
+ base::Value::ListStorage status;
+ status.emplace_back(base::Value("wifiSnr"));
+ status.emplace_back(base::Value("wifiSpeed"));
+ query.SetKey("get_status", base::Value(status));
+ CastMessage get_status_message;
+ get_status_message.message_namespace = kWebRtcNamespace;
+ const bool did_serialize_query =
+ base::JSONWriter::Write(query, &get_status_message.json_format_data);
+ DCHECK(did_serialize_query);
+ message_dispatcher_->SendOutboundMessage(get_status_message);
+}
+
+void WifiStatusMonitor::RecordStatus(const ReceiverResponse& response) {
+ if (!response.status || response.status->wifi_speed.size() != 4)
+ return;
+ if (recent_status_.size() == kMaxRecords)
+ recent_status_.pop_front();
+ WifiStatus received_status;
+ received_status.snr = response.status->wifi_snr;
+ // Only records the current speed.
+ received_status.speed = response.status->wifi_speed[3];
+ received_status.timestamp = base::Time::Now();
+ recent_status_.emplace_back(received_status);
+}
+
+} // namespace mirroring
diff --git a/chromium/components/mirroring/service/wifi_status_monitor.h b/chromium/components/mirroring/service/wifi_status_monitor.h
new file mode 100644
index 00000000000..ac348ce6d3e
--- /dev/null
+++ b/chromium/components/mirroring/service/wifi_status_monitor.h
@@ -0,0 +1,61 @@
+// Copyright 2018 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.
+
+#ifndef COMPONENTS_MIRRORING_SERVICE_WIFI_STATUS_MONITOR_H_
+#define COMPONENTS_MIRRORING_SERVICE_WIFI_STATUS_MONITOR_H_
+
+#include <vector>
+
+#include "base/containers/circular_deque.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+
+namespace mirroring {
+
+class MessageDispatcher;
+struct ReceiverResponse;
+
+struct WifiStatus {
+ double snr;
+ int32_t speed; // The current WiFi speed.
+ base::Time timestamp; // Recording time of this status.
+};
+
+// Periodically sends requests to the Cast device for WiFi network status
+// updates, processes responses, and maintains a recent history of data points.
+// This data can be included in feedback logs to help identify and diagnose
+// issues related to lousy network performance.
+class WifiStatusMonitor {
+ public:
+ // |message_dispatcher| must keep alive during the lifetime of this class.
+ WifiStatusMonitor(int32_t session_id, MessageDispatcher* message_dispatcher);
+ ~WifiStatusMonitor();
+
+ // Gets the recorded status and clear |recent_status_|.
+ std::vector<WifiStatus> GetRecentValues();
+
+ // Sends GET_STATUS message to receiver.
+ void QueryStatus();
+
+ // Callback for the STATUS_RESPONSE message. Records the WiFi status reported
+ // by receiver.
+ void RecordStatus(const ReceiverResponse& response);
+
+ private:
+ const int32_t session_id_;
+
+ MessageDispatcher* const message_dispatcher_; // Outlives this class.
+
+ base::RepeatingTimer query_timer_;
+
+ // Stores the recent status. Will be reset when GetRecentValues() is called.
+ base::circular_deque<WifiStatus> recent_status_;
+
+ DISALLOW_COPY_AND_ASSIGN(WifiStatusMonitor);
+};
+
+} // namespace mirroring
+
+#endif // COMPONENTS_MIRRORING_SERVICE_WIFI_STATUS_MONITOR_H_
diff --git a/chromium/components/mirroring/service/wifi_status_monitor_unittest.cc b/chromium/components/mirroring/service/wifi_status_monitor_unittest.cc
new file mode 100644
index 00000000000..0f7aac1a072
--- /dev/null
+++ b/chromium/components/mirroring/service/wifi_status_monitor_unittest.cc
@@ -0,0 +1,173 @@
+// Copyright 2018 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 "components/mirroring/service/wifi_status_monitor.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/json/json_reader.h"
+#include "base/macros.h"
+#include "base/test/mock_callback.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/values.h"
+#include "components/mirroring/service/message_dispatcher.h"
+#include "components/mirroring/service/value_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+
+namespace mirroring {
+
+namespace {
+
+bool IsNullMessage(const CastMessage& message) {
+ return message.message_namespace.empty() && message.json_format_data.empty();
+}
+
+std::string GetMessageType(const CastMessage& message) {
+ std::string type;
+ std::unique_ptr<base::Value> value =
+ base::JSONReader::Read(message.json_format_data);
+ EXPECT_TRUE(value);
+ EXPECT_TRUE(GetString(*value, "type", &type));
+ return type;
+}
+
+void VerifyRecordedStatus(const std::vector<WifiStatus> recorded_status,
+ double starting_snr,
+ int starting_speed,
+ int num_of_responses) {
+ EXPECT_EQ(num_of_responses, static_cast<int>(recorded_status.size()));
+ for (int i = 0; i < num_of_responses; ++i) {
+ EXPECT_EQ(starting_snr + i, recorded_status[i].snr);
+ EXPECT_EQ(starting_speed + i, recorded_status[i].speed);
+ }
+}
+
+} // namespace
+
+class WifiStatusMonitorTest : public CastMessageChannel,
+ public ::testing::Test {
+ public:
+ WifiStatusMonitorTest() : message_dispatcher_(this, error_callback_.Get()) {}
+
+ ~WifiStatusMonitorTest() override {}
+
+ // CastMessageChannel implementation. For outbound messages.
+ void Send(const CastMessage& message) override {
+ last_outbound_message_.message_namespace = message.message_namespace;
+ last_outbound_message_.json_format_data = message.json_format_data;
+ }
+
+ protected:
+ // Generates and sends |num_of_responses| responses.
+ void SendStatusResponses(double starting_snr,
+ int starting_speed,
+ int num_of_responses) {
+ for (int i = 0; i < num_of_responses; ++i) {
+ const std::string response =
+ "{\"seqNum\":" +
+ std::to_string(message_dispatcher_.GetNextSeqNumber()) +
+ ","
+ "\"type\": \"STATUS_RESPONSE\","
+ "\"result\": \"ok\","
+ "\"status\": {"
+ "\"wifiSnr\":" +
+ std::to_string(starting_snr + i) +
+ ","
+ "\"wifiSpeed\": [1234, 5678, 3000, " +
+ std::to_string(starting_speed + i) +
+ "],"
+ "\"wifiFcsError\": [12, 13, 12, 12]}" // This will be ignored.
+ "}";
+ SendInboundMessage(response);
+ }
+ }
+
+ // Sends an inbound message to |message_dispatcher|.
+ void SendInboundMessage(const std::string& response) {
+ CastMessage message;
+ message.message_namespace = kWebRtcNamespace;
+ message.json_format_data = response;
+ static_cast<CastMessageChannel*>(&message_dispatcher_)->Send(message);
+ scoped_task_environment_.RunUntilIdle();
+ }
+
+ // Creates a WifiStatusMonitor and start monitoring the status.
+ std::unique_ptr<WifiStatusMonitor> StartMonitoring() {
+ EXPECT_TRUE(IsNullMessage(last_outbound_message_));
+ EXPECT_CALL(error_callback_, Run(_)).Times(0);
+ auto status_monitor =
+ std::make_unique<WifiStatusMonitor>(123, &message_dispatcher_);
+ scoped_task_environment_.RunUntilIdle();
+ // Expect to receive request to send GET_STATUS message when create a
+ // WifiStatusMonitor.
+ EXPECT_EQ(kWebRtcNamespace, last_outbound_message_.message_namespace);
+ EXPECT_EQ("GET_STATUS", GetMessageType(last_outbound_message_));
+ // Clear the old outbound message.
+ last_outbound_message_.message_namespace.clear();
+ last_outbound_message_.json_format_data.clear();
+ EXPECT_TRUE(IsNullMessage(last_outbound_message_));
+ return status_monitor;
+ }
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ base::MockCallback<MessageDispatcher::ErrorCallback> error_callback_;
+ MessageDispatcher message_dispatcher_;
+ CastMessage last_outbound_message_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WifiStatusMonitorTest);
+};
+
+TEST_F(WifiStatusMonitorTest, QueryStatusAndRecordResponse) {
+ std::unique_ptr<WifiStatusMonitor> status_monitor = StartMonitoring();
+
+ // Send two responses and verify the data are stored.
+ SendStatusResponses(36.7, 3001, 2);
+ std::vector<WifiStatus> recent_status = status_monitor->GetRecentValues();
+ VerifyRecordedStatus(recent_status, 36.7, 3001, 2);
+
+ // There should be no further status stored.
+ recent_status = status_monitor->GetRecentValues();
+ EXPECT_TRUE(recent_status.empty());
+
+ // Sends more than the maximum number (30) of records that can be stored.
+ SendStatusResponses(36.7, 3001, 40);
+ // Expect that only the recent 30 records were stored.
+ recent_status = status_monitor->GetRecentValues();
+ VerifyRecordedStatus(recent_status, 46.7, 3011, 30);
+}
+
+TEST_F(WifiStatusMonitorTest, IgnoreMalformedStatusMessage) {
+ std::unique_ptr<WifiStatusMonitor> status_monitor = StartMonitoring();
+
+ // Sends a response with incomplete wifiSpeed data and expects it is ignored.
+ const std::string response1 =
+ "{\"seqNum\": 123,"
+ "\"type\": \"STATUS_RESPONSE\","
+ "\"result\": \"ok\","
+ "\"status\": {"
+ "\"wifiSnr\": 32,"
+ "\"wifiSpeed\": [1234, 5678, 3000],"
+ "\"wifiFcsError\": [12, 13, 12, 12]}"
+ "}";
+ SendInboundMessage(response1);
+ std::vector<WifiStatus> recent_status = status_monitor->GetRecentValues();
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_TRUE(recent_status.empty());
+
+ // Sends a response with null status field and expects it is ignored.
+ const std::string response2 =
+ "{\"seqNum\": 123,"
+ "\"type\": \"STATUS_RESPONSE\","
+ "\"status\": null}";
+ SendInboundMessage(response2);
+ recent_status = status_monitor->GetRecentValues();
+ scoped_task_environment_.RunUntilIdle();
+ EXPECT_TRUE(recent_status.empty());
+}
+
+} // namespace mirroring