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