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