blob: d15bf76ba83c6138306d9edb10a82b2f8af744cb [file] [log] [blame]
// Copyright 2018 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <cmath>
#include <random>
#include <vector>
#include "starboard/media.h"
#include "starboard/memory.h"
#include "starboard/nplb/performance_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace starboard {
namespace nplb {
namespace {
constexpr int kVideoResolutions[][2] = {
{kSbMediaVideoResolutionDimensionInvalid,
kSbMediaVideoResolutionDimensionInvalid},
{640, 480},
{1280, 720},
{1920, 1080},
{2560, 1440},
{3840, 2160},
{7680, 4320},
};
constexpr int kBitsPerPixelValues[] = {
kSbMediaBitsPerPixelInvalid,
8,
10,
12,
};
constexpr SbMediaVideoCodec kVideoCodecs[] = {
kSbMediaVideoCodecNone,
kSbMediaVideoCodecH264, kSbMediaVideoCodecH265, kSbMediaVideoCodecMpeg2,
kSbMediaVideoCodecTheora, kSbMediaVideoCodecVc1, kSbMediaVideoCodecAv1,
kSbMediaVideoCodecVp8, kSbMediaVideoCodecVp9,
};
constexpr SbMediaType kMediaTypes[] = {
kSbMediaTypeAudio,
kSbMediaTypeVideo,
};
// Minimum audio and video budgets required by the 2024 Youtube Hardware
// Requirements.
constexpr int kMinAudioBudget = 5 * 1024 * 1024;
constexpr int kMinVideoBudget1080p = 30 * 1024 * 1024;
constexpr int kMinVideoBudget4kSdr = 50 * 1024 * 1024;
constexpr int kMinVideoBudget4kHdr = 80 * 1024 * 1024;
constexpr int kMinVideoBudget8k = 200 * 1024 * 1024;
int GetVideoBufferBudget(SbMediaVideoCodec codec,
int frame_width,
bool is_hdr) {
if (codec != kSbMediaVideoCodecAv1 && codec != kSbMediaVideoCodecH264 &&
codec != kSbMediaVideoCodecVp9) {
return kMinVideoBudget1080p;
}
static const bool kSupports8k =
SbMediaCanPlayMimeAndKeySystem(
"video/mp4; codecs=\"av01.0.16M.08\"; width=7680; height=4320", "") ==
kSbMediaSupportTypeProbably;
static const bool kSupports4kHdr =
SbMediaCanPlayMimeAndKeySystem(
"video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=3840; "
"height=2160",
"") == kSbMediaSupportTypeProbably;
static const bool kSupports4kSdr =
SbMediaCanPlayMimeAndKeySystem(
"video/webm; codecs=\"vp9\"; width=3840; height=2160", "") ==
kSbMediaSupportTypeProbably;
int video_budget = kMinVideoBudget1080p;
if (kSupports8k && frame_width > 3840) {
video_budget = kMinVideoBudget8k;
} else if (frame_width > 1920 && frame_width <= 3840) {
if (kSupports4kHdr && is_hdr) {
video_budget = kMinVideoBudget4kHdr;
} else if (kSupports4kSdr && !is_hdr) {
video_budget = kMinVideoBudget4kSdr;
}
}
return video_budget;
}
std::vector<void*> TryToAllocateMemory(int size,
int allocation_unit,
int alignment) {
int total_allocated = 0;
std::vector<void*> allocated_ptrs;
if (allocation_unit != 0) {
allocated_ptrs.reserve(std::ceil(size / allocation_unit));
}
while (total_allocated < size) {
// When |allocation_unit| == 0, randomly allocate a size between 100k -
// 500k.
int allocation_increment = allocation_unit != 0
? allocation_unit
: (std::rand() % 500 + 100) * 1024;
void* allocated_memory = NULL;
#if SB_API_VERSION < 16
allocated_memory = SbMemoryAllocateAligned(alignment, allocation_increment);
#else
posix_memalign(&allocated_memory, alignment, allocation_increment);
#endif
EXPECT_NE(allocated_memory, nullptr);
if (!allocated_memory) {
return allocated_ptrs;
}
allocated_ptrs.push_back(allocated_memory);
total_allocated += allocation_increment;
}
return allocated_ptrs;
}
} // namespace
TEST(SbMediaBufferTest, VideoCodecs) {
// Perform a check to determine if new codecs have been added to the
// enum, but not the array. If the compiler warns about a missing case here,
// the value must be added to the array and the switch case.
SbMediaVideoCodec codec = kVideoCodecs[0];
switch (codec) {
case kVideoCodecs[0]:
case kVideoCodecs[1]:
case kVideoCodecs[2]:
case kVideoCodecs[3]:
case kVideoCodecs[4]:
case kVideoCodecs[5]:
case kVideoCodecs[6]:
case kVideoCodecs[7]:
case kVideoCodecs[8]:
break;
}
}
TEST(SbMediaBufferTest, MediaTypes) {
// Perform a check to determine if new types have been added to the
// enum, but not the array. If the compiler warns about a missing case here,
// the value must be added to the array and the switch case.
SbMediaType type = kMediaTypes[0];
switch (type) {
case kMediaTypes[0]:
case kMediaTypes[1]:
break;
}
}
#if SB_API_VERSION < 16
TEST(SbMediaBufferTest, Alignment) {
for (auto type : kMediaTypes) {
// The test will be run more than once, it's redundant but allows us to keep
// the test logic in one place.
int alignment = SbMediaGetBufferAlignment();
#if SB_API_VERSION >= 16
// SbMediaGetBufferAlignment() was deprecated in Starboard 16, its return
// value is no longer used when allocating media buffers. This is verified
// explicitly here by ensuring its return value is 1.
// The app MAY take best effort to allocate media buffers aligned to an
// optimal alignment for the platform, but not guaranteed.
// An implementation that has specific alignment requirement should check
// the alignment of the incoming buffer, and make a copy when necessary.
EXPECT_EQ(alignment, 1);
#else // SB_API_VERSION >= 16
EXPECT_GE(alignment, 1);
EXPECT_EQ(alignment & (alignment - 1), 0)
<< "Alignment must always be a power of 2";
#endif // SB_API_VERSION >= 16
}
}
#endif // SB_API_VERSION < 16
TEST(SbMediaBufferTest, AllocationUnit) {
EXPECT_GE(SbMediaGetBufferAllocationUnit(), 0);
if (SbMediaGetBufferAllocationUnit() != 0) {
EXPECT_GE(SbMediaGetBufferAllocationUnit(), 64 * 1024);
}
int allocation_unit = SbMediaGetBufferAllocationUnit();
std::vector<void*> allocated_ptrs;
int initial_buffer_capacity = SbMediaGetInitialBufferCapacity();
if (initial_buffer_capacity > 0) {
allocated_ptrs = TryToAllocateMemory(initial_buffer_capacity,
allocation_unit, sizeof(void*));
}
#if SB_API_VERSION < 16
if (!HasNonfatalFailure()) {
for (SbMediaType type : kMediaTypes) {
// The test will be run more than once, it's redundant but allows us to
// keep the test logic in one place.
int alignment = SbMediaGetBufferAlignment();
SB_LOG(INFO) << "alignment=" << alignment;
EXPECT_EQ(alignment & (alignment - 1), 0)
<< "Alignment must always be a power of 2";
if (HasNonfatalFailure()) {
break;
}
int media_budget = type == SbMediaType::kSbMediaTypeAudio
? kMinAudioBudget
: kMinVideoBudget1080p;
std::vector<void*> media_buffer_allocated_memory =
TryToAllocateMemory(media_budget, allocation_unit, alignment);
allocated_ptrs.insert(allocated_ptrs.end(),
media_buffer_allocated_memory.begin(),
media_buffer_allocated_memory.end());
if (HasNonfatalFailure()) {
break;
}
}
}
#endif // SB_API_VERSION < 16
for (void* ptr : allocated_ptrs) {
free(ptr);
}
}
TEST(SbMediaBufferTest, AudioBudget) {
EXPECT_GE(SbMediaGetAudioBufferBudget(), kMinAudioBudget);
}
TEST(SbMediaBufferTest, GarbageCollectionDurationThreshold) {
int kMinGarbageCollectionDurationThreshold = 30'000'000LL; // 30 seconds
int kMaxGarbageCollectionDurationThreshold = 240'000'000LL; // 240 seconds
int threshold = SbMediaGetBufferGarbageCollectionDurationThreshold();
EXPECT_GE(threshold, kMinGarbageCollectionDurationThreshold);
EXPECT_LE(threshold, kMaxGarbageCollectionDurationThreshold);
}
TEST(SbMediaBufferTest, InitialCapacity) {
EXPECT_GE(SbMediaGetInitialBufferCapacity(), 0);
}
TEST(SbMediaBufferTest, MaxCapacity) {
// TODO: Limit EXPECT statements to only codecs and resolutions that are
// supported by the platform. If unsupported, still call
// SbMediaGetMaxBufferCapacity() to ensure there isn't a crash.
for (auto resolution : kVideoResolutions) {
for (auto bits_per_pixel : kBitsPerPixelValues) {
for (auto codec : kVideoCodecs) {
EXPECT_GT(SbMediaGetMaxBufferCapacity(codec, resolution[0],
resolution[1], bits_per_pixel),
0);
EXPECT_GE(SbMediaGetMaxBufferCapacity(codec, resolution[0],
resolution[1], bits_per_pixel),
SbMediaGetInitialBufferCapacity());
}
}
}
}
TEST(SbMediaBufferTest, Padding) {
#if SB_API_VERSION >= 16
// SbMediaGetBufferPadding() was deprecated in Starboard 16, its return value
// is no longer used when allocating media buffers. This is verified
// explicitly here by ensuring its return value is 0.
// An implementation that has specific padding requirement should make a
// copy of the incoming buffer when necessary.
EXPECT_EQ(SbMediaGetBufferPadding(), 0);
#else // SB_API_VERSION >= 16
EXPECT_GE(SbMediaGetBufferPadding(), 0);
#endif // SB_API_VERSION >= 16
}
TEST(SbMediaBufferTest, PoolAllocateOnDemand) {
// Just don't crash.
SbMediaIsBufferPoolAllocateOnDemand();
}
TEST(SbMediaBufferTest, ProgressiveBudget) {
const int kMinProgressiveBudget = 8 * 1024 * 1024;
int audio_budget = SbMediaGetAudioBufferBudget();
for (auto video_codec : kVideoCodecs) {
for (auto bits_per_pixel : kBitsPerPixelValues) {
int video_budget_1080p =
SbMediaGetVideoBufferBudget(video_codec, 1920, 1080, bits_per_pixel);
for (auto resolution : kVideoResolutions) {
int progressive_budget = SbMediaGetProgressiveBufferBudget(
video_codec, resolution[0], resolution[1], bits_per_pixel);
EXPECT_LT(progressive_budget, video_budget_1080p + audio_budget)
<< "Progressive budget must be less than sum of 1080p video "
"budget and audio budget";
EXPECT_GE(progressive_budget, kMinProgressiveBudget);
}
}
}
}
#if SB_API_VERSION < 16
TEST(SbMediaBufferTest, StorageType) {
// Just don't crash.
SbMediaBufferStorageType type = SbMediaGetBufferStorageType();
switch (type) {
case kSbMediaBufferStorageTypeMemory:
case kSbMediaBufferStorageTypeFile:
return;
}
SB_NOTREACHED();
}
#endif // SB_API_VERSION < 16
TEST(SbMediaBufferTest, UsingMemoryPool) {
// Just don't crash.
SbMediaIsBufferUsingMemoryPool();
}
TEST(SbMediaBufferTest, VideoBudget) {
for (auto codec : kVideoCodecs) {
for (auto resolution : kVideoResolutions) {
for (auto bits_per_pixel : kBitsPerPixelValues) {
int video_budget =
GetVideoBufferBudget(codec, resolution[0], bits_per_pixel > 8);
EXPECT_GE(SbMediaGetVideoBufferBudget(codec, resolution[0],
resolution[1], bits_per_pixel),
video_budget);
}
}
}
}
TEST(SbMediaBufferTest, ValidatePerformance) {
TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaGetBufferAllocationUnit);
TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaGetAudioBufferBudget);
TEST_PERF_FUNCNOARGS_DEFAULT(
SbMediaGetBufferGarbageCollectionDurationThreshold);
TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaGetInitialBufferCapacity);
TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaIsBufferPoolAllocateOnDemand);
#if SB_API_VERSION < 16
TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaGetBufferStorageType);
#endif // SB_API_VERSION < 16
TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaIsBufferUsingMemoryPool);
#if SB_API_VERSION < 16
for (auto type : kMediaTypes) {
TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaGetBufferAlignment);
TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaGetBufferPadding);
}
#endif // SB_API_VERSION < 16
for (auto resolution : kVideoResolutions) {
for (auto bits_per_pixel : kBitsPerPixelValues) {
for (auto codec : kVideoCodecs) {
TEST_PERF_FUNCWITHARGS_DEFAULT(SbMediaGetMaxBufferCapacity, codec,
resolution[0], resolution[1],
bits_per_pixel);
TEST_PERF_FUNCWITHARGS_DEFAULT(SbMediaGetProgressiveBufferBudget, codec,
resolution[0], resolution[1],
bits_per_pixel);
TEST_PERF_FUNCWITHARGS_DEFAULT(SbMediaGetVideoBufferBudget, codec,
resolution[0], resolution[1],
bits_per_pixel);
}
}
}
}
} // namespace nplb
} // namespace starboard