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"
10 #include "base/logging.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/threading/sequenced_worker_pool.h"
13 #include "chrome/browser/media/desktop_media_list_observer.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "grit/generated_resources.h"
16 #include "media/base/video_util.h"
17 #include "third_party/libyuv/include/libyuv/scale_argb.h"
18 #include "third_party/skia/include/core/SkBitmap.h"
19 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
20 #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h"
21 #include "third_party/webrtc/modules/desktop_capture/window_capturer.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/gfx/skia_util.h"
25 using content::BrowserThread
;
26 using content::DesktopMediaID
;
30 // Update the list every second.
31 const int kDefaultUpdatePeriod
= 1000;
33 // Returns a hash of a DesktopFrame content to detect when image for a desktop
34 // media source has changed.
35 uint32
GetFrameHash(webrtc::DesktopFrame
* frame
) {
36 int data_size
= frame
->stride() * frame
->size().height();
37 return base::SuperFastHash(reinterpret_cast<char*>(frame
->data()), data_size
);
40 gfx::ImageSkia
ScaleDesktopFrame(scoped_ptr
<webrtc::DesktopFrame
> frame
,
42 gfx::Rect scaled_rect
= media::ComputeLetterboxRegion(
43 gfx::Rect(0, 0, size
.width(), size
.height()),
44 gfx::Size(frame
->size().width(), frame
->size().height()));
47 result
.setConfig(SkBitmap::kARGB_8888_Config
,
48 scaled_rect
.width(), scaled_rect
.height(), 0,
53 uint8
* pixels_data
= reinterpret_cast<uint8
*>(result
.getPixels());
54 libyuv::ARGBScale(frame
->data(), frame
->stride(),
55 frame
->size().width(), frame
->size().height(),
56 pixels_data
, result
.rowBytes(),
57 scaled_rect
.width(), scaled_rect
.height(),
58 libyuv::kFilterBilinear
);
60 // Set alpha channel values to 255 for all pixels.
61 // TODO(sergeyu): Fix screen/window capturers to capture alpha channel and
62 // remove this code. Currently screen/window capturers (at least some
63 // implementations) only capture R, G and B channels and set Alpha to 0.
65 for (int y
= 0; y
< result
.height(); ++y
) {
66 for (int x
= 0; x
< result
.width(); ++x
) {
67 pixels_data
[result
.rowBytes() * y
+ x
* result
.bytesPerPixel() + 3] =
72 result
.unlockPixels();
74 return gfx::ImageSkia::CreateFrom1xBitmap(result
);
79 NativeDesktopMediaList::SourceDescription::SourceDescription(
81 const base::string16
& name
)
86 class NativeDesktopMediaList::Worker
87 : public webrtc::DesktopCapturer::Callback
{
89 Worker(base::WeakPtr
<NativeDesktopMediaList
> media_list
,
90 scoped_ptr
<webrtc::ScreenCapturer
> screen_capturer
,
91 scoped_ptr
<webrtc::WindowCapturer
> window_capturer
);
94 void Refresh(const gfx::Size
& thumbnail_size
,
95 content::DesktopMediaID::Id view_dialog_id
);
98 typedef std::map
<DesktopMediaID
, uint32
> ImageHashesMap
;
100 // webrtc::DesktopCapturer::Callback interface.
101 virtual webrtc::SharedMemory
* CreateSharedMemory(size_t size
) OVERRIDE
;
102 virtual void OnCaptureCompleted(webrtc::DesktopFrame
* frame
) OVERRIDE
;
104 base::WeakPtr
<NativeDesktopMediaList
> media_list_
;
106 scoped_ptr
<webrtc::ScreenCapturer
> screen_capturer_
;
107 scoped_ptr
<webrtc::WindowCapturer
> window_capturer_
;
109 scoped_ptr
<webrtc::DesktopFrame
> current_frame_
;
111 ImageHashesMap image_hashes_
;
113 DISALLOW_COPY_AND_ASSIGN(Worker
);
116 NativeDesktopMediaList::Worker::Worker(
117 base::WeakPtr
<NativeDesktopMediaList
> media_list
,
118 scoped_ptr
<webrtc::ScreenCapturer
> screen_capturer
,
119 scoped_ptr
<webrtc::WindowCapturer
> window_capturer
)
120 : media_list_(media_list
),
121 screen_capturer_(screen_capturer
.Pass()),
122 window_capturer_(window_capturer
.Pass()) {
123 if (screen_capturer_
)
124 screen_capturer_
->Start(this);
125 if (window_capturer_
)
126 window_capturer_
->Start(this);
129 NativeDesktopMediaList::Worker::~Worker() {}
131 void NativeDesktopMediaList::Worker::Refresh(
132 const gfx::Size
& thumbnail_size
,
133 content::DesktopMediaID::Id view_dialog_id
) {
134 std::vector
<SourceDescription
> sources
;
136 if (screen_capturer_
) {
137 // TODO(sergeyu): Enumerate each screen when ScreenCapturer supports it.
138 sources
.push_back(SourceDescription(DesktopMediaID(
139 DesktopMediaID::TYPE_SCREEN
, 0),
140 l10n_util::GetStringUTF16(IDS_DESKTOP_MEDIA_PICKER_SCREEN_NAME
)));
143 if (window_capturer_
) {
144 webrtc::WindowCapturer::WindowList windows
;
145 if (window_capturer_
->GetWindowList(&windows
)) {
146 for (webrtc::WindowCapturer::WindowList::iterator it
= windows
.begin();
147 it
!= windows
.end(); ++it
) {
148 // Skip the picker dialog window.
149 if (it
->id
!= view_dialog_id
) {
150 sources
.push_back(SourceDescription(
151 DesktopMediaID(DesktopMediaID::TYPE_WINDOW
, it
->id
),
152 base::UTF8ToUTF16(it
->title
)));
158 // Sort the list of sources so that they appear in a predictable order.
159 std::sort(sources
.begin(), sources
.end(), CompareSources
);
161 // Update list of windows before updating thumbnails.
162 BrowserThread::PostTask(
163 BrowserThread::UI
, FROM_HERE
,
164 base::Bind(&NativeDesktopMediaList::OnSourcesList
,
165 media_list_
, sources
));
167 ImageHashesMap new_image_hashes
;
169 // Get a thumbnail for each source.
170 for (size_t i
= 0; i
< sources
.size(); ++i
) {
171 SourceDescription
& source
= sources
[i
];
172 switch (source
.id
.type
) {
173 case DesktopMediaID::TYPE_SCREEN
:
174 screen_capturer_
->Capture(webrtc::DesktopRegion());
175 DCHECK(current_frame_
);
178 case DesktopMediaID::TYPE_WINDOW
:
179 if (!window_capturer_
->SelectWindow(source
.id
.id
))
181 window_capturer_
->Capture(webrtc::DesktopRegion());
188 // Expect that DesktopCapturer to always captures frames synchronously.
189 // |current_frame_| may be NULL if capture failed (e.g. because window has
191 if (current_frame_
) {
192 uint32 frame_hash
= GetFrameHash(current_frame_
.get());
193 new_image_hashes
[source
.id
] = frame_hash
;
195 // Scale the image only if it has changed.
196 ImageHashesMap::iterator it
= image_hashes_
.find(source
.id
);
197 if (it
== image_hashes_
.end() || it
->second
!= frame_hash
) {
198 gfx::ImageSkia thumbnail
=
199 ScaleDesktopFrame(current_frame_
.Pass(), thumbnail_size
);
200 BrowserThread::PostTask(
201 BrowserThread::UI
, FROM_HERE
,
202 base::Bind(&NativeDesktopMediaList::OnSourceThumbnail
,
203 media_list_
, i
, thumbnail
));
208 image_hashes_
.swap(new_image_hashes
);
210 BrowserThread::PostTask(
211 BrowserThread::UI
, FROM_HERE
,
212 base::Bind(&NativeDesktopMediaList::OnRefreshFinished
, media_list_
));
215 webrtc::SharedMemory
* NativeDesktopMediaList::Worker::CreateSharedMemory(
220 void NativeDesktopMediaList::Worker::OnCaptureCompleted(
221 webrtc::DesktopFrame
* frame
) {
222 current_frame_
.reset(frame
);
225 NativeDesktopMediaList::NativeDesktopMediaList(
226 scoped_ptr
<webrtc::ScreenCapturer
> screen_capturer
,
227 scoped_ptr
<webrtc::WindowCapturer
> window_capturer
)
228 : screen_capturer_(screen_capturer
.Pass()),
229 window_capturer_(window_capturer
.Pass()),
230 update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod
)),
231 thumbnail_size_(100, 100),
234 weak_factory_(this) {
235 base::SequencedWorkerPool
* worker_pool
= BrowserThread::GetBlockingPool();
236 capture_task_runner_
= worker_pool
->GetSequencedTaskRunner(
237 worker_pool
->GetSequenceToken());
240 NativeDesktopMediaList::~NativeDesktopMediaList() {
241 capture_task_runner_
->DeleteSoon(FROM_HERE
, worker_
.release());
244 void NativeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period
) {
246 update_period_
= period
;
249 void NativeDesktopMediaList::SetThumbnailSize(
250 const gfx::Size
& thumbnail_size
) {
251 thumbnail_size_
= thumbnail_size
;
254 void NativeDesktopMediaList::SetViewDialogWindowId(
255 content::DesktopMediaID::Id dialog_id
) {
256 view_dialog_id_
= dialog_id
;
259 void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver
* observer
) {
261 DCHECK(screen_capturer_
|| window_capturer_
);
263 observer_
= observer
;
265 worker_
.reset(new Worker(weak_factory_
.GetWeakPtr(),
266 screen_capturer_
.Pass(), window_capturer_
.Pass()));
270 int NativeDesktopMediaList::GetSourceCount() const {
271 return sources_
.size();
274 const DesktopMediaList::Source
& NativeDesktopMediaList::GetSource(
276 return sources_
[index
];
280 bool NativeDesktopMediaList::CompareSources(const SourceDescription
& a
,
281 const SourceDescription
& b
) {
285 void NativeDesktopMediaList::Refresh() {
286 capture_task_runner_
->PostTask(
287 FROM_HERE
, base::Bind(&Worker::Refresh
, base::Unretained(worker_
.get()),
288 thumbnail_size_
, view_dialog_id_
));
291 void NativeDesktopMediaList::OnSourcesList(
292 const std::vector
<SourceDescription
>& new_sources
) {
293 // Step through |new_sources| adding and removing entries from |sources_|, and
294 // notifying the |observer_|, until two match. Requires that |sources| and
295 // |sources_| have the same ordering.
297 while (pos
< sources_
.size() || pos
< new_sources
.size()) {
298 // If |sources_[pos]| is not in |new_sources| then remove it.
299 if (pos
< sources_
.size() &&
300 (pos
== new_sources
.size() || sources_
[pos
].id
< new_sources
[pos
].id
)) {
301 sources_
.erase(sources_
.begin() + pos
);
302 observer_
->OnSourceRemoved(pos
);
306 if (pos
== sources_
.size() || !(sources_
[pos
].id
== new_sources
[pos
].id
)) {
307 sources_
.insert(sources_
.begin() + pos
, Source());
308 sources_
[pos
].id
= new_sources
[pos
].id
;
309 sources_
[pos
].name
= new_sources
[pos
].name
;
310 observer_
->OnSourceAdded(pos
);
311 } else if (sources_
[pos
].name
!= new_sources
[pos
].name
) {
312 sources_
[pos
].name
= new_sources
[pos
].name
;
313 observer_
->OnSourceNameChanged(pos
);
319 DCHECK_EQ(new_sources
.size(), sources_
.size());
322 void NativeDesktopMediaList::OnSourceThumbnail(
324 const gfx::ImageSkia
& image
) {
325 DCHECK_LT(index
, static_cast<int>(sources_
.size()));
326 sources_
[index
].thumbnail
= image
;
327 observer_
->OnSourceThumbnailChanged(index
);
330 void NativeDesktopMediaList::OnRefreshFinished() {
331 BrowserThread::PostDelayedTask(
332 BrowserThread::UI
, FROM_HERE
,
333 base::Bind(&NativeDesktopMediaList::Refresh
,
334 weak_factory_
.GetWeakPtr()),