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 <map> 8#include <set> 9#include <sstream> 10 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 "chrome/grit/generated_resources.h" 17#include "content/public/browser/browser_thread.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" 26 27using content::BrowserThread; 28using content::DesktopMediaID; 29 30namespace { 31 32// Update the list every second. 33const int kDefaultUpdatePeriod = 1000; 34 35// Returns a hash of a DesktopFrame content to detect when image for a desktop 36// media source has changed. 37uint32 GetFrameHash(webrtc::DesktopFrame* frame) { 38 int data_size = frame->stride() * frame->size().height(); 39 return base::SuperFastHash(reinterpret_cast<char*>(frame->data()), data_size); 40} 41 42gfx::ImageSkia ScaleDesktopFrame(scoped_ptr<webrtc::DesktopFrame> frame, 43 gfx::Size size) { 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())); 47 48 SkBitmap result; 49 result.allocN32Pixels(scaled_rect.width(), scaled_rect.height(), true); 50 result.lockPixels(); 51 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); 58 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. 63 // crbug.com/264424 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] = 67 0xff; 68 } 69 } 70 71 result.unlockPixels(); 72 73 return gfx::ImageSkia::CreateFrom1xBitmap(result); 74} 75 76} // namespace 77 78NativeDesktopMediaList::SourceDescription::SourceDescription( 79 DesktopMediaID id, 80 const base::string16& name) 81 : id(id), 82 name(name) { 83} 84 85class NativeDesktopMediaList::Worker 86 : public webrtc::DesktopCapturer::Callback { 87 public: 88 Worker(base::WeakPtr<NativeDesktopMediaList> media_list, 89 scoped_ptr<webrtc::ScreenCapturer> screen_capturer, 90 scoped_ptr<webrtc::WindowCapturer> window_capturer); 91 virtual ~Worker(); 92 93 void Refresh(const gfx::Size& thumbnail_size, 94 content::DesktopMediaID::Id view_dialog_id); 95 96 private: 97 typedef std::map<DesktopMediaID, uint32> ImageHashesMap; 98 99 // webrtc::DesktopCapturer::Callback interface. 100 virtual webrtc::SharedMemory* CreateSharedMemory(size_t size) OVERRIDE; 101 virtual void OnCaptureCompleted(webrtc::DesktopFrame* frame) OVERRIDE; 102 103 base::WeakPtr<NativeDesktopMediaList> media_list_; 104 105 scoped_ptr<webrtc::ScreenCapturer> screen_capturer_; 106 scoped_ptr<webrtc::WindowCapturer> window_capturer_; 107 108 scoped_ptr<webrtc::DesktopFrame> current_frame_; 109 110 ImageHashesMap image_hashes_; 111 112 DISALLOW_COPY_AND_ASSIGN(Worker); 113}; 114 115NativeDesktopMediaList::Worker::Worker( 116 base::WeakPtr<NativeDesktopMediaList> media_list, 117 scoped_ptr<webrtc::ScreenCapturer> screen_capturer, 118 scoped_ptr<webrtc::WindowCapturer> window_capturer) 119 : media_list_(media_list), 120 screen_capturer_(screen_capturer.Pass()), 121 window_capturer_(window_capturer.Pass()) { 122 if (screen_capturer_) 123 screen_capturer_->Start(this); 124 if (window_capturer_) 125 window_capturer_->Start(this); 126} 127 128NativeDesktopMediaList::Worker::~Worker() {} 129 130void NativeDesktopMediaList::Worker::Refresh( 131 const gfx::Size& thumbnail_size, 132 content::DesktopMediaID::Id view_dialog_id) { 133 std::vector<SourceDescription> sources; 134 135 if (screen_capturer_) { 136 webrtc::ScreenCapturer::ScreenList screens; 137 if (screen_capturer_->GetScreenList(&screens)) { 138 bool mutiple_screens = screens.size() > 1; 139 base::string16 title; 140 for (size_t i = 0; i < screens.size(); ++i) { 141 if (mutiple_screens) { 142 title = l10n_util::GetStringFUTF16Int( 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 sources.push_back(SourceDescription( 163 DesktopMediaID(DesktopMediaID::TYPE_WINDOW, it->id), 164 base::UTF8ToUTF16(it->title))); 165 } 166 } 167 } 168 } 169 // Update list of windows before updating thumbnails. 170 BrowserThread::PostTask( 171 BrowserThread::UI, FROM_HERE, 172 base::Bind(&NativeDesktopMediaList::OnSourcesList, 173 media_list_, sources)); 174 175 ImageHashesMap new_image_hashes; 176 177 // Get a thumbnail for each source. 178 for (size_t i = 0; i < sources.size(); ++i) { 179 SourceDescription& source = sources[i]; 180 switch (source.id.type) { 181 case DesktopMediaID::TYPE_SCREEN: 182 if (!screen_capturer_->SelectScreen(source.id.id)) 183 continue; 184 screen_capturer_->Capture(webrtc::DesktopRegion()); 185 break; 186 187 case DesktopMediaID::TYPE_WINDOW: 188 if (!window_capturer_->SelectWindow(source.id.id)) 189 continue; 190 window_capturer_->Capture(webrtc::DesktopRegion()); 191 break; 192 193 default: 194 NOTREACHED(); 195 } 196 197 // Expect that DesktopCapturer to always captures frames synchronously. 198 // |current_frame_| may be NULL if capture failed (e.g. because window has 199 // been closed). 200 if (current_frame_) { 201 uint32 frame_hash = GetFrameHash(current_frame_.get()); 202 new_image_hashes[source.id] = frame_hash; 203 204 // Scale the image only if it has changed. 205 ImageHashesMap::iterator it = image_hashes_.find(source.id); 206 if (it == image_hashes_.end() || it->second != frame_hash) { 207 gfx::ImageSkia thumbnail = 208 ScaleDesktopFrame(current_frame_.Pass(), thumbnail_size); 209 BrowserThread::PostTask( 210 BrowserThread::UI, FROM_HERE, 211 base::Bind(&NativeDesktopMediaList::OnSourceThumbnail, 212 media_list_, i, thumbnail)); 213 } 214 } 215 } 216 217 image_hashes_.swap(new_image_hashes); 218 219 BrowserThread::PostTask( 220 BrowserThread::UI, FROM_HERE, 221 base::Bind(&NativeDesktopMediaList::OnRefreshFinished, media_list_)); 222} 223 224webrtc::SharedMemory* NativeDesktopMediaList::Worker::CreateSharedMemory( 225 size_t size) { 226 return NULL; 227} 228 229void NativeDesktopMediaList::Worker::OnCaptureCompleted( 230 webrtc::DesktopFrame* frame) { 231 current_frame_.reset(frame); 232} 233 234NativeDesktopMediaList::NativeDesktopMediaList( 235 scoped_ptr<webrtc::ScreenCapturer> screen_capturer, 236 scoped_ptr<webrtc::WindowCapturer> window_capturer) 237 : screen_capturer_(screen_capturer.Pass()), 238 window_capturer_(window_capturer.Pass()), 239 update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)), 240 thumbnail_size_(100, 100), 241 view_dialog_id_(-1), 242 observer_(NULL), 243 weak_factory_(this) { 244 base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool(); 245 capture_task_runner_ = worker_pool->GetSequencedTaskRunner( 246 worker_pool->GetSequenceToken()); 247} 248 249NativeDesktopMediaList::~NativeDesktopMediaList() { 250 capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release()); 251} 252 253void NativeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period) { 254 DCHECK(!observer_); 255 update_period_ = period; 256} 257 258void NativeDesktopMediaList::SetThumbnailSize( 259 const gfx::Size& thumbnail_size) { 260 thumbnail_size_ = thumbnail_size; 261} 262 263void NativeDesktopMediaList::SetViewDialogWindowId( 264 content::DesktopMediaID::Id dialog_id) { 265 view_dialog_id_ = dialog_id; 266} 267 268void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) { 269 DCHECK(!observer_); 270 DCHECK(screen_capturer_ || window_capturer_); 271 272 observer_ = observer; 273 274 worker_.reset(new Worker(weak_factory_.GetWeakPtr(), 275 screen_capturer_.Pass(), window_capturer_.Pass())); 276 Refresh(); 277} 278 279int NativeDesktopMediaList::GetSourceCount() const { 280 return sources_.size(); 281} 282 283const DesktopMediaList::Source& NativeDesktopMediaList::GetSource( 284 int index) const { 285 return sources_[index]; 286} 287 288void NativeDesktopMediaList::Refresh() { 289 capture_task_runner_->PostTask( 290 FROM_HERE, base::Bind(&Worker::Refresh, base::Unretained(worker_.get()), 291 thumbnail_size_, view_dialog_id_)); 292} 293 294void NativeDesktopMediaList::OnSourcesList( 295 const std::vector<SourceDescription>& new_sources) { 296 typedef std::set<content::DesktopMediaID> SourceSet; 297 SourceSet new_source_set; 298 for (size_t i = 0; i < new_sources.size(); ++i) { 299 new_source_set.insert(new_sources[i].id); 300 } 301 // Iterate through the old sources to find the removed sources. 302 for (size_t i = 0; i < sources_.size(); ++i) { 303 if (new_source_set.find(sources_[i].id) == new_source_set.end()) { 304 sources_.erase(sources_.begin() + i); 305 observer_->OnSourceRemoved(i); 306 --i; 307 } 308 } 309 // Iterate through the new sources to find the added sources. 310 if (new_sources.size() > sources_.size()) { 311 SourceSet old_source_set; 312 for (size_t i = 0; i < sources_.size(); ++i) { 313 old_source_set.insert(sources_[i].id); 314 } 315 316 for (size_t i = 0; i < new_sources.size(); ++i) { 317 if (old_source_set.find(new_sources[i].id) == old_source_set.end()) { 318 sources_.insert(sources_.begin() + i, Source()); 319 sources_[i].id = new_sources[i].id; 320 sources_[i].name = new_sources[i].name; 321 observer_->OnSourceAdded(i); 322 } 323 } 324 } 325 DCHECK_EQ(new_sources.size(), sources_.size()); 326 327 // Find the moved/changed sources. 328 size_t pos = 0; 329 while (pos < sources_.size()) { 330 if (!(sources_[pos].id == new_sources[pos].id)) { 331 // Find the source that should be moved to |pos|, starting from |pos + 1| 332 // of |sources_|, because entries before |pos| should have been sorted. 333 size_t old_pos = pos + 1; 334 for (; old_pos < sources_.size(); ++old_pos) { 335 if (sources_[old_pos].id == new_sources[pos].id) 336 break; 337 } 338 DCHECK(sources_[old_pos].id == new_sources[pos].id); 339 340 // Move the source from |old_pos| to |pos|. 341 Source temp = sources_[old_pos]; 342 sources_.erase(sources_.begin() + old_pos); 343 sources_.insert(sources_.begin() + pos, temp); 344 345 observer_->OnSourceMoved(old_pos, pos); 346 } 347 348 if (sources_[pos].name != new_sources[pos].name) { 349 sources_[pos].name = new_sources[pos].name; 350 observer_->OnSourceNameChanged(pos); 351 } 352 ++pos; 353 } 354} 355 356void NativeDesktopMediaList::OnSourceThumbnail( 357 int index, 358 const gfx::ImageSkia& image) { 359 DCHECK_LT(index, static_cast<int>(sources_.size())); 360 sources_[index].thumbnail = image; 361 observer_->OnSourceThumbnailChanged(index); 362} 363 364void NativeDesktopMediaList::OnRefreshFinished() { 365 BrowserThread::PostDelayedTask( 366 BrowserThread::UI, FROM_HERE, 367 base::Bind(&NativeDesktopMediaList::Refresh, 368 weak_factory_.GetWeakPtr()), 369 update_period_); 370} 371