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

[Lacros] Improve desktop screen/window capture efficiency

Changes the interface to use SkBitmap, which is passed over Mojo IPC
using BigBuffer (shared memory). This is then wrapped using a special
subclass of webrtc::DesktopFrame (one less copy).

There are still more copies than would be desirable here. We have the
initial copy created by Ash. That is translated into a BigBuffer for
transport over Mojo. That is then copied back to a SkBitmap. Avoiding
those copies and even reusing the transport buffer are an exercise for
a later CL.

The interface design also unifies screen and window capture in a way
that is consistent with webrtc::DesktopCapturer. This also enables a
path to adding support for multiple screens in a future CL.

There's a good deal of complexity in this CL to handle version skew.
QueryVersion doesn't work synchronously, so a nested event loop is
used on the background thread where DesktopCapturerLacros runs. That
is safe, since this is a dedicated thread, but not ideal.

The deprecated methods are re-implemented in terms of the newer
interface.

Bug: 1094460
Change-Id: I2b2f86df08612b79faae7da547ff1242c05ac467
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2443404Reviewed-by: default avatarGreg Kerr <kerrnel@chromium.org>
Reviewed-by: default avatarErik Chen <erikchen@chromium.org>
Commit-Queue: Darin Fisher <darin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#814382}
parent dfcb4cc6
...@@ -7,22 +7,16 @@ ...@@ -7,22 +7,16 @@
#include <stdint.h> #include <stdint.h>
#include <map>
#include "base/memory/weak_ptr.h"
#include "chromeos/crosapi/mojom/screen_manager.mojom.h" #include "chromeos/crosapi/mojom/screen_manager.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/receiver_set.h"
#include "ui/aura/window_observer.h"
#include "ui/gfx/image/image.h" #include "ui/gfx/image/image.h"
namespace crosapi { namespace crosapi {
struct Bitmap;
// This class is the ash-chrome implementation of the ScreenManager interface. // This class is the ash-chrome implementation of the ScreenManager interface.
// This class must only be used from the main thread. // This class must only be used from the main thread.
class ScreenManagerAsh : public mojom::ScreenManager, aura::WindowObserver { class ScreenManagerAsh : public mojom::ScreenManager {
public: public:
ScreenManagerAsh(); ScreenManagerAsh();
ScreenManagerAsh(const ScreenManagerAsh&) = delete; ScreenManagerAsh(const ScreenManagerAsh&) = delete;
...@@ -32,38 +26,31 @@ class ScreenManagerAsh : public mojom::ScreenManager, aura::WindowObserver { ...@@ -32,38 +26,31 @@ class ScreenManagerAsh : public mojom::ScreenManager, aura::WindowObserver {
void BindReceiver(mojo::PendingReceiver<mojom::ScreenManager> receiver); void BindReceiver(mojo::PendingReceiver<mojom::ScreenManager> receiver);
// crosapi::mojom::ScreenManager: // crosapi::mojom::ScreenManager:
void TakeScreenSnapshot(TakeScreenSnapshotCallback callback) override; void DeprecatedTakeScreenSnapshot(
void ListWindows(ListWindowsCallback callback) override; DeprecatedTakeScreenSnapshotCallback callback) override;
void TakeWindowSnapshot(uint64_t id, void DeprecatedListWindows(DeprecatedListWindowsCallback callback) override;
TakeWindowSnapshotCallback callback) override; void DeprecatedTakeWindowSnapshot(
uint64_t id,
// aura::WindowObserver DeprecatedTakeWindowSnapshotCallback callback) override;
// This method is overridden purely to remove dead windows from void GetScreenCapturer(
// |id_to_window_| and |window_to_id_|. This ensures that if the pointer is mojo::PendingReceiver<mojom::SnapshotCapturer> receiver) override;
// reused for a new window, it does not get confused with a previous window. void GetWindowCapturer(
void OnWindowDestroying(aura::Window* window) final; mojo::PendingReceiver<mojom::SnapshotCapturer> receiver) override;
private: private:
using SnapshotCallback = base::OnceCallback<void(Bitmap)>; class ScreenCapturerImpl;
void DidTakeSnapshot(SnapshotCallback callback, gfx::Image image); class WindowCapturerImpl;
// This class generates unique, non-reused IDs for windows on demand. The IDs ScreenCapturerImpl* GetScreenCapturerImpl();
// are monotonically increasing 64-bit integers. Once an ID is assigned to a WindowCapturerImpl* GetWindowCapturerImpl();
// window, this class listens for the destruction of the window in order to
// remove dead windows from the map. std::unique_ptr<ScreenCapturerImpl> screen_capturer_impl_;
// std::unique_ptr<WindowCapturerImpl> window_capturer_impl_;
// The members |id_to_window_| and |window_to_id_| must be kept in sync. The
// members exist to allow fast lookup in both directions.
std::map<uint64_t, aura::Window*> id_to_window_;
std::map<aura::Window*, uint64_t> window_to_id_;
uint64_t next_window_id_ = 0;
// This class supports any number of connections. This allows the client to // This class supports any number of connections. This allows the client to
// have multiple, potentially thread-affine, remotes. This is needed by // have multiple, potentially thread-affine, remotes. This is needed by
// WebRTC. // WebRTC.
mojo::ReceiverSet<mojom::ScreenManager> receivers_; mojo::ReceiverSet<mojom::ScreenManager> receivers_;
base::WeakPtrFactory<ScreenManagerAsh> weak_factory_{this};
}; };
} // namespace crosapi } // namespace crosapi
......
...@@ -79,7 +79,8 @@ class ScreenManagerAshBrowserTest : public InProcessBrowserTest { ...@@ -79,7 +79,8 @@ class ScreenManagerAshBrowserTest : public InProcessBrowserTest {
}; };
// Tests that taking a screen snapshot works. // Tests that taking a screen snapshot works.
IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, TakeScreenSnapshot) { IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest,
DeprecatedTakeScreenSnapshot) {
base::RunLoop run_loop; base::RunLoop run_loop;
Bitmap snapshot; Bitmap snapshot;
...@@ -88,7 +89,7 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, TakeScreenSnapshot) { ...@@ -88,7 +89,7 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, TakeScreenSnapshot) {
auto take_snapshot_background = base::BindOnce( auto take_snapshot_background = base::BindOnce(
[](SMRemote* remote, Bitmap* snapshot) { [](SMRemote* remote, Bitmap* snapshot) {
mojo::ScopedAllowSyncCallForTesting allow_sync; mojo::ScopedAllowSyncCallForTesting allow_sync;
(*remote)->TakeScreenSnapshot(snapshot); (*remote)->DeprecatedTakeScreenSnapshot(snapshot);
}, },
screen_manager_remote_.get(), &snapshot); screen_manager_remote_.get(), &snapshot);
background_sequence_->PostTaskAndReply( background_sequence_->PostTaskAndReply(
...@@ -102,7 +103,8 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, TakeScreenSnapshot) { ...@@ -102,7 +103,8 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, TakeScreenSnapshot) {
EXPECT_EQ(int{snapshot.height}, primary_window->bounds().height()); EXPECT_EQ(int{snapshot.height}, primary_window->bounds().height());
} }
IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, TakeWindowSnapshot) { IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest,
DeprecatedTakeWindowSnapshot) {
base::RunLoop run_loop; base::RunLoop run_loop;
bool success; bool success;
Bitmap snapshot; Bitmap snapshot;
...@@ -112,13 +114,14 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, TakeWindowSnapshot) { ...@@ -112,13 +114,14 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, TakeWindowSnapshot) {
auto take_snapshot_background = base::BindOnce( auto take_snapshot_background = base::BindOnce(
[](SMRemote* remote, bool* success, Bitmap* snapshot) { [](SMRemote* remote, bool* success, Bitmap* snapshot) {
mojo::ScopedAllowSyncCallForTesting allow_sync; mojo::ScopedAllowSyncCallForTesting allow_sync;
std::vector<mojom::WindowDetailsPtr> windows; std::vector<mojom::SnapshotSourcePtr> windows;
(*remote)->ListWindows(&windows); (*remote)->DeprecatedListWindows(&windows);
// There should be exactly 1 window. // There should be exactly 1 window.
ASSERT_EQ(1u, windows.size()); ASSERT_EQ(1u, windows.size());
(*remote)->TakeWindowSnapshot(windows[0]->id, success, snapshot); (*remote)->DeprecatedTakeWindowSnapshot(windows[0]->id, success,
snapshot);
}, },
screen_manager_remote_.get(), &success, &snapshot); screen_manager_remote_.get(), &success, &snapshot);
background_sequence_->PostTaskAndReply( background_sequence_->PostTaskAndReply(
...@@ -134,5 +137,82 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, TakeWindowSnapshot) { ...@@ -134,5 +137,82 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, TakeWindowSnapshot) {
EXPECT_EQ(int{snapshot.height}, window->bounds().height()); EXPECT_EQ(int{snapshot.height}, window->bounds().height());
} }
IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, ScreenCapturer) {
base::RunLoop run_loop;
bool success;
SkBitmap snapshot;
// Take a snapshot on a background sequence. The call is blocking, so when it
// finishes, we can also unblock the main thread.
auto take_snapshot_background = base::BindOnce(
[](SMRemote* remote, bool* success, SkBitmap* snapshot) {
mojo::Remote<mojom::SnapshotCapturer> capturer;
(*remote)->GetScreenCapturer(capturer.BindNewPipeAndPassReceiver());
{
mojo::ScopedAllowSyncCallForTesting allow_sync;
std::vector<mojom::SnapshotSourcePtr> screens;
capturer->ListSources(&screens);
// There should be at least one screen!
ASSERT_LE(1u, screens.size());
capturer->TakeSnapshot(screens[0]->id, success, snapshot);
}
},
screen_manager_remote_.get(), &success, &snapshot);
background_sequence_->PostTaskAndReply(
FROM_HERE, std::move(take_snapshot_background), run_loop.QuitClosure());
run_loop.Run();
// Check that the IPC succeeded.
ASSERT_TRUE(success);
// Check that the screenshot has the right dimensions.
aura::Window* primary_window =
browser()->window()->GetNativeWindow()->GetRootWindow();
EXPECT_EQ(int{snapshot.width()}, primary_window->bounds().width());
EXPECT_EQ(int{snapshot.height()}, primary_window->bounds().height());
}
IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, WindowCapturer) {
base::RunLoop run_loop;
bool success;
SkBitmap snapshot;
// Take a snapshot on a background sequence. The call is blocking, so when it
// finishes, we can also unblock the main thread.
auto take_snapshot_background = base::BindOnce(
[](SMRemote* remote, bool* success, SkBitmap* snapshot) {
mojo::Remote<mojom::SnapshotCapturer> capturer;
(*remote)->GetWindowCapturer(capturer.BindNewPipeAndPassReceiver());
{
mojo::ScopedAllowSyncCallForTesting allow_sync;
std::vector<mojom::SnapshotSourcePtr> windows;
capturer->ListSources(&windows);
// There should be at least one window!
ASSERT_LE(1u, windows.size());
capturer->TakeSnapshot(windows[0]->id, success, snapshot);
}
},
screen_manager_remote_.get(), &success, &snapshot);
background_sequence_->PostTaskAndReply(
FROM_HERE, std::move(take_snapshot_background), run_loop.QuitClosure());
run_loop.Run();
// Check that the IPC succeeded.
ASSERT_TRUE(success);
// Check that the screenshot has the right dimensions.
aura::Window* window = browser()->window()->GetNativeWindow();
EXPECT_EQ(int{snapshot.width()}, window->bounds().width());
EXPECT_EQ(int{snapshot.height()}, window->bounds().height());
}
} // namespace } // namespace
} // namespace crosapi } // namespace crosapi
...@@ -28,10 +28,47 @@ ...@@ -28,10 +28,47 @@
namespace { namespace {
const char* kLacrosPageTitle = "Title Of Lacros Browser Test";
const char* kLacrosPageTitleHTML = const char* kLacrosPageTitleHTML =
"<html><head><title>Title Of Lacros Browser Test</title></head>" "<html><head><title>Title Of Lacros Browser Test</title></head>"
"<body>This page has a title.</body></html>"; "<body>This page has a title.</body></html>";
using ListWindowsCallback = base::RepeatingCallback<void(
std::vector<crosapi::mojom::SnapshotSourcePtr>*)>;
// Used to find the window corresponding to the test page.
bool FindTestWindow(ListWindowsCallback list_windows, uint64_t* window_id) {
base::RunLoop run_loop;
bool found_window = false;
auto look_for_window = base::BindRepeating(
[](ListWindowsCallback list_windows, base::RunLoop* run_loop,
bool* found_window, uint64_t* window_id) {
const base::string16 tab_title(base::ASCIIToUTF16(kLacrosPageTitle));
std::string expected_window_title = l10n_util::GetStringFUTF8(
IDS_BROWSER_WINDOW_TITLE_FORMAT, tab_title);
std::vector<crosapi::mojom::SnapshotSourcePtr> windows;
list_windows.Run(&windows);
for (auto& window : windows) {
if (window->title == expected_window_title) {
(*found_window) = true;
(*window_id) = window->id;
run_loop->Quit();
break;
}
}
},
std::move(list_windows), &run_loop, &found_window, window_id);
// When the browser test start, there is no guarantee that the window is
// open from ash's perspective.
base::RepeatingTimer timer;
timer.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(1),
std::move(look_for_window));
run_loop.Run();
return found_window;
}
} // namespace } // namespace
class ScreenManagerLacrosBrowserTest : public InProcessBrowserTest { class ScreenManagerLacrosBrowserTest : public InProcessBrowserTest {
...@@ -46,26 +83,39 @@ class ScreenManagerLacrosBrowserTest : public InProcessBrowserTest { ...@@ -46,26 +83,39 @@ class ScreenManagerLacrosBrowserTest : public InProcessBrowserTest {
~ScreenManagerLacrosBrowserTest() override = default; ~ScreenManagerLacrosBrowserTest() override = default;
void BindScreenManager() { void BindScreenManager() {
mojo::PendingRemote<crosapi::mojom::ScreenManager> pending_screen_manager;
mojo::PendingReceiver<crosapi::mojom::ScreenManager> pending_receiver =
pending_screen_manager.InitWithNewPipeAndPassReceiver();
auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get(); auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get();
ASSERT_TRUE(lacros_chrome_service); ASSERT_TRUE(lacros_chrome_service);
mojo::PendingRemote<crosapi::mojom::ScreenManager> pending_screen_manager;
lacros_chrome_service->BindScreenManagerReceiver( lacros_chrome_service->BindScreenManagerReceiver(
std::move(pending_receiver)); pending_screen_manager.InitWithNewPipeAndPassReceiver());
screen_manager_.Bind(std::move(pending_screen_manager)); screen_manager_.Bind(std::move(pending_screen_manager));
} }
uint32_t QueryScreenManagerVersion() {
// Synchronously fetch the version of the ScreenManager interface. The
// version field will be available as |screen_manager_.version()|.
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
screen_manager_.QueryVersion(
base::BindOnce([](base::OnceClosure done_closure,
uint32_t version) { std::move(done_closure).Run(); },
run_loop.QuitClosure()));
run_loop.Run();
return screen_manager_.version();
}
mojo::Remote<crosapi::mojom::ScreenManager> screen_manager_; mojo::Remote<crosapi::mojom::ScreenManager> screen_manager_;
}; };
// Tests that taking a screen snapshot via crosapi works. // Tests that taking a screen snapshot via crosapi works.
IN_PROC_BROWSER_TEST_F(ScreenManagerLacrosBrowserTest, TakeScreenSnapshot) { IN_PROC_BROWSER_TEST_F(ScreenManagerLacrosBrowserTest,
DeprecatedTakeScreenSnapshot) {
BindScreenManager(); BindScreenManager();
crosapi::Bitmap snapshot; crosapi::Bitmap snapshot;
{ {
mojo::ScopedAllowSyncCallForTesting allow_sync_call; mojo::ScopedAllowSyncCallForTesting allow_sync_call;
screen_manager_->TakeScreenSnapshot(&snapshot); screen_manager_->DeprecatedTakeScreenSnapshot(&snapshot);
} }
// Verify the snapshot is non-empty. // Verify the snapshot is non-empty.
EXPECT_GT(snapshot.height, 0u); EXPECT_GT(snapshot.height, 0u);
...@@ -76,51 +126,34 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerLacrosBrowserTest, TakeScreenSnapshot) { ...@@ -76,51 +126,34 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerLacrosBrowserTest, TakeScreenSnapshot) {
// Tests that taking a screen snapshot via crosapi works. // Tests that taking a screen snapshot via crosapi works.
// This test makes the browser load a page with specific title, and then scans // This test makes the browser load a page with specific title, and then scans
// through a list of windows to look for the window with the expected title. // through a list of windows to look for the window with the expected title.
// This test cannot simply asserts exactly 1 window is present because currently // This test cannot simply assert exactly 1 window is present because currently
// in lacros_chrome_browsertests, different browser tests share the same // in lacros_chrome_browsertests, different browser tests share the same
// ash-chrome, so a window could come from any one of them. // ash-chrome, so a window could come from any one of them.
IN_PROC_BROWSER_TEST_F(ScreenManagerLacrosBrowserTest, TakeWindowSnapshot) { IN_PROC_BROWSER_TEST_F(ScreenManagerLacrosBrowserTest,
DeprecatedTakeWindowSnapshot) {
GURL url(std::string("data:text/html,") + kLacrosPageTitleHTML); GURL url(std::string("data:text/html,") + kLacrosPageTitleHTML);
ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::NavigateToURL(browser(), url);
BindScreenManager(); BindScreenManager();
base::RunLoop run_loop;
bool found_window = false; auto list_windows = base::BindRepeating(
uint64_t window_id;
auto look_for_window = base::BindRepeating(
[](mojo::Remote<crosapi::mojom::ScreenManager>* screen_manager, [](mojo::Remote<crosapi::mojom::ScreenManager>* screen_manager,
base::RunLoop* run_loop, bool* found_window, uint64_t* window_id) { std::vector<crosapi::mojom::SnapshotSourcePtr>* windows) {
mojo::ScopedAllowSyncCallForTesting allow_sync_call; mojo::ScopedAllowSyncCallForTesting allow_sync_call;
const base::string16 tab_title( (*screen_manager)->DeprecatedListWindows(windows);
base::ASCIIToUTF16("Title Of Lacros Browser Test"));
std::string expected_window_title = l10n_util::GetStringFUTF8(
IDS_BROWSER_WINDOW_TITLE_FORMAT, tab_title);
std::vector<crosapi::mojom::WindowDetailsPtr> windows;
(*screen_manager)->ListWindows(&windows);
for (auto& window_details : windows) {
if (window_details->title == expected_window_title) {
(*found_window) = true;
(*window_id) = window_details->id;
run_loop->Quit();
break;
}
}
}, },
&screen_manager_, &run_loop, &found_window, &window_id); &screen_manager_);
// When the browser test start, there is no guaranteen that the window is
// open from ash's perspective. uint64_t window_id;
base::RepeatingTimer timer; bool found_window = FindTestWindow(std::move(list_windows), &window_id);
timer.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(1),
std::move(look_for_window));
run_loop.Run();
ASSERT_TRUE(found_window); ASSERT_TRUE(found_window);
bool success = false; bool success = false;
crosapi::Bitmap snapshot; crosapi::Bitmap snapshot;
{ {
mojo::ScopedAllowSyncCallForTesting allow_sync_call; mojo::ScopedAllowSyncCallForTesting allow_sync_call;
screen_manager_->TakeWindowSnapshot(window_id, &success, &snapshot); screen_manager_->DeprecatedTakeWindowSnapshot(window_id, &success,
&snapshot);
} }
ASSERT_TRUE(success); ASSERT_TRUE(success);
// Verify the snapshot is non-empty. // Verify the snapshot is non-empty.
...@@ -128,3 +161,79 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerLacrosBrowserTest, TakeWindowSnapshot) { ...@@ -128,3 +161,79 @@ IN_PROC_BROWSER_TEST_F(ScreenManagerLacrosBrowserTest, TakeWindowSnapshot) {
EXPECT_GT(snapshot.width, 0u); EXPECT_GT(snapshot.width, 0u);
EXPECT_GT(snapshot.pixels.size(), 0u); EXPECT_GT(snapshot.pixels.size(), 0u);
} }
// Tests that taking a screen snapshot via crosapi works.
IN_PROC_BROWSER_TEST_F(ScreenManagerLacrosBrowserTest, ScreenCapturer) {
BindScreenManager();
if (QueryScreenManagerVersion() < 1) {
LOG(WARNING) << "Ash version does not support required method.";
return;
}
mojo::Remote<crosapi::mojom::SnapshotCapturer> capturer;
screen_manager_->GetScreenCapturer(capturer.BindNewPipeAndPassReceiver());
std::vector<crosapi::mojom::SnapshotSourcePtr> screens;
{
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
capturer->ListSources(&screens);
}
ASSERT_LE(1u, screens.size());
bool success = false;
SkBitmap snapshot;
{
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
capturer->TakeSnapshot(screens[0]->id, &success, &snapshot);
}
EXPECT_TRUE(success);
// Verify the snapshot is non-empty.
EXPECT_GT(snapshot.height(), 0);
EXPECT_GT(snapshot.width(), 0);
}
// Tests that taking a window snapshot via crosapi works.
// This test makes the browser load a page with specific title, and then scans
// through a list of windows to look for the window with the expected title.
// This test cannot simply asserts exactly 1 window is present because currently
// in lacros_chrome_browsertests, different browser tests share the same
// ash-chrome, so a window could come from any one of them.
IN_PROC_BROWSER_TEST_F(ScreenManagerLacrosBrowserTest, WindowCapturer) {
BindScreenManager();
if (QueryScreenManagerVersion() < 1) {
LOG(WARNING) << "Ash version does not support required method.";
return;
}
GURL url(std::string("data:text/html,") + kLacrosPageTitleHTML);
ui_test_utils::NavigateToURL(browser(), url);
mojo::Remote<crosapi::mojom::SnapshotCapturer> capturer;
screen_manager_->GetWindowCapturer(capturer.BindNewPipeAndPassReceiver());
auto list_windows = base::BindRepeating(
[](mojo::Remote<crosapi::mojom::SnapshotCapturer>* capturer,
std::vector<crosapi::mojom::SnapshotSourcePtr>* windows) {
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
(*capturer)->ListSources(windows);
},
&capturer);
uint64_t window_id;
bool found_window = FindTestWindow(std::move(list_windows), &window_id);
ASSERT_TRUE(found_window);
bool success = false;
SkBitmap snapshot;
{
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
capturer->TakeSnapshot(window_id, &success, &snapshot);
}
ASSERT_TRUE(success);
// Verify the snapshot is non-empty.
EXPECT_GT(snapshot.height(), 0);
EXPECT_GT(snapshot.width(), 0);
}
...@@ -5,17 +5,32 @@ ...@@ -5,17 +5,32 @@
module crosapi.mojom; module crosapi.mojom;
import "chromeos/crosapi/mojom/bitmap.mojom"; import "chromeos/crosapi/mojom/bitmap.mojom";
import "skia/public/mojom/bitmap.mojom";
// A unique identifier and title for a window. // A unique identifier and title for a window (or screen).
[Stable] [Stable, RenamedFrom="crosapi.mojom.WindowDetails"]
struct WindowDetails { struct SnapshotSource {
// Guaranteed to be unique and never reused. // Guaranteed to be unique and never reused.
uint64 id@0; uint64 id@0;
// The title of the window in UTF-8 encoding. // The user-visible name of the window or screen in UTF-8 encoding.
string title@1; string title@1;
}; };
// This interface is used to capture bitmap snapshots of screens or windows.
// See ScreenManager methods used to create instances of this interface.
[Stable]
interface SnapshotCapturer {
// Returns list of screens or windows that can be captured.
[Sync]
ListSources@0() => (array<SnapshotSource> sources);
// Captures a bitmap snapshot of the specified screen or window. If |success|
// is false, that may indicate that the specified source no longer exists.
[Sync]
TakeSnapshot@1(uint64 id) => (bool success, skia.mojom.Bitmap? snapshot);
};
// This interface is implemented by ash-chrome. It allows lacros-chrome to query // This interface is implemented by ash-chrome. It allows lacros-chrome to query
// information about existing windows, screens, and displays. // information about existing windows, screens, and displays.
// //
...@@ -29,11 +44,8 @@ struct WindowDetails { ...@@ -29,11 +44,8 @@ struct WindowDetails {
// interface in the future. // interface in the future.
[Stable] [Stable]
interface ScreenManager { interface ScreenManager {
// TODO(https://crbug.com/1094460): We will need to add more methods for // DEPRECATED. Use |GetScreenCapturer| instead.
// querying screens, windows, etc. Details still TBD. //
// TODO(https://crbug.com/1094460): Use a more performant transport
// mechanism.
// This method assumes that there's exactly one screen. The implementation // This method assumes that there's exactly one screen. The implementation
// of this method takes a screenshot and converts it into a bitmap. Each // of this method takes a screenshot and converts it into a bitmap. Each
// pixel is represented by 4 bytes [RGBA]. // pixel is represented by 4 bytes [RGBA].
...@@ -41,16 +53,26 @@ interface ScreenManager { ...@@ -41,16 +53,26 @@ interface ScreenManager {
// The media screen capture implementation assumes that every platform // The media screen capture implementation assumes that every platform
// provides a synchronous method to take a snapshot of the screen. // provides a synchronous method to take a snapshot of the screen.
[Sync] [Sync]
TakeScreenSnapshot@0() => (Bitmap snapshot); DeprecatedTakeScreenSnapshot@0() => (Bitmap snapshot);
// Synchronously returns a list of visible, focusable windows. This method // DEPRECATED. Use |GetWindowCapturer| instead.
// needs to be synchronous to support cross-platform code that relies on the //
// assumption that every OS provides a similar synchronous IPC. // Returns a list of visible, focusable top-level windows.
[Sync] [Sync]
ListWindows@1() => (array<WindowDetails> windows); DeprecatedListWindows@1() => (array<SnapshotSource> windows);
// DEPRECATED. Use |GetWindowCapturer| instead.
//
// Take a screenshot of a window with an id from ListWindows. If |success| // Take a screenshot of a window with an id from ListWindows. If |success|
// is false, then the window no longer exists. // is false, then the window no longer exists.
[Sync] [Sync]
TakeWindowSnapshot@2(uint64 id) => (bool success, Bitmap snapshot); DeprecatedTakeWindowSnapshot@2(uint64 id) => (bool success, Bitmap snapshot);
// Get a SnapshotCapturer instance that can snapshot available screens.
[MinVersion=1]
GetScreenCapturer@3(pending_receiver<SnapshotCapturer> capturer);
// Get a SnapshotCapturer instance that can snapshot available windows.
[MinVersion=1]
GetWindowCapturer@4(pending_receiver<SnapshotCapturer> capturer);
}; };
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "content/browser/media/capture/desktop_capturer_lacros.h" #include "content/browser/media/capture/desktop_capturer_lacros.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/task_traits.h" #include "base/task/task_traits.h"
#include "base/task/thread_pool.h" #include "base/task/thread_pool.h"
...@@ -11,6 +13,30 @@ ...@@ -11,6 +13,30 @@
#include "chromeos/lacros/lacros_chrome_service_impl.h" #include "chromeos/lacros/lacros_chrome_service_impl.h"
namespace content { namespace content {
namespace {
// An SkBitmap backed subclass of DesktopFrame. This enables the webrtc system
// to retain the SkBitmap buffer without having to copy the pixels out until
// they are needed (e.g., for encoding).
class DesktopFrameSkia : public webrtc::DesktopFrame {
public:
explicit DesktopFrameSkia(const SkBitmap& bitmap)
: webrtc::DesktopFrame(
webrtc::DesktopSize(bitmap.width(), bitmap.height()),
bitmap.rowBytes(),
static_cast<uint8_t*>(bitmap.getPixels()),
nullptr),
bitmap_(bitmap) {}
~DesktopFrameSkia() override = default;
private:
DesktopFrameSkia(const DesktopFrameSkia&) = delete;
DesktopFrameSkia& operator=(const DesktopFrameSkia&) = delete;
SkBitmap bitmap_;
};
} // namespace
DesktopCapturerLacros::DesktopCapturerLacros( DesktopCapturerLacros::DesktopCapturerLacros(
CaptureType capture_type, CaptureType capture_type,
...@@ -24,31 +50,32 @@ DesktopCapturerLacros::~DesktopCapturerLacros() { ...@@ -24,31 +50,32 @@ DesktopCapturerLacros::~DesktopCapturerLacros() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
} }
bool DesktopCapturerLacros::GetSourceList(SourceList* sources) { bool DesktopCapturerLacros::GetSourceList(SourceList* result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<crosapi::mojom::SnapshotSourcePtr> sources;
if (snapshot_capturer_) {
mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call;
snapshot_capturer_->ListSources(&sources);
} else {
if (capture_type_ == kScreen) { if (capture_type_ == kScreen) {
// TODO(https://crbug.com/1094460): Implement this source list // TODO(https://crbug.com/1094460): Implement this source list
// appropriately. // appropriately.
Source w; Source w;
w.id = 1; w.id = 1;
sources->push_back(w); result->push_back(w);
return true; return true;
} }
EnsureScreenManager();
std::vector<crosapi::mojom::WindowDetailsPtr> windows;
{
mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call;
screen_manager_->ListWindows(&windows); screen_manager_->DeprecatedListWindows(&sources);
} }
for (auto& window : windows) { for (auto& source : sources) {
Source w; Source s;
w.id = window->id; s.id = source->id;
w.title = window->title; s.title = source->title;
sources->push_back(w); result->push_back(s);
} }
return true; return true;
} }
...@@ -70,23 +97,56 @@ bool DesktopCapturerLacros::FocusOnSelectedSource() { ...@@ -70,23 +97,56 @@ bool DesktopCapturerLacros::FocusOnSelectedSource() {
void DesktopCapturerLacros::Start(Callback* callback) { void DesktopCapturerLacros::Start(Callback* callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
callback_ = callback; callback_ = callback;
// The lacros chrome service exists at all times except during early start-up
// and late shut-down. This class should never be used in those two times.
auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get();
DCHECK(lacros_chrome_service);
lacros_chrome_service->BindScreenManagerReceiver(
screen_manager_.BindNewPipeAndPassReceiver());
// Do a round-trip to make sure we know what version of the screen manager we
// are talking to. It is safe to run a nested loop here because we are on a
// dedicated thread (see DesktopCaptureDevice) with nothing else happening.
// TODO(crbug/1094460): Avoid this nested event loop.
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
screen_manager_.QueryVersion(
base::BindOnce([](base::OnceClosure quit_closure,
uint32_t version) { std::move(quit_closure).Run(); },
run_loop.QuitClosure()));
run_loop.Run();
if (screen_manager_.version() >= 1) {
if (capture_type_ == kScreen) {
screen_manager_->GetScreenCapturer(
snapshot_capturer_.BindNewPipeAndPassReceiver());
} else {
screen_manager_->GetWindowCapturer(
snapshot_capturer_.BindNewPipeAndPassReceiver());
}
}
} }
void DesktopCapturerLacros::CaptureFrame() { void DesktopCapturerLacros::CaptureFrame() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureScreenManager(); if (snapshot_capturer_) {
snapshot_capturer_->TakeSnapshot(
if (capture_type_ == kScreen) { selected_source_,
screen_manager_->TakeScreenSnapshot(
base::BindOnce(&DesktopCapturerLacros::DidTakeSnapshot, base::BindOnce(&DesktopCapturerLacros::DidTakeSnapshot,
weak_factory_.GetWeakPtr()));
} else {
if (capture_type_ == kScreen) {
screen_manager_->DeprecatedTakeScreenSnapshot(
base::BindOnce(&DesktopCapturerLacros::DeprecatedDidTakeSnapshot,
weak_factory_.GetWeakPtr(), /*success*/ true)); weak_factory_.GetWeakPtr(), /*success*/ true));
} else { } else {
screen_manager_->TakeWindowSnapshot( screen_manager_->DeprecatedTakeWindowSnapshot(
selected_source_, selected_source_,
base::BindOnce(&DesktopCapturerLacros::DidTakeSnapshot, base::BindOnce(&DesktopCapturerLacros::DeprecatedDidTakeSnapshot,
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
} }
}
} }
bool DesktopCapturerLacros::IsOccluded(const webrtc::DesktopVector& pos) { bool DesktopCapturerLacros::IsOccluded(const webrtc::DesktopVector& pos) {
...@@ -98,21 +158,22 @@ void DesktopCapturerLacros::SetSharedMemoryFactory( ...@@ -98,21 +158,22 @@ void DesktopCapturerLacros::SetSharedMemoryFactory(
void DesktopCapturerLacros::SetExcludedWindow(webrtc::WindowId window) {} void DesktopCapturerLacros::SetExcludedWindow(webrtc::WindowId window) {}
void DesktopCapturerLacros::EnsureScreenManager() { void DesktopCapturerLacros::DidTakeSnapshot(bool success,
const SkBitmap& snapshot) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (screen_manager_) if (!success) {
callback_->OnCaptureResult(Result::ERROR_PERMANENT,
std::unique_ptr<webrtc::DesktopFrame>());
return; return;
}
// The lacros chrome service exists at all times except during early start-up callback_->OnCaptureResult(Result::SUCCESS,
// and late shut-down. This class should never be used in those two times. std::make_unique<DesktopFrameSkia>(snapshot));
auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get();
DCHECK(lacros_chrome_service);
lacros_chrome_service->BindScreenManagerReceiver(
screen_manager_.BindNewPipeAndPassReceiver());
} }
void DesktopCapturerLacros::DidTakeSnapshot(bool success, void DesktopCapturerLacros::DeprecatedDidTakeSnapshot(
bool success,
crosapi::Bitmap snapshot) { crosapi::Bitmap snapshot) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
......
...@@ -47,13 +47,10 @@ class DesktopCapturerLacros : public webrtc::DesktopCapturer { ...@@ -47,13 +47,10 @@ class DesktopCapturerLacros : public webrtc::DesktopCapturer {
void SetExcludedWindow(webrtc::WindowId window) override; void SetExcludedWindow(webrtc::WindowId window) override;
private: private:
// This method is used to lazily initialize screen_manager_ on the same
// affine sequence on which |Start| is called.
void EnsureScreenManager();
// Callback for when ash-chrome returns a snapshot of the screen or window as // Callback for when ash-chrome returns a snapshot of the screen or window as
// a bitmap. // a bitmap.
void DidTakeSnapshot(bool success, crosapi::Bitmap snapshot); void DidTakeSnapshot(bool success, const SkBitmap& snapshot);
void DeprecatedDidTakeSnapshot(bool success, crosapi::Bitmap snapshot);
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
...@@ -79,6 +76,10 @@ class DesktopCapturerLacros : public webrtc::DesktopCapturer { ...@@ -79,6 +76,10 @@ class DesktopCapturerLacros : public webrtc::DesktopCapturer {
// The remote connection to the screen manager. // The remote connection to the screen manager.
mojo::Remote<crosapi::mojom::ScreenManager> screen_manager_; mojo::Remote<crosapi::mojom::ScreenManager> screen_manager_;
// Only set if we are using a newer screen manager. Otherwise, we fallback to
// the deprecated methods.
mojo::Remote<crosapi::mojom::SnapshotCapturer> snapshot_capturer_;
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