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/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