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/desktop_media_picker_model.h" 6 7 #include <map> 8 9 #include "base/hash.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" 23 24 using content::BrowserThread; 25 using content::DesktopMediaID; 26 27 namespace { 28 29 // Update the list every second. 30 const int kDefaultUpdatePeriod = 1000; 31 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); 37 } 38 39 gfx::ImageSkia ScaleDesktopFrame(scoped_ptr<webrtc::DesktopFrame> frame, 40 gfx::Size size) { 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())); 44 45 SkBitmap result; 46 result.setConfig(SkBitmap::kARGB_8888_Config, 47 scaled_rect.width(), scaled_rect.height()); 48 result.allocPixels(); 49 result.lockPixels(); 50 51 uint8* pixels_data = reinterpret_cast<uint8*>(result.getPixels()); 52 libyuv::ARGBScale(frame->data(), frame->stride(), 53 frame->size().width(), frame->size().height(), 54 pixels_data, result.rowBytes(), 55 scaled_rect.width(), scaled_rect.height(), 56 libyuv::kFilterBilinear); 57 58 // Set alpha channel values to 255 for all pixels. 59 // TODO(sergeyu): Fix screen/window capturers to capture alpha channel and 60 // remove this code. Currently screen/window capturers (at least some 61 // implementations) only capture R, G and B channels and set Alpha to 0. 62 // crbug.com/264424 63 for (int y = 0; y < result.height(); ++y) { 64 for (int x = 0; x < result.width(); ++x) { 65 pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] = 66 0xff; 67 } 68 } 69 70 result.unlockPixels(); 71 result.setIsOpaque(true); 72 73 return gfx::ImageSkia::CreateFrom1xBitmap(result); 74 } 75 76 } // namespace 77 78 DesktopMediaPickerModel::Source::Source(DesktopMediaID id, const string16& name) 79 : id(id), 80 name(name) { 81 } 82 83 DesktopMediaPickerModel::SourceDescription::SourceDescription( 84 DesktopMediaID id, 85 const string16& name) 86 : id(id), 87 name(name) { 88 } 89 90 class DesktopMediaPickerModel::Worker 91 : public webrtc::DesktopCapturer::Callback { 92 public: 93 Worker(base::WeakPtr<DesktopMediaPickerModel> model, 94 scoped_ptr<webrtc::ScreenCapturer> screen_capturer, 95 scoped_ptr<webrtc::WindowCapturer> window_capturer); 96 virtual ~Worker(); 97 98 void Refresh(const gfx::Size& thumbnail_size); 99 100 private: 101 typedef std::map<DesktopMediaID, uint32> ImageHashesMap; 102 103 // webrtc::DesktopCapturer::Callback interface. 104 virtual webrtc::SharedMemory* CreateSharedMemory(size_t size) OVERRIDE; 105 virtual void OnCaptureCompleted(webrtc::DesktopFrame* frame) OVERRIDE; 106 107 base::WeakPtr<DesktopMediaPickerModel> model_; 108 109 scoped_ptr<webrtc::ScreenCapturer> screen_capturer_; 110 scoped_ptr<webrtc::WindowCapturer> window_capturer_; 111 112 scoped_ptr<webrtc::DesktopFrame> current_frame_; 113 114 ImageHashesMap image_hashes_; 115 116 DISALLOW_COPY_AND_ASSIGN(Worker); 117 }; 118 119 DesktopMediaPickerModel::Worker::Worker( 120 base::WeakPtr<DesktopMediaPickerModel> model, 121 scoped_ptr<webrtc::ScreenCapturer> screen_capturer, 122 scoped_ptr<webrtc::WindowCapturer> window_capturer) 123 : model_(model), 124 screen_capturer_(screen_capturer.Pass()), 125 window_capturer_(window_capturer.Pass()) { 126 if (screen_capturer_) 127 screen_capturer_->Start(this); 128 if (window_capturer_) 129 window_capturer_->Start(this); 130 } 131 132 DesktopMediaPickerModel::Worker::~Worker() {} 133 134 void DesktopMediaPickerModel::Worker::Refresh(const gfx::Size& thumbnail_size) { 135 std::vector<SourceDescription> sources; 136 137 if (screen_capturer_) { 138 // TODO(sergeyu): Enumerate each screen when ScreenCapturer supports it. 139 sources.push_back(SourceDescription(DesktopMediaID( 140 DesktopMediaID::TYPE_SCREEN, 0), 141 l10n_util::GetStringUTF16(IDS_DESKTOP_MEDIA_PICKER_SCREEN_NAME))); 142 } 143 144 if (window_capturer_) { 145 webrtc::WindowCapturer::WindowList windows; 146 if (window_capturer_->GetWindowList(&windows)) { 147 for (webrtc::WindowCapturer::WindowList::iterator it = windows.begin(); 148 it != windows.end(); ++it) { 149 sources.push_back(SourceDescription( 150 DesktopMediaID(DesktopMediaID::TYPE_WINDOW, it->id), 151 base::UTF8ToUTF16(it->title))); 152 } 153 } 154 } 155 156 // Sort the list of sources so that they appear in a predictable order. 157 std::sort(sources.begin(), sources.end(), CompareSources); 158 159 // Update list of windows before updating thumbnails. 160 BrowserThread::PostTask( 161 BrowserThread::UI, FROM_HERE, 162 base::Bind(&DesktopMediaPickerModel::OnSourcesList, model_, sources)); 163 164 ImageHashesMap new_image_hashes; 165 166 // Get a thumbnail for each source. 167 for (size_t i = 0; i < sources.size(); ++i) { 168 SourceDescription& source = sources[i]; 169 switch (source.id.type) { 170 case DesktopMediaID::TYPE_SCREEN: 171 screen_capturer_->Capture(webrtc::DesktopRegion()); 172 DCHECK(current_frame_); 173 break; 174 175 case DesktopMediaID::TYPE_WINDOW: 176 if (!window_capturer_->SelectWindow(source.id.id)) 177 continue; 178 window_capturer_->Capture(webrtc::DesktopRegion()); 179 break; 180 181 default: 182 NOTREACHED(); 183 } 184 185 // Expect that DesktopCapturer to always captures frames synchronously. 186 // |current_frame_| may be NULL if capture failed (e.g. because window has 187 // been closed). 188 if (current_frame_) { 189 uint32 frame_hash = GetFrameHash(current_frame_.get()); 190 new_image_hashes[source.id] = frame_hash; 191 192 // Scale the image only if it has changed. 193 ImageHashesMap::iterator it = image_hashes_.find(source.id); 194 if (it == image_hashes_.end() || it->second != frame_hash) { 195 gfx::ImageSkia thumbnail = 196 ScaleDesktopFrame(current_frame_.Pass(), thumbnail_size); 197 BrowserThread::PostTask( 198 BrowserThread::UI, FROM_HERE, 199 base::Bind(&DesktopMediaPickerModel::OnSourceThumbnail, model_, 200 i, thumbnail)); 201 } 202 } 203 } 204 205 image_hashes_.swap(new_image_hashes); 206 207 BrowserThread::PostTask( 208 BrowserThread::UI, FROM_HERE, 209 base::Bind(&DesktopMediaPickerModel::OnRefreshFinished, model_)); 210 } 211 212 webrtc::SharedMemory* DesktopMediaPickerModel::Worker::CreateSharedMemory( 213 size_t size) { 214 return NULL; 215 } 216 217 void DesktopMediaPickerModel::Worker::OnCaptureCompleted( 218 webrtc::DesktopFrame* frame) { 219 current_frame_.reset(frame); 220 } 221 222 DesktopMediaPickerModel::DesktopMediaPickerModel() 223 : update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)), 224 thumbnail_size_(100, 100), 225 observer_(NULL), 226 weak_factory_(this) { 227 base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool(); 228 capture_task_runner_ = worker_pool->GetSequencedTaskRunner( 229 worker_pool->GetSequenceToken()); 230 } 231 232 DesktopMediaPickerModel::~DesktopMediaPickerModel() { 233 capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release()); 234 } 235 236 void DesktopMediaPickerModel::SetCapturers( 237 scoped_ptr<webrtc::ScreenCapturer> screen_capturer, 238 scoped_ptr<webrtc::WindowCapturer> window_capturer) { 239 DCHECK(!observer_); 240 screen_capturer_ = screen_capturer.Pass(); 241 window_capturer_ = window_capturer.Pass(); 242 } 243 244 void DesktopMediaPickerModel::SetUpdatePeriod(base::TimeDelta period) { 245 DCHECK(!observer_); 246 update_period_ = period; 247 } 248 249 void DesktopMediaPickerModel::SetThumbnailSize( 250 const gfx::Size& thumbnail_size) { 251 thumbnail_size_ = thumbnail_size; 252 } 253 254 void DesktopMediaPickerModel::StartUpdating(Observer* observer) { 255 DCHECK(!observer_); 256 DCHECK(screen_capturer_ || window_capturer_); 257 258 observer_ = observer; 259 260 worker_.reset(new Worker(weak_factory_.GetWeakPtr(), 261 screen_capturer_.Pass(), window_capturer_.Pass())); 262 Refresh(); 263 } 264 265 // static 266 bool DesktopMediaPickerModel::CompareSources(const SourceDescription& a, 267 const SourceDescription& b) { 268 return a.id < b.id; 269 } 270 271 void DesktopMediaPickerModel::Refresh() { 272 capture_task_runner_->PostTask( 273 FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()), 274 thumbnail_size_)); 275 } 276 277 void DesktopMediaPickerModel::OnSourcesList( 278 const std::vector<SourceDescription>& new_sources) { 279 // Step through |new_sources| adding and removing entries from |sources_|, and 280 // notifying the |observer_|, until two match. Requires that |sources| and 281 // |sources_| have the same ordering. 282 size_t pos = 0; 283 while (pos < sources_.size() || pos < new_sources.size()) { 284 // If |sources_[pos]| is not in |new_sources| then remove it. 285 if (pos < sources_.size() && 286 (pos == new_sources.size() || sources_[pos].id < new_sources[pos].id)) { 287 sources_.erase(sources_.begin() + pos); 288 observer_->OnSourceRemoved(pos); 289 continue; 290 } 291 292 if (pos == sources_.size() || !(sources_[pos].id == new_sources[pos].id)) { 293 sources_.insert(sources_.begin() + pos, 294 Source(new_sources[pos].id, new_sources[pos].name)); 295 observer_->OnSourceAdded(pos); 296 } else if (sources_[pos].name != new_sources[pos].name) { 297 sources_[pos].name = new_sources[pos].name; 298 observer_->OnSourceNameChanged(pos); 299 } 300 301 ++pos; 302 } 303 304 DCHECK_EQ(new_sources.size(), sources_.size()); 305 } 306 307 void DesktopMediaPickerModel::OnSourceThumbnail(int index, 308 const gfx::ImageSkia& image) { 309 DCHECK_LT(index, static_cast<int>(sources_.size())); 310 sources_[index].thumbnail = image; 311 observer_->OnSourceThumbnailChanged(index); 312 } 313 314 void DesktopMediaPickerModel::OnRefreshFinished() { 315 BrowserThread::PostDelayedTask( 316 BrowserThread::UI, FROM_HERE, 317 base::Bind(&DesktopMediaPickerModel::Refresh, weak_factory_.GetWeakPtr()), 318 update_period_); 319 } 320