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 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 "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" 24 25 using content::BrowserThread; 26 using content::DesktopMediaID; 27 28 namespace { 29 30 // Update the list every second. 31 const int kDefaultUpdatePeriod = 1000; 32 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); 38 } 39 40 gfx::ImageSkia ScaleDesktopFrame(scoped_ptr<webrtc::DesktopFrame> frame, 41 gfx::Size size) { 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())); 45 46 SkBitmap result; 47 result.setConfig(SkBitmap::kARGB_8888_Config, 48 scaled_rect.width(), scaled_rect.height(), 0, 49 kOpaque_SkAlphaType); 50 result.allocPixels(); 51 result.lockPixels(); 52 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); 59 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. 64 // crbug.com/264424 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] = 68 0xff; 69 } 70 } 71 72 result.unlockPixels(); 73 74 return gfx::ImageSkia::CreateFrom1xBitmap(result); 75 } 76 77 } // namespace 78 79 NativeDesktopMediaList::SourceDescription::SourceDescription( 80 DesktopMediaID id, 81 const base::string16& name) 82 : id(id), 83 name(name) { 84 } 85 86 class NativeDesktopMediaList::Worker 87 : public webrtc::DesktopCapturer::Callback { 88 public: 89 Worker(base::WeakPtr<NativeDesktopMediaList> media_list, 90 scoped_ptr<webrtc::ScreenCapturer> screen_capturer, 91 scoped_ptr<webrtc::WindowCapturer> window_capturer); 92 virtual ~Worker(); 93 94 void Refresh(const gfx::Size& thumbnail_size, 95 content::DesktopMediaID::Id view_dialog_id); 96 97 private: 98 typedef std::map<DesktopMediaID, uint32> ImageHashesMap; 99 100 // webrtc::DesktopCapturer::Callback interface. 101 virtual webrtc::SharedMemory* CreateSharedMemory(size_t size) OVERRIDE; 102 virtual void OnCaptureCompleted(webrtc::DesktopFrame* frame) OVERRIDE; 103 104 base::WeakPtr<NativeDesktopMediaList> media_list_; 105 106 scoped_ptr<webrtc::ScreenCapturer> screen_capturer_; 107 scoped_ptr<webrtc::WindowCapturer> window_capturer_; 108 109 scoped_ptr<webrtc::DesktopFrame> current_frame_; 110 111 ImageHashesMap image_hashes_; 112 113 DISALLOW_COPY_AND_ASSIGN(Worker); 114 }; 115 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); 127 } 128 129 NativeDesktopMediaList::Worker::~Worker() {} 130 131 void NativeDesktopMediaList::Worker::Refresh( 132 const gfx::Size& thumbnail_size, 133 content::DesktopMediaID::Id view_dialog_id) { 134 std::vector<SourceDescription> sources; 135 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))); 141 } 142 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))); 153 } 154 } 155 } 156 } 157 158 // Sort the list of sources so that they appear in a predictable order. 159 std::sort(sources.begin(), sources.end(), CompareSources); 160 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)); 166 167 ImageHashesMap new_image_hashes; 168 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_); 176 break; 177 178 case DesktopMediaID::TYPE_WINDOW: 179 if (!window_capturer_->SelectWindow(source.id.id)) 180 continue; 181 window_capturer_->Capture(webrtc::DesktopRegion()); 182 break; 183 184 default: 185 NOTREACHED(); 186 } 187 188 // Expect that DesktopCapturer to always captures frames synchronously. 189 // |current_frame_| may be NULL if capture failed (e.g. because window has 190 // been closed). 191 if (current_frame_) { 192 uint32 frame_hash = GetFrameHash(current_frame_.get()); 193 new_image_hashes[source.id] = frame_hash; 194 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)); 204 } 205 } 206 } 207 208 image_hashes_.swap(new_image_hashes); 209 210 BrowserThread::PostTask( 211 BrowserThread::UI, FROM_HERE, 212 base::Bind(&NativeDesktopMediaList::OnRefreshFinished, media_list_)); 213 } 214 215 webrtc::SharedMemory* NativeDesktopMediaList::Worker::CreateSharedMemory( 216 size_t size) { 217 return NULL; 218 } 219 220 void NativeDesktopMediaList::Worker::OnCaptureCompleted( 221 webrtc::DesktopFrame* frame) { 222 current_frame_.reset(frame); 223 } 224 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), 232 view_dialog_id_(-1), 233 observer_(NULL), 234 weak_factory_(this) { 235 base::SequencedWorkerPool* worker_pool = BrowserThread::GetBlockingPool(); 236 capture_task_runner_ = worker_pool->GetSequencedTaskRunner( 237 worker_pool->GetSequenceToken()); 238 } 239 240 NativeDesktopMediaList::~NativeDesktopMediaList() { 241 capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release()); 242 } 243 244 void NativeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period) { 245 DCHECK(!observer_); 246 update_period_ = period; 247 } 248 249 void NativeDesktopMediaList::SetThumbnailSize( 250 const gfx::Size& thumbnail_size) { 251 thumbnail_size_ = thumbnail_size; 252 } 253 254 void NativeDesktopMediaList::SetViewDialogWindowId( 255 content::DesktopMediaID::Id dialog_id) { 256 view_dialog_id_ = dialog_id; 257 } 258 259 void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) { 260 DCHECK(!observer_); 261 DCHECK(screen_capturer_ || window_capturer_); 262 263 observer_ = observer; 264 265 worker_.reset(new Worker(weak_factory_.GetWeakPtr(), 266 screen_capturer_.Pass(), window_capturer_.Pass())); 267 Refresh(); 268 } 269 270 int NativeDesktopMediaList::GetSourceCount() const { 271 return sources_.size(); 272 } 273 274 const DesktopMediaList::Source& NativeDesktopMediaList::GetSource( 275 int index) const { 276 return sources_[index]; 277 } 278 279 // static 280 bool NativeDesktopMediaList::CompareSources(const SourceDescription& a, 281 const SourceDescription& b) { 282 return a.id < b.id; 283 } 284 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_)); 289 } 290 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. 296 size_t pos = 0; 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); 303 continue; 304 } 305 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); 314 } 315 316 ++pos; 317 } 318 319 DCHECK_EQ(new_sources.size(), sources_.size()); 320 } 321 322 void NativeDesktopMediaList::OnSourceThumbnail( 323 int index, 324 const gfx::ImageSkia& image) { 325 DCHECK_LT(index, static_cast<int>(sources_.size())); 326 sources_[index].thumbnail = image; 327 observer_->OnSourceThumbnailChanged(index); 328 } 329 330 void NativeDesktopMediaList::OnRefreshFinished() { 331 BrowserThread::PostDelayedTask( 332 BrowserThread::UI, FROM_HERE, 333 base::Bind(&NativeDesktopMediaList::Refresh, 334 weak_factory_.GetWeakPtr()), 335 update_period_); 336 } 337