OLD | NEW |
| (Empty) |
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 "base/hash.h" | |
8 #include "base/strings/utf_string_conversions.h" | |
9 #include "base/threading/sequenced_worker_pool.h" | |
10 #include "chrome/browser/media/desktop_media_list_observer.h" | |
11 #include "chrome/grit/generated_resources.h" | |
12 #include "content/public/browser/browser_thread.h" | |
13 #include "media/base/video_util.h" | |
14 #include "third_party/libyuv/include/libyuv/scale_argb.h" | |
15 #include "third_party/skia/include/core/SkBitmap.h" | |
16 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" | |
17 #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" | |
18 #include "third_party/webrtc/modules/desktop_capture/window_capturer.h" | |
19 #include "ui/aura/window.h" | |
20 #include "ui/base/l10n/l10n_util.h" | |
21 #include "ui/snapshot/snapshot.h" | |
22 | |
23 #if defined(OS_WIN) | |
24 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_win.h" | |
25 #endif // defined(OS_WIN) | |
26 | |
27 #if defined(USE_X11) && !defined(OS_CHROMEOS) | |
28 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h" | |
29 #endif // defined(USE_X11) && !defined(OS_CHROMEOS) | |
30 | |
31 using content::BrowserThread; | |
32 using content::DesktopMediaID; | |
33 | |
34 namespace { | |
35 | |
36 // Update the list every second. | |
37 const int kDefaultUpdatePeriod = 1000; | |
38 | |
39 // Returns a hash of a DesktopFrame content to detect when image for a desktop | |
40 // media source has changed. | |
41 uint32_t GetFrameHash(webrtc::DesktopFrame* frame) { | |
42 int data_size = frame->stride() * frame->size().height(); | |
43 return base::Hash(reinterpret_cast<char*>(frame->data()), data_size); | |
44 } | |
45 | |
46 gfx::ImageSkia ScaleDesktopFrame(std::unique_ptr<webrtc::DesktopFrame> frame, | |
47 gfx::Size size) { | |
48 gfx::Rect scaled_rect = media::ComputeLetterboxRegion( | |
49 gfx::Rect(0, 0, size.width(), size.height()), | |
50 gfx::Size(frame->size().width(), frame->size().height())); | |
51 | |
52 SkBitmap result; | |
53 result.allocN32Pixels(scaled_rect.width(), scaled_rect.height(), true); | |
54 result.lockPixels(); | |
55 | |
56 uint8_t* pixels_data = reinterpret_cast<uint8_t*>(result.getPixels()); | |
57 libyuv::ARGBScale(frame->data(), frame->stride(), | |
58 frame->size().width(), frame->size().height(), | |
59 pixels_data, result.rowBytes(), | |
60 scaled_rect.width(), scaled_rect.height(), | |
61 libyuv::kFilterBilinear); | |
62 | |
63 // Set alpha channel values to 255 for all pixels. | |
64 // TODO(sergeyu): Fix screen/window capturers to capture alpha channel and | |
65 // remove this code. Currently screen/window capturers (at least some | |
66 // implementations) only capture R, G and B channels and set Alpha to 0. | |
67 // crbug.com/264424 | |
68 for (int y = 0; y < result.height(); ++y) { | |
69 for (int x = 0; x < result.width(); ++x) { | |
70 pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] = | |
71 0xff; | |
72 } | |
73 } | |
74 | |
75 result.unlockPixels(); | |
76 | |
77 return gfx::ImageSkia::CreateFrom1xBitmap(result); | |
78 } | |
79 | |
80 } // namespace | |
81 | |
82 class NativeDesktopMediaList::Worker | |
83 : public webrtc::DesktopCapturer::Callback { | |
84 public: | |
85 Worker(base::WeakPtr<NativeDesktopMediaList> media_list, | |
86 std::unique_ptr<webrtc::ScreenCapturer> screen_capturer, | |
87 std::unique_ptr<webrtc::WindowCapturer> window_capturer); | |
88 ~Worker() override; | |
89 | |
90 void Refresh(const DesktopMediaID::Id& view_dialog_id); | |
91 | |
92 void RefreshThumbnails(const std::vector<DesktopMediaID>& native_ids, | |
93 const gfx::Size& thumbnail_size); | |
94 | |
95 private: | |
96 typedef std::map<DesktopMediaID, uint32_t> ImageHashesMap; | |
97 | |
98 // webrtc::DesktopCapturer::Callback interface. | |
99 void OnCaptureResult(webrtc::DesktopCapturer::Result result, | |
100 std::unique_ptr<webrtc::DesktopFrame> frame) override; | |
101 | |
102 base::WeakPtr<NativeDesktopMediaList> media_list_; | |
103 | |
104 std::unique_ptr<webrtc::ScreenCapturer> screen_capturer_; | |
105 std::unique_ptr<webrtc::WindowCapturer> window_capturer_; | |
106 | |
107 std::unique_ptr<webrtc::DesktopFrame> current_frame_; | |
108 | |
109 ImageHashesMap image_hashes_; | |
110 | |
111 DISALLOW_COPY_AND_ASSIGN(Worker); | |
112 }; | |
113 | |
114 NativeDesktopMediaList::Worker::Worker( | |
115 base::WeakPtr<NativeDesktopMediaList> media_list, | |
116 std::unique_ptr<webrtc::ScreenCapturer> screen_capturer, | |
117 std::unique_ptr<webrtc::WindowCapturer> window_capturer) | |
118 : media_list_(media_list), | |
119 screen_capturer_(std::move(screen_capturer)), | |
120 window_capturer_(std::move(window_capturer)) { | |
121 if (screen_capturer_) | |
122 screen_capturer_->Start(this); | |
123 if (window_capturer_) | |
124 window_capturer_->Start(this); | |
125 } | |
126 | |
127 NativeDesktopMediaList::Worker::~Worker() {} | |
128 | |
129 void NativeDesktopMediaList::Worker::Refresh( | |
130 const DesktopMediaID::Id& view_dialog_id) { | |
131 std::vector<SourceDescription> sources; | |
132 | |
133 if (screen_capturer_) { | |
134 webrtc::ScreenCapturer::ScreenList screens; | |
135 if (screen_capturer_->GetScreenList(&screens)) { | |
136 bool mutiple_screens = screens.size() > 1; | |
137 base::string16 title; | |
138 for (size_t i = 0; i < screens.size(); ++i) { | |
139 if (mutiple_screens) { | |
140 // Just in case 'Screen' is inflected depending on the screen number, | |
141 // use plural formatter. | |
142 title = l10n_util::GetPluralStringFUTF16( | |
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 continue; | |
163 | |
164 DesktopMediaID media_id(DesktopMediaID::TYPE_WINDOW, it->id); | |
165 sources.push_back( | |
166 SourceDescription(media_id, base::UTF8ToUTF16(it->title))); | |
167 } | |
168 } | |
169 } | |
170 | |
171 BrowserThread::PostTask( | |
172 BrowserThread::UI, FROM_HERE, | |
173 base::Bind(&NativeDesktopMediaList::RefreshForAuraWindows, media_list_, | |
174 sources)); | |
175 } | |
176 | |
177 void NativeDesktopMediaList::Worker::RefreshThumbnails( | |
178 const std::vector<DesktopMediaID>& native_ids, | |
179 const gfx::Size& thumbnail_size) { | |
180 ImageHashesMap new_image_hashes; | |
181 | |
182 // Get a thumbnail for each native source. | |
183 for (const auto& id : native_ids) { | |
184 switch (id.type) { | |
185 case DesktopMediaID::TYPE_SCREEN: | |
186 if (!screen_capturer_->SelectScreen(id.id)) | |
187 continue; | |
188 screen_capturer_->Capture(webrtc::DesktopRegion()); | |
189 break; | |
190 | |
191 case DesktopMediaID::TYPE_WINDOW: | |
192 if (!window_capturer_->SelectWindow(id.id)) | |
193 continue; | |
194 window_capturer_->Capture(webrtc::DesktopRegion()); | |
195 break; | |
196 | |
197 default: | |
198 NOTREACHED(); | |
199 } | |
200 | |
201 // Expect that DesktopCapturer to always captures frames synchronously. | |
202 // |current_frame_| may be NULL if capture failed (e.g. because window has | |
203 // been closed). | |
204 if (current_frame_) { | |
205 uint32_t frame_hash = GetFrameHash(current_frame_.get()); | |
206 new_image_hashes[id] = frame_hash; | |
207 | |
208 // Scale the image only if it has changed. | |
209 ImageHashesMap::iterator it = image_hashes_.find(id); | |
210 if (it == image_hashes_.end() || it->second != frame_hash) { | |
211 gfx::ImageSkia thumbnail = | |
212 ScaleDesktopFrame(std::move(current_frame_), thumbnail_size); | |
213 BrowserThread::PostTask( | |
214 BrowserThread::UI, FROM_HERE, | |
215 base::Bind(&NativeDesktopMediaList::UpdateSourceThumbnail, | |
216 media_list_, id, thumbnail)); | |
217 } | |
218 } | |
219 } | |
220 | |
221 image_hashes_.swap(new_image_hashes); | |
222 | |
223 BrowserThread::PostTask( | |
224 BrowserThread::UI, FROM_HERE, | |
225 base::Bind(&NativeDesktopMediaList::UpdateNativeThumbnailsFinished, | |
226 media_list_)); | |
227 } | |
228 | |
229 void NativeDesktopMediaList::Worker::OnCaptureResult( | |
230 webrtc::DesktopCapturer::Result result, | |
231 std::unique_ptr<webrtc::DesktopFrame> frame) { | |
232 current_frame_ = std::move(frame); | |
233 } | |
234 | |
235 NativeDesktopMediaList::NativeDesktopMediaList( | |
236 std::unique_ptr<webrtc::ScreenCapturer> screen_capturer, | |
237 std::unique_ptr<webrtc::WindowCapturer> window_capturer) | |
238 : DesktopMediaListBase( | |
239 base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)), | |
240 weak_factory_(this) { | |
241 base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool(); | |
242 capture_task_runner_ = worker_pool->GetSequencedTaskRunner( | |
243 worker_pool->GetSequenceToken()); | |
244 | |
245 worker_.reset(new Worker(weak_factory_.GetWeakPtr(), | |
246 std::move(screen_capturer), | |
247 std::move(window_capturer))); | |
248 } | |
249 | |
250 NativeDesktopMediaList::~NativeDesktopMediaList() { | |
251 capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release()); | |
252 } | |
253 | |
254 void NativeDesktopMediaList::Refresh() { | |
255 #if defined(USE_AURA) | |
256 DCHECK_EQ(pending_aura_capture_requests_, 0); | |
257 DCHECK(!pending_native_thumbnail_capture_); | |
258 new_aura_thumbnail_hashes_.clear(); | |
259 #endif | |
260 | |
261 capture_task_runner_->PostTask( | |
262 FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()), | |
263 view_dialog_id_.id)); | |
264 } | |
265 | |
266 void NativeDesktopMediaList::RefreshForAuraWindows( | |
267 std::vector<SourceDescription> sources) { | |
268 #if defined(USE_AURA) | |
269 // Associate aura id with native id. | |
270 for (auto& source : sources) { | |
271 if (source.id.type != DesktopMediaID::TYPE_WINDOW) | |
272 continue; | |
273 | |
274 aura::Window* aura_window = NULL; | |
275 #if defined(OS_WIN) | |
276 aura_window = views::DesktopWindowTreeHostWin::GetContentWindowForHWND( | |
277 reinterpret_cast<HWND>(source.id.id)); | |
278 #elif defined(USE_X11) && !defined(OS_CHROMEOS) | |
279 aura_window = | |
280 views::DesktopWindowTreeHostX11::GetContentWindowForXID(source.id.id); | |
281 #endif // defined(USE_X11) && !defined(OS_CHROMEOS) | |
282 if (aura_window) { | |
283 DesktopMediaID aura_id = DesktopMediaID::RegisterAuraWindow( | |
284 DesktopMediaID::TYPE_WINDOW, aura_window); | |
285 source.id.aura_id = aura_id.aura_id; | |
286 } | |
287 } | |
288 #endif // defined(USE_AURA) | |
289 | |
290 UpdateSourcesList(sources); | |
291 | |
292 // OnAuraThumbnailCaptured() and UpdateNativeThumbnailsFinished() are | |
293 // guaranteed to be excuted after RefreshForAuraWindows() and | |
294 // CaptureAuraWindowThumbnail() in the browser UI thread. | |
295 // Therefore pending_aura_capture_requests_ will be set the number of aura | |
296 // windows to be captured and pending_native_thumbnail_capture_ will be set | |
297 // true if native thumbnail capture is needed before OnAuraThumbnailCaptured() | |
298 // or UpdateNativeThumbnailsFinished() are called. | |
299 std::vector<DesktopMediaID> native_ids; | |
300 for (const auto& source : sources) { | |
301 #if defined(USE_AURA) | |
302 if (source.id.aura_id > DesktopMediaID::kNullId) { | |
303 CaptureAuraWindowThumbnail(source.id); | |
304 continue; | |
305 } | |
306 #endif // defined(USE_AURA) | |
307 native_ids.push_back(source.id); | |
308 } | |
309 | |
310 if (native_ids.size() > 0) { | |
311 #if defined(USE_AURA) | |
312 pending_native_thumbnail_capture_ = true; | |
313 #endif | |
314 capture_task_runner_->PostTask( | |
315 FROM_HERE, | |
316 base::Bind(&Worker::RefreshThumbnails, base::Unretained(worker_.get()), | |
317 native_ids, thumbnail_size_)); | |
318 } | |
319 } | |
320 | |
321 void NativeDesktopMediaList::UpdateNativeThumbnailsFinished() { | |
322 #if defined(USE_AURA) | |
323 DCHECK(pending_native_thumbnail_capture_); | |
324 pending_native_thumbnail_capture_ = false; | |
325 // Schedule next refresh if native thumbnail captures finished after aura | |
326 // thumbnail captures. | |
327 if (pending_aura_capture_requests_ == 0) | |
328 ScheduleNextRefresh(); | |
329 #else | |
330 ScheduleNextRefresh(); | |
331 #endif // defined(USE_AURA) | |
332 } | |
333 | |
334 #if defined(USE_AURA) | |
335 | |
336 void NativeDesktopMediaList::CaptureAuraWindowThumbnail( | |
337 const DesktopMediaID& id) { | |
338 gfx::NativeWindow window = DesktopMediaID::GetAuraWindowById(id); | |
339 if (!window) | |
340 return; | |
341 | |
342 gfx::Rect window_rect(window->bounds().width(), window->bounds().height()); | |
343 gfx::Rect scaled_rect = media::ComputeLetterboxRegion( | |
344 gfx::Rect(thumbnail_size_), window_rect.size()); | |
345 | |
346 pending_aura_capture_requests_++; | |
347 ui::GrabWindowSnapshotAndScaleAsync( | |
348 window, window_rect, scaled_rect.size(), BrowserThread::GetBlockingPool(), | |
349 base::Bind(&NativeDesktopMediaList::OnAuraThumbnailCaptured, | |
350 weak_factory_.GetWeakPtr(), id)); | |
351 } | |
352 | |
353 void NativeDesktopMediaList::OnAuraThumbnailCaptured(const DesktopMediaID& id, | |
354 const gfx::Image& image) { | |
355 if (!image.IsEmpty()) { | |
356 // Only new or changed thumbnail need update. | |
357 new_aura_thumbnail_hashes_[id] = GetImageHash(image); | |
358 if (!previous_aura_thumbnail_hashes_.count(id) || | |
359 previous_aura_thumbnail_hashes_[id] != new_aura_thumbnail_hashes_[id]) { | |
360 UpdateSourceThumbnail(id, image.AsImageSkia()); | |
361 } | |
362 } | |
363 | |
364 // After all aura windows are processed, schedule next refresh; | |
365 pending_aura_capture_requests_--; | |
366 DCHECK_GE(pending_aura_capture_requests_, 0); | |
367 if (pending_aura_capture_requests_ == 0) { | |
368 previous_aura_thumbnail_hashes_ = std::move(new_aura_thumbnail_hashes_); | |
369 // Schedule next refresh if aura thumbnail captures finished after native | |
370 // thumbnail captures. | |
371 if (!pending_native_thumbnail_capture_) | |
372 ScheduleNextRefresh(); | |
373 } | |
374 } | |
375 | |
376 #endif // defined(USE_AURA) | |
OLD | NEW |