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 #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 
     27 using content::BrowserThread;
     28 using content::DesktopMediaID;
     29 
     30 namespace {
     31 
     32 // Update the list every second.
     33 const int kDefaultUpdatePeriod = 1000;
     34 
     35 // Returns a hash of a DesktopFrame content to detect when image for a desktop
     36 // media source has changed.
     37 uint32 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 
     42 gfx::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 
     78 NativeDesktopMediaList::SourceDescription::SourceDescription(
     79     DesktopMediaID id,
     80     const base::string16& name)
     81     : id(id),
     82       name(name) {
     83 }
     84 
     85 class 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 
    115 NativeDesktopMediaList::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 
    128 NativeDesktopMediaList::Worker::~Worker() {}
    129 
    130 void 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 
    224 webrtc::SharedMemory* NativeDesktopMediaList::Worker::CreateSharedMemory(
    225     size_t size) {
    226   return NULL;
    227 }
    228 
    229 void NativeDesktopMediaList::Worker::OnCaptureCompleted(
    230     webrtc::DesktopFrame* frame) {
    231   current_frame_.reset(frame);
    232 }
    233 
    234 NativeDesktopMediaList::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 
    249 NativeDesktopMediaList::~NativeDesktopMediaList() {
    250   capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release());
    251 }
    252 
    253 void NativeDesktopMediaList::SetUpdatePeriod(base::TimeDelta period) {
    254   DCHECK(!observer_);
    255   update_period_ = period;
    256 }
    257 
    258 void NativeDesktopMediaList::SetThumbnailSize(
    259     const gfx::Size& thumbnail_size) {
    260   thumbnail_size_ = thumbnail_size;
    261 }
    262 
    263 void NativeDesktopMediaList::SetViewDialogWindowId(
    264     content::DesktopMediaID::Id dialog_id) {
    265   view_dialog_id_ = dialog_id;
    266 }
    267 
    268 void 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 
    279 int NativeDesktopMediaList::GetSourceCount() const {
    280   return sources_.size();
    281 }
    282 
    283 const DesktopMediaList::Source& NativeDesktopMediaList::GetSource(
    284     int index) const {
    285   return sources_[index];
    286 }
    287 
    288 void 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 
    294 void 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 
    356 void 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 
    364 void NativeDesktopMediaList::OnRefreshFinished() {
    365   BrowserThread::PostDelayedTask(
    366       BrowserThread::UI, FROM_HERE,
    367       base::Bind(&NativeDesktopMediaList::Refresh,
    368                  weak_factory_.GetWeakPtr()),
    369       update_period_);
    370 }
    371