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/desktop_media_picker_model.h"
10 #include "base/logging.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/threading/sequenced_worker_pool.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "grit/generated_resources.h"
15 #include "media/base/video_util.h"
16 #include "third_party/libyuv/include/libyuv/scale_argb.h"
17 #include "third_party/skia/include/core/SkBitmap.h"
18 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
19 #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h"
20 #include "third_party/webrtc/modules/desktop_capture/window_capturer.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/gfx/skia_util.h"
24 using content::BrowserThread;
25 using content::DesktopMediaID;
29 // Update the list every second.
30 const int kDefaultUpdatePeriod = 1000;
32 // Returns a hash of a DesktopFrame content to detect when image for a desktop
33 // media source has changed.
34 uint32 GetFrameHash(webrtc::DesktopFrame* frame) {
35 int data_size = frame->stride() * frame->size().height();
36 return base::SuperFastHash(reinterpret_cast<char*>(frame->data()), data_size);
39 gfx::ImageSkia ScaleDesktopFrame(scoped_ptr<webrtc::DesktopFrame> frame,
41 gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
42 gfx::Rect(0, 0, size.width(), size.height()),
43 gfx::Size(frame->size().width(), frame->size().height()));
46 result.setConfig(SkBitmap::kARGB_8888_Config,
47 scaled_rect.width(), scaled_rect.height(), 0,
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);
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.
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] =
71 result.unlockPixels();
73 return gfx::ImageSkia::CreateFrom1xBitmap(result);
78 DesktopMediaPickerModel::Source::Source(DesktopMediaID id, const string16& name)
83 DesktopMediaPickerModelImpl::SourceDescription::SourceDescription(
90 class DesktopMediaPickerModelImpl::Worker
91 : public webrtc::DesktopCapturer::Callback {
93 Worker(base::WeakPtr<DesktopMediaPickerModelImpl> model,
94 scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
95 scoped_ptr<webrtc::WindowCapturer> window_capturer);
98 void Refresh(const gfx::Size& thumbnail_size,
99 content::DesktopMediaID::Id view_dialog_id);
102 typedef std::map<DesktopMediaID, uint32> ImageHashesMap;
104 // webrtc::DesktopCapturer::Callback interface.
105 virtual webrtc::SharedMemory* CreateSharedMemory(size_t size) OVERRIDE;
106 virtual void OnCaptureCompleted(webrtc::DesktopFrame* frame) OVERRIDE;
108 base::WeakPtr<DesktopMediaPickerModelImpl> model_;
110 scoped_ptr<webrtc::ScreenCapturer> screen_capturer_;
111 scoped_ptr<webrtc::WindowCapturer> window_capturer_;
113 scoped_ptr<webrtc::DesktopFrame> current_frame_;
115 ImageHashesMap image_hashes_;
117 DISALLOW_COPY_AND_ASSIGN(Worker);
120 DesktopMediaPickerModelImpl::Worker::Worker(
121 base::WeakPtr<DesktopMediaPickerModelImpl> model,
122 scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
123 scoped_ptr<webrtc::WindowCapturer> window_capturer)
125 screen_capturer_(screen_capturer.Pass()),
126 window_capturer_(window_capturer.Pass()) {
127 if (screen_capturer_)
128 screen_capturer_->Start(this);
129 if (window_capturer_)
130 window_capturer_->Start(this);
133 DesktopMediaPickerModelImpl::Worker::~Worker() {}
135 void DesktopMediaPickerModelImpl::Worker::Refresh(
136 const gfx::Size& thumbnail_size,
137 content::DesktopMediaID::Id view_dialog_id) {
138 std::vector<SourceDescription> sources;
140 if (screen_capturer_) {
141 // TODO(sergeyu): Enumerate each screen when ScreenCapturer supports it.
142 sources.push_back(SourceDescription(DesktopMediaID(
143 DesktopMediaID::TYPE_SCREEN, 0),
144 l10n_util::GetStringUTF16(IDS_DESKTOP_MEDIA_PICKER_SCREEN_NAME)));
147 if (window_capturer_) {
148 webrtc::WindowCapturer::WindowList windows;
149 if (window_capturer_->GetWindowList(&windows)) {
150 for (webrtc::WindowCapturer::WindowList::iterator it = windows.begin();
151 it != windows.end(); ++it) {
152 // Skip the picker dialog window.
153 if (it->id != view_dialog_id) {
154 sources.push_back(SourceDescription(
155 DesktopMediaID(DesktopMediaID::TYPE_WINDOW, it->id),
156 base::UTF8ToUTF16(it->title)));
162 // Sort the list of sources so that they appear in a predictable order.
163 std::sort(sources.begin(), sources.end(), CompareSources);
165 // Update list of windows before updating thumbnails.
166 BrowserThread::PostTask(
167 BrowserThread::UI, FROM_HERE,
168 base::Bind(&DesktopMediaPickerModelImpl::OnSourcesList, model_, sources));
170 ImageHashesMap new_image_hashes;
172 // Get a thumbnail for each source.
173 for (size_t i = 0; i < sources.size(); ++i) {
174 SourceDescription& source = sources[i];
175 switch (source.id.type) {
176 case DesktopMediaID::TYPE_SCREEN:
177 screen_capturer_->Capture(webrtc::DesktopRegion());
178 DCHECK(current_frame_);
181 case DesktopMediaID::TYPE_WINDOW:
182 if (!window_capturer_->SelectWindow(source.id.id))
184 window_capturer_->Capture(webrtc::DesktopRegion());
191 // Expect that DesktopCapturer to always captures frames synchronously.
192 // |current_frame_| may be NULL if capture failed (e.g. because window has
194 if (current_frame_) {
195 uint32 frame_hash = GetFrameHash(current_frame_.get());
196 new_image_hashes[source.id] = frame_hash;
198 // Scale the image only if it has changed.
199 ImageHashesMap::iterator it = image_hashes_.find(source.id);
200 if (it == image_hashes_.end() || it->second != frame_hash) {
201 gfx::ImageSkia thumbnail =
202 ScaleDesktopFrame(current_frame_.Pass(), thumbnail_size);
203 BrowserThread::PostTask(
204 BrowserThread::UI, FROM_HERE,
205 base::Bind(&DesktopMediaPickerModelImpl::OnSourceThumbnail, model_,
211 image_hashes_.swap(new_image_hashes);
213 BrowserThread::PostTask(
214 BrowserThread::UI, FROM_HERE,
215 base::Bind(&DesktopMediaPickerModelImpl::OnRefreshFinished, model_));
218 webrtc::SharedMemory* DesktopMediaPickerModelImpl::Worker::CreateSharedMemory(
223 void DesktopMediaPickerModelImpl::Worker::OnCaptureCompleted(
224 webrtc::DesktopFrame* frame) {
225 current_frame_.reset(frame);
228 DesktopMediaPickerModelImpl::DesktopMediaPickerModelImpl(
229 scoped_ptr<webrtc::ScreenCapturer> screen_capturer,
230 scoped_ptr<webrtc::WindowCapturer> window_capturer)
231 : screen_capturer_(screen_capturer.Pass()),
232 window_capturer_(window_capturer.Pass()),
233 update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)),
234 thumbnail_size_(100, 100),
237 weak_factory_(this) {
238 base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool();
239 capture_task_runner_ = worker_pool->GetSequencedTaskRunner(
240 worker_pool->GetSequenceToken());
243 DesktopMediaPickerModelImpl::~DesktopMediaPickerModelImpl() {
244 capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release());
247 void DesktopMediaPickerModelImpl::SetUpdatePeriod(base::TimeDelta period) {
249 update_period_ = period;
252 void DesktopMediaPickerModelImpl::SetThumbnailSize(
253 const gfx::Size& thumbnail_size) {
254 thumbnail_size_ = thumbnail_size;
257 void DesktopMediaPickerModelImpl::SetViewDialogWindowId(
258 content::DesktopMediaID::Id dialog_id) {
259 view_dialog_id_ = dialog_id;
262 void DesktopMediaPickerModelImpl::StartUpdating(Observer* observer) {
264 DCHECK(screen_capturer_ || window_capturer_);
266 observer_ = observer;
268 worker_.reset(new Worker(weak_factory_.GetWeakPtr(),
269 screen_capturer_.Pass(), window_capturer_.Pass()));
273 int DesktopMediaPickerModelImpl::source_count() const {
274 return sources_.size();
277 const DesktopMediaPickerModel::Source& DesktopMediaPickerModelImpl::source(
279 return sources_[index];
283 bool DesktopMediaPickerModelImpl::CompareSources(const SourceDescription& a,
284 const SourceDescription& b) {
288 void DesktopMediaPickerModelImpl::Refresh() {
289 capture_task_runner_->PostTask(
290 FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()),
291 thumbnail_size_, view_dialog_id_));
294 void DesktopMediaPickerModelImpl::OnSourcesList(
295 const std::vector<SourceDescription>& new_sources) {
296 // Step through |new_sources| adding and removing entries from |sources_|, and
297 // notifying the |observer_|, until two match. Requires that |sources| and
298 // |sources_| have the same ordering.
300 while (pos < sources_.size() || pos < new_sources.size()) {
301 // If |sources_[pos]| is not in |new_sources| then remove it.
302 if (pos < sources_.size() &&
303 (pos == new_sources.size() || sources_[pos].id < new_sources[pos].id)) {
304 sources_.erase(sources_.begin() + pos);
305 observer_->OnSourceRemoved(pos);
309 if (pos == sources_.size() || !(sources_[pos].id == new_sources[pos].id)) {
310 sources_.insert(sources_.begin() + pos,
311 Source(new_sources[pos].id, new_sources[pos].name));
312 observer_->OnSourceAdded(pos);
313 } else if (sources_[pos].name != new_sources[pos].name) {
314 sources_[pos].name = new_sources[pos].name;
315 observer_->OnSourceNameChanged(pos);
321 DCHECK_EQ(new_sources.size(), sources_.size());
324 void DesktopMediaPickerModelImpl::OnSourceThumbnail(int index,
325 const gfx::ImageSkia& image) {
326 DCHECK_LT(index, static_cast<int>(sources_.size()));
327 sources_[index].thumbnail = image;
328 observer_->OnSourceThumbnailChanged(index);
331 void DesktopMediaPickerModelImpl::OnRefreshFinished() {
332 BrowserThread::PostDelayedTask(
333 BrowserThread::UI, FROM_HERE,
334 base::Bind(&DesktopMediaPickerModelImpl::Refresh,
335 weak_factory_.GetWeakPtr()),