Commit 4a74fb0f authored by Yuri Wiitala's avatar Yuri Wiitala Committed by Commit Bot

Migrate viz::FrameSinkVideoCapturer to use new read-only shmem API.

Now that overlay rendering takes place in VIZ, there is no need to pass
read-write handles across processes. This change migrates to the new
base::ReadOnlySharedMemoryRegion API. It is also a prerequisite to
migrating the rest of the video capture stack to the new read-only API.

Bug: 797470,843117
Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel
Change-Id: I87e16de42aa00e1fa0edba55a9cbf013fc345440
Reviewed-on: https://chromium-review.googlesource.com/1166248
Commit-Queue: Yuri Wiitala <miu@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Reviewed-by: default avatarSaman Sami <samans@chromium.org>
Reviewed-by: default avatarXiangjun Zhang <xjz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#587022}
parent fce1a0bf
......@@ -7,6 +7,7 @@
#include <utility>
#include "base/bind.h"
#include "base/memory/shared_memory_mapping.h"
#include "build/build_config.h"
#include "cc/paint/skia_paint_canvas.h"
#include "components/viz/common/features.h"
......@@ -18,6 +19,7 @@
#include "content/public/common/cursor_info.h"
#include "content/public/common/screen_info.h"
#include "media/base/limits.h"
#include "media/base/video_frame.h"
#include "third_party/blink/public/platform/web_input_event.h"
#include "third_party/blink/public/platform/web_mouse_event.h"
#include "third_party/skia/include/core/SkCanvas.h"
......@@ -272,8 +274,7 @@ void DevToolsEyeDropper::UpdateCursor() {
}
void DevToolsEyeDropper::OnFrameCaptured(
mojo::ScopedSharedBufferHandle buffer,
uint32_t buffer_size,
base::ReadOnlySharedMemoryRegion data,
::media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& update_rect,
const gfx::Rect& content_rect,
......@@ -285,25 +286,46 @@ void DevToolsEyeDropper::OnFrameCaptured(
return;
}
if (!buffer.is_valid()) {
if (!data.IsValid()) {
callbacks->Done();
return;
}
mojo::ScopedSharedBufferMapping mapping = buffer->Map(buffer_size);
if (!mapping) {
base::ReadOnlySharedMemoryMapping mapping = data.Map();
if (!mapping.IsValid()) {
DLOG(ERROR) << "Shared memory mapping failed.";
return;
}
if (mapping.size() <
media::VideoFrame::AllocationSize(info->pixel_format, info->coded_size)) {
DLOG(ERROR) << "Shared memory size was less than expected.";
return;
}
SkImageInfo image_info = SkImageInfo::MakeN32(
content_rect.width(), content_rect.height(), kPremul_SkAlphaType);
SkPixmap pixmap(image_info, mapping.get(),
media::VideoFrame::RowBytes(media::VideoFrame::kARGBPlane,
info->pixel_format,
info->coded_size.width()));
frame_.installPixels(pixmap);
shared_memory_mapping_ = std::move(mapping);
shared_memory_releaser_ = std::move(callbacks);
// The SkBitmap's pixels will be marked as immutable, but the installPixels()
// API requires a non-const pointer. So, cast away the const.
void* const pixels = const_cast<void*>(mapping.memory());
// Call installPixels() with a |releaseProc| that: 1) notifies the capturer
// that this consumer has finished with the frame, and 2) releases the shared
// memory mapping.
struct FramePinner {
// Keeps the shared memory that backs |frame_| mapped.
base::ReadOnlySharedMemoryMapping mapping;
// Prevents FrameSinkVideoCapturer from recycling the shared memory that
// backs |frame_|.
viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr releaser;
};
frame_.installPixels(
SkImageInfo::MakeN32(content_rect.width(), content_rect.height(),
kPremul_SkAlphaType),
pixels,
media::VideoFrame::RowBytes(media::VideoFrame::kARGBPlane,
info->pixel_format, info->coded_size.width()),
[](void* addr, void* context) {
delete static_cast<FramePinner*>(context);
},
new FramePinner{std::move(mapping), std::move(callbacks)});
frame_.setImmutable();
UpdateCursor();
}
......
......@@ -13,7 +13,6 @@
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents_observer.h"
#include "media/renderers/paint_canvas_video_renderer.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace blink {
......@@ -46,21 +45,13 @@ class DevToolsEyeDropper : public content::WebContentsObserver,
// viz::mojom::FrameSinkVideoConsumer implementation.
void OnFrameCaptured(
mojo::ScopedSharedBufferHandle buffer,
uint32_t buffer_size,
base::ReadOnlySharedMemoryRegion data,
::media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& update_rect,
const gfx::Rect& content_rect,
viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) override;
void OnStopped() override;
// This object keeps the shared memory that backs |frame_| mapped.
mojo::ScopedSharedBufferMapping shared_memory_mapping_;
// This object prevents FrameSinkVideoCapturer from recycling the shared
// memory that backs |frame_|.
viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr shared_memory_releaser_;
EyeDropperCallback callback_;
SkBitmap frame_;
int last_cursor_x_;
......
......@@ -4,8 +4,9 @@
#include "components/mirroring/service/fake_video_capture_host.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "media/base/video_frame.h"
#include "mojo/public/cpp/system/buffer.h"
#include "mojo/public/cpp/base/shared_memory_utils.h"
namespace mirroring {
......@@ -38,13 +39,11 @@ void FakeVideoCaptureHost::SendOneFrame(const gfx::Size& size,
if (!observer_)
return;
mojo::ScopedSharedBufferHandle buffer =
mojo::SharedBufferHandle::Create(5000);
memset(buffer->Map(5000).get(), 125, 5000);
media::mojom::VideoBufferHandlePtr buffer_handle =
media::mojom::VideoBufferHandle::New();
buffer_handle->set_shared_buffer_handle(std::move(buffer));
observer_->OnNewBuffer(0, std::move(buffer_handle));
auto shmem = mojo::CreateReadOnlySharedMemoryRegion(5000);
memset(shmem.mapping.memory(), 125, 5000);
observer_->OnNewBuffer(
0, media::mojom::VideoBufferHandle::NewReadOnlyShmemRegion(
std::move(shmem.region)));
media::VideoFrameMetadata metadata;
metadata.SetDouble(media::VideoFrameMetadata::FRAME_RATE, 30);
metadata.SetTimeTicks(media::VideoFrameMetadata::REFERENCE_TIME,
......
......@@ -92,7 +92,6 @@ void VideoCaptureClient::OnStateChanged(media::mojom::VideoCaptureState state) {
case media::mojom::VideoCaptureState::STOPPED:
case media::mojom::VideoCaptureState::ENDED:
client_buffers_.clear();
mapped_buffers_.clear();
weak_factory_.InvalidateWeakPtrs();
error_callback_.Reset();
frame_deliver_callback_.Reset();
......@@ -106,10 +105,13 @@ void VideoCaptureClient::OnNewBuffer(
media::mojom::VideoBufferHandlePtr buffer_handle) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(3) << __func__ << ": buffer_id=" << buffer_id;
DCHECK(buffer_handle->is_shared_buffer_handle());
if (!buffer_handle->is_read_only_shmem_region()) {
NOTIMPLEMENTED();
return;
}
const auto insert_result = client_buffers_.emplace(std::make_pair(
buffer_id, std::move(buffer_handle->get_shared_buffer_handle())));
buffer_id, std::move(buffer_handle->get_read_only_shmem_region())));
DCHECK(insert_result.second);
}
......@@ -154,48 +156,33 @@ void VideoCaptureClient::OnBufferReady(int32_t buffer_id,
"time_delta", info->timestamp.InMicroseconds());
const auto& buffer_iter = client_buffers_.find(buffer_id);
DCHECK(buffer_iter != client_buffers_.end());
auto mapping_iter = mapped_buffers_.find(buffer_id);
const size_t buffer_size =
media::VideoFrame::AllocationSize(info->pixel_format, info->coded_size);
if (mapping_iter != mapped_buffers_.end() &&
buffer_size > mapping_iter->second.second) {
// Unmaps shared memory for too-small region.
mapped_buffers_.erase(mapping_iter);
mapping_iter = mapped_buffers_.end();
if (buffer_iter == client_buffers_.end()) {
LOG(DFATAL) << "Ignoring OnBufferReady() for unknown buffer.";
return;
}
if (mapping_iter == mapped_buffers_.end()) {
const auto insert_result = mapped_buffers_.emplace(std::make_pair(
buffer_id,
MappingAndSize(buffer_iter->second->Map(buffer_size), buffer_size)));
DCHECK(insert_result.second);
mapping_iter = insert_result.first;
if (!mapping_iter->second.first) {
VLOG(1) << __func__ << ": Mapping Error";
mapped_buffers_.erase(mapping_iter);
video_capture_host_->ReleaseBuffer(kDeviceId, buffer_id, -1.0);
return;
}
base::ReadOnlySharedMemoryMapping mapping = buffer_iter->second.Map();
const size_t frame_allocation_size =
media::VideoFrame::AllocationSize(info->pixel_format, info->coded_size);
scoped_refptr<media::VideoFrame> frame;
if (mapping.IsValid() && mapping.size() >= frame_allocation_size) {
frame = media::VideoFrame::WrapExternalData(
info->pixel_format, info->coded_size, info->visible_rect,
info->visible_rect.size(),
const_cast<uint8_t*>(static_cast<const uint8_t*>(mapping.memory())),
frame_allocation_size, info->timestamp);
}
DCHECK(mapping_iter != mapped_buffers_.end());
const auto& buffer = mapping_iter->second;
scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
info->pixel_format, info->coded_size, info->visible_rect,
info->visible_rect.size(), reinterpret_cast<uint8_t*>(buffer.first.get()),
buffer.second, info->timestamp);
if (!frame) {
LOG(DFATAL) << "Unable to wrap shared memory mapping.";
video_capture_host_->ReleaseBuffer(kDeviceId, buffer_id, -1.0);
OnStateChanged(media::mojom::VideoCaptureState::FAILED);
return;
}
BufferFinishedCallback buffer_finished_callback = media::BindToCurrentLoop(
base::BindOnce(&VideoCaptureClient::OnClientBufferFinished,
weak_factory_.GetWeakPtr(), buffer_id));
BufferFinishedCallback buffer_finished_callback =
media::BindToCurrentLoop(base::BindOnce(
&VideoCaptureClient::OnClientBufferFinished,
weak_factory_.GetWeakPtr(), buffer_id, std::move(mapping)));
frame->AddDestructionObserver(
base::BindOnce(&VideoCaptureClient::DidFinishConsumingFrame,
frame->metadata(), std::move(buffer_finished_callback)));
......@@ -212,20 +199,17 @@ void VideoCaptureClient::OnBufferDestroyed(int32_t buffer_id) {
const auto& buffer_iter = client_buffers_.find(buffer_id);
if (buffer_iter != client_buffers_.end())
client_buffers_.erase(buffer_iter);
const auto& mapping_iter = mapped_buffers_.find(buffer_id);
if (mapping_iter != mapped_buffers_.end())
mapped_buffers_.erase(mapping_iter);
}
void VideoCaptureClient::OnClientBufferFinished(
int buffer_id,
base::ReadOnlySharedMemoryMapping mapping,
double consumer_resource_utilization) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(3) << __func__ << ": buffer_id=" << buffer_id;
// Buffer was already destroyed.
if (client_buffers_.find(buffer_id) == client_buffers_.end()) {
DCHECK(mapped_buffers_.find(buffer_id) == mapped_buffers_.end());
return;
}
......
......@@ -8,6 +8,7 @@
#include "base/callback.h"
#include "base/component_export.h"
#include "base/containers/flat_map.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
......@@ -64,8 +65,9 @@ class COMPONENT_EXPORT(MIRRORING_SERVICE) VideoCaptureClient
static void DidFinishConsumingFrame(const media::VideoFrameMetadata* metadata,
BufferFinishedCallback callback);
// Reports the utilization and returns the buffer.
// Reports the utilization, unmaps the shared memory, and returns the buffer.
void OnClientBufferFinished(int buffer_id,
base::ReadOnlySharedMemoryMapping mapping,
double consumer_resource_utilization);
const media::VideoCaptureParams params_;
......@@ -77,17 +79,10 @@ class COMPONENT_EXPORT(MIRRORING_SERVICE) VideoCaptureClient
mojo::Binding<media::mojom::VideoCaptureObserver> binding_;
using ClientBufferMap =
base::flat_map<int32_t, mojo::ScopedSharedBufferHandle>;
base::flat_map<int32_t, base::ReadOnlySharedMemoryRegion>;
// Stores the buffer handler on OnBufferCreated(). |buffer_id| is the key.
ClientBufferMap client_buffers_;
using MappingAndSize = std::pair<mojo::ScopedSharedBufferMapping, uint32_t>;
using MappingMap = base::flat_map<int32_t, MappingAndSize>;
// Stores the mapped buffers and their size. Each buffer is added the first
// time the mapping is done or a larger size is requested.
// |buffer_id| is the key to this map.
MappingMap mapped_buffers_;
// The reference time for the first frame. Used to calculate the timestamp of
// the captured frame if not provided in the frame info.
base::TimeTicks first_frame_ref_time_;
......
......@@ -4,12 +4,14 @@
#include "components/mirroring/service/video_capture_client.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "components/mirroring/service/fake_video_capture_host.h"
#include "media/base/video_frame.h"
#include "media/base/video_frame_metadata.h"
#include "mojo/public/cpp/base/shared_memory_utils.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -82,11 +84,9 @@ TEST_F(VideoCaptureClientTest, Basic) {
}
scoped_task_environment_.RunUntilIdle();
media::mojom::VideoBufferHandlePtr buffer_handle =
media::mojom::VideoBufferHandle::New();
buffer_handle->set_shared_buffer_handle(
mojo::SharedBufferHandle::Create(100000));
client_->OnNewBuffer(0, std::move(buffer_handle));
client_->OnNewBuffer(
0, media::mojom::VideoBufferHandle::NewReadOnlyShmemRegion(
mojo::CreateReadOnlySharedMemoryRegion(100000).region));
scoped_task_environment_.RunUntilIdle();
{
base::RunLoop run_loop;
......
......@@ -145,16 +145,15 @@ ClientFrameSinkVideoCapturer::ResolutionConstraints::ResolutionConstraints(
use_fixed_aspect_ratio(use_fixed_aspect_ratio) {}
void ClientFrameSinkVideoCapturer::OnFrameCaptured(
mojo::ScopedSharedBufferHandle buffer,
uint32_t buffer_size,
base::ReadOnlySharedMemoryRegion data,
media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& update_rect,
const gfx::Rect& content_rect,
mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
consumer_->OnFrameCaptured(std::move(buffer), buffer_size, std::move(info),
update_rect, content_rect, std::move(callbacks));
consumer_->OnFrameCaptured(std::move(data), std::move(info), update_rect,
content_rect, std::move(callbacks));
}
void ClientFrameSinkVideoCapturer::OnStopped() {
......
......@@ -115,8 +115,7 @@ class VIZ_HOST_EXPORT ClientFrameSinkVideoCapturer
// mojom::FrameSinkVideoConsumer implementation.
void OnFrameCaptured(
mojo::ScopedSharedBufferHandle buffer,
uint32_t buffer_size,
base::ReadOnlySharedMemoryRegion data,
media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& update_rect,
const gfx::Rect& content_rect,
......
......@@ -10,6 +10,7 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/stl_util.h"
#include "base/time/default_tick_clock.h"
#include "base/trace_event/trace_event.h"
......@@ -702,8 +703,9 @@ void FrameSinkVideoCapturerImpl::MaybeDeliverFrame(
// send to the consumer. The handle is READ_WRITE because the consumer is free
// to modify the content further (so long as it undoes its changes before the
// InFlightFrameDelivery::Done() call).
auto buffer_and_size = frame_pool_.CloneHandleForDelivery(frame.get());
DCHECK(buffer_and_size.first.is_valid());
base::ReadOnlySharedMemoryRegion handle =
frame_pool_.CloneHandleForDelivery(frame.get());
DCHECK(handle.IsValid());
// Assemble frame layout, format, and metadata into a mojo struct to send to
// the consumer.
......@@ -733,9 +735,8 @@ void FrameSinkVideoCapturerImpl::MaybeDeliverFrame(
mojo::MakeRequest(&callbacks));
// Send the frame to the consumer.
consumer_->OnFrameCaptured(std::move(buffer_and_size.first),
buffer_and_size.second, std::move(info),
update_rect, content_rect, std::move(callbacks));
consumer_->OnFrameCaptured(std::move(handle), std::move(info), update_rect,
content_rect, std::move(callbacks));
}
gfx::Size FrameSinkVideoCapturerImpl::AdjustSizeForPixelFormat(
......
......@@ -9,6 +9,7 @@
#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/test/test_mock_time_task_runner.h"
......@@ -131,33 +132,36 @@ class MockConsumer : public mojom::FrameSinkVideoConsumer {
private:
void OnFrameCaptured(
mojo::ScopedSharedBufferHandle buffer,
uint32_t buffer_size,
base::ReadOnlySharedMemoryRegion data,
media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& update_rect,
const gfx::Rect& content_rect,
mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) final {
ASSERT_TRUE(buffer.is_valid());
ASSERT_TRUE(data.IsValid());
const auto required_bytes_to_hold_planes =
static_cast<uint32_t>(info->coded_size.GetArea() * 3 / 2);
ASSERT_LE(required_bytes_to_hold_planes, buffer_size);
ASSERT_LE(required_bytes_to_hold_planes, data.GetSize());
ASSERT_TRUE(info);
EXPECT_EQ(gfx::Rect(kCaptureSize), update_rect);
ASSERT_TRUE(callbacks.get());
// Map the shared memory buffer and re-constitute a VideoFrame instance
// around it for analysis by OnFrameCapturedMock().
mojo::ScopedSharedBufferMapping mapping = buffer->Map(buffer_size);
ASSERT_TRUE(mapping);
base::ReadOnlySharedMemoryMapping mapping = data.Map();
ASSERT_TRUE(mapping.IsValid());
ASSERT_LE(
media::VideoFrame::AllocationSize(info->pixel_format, info->coded_size),
mapping.size());
scoped_refptr<media::VideoFrame> frame =
media::VideoFrame::WrapExternalData(
info->pixel_format, info->coded_size, info->visible_rect,
info->visible_rect.size(), static_cast<uint8_t*>(mapping.get()),
buffer_size, info->timestamp);
info->visible_rect.size(),
const_cast<uint8_t*>(static_cast<const uint8_t*>(mapping.memory())),
mapping.size(), info->timestamp);
ASSERT_TRUE(frame);
frame->metadata()->MergeInternalValuesFrom(info->metadata);
frame->AddDestructionObserver(base::BindOnce(
[](mojo::ScopedSharedBufferMapping mapping) {}, std::move(mapping)));
[](base::ReadOnlySharedMemoryMapping mapping) {}, std::move(mapping)));
OnFrameCapturedMock(frame, update_rect, callbacks.get());
frames_.push_back(std::move(frame));
......
......@@ -8,6 +8,7 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "mojo/public/cpp/base/shared_memory_utils.h"
using media::VideoFrame;
using media::VideoPixelFormat;
......@@ -31,7 +32,7 @@ scoped_refptr<VideoFrame> InterprocessFramePool::ReserveVideoFrame(
// Calling this method is a signal that there is no intention of resurrecting
// the last frame.
resurrectable_handle_ = MOJO_HANDLE_INVALID;
resurrectable_buffer_memory_ = nullptr;
const size_t bytes_required = VideoFrame::AllocationSize(format, size);
......@@ -39,7 +40,7 @@ scoped_refptr<VideoFrame> InterprocessFramePool::ReserveVideoFrame(
// in a VideoFrame and return it.
for (auto it = available_buffers_.rbegin(); it != available_buffers_.rend();
++it) {
if (it->bytes_allocated < bytes_required) {
if (it->mapping.size() < bytes_required) {
continue;
}
PooledBuffer taken = std::move(*it);
......@@ -53,17 +54,16 @@ scoped_refptr<VideoFrame> InterprocessFramePool::ReserveVideoFrame(
const auto it =
std::max_element(available_buffers_.rbegin(), available_buffers_.rend(),
[this](const PooledBuffer& a, const PooledBuffer& b) {
return a.bytes_allocated < b.bytes_allocated;
return a.mapping.size() < b.mapping.size();
});
available_buffers_.erase(it.base() - 1); // Release before allocating more.
PooledBuffer reallocated;
reallocated.buffer = mojo::SharedBufferHandle::Create(bytes_required);
if (!reallocated.buffer.is_valid()) {
PooledBuffer reallocated =
mojo::CreateReadOnlySharedMemoryRegion(bytes_required);
if (!reallocated.IsValid()) {
LOG_IF(WARNING, CanLogSharedMemoryFailure())
<< "Failed to re-allocate " << bytes_required << " bytes.";
continue; // Try again after freeing the next-largest buffer.
}
reallocated.bytes_allocated = bytes_required;
return WrapBuffer(std::move(reallocated), format, size);
}
......@@ -72,14 +72,13 @@ scoped_refptr<VideoFrame> InterprocessFramePool::ReserveVideoFrame(
if (utilized_buffers_.size() >= capacity_) {
return nullptr;
}
PooledBuffer additional;
additional.buffer = mojo::SharedBufferHandle::Create(bytes_required);
if (!additional.buffer.is_valid()) {
PooledBuffer additional =
mojo::CreateReadOnlySharedMemoryRegion(bytes_required);
if (!additional.IsValid()) {
LOG_IF(WARNING, CanLogSharedMemoryFailure())
<< "Failed to allocate " << bytes_required << " bytes.";
return nullptr;
}
additional.bytes_allocated = bytes_required;
return WrapBuffer(std::move(additional), format, size);
}
......@@ -90,7 +89,7 @@ scoped_refptr<VideoFrame> InterprocessFramePool::ResurrectLastVideoFrame(
// Find the tracking entry for the resurrectable buffer. If it is still being
// used, or is not of the expected format and size, punt.
if (resurrectable_handle_ == MOJO_HANDLE_INVALID ||
if (resurrectable_buffer_memory_ == nullptr ||
last_delivered_format_ != expected_format ||
last_delivered_size_ != expected_size) {
return nullptr;
......@@ -98,7 +97,7 @@ scoped_refptr<VideoFrame> InterprocessFramePool::ResurrectLastVideoFrame(
const auto it = std::find_if(
available_buffers_.rbegin(), available_buffers_.rend(),
[this](const PooledBuffer& candidate) {
return candidate.buffer.get().value() == resurrectable_handle_;
return candidate.mapping.memory() == resurrectable_buffer_memory_;
});
if (it == available_buffers_.rend()) {
return nullptr;
......@@ -110,21 +109,21 @@ scoped_refptr<VideoFrame> InterprocessFramePool::ResurrectLastVideoFrame(
return WrapBuffer(std::move(resurrected), expected_format, expected_size);
}
InterprocessFramePool::BufferAndSize
InterprocessFramePool::CloneHandleForDelivery(const VideoFrame* frame) {
base::ReadOnlySharedMemoryRegion InterprocessFramePool::CloneHandleForDelivery(
const VideoFrame* frame) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Record that this frame is the last-delivered one, for possible future calls
// to ResurrectLastVideoFrame().
const auto it = utilized_buffers_.find(frame);
DCHECK(it != utilized_buffers_.end());
resurrectable_handle_ = it->second.buffer.get().value();
// Assumption: The first image plane's memory pointer should be the start of
// the writable mapped memory. WrapBuffer() sanity-checks this.
resurrectable_buffer_memory_ = frame->data(0);
last_delivered_format_ = frame->format();
last_delivered_size_ = frame->coded_size();
return BufferAndSize(it->second.buffer->Clone(
mojo::SharedBufferHandle::AccessMode::READ_WRITE),
it->second.bytes_allocated);
return it->second.Duplicate();
}
float InterprocessFramePool::GetUtilization() const {
......@@ -138,59 +137,49 @@ scoped_refptr<VideoFrame> InterprocessFramePool::WrapBuffer(
VideoPixelFormat format,
const gfx::Size& size) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(pooled_buffer.buffer.is_valid());
// Map the shared memory into the current process, or move an existing
// "scoped mapping" into the local variable. The mapping will be owned by the
// VideoFrame wrapper (below) until the VideoFrame goes out-of-scope; and this
// might be after InterprocessFramePool is destroyed.
mojo::ScopedSharedBufferMapping mapping;
if (pooled_buffer.mapping) {
mapping = std::move(pooled_buffer.mapping);
} else {
mapping = pooled_buffer.buffer->Map(pooled_buffer.bytes_allocated);
if (!mapping) {
LOG_IF(WARNING, CanLogSharedMemoryFailure())
<< "Failed to map shared memory to back the VideoFrame ("
<< pooled_buffer.bytes_allocated << " bytes).";
// The shared memory will be freed when pooled_buffer goes out-of-scope
// here:
return nullptr;
}
}
// Create the VideoFrame wrapper, add tracking in |utilized_buffers_|, and add
// a destruction observer to return the buffer to the pool once the VideoFrame
// goes out-of-scope.
DCHECK(pooled_buffer.IsValid());
// Create the VideoFrame wrapper. The two components of |pooled_buffer| are
// split: The shared memory handle is moved off to the side (in a
// |utilized_buffers_| map entry), while the writable mapping is transferred
// to the VideoFrame. When the VideoFrame goes out-of-scope, a destruction
// observer will re-assemble the PooledBuffer from these two components and
// return it to the |available_buffers_| pool.
//
// The VideoFrame could be held, externally, beyond the lifetime of this
// InterprocessFramePool. However, this is safe because 1) the use of a
// WeakPtr cancels the callback that would return the buffer back to the pool,
// and 2) the mapped memory remains valid until the ScopedSharedBufferMapping
// goes out-of-scope (when the OnceClosure is destroyed).
// and 2) the mapped memory remains valid until the
// WritableSharedMemoryMapping goes out-of-scope (when the OnceClosure is
// destroyed).
scoped_refptr<VideoFrame> frame = VideoFrame::WrapExternalData(
format, size, gfx::Rect(size), size, static_cast<uint8_t*>(mapping.get()),
pooled_buffer.bytes_allocated, base::TimeDelta());
format, size, gfx::Rect(size), size,
static_cast<uint8_t*>(pooled_buffer.mapping.memory()),
pooled_buffer.mapping.size(), base::TimeDelta());
DCHECK(frame);
utilized_buffers_.emplace(frame.get(), std::move(pooled_buffer));
// Sanity-check the assumption being made in CloneHandleForDelivery():
DCHECK_EQ(frame->data(0), pooled_buffer.mapping.memory());
utilized_buffers_.emplace(frame.get(), std::move(pooled_buffer.region));
frame->AddDestructionObserver(
base::BindOnce(&InterprocessFramePool::OnFrameWrapperDestroyed,
weak_factory_.GetWeakPtr(), base::Unretained(frame.get()),
std::move(mapping)));
std::move(pooled_buffer.mapping)));
return frame;
}
void InterprocessFramePool::OnFrameWrapperDestroyed(
const VideoFrame* frame,
mojo::ScopedSharedBufferMapping mapping) {
base::WritableSharedMemoryMapping mapping) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(mapping.IsValid());
// Return the buffer to the pool by moving the PooledBuffer back into
// |available_buffers_|.
const auto it = utilized_buffers_.find(frame);
DCHECK(it != utilized_buffers_.end());
available_buffers_.emplace_back(std::move(it->second));
available_buffers_.back().mapping = std::move(mapping);
available_buffers_.emplace_back(
PooledBuffer{std::move(it->second), std::move(mapping)});
DCHECK(available_buffers_.back().IsValid());
utilized_buffers_.erase(it);
DCHECK_LE(available_buffers_.size() + utilized_buffers_.size(), capacity_);
}
......@@ -206,11 +195,4 @@ bool InterprocessFramePool::CanLogSharedMemoryFailure() {
return false;
}
InterprocessFramePool::PooledBuffer::PooledBuffer() = default;
InterprocessFramePool::PooledBuffer::PooledBuffer(
PooledBuffer&& other) noexcept = default;
InterprocessFramePool::PooledBuffer& InterprocessFramePool::PooledBuffer::
operator=(PooledBuffer&& other) noexcept = default;
InterprocessFramePool::PooledBuffer::~PooledBuffer() = default;
} // namespace viz
......@@ -11,14 +11,15 @@
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "components/viz/service/viz_service_export.h"
#include "media/base/video_frame.h"
#include "media/base/video_types.h"
#include "mojo/public/cpp/system/buffer.h"
#include "ui/gfx/geometry/size.h"
namespace viz {
......@@ -27,8 +28,6 @@ namespace viz {
// efficiently across mojo service boundaries.
class VIZ_SERVICE_EXPORT InterprocessFramePool {
public:
using BufferAndSize = std::pair<mojo::ScopedSharedBufferHandle, size_t>;
// |capacity| is the maximum number of pooled VideoFrames; but they can be of
// any byte size.
explicit InterprocessFramePool(int capacity);
......@@ -58,24 +57,15 @@ class VIZ_SERVICE_EXPORT InterprocessFramePool {
//
// Calling this method is a signal that |frame| should be considered the
// last-delivered frame, for the purposes of ResurrectLastVideoFrame().
BufferAndSize CloneHandleForDelivery(const media::VideoFrame* frame);
base::ReadOnlySharedMemoryRegion CloneHandleForDelivery(
const media::VideoFrame* frame);
// Returns the current pool utilization, based on the number of VideoFrames
// being held by the client.
float GetUtilization() const;
private:
// Tracking data for pooled buffers.
struct PooledBuffer {
mojo::ScopedSharedBufferHandle buffer;
size_t bytes_allocated;
mojo::ScopedSharedBufferMapping mapping;
PooledBuffer();
PooledBuffer(PooledBuffer&&) noexcept;
PooledBuffer& operator=(PooledBuffer&&) noexcept;
~PooledBuffer();
};
using PooledBuffer = base::MappedReadOnlyRegion;
// Creates a media::VideoFrame backed by a specific pooled buffer.
scoped_refptr<media::VideoFrame> WrapBuffer(PooledBuffer pooled_buffer,
......@@ -86,7 +76,7 @@ class VIZ_SERVICE_EXPORT InterprocessFramePool {
// the entry from |utilized_buffers_| and place the PooledBuffer back into
// |available_buffers_|.
void OnFrameWrapperDestroyed(const media::VideoFrame* frame,
mojo::ScopedSharedBufferMapping mapping);
base::WritableSharedMemoryMapping mapping);
// Returns true if a shared memory failure can be logged. This is a rate
// throttle, to ensure the logs aren't spammed in chronically low-memory
......@@ -105,12 +95,13 @@ class VIZ_SERVICE_EXPORT InterprocessFramePool {
// A map of externally-owned VideoFrames and the tracking information about
// the shared memory buffer backing them.
base::flat_map<const media::VideoFrame*, PooledBuffer> utilized_buffers_;
base::flat_map<const media::VideoFrame*, base::ReadOnlySharedMemoryRegion>
utilized_buffers_;
// The handle of the buffer that was last delivered, along with its format and
// size. ResurrectLastVideoFrame() uses this information to locate and confirm
// that a prior frame can be resurrected.
MojoHandle resurrectable_handle_ = MOJO_HANDLE_INVALID;
// The pointer to the mapped memory of the buffer that was last delivered,
// along with its format and size. ResurrectLastVideoFrame() uses this
// information to locate and confirm that a prior frame can be resurrected.
const void* resurrectable_buffer_memory_ = nullptr;
media::VideoPixelFormat last_delivered_format_ = media::PIXEL_FORMAT_UNKNOWN;
gfx::Size last_delivered_size_;
......
......@@ -18,12 +18,12 @@ namespace {
constexpr gfx::Size kSize = gfx::Size(32, 18);
constexpr media::VideoPixelFormat kFormat = media::PIXEL_FORMAT_I420;
void ExpectValidBufferForDelivery(
const InterprocessFramePool::BufferAndSize& buffer_and_size) {
EXPECT_TRUE(buffer_and_size.first.is_valid());
void ExpectValidHandleForDelivery(
const base::ReadOnlySharedMemoryRegion& region) {
EXPECT_TRUE(region.IsValid());
constexpr int kI420BitsPerPixel = 12;
EXPECT_LE(static_cast<size_t>(kSize.GetArea() * kI420BitsPerPixel / 8),
buffer_and_size.second);
region.GetSize());
}
TEST(InterprocessFramePoolTest, FramesConfiguredCorrectly) {
......@@ -51,9 +51,9 @@ TEST(InterprocessFramePool, UsesAvailableBuffersIfPossible) {
ASSERT_TRUE(frame);
size_t baseline_bytes_allocated;
{
auto buffer_and_size = pool.CloneHandleForDelivery(frame.get());
ExpectValidBufferForDelivery(buffer_and_size);
baseline_bytes_allocated = buffer_and_size.second;
auto handle = pool.CloneHandleForDelivery(frame.get());
ExpectValidHandleForDelivery(handle);
baseline_bytes_allocated = handle.GetSize();
}
frame = nullptr; // Returns frame to pool.
......@@ -62,9 +62,9 @@ TEST(InterprocessFramePool, UsesAvailableBuffersIfPossible) {
frame = pool.ReserveVideoFrame(kFormat, kSmallerSize);
ASSERT_TRUE(frame);
{
auto buffer_and_size = pool.CloneHandleForDelivery(frame.get());
ExpectValidBufferForDelivery(buffer_and_size);
EXPECT_EQ(baseline_bytes_allocated, buffer_and_size.second);
auto handle = pool.CloneHandleForDelivery(frame.get());
ExpectValidHandleForDelivery(handle);
EXPECT_EQ(baseline_bytes_allocated, handle.GetSize());
}
frame = nullptr; // Returns frame to pool.
......@@ -75,9 +75,9 @@ TEST(InterprocessFramePool, UsesAvailableBuffersIfPossible) {
ASSERT_TRUE(frame);
size_t larger_buffer_bytes_allocated;
{
auto buffer_and_size = pool.CloneHandleForDelivery(frame.get());
ExpectValidBufferForDelivery(buffer_and_size);
larger_buffer_bytes_allocated = buffer_and_size.second;
auto handle = pool.CloneHandleForDelivery(frame.get());
ExpectValidHandleForDelivery(handle);
larger_buffer_bytes_allocated = handle.GetSize();
EXPECT_LT(baseline_bytes_allocated, larger_buffer_bytes_allocated);
}
frame = nullptr; // Returns frame to pool.
......@@ -89,9 +89,9 @@ TEST(InterprocessFramePool, UsesAvailableBuffersIfPossible) {
frame = pool.ReserveVideoFrame(kFormat, size);
ASSERT_TRUE(frame);
{
auto buffer_and_size = pool.CloneHandleForDelivery(frame.get());
ExpectValidBufferForDelivery(buffer_and_size);
EXPECT_EQ(larger_buffer_bytes_allocated, buffer_and_size.second);
auto handle = pool.CloneHandleForDelivery(frame.get());
ExpectValidHandleForDelivery(handle);
EXPECT_EQ(larger_buffer_bytes_allocated, handle.GetSize());
}
frame = nullptr; // Returns frame to pool.
}
......@@ -165,8 +165,8 @@ TEST(InterprocessFramePoolTest, ResurrectsDeliveredFramesOnly) {
const uint8_t kValues[3] = {0x44, 0x55, 0x66};
media::FillYUV(frame.get(), kValues[0], kValues[1], kValues[2]);
{
auto buffer_and_size = pool.CloneHandleForDelivery(frame.get());
ExpectValidBufferForDelivery(buffer_and_size);
auto handle = pool.CloneHandleForDelivery(frame.get());
ExpectValidHandleForDelivery(handle);
}
frame = nullptr; // Returns frame to pool.
......@@ -190,8 +190,8 @@ TEST(InterprocessFramePoolTest, ResurrectsDeliveredFramesOnly) {
media::FillYUV(frame.get(), 0x77, 0x88, 0x99);
}
{
auto buffer_and_size = pool.CloneHandleForDelivery(frame.get());
ExpectValidBufferForDelivery(buffer_and_size);
auto handle = pool.CloneHandleForDelivery(frame.get());
ExpectValidHandleForDelivery(handle);
}
scoped_refptr<media::VideoFrame> should_be_null =
pool.ResurrectLastVideoFrame(kFormat, kSize);
......@@ -226,8 +226,8 @@ TEST(InterprocessFramePoolTest, ReportsCorrectUtilization) {
// Signal that the frame will be delivered. This should not change the
// utilization.
{
auto buffer_and_size = pool.CloneHandleForDelivery(frame.get());
ExpectValidBufferForDelivery(buffer_and_size);
auto handle = pool.CloneHandleForDelivery(frame.get());
ExpectValidHandleForDelivery(handle);
}
ASSERT_EQ(0.5f, pool.GetUtilization());
......
......@@ -6,6 +6,7 @@
#include <utility>
#include "base/memory/shared_memory_mapping.h"
#include "cc/paint/skia_paint_canvas.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h"
......@@ -129,31 +130,46 @@ bool DevToolsVideoConsumer::IsValidMinAndMaxFrameSize(
}
void DevToolsVideoConsumer::OnFrameCaptured(
mojo::ScopedSharedBufferHandle buffer,
uint32_t buffer_size,
base::ReadOnlySharedMemoryRegion data,
::media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& update_rect,
const gfx::Rect& content_rect,
viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) {
if (!buffer.is_valid())
if (!data.IsValid())
return;
mojo::ScopedSharedBufferMapping mapping = buffer->Map(buffer_size);
if (!mapping) {
base::ReadOnlySharedMemoryMapping mapping = data.Map();
if (!mapping.IsValid()) {
DLOG(ERROR) << "Shared memory mapping failed.";
return;
}
if (mapping.size() <
media::VideoFrame::AllocationSize(info->pixel_format, info->coded_size)) {
DLOG(ERROR) << "Shared memory size was less than expected.";
return;
}
scoped_refptr<media::VideoFrame> frame;
// Create a media::VideoFrame that wraps the read-only shared memory data.
// Unfortunately, a deep web of not-const-correct code exists in
// media::VideoFrame and media::PaintCanvasVideoRenderer (see
// GetSkBitmapFromFrame() above). So, the pointer's const attribute must be
// casted away. This is safe since the operating system will page fault if
// there is any attempt downstream to mutate the data.
//
// Setting |frame|'s visible rect equal to |content_rect| so that only the
// portion of the frame that contain content are used.
frame = media::VideoFrame::WrapExternalData(
// portion of the frame that contains content is used.
scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
info->pixel_format, info->coded_size, content_rect, content_rect.size(),
static_cast<uint8_t*>(mapping.get()), buffer_size, info->timestamp);
if (!frame)
const_cast<uint8_t*>(static_cast<const uint8_t*>(mapping.memory())),
mapping.size(), info->timestamp);
if (!frame) {
DLOG(ERROR) << "Unable to create VideoFrame wrapper around the shmem.";
return;
}
frame->AddDestructionObserver(base::BindOnce(
[](mojo::ScopedSharedBufferMapping mapping) {}, std::move(mapping)));
[](base::ReadOnlySharedMemoryMapping mapping,
viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) {},
std::move(mapping), std::move(callbacks)));
frame->metadata()->MergeInternalValuesFrom(info->metadata);
callback_.Run(std::move(frame));
......
......@@ -10,7 +10,6 @@
#include "base/time/time.h"
#include "components/viz/host/client_frame_sink_video_capturer.h"
#include "content/common/content_export.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "ui/gfx/geometry/size.h"
class SkBitmap;
......@@ -64,8 +63,7 @@ class CONTENT_EXPORT DevToolsVideoConsumer
// viz::mojom::FrameSinkVideoConsumer:
void OnFrameCaptured(
mojo::ScopedSharedBufferHandle buffer,
uint32_t buffer_size,
base::ReadOnlySharedMemoryRegion data,
::media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& update_rect,
const gfx::Rect& content_rect,
......
......@@ -5,10 +5,13 @@
#include <utility>
#include <vector>
#include "base/memory/read_only_shared_memory_region.h"
#include "base/message_loop/message_loop.h"
#include "content/browser/devtools/devtools_video_consumer.h"
#include "content/public/test/test_utils.h"
#include "media/base/limits.h"
#include "mojo/public/cpp/base/shared_memory_utils.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "testing/gmock/include/gmock/gmock.h"
using testing::_;
......@@ -172,8 +175,7 @@ class DevToolsVideoConsumerTest : public testing::Test {
consumer_->SetFrameSinkId(kInitialFrameSinkId);
}
void SimulateFrameCapture(mojo::ScopedSharedBufferHandle buffer,
uint32_t buffer_size) {
void SimulateFrameCapture(base::ReadOnlySharedMemoryRegion data) {
viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks_ptr;
callbacks.Bind(mojo::MakeRequest(&callbacks_ptr));
......@@ -181,7 +183,7 @@ class DevToolsVideoConsumerTest : public testing::Test {
base::TimeDelta(), base::Value(base::Value::Type::DICTIONARY), kFormat,
kResolution, gfx::Rect(kResolution));
consumer_->OnFrameCaptured(std::move(buffer), buffer_size, std::move(info),
consumer_->OnFrameCaptured(std::move(data), std::move(info),
gfx::Rect(kResolution), gfx::Rect(kResolution),
std::move(callbacks_ptr));
}
......@@ -235,48 +237,40 @@ class DevToolsVideoConsumerTest : public testing::Test {
// Tests that the OnFrameFromVideoConsumer callbacks is called when
// OnFrameCaptured is passed a valid buffer with valid mapping.
TEST_F(DevToolsVideoConsumerTest, CallbacksAreCalledWhenBufferValid) {
// Create a valid buffer.
const size_t buffer_size =
media::VideoFrame::AllocationSize(kFormat, kResolution);
mojo::ScopedSharedBufferHandle buffer =
mojo::SharedBufferHandle::Create(buffer_size);
// On valid buffer the |receiver_| gets a frame via OnFrameFromVideoConsumer.
EXPECT_CALL(receiver_, OnFrameFromVideoConsumerMock(_)).Times(1);
SimulateFrameCapture(std::move(buffer), buffer_size);
auto region = mojo::CreateReadOnlySharedMemoryRegion(
media::VideoFrame::AllocationSize(kFormat, kResolution))
.region;
ASSERT_TRUE(region.IsValid());
SimulateFrameCapture(std::move(region));
base::RunLoop().RunUntilIdle();
}
// Tests that only the OnFrameFromVideoConsumer callback is not called when
// OnFrameCaptured is passed an invalid buffer.
TEST_F(DevToolsVideoConsumerTest, OnFrameCapturedExitEarlyOnInvalidBuffer) {
// Create an invalid buffer.
const size_t buffer_size = 0;
mojo::ScopedSharedBufferHandle buffer =
mojo::SharedBufferHandle::Create(buffer_size);
TEST_F(DevToolsVideoConsumerTest, CallbackIsNotCalledWhenBufferIsNotValid) {
// On invalid buffer, the |receiver_| doesn't get a frame.
EXPECT_CALL(receiver_, OnFrameFromVideoConsumerMock(_)).Times(0);
SimulateFrameCapture(std::move(buffer), buffer_size);
SimulateFrameCapture(base::ReadOnlySharedMemoryRegion());
base::RunLoop().RunUntilIdle();
}
// Tests that the OnFrameFromVideoConsumer callback is not called when
// OnFrameCaptured is passed a buffer with invalid mapping.
TEST_F(DevToolsVideoConsumerTest, OnFrameCapturedExitsOnInvalidMapping) {
// Create a valid buffer, but change buffer_size to simulate an invalid
// mapping.
size_t buffer_size = media::VideoFrame::AllocationSize(kFormat, kResolution);
mojo::ScopedSharedBufferHandle buffer =
mojo::SharedBufferHandle::Create(buffer_size);
buffer_size = 0;
// OnFrameCaptured is passed a buffer with less-than-expected size.
TEST_F(DevToolsVideoConsumerTest, CallbackIsNotCalledWhenBufferIsTooSmall) {
// On invalid mapping, the |receiver_| doesn't get a frame.
EXPECT_CALL(receiver_, OnFrameFromVideoConsumerMock(_)).Times(0);
SimulateFrameCapture(std::move(buffer), buffer_size);
constexpr size_t too_few_number_of_bytes = 4;
ASSERT_LT(too_few_number_of_bytes,
media::VideoFrame::AllocationSize(kFormat, kResolution));
auto region =
mojo::CreateReadOnlySharedMemoryRegion(too_few_number_of_bytes).region;
ASSERT_TRUE(region.IsValid());
SimulateFrameCapture(std::move(region));
base::RunLoop().RunUntilIdle();
}
......
......@@ -48,29 +48,28 @@ class FakeVideoCaptureStack::Receiver : public media::VideoFrameReceiver {
media::mojom::VideoFrameInfoPtr frame_info) final {
const auto it = buffers_.find(buffer_id);
CHECK(it != buffers_.end());
CHECK(it->second->is_shared_buffer_handle());
mojo::ScopedSharedBufferHandle& buffer =
it->second->get_shared_buffer_handle();
const size_t mapped_size =
media::VideoCaptureFormat(frame_info->coded_size, 0.0f,
frame_info->pixel_format)
.ImageAllocationSize();
mojo::ScopedSharedBufferMapping mapping = buffer->Map(mapped_size);
CHECK(mapping.get());
CHECK(it->second->is_read_only_shmem_region());
base::ReadOnlySharedMemoryMapping mapping =
it->second->get_read_only_shmem_region().Map();
CHECK(mapping.IsValid());
CHECK_LE(media::VideoCaptureFormat(frame_info->coded_size, 0.0f,
frame_info->pixel_format)
.ImageAllocationSize(),
mapping.size());
auto frame = media::VideoFrame::WrapExternalData(
frame_info->pixel_format, frame_info->coded_size,
frame_info->visible_rect, frame_info->visible_rect.size(),
reinterpret_cast<uint8_t*>(mapping.get()), mapped_size,
frame_info->timestamp);
const_cast<uint8_t*>(static_cast<const uint8_t*>(mapping.memory())),
mapping.size(), frame_info->timestamp);
CHECK(frame);
frame->metadata()->MergeInternalValuesFrom(frame_info->metadata);
// This destruction observer will unmap the shared memory when the
// VideoFrame goes out-of-scope.
frame->AddDestructionObserver(
base::BindOnce(base::DoNothing::Once<mojo::ScopedSharedBufferMapping>(),
std::move(mapping)));
frame->AddDestructionObserver(base::BindOnce(
base::DoNothing::Once<base::ReadOnlySharedMemoryMapping>(),
std::move(mapping)));
// This destruction observer will notify the video capture device once all
// downstream code is done using the VideoFrame.
frame->AddDestructionObserver(base::BindOnce(
......
......@@ -21,7 +21,6 @@
#include "content/browser/media/capture/mouse_cursor_overlay_controller.h"
#include "media/base/bind_to_current_loop.h"
#include "media/capture/mojom/video_capture_types.mojom.h"
#include "mojo/public/cpp/system/buffer.h"
namespace content {
......@@ -179,8 +178,7 @@ void FrameSinkVideoCaptureDevice::OnUtilizationReport(int frame_feedback_id,
}
void FrameSinkVideoCaptureDevice::OnFrameCaptured(
mojo::ScopedSharedBufferHandle buffer,
uint32_t buffer_size,
base::ReadOnlySharedMemoryRegion data,
media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& update_rect,
const gfx::Rect& content_rect,
......@@ -188,7 +186,7 @@ void FrameSinkVideoCaptureDevice::OnFrameCaptured(
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callbacks);
if (!receiver_ || !buffer.is_valid()) {
if (!receiver_ || !data.IsValid()) {
callbacks->Done();
return;
}
......@@ -223,10 +221,9 @@ void FrameSinkVideoCaptureDevice::OnFrameCaptured(
// Pass the video frame to the VideoFrameReceiver. This is done by first
// passing the shared memory buffer handle and then notifying it that a new
// frame is ready to be read from the buffer.
media::mojom::VideoBufferHandlePtr buffer_handle =
media::mojom::VideoBufferHandle::New();
buffer_handle->set_shared_buffer_handle(std::move(buffer));
receiver_->OnNewBuffer(buffer_id, std::move(buffer_handle));
receiver_->OnNewBuffer(
buffer_id,
media::mojom::VideoBufferHandle::NewReadOnlyShmemRegion(std::move(data)));
receiver_->OnFrameReadyInBuffer(
buffer_id, buffer_id,
std::make_unique<ScopedFrameDoneHelper>(
......
......@@ -73,8 +73,7 @@ class CONTENT_EXPORT FrameSinkVideoCaptureDevice
// FrameSinkVideoConsumer implementation.
void OnFrameCaptured(
mojo::ScopedSharedBufferHandle buffer,
uint32_t buffer_size,
base::ReadOnlySharedMemoryRegion data,
media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& update_rect,
const gfx::Rect& content_rect,
......
......@@ -9,11 +9,14 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/containers/flat_map.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/shared_memory_mapping.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "media/base/video_frame.h"
#include "media/capture/video/video_frame_receiver.h"
#include "media/capture/video_capture_types.h"
#include "mojo/public/cpp/base/shared_memory_utils.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "services/viz/privileged/interfaces/compositing/frame_sink_video_capture.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
......@@ -189,15 +192,15 @@ class MockVideoFrameReceiver : public media::VideoFrameReceiver {
MOCK_METHOD0(OnStarted, void());
void OnStartedUsingGpuDecode() final { NOTREACHED(); }
mojo::ScopedSharedBufferHandle TakeBufferHandle(int buffer_id) {
base::ReadOnlySharedMemoryRegion TakeBufferHandle(int buffer_id) {
DCHECK_NOT_ON_DEVICE_THREAD();
const auto it = buffer_handles_.find(buffer_id);
if (it == buffer_handles_.end()) {
ADD_FAILURE() << "Missing entry for buffer_id=" << buffer_id;
return mojo::ScopedSharedBufferHandle();
return base::ReadOnlySharedMemoryRegion();
}
CHECK(it->second->is_shared_buffer_handle());
auto buffer = std::move(it->second->get_shared_buffer_handle());
CHECK(it->second->is_read_only_shmem_region());
auto buffer = std::move(it->second->get_read_only_shmem_region());
buffer_handles_.erase(it);
return buffer;
}
......@@ -346,12 +349,11 @@ class FrameSinkVideoCaptureDeviceTest : public testing::Test {
int frame_number,
MockFrameSinkVideoConsumerFrameCallbacks* callbacks) {
// Allocate a buffer and fill it with values based on |frame_number|.
const size_t buffer_size =
media::VideoFrame::AllocationSize(kFormat, kResolution);
mojo::ScopedSharedBufferHandle buffer =
mojo::SharedBufferHandle::Create(buffer_size);
memset(buffer->Map(buffer_size).get(), GetFrameFillValue(frame_number),
buffer_size);
base::MappedReadOnlyRegion region = mojo::CreateReadOnlySharedMemoryRegion(
media::VideoFrame::AllocationSize(kFormat, kResolution));
CHECK(region.IsValid());
memset(region.mapping.memory(), GetFrameFillValue(frame_number),
region.mapping.size());
viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks_ptr;
callbacks->Bind(mojo::MakeRequest(&callbacks_ptr));
......@@ -359,13 +361,12 @@ class FrameSinkVideoCaptureDeviceTest : public testing::Test {
// to the device thread before calling OnFrameCaptured().
POST_DEVICE_TASK(base::BindOnce(
[](FrameSinkVideoCaptureDevice* device,
mojo::ScopedSharedBufferHandle buffer, size_t buffer_size,
int frame_number,
base::ReadOnlySharedMemoryRegion data, int frame_number,
mojo::InterfacePtrInfo<
viz::mojom::FrameSinkVideoConsumerFrameCallbacks>
callbacks_info) {
device->OnFrameCaptured(
std::move(buffer), buffer_size,
std::move(data),
media::mojom::VideoFrameInfo::New(
kMinCapturePeriod * frame_number,
base::Value(base::Value::Type::DICTIONARY), kFormat,
......@@ -374,8 +375,8 @@ class FrameSinkVideoCaptureDeviceTest : public testing::Test {
viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr(
std::move(callbacks_info)));
},
base::Unretained(device_.get()), std::move(buffer), buffer_size,
frame_number, callbacks_ptr.PassInterface()));
base::Unretained(device_.get()), std::move(region.region), frame_number,
callbacks_ptr.PassInterface()));
}
// Returns a byte value based on the given |frame_number|.
......@@ -387,13 +388,14 @@ class FrameSinkVideoCaptureDeviceTest : public testing::Test {
// given |frame_number|.
static bool IsExpectedBufferContentForFrame(
int frame_number,
mojo::ScopedSharedBufferHandle buffer) {
const size_t buffer_size =
base::ReadOnlySharedMemoryRegion buffer) {
const auto mapping = buffer.Map();
const size_t frame_allocation_size =
media::VideoFrame::AllocationSize(kFormat, kResolution);
const auto mapping = buffer->Map(buffer_size);
const uint8_t* src = static_cast<uint8_t*>(mapping.get());
CHECK_LE(frame_allocation_size, mapping.size());
const uint8_t* src = mapping.GetMemoryAs<const uint8_t>();
const uint8_t expected_value = GetFrameFillValue(frame_number);
for (size_t i = 0; i < buffer_size; ++i) {
for (size_t i = 0; i < frame_allocation_size; ++i) {
if (src[i] != expected_value) {
return false;
}
......@@ -457,7 +459,7 @@ TEST_F(FrameSinkVideoCaptureDeviceTest, CapturesAndDeliversFrames) {
const int buffer_id = buffer_ids[frame_number - first_frame_number];
auto buffer = receiver->TakeBufferHandle(buffer_id);
ASSERT_TRUE(buffer.is_valid());
ASSERT_TRUE(buffer.IsValid());
EXPECT_TRUE(
IsExpectedBufferContentForFrame(frame_number, std::move(buffer)));
......
......@@ -13,6 +13,7 @@
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "media/base/limits.h"
#include "media/base/video_util.h"
#include "mojo/public/cpp/base/shared_memory_utils.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "ui/gfx/geometry/rect.h"
......@@ -153,18 +154,15 @@ class LameWindowCapturerChromeOS::InFlightFrame
: public viz::mojom::FrameSinkVideoConsumerFrameCallbacks {
public:
InFlightFrame(base::WeakPtr<LameWindowCapturerChromeOS> capturer,
BufferAndSize buffer)
base::MappedReadOnlyRegion buffer)
: capturer_(std::move(capturer)), buffer_(std::move(buffer)) {}
~InFlightFrame() final { Done(); }
mojo::ScopedSharedBufferHandle CloneBufferHandle() {
return buffer_.first->Clone(
mojo::SharedBufferHandle::AccessMode::READ_WRITE);
base::ReadOnlySharedMemoryRegion CloneBufferHandle() {
return buffer_.region.Duplicate();
}
size_t buffer_size() const { return buffer_.second; }
VideoFrame* video_frame() const { return video_frame_.get(); }
void set_video_frame(scoped_refptr<VideoFrame> frame) {
video_frame_ = std::move(frame);
......@@ -183,11 +181,13 @@ class LameWindowCapturerChromeOS::InFlightFrame
}
void Done() final {
video_frame_ = nullptr;
if (auto* capturer = capturer_.get()) {
DCHECK_GT(capturer->in_flight_count_, 0);
--capturer->in_flight_count_;
// If the capture size hasn't changed, return the buffer to the pool.
if (buffer_.second ==
if (buffer_.mapping.size() ==
VideoFrame::AllocationSize(media::PIXEL_FORMAT_I420,
capturer->capture_size_)) {
capturer->buffer_pool_.emplace_back(std::move(buffer_));
......@@ -195,15 +195,14 @@ class LameWindowCapturerChromeOS::InFlightFrame
capturer_ = nullptr;
}
buffer_.first.reset();
buffer_.second = 0;
buffer_ = base::MappedReadOnlyRegion();
}
void ProvideFeedback(double utilization) final {}
private:
base::WeakPtr<LameWindowCapturerChromeOS> capturer_;
BufferAndSize buffer_;
base::MappedReadOnlyRegion buffer_;
scoped_refptr<VideoFrame> video_frame_;
gfx::Rect content_rect_;
LameCaptureOverlayChromeOS::OnceRenderer overlay_renderer_;
......@@ -227,30 +226,22 @@ void LameWindowCapturerChromeOS::CaptureNextFrame() {
// Attempt to re-use a buffer from the pool. Otherwise, create a new one.
const size_t allocation_size =
VideoFrame::AllocationSize(media::PIXEL_FORMAT_I420, capture_size_);
BufferAndSize buffer;
base::MappedReadOnlyRegion buffer;
if (buffer_pool_.empty()) {
buffer.first = mojo::SharedBufferHandle::Create(allocation_size);
if (!buffer.first.is_valid()) {
// If creating the shared memory failed, abort the frame, and hope this
// is a transient problem.
buffer = mojo::CreateReadOnlySharedMemoryRegion(allocation_size);
if (!buffer.IsValid()) {
// If the shared memory region creation failed, just abort this frame,
// hoping the issue is a transient one (e.g., lack of an available region
// in the address space).
return;
}
buffer.second = allocation_size;
} else {
buffer = std::move(buffer_pool_.back());
buffer_pool_.pop_back();
DCHECK(buffer.first.is_valid());
DCHECK_EQ(buffer.second, allocation_size);
}
// Map the shared memory buffer, to populate its data.
mojo::ScopedSharedBufferMapping mapping = buffer.first->Map(buffer.second);
if (!mapping) {
// If the shared memory mapping failed, just abort this frame, hoping the
// issue is a transient one (e.g., lack of an available region in address
// space).
return;
DCHECK(buffer.IsValid());
DCHECK_EQ(buffer.mapping.size(), allocation_size);
}
void* const backing_memory = buffer.mapping.memory();
// At this point, frame capture will proceed. Create an InFlightFrame to track
// population and consumption of the frame, and to eventually return the
......@@ -266,13 +257,10 @@ void LameWindowCapturerChromeOS::CaptureNextFrame() {
}
in_flight_frame->set_video_frame(VideoFrame::WrapExternalData(
media::PIXEL_FORMAT_I420, capture_size_, gfx::Rect(capture_size_),
capture_size_, static_cast<uint8_t*>(mapping.get()), allocation_size,
capture_size_, static_cast<uint8_t*>(backing_memory), allocation_size,
begin_time - first_frame_reference_time_));
auto* const frame = in_flight_frame->video_frame();
DCHECK(frame);
frame->AddDestructionObserver(
base::BindOnce(base::DoNothing::Once<mojo::ScopedSharedBufferMapping>(),
std::move(mapping)));
VideoFrameMetadata* const metadata = frame->metadata();
metadata->SetTimeTicks(VideoFrameMetadata::CAPTURE_BEGIN_TIME, begin_time);
metadata->SetInteger(VideoFrameMetadata::COLOR_SPACE,
......@@ -360,12 +348,11 @@ void LameWindowCapturerChromeOS::DeliverFrame(
base::TimeTicks::Now());
// Clone the buffer handle for the consumer.
mojo::ScopedSharedBufferHandle buffer_for_consumer =
base::ReadOnlySharedMemoryRegion handle =
in_flight_frame->CloneBufferHandle();
if (!buffer_for_consumer.is_valid()) {
if (!handle.IsValid()) {
return; // This should only fail if the OS is exhausted of handles.
}
const size_t buffer_allocation_size = in_flight_frame->buffer_size();
// Assemble frame layout, format, and metadata into a mojo struct to send to
// the consumer.
......@@ -378,10 +365,6 @@ void LameWindowCapturerChromeOS::DeliverFrame(
const gfx::Rect update_rect = frame->visible_rect();
const gfx::Rect content_rect = in_flight_frame->content_rect();
// Drop the VideoFrame wrapper, which will unmap the shared memory from this
// process.
in_flight_frame->set_video_frame(nullptr);
// Create a mojo message pipe and bind to the InFlightFrame to wait for the
// Done() signal from the consumer. The mojo::StrongBinding takes ownership of
// the InFlightFrame.
......@@ -390,9 +373,8 @@ void LameWindowCapturerChromeOS::DeliverFrame(
mojo::MakeRequest(&callbacks));
// Send the frame to the consumer.
consumer_->OnFrameCaptured(std::move(buffer_for_consumer),
buffer_allocation_size, std::move(info),
update_rect, content_rect, std::move(callbacks));
consumer_->OnFrameCaptured(std::move(handle), std::move(info), update_rect,
content_rect, std::move(callbacks));
}
void LameWindowCapturerChromeOS::OnWindowDestroying(aura::Window* window) {
......
......@@ -10,13 +10,13 @@
#include <vector>
#include "base/macros.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/unguessable_token.h"
#include "content/browser/media/capture/lame_capture_overlay_chromeos.h"
#include "media/base/video_frame.h"
#include "mojo/public/cpp/system/buffer.h"
#include "services/viz/privileged/interfaces/compositing/frame_sink_video_capture.mojom.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
......@@ -114,8 +114,7 @@ class LameWindowCapturerChromeOS : public viz::mojom::FrameSinkVideoCapturer,
base::RepeatingTimer timer_;
// A pool of shared memory buffers for re-use.
using BufferAndSize = std::pair<mojo::ScopedSharedBufferHandle, size_t>;
std::vector<BufferAndSize> buffer_pool_;
std::vector<base::MappedReadOnlyRegion> buffer_pool_;
// The current number of frames in-flight. If incrementing this would be
// exceed kMaxInFlightFrames, frame capture is not attempted.
......
......@@ -7,6 +7,7 @@ module viz.mojom;
import "media/capture/mojom/video_capture_types.mojom";
import "media/mojo/interfaces/media_types.mojom";
import "mojo/public/mojom/base/time.mojom";
import "mojo/public/mojom/base/shared_memory.mojom";
import "services/viz/public/interfaces/compositing/frame_sink_id.mojom";
import "skia/public/interfaces/bitmap.mojom";
import "ui/gfx/geometry/mojo/geometry.mojom";
......@@ -31,8 +32,9 @@ interface FrameSinkVideoConsumerFrameCallbacks {
// capture of the source content. An instance that implements this interface is
// provided to FrameSinkVideoCapturer.Start().
interface FrameSinkVideoConsumer {
// Called to deliver each frame to the consumer. |buffer| and |buffer_size|
// are provided to access the video data. |info| is used to interpret the
// Called to deliver each frame to the consumer. |data| contains the video
// frame's image data, and is valid for reading until the consumer notifies
// the service that it is Done(). |info| is used to interpret the
// format/layout of the data, and also contains the frame timestamps and other
// metadata (the following media::VideoFrameMetadata keys are set:
// CAPTURE_BEGIN_TIME, CAPTURE_END_TIME, COLOR_SPACE, FRAME_DURATION,
......@@ -40,8 +42,7 @@ interface FrameSinkVideoConsumer {
// frame that has changed since the last frame. |content_rect| is the region
// of the frame that contains the captured content, with the rest of the frame
// having been letterboxed to adhere to resolution constraints.
OnFrameCaptured(handle<shared_buffer> buffer,
uint32 buffer_size,
OnFrameCaptured(mojo_base.mojom.ReadOnlySharedMemoryRegion data,
media.mojom.VideoFrameInfo info,
gfx.mojom.Rect update_rect,
gfx.mojom.Rect content_rect,
......
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