Skip to content

Commit a2aea32

Browse files
committed
feat: add reference decoders to soothe
Add decoders such as the h264, h265 and av1 reference decoders to decode content after encoding. This will help to validate that the encoder is the only changing brick for the pipeline. The decoders does not support Y4M as output format so use a YUV to Y4M converter to generate the files. It will use reference decoder from the fraunhofer institute for h26x and Open Media for AV1.
1 parent 1b66f74 commit a2aea32

File tree

18 files changed

+686
-39
lines changed

18 files changed

+686
-39
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
__pycache__/
22
*.py[cod]
3-
resources/
3+
resources/
4+
contrib/
5+
decoders/
6+
encoders/

Makefile

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
CONTRIB_DIR=contrib
2+
DECODERS_DIR=decoders
3+
SOOTHE=python3 ./soothe.py
4+
CMAKE_GENERATOR=Unix Makefiles
5+
6+
help:
7+
@awk -F ':|##' '/^[^\t].+?:.*?##/ { printf "\033[36m%-30s\033[0m %s\n", $$1, $$NF }' $(MAKEFILE_LIST)
8+
9+
10+
check: ## check that very basic tests run
11+
@echo "Running dummy test..."
12+
$(SOOTHE) list
13+
$(SOOTHE) list -c
14+
$(SOOTHE) download
15+
$(SOOTHE) run -e dummy
16+
17+
18+
create_dirs=mkdir -p $(CONTRIB_DIR) $(DECODERS_DIR)
19+
20+
all_reference_decoders: h264_reference_decoder h265_reference_decoder av1_reference_decoder vp9_reference_decoder ## build all reference decoders
21+
22+
h265_reference_decoder: ## build H.265 reference decoder
23+
$(create_dirs)
24+
cd $(CONTRIB_DIR) && git clone --branch=HM-18.0 https://vcgit.hhi.fraunhofer.de/jct-vc/HM.git --depth=1 || true
25+
cd $(CONTRIB_DIR)/HM && git stash && git pull && git stash apply || true
26+
cd $(CONTRIB_DIR)/HM && cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-Wno-array-bounds" && $(MAKE) -C build TAppDecoder
27+
find $(CONTRIB_DIR)/HM/bin/umake -name "TAppDecoder" -type f -exec cp {} $(DECODERS_DIR)/ \;
28+
29+
h264_reference_decoder: ## build H.264 reference decoder
30+
$(create_dirs)
31+
cd $(CONTRIB_DIR) && git clone --branch=JM-19.1 https://vcgit.hhi.fraunhofer.de/jct-vc/JM.git --depth=1 || true
32+
cd $(CONTRIB_DIR)/JM && git stash && git pull && git stash apply || true
33+
cd $(CONTRIB_DIR)/JM && cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-Wno-stringop-truncation -Wno-stringop-overflow" && $(MAKE) -C build ldecod
34+
find $(CONTRIB_DIR)/JM/bin/umake -name "ldecod" -type f -exec cp {} $(DECODERS_DIR)/ \;
35+
36+
av1_reference_decoder: ## build AV1 reference decoder
37+
$(create_dirs)
38+
cd $(CONTRIB_DIR) && git clone --branch=v3.12.1 https://aomedia.googlesource.com/aom --depth=1 || true
39+
cd $(CONTRIB_DIR)/aom && git stash && git pull && git stash apply || true
40+
cd $(CONTRIB_DIR)/aom && cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -Wno-stringop-overflow && $(MAKE) -j -C build aomdec
41+
find $(CONTRIB_DIR)/ -name "aomdec" -type f -exec cp {} $(DECODERS_DIR)/ \;
42+
43+
vp9_reference_decoder: ## build VP9 reference decoder
44+
$(create_dirs)
45+
cd $(CONTRIB_DIR) && git clone --branch=v1.15.2 https://chromium.googlesource.com/webm/libvpx --depth=1 || true
46+
cd $(CONTRIB_DIR)/libvpx && git stash && git pull && git stash apply || true
47+
cd $(CONTRIB_DIR)/libvpx && ./configure --disable-unit-tests --enable-vp9 && $(MAKE) -j
48+
find $(CONTRIB_DIR)/libvpx -name "vpxdec" -type f -exec cp {} $(DECODERS_DIR)/ \;
49+
50+
clean: ## remove contrib temporary folder
51+
rm -rf $(CONTRIB_DIR)
52+
53+
dbg-%:
54+
echo "Value of $* = $($*)"
55+
56+
.PHONY: help all_reference_decoders h264_reference_decoder h265_reference_decoder av1_reference_decoder vp9_reference_decoder \
57+
check install_deps clean

assets/media_xiph_org.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,33 @@
66
"name": "blue_sky",
77
"source": "https://media.xiph.org/video/derf/y4m/blue_sky_1080p25.y4m",
88
"checksum": "a4ef8836e546bbef4276346d0b86e81b",
9-
"filename": "blue_sky_1080p25.y4m"
9+
"filename": "blue_sky_1080p25.y4m",
10+
"width": 1920,
11+
"height": 1080
1012
},
1113
{
1214
"name": "four_people",
1315
"source": "https://media.xiph.org/video/derf/y4m/FourPeople_1280x720_60.y4m",
1416
"checksum": "83a3c203dc86fe8ecaa9010ad59f5757",
15-
"filename": "FourPeople_1280x720_60.y4m"
17+
"filename": "FourPeople_1280x720_60.y4m",
18+
"width": 1280,
19+
"height": 720
1620
},
1721
{
1822
"name": "riverbed",
1923
"source": "https://media.xiph.org/video/derf/y4m/riverbed_1080p25.y4m",
2024
"checksum": "9aeb08e3fe02e61d4a1b38be254ef293",
21-
"filename": "riverbed_1080p25.y4m"
25+
"filename": "riverbed_1080p25.y4m",
26+
"width": 1920,
27+
"height": 1080
2228
},
2329
{
2430
"name": "station2",
2531
"source": "https://media.xiph.org/video/derf/y4m/station2_1080p25.y4m",
2632
"checksum": "06be985bf3c2d986ecda3a331928da69",
27-
"filename": "station2_1080p25.y4m"
33+
"filename": "station2_1080p25.y4m",
34+
"width": 1920,
35+
"height": 1080
2836
}
2937
]
3038
}

soothe/asset.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,16 @@ def __init__(
3131
source: str,
3232
checksum: str,
3333
filename: str,
34+
width: int,
35+
height: int,
3436
):
3537
# JSON members
3638
self.name = name
3739
self.source = source
3840
self.checksum = checksum
3941
self.filename = filename
42+
self.width = width
43+
self.height = height
4044

4145
# Not in JSON
4246
self.test_time = 0.0

soothe/codec.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,34 @@ class Codec(Enum):
2929
DUMMY = "Dummy"
3030
H264 = "H.264"
3131
H265 = "H.265"
32-
H266 = "H.266"
33-
VP8 = "VP8"
3432
VP9 = "VP9"
3533
AV1 = "AV1"
36-
MPEG2_VIDEO = "MPEG2_VIDEO"
3734

3835
def __str__(self) -> str:
3936
return self.value
37+
38+
39+
class OutputFormat(Enum):
40+
"""Output format"""
41+
42+
NONE = "None"
43+
YUV420P = "yuv420p"
44+
YUV420P10LE = "yuv420p10le"
45+
YUV420P12LE = "yuv420p12le"
46+
YUV422P = "yuv422p"
47+
YUV422P10LE = "yuv422p10le"
48+
YUV422P12LE = "yuv422p12le"
49+
YUV444P = "yuv444p"
50+
YUV444P10LE = "yuv444p10le"
51+
YUV444P12LE = "yuv444p12le"
52+
YUV444P16LE = "yuv444p16le"
53+
GBRP = "gbrp"
54+
GBRP10LE = "gbrp10le"
55+
GBRP12LE = "gbrp12le"
56+
GBRP14LE = "gbrp14le"
57+
GRAY = "gray"
58+
GRAY10LE = "gray10le"
59+
GRAY12LE = "gray12le"
60+
GRAY16LE = "gray16le"
61+
UNKNOWN = "Unknown"
62+
FLTP = "fltp"

soothe/decoder.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Soothe - testing framework for encoders quality
2+
# Copyright (C) 2020, Fluendo, S.A.
3+
# Author: Pablo Marcos Oltra <[email protected]>, Fluendo, S.A.
4+
# Copyright (C) 2025, Igalia, S.L.
5+
# Author: Stéphane Cerveau <[email protected]>
6+
#
7+
# This library is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU Lesser General Public License
9+
# as published by the Free Software Foundation, either version 3
10+
# of the License, or (at your option) any later version.
11+
#
12+
# This library is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
# Lesser General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser General Public
18+
# License along with this library. If not, see <https://www.gnu.org/licenses/>.
19+
20+
"""Module with the decoder abstract class and the list of available decoders"""
21+
22+
23+
from abc import ABC, abstractmethod
24+
from functools import lru_cache
25+
from shutil import which
26+
from typing import List, Optional, Type
27+
28+
from .codec import Codec, OutputFormat
29+
from .utils import normalize_binary_cmd
30+
31+
32+
class Decoder(ABC):
33+
"""Base class for decoders"""
34+
35+
name = ""
36+
codec = Codec.NONE
37+
description = ""
38+
binary = ""
39+
is_reference = False
40+
outputs_y4m = False # True if decoder outputs Y4M, False for raw YUV
41+
42+
def __init__(self) -> None:
43+
if self.binary:
44+
self.binary = normalize_binary_cmd(self.binary)
45+
46+
@abstractmethod
47+
def decode(
48+
self,
49+
input_filepath: str,
50+
output_filepath: str,
51+
output_format: OutputFormat,
52+
timeout: int,
53+
verbose: bool,
54+
) -> str:
55+
"""Decodes input_filepath in output_filepath"""
56+
raise Exception("Not implemented")
57+
58+
@lru_cache(maxsize=128)
59+
def check(self, verbose: bool) -> bool:
60+
"""Checks whether the decoder can be run"""
61+
if hasattr(self, "binary") and self.binary:
62+
try:
63+
path = which(self.binary)
64+
if verbose and not path:
65+
print(f"Binary {self.binary} can't be found to be "
66+
f"executed")
67+
68+
return path is not None
69+
except Exception:
70+
return False
71+
return True
72+
73+
def __str__(self) -> str:
74+
return f" {self.name}: {self.description}"
75+
76+
77+
DECODERS: List[Decoder] = []
78+
79+
80+
def register_decoder(cls: Type[Decoder]) -> Type[Decoder]:
81+
"""Register a new decoder implementation"""
82+
DECODERS.append(cls())
83+
DECODERS.sort(key=lambda dec: dec.name)
84+
return cls
85+
86+
87+
def get_reference_decoder_for_codec(codec: Codec) -> Optional["Decoder"]:
88+
"""Find the reference decoder for a specific codec"""
89+
90+
reference_decoders = [d for d in DECODERS if d.codec == codec and
91+
d.is_reference]
92+
93+
if not reference_decoders:
94+
return None
95+
if len(reference_decoders) > 1:
96+
print(f"Multiple reference decoders found for codec {codec.name}")
97+
98+
return reference_decoders[0]

soothe/decoders/__init__.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Soothe - testing framework for encoders quality
2+
# Copyright (C) 2020, Fluendo, S.A.
3+
# Author: Pablo Marcos Oltra <[email protected]>, Fluendo, S.A.
4+
# Copyright (C) 2025, Igalia, S.L.
5+
# Author: Stéphane Cerveau <[email protected]>
6+
#
7+
# This library is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU Lesser General Public License
9+
# as published by the Free Software Foundation, either version 3
10+
# of the License, or (at your option) any later version.
11+
#
12+
# This library is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
# Lesser General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser General Public
18+
# License along with this library. If not, see <https://www.gnu.org/licenses/>.
19+
20+
"""List of decoders"""
21+
22+
import glob
23+
import os.path
24+
25+
modules = glob.glob(os.path.join(os.path.dirname(__file__), "*.py"))
26+
__all__ = [
27+
os.path.basename(os.path.splitext(f)[0]) for f in modules
28+
if os.path.isfile(f) and not f.endswith("__init__.py")
29+
]

soothe/decoders/av1_aom.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Soothe - testing framework for encoders quality
2+
# Copyright (C) 2020, Fluendo, S.A.
3+
# Author: Pablo Marcos Oltra <[email protected]>, Fluendo, S.A.
4+
# Copyright (C) 2025, Igalia, S.L.
5+
# Author: Stéphane Cerveau <[email protected]>
6+
#
7+
# This library is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU Lesser General Public License
9+
# as published by the Free Software Foundation, either version 3
10+
# of the License, or (at your option) any later version.
11+
#
12+
# This library is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
# Lesser General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser General Public
18+
# License along with this library. If not, see <https://www.gnu.org/licenses/>.
19+
20+
from ..codec import Codec, OutputFormat
21+
from ..decoder import Decoder, register_decoder
22+
from ..utils import file_checksum, run_command
23+
24+
25+
@register_decoder
26+
class AV1AOMDecoder(Decoder):
27+
"""libaom AV1 reference decoder implementation"""
28+
29+
name = "libaom-AV1"
30+
description = "libaom AV1 reference decoder"
31+
binary = "aomdec"
32+
codec = Codec.AV1
33+
is_reference = True
34+
multiple_layers = False
35+
annexb = False
36+
37+
def decode(
38+
self,
39+
input_filepath: str,
40+
output_filepath: str,
41+
output_format: OutputFormat,
42+
timeout: int,
43+
verbose: bool,
44+
) -> str:
45+
"""Decodes input_filepath in output_filepath"""
46+
fmt = "--rawvideo"
47+
if not self.multiple_layers and not self.annexb:
48+
if output_format in [OutputFormat.YUV420P,
49+
OutputFormat.YUV420P10LE]:
50+
fmt = "--i420"
51+
52+
cmd = [
53+
self.binary,
54+
"--annexb" if self.annexb else "",
55+
"--all-layers" if self.multiple_layers else "",
56+
fmt,
57+
input_filepath,
58+
"-o",
59+
output_filepath,
60+
]
61+
62+
cmd = [arg for arg in cmd if arg]
63+
run_command(cmd, timeout=timeout, verbose=verbose)
64+
return file_checksum(output_filepath)

0 commit comments

Comments
 (0)