blob: a45994100c3fdd84d729ac8f4bb002535bf435b7 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
#include "base/hash.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_scheduler/post_task.h"
#include "chrome/browser/media/webrtc/desktop_media_list_observer.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
#include "media/base/video_util.h"
#include "third_party/libyuv/include/libyuv/scale_argb.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/snapshot/snapshot.h"
#if defined(OS_WIN)
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_win.h"
#endif // defined(OS_WIN)
#if defined(USE_X11) && !defined(OS_CHROMEOS)
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
#endif // defined(USE_X11) && !defined(OS_CHROMEOS)
#if defined(USE_AURA)
#include "ui/snapshot/snapshot_aura.h"
#endif
using content::BrowserThread;
using content::DesktopMediaID;
namespace {
// Update the list every second.
const int kDefaultUpdatePeriod = 1000;
// Returns a hash of a DesktopFrame content to detect when image for a desktop
// media source has changed.
uint32_t GetFrameHash(webrtc::DesktopFrame* frame) {
int data_size = frame->stride() * frame->size().height();
return base::Hash(reinterpret_cast<char*>(frame->data()), data_size);
}
gfx::ImageSkia ScaleDesktopFrame(std::unique_ptr<webrtc::DesktopFrame> frame,
gfx::Size size) {
gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
gfx::Rect(0, 0, size.width(), size.height()),
gfx::Size(frame->size().width(), frame->size().height()));
SkBitmap result;
result.allocN32Pixels(scaled_rect.width(), scaled_rect.height(), true);
uint8_t* pixels_data = reinterpret_cast<uint8_t*>(result.getPixels());
libyuv::ARGBScale(frame->data(), frame->stride(),
frame->size().width(), frame->size().height(),
pixels_data, result.rowBytes(),
scaled_rect.width(), scaled_rect.height(),
libyuv::kFilterBilinear);
// Set alpha channel values to 255 for all pixels.
// TODO(sergeyu): Fix screen/window capturers to capture alpha channel and
// remove this code. Currently screen/window capturers (at least some
// implementations) only capture R, G and B channels and set Alpha to 0.
// crbug.com/264424
for (int y = 0; y < result.height(); ++y) {
for (int x = 0; x < result.width(); ++x) {
pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] =
0xff;
}
}
return gfx::ImageSkia::CreateFrom1xBitmap(result);
}
} // namespace
class NativeDesktopMediaList::Worker
: public webrtc::DesktopCapturer::Callback {
public:
Worker(base::WeakPtr<NativeDesktopMediaList> media_list,
DesktopMediaID::Type type,
std::unique_ptr<webrtc::DesktopCapturer> capturer);
~Worker() override;
void Refresh(const DesktopMediaID::Id& view_dialog_id);
void RefreshThumbnails(const std::vector<DesktopMediaID>& native_ids,
const gfx::Size& thumbnail_size);
private:
typedef std::map<DesktopMediaID, uint32_t> ImageHashesMap;
// webrtc::DesktopCapturer::Callback interface.
void OnCaptureResult(webrtc::DesktopCapturer::Result result,
std::unique_ptr<webrtc::DesktopFrame> frame) override;
base::WeakPtr<NativeDesktopMediaList> media_list_;
DesktopMediaID::Type type_;
std::unique_ptr<webrtc::DesktopCapturer> capturer_;
std::unique_ptr<webrtc::DesktopFrame> current_frame_;
ImageHashesMap image_hashes_;
DISALLOW_COPY_AND_ASSIGN(Worker);
};
NativeDesktopMediaList::Worker::Worker(
base::WeakPtr<NativeDesktopMediaList> media_list,
DesktopMediaID::Type type,
std::unique_ptr<webrtc::DesktopCapturer> capturer)
: media_list_(media_list), type_(type), capturer_(std::move(capturer)) {
capturer_->Start(this);
}
NativeDesktopMediaList::Worker::~Worker() {}
void NativeDesktopMediaList::Worker::Refresh(
const DesktopMediaID::Id& view_dialog_id) {
std::vector<SourceDescription> result;
webrtc::DesktopCapturer::SourceList sources;
if (!capturer_->GetSourceList(&sources)) {
// Will pass empty results list to RefreshForAuraWindows().
sources.clear();
}
bool mutiple_sources = sources.size() > 1;
base::string16 title;
for (size_t i = 0; i < sources.size(); ++i) {
switch (type_) {
case DesktopMediaID::TYPE_SCREEN:
// Just in case 'Screen' is inflected depending on the screen number,
// use plural formatter.
title = mutiple_sources
? l10n_util::GetPluralStringFUTF16(
IDS_DESKTOP_MEDIA_PICKER_MULTIPLE_SCREEN_NAME,
static_cast<int>(i + 1))
: l10n_util::GetStringUTF16(
IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME);
break;
case DesktopMediaID::TYPE_WINDOW:
// Skip the picker dialog window.
if (sources[i].id == view_dialog_id)
continue;
title = base::UTF8ToUTF16(sources[i].title);
break;
default:
NOTREACHED();
}
result.push_back(
SourceDescription(DesktopMediaID(type_, sources[i].id), title));
}
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&NativeDesktopMediaList::RefreshForAuraWindows,
media_list_, result));
}
void NativeDesktopMediaList::Worker::RefreshThumbnails(
const std::vector<DesktopMediaID>& native_ids,
const gfx::Size& thumbnail_size) {
ImageHashesMap new_image_hashes;
// Get a thumbnail for each native source.
for (const auto& id : native_ids) {
if (!capturer_->SelectSource(id.id))
continue;
capturer_->CaptureFrame();
// Expect that DesktopCapturer to always captures frames synchronously.
// |current_frame_| may be NULL if capture failed (e.g. because window has
// been closed).
if (current_frame_) {
uint32_t frame_hash = GetFrameHash(current_frame_.get());
new_image_hashes[id] = frame_hash;
// Scale the image only if it has changed.
ImageHashesMap::iterator it = image_hashes_.find(id);
if (it == image_hashes_.end() || it->second != frame_hash) {
gfx::ImageSkia thumbnail =
ScaleDesktopFrame(std::move(current_frame_), thumbnail_size);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&NativeDesktopMediaList::UpdateSourceThumbnail,
media_list_, id, thumbnail));
}
}
}
image_hashes_.swap(new_image_hashes);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&NativeDesktopMediaList::UpdateNativeThumbnailsFinished,
media_list_));
}
void NativeDesktopMediaList::Worker::OnCaptureResult(
webrtc::DesktopCapturer::Result result,
std::unique_ptr<webrtc::DesktopFrame> frame) {
current_frame_ = std::move(frame);
}
NativeDesktopMediaList::NativeDesktopMediaList(
DesktopMediaID::Type type,
std::unique_ptr<webrtc::DesktopCapturer> capturer)
: DesktopMediaListBase(
base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)),
weak_factory_(this) {
type_ = type;
capture_task_runner_ = base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE});
worker_.reset(
new Worker(weak_factory_.GetWeakPtr(), type, std::move(capturer)));
}
NativeDesktopMediaList::~NativeDesktopMediaList() {
capture_task_runner_->DeleteSoon(FROM_HERE, worker_.release());
}
void NativeDesktopMediaList::Refresh() {
#if defined(USE_AURA)
DCHECK_EQ(pending_aura_capture_requests_, 0);
DCHECK(!pending_native_thumbnail_capture_);
new_aura_thumbnail_hashes_.clear();
#endif
capture_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&Worker::Refresh, base::Unretained(worker_.get()),
view_dialog_id_.id));
}
void NativeDesktopMediaList::RefreshForAuraWindows(
std::vector<SourceDescription> sources) {
#if defined(USE_AURA)
// Associate aura id with native id.
for (auto& source : sources) {
if (source.id.type != DesktopMediaID::TYPE_WINDOW)
continue;
aura::Window* aura_window = NULL;
#if defined(OS_WIN)
aura_window = views::DesktopWindowTreeHostWin::GetContentWindowForHWND(
reinterpret_cast<HWND>(source.id.id));
#elif defined(USE_X11) && !defined(OS_CHROMEOS)
aura_window =
views::DesktopWindowTreeHostX11::GetContentWindowForXID(source.id.id);
#endif // defined(USE_X11) && !defined(OS_CHROMEOS)
if (aura_window) {
DesktopMediaID aura_id = DesktopMediaID::RegisterAuraWindow(
DesktopMediaID::TYPE_WINDOW, aura_window);
source.id.aura_id = aura_id.aura_id;
}
}
#endif // defined(USE_AURA)
UpdateSourcesList(sources);
// OnAuraThumbnailCaptured() and UpdateNativeThumbnailsFinished() are
// guaranteed to be excuted after RefreshForAuraWindows() and
// CaptureAuraWindowThumbnail() in the browser UI thread.
// Therefore pending_aura_capture_requests_ will be set the number of aura
// windows to be captured and pending_native_thumbnail_capture_ will be set
// true if native thumbnail capture is needed before OnAuraThumbnailCaptured()
// or UpdateNativeThumbnailsFinished() are called.
std::vector<DesktopMediaID> native_ids;
for (const auto& source : sources) {
#if defined(USE_AURA)
if (source.id.aura_id > DesktopMediaID::kNullId) {
CaptureAuraWindowThumbnail(source.id);
continue;
}
#endif // defined(USE_AURA)
native_ids.push_back(source.id);
}
if (native_ids.size() > 0) {
#if defined(USE_AURA)
pending_native_thumbnail_capture_ = true;
#endif
capture_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Worker::RefreshThumbnails,
base::Unretained(worker_.get()), native_ids,
thumbnail_size_));
}
}
void NativeDesktopMediaList::UpdateNativeThumbnailsFinished() {
#if defined(USE_AURA)
DCHECK(pending_native_thumbnail_capture_);
pending_native_thumbnail_capture_ = false;
// Schedule next refresh if native thumbnail captures finished after aura
// thumbnail captures.
if (pending_aura_capture_requests_ == 0)
ScheduleNextRefresh();
#else
ScheduleNextRefresh();
#endif // defined(USE_AURA)
}
#if defined(USE_AURA)
void NativeDesktopMediaList::CaptureAuraWindowThumbnail(
const DesktopMediaID& id) {
gfx::NativeWindow window = DesktopMediaID::GetAuraWindowById(id);
if (!window)
return;
gfx::Rect window_rect(window->bounds().width(), window->bounds().height());
gfx::Rect scaled_rect = media::ComputeLetterboxRegion(
gfx::Rect(thumbnail_size_), window_rect.size());
pending_aura_capture_requests_++;
ui::GrabWindowSnapshotAndScaleAsyncAura(
window, window_rect, scaled_rect.size(),
base::Bind(&NativeDesktopMediaList::OnAuraThumbnailCaptured,
weak_factory_.GetWeakPtr(), id));
}
void NativeDesktopMediaList::OnAuraThumbnailCaptured(const DesktopMediaID& id,
const gfx::Image& image) {
if (!image.IsEmpty()) {
// Only new or changed thumbnail need update.
new_aura_thumbnail_hashes_[id] = GetImageHash(image);
if (!previous_aura_thumbnail_hashes_.count(id) ||
previous_aura_thumbnail_hashes_[id] != new_aura_thumbnail_hashes_[id]) {
UpdateSourceThumbnail(id, image.AsImageSkia());
}
}
// After all aura windows are processed, schedule next refresh;
pending_aura_capture_requests_--;
DCHECK_GE(pending_aura_capture_requests_, 0);
if (pending_aura_capture_requests_ == 0) {
previous_aura_thumbnail_hashes_ = std::move(new_aura_thumbnail_hashes_);
// Schedule next refresh if aura thumbnail captures finished after native
// thumbnail captures.
if (!pending_native_thumbnail_capture_)
ScheduleNextRefresh();
}
}
#endif // defined(USE_AURA)