- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / media / desktop_media_picker_model.cc
1 // Copyright 2013 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 "chrome/browser/media/desktop_media_picker_model.h"
6
7 #include <map>
8
9 #include "base/hash.h"
10 #include "base/logging.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/threading/sequenced_worker_pool.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "grit/generated_resources.h"
15 #include "media/base/video_util.h"
16 #include "third_party/libyuv/include/libyuv/scale_argb.h"
17 #include "third_party/skia/include/core/SkBitmap.h"
18 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
19 #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h"
20 #include "third_party/webrtc/modules/desktop_capture/window_capturer.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/gfx/skia_util.h"
23
24 using content::BrowserThread;
25 using content::DesktopMediaID;
26
27 namespace {
28
29 // Update the list every second.
30 const int kDefaultUpdatePeriod = 1000;
31
32 // Returns a hash of a DesktopFrame content to detect when image for a desktop
33 // media source has changed.
34 uint32 GetFrameHash(webrtc::DesktopFrame* frame) {
35   int data_size = frame->stride() * frame->size().height();
36   return base::SuperFastHash(reinterpret_cast<char*>(frame->data()), data_size);
37 }
38
39 gfx::ImageSkia ScaleDesktopFrame(scoped_ptr<webrtc::DesktopFrame> frame,
40                                  gfx::Size size) {
41   gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
42       gfx::Rect(0, 0, size.width(), size.height()),
43       gfx::Size(frame->size().width(), frame->size().height()));
44
45   SkBitmap result;
46   result.setConfig(SkBitmap::kARGB_8888_Config,
47                    scaled_rect.width(), scaled_rect.height(), 0,
48                    kOpaque_SkAlphaType);
49   result.allocPixels();
50   result.lockPixels();
51
52   uint8* pixels_data = reinterpret_cast<uint8*>(result.getPixels());
53   libyuv::ARGBScale(frame->data(), frame->stride(),
54                     frame->size().width(), frame->size().height(),
55                     pixels_data, result.rowBytes(),
56                     scaled_rect.width(), scaled_rect.height(),
57                     libyuv::kFilterBilinear);
58
59   // Set alpha channel values to 255 for all pixels.
60   // TODO(sergeyu): Fix screen/window capturers to capture alpha channel and
61   // remove this code. Currently screen/window capturers (at least some
62   // implementations) only capture R, G and B channels and set Alpha to 0.
63   // crbug.com/264424
64   for (int y = 0; y < result.height(); ++y) {
65     for (int x = 0; x < result.width(); ++x) {
66       pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] =
67           0xff;
68     }
69   }
70
71   result.unlockPixels();
72
73   return gfx::ImageSkia::CreateFrom1xBitmap(result);
74 }
75
76 }  // namespace
77
78 DesktopMediaPickerModel::Source::Source(DesktopMediaID id, const string16& name)
79     : id(id),
80       name(name) {
81 }
82
83 DesktopMediaPickerModelImpl::SourceDescription::SourceDescription(
84     DesktopMediaID id,
85     const string16& name)
86     : id(id),
87       name(name) {
88 }
89
90 class DesktopMediaPickerModelImpl::Worker
91     : public webrtc::DesktopCapturer::Callback {
92  public:
93   Worker(base::WeakPtr<DesktopMediaPickerModelImpl> model,
94          scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
95          scoped_ptr<webrtc::WindowCapturer> window_capturer);
96   virtual ~Worker();
97
98   void Refresh(const gfx::Size& thumbnail_size,
99                content::DesktopMediaID::Id view_dialog_id);
100
101  private:
102   typedef std::map<DesktopMediaID, uint32> ImageHashesMap;
103
104   // webrtc::DesktopCapturer::Callback interface.
105   virtual webrtc::SharedMemory* CreateSharedMemory(size_t size) OVERRIDE;
106   virtual void OnCaptureCompleted(webrtc::DesktopFrame* frame) OVERRIDE;
107
108   base::WeakPtr<DesktopMediaPickerModelImpl> model_;
109
110   scoped_ptr<webrtc::ScreenCapturer> screen_capturer_;
111   scoped_ptr<webrtc::WindowCapturer> window_capturer_;
112
113   scoped_ptr<webrtc::DesktopFrame> current_frame_;
114
115   ImageHashesMap image_hashes_;
116
117   DISALLOW_COPY_AND_ASSIGN(Worker);
118 };
119
120 DesktopMediaPickerModelImpl::Worker::Worker(
121     base::WeakPtr<DesktopMediaPickerModelImpl> model,
122     scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
123     scoped_ptr<webrtc::WindowCapturer> window_capturer)
124     : model_(model),
125       screen_capturer_(screen_capturer.Pass()),
126       window_capturer_(window_capturer.Pass()) {
127   if (screen_capturer_)
128     screen_capturer_->Start(this);
129   if (window_capturer_)
130     window_capturer_->Start(this);
131 }
132
133 DesktopMediaPickerModelImpl::Worker::~Worker() {}
134
135 void DesktopMediaPickerModelImpl::Worker::Refresh(
136     const gfx::Size& thumbnail_size,
137     content::DesktopMediaID::Id view_dialog_id) {
138   std::vector<SourceDescription> sources;
139
140   if (screen_capturer_) {
141     // TODO(sergeyu): Enumerate each screen when ScreenCapturer supports it.
142     sources.push_back(SourceDescription(DesktopMediaID(
143         DesktopMediaID::TYPE_SCREEN, 0),
144         l10n_util::GetStringUTF16(IDS_DESKTOP_MEDIA_PICKER_SCREEN_NAME)));
145   }
146
147   if (window_capturer_) {
148     webrtc::WindowCapturer::WindowList windows;
149     if (window_capturer_->GetWindowList(&windows)) {
150       for (webrtc::WindowCapturer::WindowList::iterator it = windows.begin();
151            it != windows.end(); ++it) {
152         // Skip the picker dialog window.
153         if (it->id != view_dialog_id) {
154           sources.push_back(SourceDescription(
155               DesktopMediaID(DesktopMediaID::TYPE_WINDOW, it->id),
156               base::UTF8ToUTF16(it->title)));
157         }
158       }
159     }
160   }
161
162   // Sort the list of sources so that they appear in a predictable order.
163   std::sort(sources.begin(), sources.end(), CompareSources);
164
165   // Update list of windows before updating thumbnails.
166   BrowserThread::PostTask(
167       BrowserThread::UI, FROM_HERE,
168       base::Bind(&DesktopMediaPickerModelImpl::OnSourcesList, model_, sources));
169
170   ImageHashesMap new_image_hashes;
171
172   // Get a thumbnail for each source.
173   for (size_t i = 0; i < sources.size(); ++i) {
174     SourceDescription& source = sources[i];
175     switch (source.id.type) {
176       case DesktopMediaID::TYPE_SCREEN:
177         screen_capturer_->Capture(webrtc::DesktopRegion());
178         DCHECK(current_frame_);
179         break;
180
181       case DesktopMediaID::TYPE_WINDOW:
182         if (!window_capturer_->SelectWindow(source.id.id))
183           continue;
184         window_capturer_->Capture(webrtc::DesktopRegion());
185         break;
186
187       default:
188         NOTREACHED();
189     }
190
191     // Expect that DesktopCapturer to always captures frames synchronously.
192     // |current_frame_| may be NULL if capture failed (e.g. because window has
193     // been closed).
194     if (current_frame_) {
195       uint32 frame_hash = GetFrameHash(current_frame_.get());
196       new_image_hashes[source.id] = frame_hash;
197
198       // Scale the image only if it has changed.
199       ImageHashesMap::iterator it = image_hashes_.find(source.id);
200       if (it == image_hashes_.end() || it->second != frame_hash) {
201         gfx::ImageSkia thumbnail =
202             ScaleDesktopFrame(current_frame_.Pass(), thumbnail_size);
203         BrowserThread::PostTask(
204             BrowserThread::UI, FROM_HERE,
205             base::Bind(&DesktopMediaPickerModelImpl::OnSourceThumbnail, model_,
206                        i, thumbnail));
207       }
208     }
209   }
210
211   image_hashes_.swap(new_image_hashes);
212
213   BrowserThread::PostTask(
214       BrowserThread::UI, FROM_HERE,
215       base::Bind(&DesktopMediaPickerModelImpl::OnRefreshFinished, model_));
216 }
217
218 webrtc::SharedMemory* DesktopMediaPickerModelImpl::Worker::CreateSharedMemory(
219     size_t size) {
220   return NULL;
221 }
222
223 void DesktopMediaPickerModelImpl::Worker::OnCaptureCompleted(
224     webrtc::DesktopFrame* frame) {
225   current_frame_.reset(frame);
226 }
227
228 DesktopMediaPickerModelImpl::DesktopMediaPickerModelImpl(
229     scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
230     scoped_ptr<webrtc::WindowCapturer> window_capturer)
231     : screen_capturer_(screen_capturer.Pass()),
232       window_capturer_(window_capturer.Pass()),
233       update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)),
234       thumbnail_size_(100, 100),
235       view_dialog_id_(-1),
236       observer_(NULL),
237       weak_factory_(this) {
238   base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool();
239   capture_task_runner_ = worker_pool->GetSequencedTaskRunner(
240       worker_pool->GetSequenceToken());
241 }
242
243 DesktopMediaPickerModelImpl::~DesktopMediaPickerModelImpl() {
244   capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release());
245 }
246
247 void DesktopMediaPickerModelImpl::SetUpdatePeriod(base::TimeDelta period) {
248   DCHECK(!observer_);
249   update_period_ = period;
250 }
251
252 void DesktopMediaPickerModelImpl::SetThumbnailSize(
253     const gfx::Size& thumbnail_size) {
254   thumbnail_size_ = thumbnail_size;
255 }
256
257 void DesktopMediaPickerModelImpl::SetViewDialogWindowId(
258     content::DesktopMediaID::Id dialog_id) {
259   view_dialog_id_ = dialog_id;
260 }
261
262 void DesktopMediaPickerModelImpl::StartUpdating(Observer* observer) {
263   DCHECK(!observer_);
264   DCHECK(screen_capturer_ || window_capturer_);
265
266   observer_ = observer;
267
268   worker_.reset(new Worker(weak_factory_.GetWeakPtr(),
269                            screen_capturer_.Pass(), window_capturer_.Pass()));
270   Refresh();
271 }
272
273 int DesktopMediaPickerModelImpl::source_count() const {
274   return sources_.size();
275 }
276
277 const DesktopMediaPickerModel::Source& DesktopMediaPickerModelImpl::source(
278     int index) const {
279   return sources_[index];
280 }
281
282 // static
283 bool DesktopMediaPickerModelImpl::CompareSources(const SourceDescription& a,
284                                              const SourceDescription& b) {
285   return a.id < b.id;
286 }
287
288 void DesktopMediaPickerModelImpl::Refresh() {
289   capture_task_runner_->PostTask(
290       FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()),
291                             thumbnail_size_, view_dialog_id_));
292 }
293
294 void DesktopMediaPickerModelImpl::OnSourcesList(
295     const std::vector<SourceDescription>& new_sources) {
296   // Step through |new_sources| adding and removing entries from |sources_|, and
297   // notifying the |observer_|, until two match. Requires that |sources| and
298   // |sources_| have the same ordering.
299   size_t pos = 0;
300   while (pos < sources_.size() || pos < new_sources.size()) {
301     // If |sources_[pos]| is not in |new_sources| then remove it.
302     if (pos < sources_.size() &&
303         (pos == new_sources.size() || sources_[pos].id < new_sources[pos].id)) {
304       sources_.erase(sources_.begin() + pos);
305       observer_->OnSourceRemoved(pos);
306       continue;
307     }
308
309     if (pos == sources_.size() || !(sources_[pos].id == new_sources[pos].id)) {
310       sources_.insert(sources_.begin() + pos,
311                       Source(new_sources[pos].id, new_sources[pos].name));
312       observer_->OnSourceAdded(pos);
313     } else if (sources_[pos].name != new_sources[pos].name) {
314       sources_[pos].name = new_sources[pos].name;
315       observer_->OnSourceNameChanged(pos);
316     }
317
318     ++pos;
319   }
320
321   DCHECK_EQ(new_sources.size(), sources_.size());
322 }
323
324 void DesktopMediaPickerModelImpl::OnSourceThumbnail(int index,
325                                                 const gfx::ImageSkia& image) {
326   DCHECK_LT(index, static_cast<int>(sources_.size()));
327   sources_[index].thumbnail = image;
328   observer_->OnSourceThumbnailChanged(index);
329 }
330
331 void DesktopMediaPickerModelImpl::OnRefreshFinished() {
332   BrowserThread::PostDelayedTask(
333       BrowserThread::UI, FROM_HERE,
334       base::Bind(&DesktopMediaPickerModelImpl::Refresh,
335                  weak_factory_.GetWeakPtr()),
336       update_period_);
337 }