1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "remoting/host/video_frame_recorder_host_extension.h"
6
7#include "base/base64.h"
8#include "base/json/json_reader.h"
9#include "base/json/json_writer.h"
10#include "base/logging.h"
11#include "base/values.h"
12#include "remoting/codec/video_encoder_verbatim.h"
13#include "remoting/host/host_extension_session.h"
14#include "remoting/host/video_frame_recorder.h"
15#include "remoting/proto/control.pb.h"
16#include "remoting/proto/video.pb.h"
17#include "remoting/protocol/client_stub.h"
18#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
19
20namespace remoting {
21
22namespace {
23
24// Name of the extension message type field, and its value for this extension.
25const char kType[] = "type";
26const char kVideoRecorderType[] = "video-recorder";
27
28class VideoFrameRecorderHostExtensionSession : public HostExtensionSession {
29 public:
30  explicit VideoFrameRecorderHostExtensionSession(int64_t max_content_bytes);
31  virtual ~VideoFrameRecorderHostExtensionSession();
32
33  // remoting::HostExtensionSession interface.
34  virtual void OnCreateVideoEncoder(scoped_ptr<VideoEncoder>* encoder) OVERRIDE;
35  virtual bool ModifiesVideoPipeline() const OVERRIDE;
36  virtual bool OnExtensionMessage(
37      ClientSessionControl* client_session_control,
38      protocol::ClientStub* client_stub,
39      const protocol::ExtensionMessage& message) OVERRIDE;
40
41 private:
42  // Handlers for the different frame recorder extension message types.
43  void OnStart();
44  void OnStop();
45  void OnNextFrame(protocol::ClientStub* client_stub);
46
47  VideoEncoderVerbatim verbatim_encoder_;
48  VideoFrameRecorder video_frame_recorder_;
49  bool first_frame_;
50
51  DISALLOW_COPY_AND_ASSIGN(VideoFrameRecorderHostExtensionSession);
52};
53
54VideoFrameRecorderHostExtensionSession::VideoFrameRecorderHostExtensionSession(
55    int64_t max_content_bytes)
56    : first_frame_(false) {
57  video_frame_recorder_.SetMaxContentBytes(max_content_bytes);
58}
59
60VideoFrameRecorderHostExtensionSession::
61    ~VideoFrameRecorderHostExtensionSession() {
62}
63
64void VideoFrameRecorderHostExtensionSession::OnCreateVideoEncoder(
65    scoped_ptr<VideoEncoder>* encoder) {
66  video_frame_recorder_.DetachVideoEncoderWrapper();
67  *encoder = video_frame_recorder_.WrapVideoEncoder(encoder->Pass());
68}
69
70bool VideoFrameRecorderHostExtensionSession::ModifiesVideoPipeline() const {
71  return true;
72}
73
74bool VideoFrameRecorderHostExtensionSession::OnExtensionMessage(
75    ClientSessionControl* client_session_control,
76    protocol::ClientStub* client_stub,
77    const protocol::ExtensionMessage& message) {
78  if (message.type() != kVideoRecorderType) {
79    return false;
80  }
81
82  if (!message.has_data()) {
83    return true;
84  }
85
86  scoped_ptr<base::Value> value(base::JSONReader::Read(message.data()));
87  base::DictionaryValue* client_message;
88  if (!value || !value->GetAsDictionary(&client_message)) {
89    return true;
90  }
91
92  std::string type;
93  if (!client_message->GetString(kType, &type)) {
94    LOG(ERROR) << "Invalid video-recorder message";
95    return true;
96  }
97
98  const char kStartType[] = "start";
99  const char kStopType[] = "stop";
100  const char kNextFrameType[] = "next-frame";
101
102  if (type == kStartType) {
103    OnStart();
104  } else if (type == kStopType) {
105    OnStop();
106  } else if (type == kNextFrameType) {
107    OnNextFrame(client_stub);
108  }
109
110  return true;
111}
112
113void VideoFrameRecorderHostExtensionSession::OnStart() {
114  video_frame_recorder_.SetEnableRecording(true);
115  first_frame_ = true;
116}
117
118void VideoFrameRecorderHostExtensionSession::OnStop() {
119  video_frame_recorder_.SetEnableRecording(false);
120}
121
122void VideoFrameRecorderHostExtensionSession::OnNextFrame(
123    protocol::ClientStub* client_stub) {
124  scoped_ptr<webrtc::DesktopFrame> frame(video_frame_recorder_.NextFrame());
125
126  // TODO(wez): This involves six copies of the entire frame.
127  // See if there's some way to optimize at least a few of them out.
128  const char kNextFrameReplyType[] = "next-frame-reply";
129  base::DictionaryValue reply_message;
130  reply_message.SetString(kType, kNextFrameReplyType);
131  if (frame) {
132    // If this is the first frame then override the updated region so that
133    // the encoder will send the whole frame's contents.
134    if (first_frame_) {
135      first_frame_ = false;
136
137      frame->mutable_updated_region()->SetRect(
138          webrtc::DesktopRect::MakeSize(frame->size()));
139    }
140
141    // Encode the frame into a raw ARGB VideoPacket.
142    scoped_ptr<VideoPacket> encoded_frame(
143        verbatim_encoder_.Encode(*frame));
144
145    // Serialize that packet into a string.
146    std::string data(encoded_frame->ByteSize(), 0);
147    encoded_frame->SerializeWithCachedSizesToArray(
148        reinterpret_cast<uint8_t*>(&data[0]));
149
150    // Convert that string to Base64, so it's JSON-friendly.
151    std::string base64_data;
152    base::Base64Encode(data, &base64_data);
153
154    // Copy the Base64 data into the message.
155    const char kData[] = "data";
156    reply_message.SetString(kData, base64_data);
157  }
158
159  // JSON-encode the reply into a string.
160  // Note that JSONWriter::Write() can only fail due to invalid inputs, and will
161  // DCHECK in Debug builds in that case.
162  std::string reply_json;
163  if (!base::JSONWriter::Write(&reply_message, &reply_json)) {
164    return;
165  }
166
167  // Return the frame (or a 'data'-less reply) to the client.
168  protocol::ExtensionMessage message;
169  message.set_type(kVideoRecorderType);
170  message.set_data(reply_json);
171  client_stub->DeliverHostMessage(message);
172}
173
174} // namespace
175
176VideoFrameRecorderHostExtension::VideoFrameRecorderHostExtension() {}
177
178VideoFrameRecorderHostExtension::~VideoFrameRecorderHostExtension() {}
179
180void VideoFrameRecorderHostExtension::SetMaxContentBytes(
181    int64_t max_content_bytes) {
182  max_content_bytes_ = max_content_bytes;
183}
184
185std::string VideoFrameRecorderHostExtension::capability() const {
186  const char kVideoRecorderCapability[] = "videoRecorder";
187  return kVideoRecorderCapability;
188}
189
190scoped_ptr<HostExtensionSession>
191VideoFrameRecorderHostExtension::CreateExtensionSession(
192    ClientSessionControl* client_session_control,
193    protocol::ClientStub* client_stub) {
194  return scoped_ptr<HostExtensionSession>(
195      new VideoFrameRecorderHostExtensionSession(max_content_bytes_));
196}
197
198}  // namespace remoting
199