blob: 1122f447615a085ff87cdb4ab1cb547b92102058 [file] [log] [blame]
// Copyright (c) 2012 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/mac/video_frame_mac.h"
#include <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "media/base/video_frame.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace {
const int kWidth = 64;
const int kHeight = 48;
const int kVisibleRectOffset = 8;
const base::TimeDelta kTimestamp = base::Microseconds(1337);
struct FormatPair {
VideoPixelFormat chrome;
OSType corevideo;
};
void Increment(int* i) {
++(*i);
}
} // namespace
TEST(VideoFrameMac, CheckBasicAttributes) {
gfx::Size size(kWidth, kHeight);
auto frame = VideoFrame::CreateFrame(PIXEL_FORMAT_I420, size, gfx::Rect(size),
size, kTimestamp);
ASSERT_TRUE(frame.get());
auto pb = WrapVideoFrameInCVPixelBuffer(*frame);
ASSERT_TRUE(pb.get());
const gfx::Size coded_size = frame->coded_size();
const VideoPixelFormat format = frame->format();
EXPECT_EQ(coded_size.width(), static_cast<int>(CVPixelBufferGetWidth(pb)));
EXPECT_EQ(coded_size.height(), static_cast<int>(CVPixelBufferGetHeight(pb)));
EXPECT_EQ(VideoFrame::NumPlanes(format), CVPixelBufferGetPlaneCount(pb));
CVPixelBufferLockBaseAddress(pb, 0);
for (size_t i = 0; i < VideoFrame::NumPlanes(format); ++i) {
const gfx::Size plane_size = VideoFrame::PlaneSize(format, i, coded_size);
EXPECT_EQ(plane_size.width(),
static_cast<int>(CVPixelBufferGetWidthOfPlane(pb, i)));
EXPECT_EQ(plane_size.height(),
static_cast<int>(CVPixelBufferGetHeightOfPlane(pb, i)));
EXPECT_EQ(frame->data(i), CVPixelBufferGetBaseAddressOfPlane(pb, i));
}
CVPixelBufferUnlockBaseAddress(pb, 0);
}
TEST(VideoFrameMac, CheckFormats) {
// CreateFrame() does not support non planar YUV, e.g. NV12.
const FormatPair format_pairs[] = {
{PIXEL_FORMAT_I420, kCVPixelFormatType_420YpCbCr8Planar},
{PIXEL_FORMAT_YV12, 0},
{PIXEL_FORMAT_I422, 0},
{PIXEL_FORMAT_I420A, 0},
{PIXEL_FORMAT_I444, 0},
};
gfx::Size size(kWidth, kHeight);
for (const auto& format_pair : format_pairs) {
auto frame = VideoFrame::CreateFrame(format_pair.chrome, size,
gfx::Rect(size), size, kTimestamp);
ASSERT_TRUE(frame.get());
auto pb = WrapVideoFrameInCVPixelBuffer(*frame);
if (format_pair.corevideo) {
EXPECT_EQ(format_pair.corevideo, CVPixelBufferGetPixelFormatType(pb));
} else {
EXPECT_EQ(nullptr, pb.get());
}
}
}
TEST(VideoFrameMac, CheckLifetime) {
gfx::Size size(kWidth, kHeight);
auto frame = VideoFrame::CreateFrame(PIXEL_FORMAT_I420, size, gfx::Rect(size),
size, kTimestamp);
ASSERT_TRUE(frame.get());
int instances_destroyed = 0;
auto wrapper_frame = VideoFrame::WrapVideoFrame(
frame, frame->format(), frame->visible_rect(), frame->natural_size());
wrapper_frame->AddDestructionObserver(
base::BindOnce(&Increment, &instances_destroyed));
ASSERT_TRUE(wrapper_frame.get());
auto pb = WrapVideoFrameInCVPixelBuffer(*wrapper_frame);
ASSERT_TRUE(pb.get());
wrapper_frame = nullptr;
EXPECT_EQ(0, instances_destroyed);
pb.reset();
EXPECT_EQ(1, instances_destroyed);
}
TEST(VideoFrameMac, CheckWrapperFrame) {
const FormatPair format_pairs[] = {
{PIXEL_FORMAT_I420, kCVPixelFormatType_420YpCbCr8Planar},
{PIXEL_FORMAT_NV12, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange},
};
for (const auto& format_pair : format_pairs) {
base::ScopedCFTypeRef<CVPixelBufferRef> pb;
CVPixelBufferCreate(nullptr, kWidth, kHeight, format_pair.corevideo,
nullptr, pb.InitializeInto());
ASSERT_TRUE(pb.get());
auto frame = VideoFrame::WrapCVPixelBuffer(pb.get(), kTimestamp);
ASSERT_TRUE(frame.get());
EXPECT_EQ(pb.get(), frame->CvPixelBuffer());
EXPECT_EQ(format_pair.chrome, frame->format());
frame = nullptr;
EXPECT_EQ(1, CFGetRetainCount(pb.get()));
}
}
static void FillFrameWithPredictableValues(const VideoFrame& frame) {
for (size_t i = 0; i < VideoFrame::NumPlanes(frame.format()); ++i) {
const gfx::Size& size =
VideoFrame::PlaneSize(frame.format(), i, frame.coded_size());
uint8_t* plane_ptr = const_cast<uint8_t*>(frame.data(i));
for (int h = 0; h < size.height(); ++h) {
const int row_index = h * frame.stride(i);
for (int w = 0; w < size.width(); ++w) {
const int index = row_index + w;
plane_ptr[index] = static_cast<uint8_t>(w ^ h);
}
}
}
}
TEST(VideoFrameMac, CorrectlyWrapsFramesWithPadding) {
const gfx::Size coded_size(kWidth, kHeight);
const gfx::Rect visible_rect(kVisibleRectOffset, kVisibleRectOffset,
kWidth - 2 * kVisibleRectOffset,
kHeight - 2 * kVisibleRectOffset);
auto frame =
VideoFrame::CreateFrame(PIXEL_FORMAT_I420, coded_size, visible_rect,
visible_rect.size(), kTimestamp);
ASSERT_TRUE(frame.get());
FillFrameWithPredictableValues(*frame);
auto pb = WrapVideoFrameInCVPixelBuffer(*frame);
ASSERT_TRUE(pb.get());
EXPECT_EQ(kCVPixelFormatType_420YpCbCr8Planar,
CVPixelBufferGetPixelFormatType(pb));
EXPECT_EQ(visible_rect.width(), static_cast<int>(CVPixelBufferGetWidth(pb)));
EXPECT_EQ(visible_rect.height(),
static_cast<int>(CVPixelBufferGetHeight(pb)));
CVPixelBufferLockBaseAddress(pb, 0);
for (size_t i = 0; i < VideoFrame::NumPlanes(frame->format()); ++i) {
const gfx::Size plane_size =
VideoFrame::PlaneSize(frame->format(), i, visible_rect.size());
EXPECT_EQ(plane_size.width(),
static_cast<int>(CVPixelBufferGetWidthOfPlane(pb, i)));
EXPECT_EQ(plane_size.height(),
static_cast<int>(CVPixelBufferGetHeightOfPlane(pb, i)));
uint8_t* plane_ptr =
reinterpret_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(pb, i));
EXPECT_EQ(frame->visible_data(i), plane_ptr);
const int stride =
static_cast<int>(CVPixelBufferGetBytesPerRowOfPlane(pb, i));
EXPECT_EQ(frame->stride(i), stride);
const int offset = kVisibleRectOffset / ((i == 0) ? 1 : 2);
for (int h = 0; h < plane_size.height(); ++h) {
const int row_index = h * stride;
for (int w = 0; w < plane_size.width(); ++w) {
const int index = row_index + w;
EXPECT_EQ(static_cast<uint8_t>((w + offset) ^ (h + offset)),
plane_ptr[index]);
}
}
}
CVPixelBufferUnlockBaseAddress(pb, 0);
}
} // namespace media