| // 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 { |
| |
| // 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<VideoEncoder>* 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<VideoEncoder>* encoder) { |
| video_frame_recorder_.DetachVideoEncoderWrapper(); |
| *encoder = 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)) { |
| 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<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. |
| 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<VideoPacket> encoded_frame( |
| verbatim_encoder_.Encode(*frame)); |
| |
| // Serialize that packet into a string. |
| std::string data(encoded_frame->ByteSize(), 0); |
| 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. |
| 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<HostExtensionSession> |
| VideoFrameRecorderHostExtension::CreateExtensionSession( |
| ClientSessionControl* client_session_control, |
| protocol::ClientStub* client_stub) { |
| return make_scoped_ptr( |
| new VideoFrameRecorderHostExtensionSession(max_content_bytes_)); |
| } |
| |
| } // namespace remoting |