From 1cfb644ca2e0e860891df37c41d3d2af10175168 Mon Sep 17 00:00:00 2001 From: rsanchez Date: Wed, 9 Apr 2025 14:35:13 +0200 Subject: [PATCH 1/3] build: add mpeg2v test suites to pyproject.toml --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2df45eaf..d1c02ae3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,6 +98,10 @@ packages = ["fluster", "fluster.decoders"] "test_suites/vp9/VP9-TEST-VECTORS-HIGH.json", "test_suites/vp9/VP9-TEST-VECTORS.json" ] +"share/fluster/test_suites/mpeg2v" = [ + "test_suites/mpeg2v/MPEG2_VIDEO-422.json", + "test_suites/mpeg2v/MPEG2_VIDEO-MAIN.json" +] # Once https://github.com/pypa/pip/issues/12963 is implemented we can think about adding # [dependency-groups] From 15711939fdddb061463437a47ded5408ee98ab0c Mon Sep 17 00:00:00 2001 From: rsanchez Date: Tue, 15 Apr 2025 10:01:29 +0200 Subject: [PATCH 2/3] docs: add "ISO-MPEG2-VIDEO: ISO MPEG2 Video reference" decoder to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9cb1d298..254e0ab7 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,7 @@ H.266 MPEG2_VIDEO FFmpeg-MPEG2_VIDEO: FFmpeg MPEG2 VIDEO SW decoder Fluendo-MPEG2_VIDEO-SW-Gst1.0: Fluendo MPEG2 VIDEO SW decoder for GStreamer 1.0 + ISO-MPEG2-VIDEO: ISO MPEG2 Video reference decoder VP8 FFmpeg-VP8: FFmpeg VP8 SW decoder From 6ed4c37540b6cc6d7a4c0c82d5c36aab82cdfe04 Mon Sep 17 00:00:00 2001 From: rsanchez Date: Fri, 4 Apr 2025 11:03:07 +0200 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20Add=20a=20pixel=20comparison=20meth?= =?UTF-8?q?od=20for=20codecs=20that=20don=E2=80=99t=20generate=20identical?= =?UTF-8?q?=20outputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Old codecs like MPEG-2 Video do not use a standarized IDCT (MPEG-C Part1) causing mismatches in the generated output. -This new method allows creating tests suites that will compare the output pixel by pixel with a tolerance range --- check/dummy.json | 5 +- fluster/decoder.py | 16 +- fluster/decoders/iso_mpeg2_aac.py | 1 + fluster/decoders/iso_mpeg2_video.py | 85 ++++++++++ fluster/decoders/iso_mpeg4_aac.py | 1 + fluster/decoders/iso_mpeg4_aac_er.py | 1 + fluster/fluster.py | 4 +- fluster/test.py | 194 +++++++++++++++++------ fluster/test_suite.py | 70 ++++++-- fluster/utils.py | 37 ++++- test_suites/mpeg2v/MPEG2_VIDEO-422.json | 1 + test_suites/mpeg2v/MPEG2_VIDEO-MAIN.json | 1 + 12 files changed, 347 insertions(+), 69 deletions(-) create mode 100644 fluster/decoders/iso_mpeg2_video.py diff --git a/check/dummy.json b/check/dummy.json index 2473b189..6c2fbdf2 100644 --- a/check/dummy.json +++ b/check/dummy.json @@ -11,5 +11,6 @@ "output_format": "yuv420p", "result": "19b8d716307ae8b28c81b21f14f870dc" } - ] -} \ No newline at end of file + ], + "test_method": "md5" +} diff --git a/fluster/decoder.py b/fluster/decoder.py index 5e09d8f0..a8785c3c 100644 --- a/fluster/decoder.py +++ b/fluster/decoder.py @@ -18,7 +18,7 @@ from abc import ABC, abstractmethod from functools import lru_cache from shutil import which -from typing import List, Type +from typing import List, Optional, Type from fluster.codec import Codec, OutputFormat from fluster.utils import normalize_binary_cmd @@ -32,6 +32,7 @@ class Decoder(ABC): hw_acceleration = False description = "" binary = "" + is_reference = False def __init__(self) -> None: if self.binary: @@ -76,3 +77,16 @@ def register_decoder(cls: Type[Decoder]) -> Type[Decoder]: DECODERS.append(cls()) DECODERS.sort(key=lambda dec: dec.name) return cls + + +def get_reference_decoder_for_codec(codec: Codec) -> Optional["Decoder"]: + """Find the reference decoder for a specific codec""" + + reference_decoders = [d for d in DECODERS if d.codec == codec and d.is_reference] + + if not reference_decoders: + return None + if len(reference_decoders) > 1: + print(f"Multiple reference decoders found for codec {codec.name}") + + return reference_decoders[0] diff --git a/fluster/decoders/iso_mpeg2_aac.py b/fluster/decoders/iso_mpeg2_aac.py index efaaf627..0c2ee5ee 100644 --- a/fluster/decoders/iso_mpeg2_aac.py +++ b/fluster/decoders/iso_mpeg2_aac.py @@ -30,6 +30,7 @@ class ISOAACDecoder(Decoder): description = "ISO MPEG2 AAC reference decoder" codec = Codec.AAC binary = "aacdec_mc" + is_reference = True def decode( self, diff --git a/fluster/decoders/iso_mpeg2_video.py b/fluster/decoders/iso_mpeg2_video.py new file mode 100644 index 00000000..c0001644 --- /dev/null +++ b/fluster/decoders/iso_mpeg2_video.py @@ -0,0 +1,85 @@ +# Fluster - testing framework for decoders conformance +# Copyright (C) 2024, Fluendo, S.A. +# Author: Rubén Sánchez , Fluendo, S.A. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation, either version 3 +# of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . +import glob +import os +import tempfile + +from fluster.codec import Codec, OutputFormat +from fluster.decoder import Decoder, register_decoder +from fluster.utils import file_checksum, run_command + + +@register_decoder +class ISOMPEG2VDecoder(Decoder): + """ISO MPEG2 Video reference decoder implementation""" + + name = "ISO-MPEG2-VIDEO" + description = "ISO MPEG2 Video reference decoder" + codec = Codec.MPEG2_VIDEO + binary = "mpeg2decode" + is_reference = True + + def decode( + self, + input_filepath: str, + output_filepath: str, + output_format: OutputFormat, + timeout: int, + verbose: bool, + keep_files: bool, + ) -> str: + """Decodes input_filepath in output_filepath""" + with tempfile.TemporaryDirectory() as temp_dir: + run_command( + [self.binary, "-b", input_filepath, "-f", "-r", "-o0", os.path.join(temp_dir, "rec%d")], + timeout=timeout, + verbose=verbose, + ) + self._merge_yuv_files(temp_dir, output_filepath) + checksum = file_checksum(output_filepath) + + if not keep_files: + os.remove(output_filepath) + + return checksum + + @staticmethod + def _merge_yuv_files(input_dir: str, output_filepath: str) -> None: + """Merge YUV frames into an only raw .yuv file for mpeg2 video test suite""" + num_frames = len(glob.glob(os.path.join(input_dir, "rec*.Y"))) + + if num_frames == 0: + raise ValueError("No frames were decoded") + + with open(output_filepath, "wb") as output_file: + for frame_num in range(num_frames): + frame_name = f"rec{frame_num}" + y_file = os.path.join(input_dir, f"{frame_name}.Y") + u_file = os.path.join(input_dir, f"{frame_name}.U") + v_file = os.path.join(input_dir, f"{frame_name}.V") + + if not (os.path.exists(y_file) and os.path.exists(u_file) and os.path.exists(v_file)): + print(f"Warning: Files for frame {frame_name} not found in {input_dir}") + continue + + chunk_size = 1024 + for plane_file in [y_file, u_file, v_file]: + with open(plane_file, "rb") as file: + chunk = file.read(chunk_size) + while chunk: + output_file.write(chunk) + chunk = file.read(chunk_size) diff --git a/fluster/decoders/iso_mpeg4_aac.py b/fluster/decoders/iso_mpeg4_aac.py index 673309c3..c402a837 100644 --- a/fluster/decoders/iso_mpeg4_aac.py +++ b/fluster/decoders/iso_mpeg4_aac.py @@ -30,6 +30,7 @@ class ISOAACDecoder(Decoder): description = "ISO MPEG4 AAC reference decoder" codec = Codec.AAC binary = "mp4audec_mc" + is_reference = True def decode( self, diff --git a/fluster/decoders/iso_mpeg4_aac_er.py b/fluster/decoders/iso_mpeg4_aac_er.py index 4264dca7..280bbdda 100644 --- a/fluster/decoders/iso_mpeg4_aac_er.py +++ b/fluster/decoders/iso_mpeg4_aac_er.py @@ -30,6 +30,7 @@ class ISOAACDecoder(Decoder): description = "ISO MPEG4 AAC ER reference decoder" codec = Codec.AAC binary = "mp4audec" + is_reference = True def decode( self, diff --git a/fluster/fluster.py b/fluster/fluster.py index c8f71e5e..ccf1ae62 100644 --- a/fluster/fluster.py +++ b/fluster/fluster.py @@ -31,7 +31,7 @@ from fluster.decoders import * # noqa: F403 from fluster.decoders.av1_aom import AV1AOMDecoder from fluster.test_suite import Context as TestSuiteContext -from fluster.test_suite import TestSuite +from fluster.test_suite import TestMethod, TestSuite from fluster.test_vector import TestVector, TestVectorResult @@ -278,6 +278,8 @@ def run_test_suites(self, ctx: Context) -> None: decoder.multiple_layers = True if decoder.codec != test_suite.codec: continue + if test_suite.test_method == TestMethod.PIXEL and decoder.is_reference: + continue test_suite_res = test_suite.run( ctx.to_test_suite_context( decoder, diff --git a/fluster/test.py b/fluster/test.py index 12823cb1..50290d2d 100644 --- a/fluster/test.py +++ b/fluster/test.py @@ -6,24 +6,17 @@ # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation, either version 3 # of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see . import os import unittest +from abc import abstractmethod from subprocess import TimeoutExpired from time import perf_counter from typing import Any from fluster.decoder import Decoder from fluster.test_vector import TestVector, TestVectorResult -from fluster.utils import normalize_path +from fluster.utils import compare_byte_wise_files, normalize_path class Test(unittest.TestCase): @@ -51,57 +44,160 @@ def __init__( self.timeout = timeout self.keep_files = keep_files self.verbose = verbose - setattr(self, test_vector.name, self._test) + self._keep_files_during_test = False + self.test_vector_result = self.test_suite.test_vectors[self.test_vector.name] + + # Set up the test method + setattr(self, test_vector.name, self._test_wrapper) super().__init__(test_vector.name) + # Initialize file paths + self._initialize_file_paths() + + def _initialize_file_paths(self) -> None: + """Initialize input and output file paths.""" + self.output_filepath = normalize_path(os.path.join(self.output_dir, self.test_vector.name + ".out")) + + input_dir = os.path.join(self.resources_dir, self.test_suite.name) + + if not self.test_suite.is_single_archive: + input_dir = os.path.join(input_dir, self.test_vector.name) + + self.input_filepath = normalize_path(os.path.join(input_dir, self.test_vector.input_file)) + + def _execute_decode(self) -> str: + """Execute the decoder and return the result.""" + keep_files_for_decode = self._keep_files_during_test or self.keep_files + + return self.decoder.decode( + self.input_filepath, + self.output_filepath, + self.test_vector.output_format, + self.timeout, + self.verbose, + keep_files_for_decode, + ) + + def _cleanup_if_needed(self) -> None: + """Clean up output files if keep_files is False.""" + if not self.keep_files and os.path.exists(self.output_filepath): + os.remove(self.output_filepath) + + def _test_wrapper(self) -> None: + try: + self._test() + finally: + self._cleanup_if_needed() + def _test(self) -> None: + """Execute the test and process results.""" if self.skip: - self.test_suite.test_vectors[self.test_vector.name].test_result = TestVectorResult.NOT_RUN + self.test_vector_result.test_result = TestVectorResult.NOT_RUN return - output_filepath = os.path.join(self.output_dir, self.test_vector.name + ".out") - - input_filepath = os.path.join( - self.resources_dir, - self.test_suite.name, - (self.test_vector.name if not self.test_suite.is_single_archive else ""), - self.test_vector.input_file, - ) - output_filepath = normalize_path(output_filepath) - input_filepath = normalize_path(input_filepath) + start = perf_counter() try: - start = perf_counter() - result = self.decoder.decode( - input_filepath, - output_filepath, - self.test_vector.output_format, - self.timeout, - self.verbose, - self.keep_files, - ) - self.test_suite.test_vectors[self.test_vector.name].test_time = perf_counter() - start + result = self._execute_decode() + self.test_vector_result.test_time = perf_counter() - start except TimeoutExpired: - self.test_suite.test_vectors[self.test_vector.name].test_result = TestVectorResult.TIMEOUT - self.test_suite.test_vectors[self.test_vector.name].test_time = perf_counter() - start + self.test_vector_result.test_result = TestVectorResult.TIMEOUT + self.test_vector_result.test_time = perf_counter() - start raise except Exception: - self.test_suite.test_vectors[self.test_vector.name].test_result = TestVectorResult.ERROR - self.test_suite.test_vectors[self.test_vector.name].test_time = perf_counter() - start + self.test_vector_result.test_result = TestVectorResult.ERROR + self.test_vector_result.test_time = perf_counter() - start raise - if not self.keep_files and os.path.exists(output_filepath) and os.path.isfile(output_filepath): - os.remove(output_filepath) - - if not self.reference: - self.test_suite.test_vectors[self.test_vector.name].test_result = TestVectorResult.FAIL - if self.test_vector.result.lower() == result.lower(): - self.test_suite.test_vectors[self.test_vector.name].test_result = TestVectorResult.SUCCESS - self.assertEqual( - self.test_vector.result.lower(), - result.lower(), - self.test_vector.name, - ) + if self.reference: + self.test_vector_result.test_result = TestVectorResult.REFERENCE + self.test_vector_result.result = result else: - self.test_suite.test_vectors[self.test_vector.name].test_result = TestVectorResult.REFERENCE - self.test_suite.test_vectors[self.test_vector.name].result = result + try: + self.compare_result(result) + self.test_vector_result.test_result = TestVectorResult.SUCCESS + except Exception: + self.test_vector_result.test_result = TestVectorResult.FAIL + raise + + @abstractmethod + def compare_result(self, result: str) -> None: + """Compare the test result with the expected value. + + Args: + result: The result string from the decoder + """ + + +class MD5ComparisonTest(Test): + """Test class for MD5 comparison""" + + def compare_result(self, result: str) -> None: + """Compare MD5 hash results.""" + expected = self.test_vector.result.lower() + actual = result.lower() + + self.assertEqual(expected, actual, self.test_vector.name) + + +class PixelComparisonTest(Test): + """Test class for pixel comparison""" + + def __init__( + self, + decoder: Decoder, + test_suite: Any, # can't use TestSuite type because of circular dependency + test_vector: TestVector, + skip: bool, + output_dir: str, + reference: bool, + timeout: int, + keep_files: bool, + verbose: bool, + reference_decoder: Decoder, + ): + super().__init__( + decoder, + test_suite, + test_vector, + skip, + output_dir, + reference, + timeout, + keep_files, + verbose, + ) + self._keep_files_during_test = True + self.reference_decoder = reference_decoder + self.reference_filepath = normalize_path(os.path.join(self.output_dir, self.test_vector.name + "_ref.yuv")) + + def _decode_reference(self) -> str: + """Decode the reference file.""" + keep_files_for_decode = self._keep_files_during_test or self.keep_files + + return self.reference_decoder.decode( + self.input_filepath, + self.reference_filepath, + self.test_vector.output_format, + self.timeout, + self.verbose, + keep_files_for_decode, + ) + + def _cleanup_if_needed(self) -> None: + super()._cleanup_if_needed() + if not self.keep_files and os.path.exists(self.reference_filepath): + os.remove(self.reference_filepath) + + def compare_result(self, result: str) -> None: + """Compare decoded output with reference decoder output pixel-wise.""" + reference_result = self._decode_reference() + + if not os.path.exists(self.reference_filepath) and os.path.exists(reference_result): + self.reference_filepath = reference_result + + comparison_result = compare_byte_wise_files( + self.reference_filepath, self.output_filepath, keep_files=self.keep_files + ) + + self.assertEqual(0, comparison_result, self.test_vector.name) diff --git a/fluster/test_suite.py b/fluster/test_suite.py index 35ab5f4b..78a7c630 100644 --- a/fluster/test_suite.py +++ b/fluster/test_suite.py @@ -21,6 +21,7 @@ import sys import urllib.error import zipfile +from enum import Enum from functools import lru_cache from multiprocessing import Pool from shutil import rmtree @@ -30,8 +31,8 @@ from fluster import utils from fluster.codec import Codec -from fluster.decoder import Decoder -from fluster.test import Test +from fluster.decoder import Decoder, get_reference_decoder_for_codec +from fluster.test import MD5ComparisonTest, PixelComparisonTest, Test from fluster.test_vector import TestVector @@ -95,6 +96,7 @@ def __init__( failing_test_vectors: Optional[List[str]] = None, keep_files: bool = False, verbose: bool = False, + reference_decoder: Optional[Decoder] = None, ): self.jobs = jobs self.decoder = decoder @@ -108,6 +110,14 @@ def __init__( self.failing_test_vectors = failing_test_vectors self.keep_files = keep_files self.verbose = verbose + self.reference_decoder = reference_decoder + + +class TestMethod(Enum): + """Test method types enum""" + + MD5 = "md5" + PIXEL = "pixel" class TestSuite: @@ -128,6 +138,7 @@ def __init__( test_vectors: Dict[str, TestVector], is_single_archive: Optional[bool] = False, failing_test_vectors: Optional[Dict[str, TestVector]] = None, + test_method: TestMethod = TestMethod.MD5, ): # JSON members self.name = name @@ -142,6 +153,7 @@ def __init__( self.resources_dir = resources_dir self.test_vectors_success = 0 self.time_taken = 0.0 + self.test_method = test_method def clone(self) -> "TestSuite": """Create a deep copy of the object""" @@ -156,6 +168,8 @@ def from_json_file(cls: Type["TestSuite"], filename: str, resources_dir: str) -> data["failing_test_vectors"] = dict(map(TestVector.from_json, data["failing_test_vectors"])) data["test_vectors"] = dict(map(TestVector.from_json, data["test_vectors"])) data["codec"] = Codec(data["codec"]) + if "test_method" in data: + data["test_method"] = TestMethod(data["test_method"]) return cls(filename, resources_dir, **data) def to_json_file(self, filename: str) -> None: @@ -177,6 +191,7 @@ def to_json_file(self, filename: str) -> None: ] data["codec"] = str(self.codec.value) data["test_vectors"] = [test_vector.data_to_serialize() for test_vector in self.test_vectors.values()] + data["test_method"] = self.test_method.value if self.test_method else None json.dump(data, json_file, indent=4) json_file.write("\n") @@ -503,6 +518,12 @@ def run(self, ctx: Context) -> Optional["TestSuite"]: print(f"Skipping decoder {ctx.decoder.name} because it cannot be run") return None + if self.test_method == TestMethod.PIXEL: + ctx.reference_decoder = get_reference_decoder_for_codec(ctx.decoder.codec) + if ctx.reference_decoder is None or not ctx.reference_decoder.check(ctx.verbose): + print(f"Skipping test suite {self.name}: no reference decoder for codec {ctx.decoder.codec.name}") + return None + ctx.output_dir = os.path.join(ctx.output_dir, self.name) if os.path.exists(ctx.output_dir): rmtree(ctx.output_dir) @@ -539,8 +560,9 @@ def run(self, ctx: Context) -> Optional["TestSuite"]: def generate_tests(self, ctx: Context) -> List[Test]: """Generate the tests for a decoder""" - tests = [] + tests: List[Test] = [] test_vectors_run = {} + for name, test_vector in self.test_vectors.items(): skip = False if ctx.test_vectors: @@ -549,19 +571,37 @@ def generate_tests(self, ctx: Context) -> List[Test]: if ctx.skip_vectors: if test_vector.name.lower() in ctx.skip_vectors: skip = True - tests.append( - Test( - ctx.decoder, - self, - test_vector, - skip, - ctx.output_dir, - ctx.reference, - ctx.timeout, - ctx.keep_files, - ctx.verbose, + + if self.test_method == TestMethod.PIXEL: + assert ctx.reference_decoder is not None + tests.append( + PixelComparisonTest( + ctx.decoder, + self, + test_vector, + skip, + ctx.output_dir, + ctx.reference, + ctx.timeout, + ctx.keep_files, + ctx.verbose, + ctx.reference_decoder, + ) + ) + else: + tests.append( + MD5ComparisonTest( + ctx.decoder, + self, + test_vector, + skip, + ctx.output_dir, + ctx.reference, + ctx.timeout, + ctx.keep_files, + ctx.verbose, + ) ) - ) test_vectors_run[name] = test_vector self.test_vectors = test_vectors_run return tests diff --git a/fluster/utils.py b/fluster/utils.py index 51e2fc41..f5aebd82 100644 --- a/fluster/utils.py +++ b/fluster/utils.py @@ -26,6 +26,7 @@ import time import urllib.request import zipfile +from functools import partial from threading import Lock from typing import List, Optional @@ -161,6 +162,41 @@ def normalize_path(path: str) -> str: return path +def compare_byte_wise_files( + reference_file: str, test_file: str, tolerance: int = 2, keep_files: bool = False, blocksize: int = 1024 +) -> int: + """ + Compares two binary files byte by byte with a given tolerance, reading in blocks. + """ + total_violations = 0 + + with open(reference_file, "rb") as ref_file, open(test_file, "rb") as test_file_obj: + ref_iter = iter(partial(ref_file.read, blocksize), b"") + test_iter = iter(partial(test_file_obj.read, blocksize), b"") + + for ref_block in ref_iter: + test_block = next(test_iter, None) + + if test_block is None: + raise ValueError("Test file is shorter than reference file") + + if len(ref_block) != len(test_block): + raise ValueError("File blocks do not match in size") + + for i in range(len(ref_block)): + diff = abs(ref_block[i] - test_block[i]) + if diff > tolerance: + total_violations += 1 + + if next(test_iter, None) is not None: + raise ValueError("Test file is longer than reference file") + + if not keep_files and os.path.isfile(test_file): + os.remove(test_file) + + return total_violations + + def find_by_ext(dest_dir: str, exts: List[str], excludes: Optional[List[str]] = None) -> Optional[str]: """Return name by file extension""" excludes = excludes or [] @@ -209,7 +245,6 @@ def interleave_pcm_files(pcm_files: List[str], output_filepath: str) -> None: 3. Back channels (b00-b0X) in numerical order 4. LFE channel (l00) if present """ - front_channels = [f for f in pcm_files if "_f" in os.path.basename(f).lower()] side_channels = [f for f in pcm_files if "_s" in os.path.basename(f).lower()] back_channels = [f for f in pcm_files if "_b" in os.path.basename(f).lower()] diff --git a/test_suites/mpeg2v/MPEG2_VIDEO-422.json b/test_suites/mpeg2v/MPEG2_VIDEO-422.json index eb59bd9b..702f655f 100644 --- a/test_suites/mpeg2v/MPEG2_VIDEO-422.json +++ b/test_suites/mpeg2v/MPEG2_VIDEO-422.json @@ -2,6 +2,7 @@ "name": "MPEG2_VIDEO-422", "codec": "MPEG2_VIDEO", "description": "ISO IEC 13818-4 MPEG2 video 422 profile test suite", + "test_method": "pixel", "test_vectors": [ { "name": "sony_422_id13-1", diff --git a/test_suites/mpeg2v/MPEG2_VIDEO-MAIN.json b/test_suites/mpeg2v/MPEG2_VIDEO-MAIN.json index 87c18623..238348c0 100644 --- a/test_suites/mpeg2v/MPEG2_VIDEO-MAIN.json +++ b/test_suites/mpeg2v/MPEG2_VIDEO-MAIN.json @@ -2,6 +2,7 @@ "name": "MPEG2_VIDEO-MAIN", "codec": "MPEG2_VIDEO", "description": "ISO IEC 13818-4 MPEG2 video main profile test suite", + "test_method": "pixel", "test_vectors": [ { "name": "att",