summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--remoting/host/chromoting_host.cc1
-rw-r--r--remoting/host/host_config.cc1
-rw-r--r--remoting/host/host_config.h2
-rw-r--r--remoting/host/remoting_me2me_host.cc33
-rw-r--r--remoting/host/video_frame_recorder.cc26
-rw-r--r--remoting/host/video_frame_recorder.h9
-rw-r--r--remoting/host/video_frame_recorder_host_extension.cc161
-rw-r--r--remoting/host/video_frame_recorder_host_extension.h38
-rw-r--r--remoting/remoting_host.gypi2
-rw-r--r--remoting/webapp/client_plugin.js4
-rw-r--r--remoting/webapp/client_session.js3
11 files changed, 274 insertions, 6 deletions
diff --git a/remoting/host/chromoting_host.cc b/remoting/host/chromoting_host.cc
index 8e1a452..427ced6 100644
--- a/remoting/host/chromoting_host.cc
+++ b/remoting/host/chromoting_host.cc
@@ -18,6 +18,7 @@
#include "remoting/host/desktop_environment.h"
#include "remoting/host/host_config.h"
#include "remoting/host/input_injector.h"
+#include "remoting/host/video_frame_recorder.h"
#include "remoting/protocol/connection_to_client.h"
#include "remoting/protocol/client_stub.h"
#include "remoting/protocol/host_stub.h"
diff --git a/remoting/host/host_config.cc b/remoting/host/host_config.cc
index 5b277db..29a2720 100644
--- a/remoting/host/host_config.cc
+++ b/remoting/host/host_config.cc
@@ -18,5 +18,6 @@ const char kHostSecretHashConfigPath[] = "host_secret_hash";
const char kPrivateKeyConfigPath[] = "private_key";
const char kUsageStatsConsentConfigPath[] = "usage_stats_consent";
const char kEnableVp9ConfigPath[] = "enable_vp9";
+const char kFrameRecorderBufferKbConfigPath[] = "frame-recorder-buffer-kb";
} // namespace remoting
diff --git a/remoting/host/host_config.h b/remoting/host/host_config.h
index 71c91ed..19332b3 100644
--- a/remoting/host/host_config.h
+++ b/remoting/host/host_config.h
@@ -42,6 +42,8 @@ extern const char kPrivateKeyConfigPath[];
extern const char kUsageStatsConsentConfigPath[];
// Whether to offer VP9 encoding to clients.
extern const char kEnableVp9ConfigPath[];
+// Number of Kibibytes of frame data to allow each client to record.
+extern const char kFrameRecorderBufferKbConfigPath[];
// HostConfig interace provides read-only access to host configuration.
class HostConfig {
diff --git a/remoting/host/remoting_me2me_host.cc b/remoting/host/remoting_me2me_host.cc
index 78daa67..7ac8d09 100644
--- a/remoting/host/remoting_me2me_host.cc
+++ b/remoting/host/remoting_me2me_host.cc
@@ -68,6 +68,7 @@
#include "remoting/host/token_validator_factory_impl.h"
#include "remoting/host/usage_stats_consent.h"
#include "remoting/host/username.h"
+#include "remoting/host/video_frame_recorder_host_extension.h"
#include "remoting/protocol/me2me_host_authenticator_factory.h"
#include "remoting/protocol/network_settings.h"
#include "remoting/protocol/pairing_registry.h"
@@ -125,6 +126,9 @@ const char kSignalParentSwitchName[] = "signal-parent";
// Command line switch used to enable VP9 encoding.
const char kEnableVp9SwitchName[] = "enable-vp9";
+// Command line switch used to enable and configure the frame-recorder.
+const char kFrameRecorderBufferKbName[] = "frame-recorder-buffer-kb";
+
// Value used for --host-config option to indicate that the path must be read
// from stdin.
const char kStdinConfigPath[] = "-";
@@ -289,6 +293,7 @@ class HostProcess
std::string host_owner_;
bool use_service_account_;
bool enable_vp9_;
+ int64_t frame_recorder_buffer_size_;
scoped_ptr<policy_hack::PolicyWatcher> policy_watcher_;
std::string host_domain_;
@@ -334,6 +339,7 @@ HostProcess::HostProcess(scoped_ptr<ChromotingHostContext> context,
state_(HOST_INITIALIZING),
use_service_account_(false),
enable_vp9_(false),
+ frame_recorder_buffer_size_(0),
host_username_match_required_(false),
allow_nat_traversal_(true),
allow_relay_(true),
@@ -827,6 +833,24 @@ bool HostProcess::ApplyConfig(scoped_ptr<JsonHostConfig> config) {
config->GetBoolean(kEnableVp9ConfigPath, &enable_vp9_);
}
+ // Allow the command-line to override the size of the frame recorder buffer.
+ std::string frame_recorder_buffer_kb;
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ kFrameRecorderBufferKbName)) {
+ frame_recorder_buffer_kb =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ kFrameRecorderBufferKbName);
+ } else {
+ config->GetString(kFrameRecorderBufferKbConfigPath,
+ &frame_recorder_buffer_kb);
+ }
+ if (!frame_recorder_buffer_kb.empty()) {
+ int buffer_kb = 0;
+ if (base::StringToInt(frame_recorder_buffer_kb, &buffer_kb)) {
+ frame_recorder_buffer_size_ = 1024LL * buffer_kb;
+ }
+ }
+
return true;
}
@@ -1207,6 +1231,13 @@ void HostProcess::StartHost() {
host_->set_protocol_config(config.Pass());
}
+ if (frame_recorder_buffer_size_ > 0) {
+ scoped_ptr<VideoFrameRecorderHostExtension> frame_recorder_extension(
+ new VideoFrameRecorderHostExtension());
+ frame_recorder_extension->SetMaxContentBytes(frame_recorder_buffer_size_);
+ host_->AddExtension(frame_recorder_extension.PassAs<HostExtension>());
+ }
+
// TODO(simonmorris): Get the maximum session duration from a policy.
#if defined(OS_LINUX)
host_->SetMaximumSessionDuration(base::TimeDelta::FromHours(20));
@@ -1226,7 +1257,7 @@ void HostProcess::StartHost() {
new HostStatusLogger(host_->AsWeakPtr(), ServerLogEntry::ME2ME,
signal_strategy_.get(), directory_bot_jid_));
- // Set up repoting the host status notifications.
+ // Set up reporting the host status notifications.
#if defined(REMOTING_MULTI_PROCESS)
host_event_logger_.reset(
new IpcHostEventLogger(host_->AsWeakPtr(), daemon_channel_.get()));
diff --git a/remoting/host/video_frame_recorder.cc b/remoting/host/video_frame_recorder.cc
index cccfe7c..38213b3 100644
--- a/remoting/host/video_frame_recorder.cc
+++ b/remoting/host/video_frame_recorder.cc
@@ -107,12 +107,12 @@ VideoFrameRecorder::VideoFrameRecorder()
}
VideoFrameRecorder::~VideoFrameRecorder() {
- SetEnableRecording(false);
- STLDeleteElements(&recorded_frames_);
+ DetachVideoEncoderWrapper();
}
scoped_ptr<VideoEncoder> VideoFrameRecorder::WrapVideoEncoder(
scoped_ptr<VideoEncoder> encoder) {
+ DCHECK(!encoder_task_runner_);
DCHECK(!caller_task_runner_);
caller_task_runner_ = base::ThreadTaskRunnerHandle::Get();
@@ -125,6 +125,28 @@ scoped_ptr<VideoEncoder> VideoFrameRecorder::WrapVideoEncoder(
return recording_encoder.PassAs<VideoEncoder>();
}
+void VideoFrameRecorder::DetachVideoEncoderWrapper() {
+ DCHECK(!caller_task_runner_ || caller_task_runner_->BelongsToCurrentThread());
+
+ // Immediately detach the wrapper from this recorder.
+ weak_factory_.InvalidateWeakPtrs();
+
+ // Clean up any pending recorded frames.
+ STLDeleteElements(&recorded_frames_);
+ content_bytes_ = 0;
+
+ // Tell the wrapper to stop recording and posting frames to us.
+ if (encoder_task_runner_) {
+ encoder_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&RecordingVideoEncoder::SetEnableRecording,
+ recording_encoder_, false));
+ }
+
+ // Detach this recorder from the calling and encode threads.
+ caller_task_runner_ = NULL;
+ encoder_task_runner_ = NULL;
+}
+
void VideoFrameRecorder::SetEnableRecording(bool enable_recording) {
DCHECK(!caller_task_runner_ || caller_task_runner_->BelongsToCurrentThread());
diff --git a/remoting/host/video_frame_recorder.h b/remoting/host/video_frame_recorder.h
index c204c9a..9d31722 100644
--- a/remoting/host/video_frame_recorder.h
+++ b/remoting/host/video_frame_recorder.h
@@ -23,7 +23,7 @@ class VideoEncoder;
// Allows sequences of DesktopFrames passed to a VideoEncoder to be recorded.
//
-// VideoFrameRecorder is design to support applications which use a dedicated
+// VideoFrameRecorder is designed to support applications which use a dedicated
// thread for video encoding, but need to manage that process from a "main"
// or "control" thread.
//
@@ -50,9 +50,14 @@ class VideoFrameRecorder {
// Wraps the supplied VideoEncoder, returning a replacement VideoEncoder that
// will route frames to the recorder, as well as passing them for encoding.
- // This may be called at most once on each VideoFrameRecorder instance.
+ // The caller must delete the previous recording VideoEncoder, or call
+ // DetachVideoEncoderWrapper() before calling WrapVideoEncoder() to create
+ // a new wrapper.
scoped_ptr<VideoEncoder> WrapVideoEncoder(scoped_ptr<VideoEncoder> encoder);
+ // Detaches the existing VideoEncoder wrapper, stopping it from recording.
+ void DetachVideoEncoderWrapper();
+
// Enables/disables frame recording. Frame recording is initially disabled.
void SetEnableRecording(bool enable_recording);
diff --git a/remoting/host/video_frame_recorder_host_extension.cc b/remoting/host/video_frame_recorder_host_extension.cc
new file mode 100644
index 0000000..67f9c94
--- /dev/null
+++ b/remoting/host/video_frame_recorder_host_extension.cc
@@ -0,0 +1,161 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/video_frame_recorder_host_extension.h"
+
+#include "base/base64.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "remoting/codec/video_encoder_verbatim.h"
+#include "remoting/host/host_extension_session.h"
+#include "remoting/host/video_frame_recorder.h"
+#include "remoting/proto/control.pb.h"
+#include "remoting/proto/video.pb.h"
+#include "remoting/protocol/client_stub.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+
+namespace remoting {
+
+namespace {
+
+const char kVideoRecorderCapabilities[] = "videoRecorder";
+
+const char kVideoRecorderType[] = "video-recorder";
+
+const char kType[] = "type";
+const char kData[] = "data";
+
+const char kStartType[] = "start";
+const char kStopType[] = "stop";
+const char kNextFrameType[] = "next-frame";
+const char kNextFrameReplyType[] = "next-frame-reply";
+
+class VideoFrameRecorderHostExtensionSession : public HostExtensionSession {
+ public:
+ explicit VideoFrameRecorderHostExtensionSession(int64_t max_content_bytes);
+ virtual ~VideoFrameRecorderHostExtensionSession() {}
+
+ // remoting::HostExtensionSession interface.
+ virtual scoped_ptr<VideoEncoder> OnCreateVideoEncoder(
+ scoped_ptr<VideoEncoder> encoder) OVERRIDE;
+ virtual bool ModifiesVideoPipeline() const OVERRIDE;
+ virtual bool OnExtensionMessage(
+ ClientSessionControl* client_session_control,
+ protocol::ClientStub* client_stub,
+ const protocol::ExtensionMessage& message) OVERRIDE;
+
+ private:
+ VideoEncoderVerbatim verbatim_encoder;
+ VideoFrameRecorder video_frame_recorder;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoFrameRecorderHostExtensionSession);
+};
+
+VideoFrameRecorderHostExtensionSession::VideoFrameRecorderHostExtensionSession(
+ int64_t max_content_bytes) {
+ video_frame_recorder.SetMaxContentBytes(max_content_bytes);
+}
+
+scoped_ptr<VideoEncoder>
+VideoFrameRecorderHostExtensionSession::OnCreateVideoEncoder(
+ scoped_ptr<VideoEncoder> encoder) {
+ video_frame_recorder.DetachVideoEncoderWrapper();
+ return video_frame_recorder.WrapVideoEncoder(encoder.Pass());
+}
+
+bool VideoFrameRecorderHostExtensionSession::ModifiesVideoPipeline() const {
+ return true;
+}
+
+bool VideoFrameRecorderHostExtensionSession::OnExtensionMessage(
+ ClientSessionControl* client_session_control,
+ protocol::ClientStub* client_stub,
+ const protocol::ExtensionMessage& message) {
+ if (message.type() != kVideoRecorderType) {
+ return false;
+ }
+
+ if (!message.has_data()) {
+ return true;
+ }
+
+ scoped_ptr<base::Value> value(base::JSONReader::Read(message.data()));
+ base::DictionaryValue* client_message;
+ if (value && value->GetAsDictionary(&client_message)) {
+ std::string type;
+ if (!client_message->GetString(kType, &type)) {
+ LOG(ERROR) << "Invalid video-recorder message";
+ return true;
+ }
+
+ if (type == kStartType) {
+ video_frame_recorder.SetEnableRecording(true);
+ } else if (type == kStopType) {
+ video_frame_recorder.SetEnableRecording(false);
+ } else if (type == kNextFrameType) {
+ scoped_ptr<webrtc::DesktopFrame> frame(video_frame_recorder.NextFrame());
+
+ // TODO(wez): This involves six copies of the entire frame.
+ // See if there's some way to optimize at least a few of them out.
+ base::DictionaryValue reply_message;
+ reply_message.SetString(kType, kNextFrameReplyType);
+ if (frame) {
+ // Encode the frame into a raw ARGB VideoPacket.
+ scoped_ptr<VideoPacket> encoded_frame(
+ verbatim_encoder.Encode(*frame));
+
+ // Serialize that packet into a string.
+ std::string data;
+ data.resize(encoded_frame->ByteSize());
+ encoded_frame->SerializeWithCachedSizesToArray(
+ reinterpret_cast<uint8_t*>(&data[0]));
+
+ // Convert that string to Base64, so it's JSON-friendly.
+ std::string base64_data;
+ base::Base64Encode(data, &base64_data);
+
+ // Copy the Base64 data into the message.
+ reply_message.SetString(kData, base64_data);
+ }
+
+ // JSON-encode the reply into a string.
+ std::string reply_json;
+ if (!base::JSONWriter::Write(&reply_message, &reply_json)) {
+ LOG(ERROR) << "Failed to create reply json";
+ return true;
+ }
+
+ // Return the frame (or a 'data'-less reply) to the client.
+ protocol::ExtensionMessage message;
+ message.set_type(kVideoRecorderType);
+ message.set_data(reply_json);
+ client_stub->DeliverHostMessage(message);
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+void VideoFrameRecorderHostExtension::SetMaxContentBytes(
+ int64_t max_content_bytes) {
+ max_content_bytes_ = max_content_bytes;
+}
+
+std::string VideoFrameRecorderHostExtension::capability() const {
+ return kVideoRecorderCapabilities;
+}
+
+scoped_ptr<HostExtensionSession>
+VideoFrameRecorderHostExtension::CreateExtensionSession(
+ ClientSessionControl* client_session_control,
+ protocol::ClientStub* client_stub) {
+ return scoped_ptr<HostExtensionSession>(
+ new VideoFrameRecorderHostExtensionSession(max_content_bytes_));
+}
+
+} // namespace remoting
diff --git a/remoting/host/video_frame_recorder_host_extension.h b/remoting/host/video_frame_recorder_host_extension.h
new file mode 100644
index 0000000..4a974e3
--- /dev/null
+++ b/remoting/host/video_frame_recorder_host_extension.h
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_VIDEO_FRAME_RECORDER_HOST_EXTENSION_H_
+#define REMOTING_HOST_VIDEO_FRAME_RECORDER_HOST_EXTENSION_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "remoting/host/host_extension.h"
+
+namespace remoting {
+
+// Extends Chromoting sessions with the ability to record raw frames and
+// download them to the client. This can be used to obtain representative
+// sequences of frames to run tests against.
+class VideoFrameRecorderHostExtension : public HostExtension {
+ public:
+ VideoFrameRecorderHostExtension() {}
+ virtual ~VideoFrameRecorderHostExtension() {}
+
+ // Sets the maximum number of bytes that each session may record.
+ void SetMaxContentBytes(int64_t max_content_bytes);
+
+ // remoting::HostExtension interface.
+ virtual std::string capability() const OVERRIDE;
+ virtual scoped_ptr<HostExtensionSession> CreateExtensionSession(
+ ClientSessionControl* client_session,
+ protocol::ClientStub* client_stub) OVERRIDE;
+
+ private:
+ int64_t max_content_bytes_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoFrameRecorderHostExtension);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_HOST_VIDEO_FRAME_RECORDER_HOST_EXTENSION_H_
diff --git a/remoting/remoting_host.gypi b/remoting/remoting_host.gypi
index 32cb97a..e08b1c2 100644
--- a/remoting/remoting_host.gypi
+++ b/remoting/remoting_host.gypi
@@ -251,6 +251,8 @@
'host/username.h',
'host/video_frame_recorder.cc',
'host/video_frame_recorder.h',
+ 'host/video_frame_recorder_host_extension.cc',
+ 'host/video_frame_recorder_host_extension.h',
'host/video_scheduler.cc',
'host/video_scheduler.h',
'host/win/com_imported_mstscax.tlh',
diff --git a/remoting/webapp/client_plugin.js b/remoting/webapp/client_plugin.js
index b7ac22a..7b854a2 100644
--- a/remoting/webapp/client_plugin.js
+++ b/remoting/webapp/client_plugin.js
@@ -217,6 +217,10 @@ remoting.ClientPlugin.prototype.handleMessageMethod_ = function(message) {
// it rate-limits desktop-resize requests.
this.capabilities_.push(
remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS);
+
+ // Let the host know that we can use the video framerecording extension.
+ this.capabilities_.push(
+ remoting.ClientSession.Capability.VIDEO_RECORDER);
} else if (this.pluginApiVersion_ >= 6) {
this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent'];
} else {
diff --git a/remoting/webapp/client_session.js b/remoting/webapp/client_session.js
index 3e0a5d8..d3114aa 100644
--- a/remoting/webapp/client_session.js
+++ b/remoting/webapp/client_session.js
@@ -362,7 +362,8 @@ remoting.ClientSession.Capability = {
// resolution to the host once connection has been established. See
// this.plugin_.notifyClientResolution().
SEND_INITIAL_RESOLUTION: 'sendInitialResolution',
- RATE_LIMIT_RESIZE_REQUESTS: 'rateLimitResizeRequests'
+ RATE_LIMIT_RESIZE_REQUESTS: 'rateLimitResizeRequests',
+ VIDEO_RECORDER: 'videoRecorder'
};
/**