From 959bf21e719314d88028cedb91bea490ede47bfc Mon Sep 17 00:00:00 2001 From: rsanchez Date: Fri, 23 Oct 2026 14:09:27 +0200 Subject: [PATCH] feat: build the MPEG-4 Visual reference decoder (vm_dec) Enabled by default like the other reference decoders (-Dmpeg4_video_decoder=false to disable). download_deps.py fetches the ISO sources and adapts them to current compilers; no ISO code is committed. Built in CI on Linux (x86_64, ARM64), macOS (ARM64) and Windows (MSYS2) --- .gitignore | 1 + README.md | 2 +- build/compat/mpeg4video/compat.h | 56 ++++++ build/compat/mpeg4video/fstream.h | 2 + build/compat/mpeg4video/iomanip.h | 2 + build/compat/mpeg4video/ios.h | 2 + build/compat/mpeg4video/iostream.h | 2 + build/compat/mpeg4video/istream.h | 2 + build/compat/mpeg4video/ostream.h | 2 + build/compat/mpeg4video/strstream.h | 2 + build/download_deps.py | 82 ++++++++- meson.build | 36 ++++ meson_options.txt | 2 + subprojects/mpeg4video.wrap | 6 + .../packagefiles/mpeg4video/meson.build | 162 ++++++++++++++++++ 15 files changed, 358 insertions(+), 3 deletions(-) create mode 100644 build/compat/mpeg4video/compat.h create mode 100644 build/compat/mpeg4video/fstream.h create mode 100644 build/compat/mpeg4video/iomanip.h create mode 100644 build/compat/mpeg4video/ios.h create mode 100644 build/compat/mpeg4video/iostream.h create mode 100644 build/compat/mpeg4video/istream.h create mode 100644 build/compat/mpeg4video/ostream.h create mode 100644 build/compat/mpeg4video/strstream.h create mode 100644 subprojects/mpeg4video.wrap create mode 100644 subprojects/packagefiles/mpeg4video/meson.build diff --git a/.gitignore b/.gitignore index c27dd533..947a7ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ subprojects/jm/ subprojects/libtsp-v7r0 subprojects/mpeg2aac subprojects/mpeg2video +subprojects/mpeg4video subprojects/vvcsoftware_vtm/ subprojects/vvdec/ diff --git a/README.md b/README.md index 12b0a15d..cec87fe5 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ For complete setup, usage examples, hardware acceleration configuration, and tro - `./fluster.py download` downloads all available test suites 2. (Optional) Build the reference decoders for AAC, H.264/AVC, H.265/HEVC, - H.266/VVC. First, download the ISO reference software: + H.266/VVC, MPEG-4 Visual. First, download the ISO reference software: ```bash python3 build/download_deps.py diff --git a/build/compat/mpeg4video/compat.h b/build/compat/mpeg4video/compat.h new file mode 100644 index 00000000..ff7626e1 --- /dev/null +++ b/build/compat/mpeg4video/compat.h @@ -0,0 +1,56 @@ +/* + * Fluster - testing framework for decoders conformance + * Copyright (C) 2026, Fluendo, S.A. + * + * Force-included (-include) when building the MPEG-4 Visual reference software (vm_dec): + * its pre-standard C++ uses iostream names without the std:: prefix. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CVideoObjectDecoder; +class CVideoObjectDecoderTPS; +class CEnhcBufferEncoder; + +using std::cerr; +using std::cin; +using std::cout; +using std::dec; +using std::endl; +using std::ends; +using std::fixed; +using std::flush; +using std::fstream; +using std::hex; +using std::ifstream; +using std::ios; +using std::iostream; +using std::istream; +using std::istrstream; +using std::left; +using std::ofstream; +using std::oct; +using std::ostream; +using std::ostrstream; +using std::right; +using std::scientific; +using std::setfill; +using std::setprecision; +using std::setw; +using std::streambuf; +using std::streamoff; +using std::streampos; +using std::strstream; +using std::strstreambuf; +using std::ws; diff --git a/build/compat/mpeg4video/fstream.h b/build/compat/mpeg4video/fstream.h new file mode 100644 index 00000000..ce336020 --- /dev/null +++ b/build/compat/mpeg4video/fstream.h @@ -0,0 +1,2 @@ +/* Fluster: replacement for the pre-standard used by the MPEG-4 Visual reference software */ +#include diff --git a/build/compat/mpeg4video/iomanip.h b/build/compat/mpeg4video/iomanip.h new file mode 100644 index 00000000..38ac20f2 --- /dev/null +++ b/build/compat/mpeg4video/iomanip.h @@ -0,0 +1,2 @@ +/* Fluster: replacement for the pre-standard used by the MPEG-4 Visual reference software */ +#include diff --git a/build/compat/mpeg4video/ios.h b/build/compat/mpeg4video/ios.h new file mode 100644 index 00000000..6933d103 --- /dev/null +++ b/build/compat/mpeg4video/ios.h @@ -0,0 +1,2 @@ +/* Fluster: replacement for the pre-standard used by the MPEG-4 Visual reference software */ +#include diff --git a/build/compat/mpeg4video/iostream.h b/build/compat/mpeg4video/iostream.h new file mode 100644 index 00000000..a60bc888 --- /dev/null +++ b/build/compat/mpeg4video/iostream.h @@ -0,0 +1,2 @@ +/* Fluster: replacement for the pre-standard used by the MPEG-4 Visual reference software */ +#include diff --git a/build/compat/mpeg4video/istream.h b/build/compat/mpeg4video/istream.h new file mode 100644 index 00000000..80250d4d --- /dev/null +++ b/build/compat/mpeg4video/istream.h @@ -0,0 +1,2 @@ +/* Fluster: replacement for the pre-standard used by the MPEG-4 Visual reference software */ +#include diff --git a/build/compat/mpeg4video/ostream.h b/build/compat/mpeg4video/ostream.h new file mode 100644 index 00000000..2c298037 --- /dev/null +++ b/build/compat/mpeg4video/ostream.h @@ -0,0 +1,2 @@ +/* Fluster: replacement for the pre-standard used by the MPEG-4 Visual reference software */ +#include diff --git a/build/compat/mpeg4video/strstream.h b/build/compat/mpeg4video/strstream.h new file mode 100644 index 00000000..e460a414 --- /dev/null +++ b/build/compat/mpeg4video/strstream.h @@ -0,0 +1,2 @@ +/* Fluster: replacement for the pre-standard used by the MPEG-4 Visual reference software */ +#include diff --git a/build/download_deps.py b/build/download_deps.py index ba8da983..566a5bc0 100755 --- a/build/download_deps.py +++ b/build/download_deps.py @@ -3,7 +3,7 @@ # Copyright (C) 2025, Fluendo, S.A. # # Helper script to download all ISO reference decoder dependencies -# Downloads: MPEG-2 AAC, MPEG-2 video, MPEG-4 AAC, and libtsp +# Downloads: MPEG-2 AAC, MPEG-2 video, MPEG-4 AAC, MPEG-H, MPEG-4 Visual, and libtsp import argparse import hashlib @@ -172,6 +172,77 @@ def download_mpegh(contrib_dir): spatial_decoder.write_text(re.sub(r"^[ \t]*#pragma (end)?region.*\n", "", source, flags=re.M), encoding="utf-8") +# Forward declarations that would clash with the std:: names brought in by the compat header +MPEG4_VIDEO_FORWARD_DECLARATION = re.compile( + rb"^[ \t]*class[ \t]+(istream|ostream|ifstream|strstreambuf)[ \t]*;[ \t]*\r?\n", re.M +) +# "ios::bin" is the pre-standard spelling of "ios::binary" +MPEG4_VIDEO_IOS_BIN = re.compile(rb"\bios::bin\b") + + +def adapt_mpeg4_video_sources(source_dir): + """Adapt the MPEG-4 Visual reference software, which predates C++98, to build with current compilers""" + for path in source_dir.rglob("*"): + if not path.is_file() or path.suffix not in (".c", ".cpp", ".h", ".hpp"): + continue + original = path.read_bytes() + adapted = MPEG4_VIDEO_IOS_BIN.sub(b"ios::binary", MPEG4_VIDEO_FORWARD_DECLARATION.sub(b"", original)) + if adapted != original: + path.write_bytes(adapted) + + +def download_mpeg4_video(contrib_dir): + """Download the MPEG-4 Visual (ISO/IEC 14496-5) reference software, which builds vm_dec""" + print() + print("=== Downloading MPEG-4 Visual reference software ===") + + mpeg4_video_dir = contrib_dir / "MPEG-4_video_refSoft" + mpeg4_video_zip = contrib_dir / "mpeg4_video_refsoft.zip" + mpeg4_video_url = ( + "https://standards.iso.org/ittf/PubliclyAvailableStandards/" + "c039392_ISO_IEC_14496-5_2001_Amd%205_2004(E)_Reference_Software.zip" + ) + # The zip holds several implementations, only this one produces vm_dec + implementation = "microsoft-v2.5-040207-NTU/" + + if mpeg4_video_dir.exists(): + print(f"Skipping download of MPEG-4 Visual: {mpeg4_video_dir} already exists") + # Re-apply the source rewrites, in case the tree was extracted before they were added + adapt_mpeg4_video_sources(mpeg4_video_dir) + return + + # Get cookies first, then download with cookies and form data + cookie_jar = CookieJar() + build_opener(HTTPCookieProcessor(cookie_jar)).open(mpeg4_video_url) + download_with_cookies(mpeg4_video_url, mpeg4_video_zip, cookie_jar) + if not zipfile.is_zipfile(mpeg4_video_zip): + print(f"Error: {mpeg4_video_zip.name} is not a zip file, the download was probably refused") + sys.exit(1) + + print(f"Extracting {implementation} from {mpeg4_video_zip.name}...") + # Extract into a temporary directory renamed once complete, so that an interrupted run + # never leaves a partial MPEG-4_video_refSoft behind + tmp_dir = contrib_dir / f".{mpeg4_video_dir.name}.tmp" + shutil.rmtree(tmp_dir, ignore_errors=True) + with zipfile.ZipFile(mpeg4_video_zip) as zip_ref: + for member in zip_ref.infolist(): + _, found, relative_path = member.filename.partition(implementation) + if not found or not relative_path or ".." in Path(relative_path).parts: + continue + target = tmp_dir / relative_path + if member.is_dir(): + target.mkdir(parents=True, exist_ok=True) + else: + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(zip_ref.read(member)) + mpeg4_video_zip.unlink() + if not tmp_dir.exists(): + print(f"Error: {implementation} not found in the downloaded MPEG-4 Visual reference software") + sys.exit(1) + adapt_mpeg4_video_sources(tmp_dir) + tmp_dir.rename(mpeg4_video_dir) + + def download_libtsp(contrib_dir): """Download libtsp audio library""" print() @@ -269,7 +340,8 @@ def prompt_iso_license(auto_accept=False): def main(): parser = argparse.ArgumentParser( - description="Download ISO reference decoder dependencies (MPEG-2 AAC, MPEG-2 video, MPEG-4 AAC, MPEG-H, libtsp)" + description="Download ISO reference decoder dependencies " + "(MPEG-2 AAC, MPEG-2 video, MPEG-4 AAC, MPEG-H, MPEG-4 Visual, libtsp)" ) parser.add_argument( "dest_dir", @@ -300,6 +372,7 @@ def main(): print(" - MPEG-2 video decoder") print(" - MPEG-4 AAC decoders") print(" - MPEG-H 3D Audio decoder") + print(" - MPEG-4 Visual decoder") print(" - libtsp audio library") print() @@ -314,6 +387,7 @@ def main(): download_mpeg2(contrib_dir) download_mpeg4(contrib_dir) download_mpegh(contrib_dir) + download_mpeg4_video(contrib_dir) download_libtsp(contrib_dir) # Copy meson.build files to contrib directories @@ -335,6 +409,9 @@ def main(): print("Copying meson.build for libtsp") shutil.copy2(packagefiles_dir / "libtsp" / "meson.build", contrib_dir / "libtsp-v7r0" / "meson.build") + print("Copying meson.build for MPEG-4 Visual") + shutil.copy2(packagefiles_dir / "mpeg4video" / "meson.build", contrib_dir / "MPEG-4_video_refSoft" / "meson.build") + # Create symlinks for meson subprojects print() print("=== Creating symlinks for meson subprojects ===") @@ -342,6 +419,7 @@ def main(): symlinks = [ ("mpeg2video", "../contrib/video"), ("mpeg2aac", "../contrib/mpeg2aac"), + ("mpeg4video", "../contrib/MPEG-4_video_refSoft"), ("libtsp-v7r0", "../contrib/libtsp-v7r0"), ] diff --git a/meson.build b/meson.build index 92dd23a5..71a5de39 100644 --- a/meson.build +++ b/meson.build @@ -46,6 +46,7 @@ build_mpeg4_aac = get_option('mpeg4_aac_decoder') build_mpeg4_aac_er = get_option('mpeg4_aac_er_decoder') build_mpeg2_video = get_option('mpeg2_video_decoder') build_mpegh = get_option('mpegh_decoder') +build_mpeg4_video = get_option('mpeg4_video_decoder') missing_deps = [] @@ -73,6 +74,13 @@ if build_mpegh endif endif +# Check MPEG-4 Visual dependency +if build_mpeg4_video + if not fs.is_dir(contrib_dir / 'MPEG-4_video_refSoft') + missing_deps += ['contrib/MPEG-4_video_refSoft (MPEG-4 Visual decoder)'] + endif +endif + # Check libtsp dependency (needed for all AAC decoders) if build_mpeg2_aac or build_mpeg4_aac or build_mpeg4_aac_er if not fs.is_dir(contrib_dir / 'libtsp-v7r0') @@ -242,6 +250,23 @@ if build_mpeg2_aac endif endif +# MPEG-4 Visual Reference Decoder (ISO/IEC 14496-5) +mpeg4video_tgt = disabler() +if build_mpeg4_video + # Check if the mpeg4video subproject directory exists (symlinked from contrib/MPEG-4_video_refSoft) + mpeg4video_dir = meson.current_source_dir() / 'subprojects' / 'mpeg4video' + if fs.is_dir(mpeg4video_dir) or fs.is_symlink(mpeg4video_dir) + mpeg4video_subproject = subproject('mpeg4video', required: false) + if mpeg4video_subproject.found() + mpeg4video_tgt = mpeg4video_subproject.get_variable('vm_dec') + else + warning('MPEG-4 Visual decoder subproject configuration failed') + endif + else + error('MPEG-4 Visual decoder: subprojects/mpeg4video symlink not found. Run: python3 build/download_deps.py') + endif +endif + # Extract compiler flags from meson's C compiler configuration # This ensures ISO decoders use the same architecture as the main build c_args_list = get_option('c_args') @@ -382,6 +407,16 @@ if not is_disabler(aacdec_mc_tgt) ) endif +# MPEG-4 Visual decoder symlink (vm_dec) +if not is_disabler(mpeg4video_tgt) + custom_target('link-vm_dec', + output: 'vm_dec.link', + input: mpeg4video_tgt, + command: ['sh', '-c', link_script, '_', meson.current_build_dir(), '@INPUT@', decoders_dir / 'vm_dec'], + build_by_default: true, + ) +endif + run_target('clean-contrib', command: ['rm', '-rf', contrib_dir], ) @@ -408,6 +443,7 @@ summary({ 'MPEG-2 video decoder': build_mpeg2_video, 'MPEG-2 AAC decoder': build_mpeg2_aac, 'MPEG-H 3D Audio decoder': build_mpegh, + 'MPEG-4 Visual decoder': build_mpeg4_video, 'isobmff library': build_isobmff, }, section: 'Meson Subproject Decoders') diff --git a/meson_options.txt b/meson_options.txt index 9dfc7f01..d6a8fce5 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -15,6 +15,8 @@ option('mpeg4_aac_decoder', type: 'boolean', value: true, description: 'Build MPEG-4 AAC reference decoder') option('mpeg4_aac_er_decoder', type: 'boolean', value: true, description: 'Build MPEG-4 AAC Error Resilient reference decoder') +option('mpeg4_video_decoder', type: 'boolean', value: true, + description: 'Build MPEG-4 Visual reference decoder (ISO/IEC 14496-5, vm_dec)') # MPEG-H 3D Audio reference decoder (disabled by default, needs build/download_deps.py) option('mpegh_decoder', type: 'boolean', value: false, diff --git a/subprojects/mpeg4video.wrap b/subprojects/mpeg4video.wrap new file mode 100644 index 00000000..8a46de63 --- /dev/null +++ b/subprojects/mpeg4video.wrap @@ -0,0 +1,6 @@ +[wrap-file] +directory = mpeg4video +patch_directory = mpeg4video + +[provide] +program_names = vm_dec diff --git a/subprojects/packagefiles/mpeg4video/meson.build b/subprojects/packagefiles/mpeg4video/meson.build new file mode 100644 index 00000000..592d5f9e --- /dev/null +++ b/subprojects/packagefiles/mpeg4video/meson.build @@ -0,0 +1,162 @@ +# Native meson.build for the MPEG-4 Visual reference decoder (vm_dec) +# ISO/IEC 14496-5 Reference Software +# This replaces the original Makefile to integrate with the meson build system +# +# This file is copied to contrib/MPEG-4_video_refSoft/ by build/download_deps.py + +project('mpeg4video', 'cpp', + version: '2.5', + default_options: [ + 'cpp_std=gnu++98', + 'buildtype=release', + 'warning_level=0', + ] +) + +cc = meson.get_compiler('cpp') + +# The reference software predates C++98: the pre-standard iostream headers and the names used +# without the std:: prefix are provided by Fluster's compatibility headers +compat_dir = meson.current_source_dir() / '..' / '..' / 'build' / 'compat' / 'mpeg4video' + +vm_dec_cpp_args = [ + '-w', + # Only the decoder is needed, with the features its Makefile enables. _VTC_DECODER_ is only + # used by vtcdec.cpp, which defines the VTC globals, but the Makefile builds it separately + '-D_VTC_DECODER_', + '-D_ERSSP_', + '-D__DOUBLE_PRECISION_', + '-D_OBSS_', + '-DFGSPOSTCLIP', + '-I' + compat_dir, + '-include', compat_dir / 'compat.h', +] + +if cc.get_id() == 'clang' + # What GCC accepts with -fpermissive is an error for clang, unless it is told that it is Microsoft code + vm_dec_cpp_args += '-fms-extensions' +else + vm_dec_cpp_args += ['-fpermissive', '-fno-operator-names'] +endif + +if host_machine.system() == 'darwin' + # macOS has no malloc.h + vm_dec_cpp_args += '-I' + meson.current_source_dir() / '..' / '..' / 'build' / 'compat' / 'macos' +endif + +# Sources of the vm_dec target in the original Makefile +vm_dec_sources = files( + 'app/decoder/decoder.cpp', + 'sys/block.cpp', + 'sys/cae.cpp', + 'sys/dct.cpp', + 'sys/decoder/blkdec.cpp', + 'sys/decoder/bp_fgs_dec.cpp', + 'sys/decoder/errdec.cpp', + 'sys/decoder/mbdec.cpp', + 'sys/decoder/mbheaddec.cpp', + 'sys/decoder/mbinterlacedec.cpp', + 'sys/decoder/mcdec.cpp', + 'sys/decoder/mvdec.cpp', + 'sys/decoder/newpdec.cpp', + 'sys/decoder/rvlcdec.cpp', + 'sys/decoder/shpdec.cpp', + 'sys/decoder/sptdec.cpp', + 'sys/decoder/vopmbdec.cpp', + 'sys/decoder/vopsedec.cpp', + 'sys/error.cpp', + 'sys/gmc_motion.cpp', + 'sys/gmc_util.cpp', + 'sys/mb.cpp', + 'sys/mc.cpp', + 'sys/mcpad.cpp', + 'sys/mode.cpp', + 'sys/mv.cpp', + 'sys/newpred.cpp', + 'sys/rrv.cpp', + 'sys/shape.cpp', + 'sys/spt.cpp', + 'sys/tps_bfshape.cpp', + 'sys/tps_enhcbuf.cpp', + 'sys/vopses.cpp', + 'tools/entropy/bitstrm.cpp', + 'tools/entropy/huffman.cpp', + 'tools/sadct/sadct.cpp', + 'type/basic.cpp', + 'type/geom.cpp', + 'type/grayc.cpp', + 'type/grayf.cpp', + 'type/grayi.cpp', + 'type/svd.cpp', + 'type/typeapi.cpp', + 'type/vop.cpp', + 'type/warp.cpp', + 'type/yuvac.cpp', + 'type/yuvai.cpp', + 'vtc/main/seg.cpp', + 'vtc/main/vtcdec.cpp', + 'vtc/main/wavelet.cpp', + 'vtc/main/write_image.cpp', + 'vtc/pezw/PEZW_ac.cpp', + 'vtc/pezw/PEZW_globals.cpp', + 'vtc/pezw/PEZW_textureLayerBQ.cpp', + 'vtc/pezw/PEZW_utils.cpp', + 'vtc/pezw/wvtPEZW.cpp', + 'vtc/pezw/wvtpezw_tree_decode.cpp', + 'vtc/pezw/wvtpezw_tree_encode.cpp', + 'vtc/pezw/wvtpezw_tree_init_decode.cpp', + 'vtc/pezw/wvtpezw_tree_init_encode.cpp', + 'vtc/shape/BinArCodec.cpp', + 'vtc/shape/ShapeBaseCommon.cpp', + 'vtc/shape/ShapeBaseDecode.cpp', + 'vtc/shape/ShapeBaseEncode.cpp', + 'vtc/shape/ShapeDecoding.cpp', + 'vtc/shape/ShapeEncoding.cpp', + 'vtc/shape/ShapeEnhCommon.cpp', + 'vtc/shape/ShapeEnhDecode.cpp', + 'vtc/shape/ShapeEnhEncode.cpp', + 'vtc/shape/ShapeUtil.cpp', + 'vtc/wavelet/download_filter.cpp', + 'vtc/wavelet/dwt.cpp', + 'vtc/wavelet/dwt_aux.cpp', + 'vtc/wavelet/dwtmask.cpp', + 'vtc/wavelet/idwt.cpp', + 'vtc/wavelet/idwt_aux.cpp', + 'vtc/wavelet/idwtmask.cpp', + 'vtc/wavelet/imagebox.cpp', + 'vtc/zte/ac.cpp', + 'vtc/zte/bitpack.cpp', + 'vtc/zte/ztscanUtil.cpp', + 'vtc/zte/ztscan_dec.cpp', + 'vtc/ztq/QMInit.cpp', + 'vtc/ztq/QMUtils.cpp', + 'vtc/ztq/Utils.cpp', + 'vtc/ztq/decQM.cpp', + 'vtc/ztq/errorHandler.cpp', + 'vtc/ztq/msg.cpp', + 'vtc/ztq/quant.cpp', +) + +# Math library +m_dep = cc.find_library('m', required: false) + +vm_dec = executable('vm_dec', + vm_dec_sources, + include_directories: include_directories( + 'sys', + 'sys/encoder', + 'sys/decoder', + 'type', + 'tools', + 'tools/entropy', + 'vtc/include', + 'vtc/ztq', + 'vtc/zte', + 'vtc/wavelet', + 'vtc/pezw', + 'vtc/shape', + ), + cpp_args: vm_dec_cpp_args, + dependencies: [m_dep], + install: false, +)