blob: a1e92f019e390546b879ae702d0c86a285296c50 [file] [log] [blame]
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import posixpath
import sys
import json
import itertools
from typing import Any
import unittest
import gpu_path_util
from gpu_tests import common_browser_args as cba
from gpu_tests import common_typing as ct
from gpu_tests import gpu_integration_test
from gpu_tests.util import host_information
html_path = os.path.join(gpu_path_util.CHROMIUM_SRC_DIR, 'content', 'test',
'data', 'gpu', 'webcodecs')
data_path = os.path.join(gpu_path_util.CHROMIUM_SRC_DIR, 'media', 'test',
'data')
four_colors_img_path = os.path.join(data_path, 'four-colors.y4m')
frame_sources = [
'camera', 'capture', 'offscreen', 'arraybuffer', 'hw_decoder', 'sw_decoder'
]
hbd_frame_sources = ['hbd_arraybuffer']
video_codecs = [
'avc1.42001E', 'hvc1.1.6.L123.00', 'vp8', 'vp09.00.10.08', 'av01.0.04M.08'
]
accelerations = ['prefer-hardware', 'prefer-software']
class WebCodecsIntegrationTest(gpu_integration_test.GpuIntegrationTest):
@classmethod
def Name(cls) -> str:
return 'webcodecs'
@classmethod
def _SuiteSupportsParallelTests(cls) -> bool:
return True
def _GetSerialGlobs(self) -> set[str]:
serial_globs = set()
if host_information.IsWindows() and host_information.IsNvidiaGpu():
serial_globs |= {
# crbug.com/1473480. Windows + NVIDIA has a maximum parallel encode
# limit of 2, so serialize hardware encoding tests on Windows.
'WebCodecs_*prefer-hardware*',
}
return serial_globs
def _GetSerialTests(self) -> set[str]:
serial_tests = set()
if host_information.IsWindows() and host_information.IsArmCpu():
serial_tests |= {
# crbug.com/323824490. Seems to flakily lose the D3D11 device when
# run in parallel.
'WebCodecs_FrameSizeChange_vp09.00.10.08_hw_decoder',
}
return serial_tests
# pylint: disable=too-many-branches
@classmethod
def GenerateGpuTests(cls, options: ct.ParsedCmdArgs) -> ct.TestGenerator:
tests = itertools.chain(cls.GenerateFrameTests(), cls.GenerateVideoTests(),
cls.GenerateAudioTests(), cls.BitrateTests(),
cls.GenerateD3D12EncodingTests())
yield from tests
@classmethod
def GenerateFrameTests(cls) -> ct.TestGenerator:
for source_type in frame_sources:
yield (
f'WebCodecs_DrawImage_{source_type}',
'draw-image.html',
[{
'source_type': source_type,
}],
)
yield (
f'WebCodecs_TexImage2d_{source_type}',
'tex-image-2d.html',
[{
'source_type': source_type,
}],
)
yield (
f'WebCodecs_copyTo_{source_type}',
'copyTo.html',
[{
'source_type': source_type,
}],
)
yield (
f'WebCodecs_convertToRGB_{source_type}',
'convert-to-rgb.html',
[{
'source_type': source_type,
}],
)
for source_type in hbd_frame_sources:
yield (
f'WebCodecs_DrawImage_{source_type}',
'draw-image.html',
[{
'source_type': source_type,
}],
)
@classmethod
def GenerateAudioTests(cls) -> ct.TestGenerator:
yield (
'WebCodecs_AudioEncoding_AAC_LC',
'audio-encode-decode.html',
[{
'codec': 'mp4a.67',
'sample_rate': 48000,
'channels': 2,
'aac_format': 'aac',
}],
)
yield (
'WebCodecs_AudioEncoding_AAC_LC_ADTS',
'audio-encode-decode.html',
[{
'codec': 'mp4a.67',
'sample_rate': 48000,
'channels': 2,
'aac_format': 'adts',
}],
)
@classmethod
def BitrateTests(cls) -> ct.TestGenerator:
high_res_codecs = [
'avc1.420034',
'hvc1.1.6.L123.00',
'vp8',
'vp09.00.10.08',
'av01.0.04M.08',
]
for codec, acc, bitrate_mode, bitrate in itertools.product(
high_res_codecs, accelerations, ['constant', 'variable'],
[1500000, 2000000, 3000000]):
yield (
f'WebCodecs_EncodingRateControl_'
f'{codec}_{acc}_{bitrate_mode}_{bitrate}',
'encoding-rate-control.html',
[{
'codec': codec,
'acceleration': acc,
'bitrate_mode': bitrate_mode,
'bitrate': bitrate,
}],
)
@classmethod
def GenerateVideoTests(cls) -> ct.TestGenerator:
yield (
'WebCodecs_WebRTCPeerConnection_Window',
'webrtc-peer-connection.html',
[{
'use_worker': False,
}],
)
yield (
'WebCodecs_WebRTCPeerConnection_Worker',
'webrtc-peer-connection.html',
[{
'use_worker': True,
}],
)
yield (
'WebCodecs_Terminate_Worker',
'terminate-worker.html',
[{
'source_type': 'offscreen',
}],
)
source_type = 'offscreen'
acc = 'prefer-hardware'
for codec in ['avc1.42001E', 'hvc1.1.6.L93.B0']:
yield (
f'WebCodecs_PerFrameQpEncoding_{source_type}_{codec}_{acc}',
'frame-qp-encoding.html',
[{
'source_type': source_type,
'codec': codec,
'acceleration': acc,
}],
)
codec = 'av01.0.04M.08'
acc = 'prefer-software'
for layers in range(4):
yield (
f'WebCodecs_ManualSVC_{codec}_{acc}_layers_{layers}',
'manual-svc.html',
[{
'codec': codec,
'acceleration': acc,
'layers': layers,
}],
)
for source_type, codec, acc in itertools.product(frame_sources,
video_codecs,
accelerations):
yield (
f'WebCodecs_EncodeDecode_{source_type}_{codec}_{acc}',
'encode-decode.html',
[{
'source_type': source_type,
'codec': codec,
'acceleration': acc,
}],
)
# Also verify we can deal with the encoder's frame delay that we can
# encounter for the AVC High profile (avc1.64).
for source_type, codec, acc in itertools.product(
frame_sources, video_codecs + ['avc1.64001E'], accelerations):
yield (
f'WebCodecs_Encode_{source_type}_{codec}_{acc}',
'encode.html',
[{
'source_type': source_type,
'codec': codec,
'acceleration': acc,
}],
)
resolutions = ['1920x1080', '3840x2160', '7680x3840']
framerates = [30, 60, 120, 240]
# Use at least level 6.2 (H.264/H.265/VP9), or level 6.3 (AV1) mimetypes to
# test 8k 120fps support.
codecs = [
'avc1.64003E',
'hvc1.1.6.L186.B0',
'vp09.00.62.08',
'av01.1.19M.08',
]
for resolution, framerate, codec in itertools.product(
resolutions, framerates, codecs):
acc = 'prefer-hardware'
latency_mode = 'quality'
yield (
f'WebCodecs_EncodingFramerateResolutions_'
f'{resolution}_{framerate}_{codec}_{acc}_{latency_mode}',
'encoding-framerate-resolutions.html',
[{
'resolution': resolution,
'framerate': framerate,
'codec': codec,
'acceleration': acc,
'latency_mode': latency_mode,
}],
)
for codec, acc, bitrate_mode, latency_mode in itertools.product(
video_codecs, accelerations, ['constant', 'variable'],
['realtime', 'quality']):
source_type = 'offscreen'
content_hint = 'motion'
yield (
f'WebCodecs_EncodingModes_'
f'{source_type}_{codec}_{acc}_{bitrate_mode}_{latency_mode}',
'encoding-modes.html',
[{
'source_type': source_type,
'codec': codec,
'acceleration': acc,
'bitrate_mode': bitrate_mode,
'latency_mode': latency_mode,
'content_hint': content_hint,
}],
)
for codec, content_hint in itertools.product(video_codecs,
['detail', 'text', 'motion']):
source_type = 'offscreen'
acc = 'prefer-hardware'
bitrate_mode = 'constant'
latency_mode = 'realtime'
yield (
f'WebCodecs_ContentHint_{codec}_{content_hint}',
'encoding-modes.html',
[{
'source_type': source_type,
'codec': codec,
'acceleration': acc,
'bitrate_mode': bitrate_mode,
'latency_mode': latency_mode,
'content_hint': content_hint,
}],
)
for codec, acc, layers in itertools.product(video_codecs, accelerations,
[2, 3]):
yield (
f'WebCodecs_SVC_{codec}_{acc}_layers_{layers}',
'svc.html',
[{
'codec': codec,
'acceleration': acc,
'layers': layers,
}],
)
for codec, acc in itertools.product(video_codecs, accelerations):
yield (
f'WebCodecs_EncodeColorSpace_{codec}_{acc}',
'encode-color-space.html',
[{
'codec': codec,
'acceleration': acc,
}],
)
yield (
f'WebCodecs_MixedSourceEncoding_{codec}_{acc}',
'mixed-source-encoding.html',
[{
'codec': codec,
'acceleration': acc,
}],
)
for codec, source_type in itertools.product(video_codecs, frame_sources):
yield (
f'WebCodecs_FrameSizeChange_{codec}_{source_type}',
'frame-size-change.html',
[{
'codec': codec,
'source_type': source_type,
}],
)
@classmethod
def GenerateD3D12EncodingTests(cls) -> ct.TestGenerator:
source_type = 'offscreen'
acc = 'prefer-hardware'
for source_type, codec in itertools.product(frame_sources, video_codecs):
yield (
f'WebCodecs_D3D12_EncodeDecode_{source_type}_{codec}',
'encode-decode.html',
[{
'source_type': source_type,
'codec': codec,
'acceleration': acc,
'runs_on_d3d12_encoder': True,
}],
)
for source_type, codec in itertools.product(frame_sources, video_codecs):
yield (
f'WebCodecs_D3D12_Encode_{source_type}_{codec}',
'encode.html',
[{
'source_type': source_type,
'codec': codec,
'acceleration': acc,
'runs_on_d3d12_encoder': True,
}],
)
for codec, bitrate_mode, latency_mode in itertools.product(
video_codecs, ['constant', 'variable'], ['realtime', 'quality']):
source_type = 'offscreen'
content_hint = 'motion'
yield (
f'WebCodecs_D3D12_EncodingModes_'
f'{source_type}_{codec}_{bitrate_mode}_{latency_mode}',
'encoding-modes.html',
[{
'source_type': source_type,
'codec': codec,
'acceleration': acc,
'bitrate_mode': bitrate_mode,
'latency_mode': latency_mode,
'content_hint': content_hint,
'runs_on_d3d12_encoder': True,
}],
)
for codec, content_hint in itertools.product(video_codecs,
['detail', 'text', 'motion']):
source_type = 'offscreen'
bitrate_mode = 'constant'
latency_mode = 'realtime'
yield (
f'WebCodecs_D3D12_ContentHint_{codec}_{content_hint}',
'encoding-modes.html',
[{
'source_type': source_type,
'codec': codec,
'acceleration': acc,
'bitrate_mode': bitrate_mode,
'latency_mode': latency_mode,
'content_hint': content_hint,
'runs_on_d3d12_encoder': True,
}],
)
for codec, layers in itertools.product(video_codecs, [2, 3]):
yield (
f'WebCodecs_D3D12_SVC_{codec}_layers_{layers}',
'svc.html',
[{
'codec': codec,
'acceleration': acc,
'layers': layers,
'runs_on_d3d12_encoder': True,
}],
)
for codec in video_codecs:
yield (
f'WebCodecs_D3D12_EncodeColorSpace_{codec}',
'encode-color-space.html',
[{
'codec': codec,
'acceleration': acc,
'runs_on_d3d12_encoder': True,
}],
)
yield (
f'WebCodecs_D3D12_MixedSourceEncoding_{codec}',
'mixed-source-encoding.html',
[{
'codec': codec,
'acceleration': acc,
'runs_on_d3d12_encoder': True,
}],
)
for codec, source_type in itertools.product(video_codecs, frame_sources):
yield (
f'WebCodecs_D3D12_FrameSizeChange_{codec}_{source_type}',
'frame-size-change.html',
[{
'codec': codec,
'source_type': source_type,
'runs_on_d3d12_encoder': True,
}],
)
# pylint: enable=too-many-branches
def RunActualGpuTest(self, test_path: str, args: ct.TestArgs) -> None:
url = self.UrlOfStaticFilePath(posixpath.join(html_path, test_path))
arg_obj = args[0]
os_name = self.platform.GetOSName()
enable_d3d12_encoder = arg_obj.get('runs_on_d3d12_encoder', False)
if enable_d3d12_encoder and not host_information.IsWindows():
self.skipTest('Skipping D3D12 based test.')
return
args = self.GetBrowserArguments(enable_d3d12_encoder)
self.RestartBrowserIfNecessaryWithArgs(args)
arg_obj['validate_camera_frames'] = self.CameraCanShowFourColors(os_name)
tab = self.tab
tab.Navigate(url)
tab.action_runner.WaitForJavaScriptCondition(
'document.readyState == "complete"')
tab.EvaluateJavaScript('TEST.run(' + json.dumps(arg_obj) + ')')
tab.action_runner.WaitForJavaScriptCondition('TEST.finished', timeout=60)
if tab.EvaluateJavaScript('TEST.skipped'):
self.skipTest('Skipping test:' + tab.EvaluateJavaScript('TEST.summary()'))
if not tab.EvaluateJavaScript('TEST.success'):
self.fail('Test failure:' + tab.EvaluateJavaScript('TEST.summary()'))
@staticmethod
def CameraCanShowFourColors(os_name: str) -> bool:
return os_name not in ('android', 'chromeos')
@classmethod
def GetBrowserArguments(cls, enable_d3d12_encoder: bool) -> list[str]:
args = [
'--use-fake-device-for-media-stream',
'--use-fake-ui-for-media-stream',
'--enable-blink-features=SharedArrayBuffer',
cba.ENABLE_EXPERIMENTAL_WEB_PLATFORM_FEATURES,
] + cba.ENABLE_WEBGPU_FOR_TESTING
if enable_d3d12_encoder:
args.append('--enable-features=D3D12VideoEncodeAccelerator')
args.append('--disable-features=MediaFoundationVideoEncodeAccelerator')
if cls.CameraCanShowFourColors(cls.platform.GetOSName()):
args.append('--use-file-for-fake-video-capture=' + four_colors_img_path)
return args
@classmethod
def SetUpProcess(cls) -> None:
super(WebCodecsIntegrationTest, cls).SetUpProcess()
args = []
# If we don't call CustomizeBrowserArgs cls.platform is None
cls.CustomizeBrowserArgs(args)
args = cls.GetBrowserArguments(False)
cls.CustomizeBrowserArgs(args)
cls.StartBrowser()
cls.SetStaticServerDirs([html_path, data_path])
@classmethod
def ExpectationsFiles(cls) -> list[str]:
return [
os.path.join(os.path.dirname(os.path.abspath(__file__)),
'test_expectations', 'webcodecs_expectations.txt')
]
def load_tests(loader: unittest.TestLoader, tests: Any,
pattern: Any) -> unittest.TestSuite:
del loader, tests, pattern # Unused.
return gpu_integration_test.LoadAllTestsInModule(sys.modules[__name__])