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