38213b3bcba9ae3693d2ec81329c65452f083da7
[platform/framework/web/crosswalk.git] / src / remoting / host / video_frame_recorder.cc
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.h"
6
7 #include "base/bind.h"
8 #include "base/location.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/stl_util.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "remoting/codec/video_encoder.h"
13 #include "remoting/proto/video.pb.h"
14 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
15 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
16 #include "third_party/webrtc/modules/desktop_capture/desktop_region.h"
17
18 namespace remoting {
19
20 static int64_t FrameContentSize(const webrtc::DesktopFrame* frame) {
21   DCHECK_GT(frame->stride(), 0);
22   return frame->stride() * frame->size().height();
23 }
24
25 // VideoEncoder wrapper used to intercept frames passed to a real VideoEncoder.
26 class VideoFrameRecorder::RecordingVideoEncoder : public VideoEncoder {
27  public:
28   RecordingVideoEncoder(scoped_ptr<VideoEncoder> encoder,
29                         scoped_refptr<base::TaskRunner> recorder_task_runner,
30                         base::WeakPtr<VideoFrameRecorder> recorder)
31       : encoder_(encoder.Pass()),
32         recorder_task_runner_(recorder_task_runner),
33         recorder_(recorder),
34         enable_recording_(false),
35         weak_factory_(this) {
36     DCHECK(encoder_);
37     DCHECK(recorder_task_runner_);
38   }
39
40   base::WeakPtr<RecordingVideoEncoder> AsWeakPtr() {
41     return weak_factory_.GetWeakPtr();
42   }
43
44   void SetEnableRecording(bool enable_recording) {
45     DCHECK(!encoder_task_runner_ ||
46            encoder_task_runner_->BelongsToCurrentThread());
47     enable_recording_ = enable_recording;
48   }
49
50   // remoting::VideoEncoder interface.
51   virtual void SetLosslessEncode(bool want_lossless) OVERRIDE {
52     encoder_->SetLosslessEncode(want_lossless);
53   }
54   virtual void SetLosslessColor(bool want_lossless) OVERRIDE {
55     encoder_->SetLosslessColor(want_lossless);
56   }
57   virtual scoped_ptr<VideoPacket> Encode(
58       const webrtc::DesktopFrame& frame) OVERRIDE {
59     // If this is the first Encode() then store the TaskRunner and inform the
60     // VideoFrameRecorder so it can post SetEnableRecording() on it.
61     if (!encoder_task_runner_) {
62       encoder_task_runner_ = base::ThreadTaskRunnerHandle::Get();
63       recorder_task_runner_->PostTask(FROM_HERE,
64           base::Bind(&VideoFrameRecorder::SetEncoderTaskRunner,
65                      recorder_,
66                      encoder_task_runner_));
67     }
68
69     DCHECK(encoder_task_runner_->BelongsToCurrentThread());
70
71     if (enable_recording_) {
72       // Copy the frame and post it to the VideoFrameRecorder to store.
73       scoped_ptr<webrtc::DesktopFrame> frame_copy(
74           new webrtc::BasicDesktopFrame(frame.size()));
75       *frame_copy->mutable_updated_region() = frame.updated_region();
76       frame_copy->set_dpi(frame.dpi());
77       frame_copy->CopyPixelsFrom(frame.data(),
78                                  frame.stride(),
79                                  webrtc::DesktopRect::MakeSize(frame.size()));
80       recorder_task_runner_->PostTask(FROM_HERE,
81           base::Bind(&VideoFrameRecorder::RecordFrame,
82                      recorder_,
83                      base::Passed(&frame_copy)));
84     }
85
86     return encoder_->Encode(frame);
87   }
88
89  private:
90   scoped_ptr<VideoEncoder> encoder_;
91   scoped_refptr<base::TaskRunner> recorder_task_runner_;
92   base::WeakPtr<VideoFrameRecorder> recorder_;
93
94   bool enable_recording_;
95   scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner_;
96
97   base::WeakPtrFactory<RecordingVideoEncoder> weak_factory_;
98
99   DISALLOW_COPY_AND_ASSIGN(RecordingVideoEncoder);
100 };
101
102 VideoFrameRecorder::VideoFrameRecorder()
103     : content_bytes_(0),
104       max_content_bytes_(0),
105       enable_recording_(false),
106       weak_factory_(this) {
107 }
108
109 VideoFrameRecorder::~VideoFrameRecorder() {
110   DetachVideoEncoderWrapper();
111 }
112
113 scoped_ptr<VideoEncoder> VideoFrameRecorder::WrapVideoEncoder(
114     scoped_ptr<VideoEncoder> encoder) {
115   DCHECK(!encoder_task_runner_);
116   DCHECK(!caller_task_runner_);
117   caller_task_runner_ = base::ThreadTaskRunnerHandle::Get();
118
119   scoped_ptr<RecordingVideoEncoder> recording_encoder(
120       new RecordingVideoEncoder(encoder.Pass(),
121                                 caller_task_runner_,
122                                 weak_factory_.GetWeakPtr()));
123   recording_encoder_ = recording_encoder->AsWeakPtr();
124
125   return recording_encoder.PassAs<VideoEncoder>();
126 }
127
128 void VideoFrameRecorder::DetachVideoEncoderWrapper() {
129   DCHECK(!caller_task_runner_ || caller_task_runner_->BelongsToCurrentThread());
130
131   // Immediately detach the wrapper from this recorder.
132   weak_factory_.InvalidateWeakPtrs();
133
134   // Clean up any pending recorded frames.
135   STLDeleteElements(&recorded_frames_);
136   content_bytes_ = 0;
137
138   // Tell the wrapper to stop recording and posting frames to us.
139   if (encoder_task_runner_) {
140     encoder_task_runner_->PostTask(FROM_HERE,
141         base::Bind(&RecordingVideoEncoder::SetEnableRecording,
142                    recording_encoder_, false));
143   }
144
145   // Detach this recorder from the calling and encode threads.
146   caller_task_runner_ = NULL;
147   encoder_task_runner_ = NULL;
148 }
149
150 void VideoFrameRecorder::SetEnableRecording(bool enable_recording) {
151   DCHECK(!caller_task_runner_ || caller_task_runner_->BelongsToCurrentThread());
152
153   if (enable_recording_ == enable_recording) {
154     return;
155   }
156   enable_recording_ = enable_recording;
157
158   if (encoder_task_runner_) {
159     encoder_task_runner_->PostTask(FROM_HERE,
160         base::Bind(&RecordingVideoEncoder::SetEnableRecording,
161                    recording_encoder_,
162                    enable_recording_));
163   }
164 }
165
166 void VideoFrameRecorder::SetMaxContentBytes(int64_t max_content_bytes) {
167   DCHECK(!caller_task_runner_ || caller_task_runner_->BelongsToCurrentThread());
168   DCHECK_GE(max_content_bytes, 0);
169
170   max_content_bytes_ = max_content_bytes;
171 }
172
173 scoped_ptr<webrtc::DesktopFrame> VideoFrameRecorder::NextFrame() {
174   DCHECK(caller_task_runner_->BelongsToCurrentThread());
175
176   scoped_ptr<webrtc::DesktopFrame> frame;
177   if (!recorded_frames_.empty()) {
178     frame.reset(recorded_frames_.front());
179     recorded_frames_.pop_front();
180     content_bytes_ -= FrameContentSize(frame.get());
181     DCHECK_GE(content_bytes_, 0);
182   }
183
184   return frame.Pass();
185 }
186
187 void VideoFrameRecorder::SetEncoderTaskRunner(
188     scoped_refptr<base::TaskRunner> task_runner) {
189   DCHECK(caller_task_runner_->BelongsToCurrentThread());
190   DCHECK(!encoder_task_runner_);
191   DCHECK(task_runner);
192
193   encoder_task_runner_ = task_runner;
194
195   // If the caller already enabled recording, inform the recording encoder.
196   if (enable_recording_ && encoder_task_runner_) {
197     encoder_task_runner_->PostTask(FROM_HERE,
198         base::Bind(&RecordingVideoEncoder::SetEnableRecording,
199                    recording_encoder_,
200                    enable_recording_));
201   }
202 }
203
204 void VideoFrameRecorder::RecordFrame(scoped_ptr<webrtc::DesktopFrame> frame) {
205   DCHECK(caller_task_runner_->BelongsToCurrentThread());
206
207   int64_t frame_bytes = FrameContentSize(frame.get());
208   DCHECK_GE(frame_bytes, 0);
209
210   // Purge existing frames until there is space for the new one.
211   while (content_bytes_ + frame_bytes > max_content_bytes_ &&
212          !recorded_frames_.empty()) {
213     scoped_ptr<webrtc::DesktopFrame> drop_frame(recorded_frames_.front());
214     recorded_frames_.pop_front();
215     content_bytes_ -= FrameContentSize(drop_frame.get());
216     DCHECK_GE(content_bytes_, 0);
217   }
218
219   // If the frame is still too big, ignore it.
220   if (content_bytes_ + frame_bytes > max_content_bytes_) {
221     return;
222   }
223
224   // Store the frame and update the content byte count.
225   recorded_frames_.push_back(frame.release());
226   content_bytes_ += frame_bytes;
227 }
228
229 }  // namespace remoting