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.
5 #include "chrome/browser/media/native_desktop_media_list.h"
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"
27 using content::BrowserThread
;
28 using content::DesktopMediaID
;
32 // Update the list every second.
33 const int kDefaultUpdatePeriod
= 1000;
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
);
42 gfx::ImageSkia
ScaleDesktopFrame(scoped_ptr
<webrtc::DesktopFrame
> frame
,
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()));
49 result
.setConfig(SkBitmap::kARGB_8888_Config
,
50 scaled_rect
.width(), scaled_rect
.height(), 0,
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
);
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.
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] =
74 result
.unlockPixels();
76 return gfx::ImageSkia::CreateFrom1xBitmap(result
);
81 NativeDesktopMediaList::SourceDescription::SourceDescription(
83 const base::string16
& name
)
88 class NativeDesktopMediaList::Worker
89 : public webrtc::DesktopCapturer::Callback
{
91 Worker(base::WeakPtr
<NativeDesktopMediaList
> media_list
,
92 scoped_ptr
<webrtc::ScreenCapturer
> screen_capturer
,
93 scoped_ptr
<webrtc::WindowCapturer
> window_capturer
);
96 void Refresh(const gfx::Size
& thumbnail_size
,
97 content::DesktopMediaID::Id view_dialog_id
);
100 typedef std::map
<DesktopMediaID
, uint32
> ImageHashesMap
;
102 // webrtc::DesktopCapturer::Callback interface.
103 virtual webrtc::SharedMemory
* CreateSharedMemory(size_t size
) OVERRIDE
;
104 virtual void OnCaptureCompleted(webrtc::DesktopFrame
* frame
) OVERRIDE
;
106 base::WeakPtr
<NativeDesktopMediaList
> media_list_
;
108 scoped_ptr
<webrtc::ScreenCapturer
> screen_capturer_
;
109 scoped_ptr
<webrtc::WindowCapturer
> window_capturer_
;
111 scoped_ptr
<webrtc::DesktopFrame
> current_frame_
;
113 ImageHashesMap image_hashes_
;
115 DISALLOW_COPY_AND_ASSIGN(Worker
);
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);
131 NativeDesktopMediaList::Worker::~Worker() {}
133 void NativeDesktopMediaList::Worker::Refresh(
134 const gfx::Size
& thumbnail_size
,
135 content::DesktopMediaID::Id view_dialog_id
) {
136 std::vector
<SourceDescription
> sources
;
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));
149 title
= l10n_util::GetStringUTF16(
150 IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME
);
152 sources
.push_back(SourceDescription(DesktopMediaID(
153 DesktopMediaID::TYPE_SCREEN
, screens
[i
].id
), title
));
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
)));
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
));
178 ImageHashesMap new_image_hashes
;
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
))
187 screen_capturer_
->Capture(webrtc::DesktopRegion());
190 case DesktopMediaID::TYPE_WINDOW
:
191 if (!window_capturer_
->SelectWindow(source
.id
.id
))
193 window_capturer_
->Capture(webrtc::DesktopRegion());
200 // Expect that DesktopCapturer to always captures frames synchronously.
201 // |current_frame_| may be NULL if capture failed (e.g. because window has
203 if (current_frame_
) {
204 uint32 frame_hash
= GetFrameHash(current_frame_
.get());
205 new_image_hashes
[source
.id
] = frame_hash
;
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
));
220 image_hashes_
.swap(new_image_hashes
);
222 BrowserThread::PostTask(
223 BrowserThread::UI
, FROM_HERE
,
224 base::Bind(&NativeDesktopMediaList::OnRefreshFinished
, media_list_
));
227 webrtc::SharedMemory
* NativeDesktopMediaList::Worker::CreateSharedMemory(
232 void NativeDesktopMediaList::Worker::OnCaptureCompleted(
233 webrtc::DesktopFrame
* frame
) {
234 current_frame_
.reset(frame
);
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),
246 weak_factory_(this) {
247 base::SequencedWorkerPool
* worker_pool
= BrowserThread::GetBlockingPool();
248 capture_task_runner_
= worker_pool
->GetSequencedTaskRunner(
249 worker_pool
->GetSequenceToken());
252 NativeDesktopMediaList::~NativeDesktopMediaList() {
253 capture_task_runner_
->DeleteSoon(FROM_HERE
, worker_
.release());
256 void NativeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period
) {
258 update_period_
= period
;
261 void NativeDesktopMediaList::SetThumbnailSize(
262 const gfx::Size
& thumbnail_size
) {
263 thumbnail_size_
= thumbnail_size
;
266 void NativeDesktopMediaList::SetViewDialogWindowId(
267 content::DesktopMediaID::Id dialog_id
) {
268 view_dialog_id_
= dialog_id
;
271 void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver
* observer
) {
273 DCHECK(screen_capturer_
|| window_capturer_
);
275 observer_
= observer
;
277 worker_
.reset(new Worker(weak_factory_
.GetWeakPtr(),
278 screen_capturer_
.Pass(), window_capturer_
.Pass()));
282 int NativeDesktopMediaList::GetSourceCount() const {
283 return sources_
.size();
286 const DesktopMediaList::Source
& NativeDesktopMediaList::GetSource(
288 return sources_
[index
];
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_
));
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
);
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
);
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
);
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
);
328 DCHECK_EQ(new_sources
.size(), sources_
.size());
330 // Find the moved/changed sources.
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
)
341 DCHECK(sources_
[old_pos
].id
== new_sources
[pos
].id
);
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
);
348 observer_
->OnSourceMoved(old_pos
, pos
);
351 if (sources_
[pos
].name
!= new_sources
[pos
].name
) {
352 sources_
[pos
].name
= new_sources
[pos
].name
;
353 observer_
->OnSourceNameChanged(pos
);
359 void NativeDesktopMediaList::OnSourceThumbnail(
361 const gfx::ImageSkia
& image
) {
362 DCHECK_LT(index
, static_cast<int>(sources_
.size()));
363 sources_
[index
].thumbnail
= image
;
364 observer_
->OnSourceThumbnailChanged(index
);
367 void NativeDesktopMediaList::OnRefreshFinished() {
368 BrowserThread::PostDelayedTask(
369 BrowserThread::UI
, FROM_HERE
,
370 base::Bind(&NativeDesktopMediaList::Refresh
,
371 weak_factory_
.GetWeakPtr()),