diff options
author | wez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-02 07:55:49 +0000 |
---|---|---|
committer | wez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-08-02 07:55:49 +0000 |
commit | 10dc72b1cde6227d5c1262c5c0fd6c9fd4cb0c67 (patch) | |
tree | 2f1d546cf6b3ef7b717e09ce9fda44ee20af62ff | |
parent | 2a021fb7bd80168ce79e328f7c6e85de471b13d1 (diff) | |
download | chromium_src-10dc72b1cde6227d5c1262c5c0fd6c9fd4cb0c67.zip chromium_src-10dc72b1cde6227d5c1262c5c0fd6c9fd4cb0c67.tar.gz chromium_src-10dc72b1cde6227d5c1262c5c0fd6c9fd4cb0c67.tar.bz2 |
Add video frame recording capability to Chromoting hosts.
This will be used to record sequences of video frames for real-world sessions, for performance-evaluation purposes.
If the host is run with a video-recording buffer size specified then it will advertise a videoRecorder capability to clients. Clients seeing this capability can send "video-recorder" extension messages to the host to start and stop recording, and to fetch the next recorded frame.
Frames are encoded to VideoPackets using the "verbatim" encoder, and must then be serialized and Base-64 encoded for transmission via the extension message protocol.
See crrev.com/386853002 for the client part of this.
Review URL: https://codereview.chromium.org/372943002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@287183 0039d316-1c4b-4281-b951-d872f2087c98
-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' }; /** |