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();
// Expect that DesktopCapturer to always captures frames synchronously. // To refresh thumbnails, a snapshot of each window is captured and scaled
// |current_frame_| may be NULL if capture failed (e.g. because window has // down to the specified size. Snapshotting can be asynchronous, and so
// been closed). // the process looks like the following steps:
if (IsCurrentFrameValid()) { //
uint32_t frame_hash = GetFrameHash(current_frame_.get()); // 1) RefreshNextThumbnail
new_image_hashes[id] = frame_hash; // 2) OnCaptureResult
// 3) UpdateSourceThumbnail (if the snapshot changed)
// [repeat 1, 2 and 3 until all thumbnails are refreshed]
// 4) RefreshNextThumbnail
// 5) UpdateNativeThumbnailsFinished
//
// |image_hashes_| is used to help avoid updating thumbnails that haven't
// changed since the last refresh.
refresh_thumbnails_state_ = std::make_unique<RefreshThumbnailsState>();
refresh_thumbnails_state_->source_ids = std::move(native_ids);
refresh_thumbnails_state_->thumbnail_size = thumbnail_size;
RefreshNextThumbnail();
}
// Scale the image only if it has changed. void NativeDesktopMediaList::Worker::RefreshNextThumbnail() {
auto it = image_hashes_.find(id); DCHECK(refresh_thumbnails_state_);
if (it == image_hashes_.end() || it->second != frame_hash) {
gfx::ImageSkia thumbnail = for (size_t index = refresh_thumbnails_state_->next_source_index;
ScaleDesktopFrame(std::move(current_frame_), thumbnail_size); index < refresh_thumbnails_state_->source_ids.size(); ++index) {
content::GetUIThreadTaskRunner({})->PostTask( refresh_thumbnails_state_->next_source_index = index + 1;
FROM_HERE, DesktopMediaID source_id = refresh_thumbnails_state_->source_ids[index];
base::BindOnce(&NativeDesktopMediaList::UpdateSourceThumbnail, if (capturer_->SelectSource(source_id.id)) {
media_list_, id, thumbnail)); 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