diff options
-rw-r--r-- | remoting/host/chromoting_host.cc | 1 | ||||
-rw-r--r-- | remoting/host/host_config.cc | 1 | ||||
-rw-r--r-- | remoting/host/host_config.h | 2 | ||||
-rw-r--r-- | remoting/host/remoting_me2me_host.cc | 33 | ||||
-rw-r--r-- | remoting/host/video_frame_recorder.cc | 26 | ||||
-rw-r--r-- | remoting/host/video_frame_recorder.h | 9 | ||||
-rw-r--r-- | remoting/host/video_frame_recorder_host_extension.cc | 161 | ||||
-rw-r--r-- | remoting/host/video_frame_recorder_host_extension.h | 38 | ||||
-rw-r--r-- | remoting/remoting_host.gypi | 2 | ||||
-rw-r--r-- | remoting/webapp/client_plugin.js | 4 | ||||
-rw-r--r-- | remoting/webapp/client_session.js | 3 |
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' }; /** |