Home | History | Annotate | Download | only in media
      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