Chromium Code Reviews| 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"; | |
| 25 | |
| 26 const char kVideoRecorderType[] = "video-recorder"; | |
| 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 { | 24 class VideoFrameRecorderHostExtensionSession : public HostExtensionSession { |
| 37 public: | 25 public: |
| 38 explicit VideoFrameRecorderHostExtensionSession(int64_t max_content_bytes); | 26 explicit VideoFrameRecorderHostExtensionSession(int64_t max_content_bytes); |
| 39 virtual ~VideoFrameRecorderHostExtensionSession() {} | 27 virtual ~VideoFrameRecorderHostExtensionSession(); |
| 40 | 28 |
| 41 // remoting::HostExtensionSession interface. | 29 // remoting::HostExtensionSession interface. |
| 42 virtual scoped_ptr<VideoEncoder> OnCreateVideoEncoder( | 30 virtual void OnCreateVideoEncoder(scoped_ptr<VideoEncoder>* encoder) OVERRIDE; |
| 43 scoped_ptr<VideoEncoder> encoder) OVERRIDE; | |
| 44 virtual bool ModifiesVideoPipeline() const OVERRIDE; | 31 virtual bool ModifiesVideoPipeline() const OVERRIDE; |
| 45 virtual bool OnExtensionMessage( | 32 virtual bool OnExtensionMessage( |
| 46 ClientSessionControl* client_session_control, | 33 ClientSessionControl* client_session_control, |
| 47 protocol::ClientStub* client_stub, | 34 protocol::ClientStub* client_stub, |
| 48 const protocol::ExtensionMessage& message) OVERRIDE; | 35 const protocol::ExtensionMessage& message) OVERRIDE; |
| 49 | 36 |
| 50 private: | 37 private: |
| 51 VideoEncoderVerbatim verbatim_encoder; | 38 // Handlers for the different frame recorder extension message types. |
| 52 VideoFrameRecorder video_frame_recorder; | 39 void OnStart(); |
| 40 void OnStop(); | |
| 41 void OnNextFrame(protocol::ClientStub* client_stub); | |
| 42 | |
| 43 VideoEncoderVerbatim verbatim_encoder_; | |
| 44 VideoFrameRecorder video_frame_recorder_; | |
| 53 bool first_frame_; | 45 bool first_frame_; |
| 54 | 46 |
| 55 DISALLOW_COPY_AND_ASSIGN(VideoFrameRecorderHostExtensionSession); | 47 DISALLOW_COPY_AND_ASSIGN(VideoFrameRecorderHostExtensionSession); |
| 56 }; | 48 }; |
| 57 | 49 |
| 58 VideoFrameRecorderHostExtensionSession::VideoFrameRecorderHostExtensionSession( | 50 VideoFrameRecorderHostExtensionSession::VideoFrameRecorderHostExtensionSession( |
| 59 int64_t max_content_bytes) : first_frame_(false) { | 51 int64_t max_content_bytes) |
| 60 video_frame_recorder.SetMaxContentBytes(max_content_bytes); | 52 : first_frame_(false) { |
| 53 video_frame_recorder_.SetMaxContentBytes(max_content_bytes); | |
| 61 } | 54 } |
| 62 | 55 |
| 63 scoped_ptr<VideoEncoder> | 56 VideoFrameRecorderHostExtensionSession::~VideoFrameRecorderHostExtensionSession( |
| 64 VideoFrameRecorderHostExtensionSession::OnCreateVideoEncoder( | 57 ) { |
|
Peter Kasting
2014/08/26 20:58:15
Nit: I have absolutely no idea how the Google styl
Wez
2014/08/28 00:13:20
Done.
| |
| 65 scoped_ptr<VideoEncoder> encoder) { | 58 } |
| 66 video_frame_recorder.DetachVideoEncoderWrapper(); | 59 |
| 67 return video_frame_recorder.WrapVideoEncoder(encoder.Pass()); | 60 void VideoFrameRecorderHostExtensionSession::OnCreateVideoEncoder( |
| 61 scoped_ptr<VideoEncoder>* encoder) { | |
| 62 video_frame_recorder_.DetachVideoEncoderWrapper(); | |
| 63 *encoder = video_frame_recorder_.WrapVideoEncoder(encoder->Pass()); | |
| 68 } | 64 } |
| 69 | 65 |
| 70 bool VideoFrameRecorderHostExtensionSession::ModifiesVideoPipeline() const { | 66 bool VideoFrameRecorderHostExtensionSession::ModifiesVideoPipeline() const { |
| 71 return true; | 67 return true; |
| 72 } | 68 } |
| 73 | 69 |
| 70 // Name of the extension message type field, and its value for this extension. | |
| 71 const char kType[] = "type"; | |
| 72 const char kVideoRecorderType[] = "video-recorder"; | |
|
Peter Kasting
2014/08/26 20:58:15
Nit: Since these two are used by multiple function
Wez
2014/08/28 00:13:20
Declaring them in the class complicates things wit
| |
| 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)); | 108 } else { |
| 120 | 109 VLOG(1) << "Unknown video-recorder message"; |
|
Peter Kasting
2014/08/26 20:58:15
Nit: As noted before, it's usually better to avoid
Wez
2014/08/28 00:13:20
Removed the logging.
| |
| 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 } | 110 } |
| 149 | 111 |
| 150 return true; | 112 return true; |
| 151 } | 113 } |
| 152 | 114 |
| 115 void VideoFrameRecorderHostExtensionSession::OnStart() { | |
| 116 video_frame_recorder_.SetEnableRecording(true); | |
| 117 first_frame_ = true; | |
| 118 } | |
| 119 | |
| 120 void VideoFrameRecorderHostExtensionSession::OnStop() { | |
| 121 video_frame_recorder_.SetEnableRecording(false); | |
| 122 } | |
| 123 | |
| 124 void VideoFrameRecorderHostExtensionSession::OnNextFrame( | |
| 125 protocol::ClientStub* client_stub) { | |
| 126 scoped_ptr<webrtc::DesktopFrame> frame(video_frame_recorder_.NextFrame()); | |
| 127 | |
| 128 // TODO(wez): This involves six copies of the entire frame. | |
| 129 // See if there's some way to optimize at least a few of them out. | |
| 130 const char kNextFrameReplyType[] = "next-frame-reply"; | |
| 131 base::DictionaryValue reply_message; | |
| 132 reply_message.SetString(kType, kNextFrameReplyType); | |
| 133 if (frame) { | |
| 134 // If this is the first frame then override the updated region so that | |
| 135 // the encoder will send the whole frame's contents. | |
| 136 if (first_frame_) { | |
| 137 first_frame_ = false; | |
| 138 | |
| 139 frame->mutable_updated_region()->SetRect( | |
| 140 webrtc::DesktopRect::MakeSize(frame->size())); | |
| 141 } | |
| 142 | |
| 143 // Encode the frame into a raw ARGB VideoPacket. | |
| 144 scoped_ptr<VideoPacket> encoded_frame( | |
| 145 verbatim_encoder_.Encode(*frame)); | |
| 146 | |
| 147 // Serialize that packet into a string. | |
| 148 std::string data(encoded_frame->ByteSize(), 0); | |
| 149 encoded_frame->SerializeWithCachedSizesToArray( | |
| 150 reinterpret_cast<uint8_t*>(&data[0])); | |
| 151 | |
| 152 // Convert that string to Base64, so it's JSON-friendly. | |
| 153 std::string base64_data; | |
| 154 base::Base64Encode(data, &base64_data); | |
| 155 | |
| 156 // Copy the Base64 data into the message. | |
| 157 const char kData[] = "data"; | |
| 158 reply_message.SetString(kData, base64_data); | |
| 159 } | |
| 160 | |
| 161 // JSON-encode the reply into a string. | |
| 162 // Note that JSONWriter::Write() can only fail due to invalid inputs, and will | |
| 163 // DCHECK in Debug builds in that case. | |
| 164 std::string reply_json; | |
| 165 if (!base::JSONWriter::Write(&reply_message, &reply_json)) { | |
| 166 return; | |
| 167 } | |
| 168 | |
| 169 // Return the frame (or a 'data'-less reply) to the client. | |
| 170 protocol::ExtensionMessage message; | |
| 171 message.set_type(kVideoRecorderType); | |
| 172 message.set_data(reply_json); | |
| 173 client_stub->DeliverHostMessage(message); | |
| 174 } | |
| 175 | |
| 153 } // namespace | 176 } // namespace |
| 154 | 177 |
| 178 VideoFrameRecorderHostExtension::VideoFrameRecorderHostExtension() {} | |
| 179 | |
| 180 VideoFrameRecorderHostExtension::~VideoFrameRecorderHostExtension() {} | |
| 181 | |
| 155 void VideoFrameRecorderHostExtension::SetMaxContentBytes( | 182 void VideoFrameRecorderHostExtension::SetMaxContentBytes( |
| 156 int64_t max_content_bytes) { | 183 int64_t max_content_bytes) { |
| 157 max_content_bytes_ = max_content_bytes; | 184 max_content_bytes_ = max_content_bytes; |
| 158 } | 185 } |
| 159 | 186 |
| 160 std::string VideoFrameRecorderHostExtension::capability() const { | 187 std::string VideoFrameRecorderHostExtension::capability() const { |
| 161 return kVideoRecorderCapabilities; | 188 const char kVideoRecorderCapability[] = "videoRecorder"; |
| 189 return kVideoRecorderCapability; | |
| 162 } | 190 } |
| 163 | 191 |
| 164 scoped_ptr<HostExtensionSession> | 192 scoped_ptr<HostExtensionSession> |
| 165 VideoFrameRecorderHostExtension::CreateExtensionSession( | 193 VideoFrameRecorderHostExtension::CreateExtensionSession( |
| 166 ClientSessionControl* client_session_control, | 194 ClientSessionControl* client_session_control, |
| 167 protocol::ClientStub* client_stub) { | 195 protocol::ClientStub* client_stub) { |
| 168 return scoped_ptr<HostExtensionSession>( | 196 return scoped_ptr<HostExtensionSession>( |
| 169 new VideoFrameRecorderHostExtensionSession(max_content_bytes_)); | 197 new VideoFrameRecorderHostExtensionSession(max_content_bytes_)); |
| 170 } | 198 } |
| 171 | 199 |
| 172 } // namespace remoting | 200 } // namespace remoting |
| OLD | NEW |