• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "chrome/grit/generated_resources.h"
17 #include "content/public/browser/browser_thread.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.
GetFrameHash(webrtc::DesktopFrame * frame)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 
ScaleDesktopFrame(scoped_ptr<webrtc::DesktopFrame> frame,gfx::Size size)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.allocN32Pixels(scaled_rect.width(), scaled_rect.height(), true);
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 
SourceDescription(DesktopMediaID id,const base::string16 & name)78 NativeDesktopMediaList::SourceDescription::SourceDescription(
79     DesktopMediaID id,
80     const base::string16& name)
81     : id(id),
82       name(name) {
83 }
84 
85 class NativeDesktopMediaList::Worker
86     : public webrtc::DesktopCapturer::Callback {
87  public:
88   Worker(base::WeakPtr<NativeDesktopMediaList> media_list,
89          scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
90          scoped_ptr<webrtc::WindowCapturer> window_capturer);
91   virtual ~Worker();
92 
93   void Refresh(const gfx::Size& thumbnail_size,
94                content::DesktopMediaID::Id view_dialog_id);
95 
96  private:
97   typedef std::map<DesktopMediaID, uint32> ImageHashesMap;
98 
99   // webrtc::DesktopCapturer::Callback interface.
100   virtual webrtc::SharedMemory* CreateSharedMemory(size_t size) OVERRIDE;
101   virtual void OnCaptureCompleted(webrtc::DesktopFrame* frame) OVERRIDE;
102 
103   base::WeakPtr<NativeDesktopMediaList> media_list_;
104 
105   scoped_ptr<webrtc::ScreenCapturer> screen_capturer_;
106   scoped_ptr<webrtc::WindowCapturer> window_capturer_;
107 
108   scoped_ptr<webrtc::DesktopFrame> current_frame_;
109 
110   ImageHashesMap image_hashes_;
111 
112   DISALLOW_COPY_AND_ASSIGN(Worker);
113 };
114 
Worker(base::WeakPtr<NativeDesktopMediaList> media_list,scoped_ptr<webrtc::ScreenCapturer> screen_capturer,scoped_ptr<webrtc::WindowCapturer> window_capturer)115 NativeDesktopMediaList::Worker::Worker(
116     base::WeakPtr<NativeDesktopMediaList> media_list,
117     scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
118     scoped_ptr<webrtc::WindowCapturer> window_capturer)
119     : media_list_(media_list),
120       screen_capturer_(screen_capturer.Pass()),
121       window_capturer_(window_capturer.Pass()) {
122   if (screen_capturer_)
123     screen_capturer_->Start(this);
124   if (window_capturer_)
125     window_capturer_->Start(this);
126 }
127 
~Worker()128 NativeDesktopMediaList::Worker::~Worker() {}
129 
Refresh(const gfx::Size & thumbnail_size,content::DesktopMediaID::Id view_dialog_id)130 void NativeDesktopMediaList::Worker::Refresh(
131     const gfx::Size& thumbnail_size,
132     content::DesktopMediaID::Id view_dialog_id) {
133   std::vector<SourceDescription> sources;
134 
135   if (screen_capturer_) {
136     webrtc::ScreenCapturer::ScreenList screens;
137     if (screen_capturer_->GetScreenList(&screens)) {
138       bool mutiple_screens = screens.size() > 1;
139       base::string16 title;
140       for (size_t i = 0; i < screens.size(); ++i) {
141         if (mutiple_screens) {
142           title = l10n_util::GetStringFUTF16Int(
143               IDS_DESKTOP_MEDIA_PICKER_MULTIPLE_SCREEN_NAME,
144               static_cast<int>(i + 1));
145         } else {
146           title = l10n_util::GetStringUTF16(
147               IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME);
148         }
149         sources.push_back(SourceDescription(DesktopMediaID(
150             DesktopMediaID::TYPE_SCREEN, screens[i].id), title));
151       }
152     }
153   }
154 
155   if (window_capturer_) {
156     webrtc::WindowCapturer::WindowList windows;
157     if (window_capturer_->GetWindowList(&windows)) {
158       for (webrtc::WindowCapturer::WindowList::iterator it = windows.begin();
159            it != windows.end(); ++it) {
160         // Skip the picker dialog window.
161         if (it->id != view_dialog_id) {
162           sources.push_back(SourceDescription(
163               DesktopMediaID(DesktopMediaID::TYPE_WINDOW, it->id),
164               base::UTF8ToUTF16(it->title)));
165         }
166       }
167     }
168   }
169   // Update list of windows before updating thumbnails.
170   BrowserThread::PostTask(
171       BrowserThread::UI, FROM_HERE,
172       base::Bind(&NativeDesktopMediaList::OnSourcesList,
173                  media_list_, sources));
174 
175   ImageHashesMap new_image_hashes;
176 
177   // Get a thumbnail for each source.
178   for (size_t i = 0; i < sources.size(); ++i) {
179     SourceDescription& source = sources[i];
180     switch (source.id.type) {
181       case DesktopMediaID::TYPE_SCREEN:
182         if (!screen_capturer_->SelectScreen(source.id.id))
183           continue;
184         screen_capturer_->Capture(webrtc::DesktopRegion());
185         break;
186 
187       case DesktopMediaID::TYPE_WINDOW:
188         if (!window_capturer_->SelectWindow(source.id.id))
189           continue;
190         window_capturer_->Capture(webrtc::DesktopRegion());
191         break;
192 
193       default:
194         NOTREACHED();
195     }
196 
197     // Expect that DesktopCapturer to always captures frames synchronously.
198     // |current_frame_| may be NULL if capture failed (e.g. because window has
199     // been closed).
200     if (current_frame_) {
201       uint32 frame_hash = GetFrameHash(current_frame_.get());
202       new_image_hashes[source.id] = frame_hash;
203 
204       // Scale the image only if it has changed.
205       ImageHashesMap::iterator it = image_hashes_.find(source.id);
206       if (it == image_hashes_.end() || it->second != frame_hash) {
207         gfx::ImageSkia thumbnail =
208             ScaleDesktopFrame(current_frame_.Pass(), thumbnail_size);
209         BrowserThread::PostTask(
210             BrowserThread::UI, FROM_HERE,
211             base::Bind(&NativeDesktopMediaList::OnSourceThumbnail,
212                         media_list_, i, thumbnail));
213       }
214     }
215   }
216 
217   image_hashes_.swap(new_image_hashes);
218 
219   BrowserThread::PostTask(
220       BrowserThread::UI, FROM_HERE,
221       base::Bind(&NativeDesktopMediaList::OnRefreshFinished, media_list_));
222 }
223 
CreateSharedMemory(size_t size)224 webrtc::SharedMemory* NativeDesktopMediaList::Worker::CreateSharedMemory(
225     size_t size) {
226   return NULL;
227 }
228 
OnCaptureCompleted(webrtc::DesktopFrame * frame)229 void NativeDesktopMediaList::Worker::OnCaptureCompleted(
230     webrtc::DesktopFrame* frame) {
231   current_frame_.reset(frame);
232 }
233 
NativeDesktopMediaList(scoped_ptr<webrtc::ScreenCapturer> screen_capturer,scoped_ptr<webrtc::WindowCapturer> window_capturer)234 NativeDesktopMediaList::NativeDesktopMediaList(
235     scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
236     scoped_ptr<webrtc::WindowCapturer> window_capturer)
237     : screen_capturer_(screen_capturer.Pass()),
238       window_capturer_(window_capturer.Pass()),
239       update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)),
240       thumbnail_size_(100, 100),
241       view_dialog_id_(-1),
242       observer_(NULL),
243       weak_factory_(this) {
244   base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool();
245   capture_task_runner_ = worker_pool->GetSequencedTaskRunner(
246       worker_pool->GetSequenceToken());
247 }
248 
~NativeDesktopMediaList()249 NativeDesktopMediaList::~NativeDesktopMediaList() {
250   capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release());
251 }
252 
SetUpdatePeriod(base::TimeDelta period)253 void NativeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period) {
254   DCHECK(!observer_);
255   update_period_ = period;
256 }
257 
SetThumbnailSize(const gfx::Size & thumbnail_size)258 void NativeDesktopMediaList::SetThumbnailSize(
259     const gfx::Size& thumbnail_size) {
260   thumbnail_size_ = thumbnail_size;
261 }
262 
SetViewDialogWindowId(content::DesktopMediaID::Id dialog_id)263 void NativeDesktopMediaList::SetViewDialogWindowId(
264     content::DesktopMediaID::Id dialog_id) {
265   view_dialog_id_ = dialog_id;
266 }
267 
StartUpdating(DesktopMediaListObserver * observer)268 void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) {
269   DCHECK(!observer_);
270   DCHECK(screen_capturer_ || window_capturer_);
271 
272   observer_ = observer;
273 
274   worker_.reset(new Worker(weak_factory_.GetWeakPtr(),
275                            screen_capturer_.Pass(), window_capturer_.Pass()));
276   Refresh();
277 }
278 
GetSourceCount() const279 int NativeDesktopMediaList::GetSourceCount() const {
280   return sources_.size();
281 }
282 
GetSource(int index) const283 const DesktopMediaList::Source& NativeDesktopMediaList::GetSource(
284     int index) const {
285   return sources_[index];
286 }
287 
Refresh()288 void NativeDesktopMediaList::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 
OnSourcesList(const std::vector<SourceDescription> & new_sources)294 void NativeDesktopMediaList::OnSourcesList(
295     const std::vector<SourceDescription>& new_sources) {
296   typedef std::set<content::DesktopMediaID> SourceSet;
297   SourceSet new_source_set;
298   for (size_t i = 0; i < new_sources.size(); ++i) {
299     new_source_set.insert(new_sources[i].id);
300   }
301   // Iterate through the old sources to find the removed sources.
302   for (size_t i = 0; i < sources_.size(); ++i) {
303     if (new_source_set.find(sources_[i].id) == new_source_set.end()) {
304       sources_.erase(sources_.begin() + i);
305       observer_->OnSourceRemoved(i);
306       --i;
307     }
308   }
309   // Iterate through the new sources to find the added sources.
310   if (new_sources.size() > sources_.size()) {
311     SourceSet old_source_set;
312     for (size_t i = 0; i < sources_.size(); ++i) {
313       old_source_set.insert(sources_[i].id);
314     }
315 
316     for (size_t i = 0; i < new_sources.size(); ++i) {
317       if (old_source_set.find(new_sources[i].id) == old_source_set.end()) {
318         sources_.insert(sources_.begin() + i, Source());
319         sources_[i].id = new_sources[i].id;
320         sources_[i].name = new_sources[i].name;
321         observer_->OnSourceAdded(i);
322       }
323     }
324   }
325   DCHECK_EQ(new_sources.size(), sources_.size());
326 
327   // Find the moved/changed sources.
328   size_t pos = 0;
329   while (pos < sources_.size()) {
330     if (!(sources_[pos].id == new_sources[pos].id)) {
331       // Find the source that should be moved to |pos|, starting from |pos + 1|
332       // of |sources_|, because entries before |pos| should have been sorted.
333       size_t old_pos = pos + 1;
334       for (; old_pos < sources_.size(); ++old_pos) {
335         if (sources_[old_pos].id == new_sources[pos].id)
336           break;
337       }
338       DCHECK(sources_[old_pos].id == new_sources[pos].id);
339 
340       // Move the source from |old_pos| to |pos|.
341       Source temp = sources_[old_pos];
342       sources_.erase(sources_.begin() + old_pos);
343       sources_.insert(sources_.begin() + pos, temp);
344 
345       observer_->OnSourceMoved(old_pos, pos);
346     }
347 
348     if (sources_[pos].name != new_sources[pos].name) {
349       sources_[pos].name = new_sources[pos].name;
350       observer_->OnSourceNameChanged(pos);
351     }
352     ++pos;
353   }
354 }
355 
OnSourceThumbnail(int index,const gfx::ImageSkia & image)356 void NativeDesktopMediaList::OnSourceThumbnail(
357     int index,
358     const gfx::ImageSkia& image) {
359   DCHECK_LT(index, static_cast<int>(sources_.size()));
360   sources_[index].thumbnail = image;
361   observer_->OnSourceThumbnailChanged(index);
362 }
363 
OnRefreshFinished()364 void NativeDesktopMediaList::OnRefreshFinished() {
365   BrowserThread::PostDelayedTask(
366       BrowserThread::UI, FROM_HERE,
367       base::Bind(&NativeDesktopMediaList::Refresh,
368                  weak_factory_.GetWeakPtr()),
369       update_period_);
370 }
371