// 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 #include "base/base64.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/macros.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 { // Name of the extension message type field, and its value for this extension. const char kType[] = "type"; const char kVideoRecorderType[] = "video-recorder"; class VideoFrameRecorderHostExtensionSession : public HostExtensionSession { public: explicit VideoFrameRecorderHostExtensionSession(int64_t max_content_bytes); ~VideoFrameRecorderHostExtensionSession() override; // remoting::HostExtensionSession interface. void OnCreateVideoEncoder(scoped_ptr* encoder) override; bool ModifiesVideoPipeline() const override; bool OnExtensionMessage(ClientSessionControl* client_session_control, protocol::ClientStub* client_stub, const protocol::ExtensionMessage& message) override; private: // Handlers for the different frame recorder extension message types. void OnStart(); void OnStop(); void OnNextFrame(protocol::ClientStub* client_stub); VideoEncoderVerbatim verbatim_encoder_; VideoFrameRecorder video_frame_recorder_; bool first_frame_; DISALLOW_COPY_AND_ASSIGN(VideoFrameRecorderHostExtensionSession); }; VideoFrameRecorderHostExtensionSession::VideoFrameRecorderHostExtensionSession( int64_t max_content_bytes) : first_frame_(false) { video_frame_recorder_.SetMaxContentBytes(max_content_bytes); } VideoFrameRecorderHostExtensionSession:: ~VideoFrameRecorderHostExtensionSession() { } void VideoFrameRecorderHostExtensionSession::OnCreateVideoEncoder( scoped_ptr* encoder) { video_frame_recorder_.DetachVideoEncoderWrapper(); *encoder = video_frame_recorder_.WrapVideoEncoder(std::move(*encoder)); } 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 value = base::JSONReader::Read(message.data()); base::DictionaryValue* client_message; if (!value || !value->GetAsDictionary(&client_message)) { return true; } std::string type; if (!client_message->GetString(kType, &type)) { LOG(ERROR) << "Invalid video-recorder message"; return true; } const char kStartType[] = "start"; const char kStopType[] = "stop"; const char kNextFrameType[] = "next-frame"; if (type == kStartType) { OnStart(); } else if (type == kStopType) { OnStop(); } else if (type == kNextFrameType) { OnNextFrame(client_stub); } return true; } void VideoFrameRecorderHostExtensionSession::OnStart() { video_frame_recorder_.SetEnableRecording(true); first_frame_ = true; } void VideoFrameRecorderHostExtensionSession::OnStop() { video_frame_recorder_.SetEnableRecording(false); } void VideoFrameRecorderHostExtensionSession::OnNextFrame( protocol::ClientStub* client_stub) { scoped_ptr 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. const char kNextFrameReplyType[] = "next-frame-reply"; base::DictionaryValue reply_message; reply_message.SetString(kType, kNextFrameReplyType); if (frame) { // If this is the first frame then override the updated region so that // the encoder will send the whole frame's contents. if (first_frame_) { first_frame_ = false; frame->mutable_updated_region()->SetRect( webrtc::DesktopRect::MakeSize(frame->size())); } // Encode the frame into a raw ARGB VideoPacket. scoped_ptr encoded_frame( verbatim_encoder_.Encode(*frame)); // Serialize that packet into a string. std::string data(encoded_frame->ByteSize(), 0); encoded_frame->SerializeWithCachedSizesToArray( reinterpret_cast(&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. const char kData[] = "data"; reply_message.SetString(kData, base64_data); } // JSON-encode the reply into a string. // Note that JSONWriter::Write() can only fail due to invalid inputs, and will // DCHECK in Debug builds in that case. std::string reply_json; if (!base::JSONWriter::Write(reply_message, &reply_json)) { return; } // 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); } } // namespace VideoFrameRecorderHostExtension::VideoFrameRecorderHostExtension() {} VideoFrameRecorderHostExtension::~VideoFrameRecorderHostExtension() {} void VideoFrameRecorderHostExtension::SetMaxContentBytes( int64_t max_content_bytes) { max_content_bytes_ = max_content_bytes; } std::string VideoFrameRecorderHostExtension::capability() const { const char kVideoRecorderCapability[] = "videoRecorder"; return kVideoRecorderCapability; } scoped_ptr VideoFrameRecorderHostExtension::CreateExtensionSession( ClientSessionControl* client_session_control, protocol::ClientStub* client_stub) { return make_scoped_ptr( new VideoFrameRecorderHostExtensionSession(max_content_bytes_)); } } // namespace remoting