0

media: Spanify MediaFoundationVideoEncodeAccelerator

Bug: 409619251, 40285824, 338570700
Change-Id: Icbe9b7deb2d485f11327d1e233b5629480b40aad
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6448912
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: Mustafa Emre Acer <meacer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1445651}
This commit is contained in:
Eugene Zemtsov
2025-04-10 19:59:52 -07:00
committed by Chromium LUCI CQ
parent 1a60a7abe1
commit b094accf61
9 changed files with 124 additions and 92 deletions

@ -81,9 +81,6 @@ bool CopyDXGIBufferToShMem(
Microsoft::WRL::ComPtr<ID3D11Texture2D>* staging_texture) {
DCHECK(d3d11_device);
uint8_t* dest_buffer = shared_memory.data();
size_t dst_buffer_size = shared_memory.size_bytes();
Microsoft::WRL::ComPtr<ID3D11Device1> device1;
HRESULT hr = d3d11_device->QueryInterface(IID_PPV_ARGS(&device1));
if (FAILED(hr)) {
@ -100,19 +97,18 @@ bool CopyDXGIBufferToShMem(
return false;
}
return CopyD3D11TexToMem(texture.Get(), dest_buffer, dst_buffer_size,
d3d11_device, staging_texture);
return CopyD3D11TexToMem(texture.Get(), shared_memory, d3d11_device,
staging_texture);
}
bool CopyD3D11TexToMem(
ID3D11Texture2D* src_texture,
uint8_t* dst_buffer,
size_t buffer_size,
base::span<uint8_t> dst_buffer,
ID3D11Device* d3d11_device,
Microsoft::WRL::ComPtr<ID3D11Texture2D>* staging_texture) {
DCHECK(d3d11_device);
DCHECK(staging_texture);
DCHECK(dst_buffer);
DCHECK(!dst_buffer.empty());
DCHECK(src_texture);
D3D11_TEXTURE2D_DESC texture_desc = {};
@ -124,7 +120,7 @@ bool CopyD3D11TexToMem(
return false;
}
size_t copy_size = texture_desc.Height * texture_desc.Width * 3 / 2;
if (buffer_size < copy_size) {
if (dst_buffer.size() < copy_size) {
DLOG(ERROR) << "Invalid buffer size for copy.";
return false;
}
@ -199,12 +195,12 @@ bool CopyD3D11TexToMem(
const uint32_t source_stride = mapped_resource.RowPitch;
const uint32_t dest_stride = texture_desc.Width;
return libyuv::NV12Copy(source_buffer, source_stride,
source_buffer + texture_desc.Height * source_stride,
source_stride, dst_buffer, dest_stride,
dst_buffer + texture_desc.Height * dest_stride,
dest_stride, texture_desc.Width,
texture_desc.Height) == 0;
return libyuv::NV12Copy(
source_buffer, source_stride,
source_buffer + texture_desc.Height * source_stride, source_stride,
dst_buffer.data(), dest_stride,
dst_buffer.subspan(texture_desc.Height * dest_stride).data(),
dest_stride, texture_desc.Width, texture_desc.Height) == 0;
}
GPU_EXPORT bool CopyShMemToDXGIBuffer(base::span<uint8_t> shared_memory,

@ -70,8 +70,7 @@ GPU_EXPORT bool CopyDXGIBufferToShMem(
// input texture size or format. Returns true if succeeded.
GPU_EXPORT bool CopyD3D11TexToMem(
ID3D11Texture2D* input_texture,
uint8_t* dst_buffer,
size_t buffer_size,
base::span<uint8_t> dst_buffer,
ID3D11Device* d3d11_device,
Microsoft::WRL::ComPtr<ID3D11Texture2D>* staging_texture);

@ -785,6 +785,8 @@ scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvData(
return frame;
}
// TODO(crbug.com/338570700): This method needs to be remove in favour
// of its span version.
// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvData(
VideoPixelFormat format,
@ -796,6 +798,31 @@ scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvData(
const uint8_t* y_data,
const uint8_t* uv_data,
base::TimeDelta timestamp) {
auto layout = VideoFrameLayout::CreateWithStrides(format, coded_size,
{y_stride, uv_stride});
if (!layout) {
DLOG(ERROR) << "Invalid layout.";
return nullptr;
}
return WrapExternalYuvData(
format, coded_size, visible_rect, natural_size, y_stride, uv_stride,
UNSAFE_TODO(base::span(y_data, layout->planes()[Plane::kY].size)),
UNSAFE_TODO(base::span(uv_data, layout->planes()[Plane::kUV].size)),
timestamp);
}
// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvData(
VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
size_t y_stride,
size_t uv_stride,
base::span<const uint8_t> y_data,
base::span<const uint8_t> uv_data,
base::TimeDelta timestamp) {
const StorageType storage = STORAGE_UNOWNED_MEMORY;
if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) {
DLOG(ERROR) << __func__ << " Invalid config."
@ -819,11 +846,9 @@ scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvData(
auto frame = base::MakeRefCounted<VideoFrame>(base::PassKey<VideoFrame>(),
*layout, storage, visible_rect,
natural_size, timestamp);
std::array<const uint8_t*, 2> data = {y_data, uv_data};
std::array<base::span<const uint8_t>, 2> data = {y_data, uv_data};
for (size_t plane = 0; plane < NumPlanes(format); ++plane) {
// TODO(crbug.com/338570700): y_data, uv_data should be spans
frame->data_[plane] =
UNSAFE_TODO(base::span(data[plane], layout->planes()[plane].size));
frame->data_[plane] = data[plane];
}
return frame;
}

@ -397,6 +397,17 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> {
const uint8_t* uv_data,
base::TimeDelta timestamp);
static scoped_refptr<VideoFrame> WrapExternalYuvData(
VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
size_t y_stride,
size_t uv_stride,
base::span<const uint8_t> y_data,
base::span<const uint8_t> uv_data,
base::TimeDelta timestamp);
// Wraps |gpu_memory_buffer|. This will transfer ownership of
// |gpu_memory_buffer| to the returned VideoFrame.
// For use in contexts where the GPUMemoryBuffer has no SharedImage

@ -271,16 +271,20 @@ Microsoft::WRL::ComPtr<IMFSample> CreateEmptySampleWithBuffer(
}
MediaBufferScopedPointer::MediaBufferScopedPointer(IMFMediaBuffer* media_buffer)
: media_buffer_(media_buffer),
buffer_(nullptr),
max_length_(0),
current_length_(0) {
HRESULT hr = media_buffer_->Lock(&buffer_.AsEphemeralRawAddr(), &max_length_,
&current_length_);
: media_buffer_(media_buffer) {
uint8_t* buffer;
DWORD max_length;
HRESULT hr = media_buffer_->Lock(&buffer, &max_length, nullptr);
CHECK(SUCCEEDED(hr));
// SAFETY: `IMFMediaBuffer::Lock` docs states that `max_length` is the maximum
// amount of data that can be written to the buffer.
data_ = UNSAFE_BUFFERS(base::raw_span<uint8_t>(buffer, max_length));
}
MediaBufferScopedPointer::~MediaBufferScopedPointer() {
data_ = {};
HRESULT hr = media_buffer_->Unlock();
CHECK(SUCCEEDED(hr));
}
@ -898,9 +902,9 @@ HRESULT GenerateSampleFromVideoFrame(
hr, "Failed to create memory buffer for input sample", hr);
MediaBufferScopedPointer scoped_buffer(input_buffer.Get());
bool copy_succeeded = gpu::CopyD3D11TexToMem(
input_texture.Get(), scoped_buffer.get(), scoped_buffer.max_length(),
d3d_device.Get(), staging_texture);
bool copy_succeeded =
gpu::CopyD3D11TexToMem(input_texture.Get(), scoped_buffer.as_span(),
d3d_device.Get(), staging_texture);
if (!copy_succeeded) {
LOG(ERROR) << "Failed to copy sample to memory.";
return E_FAIL;
@ -930,7 +934,7 @@ HRESULT GenerateSampleFromVideoFrame(
frame->format(), i, frame->visible_rect().size());
libyuv::CopyPlane(frame->visible_data(i),
frame->layout().planes()[i].stride,
scoped_buffer.get() + buffer_offset,
scoped_buffer.as_span().subspan(buffer_offset).data(),
frame->layout().planes()[i].stride, plane_size.width(),
plane_size.height());
buffer_offset +=

@ -83,15 +83,11 @@ class MEDIA_EXPORT MediaBufferScopedPointer {
~MediaBufferScopedPointer();
raw_ptr<uint8_t, AllowPtrArithmetic> get() { return buffer_; }
DWORD current_length() const { return current_length_; }
DWORD max_length() const { return max_length_; }
base::span<uint8_t> as_span() { return data_; }
private:
Microsoft::WRL::ComPtr<IMFMediaBuffer> media_buffer_;
raw_ptr<uint8_t, AllowPtrArithmetic> buffer_;
DWORD max_length_;
DWORD current_length_;
base::raw_span<uint8_t> data_;
};
// Copies |in_string| to |out_string| that is allocated with CoTaskMemAlloc().

@ -2,11 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "media/gpu/windows/media_foundation_video_encode_accelerator_win.h"
#include <objbase.h>
@ -24,6 +19,7 @@
#include <vector>
#include "base/containers/fixed_flat_set.h"
#include "base/containers/heap_array.h"
#include "base/features.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
@ -220,18 +216,17 @@ struct MediaFoundationVideoEncodeAccelerator::PendingInput {
class MediaFoundationVideoEncodeAccelerator::EncodeOutput {
public:
EncodeOutput(uint32_t size, const BitstreamBufferMetadata& md)
: metadata(md), data_(size) {}
: metadata(md), data_(base::HeapArray<uint8_t>::Uninit(size)) {}
EncodeOutput(const EncodeOutput&) = delete;
EncodeOutput& operator=(const EncodeOutput&) = delete;
uint8_t* memory() { return data_.data(); }
int size() const { return static_cast<int>(data_.size()); }
base::span<uint8_t> as_span() { return data_.as_span(); }
BitstreamBufferMetadata metadata;
private:
std::vector<uint8_t> data_;
base::HeapArray<uint8_t> data_;
};
struct MediaFoundationVideoEncodeAccelerator::BitstreamBufferRef {
@ -794,8 +789,16 @@ void MediaFoundationVideoEncodeAccelerator::UseOutputBitstreamBuffer(
}
auto encode_output = std::move(encoder_output_queue_.front());
encoder_output_queue_.pop_front();
memcpy(buffer_ref->mapping.memory(), encode_output->memory(),
encode_output->size());
if (buffer.size() < encode_output->as_span().size()) {
NotifyErrorStatus(
{EncoderStatus::Codes::kInvalidOutputBuffer,
"Encoder output is too large: " + base::NumberToString(buffer.size()) +
" vs. " + base::NumberToString(encode_output->as_span().size())});
return;
}
buffer_ref->mapping.GetMemoryAsSpan<uint8_t>().copy_prefix_from(
encode_output->as_span());
client_->BitstreamBufferReady(buffer_ref->id, encode_output->metadata);
if (encoder_output_queue_.empty() && state_ == kPostFlushing) {
@ -1912,22 +1915,20 @@ HRESULT MediaFoundationVideoEncodeAccelerator::PopulateInputSampleBuffer(
// Establish plain pointers into the input buffer, where we will copy pixel
// data to.
MediaBufferScopedPointer scoped_buffer(input_buffer.Get());
DCHECK(scoped_buffer.get());
uint8_t* dst_y = scoped_buffer.get();
size_t dst_y_stride = VideoFrame::RowBytes(
VideoFrame::Plane::kY, kTargetPixelFormat, input_visible_size_.width());
uint8_t* dst_uv =
scoped_buffer.get() +
size_t dst_y_size =
dst_y_stride * VideoFrame::Rows(VideoFrame::Plane::kY, kTargetPixelFormat,
input_visible_size_.height());
auto dst_y = scoped_buffer.as_span().first(dst_y_size);
size_t dst_uv_stride = VideoFrame::RowBytes(
VideoFrame::Plane::kUV, kTargetPixelFormat, input_visible_size_.width());
uint8_t* end =
dst_uv + dst_uv_stride * VideoFrame::Rows(VideoFrame::Plane::kUV,
kTargetPixelFormat,
input_visible_size_.height());
DCHECK_GE(static_cast<ptrdiff_t>(scoped_buffer.max_length()),
end - scoped_buffer.get());
size_t dst_uv_size =
dst_uv_stride * VideoFrame::Rows(VideoFrame::Plane::kUV,
kTargetPixelFormat,
input_visible_size_.height());
auto dst_uv = scoped_buffer.as_span().subspan(dst_y_size, dst_uv_size);
// Set up a VideoFrame with the data pointing into the input buffer.
// We need it to ease copying and scaling by reusing ConvertAndScale()
@ -1966,7 +1967,7 @@ HRESULT MediaFoundationVideoEncodeAccelerator::PopulateInputSampleBuffer(
.CPUAccessFlags = 0,
.MiscFlags = 0};
D3D11_SUBRESOURCE_DATA init_data = {
.pSysMem = scoped_buffer.get(),
.pSysMem = scoped_buffer.as_span().data(),
.SysMemPitch = static_cast<UINT>(dst_y_stride),
.SysMemSlicePitch = 0};
ComD3D11Texture2D input_texture;
@ -2075,9 +2076,9 @@ HRESULT MediaFoundationVideoEncodeAccelerator::CopyInputSampleBufferFromGpu(
hr);
MediaBufferScopedPointer scoped_buffer(input_buffer.Get());
bool copy_succeeded = gpu::CopyD3D11TexToMem(
sample_texture.Get(), scoped_buffer.get(), scoped_buffer.max_length(),
d3d_device.Get(), &staging_texture_);
bool copy_succeeded =
gpu::CopyD3D11TexToMem(sample_texture.Get(), scoped_buffer.as_span(),
d3d_device.Get(), &staging_texture_);
if (!copy_succeeded) {
LOG(ERROR) << "Failed to copy sample to memory.";
return E_FAIL;
@ -2302,12 +2303,14 @@ void MediaFoundationVideoEncodeAccelerator::ProcessOutput() {
const bool keyframe = MFGetAttributeUINT32(
output_data_buffer.pSample, MFSampleExtension_CleanPoint, false);
DWORD size = 0;
hr = output_buffer->GetCurrentLength(&size);
DWORD output_buffer_size = 0;
hr = output_buffer->GetCurrentLength(&output_buffer_size);
RETURN_ON_HR_FAILURE(hr, "Couldn't get buffer length", );
DCHECK_NE(size, 0u);
DCHECK_NE(output_buffer_size, 0u);
MediaBufferScopedPointer scoped_buffer(output_buffer.Get());
auto output_buffer_span = scoped_buffer.as_span().first(output_buffer_size);
BitstreamBufferMetadata md(size, keyframe, timestamp);
BitstreamBufferMetadata md(output_buffer_span.size(), keyframe, timestamp);
if (frame_qp.has_value() && IsValidQp(codec_, *frame_qp)) {
md.qp = *frame_qp;
}
@ -2319,9 +2322,8 @@ void MediaFoundationVideoEncodeAccelerator::ProcessOutput() {
if (IsTemporalScalabilityCoding()) {
DCHECK(svc_parser_);
TemporalScalabilityIdExtractor::BitstreamMetadata bits_md;
MediaBufferScopedPointer scoped_buffer(output_buffer.Get());
if (!svc_parser_->ParseChunk(base::span(scoped_buffer.get().get(), size),
metadata.frame_id, bits_md)) {
if (!svc_parser_->ParseChunk(output_buffer_span, metadata.frame_id,
bits_md)) {
NotifyErrorStatus({EncoderStatus::Codes::kEncoderHardwareDriverError,
"Parse bitstream failed"});
return;
@ -2409,19 +2411,18 @@ void MediaFoundationVideoEncodeAccelerator::ProcessOutput() {
frame_params.temporal_layer_id = temporal_id;
frame_params.timestamp = timestamp.InMilliseconds();
// Notify SW BRC about recent encoded frame size.
rate_ctrl_->PostEncodeUpdate(size, frame_params);
rate_ctrl_->PostEncodeUpdate(output_buffer_span.size(), frame_params);
}
DVLOG(3) << "Encoded data with size:" << size << " keyframe " << keyframe;
DVLOG(3) << "Encoded data with size:" << output_buffer_span.size()
<< " keyframe " << keyframe;
// If no bit stream buffer presents, queue the output first.
if (bitstream_buffer_queue_.empty()) {
DVLOG(3) << "No bitstream buffers.";
// We need to copy the output so that encoding can continue.
auto encode_output = std::make_unique<EncodeOutput>(size, md);
{
MediaBufferScopedPointer scoped_buffer(output_buffer.Get());
memcpy(encode_output->memory(), scoped_buffer.get(), size);
}
auto encode_output =
std::make_unique<EncodeOutput>(output_buffer_span.size(), md);
encode_output->as_span().copy_from(output_buffer_span);
encoder_output_queue_.push_back(std::move(encode_output));
return;
}
@ -2436,16 +2437,15 @@ void MediaFoundationVideoEncodeAccelerator::ProcessOutput() {
auto buffer_ref = std::move(bitstream_buffer_queue_.back());
bitstream_buffer_queue_.pop_back();
{
MediaBufferScopedPointer scoped_buffer(output_buffer.Get());
if (!buffer_ref->mapping.IsValid() || !scoped_buffer.get()) {
DLOG(ERROR) << "Failed to copy bitstream media buffer.";
return;
}
memcpy(buffer_ref->mapping.memory(), scoped_buffer.get(), size);
if (!buffer_ref->mapping.IsValid() ||
buffer_ref->mapping.size() < output_buffer_span.size()) {
NotifyErrorStatus({EncoderStatus::Codes::kInvalidOutputBuffer,
"Failed to copy bitstream media buffer."});
return;
}
buffer_ref->mapping.GetMemoryAsSpan<uint8_t>().copy_prefix_from(
output_buffer_span);
client_->BitstreamBufferReady(buffer_ref->id, md);
}

@ -171,8 +171,8 @@ class MFVideoProcessorAcceleratorTest : public ::testing::Test {
template <typename F>
void ValidateResult(IMFMediaBuffer* buffer, UINT size, F validation_func) {
MediaBufferScopedPointer scoped_buffer(buffer);
ASSERT_EQ(scoped_buffer.current_length(), size);
validation_func(scoped_buffer.get());
ASSERT_EQ(scoped_buffer.as_span().size(), size);
validation_func(scoped_buffer.as_span().data());
}
scoped_refptr<DXGIDeviceManager> dxgi_device_man_;

@ -5,6 +5,7 @@
#ifndef MEDIA_GPU_WINDOWS_VIDEO_RATE_CONTROL_WRAPPER_H_
#define MEDIA_GPU_WINDOWS_VIDEO_RATE_CONTROL_WRAPPER_H_
#include <array>
#include <cstdint>
#include <memory>
@ -46,19 +47,19 @@ class VideoRateControlWrapper {
VideoEncodeAccelerator::Config::ContentType content_type =
VideoEncodeAccelerator::Config::ContentType::kCamera;
// Target bitrate for svc layers.
int layer_target_bitrate[kMaxLayers] = {};
std::array<int, kMaxLayers> layer_target_bitrate = {};
// Rate decimator for temporal layers.
int ts_rate_decimator[kMaxTemporalLayers] = {};
std::array<int, kMaxTemporalLayers> ts_rate_decimator = {};
// Number of spatial layers.
int ss_number_layers = 0;
// Number of temporal layers.
int ts_number_layers = 0;
// Quantizer parameter for svc layers.
int max_quantizers[kMaxLayers] = {};
int min_quantizers[kMaxLayers] = {};
std::array<int, kMaxLayers> max_quantizers = {};
std::array<int, kMaxLayers> min_quantizers = {};
// Scaling factor parameters for spatial layers.
int scaling_factor_num[kMaxSpatialLayers] = {};
int scaling_factor_den[kMaxSpatialLayers] = {};
std::array<int, kMaxSpatialLayers> scaling_factor_num = {};
std::array<int, kMaxSpatialLayers> scaling_factor_den = {};
// If defined, the H.264 BRC uses fixed QP difference between layers. Should
// not be defined for other SW BRCs.
std::optional<int> fixed_delta_qp;