Commit b54b5507 authored by Darin Fisher's avatar Darin Fisher Committed by Commit Bot

[Lacros] Allow CaptureFrame to complete asynchronously

This CL changes NativeDesktopMediaList to no longer assume that
CaptureFrame completes synchronously. This means changing the code
to wait for OnCaptureResult before calling CaptureFrame again.

DCHECKs are added to DesktopCapturerLacros to guard against such
misuse in the future.

Bug: 1141483
Change-Id: I579dd0f5ea8bef9a02c7be6d3dead23805601038
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2492940
Commit-Queue: Darin Fisher <darin@chromium.org>
Reviewed-by: default avatarErik Chen <erikchen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#820483}
parent 08bc2bc5
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h" #include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/browser/media/webrtc/desktop_media_list.h" #include "chrome/browser/media/webrtc/desktop_media_list.h"
#include "chrome/grit/generated_resources.h" #include "chrome/grit/generated_resources.h"
...@@ -39,6 +40,15 @@ namespace { ...@@ -39,6 +40,15 @@ namespace {
// Update the list every second. // Update the list every second.
const int kDefaultNativeDesktopMediaListUpdatePeriod = 1000; const int kDefaultNativeDesktopMediaListUpdatePeriod = 1000;
bool IsFrameValid(webrtc::DesktopFrame* frame) {
// These checks ensure invalid data isn't passed along, potentially leading to
// crashes, e.g. when we calculate the hash which assumes a positive height
// and stride.
// TODO(crbug.com/1085230): figure out why the height is sometimes negative.
return frame && frame->data() && frame->stride() >= 0 &&
frame->size().height() >= 0;
}
// Returns a hash of a DesktopFrame content to detect when image for a desktop // Returns a hash of a DesktopFrame content to detect when image for a desktop
// media source has changed. // media source has changed.
uint32_t GetFrameHash(webrtc::DesktopFrame* frame) { uint32_t GetFrameHash(webrtc::DesktopFrame* frame) {
...@@ -91,13 +101,21 @@ class NativeDesktopMediaList::Worker ...@@ -91,13 +101,21 @@ class NativeDesktopMediaList::Worker
void Start(); void Start();
void Refresh(const DesktopMediaID::Id& view_dialog_id, bool update_thumnails); void Refresh(const DesktopMediaID::Id& view_dialog_id, bool update_thumnails);
void RefreshThumbnails(const std::vector<DesktopMediaID>& native_ids, void RefreshThumbnails(std::vector<DesktopMediaID> native_ids,
const gfx::Size& thumbnail_size); const gfx::Size& thumbnail_size);
private: private:
typedef std::map<DesktopMediaID, uint32_t> ImageHashesMap; typedef std::map<DesktopMediaID, uint32_t> ImageHashesMap;
bool IsCurrentFrameValid() const; // Used to hold state associated with a call to RefreshThumbnails.
struct RefreshThumbnailsState {
std::vector<DesktopMediaID> source_ids;
gfx::Size thumbnail_size;
ImageHashesMap new_image_hashes;
size_t next_source_index = 0;
};
void RefreshNextThumbnail();
// webrtc::DesktopCapturer::Callback interface. // webrtc::DesktopCapturer::Callback interface.
void OnCaptureResult(webrtc::DesktopCapturer::Result result, void OnCaptureResult(webrtc::DesktopCapturer::Result result,
...@@ -111,10 +129,14 @@ class NativeDesktopMediaList::Worker ...@@ -111,10 +129,14 @@ class NativeDesktopMediaList::Worker
DesktopMediaID::Type type_; DesktopMediaID::Type type_;
std::unique_ptr<webrtc::DesktopCapturer> capturer_; std::unique_ptr<webrtc::DesktopCapturer> capturer_;
std::unique_ptr<webrtc::DesktopFrame> current_frame_; // Stores hashes of snapshots previously captured.
ImageHashesMap image_hashes_; ImageHashesMap image_hashes_;
// Non-null when RefreshThumbnails hasn't yet completed.
std::unique_ptr<RefreshThumbnailsState> refresh_thumbnails_state_;
base::WeakPtrFactory<Worker> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(Worker); DISALLOW_COPY_AND_ASSIGN(Worker);
}; };
...@@ -186,38 +208,50 @@ void NativeDesktopMediaList::Worker::Refresh( ...@@ -186,38 +208,50 @@ void NativeDesktopMediaList::Worker::Refresh(
} }
void NativeDesktopMediaList::Worker::RefreshThumbnails( void NativeDesktopMediaList::Worker::RefreshThumbnails(
const std::vector<DesktopMediaID>& native_ids, std::vector<DesktopMediaID> native_ids,
const gfx::Size& thumbnail_size) { const gfx::Size& thumbnail_size) {
DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(task_runner_->BelongsToCurrentThread());
ImageHashesMap new_image_hashes;
// Get a thumbnail for each native source. // Ignore if refresh is already in progress.
for (const auto& id : native_ids) { if (refresh_thumbnails_state_)
if (!capturer_->SelectSource(id.id)) return;
continue;
capturer_->CaptureFrame(); // To refresh thumbnails, a snapshot of each window is captured and scaled
// down to the specified size. Snapshotting can be asynchronous, and so
// Expect that DesktopCapturer to always captures frames synchronously. // the process looks like the following steps:
// |current_frame_| may be NULL if capture failed (e.g. because window has //
// been closed). // 1) RefreshNextThumbnail
if (IsCurrentFrameValid()) { // 2) OnCaptureResult
uint32_t frame_hash = GetFrameHash(current_frame_.get()); // 3) UpdateSourceThumbnail (if the snapshot changed)
new_image_hashes[id] = frame_hash; // [repeat 1, 2 and 3 until all thumbnails are refreshed]
// 4) RefreshNextThumbnail
// Scale the image only if it has changed. // 5) UpdateNativeThumbnailsFinished
auto it = image_hashes_.find(id); //
if (it == image_hashes_.end() || it->second != frame_hash) { // |image_hashes_| is used to help avoid updating thumbnails that haven't
gfx::ImageSkia thumbnail = // changed since the last refresh.
ScaleDesktopFrame(std::move(current_frame_), thumbnail_size);
content::GetUIThreadTaskRunner({})->PostTask( refresh_thumbnails_state_ = std::make_unique<RefreshThumbnailsState>();
FROM_HERE, refresh_thumbnails_state_->source_ids = std::move(native_ids);
base::BindOnce(&NativeDesktopMediaList::UpdateSourceThumbnail, refresh_thumbnails_state_->thumbnail_size = thumbnail_size;
media_list_, id, thumbnail)); RefreshNextThumbnail();
} }
void NativeDesktopMediaList::Worker::RefreshNextThumbnail() {
DCHECK(refresh_thumbnails_state_);
for (size_t index = refresh_thumbnails_state_->next_source_index;
index < refresh_thumbnails_state_->source_ids.size(); ++index) {
refresh_thumbnails_state_->next_source_index = index + 1;
DesktopMediaID source_id = refresh_thumbnails_state_->source_ids[index];
if (capturer_->SelectSource(source_id.id)) {
capturer_->CaptureFrame(); // Completes with OnCaptureResult.
return;
} }
} }
image_hashes_.swap(new_image_hashes); // Done capturing thumbnails.
image_hashes_.swap(refresh_thumbnails_state_->new_image_hashes);
refresh_thumbnails_state_.reset();
content::GetUIThreadTaskRunner({})->PostTask( content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, FROM_HERE,
...@@ -225,19 +259,36 @@ void NativeDesktopMediaList::Worker::RefreshThumbnails( ...@@ -225,19 +259,36 @@ void NativeDesktopMediaList::Worker::RefreshThumbnails(
media_list_)); media_list_));
} }
bool NativeDesktopMediaList::Worker::IsCurrentFrameValid() const {
// These checks ensure invalid data isn't passed along, potentially leading to
// crashes, e.g. when we calculate the hash which assumes a positive height
// and stride.
// TODO(crbug.com/1085230): figure out why the height is sometimes negative.
return current_frame_ && current_frame_->data() &&
current_frame_->stride() >= 0 && current_frame_->size().height() >= 0;
}
void NativeDesktopMediaList::Worker::OnCaptureResult( void NativeDesktopMediaList::Worker::OnCaptureResult(
webrtc::DesktopCapturer::Result result, webrtc::DesktopCapturer::Result result,
std::unique_ptr<webrtc::DesktopFrame> frame) { std::unique_ptr<webrtc::DesktopFrame> frame) {
current_frame_ = std::move(frame); auto index = refresh_thumbnails_state_->next_source_index - 1;
DCHECK(index < refresh_thumbnails_state_->source_ids.size());
DesktopMediaID id = refresh_thumbnails_state_->source_ids[index];
// |frame| may be null if capture failed (e.g. because window has been
// closed).
if (IsFrameValid(frame.get())) {
uint32_t frame_hash = GetFrameHash(frame.get());
refresh_thumbnails_state_->new_image_hashes[id] = frame_hash;
// Scale the image only if it has changed.
auto it = image_hashes_.find(id);
if (it == image_hashes_.end() || it->second != frame_hash) {
gfx::ImageSkia thumbnail = ScaleDesktopFrame(
std::move(frame), refresh_thumbnails_state_->thumbnail_size);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&NativeDesktopMediaList::UpdateSourceThumbnail,
media_list_, id, thumbnail));
}
}
// Protect against possible re-entrancy since OnCaptureResult can be invoked
// from within the call to CaptureFrame.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&Worker::RefreshNextThumbnail,
weak_factory_.GetWeakPtr()));
} }
NativeDesktopMediaList::NativeDesktopMediaList( NativeDesktopMediaList::NativeDesktopMediaList(
...@@ -351,8 +402,8 @@ void NativeDesktopMediaList::RefreshForAuraWindows( ...@@ -351,8 +402,8 @@ void NativeDesktopMediaList::RefreshForAuraWindows(
#endif #endif
thread_.task_runner()->PostTask( thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&Worker::RefreshThumbnails, FROM_HERE, base::BindOnce(&Worker::RefreshThumbnails,
base::Unretained(worker_.get()), native_ids, base::Unretained(worker_.get()),
thumbnail_size_)); std::move(native_ids), thumbnail_size_));
} }
} }
......
...@@ -130,6 +130,11 @@ void DesktopCapturerLacros::Start(Callback* callback) { ...@@ -130,6 +130,11 @@ void DesktopCapturerLacros::Start(Callback* callback) {
void DesktopCapturerLacros::CaptureFrame() { void DesktopCapturerLacros::CaptureFrame() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
DCHECK(!capturing_frame_);
capturing_frame_ = true;
#endif
if (snapshot_capturer_) { if (snapshot_capturer_) {
snapshot_capturer_->TakeSnapshot( snapshot_capturer_->TakeSnapshot(
selected_source_, selected_source_,
...@@ -162,6 +167,10 @@ void DesktopCapturerLacros::DidTakeSnapshot(bool success, ...@@ -162,6 +167,10 @@ void DesktopCapturerLacros::DidTakeSnapshot(bool success,
const SkBitmap& snapshot) { const SkBitmap& snapshot) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
capturing_frame_ = false;
#endif
if (!success) { if (!success) {
callback_->OnCaptureResult(Result::ERROR_PERMANENT, callback_->OnCaptureResult(Result::ERROR_PERMANENT,
std::unique_ptr<webrtc::DesktopFrame>()); std::unique_ptr<webrtc::DesktopFrame>());
...@@ -177,6 +186,10 @@ void DesktopCapturerLacros::DeprecatedDidTakeSnapshot( ...@@ -177,6 +186,10 @@ void DesktopCapturerLacros::DeprecatedDidTakeSnapshot(
crosapi::Bitmap snapshot) { crosapi::Bitmap snapshot) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
capturing_frame_ = false;
#endif
if (!success) { if (!success) {
callback_->OnCaptureResult(Result::ERROR_PERMANENT, callback_->OnCaptureResult(Result::ERROR_PERMANENT,
std::unique_ptr<webrtc::DesktopFrame>()); std::unique_ptr<webrtc::DesktopFrame>());
......
...@@ -80,6 +80,10 @@ class DesktopCapturerLacros : public webrtc::DesktopCapturer { ...@@ -80,6 +80,10 @@ class DesktopCapturerLacros : public webrtc::DesktopCapturer {
// the deprecated methods. // the deprecated methods.
mojo::Remote<crosapi::mojom::SnapshotCapturer> snapshot_capturer_; mojo::Remote<crosapi::mojom::SnapshotCapturer> snapshot_capturer_;
#if DCHECK_IS_ON()
bool capturing_frame_ = false;
#endif
base::WeakPtrFactory<DesktopCapturerLacros> weak_factory_{this}; base::WeakPtrFactory<DesktopCapturerLacros> weak_factory_{this};
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment