blob: 2e50a84704135f435f3a3c45dd46b6c539149a7a [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/base/video_frame.h"
#include "components/viz/test/test_context_provider.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame_init.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/modules/webcodecs/video_frame.h"
#include "third_party/blink/renderer/modules/webcodecs/video_frame_handle.h"
#include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
#include "third_party/blink/renderer/platform/graphics/test/gpu_test_utils.h"
#include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/heap/thread_state.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace blink {
namespace {
ImageBitmap* ToImageBitmap(V8TestingScope* v8_scope, ScriptValue value) {
return NativeValueTraits<ImageBitmap>::NativeValue(
v8_scope->GetIsolate(), value.V8Value(), v8_scope->GetExceptionState());
}
class VideoFrameTest : public testing::Test {
public:
void SetUp() override {
test_context_provider_ = viz::TestContextProvider::Create();
InitializeSharedGpuContext(test_context_provider_.get());
}
void TearDown() override { SharedGpuContext::ResetForTesting(); }
VideoFrame* CreateBlinkVideoFrame(
scoped_refptr<media::VideoFrame> media_frame,
ExecutionContext* context) {
return MakeGarbageCollected<VideoFrame>(std::move(media_frame), context);
}
VideoFrame* CreateBlinkVideoFrameFromHandle(
scoped_refptr<VideoFrameHandle> handle) {
return MakeGarbageCollected<VideoFrame>(std::move(handle));
}
scoped_refptr<media::VideoFrame> CreateDefaultBlackMediaVideoFrame() {
return CreateBlackMediaVideoFrame(base::TimeDelta::FromMicroseconds(1000),
media::PIXEL_FORMAT_I420,
gfx::Size(112, 208) /* coded_size */,
gfx::Size(100, 200) /* visible_size */);
}
scoped_refptr<media::VideoFrame> CreateBlackMediaVideoFrame(
base::TimeDelta timestamp,
media::VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Size& visible_size) {
scoped_refptr<media::VideoFrame> media_frame =
media::VideoFrame::WrapVideoFrame(
media::VideoFrame::CreateBlackFrame(coded_size), format,
gfx::Rect(visible_size) /* visible_rect */,
visible_size /* natural_size */);
media_frame->set_timestamp(timestamp);
return media_frame;
}
private:
scoped_refptr<viz::TestContextProvider> test_context_provider_;
};
TEST_F(VideoFrameTest, ConstructorAndAttributes) {
V8TestingScope scope;
scoped_refptr<media::VideoFrame> media_frame = CreateBlackMediaVideoFrame(
base::TimeDelta::FromMicroseconds(1000), media::PIXEL_FORMAT_I420,
gfx::Size(112, 208) /* coded_size */,
gfx::Size(100, 200) /* visible_size */);
VideoFrame* blink_frame =
CreateBlinkVideoFrame(media_frame, scope.GetExecutionContext());
EXPECT_EQ(1000u, blink_frame->timestamp().value());
EXPECT_EQ(112u, blink_frame->codedWidth());
EXPECT_EQ(208u, blink_frame->codedHeight());
EXPECT_EQ(100u, blink_frame->cropWidth());
EXPECT_EQ(200u, blink_frame->cropHeight());
EXPECT_EQ(media_frame, blink_frame->frame());
blink_frame->close();
EXPECT_FALSE(blink_frame->timestamp().has_value());
EXPECT_EQ(0u, blink_frame->codedWidth());
EXPECT_EQ(0u, blink_frame->codedHeight());
EXPECT_EQ(0u, blink_frame->cropWidth());
EXPECT_EQ(0u, blink_frame->cropHeight());
EXPECT_EQ(nullptr, blink_frame->frame());
}
TEST_F(VideoFrameTest, FramesSharingHandleClose) {
V8TestingScope scope;
scoped_refptr<media::VideoFrame> media_frame =
CreateDefaultBlackMediaVideoFrame();
VideoFrame* blink_frame =
CreateBlinkVideoFrame(media_frame, scope.GetExecutionContext());
VideoFrame* frame_with_shared_handle =
CreateBlinkVideoFrameFromHandle(blink_frame->handle());
// A blink::VideoFrame created from a handle should share the same
// media::VideoFrame reference.
EXPECT_EQ(media_frame, frame_with_shared_handle->frame());
// Closing a frame should invalidate all frames sharing the same handle.
blink_frame->close();
EXPECT_EQ(nullptr, frame_with_shared_handle->frame());
}
TEST_F(VideoFrameTest, FramesNotSharingHandleClose) {
V8TestingScope scope;
scoped_refptr<media::VideoFrame> media_frame =
CreateDefaultBlackMediaVideoFrame();
VideoFrame* blink_frame =
CreateBlinkVideoFrame(media_frame, scope.GetExecutionContext());
auto new_handle = base::MakeRefCounted<VideoFrameHandle>(
blink_frame->frame(), scope.GetExecutionContext());
VideoFrame* frame_with_new_handle =
CreateBlinkVideoFrameFromHandle(std::move(new_handle));
EXPECT_EQ(media_frame, frame_with_new_handle->frame());
// If a frame was created a new handle reference the same media::VideoFrame,
// one frame's closure should not affect the other.
blink_frame->close();
EXPECT_EQ(media_frame, frame_with_new_handle->frame());
}
TEST_F(VideoFrameTest, ClonedFrame) {
V8TestingScope scope;
scoped_refptr<media::VideoFrame> media_frame =
CreateDefaultBlackMediaVideoFrame();
VideoFrame* blink_frame =
CreateBlinkVideoFrame(media_frame, scope.GetExecutionContext());
VideoFrame* cloned_frame =
blink_frame->clone(scope.GetScriptState(), scope.GetExceptionState());
// The cloned frame should be referencing the same media::VideoFrame.
EXPECT_EQ(blink_frame->frame(), cloned_frame->frame());
EXPECT_EQ(media_frame, cloned_frame->frame());
EXPECT_FALSE(scope.GetExceptionState().HadException());
blink_frame->close();
// Closing the original frame should not affect the cloned frame.
EXPECT_EQ(media_frame, cloned_frame->frame());
}
TEST_F(VideoFrameTest, CloningClosedFrame) {
V8TestingScope scope;
scoped_refptr<media::VideoFrame> media_frame =
CreateDefaultBlackMediaVideoFrame();
VideoFrame* blink_frame =
CreateBlinkVideoFrame(media_frame, scope.GetExecutionContext());
blink_frame->close();
VideoFrame* cloned_frame =
blink_frame->clone(scope.GetScriptState(), scope.GetExceptionState());
// No frame should have been created, and there should be an exception.
EXPECT_EQ(nullptr, cloned_frame);
EXPECT_TRUE(scope.GetExceptionState().HadException());
}
TEST_F(VideoFrameTest, LeakedHandlesReportLeaks) {
V8TestingScope scope;
// Create a handle directly instead of a video frame, to avoid dealing with
// the GarbageCollector.
scoped_refptr<media::VideoFrame> media_frame =
CreateDefaultBlackMediaVideoFrame();
auto handle = base::MakeRefCounted<VideoFrameHandle>(
media_frame, scope.GetExecutionContext());
// Remove the last reference to the handle without calling Invalidate().
handle.reset();
auto& logger = VideoFrameLogger::From(*scope.GetExecutionContext());
EXPECT_TRUE(logger.GetCloseAuditor()->were_frames_not_closed());
}
TEST_F(VideoFrameTest, InvalidatedHandlesDontReportLeaks) {
V8TestingScope scope;
// Create a handle directly instead of a video frame, to avoid dealing with
// the GarbageCollector.
scoped_refptr<media::VideoFrame> media_frame =
CreateDefaultBlackMediaVideoFrame();
auto handle = base::MakeRefCounted<VideoFrameHandle>(
media_frame, scope.GetExecutionContext());
handle->Invalidate();
handle.reset();
auto& logger = VideoFrameLogger::From(*scope.GetExecutionContext());
EXPECT_FALSE(logger.GetCloseAuditor()->were_frames_not_closed());
}
TEST_F(VideoFrameTest, ImageBitmapCreationAndZeroCopyRoundTrip) {
V8TestingScope scope;
auto* init = VideoFrameInit::Create();
init->setTimestamp(0);
sk_sp<SkSurface> surface(SkSurface::MakeRaster(
SkImageInfo::MakeN32Premul(5, 5, SkColorSpace::MakeSRGB())));
sk_sp<SkImage> original_image = surface->makeImageSnapshot();
const auto* default_options = ImageBitmapOptions::Create();
auto* image_bitmap = MakeGarbageCollected<ImageBitmap>(
UnacceleratedStaticBitmapImage::Create(original_image), base::nullopt,
default_options);
CanvasImageSourceUnion source;
source.SetImageBitmap(image_bitmap);
auto* video_frame = VideoFrame::Create(scope.GetScriptState(), source, init,
scope.GetExceptionState());
EXPECT_EQ(video_frame->handle()->sk_image(), original_image);
{
auto promise = video_frame->createImageBitmap(
scope.GetScriptState(), default_options, scope.GetExceptionState());
ScriptPromiseTester tester(scope.GetScriptState(), promise);
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
auto* new_bitmap = ToImageBitmap(&scope, tester.Value());
ASSERT_TRUE(new_bitmap);
auto bitmap_image =
new_bitmap->BitmapImage()->PaintImageForCurrentFrame().GetSwSkImage();
EXPECT_EQ(bitmap_image, original_image);
}
auto* clone =
video_frame->clone(scope.GetScriptState(), scope.GetExceptionState());
EXPECT_EQ(clone->handle()->sk_image(), original_image);
}
TEST_F(VideoFrameTest, VideoFrameFromGPUImageBitmap) {
V8TestingScope scope;
auto context_provider_wrapper = SharedGpuContext::ContextProviderWrapper();
CanvasResourceParams resource_params;
auto resource_provider = CanvasResourceProvider::CreateSharedImageProvider(
IntSize(100, 100), kLow_SkFilterQuality, resource_params,
CanvasResourceProvider::ShouldInitialize::kNo, context_provider_wrapper,
RasterMode::kGPU, true /*is_origin_top_left*/,
0u /*shared_image_usage_flags*/);
scoped_refptr<StaticBitmapImage> bitmap = resource_provider->Snapshot();
ASSERT_TRUE(bitmap->IsTextureBacked());
auto* image_bitmap = MakeGarbageCollected<ImageBitmap>(bitmap);
EXPECT_TRUE(image_bitmap);
EXPECT_TRUE(image_bitmap->BitmapImage()->IsTextureBacked());
auto* init = VideoFrameInit::Create();
init->setTimestamp(0);
CanvasImageSourceUnion source;
source.SetImageBitmap(image_bitmap);
auto* video_frame = VideoFrame::Create(scope.GetScriptState(), source, init,
scope.GetExceptionState());
ASSERT_TRUE(video_frame);
}
} // namespace
} // namespace blink