media-converter: Remove media-converter.

Remove it since it has been integrated into winegstreamer.

CW-Bug-Id: #23225
This commit is contained in:
Ziqing Hui 2024-01-17 16:05:11 +08:00 committed by Arkadiusz Hiler
parent d72fb93507
commit da36c977f9
19 changed files with 0 additions and 4547 deletions

View file

@ -595,27 +595,6 @@ $(OBJ)/.vkd3d-proton-post-build64:
##
## mediaconv
##
MEDIACONV_DEPENDS = gst_orc gstreamer gst_base
$(eval $(call rules-source,mediaconv,$(SRCDIR)/media-converter))
$(eval $(call rules-cargo,mediaconv,32))
$(eval $(call rules-cargo,mediaconv,64))
$(OBJ)/.mediaconv-post-build64:
mkdir -p $(MEDIACONV_DST64)/lib64/gstreamer-1.0/
cp -a $(MEDIACONV_OBJ64)/x86_64-unknown-linux-gnu/release/libprotonmediaconverter.so $(MEDIACONV_DST64)/lib64/gstreamer-1.0/
touch $@
$(OBJ)/.mediaconv-post-build32:
mkdir -p $(MEDIACONV_DST32)/lib/gstreamer-1.0/
cp -a $(MEDIACONV_OBJ32)/i686-unknown-linux-gnu/release/libprotonmediaconverter.so $(MEDIACONV_DST32)/lib/gstreamer-1.0/
touch $@
## ##
## BattlEye Bridge ## BattlEye Bridge
## ##

View file

@ -1,791 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
[[package]]
name = "array-init"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb6d71005dc22a708c7496eee5c8dc0300ee47355de6256c3b35b12b5fef596"
[[package]]
name = "atomic_refcell"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b"
[[package]]
name = "cfg-expr"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa"
dependencies = [
"smallvec",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [
"iana-time-zone",
"num-integer",
"num-traits",
"winapi",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "filetime"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"windows-sys",
]
[[package]]
name = "futures-channel"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
[[package]]
name = "futures-executor"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-macro"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-task"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
[[package]]
name = "futures-util"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
dependencies = [
"futures-core",
"futures-macro",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "gio-sys"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6da1bba9d3f2ab13a6e9932c40f240dc99ebc9f0bdc35cfb130d1a3df36f374c"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"winapi",
]
[[package]]
name = "glib"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5abffa711471e015eb93d65d6ea20e7e9f6f7951fc0a1042280439319b2de06"
dependencies = [
"bitflags",
"futures-channel",
"futures-core",
"futures-executor",
"futures-task",
"futures-util",
"gio-sys",
"glib-macros",
"glib-sys",
"gobject-sys",
"libc",
"once_cell",
"smallvec",
"thiserror",
]
[[package]]
name = "glib-macros"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e195c1311fa6b04d7b896ea39385f6bd60ef5d25bf74a7c11c8c3f94f6c1a572"
dependencies = [
"anyhow",
"heck",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "glib-sys"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b33357bb421a77bd849f6a0bfcaf3b4b256a2577802971bb5dd522d530f27021"
dependencies = [
"libc",
"system-deps",
]
[[package]]
name = "gobject-sys"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63ca11a57400f3d4fda594e002844be47900c9fb8b29e2155c6e37a1f24e51b3"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "gst-plugin-version-helper"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747ffe0e4067acfb98d6f7cbbe0a1901794587a93ab2b36c4652bc75c28d865d"
dependencies = [
"chrono",
]
[[package]]
name = "gstreamer"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e428081934c617115320750b7827f8f13131d9c3ae90b647c14a5d6019f47b4"
dependencies = [
"bitflags",
"cfg-if",
"futures-channel",
"futures-core",
"futures-util",
"glib",
"gstreamer-sys",
"libc",
"muldiv",
"num-integer",
"num-rational",
"once_cell",
"option-operations",
"paste",
"pretty-hex",
"thiserror",
]
[[package]]
name = "gstreamer-audio"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "affbf8dd22eb301f21a3ae659358a6e069850b35cab6522d40738c9500f85b17"
dependencies = [
"bitflags",
"cfg-if",
"glib",
"gstreamer",
"gstreamer-audio-sys",
"gstreamer-base",
"libc",
"once_cell",
]
[[package]]
name = "gstreamer-audio-sys"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6d6a3ad336150faf2125e29ac025b1fa152dca08b4cb2496f1e7d9c83b51e8b"
dependencies = [
"glib-sys",
"gobject-sys",
"gstreamer-base-sys",
"gstreamer-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-base"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "326674197c010e91a98d0f55a032abe22b1fd932456dbcdc3415450b4b653817"
dependencies = [
"atomic_refcell",
"bitflags",
"cfg-if",
"glib",
"gstreamer",
"gstreamer-base-sys",
"libc",
]
[[package]]
name = "gstreamer-base-sys"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd55d3858fa65a99286c1cbe8db001f4ce5cff6a038f1c1253f5d99f840970de"
dependencies = [
"glib-sys",
"gobject-sys",
"gstreamer-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-sys"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbaafc66df32b334d4aa28025fd5d83cadc971e1910205e140ea070f4ac4834f"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-video"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9b96daff8a3d853588e61207afac81a4879f3972430f6609721601ab757d7fd"
dependencies = [
"bitflags",
"cfg-if",
"futures-channel",
"glib",
"gstreamer",
"gstreamer-base",
"gstreamer-video-sys",
"libc",
"once_cell",
]
[[package]]
name = "gstreamer-video-sys"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "066ee44cd8d84f19a18c646128c1890878c034d3fb9f34d8d5f07311bbd9f41f"
dependencies = [
"glib-sys",
"gobject-sys",
"gstreamer-base-sys",
"gstreamer-sys",
"libc",
"system-deps",
]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "iana-time-zone"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "js-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "muldiv"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5136edda114182728ccdedb9f5eda882781f35fa6e80cc360af12a8932507f3"
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
[[package]]
name = "option-operations"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0"
dependencies = [
"paste",
]
[[package]]
name = "paste"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "pretty-hex"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5"
[[package]]
name = "proc-macro-crate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
dependencies = [
"once_cell",
"thiserror",
"toml",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "proton-media-converter"
version = "7.0.0"
dependencies = [
"array-init",
"crc32fast",
"filetime",
"glib",
"gst-plugin-version-helper",
"gstreamer",
"gstreamer-audio",
"gstreamer-base",
"gstreamer-video",
"once_cell",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "serde"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
[[package]]
name = "slab"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
[[package]]
name = "syn"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "system-deps"
version = "6.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml",
"version-compare",
]
[[package]]
name = "thiserror"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]]
name = "unicode-ident"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
[[package]]
name = "version-compare"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"

View file

@ -1,47 +0,0 @@
[package]
name = "proton-media-converter"
version = "7.0.0"
authors = ["Andrew Eikum <aeikum@codeweavers.com>"]
repository = "https://github.com/ValveSoftware/Proton/"
license = "MIT/Apache-2.0"
edition = "2021"
description = "Proton media converter"
[dependencies]
glib = "0.16"
gstreamer = "0.19.1"
# the versions are not in sync, the submodules below haven't seen any changes
# since 0.19.0 release so .1 release haven't happened for them
gstreamer-base = "0.19.1"
gstreamer-video = "0.19.0"
gstreamer-audio = "0.19.0"
once_cell = "1.9"
crc32fast = "1.3"
array-init = "2.0"
filetime = "0.2"
[lib]
name = "protonmediaconverter"
crate-type = ["cdylib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = "0.7.4"
[profile.release]
lto = true
opt-level = 3
debug = false
panic = 'unwind'
[profile.dev]
opt-level = 1
#if you need local modifications to gstreamer-rs you can point to it here
#[patch.'https://github.com/sdroege/gstreamer-rs']
#gstreamer = { path = "../gstreamer-rs/gstreamer" }
#gstreamer-base = { path = "../gstreamer-rs/gstreamer-base" }
#gstreamer-video = { path = "../gstreamer-rs/gstreamer-video" }
#gstreamer-audio = { path = "../gstreamer-rs/gstreamer-audio" }

View file

@ -1,38 +0,0 @@
SONAME := libprotonmediaconverter.so
TRIPLE64 := x86_64-unknown-linux-gnu
TRIPLE32 := i686-unknown-linux-gnu
DESTDIR ?= dist/
DIST_GST_DIR64 := $(DESTDIR)/lib64/gstreamer-1.0/
DIST_GST_DIR32 := $(DESTDIR)/lib/gstreamer-1.0/
ifeq ($(DEBUG),)
CARGO_RELEASE_ARG := --release
TARGET_BUILD_TYPE := release
else
CARGO_RELEASE_ARG :=
TARGET_BUILD_TYPE := debug
endif
all: install
build: blank.mkv blank.ptna
cargo build --target $(TRIPLE64) $(CARGO_RELEASE_ARG)
PKG_CONFIG_ALLOW_CROSS=1 cargo build --target $(TRIPLE32) $(CARGO_RELEASE_ARG)
install: build
install -d "$(DIST_GST_DIR64)"
install -m 755 target/$(TRIPLE64)/$(TARGET_BUILD_TYPE)/$(SONAME) "$(DIST_GST_DIR64)"
install -d "$(DIST_GST_DIR32)"
install -m 755 target/$(TRIPLE32)/$(TARGET_BUILD_TYPE)/$(SONAME) "$(DIST_GST_DIR32)"
blank.mkv:
#120 frames @ 30 FPS == 4 seconds
gst-launch-1.0 videotestsrc num-buffers=120 pattern=smpte ! 'video/x-raw,format=(string)I420,width=320,height=240,framerate=(fraction)30/1' ! av1enc ! queue ! mux. audiotestsrc num-buffers=400 freq=0 samplesperbuffer=441 ! 'audio/x-raw,rate=48000,channels=2' ! opusenc ! queue ! mux. matroskamux name=mux ! filesink location=blank.mkv
make_blank_ptna: make_blank_ptna.c
gcc -Wall -O2 $(shell pkg-config --cflags opus) -o $@ $< -lm $(shell pkg-config --libs opus)
blank.ptna: make_blank_ptna
./make_blank_ptna $@

View file

@ -1,12 +0,0 @@
This module is a gstreamer plugin which provides the ability to replace media
data encoded in certain formats with media encoded in another format. There
are two main components, `videoconv` for converting video data provided to
Quartz and Media Foundation, and `audioconv` for converting audio data
provided to XAudio2.
The broad idea is to hash the incoming data and replace it with data looked up
from a cache. If there is a cache miss, then the data is recorded to disk and
instead replaced by "blank" media. The conversion should be transparent to the
application (Wine, FAudio) so no changes are required to the application.
See the accompanying source files for more information.

Binary file not shown.

Binary file not shown.

View file

@ -1,5 +0,0 @@
extern crate gst_plugin_version_helper;
fn main() {
gst_plugin_version_helper::info()
}

View file

@ -1,271 +0,0 @@
/*
* Copyright (c) 2020, Valve Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Dumps a "blank" Proton Audio sample. For documentation of the Proton Audio
* format, see audioconv.rs. */
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <math.h>
#include <opus/opus.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))
/* indexes into frame_sizes[] */
#define FRAME_2p5MS 0
#define FRAME_SMALLEST FRAME_2p5MS
#define FRAME_5MS 1
#define FRAME_10MS 2
#define FRAME_20MS 3
#define FRAME_40MS 4
#define FRAME_60MS 5
#define FRAME_LARGEST FRAME_60MS
/* sample properties */
static const int SAMPLERATE = 48000;
static const int CHANNELS = 1;
static const int SAMPLE_LENGTH = FRAME_10MS; /* must match value in audioconv.rs */
static const float AUDIBLE_HZ = 400.f; /* freq for the --audible switch */
static const int frame_sizes[] = {
SAMPLERATE * 2.5 / 1000,
SAMPLERATE * 5 / 1000,
SAMPLERATE * 10 / 1000,
SAMPLERATE * 20 / 1000,
SAMPLERATE * 40 / 1000,
SAMPLERATE * 60 / 1000,
};
static float theta = 0.f;
static const uint32_t FLAG_HEADER = 0x10000000;
static const uint32_t AUDIOCONV_PADDING_LENGTH_SHIFT = 12;
static int complete_write(const int file, const void *buf, const size_t len)
{
size_t written = 0;
ssize_t ret;
while(written < len){
ret = write(file, ((const char *)buf) + written, len - written);
if(ret < 0){
if(errno != EINTR && errno != EAGAIN)
return 0;
}else
written += ret;
}
return written == len;
}
static int dump_hz(float hz, int samples, OpusEncoder *encoder, int outfile)
{
int i = 0, j, c, frame_size, tot = 0, padding;
opus_int32 written;
uint32_t pkt_header;
float val, *vals;
unsigned char packet[4000 /* suggested by opus_encoder(3) */ ];
fprintf(stdout, "dumping %u samples at %f hz\n", samples, hz);
vals = malloc(sizeof(float) * CHANNELS * frame_sizes[FRAME_LARGEST]);
while(i < samples){
/* find the largest packet we can write with the samples remaining */
frame_size = -1;
for(j = ARRAY_SIZE(frame_sizes) - 1; j >= 0; --j){
if(samples - i >= frame_sizes[j]){
frame_size = frame_sizes[j];
break;
}
}
if(frame_size < 0){
/* couldn't fill a whole packet, so write a partial */
frame_size = frame_sizes[FRAME_SMALLEST];
padding = frame_size - (samples - i);
}else
padding = 0;
for(j = 0; j < frame_size - padding; ++j){
val = sinf(theta);
for(c = 0; c < CHANNELS; ++c)
vals[j * CHANNELS + c] = val;
theta += hz * 2.f * M_PI / (float)SAMPLERATE;
while(theta >= 2.f * M_PI)
theta -= 2.f * M_PI;
}
if(padding)
memset(vals + (frame_size - padding) * CHANNELS, 0, sizeof(float) * padding * CHANNELS);
written = opus_encode_float(encoder, vals, frame_size, packet, sizeof(packet));
if(written < 0){
fprintf(stderr, "fatal: opus_encode failed!!!\n");
free(vals);
return 0;
}
pkt_header = written | (padding << AUDIOCONV_PADDING_LENGTH_SHIFT);
fprintf(stdout, "encoded %u samples (%u are padding) to %u bytes\n", frame_size, padding, written);
if(!complete_write(outfile, &pkt_header, sizeof(pkt_header))){
fprintf(stderr, "fatal: error writing packet header!!!\n");
free(vals);
return 0;
}
if(!complete_write(outfile, packet, written)){
fprintf(stderr, "fatal: error writing packet contents!!!\n");
free(vals);
return 0;
}
tot += written + sizeof(written);
i += frame_size;
}
fprintf(stdout, "done dumping, %u bytes\n", tot);
free(vals);
return 1;
}
/* OGG's opus header (from RFC 7845 Section 5.1) */
struct __attribute__((__packed__)) opus_header {
uint64_t magic;
uint8_t version;
uint8_t channels;
uint16_t preskip;
uint32_t input_samplerate;
uint16_t output_gain;
uint8_t mapping_family;
};
static int dump_header(int outfile)
{
struct opus_header header;
uint32_t sz = sizeof(header) | FLAG_HEADER;
static const char magic[] = "OpusHead";
memcpy(&header.magic, magic, sizeof(magic));
header.version = 1;
header.channels = CHANNELS;
header.preskip = 0;
header.input_samplerate = SAMPLERATE;
header.output_gain = 0;
header.mapping_family = 0; /* FIXME: we need to support mc */
if(!complete_write(outfile, &sz, sizeof(sz))){
fprintf(stderr, "fatal: error writing opus header header!!!\n");
return 0;
}
if(!complete_write(outfile, &header, sizeof(header))){
fprintf(stderr, "fatal: error writing opus header!!!\n");
return 0;
}
return 1;
}
void usage(const char *name)
{
fprintf(stderr, "usage:\n");
fprintf(stderr, "\t%s [--help|-h] [--audible] <outfile>\n", name);
fprintf(stderr, "\n");
fprintf(stderr, "\t--audible\tGenerate an audible sound clip instead of silence.\n");
fprintf(stderr, "\t--help -h\tPrint this help message.\n\n");
}
int main(int argc, char **argv)
{
int err, outfile, i, audible = 0;
OpusEncoder *encoder;
const char *outfile_name = NULL;
for(i = 1; i < argc; ++i){
if(!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")){
usage(argv[0]);
return 0;
}else if(!strcmp(argv[i], "--audible")){
audible = 1;
}else if(outfile_name){
fprintf(stderr, "error: too many arguments.\n\n");
usage(argv[0]);
return 1;
}else{
outfile_name = argv[i];
}
}
if(!outfile_name){
fprintf(stderr, "error: missing filename.\n\n");
usage(argv[0]);
return 1;
}
encoder = opus_encoder_create(SAMPLERATE, CHANNELS, OPUS_APPLICATION_AUDIO, &err);
if(!encoder){
fprintf(stderr, "couldn't create opus encoder, err: 0x%x\n", err);
return 1;
}
outfile = open(outfile_name, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if(outfile < 0){
fprintf(stderr, "couldn't open file \"%s\": %s\n", outfile_name, strerror(errno));
opus_encoder_destroy(encoder);
return 1;
}
if(!dump_header(outfile)){
close(outfile);
unlink(outfile_name);
opus_encoder_destroy(encoder);
return 1;
}
if(!dump_hz(audible ? AUDIBLE_HZ : 0.f, frame_sizes[SAMPLE_LENGTH], encoder, outfile)){
close(outfile);
unlink(outfile_name);
opus_encoder_destroy(encoder);
return 1;
}
close(outfile);
opus_encoder_destroy(encoder);
return 0;
}

View file

@ -1,970 +0,0 @@
/*
* Copyright (c) 2020, 2021, 2022 Valve Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::format_hash;
use crate::HASH_SEED;
use crate::discarding_disabled;
use crate::steam_compat_shader_path;
use crate::touch_file;
use gst::glib;
use gst::prelude::*;
use gst::subclass::prelude::*;
use gst::EventView;
use gst::QueryViewMut;
use std::sync::Mutex;
use std::io;
use std::io::Read;
use std::fs;
use std::fs::OpenOptions;
use std::collections::HashSet;
#[cfg(target_arch = "x86")]
use crate::murmur3_x86_128::murmur3_x86_128_full as murmur3_128_full;
#[cfg(target_arch = "x86")]
use crate::murmur3_x86_128::murmur3_x86_128_state as murmur3_128_state;
#[cfg(target_arch = "x86_64")]
use crate::murmur3_x64_128::murmur3_x64_128_full as murmur3_128_full;
#[cfg(target_arch = "x86_64")]
use crate::murmur3_x64_128::murmur3_x64_128_state as murmur3_128_state;
use crate::fossilize;
use crate::copy_into_array;
use crate::BufferedReader;
use once_cell::sync::Lazy;
/* Algorithm
* ---------
*
* The application feeds encoded audio into XAudio2 in chunks. Since we don't have access to all
* chunks in a stream on initialization (as we do with the video converter), we continuously hash
* the stream as it is sent to us. Each "chunk" is identified as the hash of the entire stream up
* to that chunk.
*
* Since chunks are small (~2 kilobytes), this leads to a significant possibility of two different
* streams having identical intro chunks (imagine two streams that start with several seconds of
* silence). This means we need a tree of chunks. Suppose two incoming streams with chunks that
* hash as shown (i.e. identical intro chunks that diverge later):
*
* Stream 1: [AA BB CC DD]
*
* Stream 2: [AA BB YY ZZ]
*
* We record a tree and the transcoder will walk it depth-first in order to reconstruct each unique
* stream:
*
* AA => aa.ptna
* AA+BB => bb.ptna
* AA+BB+CC => cc.ptna
* AA+BB+CC+DD => dd.ptna
* AA+BB+YY => yy.ptna
* AA+BB+YY+ZZ => zz.ptna
*
* Upon playback, we chain each transcoded stream chunk together as the packets come in:
*
* AA -> start stream with aa.ptna
* BB -> play bb.ptna
* CC -> play cc.ptna
* DD -> play dd.ptna
*
* or:
*
* AA -> start stream with aa.ptna
* BB -> play bb.ptna
* YY -> play yy.ptna
* ZZ -> play zz.ptna
*
* or:
*
* AA -> start stream with aa.ptna
* NN -> not recognized, instead play blank.ptna and mark this stream as needs-transcoding
* OO -> play blank.ptna
* PP -> play blank.ptna
* When the Stream is destroyed, we'll record AA+NN+OO+PP into the needs-transcode database
* for the transcoder to convert later.
*
*
* Physical Format
* ---------------
*
* All stored values are little-endian.
*
* Transcoded audio is stored in the "transcoded" Fossilize database under the
* AUDIOCONV_FOZ_TAG_PTNADATA tag. Each chunk is stored in one entry with as many of the following
* "Proton Audio" (ptna) packets as are required to store the entire transcoded chunk:
*
* uint32_t packet_header: Information about the upcoming packet, see bitmask:
* MSB [FFFF PPPP PPPP PPPP PPPP LLLL LLLL LLLL] LSB
* L: Number of _bytes_ in this packet following this header.
* P: Number of _samples_ at the end of this packet which are padding and should be skipped.
* F: Flag bits:
* 0x1: This packet is an Opus header
* 0x2, 0x4, 0x8: Reserved for future use.
*
* If the Opus header flag is set:
* Following packet is an Opus identification header, as defined in RFC 7845 "Ogg
* Encapsulation for the Opus Audio Codec" Section 5.1.
* <https://tools.ietf.org/html/rfc7845#section-5.1>
*
* If the header flag is not set:
* Following packet is raw Opus data to be sent to an Opus decoder.
*
*
* If we encounter a stream which needs transcoding, we record the buffers and metadata in
* a Fossilize database. The database has three tag types:
*
* AUDIOCONV_FOZ_TAG_STREAM: This identifies each unique stream of buffers. For example:
* [hash(AA+BB+CC+DD)] -> [AA, BB, CC, DD]
* [hash(AA+BB+XX+YY)] -> [AA, BB, XX, YY]
*
* AUDIOCONV_FOZ_TAG_AUDIODATA: This contans the actual encoded audio data. For example:
* [AA] -> [AA's buffer data]
* [BB] -> [BB's buffer data]
*
* AUDIOCONV_FOZ_TAG_CODECINFO: This contans the codec data required to decode the buffer. Only
* the "head" of each stream is recorded. For example:
* [AA] -> [
* uint32_t wmaversion (from WAVEFORMATEX.wFormatTag)
* uint32_t bitrate (from WAVEFORMATEX.nAvgBytesPerSec)
* uint32_t channels (WAVEFORMATEX.nChannels)
* uint32_t rate (WAVEFORMATEX.nSamplesPerSec)
* uint32_t block_align (WAVEFORMATEX.nBlockAlign)
* uint32_t depth (WAVEFORMATEX.wBitsPerSample)
* char[remainder of entry] codec_data (codec data which follows WAVEFORMATEX)
* ]
*
*/
const AUDIOCONV_ENCODED_LENGTH_MASK: u32 = 0x00000fff; /* 4kB fits in here */
const AUDIOCONV_PADDING_LENGTH_MASK: u32 = 0x0ffff000; /* 120ms of samples at 48kHz fits in here */
const AUDIOCONV_PADDING_LENGTH_SHIFT: u32 = 12;
const AUDIOCONV_FLAG_MASK: u32 = 0xf0000000;
const AUDIOCONV_FLAG_HEADER: u32 = 0x10000000; /* this chunk is the Opus header */
const _AUDIOCONV_FLAG_RESERVED1: u32 = 0x20000000; /* not yet used */
const _AUDIOCONV_FLAG_RESERVED2: u32 = 0x40000000; /* not yet used */
const _AUDIOCONV_FLAG_V2: u32 = 0x80000000; /* indicates a "version 2" header, process somehow differently (TBD) */
/* properties of the "blank" audio file */
const _BLANK_AUDIO_FILE_LENGTH_MS: f32 = 10.0;
const _BLANK_AUDIO_FILE_RATE: f32 = 48000.0;
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
gst::DebugCategory::new(
"protonaudioconverter",
gst::DebugColorFlags::empty(),
Some("Proton audio converter"))
});
struct AudioConverterDumpFozdb {
fozdb: Option<fossilize::StreamArchive>,
already_cleaned: bool,
}
impl AudioConverterDumpFozdb {
fn new() -> Self {
Self {
fozdb: None,
already_cleaned: false,
}
}
fn open(&mut self, create: bool) -> &mut Self {
if self.fozdb.is_none() {
let dump_file_path = match std::env::var("MEDIACONV_AUDIO_DUMP_FILE") {
Err(_) => { return self; },
Ok(c) => c,
};
let dump_file_path = std::path::Path::new(&dump_file_path);
if fs::create_dir_all(&dump_file_path.parent().unwrap()).is_err() {
return self;
}
match fossilize::StreamArchive::new(&dump_file_path, OpenOptions::new().write(true).read(true).create(create), false /* read-only? */, AUDIOCONV_FOZ_NUM_TAGS) {
Ok(newdb) => {
self.fozdb = Some(newdb);
},
Err(_) => {
return self;
},
}
}
self
}
fn close(&mut self) {
self.fozdb = None
}
fn discard_transcoded(&mut self) {
if self.already_cleaned {
return;
}
if discarding_disabled() {
self.already_cleaned = true;
return;
}
if let Some(fozdb) = &mut self.open(false).fozdb {
if let Ok(read_fozdb_path) = std::env::var("MEDIACONV_AUDIO_TRANSCODED_FILE") {
if let Ok(read_fozdb) = fossilize::StreamArchive::new(&read_fozdb_path, OpenOptions::new().read(true), true /* read-only? */, AUDIOCONV_FOZ_NUM_TAGS) {
let mut chunks_to_discard = HashSet::<(u32, u128)>::new();
let mut chunks_to_keep = HashSet::<(u32, u128)>::new();
for stream_id in fozdb.iter_tag(AUDIOCONV_FOZ_TAG_STREAM).cloned().collect::<Vec<u128>>() {
if let Ok(chunks_size) = fozdb.entry_size(AUDIOCONV_FOZ_TAG_STREAM, stream_id) {
let mut buf = vec![0u8; chunks_size].into_boxed_slice();
if fozdb.read_entry(AUDIOCONV_FOZ_TAG_STREAM, stream_id, 0, &mut buf, fossilize::CRCCheck::WithCRC).is_ok() {
let mut has_all = true;
let mut stream_chunks = Vec::<(u32, u128)>::new();
for i in 0..(chunks_size / 16) {
let offs = i * 16;
let chunk_id = u128::from_le_bytes(copy_into_array(&buf[offs..offs + 16]));
if !read_fozdb.has_entry(AUDIOCONV_FOZ_TAG_PTNADATA, chunk_id) {
has_all = false;
break;
}
stream_chunks.push((AUDIOCONV_FOZ_TAG_AUDIODATA, chunk_id));
}
for x in stream_chunks {
if has_all {
chunks_to_discard.insert(x);
chunks_to_discard.insert((AUDIOCONV_FOZ_TAG_CODECINFO, x.1));
} else {
chunks_to_keep.insert(x);
chunks_to_keep.insert((AUDIOCONV_FOZ_TAG_CODECINFO, x.1));
}
}
if has_all {
chunks_to_discard.insert((AUDIOCONV_FOZ_TAG_STREAM, stream_id));
}
}
}
}
let mut chunks = Vec::<(u32, u128)>::new();
for x in chunks_to_discard.difference(&chunks_to_keep) {
chunks.push(*x);
}
if fozdb.discard_entries(&chunks).is_err() {
self.close();
}
}
}
}
self.already_cleaned = true;
}
}
static DUMP_FOZDB: Lazy<Mutex<AudioConverterDumpFozdb>> = Lazy::new(|| {
Mutex::new(AudioConverterDumpFozdb::new())
});
static DUMPING_DISABLED: Lazy<bool> = Lazy::new(|| {
let v = match std::env::var("MEDIACONV_AUDIO_DONT_DUMP") {
Err(_) => { return false; },
Ok(c) => c,
};
v != "0"
});
#[derive(Clone)]
struct NeedTranscodeHead {
wmaversion: i32,
bitrate: i32,
channels: i32,
rate: i32,
block_align: i32,
depth: i32,
codec_data: Vec<u8>
}
impl NeedTranscodeHead {
fn new_from_caps(caps: &gst::CapsRef) -> Result<Self, gst::LoggableError> {
let s = caps.structure(0).ok_or_else(|| loggable_error!(CAT, "Caps have no WMA data!"))?;
let wmaversion = s.get::<i32>("wmaversion").map_err(|_| loggable_error!(CAT, "Caps have no wmaversion field"))?;
let bitrate = s.get::<i32>("bitrate").map_err(|_| loggable_error!(CAT, "Caps have no bitrate field"))?;
let channels = s.get::<i32>("channels").map_err(|_| loggable_error!(CAT, "Caps have no channels field"))?;
let rate = s.get::<i32>("rate").map_err(|_| loggable_error!(CAT, "Caps have no rate field"))?;
let block_align = s.get::<i32>("block_align").map_err(|_| loggable_error!(CAT, "Caps have no block_align field"))?;
let depth = s.get::<i32>("depth").map_err(|_| loggable_error!(CAT, "Caps have no depth field"))?;
let codec_data_buf = s.get::<gst::Buffer>("codec_data")
.map_err(|_| loggable_error!(CAT, "Caps have no codec_data field"))?;
let mapped = codec_data_buf.into_mapped_buffer_readable().unwrap();
let mut codec_data = Vec::new();
codec_data.extend_from_slice(mapped.as_slice());
Ok(NeedTranscodeHead {
wmaversion,
bitrate,
channels,
rate,
block_align,
depth,
codec_data,
})
}
fn serialize(&self) -> Vec<u8> {
let mut ret = Vec::new();
ret.extend_from_slice(&self.wmaversion.to_le_bytes());
ret.extend_from_slice(&self.bitrate.to_le_bytes());
ret.extend_from_slice(&self.channels.to_le_bytes());
ret.extend_from_slice(&self.rate.to_le_bytes());
ret.extend_from_slice(&self.block_align.to_le_bytes());
ret.extend_from_slice(&self.depth.to_le_bytes());
ret.extend(self.codec_data.iter());
ret
}
}
const AUDIOCONV_FOZ_TAG_STREAM: u32 = 0;
const AUDIOCONV_FOZ_TAG_CODECINFO: u32 = 1;
const AUDIOCONV_FOZ_TAG_AUDIODATA: u32 = 2;
const AUDIOCONV_FOZ_TAG_PTNADATA: u32 = 3;
const AUDIOCONV_FOZ_NUM_TAGS: usize = 4;
/* represents a Stream, a sequence of buffers */
struct StreamState {
hash_state: murmur3_128_state,
cur_hash: u128,
buffers: Vec<(u128, gst::MappedBuffer<gst::buffer::Readable>)>,
loop_buffers: Vec<(u128, gst::MappedBuffer<gst::buffer::Readable>)>,
codec_info: Option<NeedTranscodeHead>,
needs_dump: bool,
}
enum LoopState {
NoLoop,
Looping,
LoopEnded,
}
impl StreamState {
fn new() -> Self {
Self {
hash_state: murmur3_128_state::new(HASH_SEED),
buffers: Vec::<(u128, gst::MappedBuffer<gst::buffer::Readable>)>::new(),
loop_buffers: Vec::<(u128, gst::MappedBuffer<gst::buffer::Readable>)>::new(),
cur_hash: 0,
codec_info: None,
needs_dump: false,
}
}
fn record_buffer(&mut self, buf_hash: u128, loop_hash: u128, buffer: gst::MappedBuffer<gst::buffer::Readable>, codec_info: Option<&NeedTranscodeHead>) -> io::Result<LoopState> {
if self.codec_info.is_none() {
if let Some(codec_info) = codec_info {
self.codec_info = Some(codec_info.clone());
}
}
if self.loop_buffers.len() < self.buffers.len() &&
self.buffers[self.loop_buffers.len()].0 == loop_hash {
self.loop_buffers.push((buf_hash /* not loop_hash! */, buffer));
if self.loop_buffers.len() == self.buffers.len() {
/* full loop, just drop them */
self.loop_buffers.clear();
return Ok(LoopState::LoopEnded);
}
Ok(LoopState::Looping)
}else{
if !self.loop_buffers.is_empty() {
/* partial loop, track them and then continue */
self.buffers.append(&mut self.loop_buffers);
}
self.buffers.push((buf_hash, buffer));
self.cur_hash = murmur3_128_full(&mut (&buf_hash.to_le_bytes() as &[u8]), &mut self.hash_state)?;
Ok(LoopState::NoLoop)
}
}
fn write_to_foz(&self) -> Result<(), gst::LoggableError> {
if self.needs_dump && !self.buffers.is_empty() {
let db = &mut (*DUMP_FOZDB).lock().unwrap();
let mut db = &mut db.open(true).fozdb;
let db = match &mut db {
Some(d) => d,
None => { return Err(loggable_error!(CAT, "Failed to open fossilize db!")) },
};
let mut found = db.has_entry(AUDIOCONV_FOZ_TAG_STREAM, self.cur_hash);
if !found {
/* are there any recorded streams of which this stream is a subset? */
let stream_ids = db.iter_tag(AUDIOCONV_FOZ_TAG_STREAM).cloned().collect::<Vec<u128>>();
found = stream_ids.iter().any(|stream_id| {
let mut offs = 0;
for cur_buf_id in self.buffers.iter() {
let mut buf = [0u8; 16];
let res = db.read_entry(AUDIOCONV_FOZ_TAG_STREAM, *stream_id, offs, &mut buf, fossilize::CRCCheck::WithCRC);
let buffer_id = match res {
Err(_) => { return false; }
Ok(s) => {
if s != std::mem::size_of::<u128>() {
return false;
}
u128::from_le_bytes(buf)
}
};
if buffer_id != (*cur_buf_id).0 {
return false;
}
offs += 16;
}
gst::trace!(CAT, "stream id {} is a subset of {}, so not recording stream", self.cur_hash, *stream_id);
true
});
}
if !found {
if *DUMPING_DISABLED {
gst::trace!(CAT, "dumping disabled, so not recording stream id {}", self.cur_hash);
} else {
gst::trace!(CAT, "recording stream id {}", self.cur_hash);
db.write_entry(AUDIOCONV_FOZ_TAG_CODECINFO,
self.buffers[0].0,
&mut self.codec_info.as_ref().unwrap().serialize().as_slice(),
fossilize::CRCCheck::WithCRC)
.map_err(|e| loggable_error!(CAT, "Unable to write stream header: {:?}", e))?;
db.write_entry(AUDIOCONV_FOZ_TAG_STREAM,
self.cur_hash,
&mut StreamStateSerializer::new(self),
fossilize::CRCCheck::WithCRC)
.map_err(|e| loggable_error!(CAT, "Unable to write stream: {:?}", e))?;
for buffer in self.buffers.iter() {
db.write_entry(AUDIOCONV_FOZ_TAG_AUDIODATA,
buffer.0,
&mut buffer.1.as_slice(),
fossilize::CRCCheck::WithCRC)
.map_err(|e| loggable_error!(CAT, "Unable to write audio data: {:?}", e))?;
}
}
}
}
Ok(())
}
fn reset(&mut self) {
self.hash_state.reset();
self.buffers.clear();
self.loop_buffers.clear();
self.cur_hash = 0;
self.codec_info = None;
self.needs_dump = false;
}
}
struct StreamStateSerializer<'a> {
stream_state: &'a StreamState,
cur_idx: usize,
}
impl<'a> StreamStateSerializer<'a> {
fn new(stream_state: &'a StreamState) -> Self {
StreamStateSerializer {
stream_state,
cur_idx: 0
}
}
}
impl<'a> Read for StreamStateSerializer<'a> {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
if self.cur_idx >= self.stream_state.buffers.len() {
return Ok(0);
}
out[0..std::mem::size_of::<u128>()].copy_from_slice(&self.stream_state.buffers[self.cur_idx].0.to_le_bytes());
self.cur_idx += 1;
Ok(std::mem::size_of::<u128>())
}
}
fn hash_data(dat: &[u8], len: usize, hash_state: &mut murmur3_128_state) -> io::Result<u128> {
murmur3_128_full(&mut BufferedReader::new(dat, len), hash_state)
}
struct AudioConvState {
sent_header: bool,
codec_data: Option<NeedTranscodeHead>,
hash_state: murmur3_128_state,
loop_hash_state: murmur3_128_state,
stream_state: StreamState,
read_fozdb: Option<fossilize::StreamArchive>,
}
impl AudioConvState {
fn new() -> Result<AudioConvState, gst::LoggableError> {
let read_fozdb_path = std::env::var("MEDIACONV_AUDIO_TRANSCODED_FILE").map_err(|_| {
loggable_error!(CAT, "MEDIACONV_AUDIO_TRANSCODED_FILE is not set!")
})?;
let read_fozdb = match fossilize::StreamArchive::new(&read_fozdb_path, OpenOptions::new().read(true), true /* read-only? */, AUDIOCONV_FOZ_NUM_TAGS) {
Ok(s) => Some(s),
Err(_) => None,
};
Ok(AudioConvState {
sent_header: false,
codec_data: None,
hash_state: murmur3_128_state::new(HASH_SEED),
loop_hash_state: murmur3_128_state::new(HASH_SEED),
stream_state: StreamState::new(),
read_fozdb,
})
}
fn reset(&mut self) {
if let Err(e) = self.stream_state.write_to_foz() {
e.log();
}
self.stream_state.reset();
self.hash_state.reset();
self.loop_hash_state.reset();
}
fn open_transcode_file(&mut self, buffer: gst::Buffer) -> io::Result<Box<[u8]>> {
let mapped = buffer.into_mapped_buffer_readable().unwrap();
let buf_len = mapped.size();
let hash = hash_data(mapped.as_slice(), buf_len, &mut self.hash_state)
.map_err(|e|{ gst::warning!(CAT, "Hashing buffer failed! {}", e); io::ErrorKind::Other })?;
let loop_hash = hash_data(mapped.as_slice(), buf_len, &mut self.loop_hash_state)
.map_err(|e|{ gst::warning!(CAT, "Hashing buffer failed! {}", e); io::ErrorKind::Other })?;
let try_loop = match self.stream_state.record_buffer(hash, loop_hash, mapped, Some(self.codec_data.as_ref().unwrap()))? {
LoopState::NoLoop => { self.loop_hash_state.reset(); false },
LoopState::Looping => { true },
LoopState::LoopEnded => { self.loop_hash_state.reset(); true },
};
if try_loop {
gst::log!(CAT, "Buffer hash: {} (loop: {})", format_hash(hash), format_hash(loop_hash));
}else{
gst::log!(CAT, "Buffer hash: {}", format_hash(hash));
}
/* try to read transcoded data */
if let Some(read_fozdb) = &mut self.read_fozdb {
if let Ok(transcoded_size) = read_fozdb.entry_size(AUDIOCONV_FOZ_TAG_PTNADATA, hash) {
/* success */
let mut buf = vec![0u8; transcoded_size].into_boxed_slice();
if read_fozdb.read_entry(AUDIOCONV_FOZ_TAG_PTNADATA, hash, 0, &mut buf, fossilize::CRCCheck::WithoutCRC).is_ok() {
return Ok(buf);
}
}
if try_loop {
if let Ok(transcoded_size) = read_fozdb.entry_size(AUDIOCONV_FOZ_TAG_PTNADATA, loop_hash) {
/* success */
let mut buf = vec![0u8; transcoded_size].into_boxed_slice();
if read_fozdb.read_entry(AUDIOCONV_FOZ_TAG_PTNADATA, loop_hash, 0, &mut buf, fossilize::CRCCheck::WithoutCRC).is_ok() {
return Ok(buf);
}
}
}
}
/* if we can't, return the blank file */
self.stream_state.needs_dump = true;
let buf = Box::new(*include_bytes!("../../blank.ptna"));
match steam_compat_shader_path() {
None => gst::log!(CAT, "env STEAM_COMPAT_SHADER_PATH not set"),
Some(mut path) => {
path.push("placeholder-audio-used");
if let Err(e) = touch_file(path) { gst::log!(CAT, "Failed to touch placeholder-audio-used file: {:?}", e) }
},
};
Ok(buf)
}
}
pub struct AudioConv {
state: Mutex<Option<AudioConvState>>,
sinkpad: gst::Pad,
srcpad: gst::Pad,
}
#[glib::object_subclass]
impl ObjectSubclass for AudioConv {
const NAME: &'static str = "ProtonAudioConverter";
type Type = super::AudioConv;
type ParentType = gst::Element;
fn with_class(klass: &Self::Class) -> Self {
let templ = klass.pad_template("sink").unwrap();
let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink"))
.chain_function(|pad, parent, buffer| {
AudioConv::catch_panic_pad_function(
parent,
|| Err(gst::FlowError::Error),
|audioconv| audioconv.chain(pad, buffer)
)
})
.event_function(|pad, parent, event| {
AudioConv::catch_panic_pad_function(
parent,
|| false,
|audioconv| audioconv.sink_event(pad, event)
)
}).build();
let templ = klass.pad_template("src").unwrap();
let srcpad = gst::Pad::builder_with_template(&templ, Some("src"))
.query_function(|pad, parent, query| {
AudioConv::catch_panic_pad_function(
parent,
|| false,
|audioconv| audioconv.src_query(pad, query)
)
})
.activatemode_function(|pad, parent, mode, active| {
AudioConv::catch_panic_pad_function(
parent,
|| Err(loggable_error!(CAT, "Panic activating srcpad with mode")),
|audioconv| audioconv.src_activatemode(pad, mode, active)
)
}).build();
AudioConv {
state: Mutex::new(None),
sinkpad,
srcpad,
}
}
}
impl ObjectImpl for AudioConv {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.add_pad(&self.sinkpad).unwrap();
obj.add_pad(&self.srcpad).unwrap();
}
}
impl GstObjectImpl for AudioConv { }
impl ElementImpl for AudioConv {
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
gst::subclass::ElementMetadata::new(
"Proton audio converter",
"Codec/Decoder/Audio",
"Converts audio for Proton",
"Andrew Eikum <aeikum@codeweavers.com>")
});
Some(&*ELEMENT_METADATA)
}
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
let mut caps = gst::Caps::new_empty();
{
let caps = caps.get_mut().unwrap();
caps.append(gst::Caps::builder("audio/x-wma").build());
}
let sink_pad_template = gst::PadTemplate::new(
"sink",
gst::PadDirection::Sink,
gst::PadPresence::Always,
&caps).unwrap();
let caps = gst::Caps::builder("audio/x-opus").build();
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&caps).unwrap();
vec![src_pad_template, sink_pad_template]
});
PAD_TEMPLATES.as_ref()
}
fn change_state(
&self,
transition: gst::StateChange
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
gst::log!(CAT, imp: self, "State transition: {:?}", transition);
match transition {
gst::StateChange::NullToReady => {
/* do runtime setup */
{
/* open fozdb here; this is the right place to fail and opening may be
* expensive */
(*DUMP_FOZDB).lock().unwrap().discard_transcoded();
let db = &mut (*DUMP_FOZDB).lock().unwrap();
let db = &mut db.open(true).fozdb;
if db.is_none() {
gst::error!(CAT, "Failed to open fossilize db!");
return Err(gst::StateChangeError);
}
}
let new_state = AudioConvState::new().map_err(|err| {
err.log();
gst::StateChangeError
})?;
let mut state = self.state.lock().unwrap();
assert!((*state).is_none());
*state = Some(new_state);
},
gst::StateChange::ReadyToNull => {
/* do runtime teardown */
let old_state = self.state.lock().unwrap().take(); // dispose of state
if let Some(old_state) = old_state {
if old_state.stream_state.write_to_foz().is_err() {
gst::warning!(CAT, "Error writing out stream data!");
}
}
},
_ => (),
};
self.parent_change_state(transition)
/* XXX on ReadyToNull, sodium drops state _again_ here... why? */
}
}
impl AudioConv {
fn chain(
&self,
_pad: &gst::Pad,
buffer: gst::Buffer
) -> Result<gst::FlowSuccess, gst::FlowError> {
gst::log!(CAT, "Handling buffer {:?}", buffer);
let mut state = self.state.lock().unwrap();
let mut state = match &mut *state {
Some(s) => s,
None => { return Err(gst::FlowError::Error); },
};
let ptnadata = state.open_transcode_file(buffer).map_err(|_| {
gst::error!(CAT, "ERROR! Failed to read transcoded audio! Things will go badly..."); gst::FlowError::Error
})?;
let mut offs: usize = 0;
loop {
if offs >= ptnadata.len() {
break;
}
if offs + 4 >= ptnadata.len() {
gst::warning!(CAT, "Short read on ptna header?");
break;
}
let packet_hdr = u32::from_le_bytes(copy_into_array(&ptnadata[offs..offs + 4]));
offs += 4;
let (flags, padding_len, encoded_len) =
((packet_hdr & AUDIOCONV_FLAG_MASK),
(packet_hdr & AUDIOCONV_PADDING_LENGTH_MASK) >> AUDIOCONV_PADDING_LENGTH_SHIFT,
(packet_hdr & AUDIOCONV_ENCODED_LENGTH_MASK) as usize);
if offs + encoded_len > ptnadata.len() {
gst::warning!(CAT, "Short read on ptna data?");
break;
}
let pkt_is_header = (flags & AUDIOCONV_FLAG_HEADER) != 0;
if pkt_is_header && state.sent_header {
/* only send one header */
offs += encoded_len;
continue;
}
/* TODO: can we use a gstbuffer cache here? */
let mut buffer = gst::Buffer::with_size(encoded_len as usize).unwrap();
if !pkt_is_header && padding_len > 0 {
gst_audio::AudioClippingMeta::add(buffer.get_mut().unwrap(),
gst::format::Default::ZERO,
gst::format::Default::from_u64(padding_len as u64));
}
let mut writable = buffer.into_mapped_buffer_writable().unwrap();
writable.as_mut_slice().copy_from_slice(&ptnadata[offs..offs + encoded_len]);
gst::log!(CAT, "pushing one packet of len {}", encoded_len);
self.srcpad.push(writable.into_buffer())?;
if pkt_is_header {
state.sent_header = true;
}
offs += encoded_len;
}
Ok(gst::FlowSuccess::Ok)
}
fn sink_event(
&self,
pad: &gst::Pad,
event: gst::Event
) -> bool {
gst::log!(CAT, obj:pad, "Got an event {:?}", event);
match event.view() {
EventView::Caps(event_caps) => {
let mut state = self.state.lock().unwrap();
if let Some(state) = &mut *state {
let head = match NeedTranscodeHead::new_from_caps(event_caps.caps()){
Ok(h) => h,
Err(e) => {
gst::error!(CAT, "Invalid WMA caps!");
e.log();
return false;
},
};
state.codec_data = Some(head);
};
drop(state);
let mut caps = gst::Caps::new_empty();
{
let caps = caps.get_mut().unwrap();
let s = gst::Structure::builder("audio/x-opus")
.field("channel-mapping-family", &0i32)
.build();
caps.append_structure(s);
}
self.srcpad.push_event(gst::event::Caps::new(&caps))
},
EventView::FlushStop(_) => {
let mut state = self.state.lock().unwrap();
if let Some(state) = &mut *state {
state.reset();
};
drop(state);
gst::Pad::event_default(pad, Some(&*self.obj()), event)
},
_ => gst::Pad::event_default(pad, Some(&*self.obj()), event)
}
}
fn src_query(
&self,
pad: &gst::Pad,
query: &mut gst::QueryRef) -> bool
{
gst::log!(CAT, obj: pad, "got query: {:?}", query);
match query.view_mut() {
QueryViewMut::Scheduling(q) => {
let mut peer_query = gst::query::Scheduling::new();
let res = self.sinkpad.peer_query(&mut peer_query);
if ! res {
return res;
}
let (flags, min, max, align) = peer_query.result();
q.set(flags, min, max, align);
true
},
_ => gst::Pad::query_default(pad, Some(&*self.obj()), query)
}
}
fn src_activatemode(
&self,
_pad: &gst::Pad,
mode: gst::PadMode,
active: bool
) -> Result<(), gst::LoggableError> {
self.sinkpad
.activate_mode(mode, active)?;
Ok(())
}
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2020, 2021, 2022 Valve Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use gst::glib;
use gst::prelude::*;
mod imp;
glib::wrapper! {
pub struct AudioConv(ObjectSubclass<imp::AudioConv>) @extends gst::Element, gst::Object;
}
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
gst::Element::register(
Some(plugin),
"protonaudioconverter",
gst::Rank::Marginal,
AudioConv::static_type())
}

View file

@ -1,213 +0,0 @@
/*
* Copyright (c) 2020, 2021, 2022 Valve Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use gst::glib;
use gst::prelude::*;
use gst::subclass::prelude::*;
use gst::EventView;
use once_cell::sync::Lazy;
/* Opus is a great fit for our usecase except for one problem: it only supports a few samplerates.
* Notably it doesn't support 44100 Hz, which is a very frequently used samplerate. This bin
* provides a capssetter element which will override the rate we get from Opus with the rate the
* application requested. Similarly, on the transcoder side, we just encode the audio as if it were
* at 48 kHz, even if it is actually at 44.1 kHz.
*
* The downside to this is a small decrease in audio quality. If Opus is most responsive between 20
* Hz and 20 kHz, then when 44.1 audio is converted to 48, we'll gain noise between 18-20 Hz
* (although WMA probably already filtered that out) and start to lose audio above 18,375 kHz. This
* is at the very edge of human hearing, so we're unlikely to lose any noticeable quality.
*
* Resampling is an option, but has some problems. It's significantly more complicated, and more
* CPU-intensive. Also, XAudio2 buffers can be started and ended at arbitrary points, so if we
* start moving audio data from one buffer into another due to resampling, it may result in audible
* artifacts. I think just encoding at the wrong rate is the best compromise. If the application
* actually cared about audio quality, they probably would not have used WMA in the first place.
*/
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
gst::DebugCategory::new(
"protonaudioconverterbin",
gst::DebugColorFlags::empty(),
Some("Proton audio converter bin"))
});
pub struct AudioConvBin {
audioconv: gst::Element,
opusdec: gst::Element,
capssetter: gst::Element,
srcpad: gst::GhostPad,
sinkpad: gst::GhostPad,
}
#[glib::object_subclass]
impl ObjectSubclass for AudioConvBin {
const NAME: &'static str = "ProtonAudioConverterBin";
type Type = super::AudioConvBin;
type ParentType = gst::Bin;
fn with_class(klass: &Self::Class) -> Self {
let templ = klass.pad_template("src").unwrap();
let srcpad = gst::GhostPad::builder_with_template(&templ, Some("src")).build();
let templ = klass.pad_template("sink").unwrap();
let sinkpad = gst::GhostPad::builder_with_template(&templ, Some("sink"))
.event_function(|pad, parent, event| {
AudioConvBin::catch_panic_pad_function(
parent,
|| false,
|audioconvbin| audioconvbin.sink_event(pad, event)
)
}).build();
let audioconv = gst::ElementFactory::make("protonaudioconverter").build().unwrap();
let opusdec = gst::ElementFactory::make("opusdec").build().unwrap();
let capssetter = gst::ElementFactory::make("capssetter").build().unwrap();
AudioConvBin {
audioconv,
opusdec,
capssetter,
srcpad,
sinkpad,
}
}
}
impl ObjectImpl for AudioConvBin {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.add(&self.audioconv).unwrap();
obj.add(&self.opusdec).unwrap();
obj.add(&self.capssetter).unwrap();
self.audioconv.link(&self.opusdec).unwrap();
self.opusdec.link(&self.capssetter).unwrap();
self.sinkpad
.set_target(Some(&self.audioconv.static_pad("sink").unwrap()))
.unwrap();
self.srcpad
.set_target(Some(&self.capssetter.static_pad("src").unwrap()))
.unwrap();
obj.add_pad(&self.sinkpad).unwrap();
obj.add_pad(&self.srcpad).unwrap();
}
}
impl GstObjectImpl for AudioConvBin { }
impl BinImpl for AudioConvBin { }
impl ElementImpl for AudioConvBin {
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
gst::subclass::ElementMetadata::new(
"Proton audio converter with rate fixup",
"Codec/Decoder/Audio",
"Converts audio for Proton, fixing up samplerates",
"Andrew Eikum <aeikum@codeweavers.com>")
});
Some(&*ELEMENT_METADATA)
}
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
let mut caps = gst::Caps::new_empty();
{
let caps = caps.get_mut().unwrap();
caps.append(gst::Caps::builder("audio/x-wma").build());
}
let sink_pad_template = gst::PadTemplate::new(
"sink",
gst::PadDirection::Sink,
gst::PadPresence::Always,
&caps).unwrap();
let caps = gst::Caps::builder("audio/x-raw")
.field("format", "S16LE") /* opusdec always output S16LE */
.build();
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&caps).unwrap();
vec![src_pad_template, sink_pad_template]
});
PAD_TEMPLATES.as_ref()
}
}
impl AudioConvBin {
fn sink_event(
&self,
pad: &gst::GhostPad,
event: gst::Event
) -> bool {
match event.view() {
EventView::Caps(event_caps) => {
/* set up capssetter with this rate */
if let Some(s) = event_caps.caps().structure(0) {
if let Ok(override_rate) = s.get::<i32>("rate") {
let mut rate_caps = gst::Caps::new_empty();
{
let rate_caps = rate_caps.get_mut().unwrap();
let s = gst::Structure::builder("audio/x-raw")
.field("rate", &override_rate)
.build();
rate_caps.append_structure(s);
}
self.capssetter.set_property("caps", &rate_caps);
}else{
gst::warning!(CAT, "event has no rate");
}
} else {
gst::warning!(CAT, "event has no structure");
}
/* forward on to the real pad */
self.audioconv.static_pad("sink").unwrap()
.send_event(event)
},
_ => gst::Pad::event_default(pad, Some(&*self.obj()), event)
}
}
}

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) 2020, 2021, 2022 Valve Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use gst::glib;
use gst::prelude::*;
mod imp;
glib::wrapper! {
pub struct AudioConvBin(ObjectSubclass<imp::AudioConvBin>) @extends gst::Bin, gst::Element, gst::Object;
}
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
gst::Element::register(
Some(plugin),
"protonaudioconverterbin",
gst::Rank::Marginal + 1,
AudioConvBin::static_type()
)
}

View file

@ -1,554 +0,0 @@
/*
* Copyright (c) 2020, 2021, 2022 Valve Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Based on "Fossilize," which is
* Copyright (c) 2018-2019 Hans-Kristian Arntzen
* https://github.com/ValveSoftware/Fossilize/
*/
/* This is a read/write implementation of the Fossilize database format.
*
* https://github.com/ValveSoftware/Fossilize/
*
* That C++ implementation is specific to Vulkan, while this one tries to be generic to store any
* type of data.
*
* FIXME: It should probably live in that repo or in a separate project.
*/
use std::fs;
use std::io;
use std::io::Read;
use std::io::Write;
use std::io::Seek;
use std::fs::OpenOptions;
use std::convert::From;
use std::collections::HashMap;
use crate::*;
/* Fossilize StreamArchive database format version 6:
*
* The file consists of a header, followed by an unlimited series of "entries".
*
* All multi-byte entities are little-endian.
*
* The file header is as follows:
*
* Field Type Description
* ----- ---- -----------
* magic_number uint8_t[12] Constant value: "\x81""FOSSILIZEDB"
* version uint32_t StreamArchive version: 6
*
*
* Each entry follows this format:
*
* Field Type Description
* ----- ---- -----------
* name unsigned char[40] Application-defined entry identifier, stored in hexadecimal big-endian
* ASCII. Usually N-char tag followed by (40 - N)-char hash.
* stored_size uint32_t Size of the payload as stored in this file.
* flags uint32_t Flags for this entry (e.g. compression). See below.
* crc32 uint32_t CRC32 of the payload as stored in this file.
* payload_size uint32_t Size of this payload after decompression.
* payload uint8_t[stored_size] Entry data.
*
* The flags field may contain:
* 0x1: No compression.
* 0x2: Deflate compression.
*/
const FOSSILIZE_MAGIC: [u8; 12] = [0x81, 0x46, 0x4f, 0x53, 0x53, 0x49, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x42];
const FOSSILIZE_MIN_COMPAT_VERSION: u8 = 5;
const FOSSILIZE_VERSION: u8 = 6;
const MAGIC_LEN_BYTES: usize = 12 + 4;
const FOSSILIZE_COMPRESSION_NONE: u32 = 1;
const _FOSSILIZE_COMPRESSION_DEFLATE: u32 = 2;
#[derive(Debug)]
pub enum Error {
NotImplemented,
IO(io::Error),
CorruptDatabase,
DataTooLarge,
InvalidTag,
EntryNotFound,
FailedChecksum,
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::IO(e)
}
}
type FossilizeHash = u128;
const _FOSSILIZEHASH_ASCII_LEN: usize = (128 / 8) * 2;
trait ToAscii {
fn to_ascii_bytes(&self) -> Vec<u8>;
fn from_ascii_bytes(b: &[u8]) -> Result<Self, Error>
where Self: std::marker::Sized;
}
impl ToAscii for FossilizeHash {
fn to_ascii_bytes(&self) -> Vec<u8> {
format_hash(*self).into_bytes()
}
fn from_ascii_bytes(b: &[u8]) -> Result<Self, Error> {
let s = String::from_utf8(b.to_vec())
.map_err(|_| Error::CorruptDatabase)?;
Self::from_str_radix(&s, 16)
.map_err(|_| Error::CorruptDatabase)
}
}
type FossilizeTag = u32;
const FOSSILIZETAG_ASCII_LEN: usize = (32 / 8) * 2;
impl ToAscii for FossilizeTag {
fn to_ascii_bytes(&self) -> Vec<u8> {
format!("{:08x}", *self).into_bytes()
}
fn from_ascii_bytes(b: &[u8]) -> Result<Self, Error> {
let s = String::from_utf8(b.to_vec())
.map_err(|_| Error::CorruptDatabase)?;
Self::from_str_radix(&s, 16)
.map_err(|_| Error::CorruptDatabase)
}
}
const PAYLOAD_NAME_LEN_BYTES: usize = 40;
struct PayloadInfo {
size: u32,
compression: u32,
crc: u32,
full_size: u32,
}
const PAYLOAD_HEADER_LEN_BYTES: usize = 4 * 4;
impl PayloadInfo {
fn new_from_slice(dat: &[u8]) -> Self {
Self {
size: u32::from_le_bytes(copy_into_array(&dat[0..4])),
compression: u32::from_le_bytes(copy_into_array(&dat[4..8])),
crc: u32::from_le_bytes(copy_into_array(&dat[8..12])),
full_size: u32::from_le_bytes(copy_into_array(&dat[12..16])),
}
}
fn to_slice(&self) -> [u8; PAYLOAD_HEADER_LEN_BYTES] {
let mut ret = [0u8; PAYLOAD_HEADER_LEN_BYTES];
ret[0..4].copy_from_slice(&self.size.to_le_bytes());
ret[4..8].copy_from_slice(&self.compression.to_le_bytes());
ret[8..12].copy_from_slice(&self.crc.to_le_bytes());
ret[12..16].copy_from_slice(&self.full_size.to_le_bytes());
ret
}
}
pub struct PayloadEntry {
offset: u64,
payload_info: PayloadInfo,
}
impl PayloadEntry {
fn new_from_slice(offset: u64, dat: &[u8]) -> Self {
Self {
offset,
payload_info: PayloadInfo::new_from_slice(dat),
}
}
}
pub struct StreamArchive {
file: fs::File,
read_only: bool,
seen_blobs: Vec<HashMap<FossilizeHash, PayloadEntry>>,
write_pos: u64,
}
pub enum CRCCheck {
WithoutCRC,
WithCRC,
}
impl StreamArchive {
pub fn new<P: AsRef<std::path::Path>>(filename: P, fileopts: &OpenOptions, read_only: bool, num_tags: usize) -> Result<Self, Error> {
let file = fileopts.open(filename)?;
let mut seen_blobs = Vec::new();
for _ in 0..num_tags {
seen_blobs.push(HashMap::new());
}
let mut ret = Self {
file,
read_only,
seen_blobs,
write_pos: 0,
};
ret.prepare()?;
Ok(ret)
}
pub fn prepare(&mut self) -> Result<(), Error> {
self.write_pos = self.file.seek(io::SeekFrom::Start(0))?;
if self.file.metadata().unwrap().len() > 0 {
let mut magic_and_version = [0_u8; MAGIC_LEN_BYTES];
self.file.read_exact(&mut magic_and_version)?;
let version = magic_and_version[15];
if magic_and_version[0..12] != FOSSILIZE_MAGIC || !(FOSSILIZE_MIN_COMPAT_VERSION..=FOSSILIZE_VERSION).contains(&version) {
return Err(Error::CorruptDatabase);
}
self.write_pos = MAGIC_LEN_BYTES as u64;
loop {
let mut name_and_header = [0u8; PAYLOAD_NAME_LEN_BYTES + PAYLOAD_HEADER_LEN_BYTES];
let res = self.file.read_exact(&mut name_and_header);
if let Err(fail) = res {
if fail.kind() == io::ErrorKind::UnexpectedEof {
break;
}
return Err(Error::IO(fail));
}
let name = &name_and_header[0..PAYLOAD_NAME_LEN_BYTES];
let tag: usize = FossilizeTag::from_ascii_bytes(&name[0..FOSSILIZETAG_ASCII_LEN])? as usize;
let hash = FossilizeHash::from_ascii_bytes(&name[FOSSILIZETAG_ASCII_LEN..])?;
let payload_entry = PayloadEntry::new_from_slice(
self.file.seek(io::SeekFrom::Current(0))?,
&name_and_header[PAYLOAD_NAME_LEN_BYTES..]
);
let res = self.file.seek(io::SeekFrom::Current(payload_entry.payload_info.size as i64));
match res {
Ok(p) => {
self.write_pos = p;
if tag >= self.seen_blobs.len() && self.read_only {
/* ignore unknown tags for read-only DBs, otherwise panic */
continue;
}
self.seen_blobs[tag].insert(hash, payload_entry);
},
Err(e) => {
/* truncated chunk is not fatal */
if e.kind() != io::ErrorKind::UnexpectedEof {
return Err(Error::IO(e));
}
},
}
}
} else {
/* new file, write foz header */
self.file.write_all(&FOSSILIZE_MAGIC)?;
self.file.write_all(&[0u8, 0u8, 0u8, FOSSILIZE_VERSION])?;
self.write_pos = MAGIC_LEN_BYTES as u64;
}
Ok(())
}
pub fn has_entry(&self, tag: FossilizeTag, hash: FossilizeHash) -> bool {
self.seen_blobs[tag as usize].contains_key(&hash)
}
pub fn iter_tag(&self, tag: FossilizeTag) -> std::collections::hash_map::Keys<FossilizeHash, PayloadEntry> {
self.seen_blobs[tag as usize].keys()
}
pub fn entry_size(&self, tag: FossilizeTag, hash: FossilizeHash) -> Result<usize, Error> {
match self.seen_blobs[tag as usize].get(&hash) {
None => Err(Error::EntryNotFound),
Some(e) => Ok(e.payload_info.full_size as usize),
}
}
pub fn read_entry(&mut self, tag: FossilizeTag, hash: FossilizeHash, offset: u64, buf: &mut [u8], crc_opt: CRCCheck) -> Result<usize, Error> {
if tag as usize >= self.seen_blobs.len() {
return Err(Error::InvalidTag);
}
let entry = &self.seen_blobs[tag as usize].get(&hash);
let entry = match entry {
None => { return Err(Error::EntryNotFound); }
Some(e) => e,
};
if entry.payload_info.compression != FOSSILIZE_COMPRESSION_NONE {
return Err(Error::NotImplemented);
}
if offset >= entry.payload_info.full_size as u64 {
return Ok(0);
}
self.file.seek(io::SeekFrom::Start(entry.offset + offset))?;
let to_copy = std::cmp::min(entry.payload_info.full_size as usize - offset as usize, buf.len());
self.file.read_exact(&mut buf[0..to_copy])
.map_err(Error::IO)?;
if entry.payload_info.crc != 0 {
if let CRCCheck::WithCRC = crc_opt {
let mut crc_hasher = crc32fast::Hasher::new();
crc_hasher.update(&buf[0..to_copy]);
let got_crc = crc_hasher.finalize();
if got_crc != entry.payload_info.crc {
return Err(Error::FailedChecksum);
}
}
}
Ok(to_copy)
}
pub fn write_entry(&mut self, tag: FossilizeTag, hash: FossilizeHash, data: &mut dyn Read, crc_opt: CRCCheck) -> Result<(), Error> {
if self.has_entry(tag, hash) {
return Ok(());
}
self.file.seek(io::SeekFrom::Start(self.write_pos))?;
/* write entry name */
let mut name = [0u8; PAYLOAD_NAME_LEN_BYTES];
name[0..FOSSILIZETAG_ASCII_LEN].copy_from_slice(&tag.to_ascii_bytes());
name[FOSSILIZETAG_ASCII_LEN..].copy_from_slice(&hash.to_ascii_bytes());
self.file.write_all(&name)?;
/* allocate payload info space */
let payload_info_pos = self.file.seek(io::SeekFrom::Current(0))?;
let payload_info = PayloadInfo {
size: u32::max_value(), /* will be filled later */
compression: FOSSILIZE_COMPRESSION_NONE,
crc: 0, /* will be filled later */
full_size: u32::max_value(), /* will be filled later */
};
self.file.write_all(&payload_info.to_slice())?;
/* write data */
let mut payload_entry = PayloadEntry {
offset: self.file.seek(io::SeekFrom::Current(0))?,
payload_info,
};
const BUFFER_COPY_BYTES: usize = 8 * 1024 * 1024; /* tuneable */
let mut buf = box_array![0u8; BUFFER_COPY_BYTES];
let mut size: usize = 0;
let mut crc_hasher = crc32fast::Hasher::new();
loop {
let readed = data.read(&mut *buf)?;
if readed == 0 {
break;
}
if size + readed > u32::max_value() as usize {
/* Fossilize format only supports 4 GiB entries */
return Err(Error::DataTooLarge);
}
size += readed;
self.file.write_all(&buf[0..readed])?;
if let CRCCheck::WithCRC = crc_opt {
crc_hasher.update(&buf[0..readed]);
}
}
self.write_pos = self.file.seek(io::SeekFrom::Current(0))?;
/* seek back and fill in the size */
self.file.seek(io::SeekFrom::Start(payload_info_pos))?;
payload_entry.payload_info.size = size as u32;
payload_entry.payload_info.full_size = size as u32;
if let CRCCheck::WithCRC = crc_opt {
payload_entry.payload_info.crc = crc_hasher.finalize();
}
self.file.write_all(&payload_entry.payload_info.to_slice())?;
/* success. record entry and exit */
self.seen_blobs[tag as usize].insert(hash, payload_entry);
Ok(())
}
/* rewrites the database, discarding entries listed in 'to_discard' */
pub fn discard_entries(&mut self, to_discard: &[(FossilizeTag, FossilizeHash)]) -> Result<(), Error> {
self.write_pos = self.file.seek(io::SeekFrom::Start(0))?;
for v in self.seen_blobs.iter_mut() {
v.clear();
}
let mut magic_and_version = [0_u8; MAGIC_LEN_BYTES];
self.file.read_exact(&mut magic_and_version)?;
let version = magic_and_version[15];
if magic_and_version[0..12] != FOSSILIZE_MAGIC || !(FOSSILIZE_MIN_COMPAT_VERSION..=FOSSILIZE_VERSION).contains(&version) {
return Err(Error::CorruptDatabase);
}
self.write_pos = MAGIC_LEN_BYTES as u64;
loop {
let mut name_and_header = [0u8; PAYLOAD_NAME_LEN_BYTES + PAYLOAD_HEADER_LEN_BYTES];
let res = self.file.read_exact(&mut name_and_header);
if let Err(fail) = res {
if fail.kind() == io::ErrorKind::UnexpectedEof {
break;
}
return Err(Error::IO(fail));
}
let name = &name_and_header[0..PAYLOAD_NAME_LEN_BYTES];
let tag = FossilizeTag::from_ascii_bytes(&name[0..FOSSILIZETAG_ASCII_LEN])?;
let hash = FossilizeHash::from_ascii_bytes(&name[FOSSILIZETAG_ASCII_LEN..])?;
let payload_entry = PayloadEntry::new_from_slice(
self.file.seek(io::SeekFrom::Current(0))?,
&name_and_header[PAYLOAD_NAME_LEN_BYTES..]
);
if to_discard.contains(&(tag, hash)) {
/* skip over this entry */
let res = self.file.seek(io::SeekFrom::Current(payload_entry.payload_info.size as i64));
match res {
Ok(_) => {
},
Err(e) => {
/* truncated chunk is not fatal */
if e.kind() != io::ErrorKind::UnexpectedEof {
return Err(Error::IO(e));
}
},
}
} else {
let mut read_pos = self.file.seek(io::SeekFrom::Current(0))?;
if self.write_pos == read_pos - name_and_header.len() as u64 {
/* if we haven't dropped any chunks, we can just skip it rather than rewrite it */
let res = self.file.seek(io::SeekFrom::Current(payload_entry.payload_info.size as i64));
match res {
Ok(p) => {
self.write_pos = p;
},
Err(e) => {
/* truncated chunk is not fatal */
if e.kind() != io::ErrorKind::UnexpectedEof {
return Err(Error::IO(e));
}
},
}
} else {
/* we're offset, so we have to rewrite */
self.file.seek(io::SeekFrom::Start(self.write_pos))?;
{
/* write header */
let mut name = [0u8; PAYLOAD_NAME_LEN_BYTES];
name[0..FOSSILIZETAG_ASCII_LEN].copy_from_slice(&tag.to_ascii_bytes());
name[FOSSILIZETAG_ASCII_LEN..].copy_from_slice(&hash.to_ascii_bytes());
self.file.write_all(&name)?;
self.write_pos += name.len() as u64;
let buf = payload_entry.payload_info.to_slice();
self.file.write_all(&buf)?;
self.write_pos += buf.len() as u64;
}
/* copy contents */
const BUFFER_COPY_BYTES: usize = 8 * 1024 * 1024; /* tuneable */
let mut buf = box_array![0u8; BUFFER_COPY_BYTES];
let end_read = read_pos + payload_entry.payload_info.size as u64;
loop {
let to_read = std::cmp::min((end_read - read_pos) as usize, BUFFER_COPY_BYTES);
if to_read == 0 {
break;
}
self.file.seek(io::SeekFrom::Start(read_pos))?;
let readed = self.file.read(&mut (*buf)[0..to_read])?;
if readed == 0 {
break;
}
read_pos += readed as u64;
self.file.seek(io::SeekFrom::Start(self.write_pos))?;
self.file.write_all(&buf[0..readed])?;
self.write_pos += readed as u64;
}
self.file.seek(io::SeekFrom::Start(read_pos))?;
}
}
}
self.file.set_len(self.write_pos)?;
self.prepare()
}
}

View file

@ -1,191 +0,0 @@
/*
* Copyright (c) 2020, 2021, 2022 Valve Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#[macro_use]
extern crate gstreamer as gst;
extern crate gstreamer_base as gst_base;
extern crate gstreamer_video as gst_video;
extern crate gstreamer_audio as gst_audio;
extern crate once_cell;
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use filetime::FileTime;
use filetime::set_file_handle_times;
#[cfg(target_arch = "x86")]
mod murmur3_x86_128;
#[cfg(target_arch = "x86_64")]
mod murmur3_x64_128;
mod videoconv;
mod audioconv;
mod audioconvbin;
mod fossilize;
// copy_into_array:
//
// Copyright (c) 2020 Stu Small
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
fn copy_into_array<A, T>(slice: &[T]) -> A
where
A: Default + AsMut<[T]>,
T: Copy,
{
let mut a = A::default();
<A as AsMut<[T]>>::as_mut(&mut a).copy_from_slice(slice);
a
}
fn touch_file<P>(p: P) -> io::Result<()>
where
P: AsRef<Path> + std::fmt::Debug
{
let f = File::create(p)?;
let now = FileTime::now();
set_file_handle_times(&f, Some(now), Some(now))?;
Ok(())
}
fn steam_compat_shader_path() -> Option<PathBuf>
{
match std::env::var("STEAM_COMPAT_SHADER_PATH") {
Err(_) => None,
Ok(c) => Some(Path::new(&c).to_path_buf()),
}
}
/* rust has a hard time with large heap allocations. below macro works around that.
*
* by @simias from https://github.com/rust-lang/rust/issues/53827 */
#[macro_export]
macro_rules! box_array {
($val:expr ; $len:expr) => {{
// Use a generic function so that the pointer cast remains type-safe
fn vec_to_boxed_array<T>(vec: Vec<T>) -> Box<[T; $len]> {
let boxed_slice = vec.into_boxed_slice();
let ptr = ::std::boxed::Box::into_raw(boxed_slice) as *mut [T; $len];
unsafe { Box::from_raw(ptr) }
}
vec_to_boxed_array(vec![$val; $len])
}};
}
/* you MUST use this to consistently format the hash bytes into a string */
fn format_hash(hash: u128) -> String {
format!("{:032x}", hash)
}
/* changing this will invalidate the cache. you MUST clear it. */
const HASH_SEED: u32 = 0x4AA61F63;
pub struct BufferedReader<'a> {
dat: &'a [u8],
len: usize,
ofs: usize,
}
impl<'a> BufferedReader<'a> {
fn new(dat: &'a [u8], len: usize) -> Self {
BufferedReader {
dat,
len,
ofs: 0,
}
}
}
impl<'a> Read for BufferedReader<'a> {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
let to_copy = std::cmp::min(self.len - self.ofs, out.len());
if to_copy == 0 {
return Ok(0);
}
if to_copy == out.len() {
out.copy_from_slice(&self.dat[self.ofs..(self.ofs + to_copy)]);
}else{
out[0..to_copy].copy_from_slice(&self.dat[self.ofs..(self.ofs + to_copy)]);
}
self.ofs += to_copy;
Ok(to_copy)
}
}
fn discarding_disabled() -> bool {
let v = match std::env::var("MEDIACONV_DONT_DISCARD") {
Err(_) => { return false; },
Ok(c) => c,
};
v != "0"
}
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
videoconv::register(plugin)?;
audioconvbin::register(plugin)?;
audioconv::register(plugin)?;
Ok(())
}
plugin_define!(
protonmediaconverter,
env!("CARGO_PKG_DESCRIPTION"),
plugin_init,
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
"MIT/X11",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_REPOSITORY"),
env!("BUILD_REL_DATE")
);

View file

@ -1,232 +0,0 @@
// Copyright (c) 2020 Stu Small
//
// Modified to return its internal state for continuous hashing:
// Copyright (c) 2020 Andrew Eikum <aeikum@codeweavers.com> for CodeWeavers
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.
use std::io::{Read, Result};
use std::ops::Shl;
use crate::copy_into_array;
#[allow(non_camel_case_types)]
pub struct murmur3_x64_128_state {
seed: u32,
h1: u64,
h2: u64,
processed: usize,
}
impl murmur3_x64_128_state {
pub fn new(seed: u32) -> Self {
murmur3_x64_128_state {
seed,
h1: seed as u64,
h2: seed as u64,
processed: 0,
}
}
#[allow(dead_code)]
pub fn reset(&mut self) {
self.h1 = self.seed as u64;
self.h2 = self.seed as u64;
self.processed = 0;
}
}
/// Use the x64 variant of the 128 bit murmur3 to hash some [Read] implementation.
///
/// # Example
/// ```
/// use std::io::Cursor;
/// use murmur3::murmur3_x64_128;
/// let hash_result = murmur3_x64_128(&mut Cursor::new("hello world"), 0);
/// ```
pub fn murmur3_x64_128<T: Read>(source: &mut T, seed: u32) -> Result<u128> {
let mut state = murmur3_x64_128_state::new(seed);
murmur3_x64_128_full(source, &mut state)
}
pub fn murmur3_x64_128_full<T: Read>(source: &mut T, state: &mut murmur3_x64_128_state) -> Result<u128> {
const C1: u64 = 0x87c3_7b91_1142_53d5;
const C2: u64 = 0x4cf5_ad43_2745_937f;
const C3: u64 = 0x52dc_e729;
const C4: u64 = 0x3849_5ab5;
const R1: u32 = 27;
const R2: u32 = 31;
const R3: u32 = 33;
const M: u64 = 5;
let mut h1: u64 = state.h1;
let mut h2: u64 = state.h2;
let mut buf = [0; 16];
let mut processed: usize = state.processed;
loop {
let read = source.read(&mut buf[..])?;
processed += read;
if read == 16 {
let k1 = u64::from_le_bytes(copy_into_array(&buf[0..8]));
let k2 = u64::from_le_bytes(copy_into_array(&buf[8..]));
h1 ^= k1.wrapping_mul(C1).rotate_left(R2).wrapping_mul(C2);
h1 = h1
.rotate_left(R1)
.wrapping_add(h2)
.wrapping_mul(M)
.wrapping_add(C3);
h2 ^= k2.wrapping_mul(C2).rotate_left(R3).wrapping_mul(C1);
h2 = h2
.rotate_left(R2)
.wrapping_add(h1)
.wrapping_mul(M)
.wrapping_add(C4);
} else if read == 0 {
state.h1 = h1;
state.h2 = h2;
state.processed = processed;
h1 ^= processed as u64;
h2 ^= processed as u64;
h1 = h1.wrapping_add(h2);
h2 = h2.wrapping_add(h1);
h1 = fmix64(h1);
h2 = fmix64(h2);
h1 = h1.wrapping_add(h2);
h2 = h2.wrapping_add(h1);
let x = ((h2 as u128) << 64) | (h1 as u128);
return Ok(x);
} else {
let mut k1 = 0;
let mut k2 = 0;
if read >= 15 {
k2 ^= (buf[14] as u64).shl(48);
}
if read >= 14 {
k2 ^= (buf[13] as u64).shl(40);
}
if read >= 13 {
k2 ^= (buf[12] as u64).shl(32);
}
if read >= 12 {
k2 ^= (buf[11] as u64).shl(24);
}
if read >= 11 {
k2 ^= (buf[10] as u64).shl(16);
}
if read >= 10 {
k2 ^= (buf[9] as u64).shl(8);
}
if read >= 9 {
k2 ^= buf[8] as u64;
k2 = k2.wrapping_mul(C2).rotate_left(33).wrapping_mul(C1);
h2 ^= k2;
}
if read >= 8 {
k1 ^= (buf[7] as u64).shl(56);
}
if read >= 7 {
k1 ^= (buf[6] as u64).shl(48);
}
if read >= 6 {
k1 ^= (buf[5] as u64).shl(40);
}
if read >= 5 {
k1 ^= (buf[4] as u64).shl(32);
}
if read >= 4 {
k1 ^= (buf[3] as u64).shl(24);
}
if read >= 3 {
k1 ^= (buf[2] as u64).shl(16);
}
if read >= 2 {
k1 ^= (buf[1] as u64).shl(8);
}
if read >= 1 {
k1 ^= buf[0] as u64;
}
k1 = k1.wrapping_mul(C1);
k1 = k1.rotate_left(31);
k1 = k1.wrapping_mul(C2);
h1 ^= k1;
}
}
}
fn fmix64(k: u64) -> u64 {
const C1: u64 = 0xff51_afd7_ed55_8ccd;
const C2: u64 = 0xc4ce_b9fe_1a85_ec53;
const R: u32 = 33;
let mut tmp = k;
tmp ^= tmp >> R;
tmp = tmp.wrapping_mul(C1);
tmp ^= tmp >> R;
tmp = tmp.wrapping_mul(C2);
tmp ^= tmp >> R;
tmp
}
#[cfg(test)]
mod tests {
use super::*;
use std::cmp::min;
use std::io;
use std::io::Read;
const TEST_SEED: u32 = 0x00000000;
const CONST_DATA: [u8; 64] =
[ 0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8,
10u8, 11u8, 12u8, 13u8, 14u8, 15u8, 16u8, 17u8,
20u8, 21u8, 22u8, 23u8, 24u8, 25u8, 26u8, 27u8,
30u8, 31u8, 32u8, 33u8, 34u8, 35u8, 36u8, 37u8,
40u8, 41u8, 42u8, 43u8, 44u8, 45u8, 46u8, 47u8,
50u8, 51u8, 52u8, 53u8, 54u8, 55u8, 56u8, 57u8,
60u8, 61u8, 62u8, 63u8, 64u8, 65u8, 66u8, 67u8,
70u8, 71u8, 72u8, 73u8, 74u8, 75u8, 76u8, 77u8 ];
struct TestReader<'a> {
data: &'a [u8],
ofs: usize,
}
impl<'a> TestReader<'a> {
fn new(data: &'a [u8]) -> Self {
TestReader {
data,
ofs: 0,
}
}
}
impl<'a> Read for TestReader<'a> {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
let to_copy = min(out.len(), self.data.len() - self.ofs);
if to_copy > 0 {
out[0..to_copy].copy_from_slice(&self.data[self.ofs..(self.ofs + to_copy)]);
self.ofs += to_copy;
}
Ok(to_copy)
}
}
#[test]
fn test_full_hash() {
/* test with the full buffer */
let full_hash = murmur3_x64_128(&mut TestReader::new(&CONST_DATA), TEST_SEED).unwrap();
assert_eq!(full_hash, 0xeb91a9599de8337d969b1e101c4ee3bc);
/* accumulate hash across 16-byte chunks (short reads change hash due to 0-padding) */
let mut hash_state = murmur3_x64_128_state::new(TEST_SEED);
murmur3_x64_128_full(&mut TestReader::new(&CONST_DATA[0..16]), &mut hash_state).unwrap();
murmur3_x64_128_full(&mut TestReader::new(&CONST_DATA[16..32]), &mut hash_state).unwrap();
murmur3_x64_128_full(&mut TestReader::new(&CONST_DATA[32..48]), &mut hash_state).unwrap();
let hash = murmur3_x64_128_full(&mut TestReader::new(&CONST_DATA[48..64]), &mut hash_state).unwrap();
assert_eq!(hash, full_hash);
}
}

View file

@ -1,216 +0,0 @@
// Copyright (c) 2020 Stu Small
//
// Modified to return its internal state for continuous hashing:
// Copyright (c) 2020 Andrew Eikum <aeikum@codeweavers.com> for CodeWeavers
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.
use std::io::{Read, Result};
use std::ops::Shl;
use crate::copy_into_array;
#[allow(non_camel_case_types)]
pub struct murmur3_x86_128_state {
seed: u32,
h1: u32,
h2: u32,
h3: u32,
h4: u32,
processed: usize,
}
impl murmur3_x86_128_state {
pub fn new(seed: u32) -> Self {
murmur3_x86_128_state {
seed,
h1: seed,
h2: seed,
h3: seed,
h4: seed,
processed: 0,
}
}
#[allow(dead_code)]
pub fn reset(&mut self) {
self.h1 = self.seed;
self.h2 = self.seed;
self.h3 = self.seed;
self.h4 = self.seed;
self.processed = 0;
}
}
/// Use the x86 variant of the 128 bit murmur3 to hash some [Read] implementation.
///
/// # Example
/// ```
/// use std::io::Cursor;
/// use murmur3::murmur3_x86_128;
/// let hash_result = murmur3_x86_128(&mut Cursor::new("hello world"), 0);
/// ```
pub fn murmur3_x86_128<T: Read>(source: &mut T, seed: u32) -> Result<u128> {
let mut state = murmur3_x86_128_state::new(seed);
murmur3_x86_128_full(source, &mut state)
}
pub fn murmur3_x86_128_full<T: Read>(source: &mut T, state: &mut murmur3_x86_128_state) -> Result<u128> {
const C1: u32 = 0x239b_961b;
const C2: u32 = 0xab0e_9789;
const C3: u32 = 0x38b3_4ae5;
const C4: u32 = 0xa1e3_8b93;
const C5: u32 = 0x561c_cd1b;
const C6: u32 = 0x0bca_a747;
const C7: u32 = 0x96cd_1c35;
const C8: u32 = 0x32ac_3b17;
const M: u32 = 5;
let mut h1: u32 = state.h1;
let mut h2: u32 = state.h2;
let mut h3: u32 = state.h3;
let mut h4: u32 = state.h4;
let mut buf = [0; 16];
let mut processed: usize = state.processed;
loop {
let read = source.read(&mut buf[..])?;
processed += read;
if read == 16 {
let k1 = u32::from_le_bytes(copy_into_array(&buf[0..4]));
let k2 = u32::from_le_bytes(copy_into_array(&buf[4..8]));
let k3 = u32::from_le_bytes(copy_into_array(&buf[8..12]));
let k4 = u32::from_le_bytes(copy_into_array(&buf[12..16]));
h1 ^= k1.wrapping_mul(C1).rotate_left(15).wrapping_mul(C2);
h1 = h1
.rotate_left(19)
.wrapping_add(h2)
.wrapping_mul(M)
.wrapping_add(C5);
h2 ^= k2.wrapping_mul(C2).rotate_left(16).wrapping_mul(C3);
h2 = h2
.rotate_left(17)
.wrapping_add(h3)
.wrapping_mul(M)
.wrapping_add(C6);
h3 ^= k3.wrapping_mul(C3).rotate_left(17).wrapping_mul(C4);
h3 = h3
.rotate_left(15)
.wrapping_add(h4)
.wrapping_mul(M)
.wrapping_add(C7);
h4 ^= k4.wrapping_mul(C4).rotate_left(18).wrapping_mul(C1);
h4 = h4
.rotate_left(13)
.wrapping_add(h1)
.wrapping_mul(M)
.wrapping_add(C8);
} else if read == 0 {
state.h1 = h1;
state.h2 = h2;
state.h3 = h3;
state.h4 = h4;
state.processed = processed;
h1 ^= processed as u32;
h2 ^= processed as u32;
h3 ^= processed as u32;
h4 ^= processed as u32;
h1 = h1.wrapping_add(h2);
h1 = h1.wrapping_add(h3);
h1 = h1.wrapping_add(h4);
h2 = h2.wrapping_add(h1);
h3 = h3.wrapping_add(h1);
h4 = h4.wrapping_add(h1);
h1 = fmix32(h1);
h2 = fmix32(h2);
h3 = fmix32(h3);
h4 = fmix32(h4);
h1 = h1.wrapping_add(h2);
h1 = h1.wrapping_add(h3);
h1 = h1.wrapping_add(h4);
h2 = h2.wrapping_add(h1);
h3 = h3.wrapping_add(h1);
h4 = h4.wrapping_add(h1);
let x = ((h4 as u128) << 96) | ((h3 as u128) << 64) | ((h2 as u128) << 32) | h1 as u128;
return Ok(x);
} else {
let mut k1 = 0;
let mut k2 = 0;
let mut k3 = 0;
let mut k4 = 0;
if read >= 15 {
k4 ^= (buf[14] as u32).shl(16);
}
if read >= 14 {
k4 ^= (buf[13] as u32).shl(8);
}
if read >= 13 {
k4 ^= buf[12] as u32;
k4 = k4.wrapping_mul(C4).rotate_left(18).wrapping_mul(C1);
h4 ^= k4;
}
if read >= 12 {
k3 ^= (buf[11] as u32).shl(24);
}
if read >= 11 {
k3 ^= (buf[10] as u32).shl(16);
}
if read >= 10 {
k3 ^= (buf[9] as u32).shl(8);
}
if read >= 9 {
k3 ^= buf[8] as u32;
k3 = k3.wrapping_mul(C3).rotate_left(17).wrapping_mul(C4);
h3 ^= k3;
}
if read >= 8 {
k2 ^= (buf[7] as u32).shl(24);
}
if read >= 7 {
k2 ^= (buf[6] as u32).shl(16);
}
if read >= 6 {
k2 ^= (buf[5] as u32).shl(8);
}
if read >= 5 {
k2 ^= buf[4] as u32;
k2 = k2.wrapping_mul(C2).rotate_left(16).wrapping_mul(C3);
h2 ^= k2;
}
if read >= 4 {
k1 ^= (buf[3] as u32).shl(24);
}
if read >= 3 {
k1 ^= (buf[2] as u32).shl(16);
}
if read >= 2 {
k1 ^= (buf[1] as u32).shl(8);
}
if read >= 1 {
k1 ^= buf[0] as u32;
}
k1 = k1.wrapping_mul(C1);
k1 = k1.rotate_left(15);
k1 = k1.wrapping_mul(C2);
h1 ^= k1;
}
}
}
fn fmix32(k: u32) -> u32 {
const C1: u32 = 0x85eb_ca6b;
const C2: u32 = 0xc2b2_ae35;
const R1: u32 = 16;
const R2: u32 = 13;
let mut tmp = k;
tmp ^= tmp >> R1;
tmp = tmp.wrapping_mul(C1);
tmp ^= tmp >> R2;
tmp = tmp.wrapping_mul(C2);
tmp ^= tmp >> R1;
tmp
}

View file

@ -1,847 +0,0 @@
/*
* Copyright (c) 2020, 2021, 2022 Valve Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use crate::format_hash;
use crate::HASH_SEED;
use crate::box_array;
use crate::copy_into_array;
use crate::BufferedReader;
use crate::discarding_disabled;
use crate::steam_compat_shader_path;
use crate::touch_file;
use gst::glib;
use gst::prelude::*;
use gst::subclass::prelude::*;
use gst::EventView;
use gst::QueryViewMut;
use std::sync::Mutex;
use std::fs;
use std::io;
use std::io::Read;
use std::fs::OpenOptions;
#[cfg(target_arch = "x86")]
use crate::murmur3_x86_128::murmur3_x86_128 as murmur3_128;
#[cfg(target_arch = "x86_64")]
use crate::murmur3_x64_128::murmur3_x64_128 as murmur3_128;
use crate::fossilize;
use once_cell::sync::Lazy;
/* Algorithm
* ---------
*
* Nicely, both Quartz and Media Foundation allow us random access to the entire data stream. So we
* can easily hash the entire incoming stream and substitute it with our Ogg Theora video. If there
* is a cache miss, then we dump the entire incoming stream. In case of a cache hit, we dump
* nothing.
*
* Incoming video data is stored in the video.foz Fossilize database.
*
* Transcoded video data is stored in the transcoded_video.foz Fossilize database.
*
*
* Hashing algorithm
* -----------------
*
* We use murmur3 hash with the seed given below. We use the x32 variant for 32-bit programs, and
* the x64 variant for 64-bit programs.
*
* For speed when hashing, we specify a stride which will skip over chunks of the input. However,
* we will always hash the first "stride" number of bytes, to try to avoid collisions on smaller
* files with size between chunk and stride.
*
* For example, the 'H's below are hashed, the 'x's are skipped:
*
* let chunk = 4;
* let stride = chunk * 3;
* H = hashed, x = skipped
* [HHHH HHHH HHHH HHHH xxxx xxxx HHHH xxxx xxxx HHHH xxxx] < data stream
* ^^^^ ^^^^ ^^^^ stride prefix, hashed
* ^^^^ chunk
* ^^^^ ^^^^ ^^^^ stride
* ^^^^ chunk
* ^^^^ ^^^^ ^^^^ stride
* ^^^^ chunk
* ^^^^ ^^^^ stride
*/
/* changing any of these will invalidate the cache. you MUST clear it. */
const HASH_CHUNK_SIZE: usize = 8 * 1024 /* to kiB */ * 1024 /* to MiB */;
const HASH_STRIDE: usize = 6 * HASH_CHUNK_SIZE;
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
gst::DebugCategory::new(
"protonvideoconverter",
gst::DebugColorFlags::empty(),
Some("Proton video converter"))
});
const VIDEOCONV_FOZ_TAG_VIDEODATA: u32 = 0;
const VIDEOCONV_FOZ_TAG_OGVDATA: u32 = 1;
const VIDEOCONV_FOZ_TAG_STREAM: u32 = 2;
const VIDEOCONV_FOZ_TAG_MKVDATA: u32 = 3;
const VIDEOCONV_FOZ_NUM_TAGS: usize = 4;
struct VideoConverterDumpFozdb {
fozdb: Option<fossilize::StreamArchive>,
already_cleaned: bool,
}
impl VideoConverterDumpFozdb {
fn new() -> Self {
Self {
fozdb: None,
already_cleaned: false,
}
}
fn open(&mut self, create: bool) -> &mut Self {
if self.fozdb.is_none() {
let dump_file_path = match std::env::var("MEDIACONV_VIDEO_DUMP_FILE") {
Err(_) => { return self; },
Ok(c) => c,
};
let dump_file_path = std::path::Path::new(&dump_file_path);
if fs::create_dir_all(&dump_file_path.parent().unwrap()).is_err() {
return self;
}
match fossilize::StreamArchive::new(&dump_file_path, OpenOptions::new().write(true).read(true).create(create), false /* read-only? */, VIDEOCONV_FOZ_NUM_TAGS) {
Ok(newdb) => {
self.fozdb = Some(newdb);
},
Err(_) => {
return self;
},
}
}
self
}
fn close(&mut self) {
self.fozdb = None
}
fn discard_transcoded(&mut self) {
if self.already_cleaned {
return;
}
if discarding_disabled() {
self.already_cleaned = true;
return;
}
if let Some(fozdb) = &mut self.open(false).fozdb {
if let Ok(read_fozdb_path) = std::env::var("MEDIACONV_VIDEO_TRANSCODED_FILE") {
if let Ok(read_fozdb) = fossilize::StreamArchive::new(&read_fozdb_path, OpenOptions::new().read(true), true /* read-only? */, VIDEOCONV_FOZ_NUM_TAGS) {
let mut chunks = Vec::<(u32, u128)>::new();
for stream_id in fozdb.iter_tag(VIDEOCONV_FOZ_TAG_STREAM).cloned().collect::<Vec<u128>>() {
if read_fozdb.has_entry(VIDEOCONV_FOZ_TAG_MKVDATA, stream_id) {
if let Ok(chunks_size) = fozdb.entry_size(VIDEOCONV_FOZ_TAG_STREAM, stream_id) {
let mut buf = vec![0u8; chunks_size].into_boxed_slice();
if fozdb.read_entry(VIDEOCONV_FOZ_TAG_STREAM, stream_id, 0, &mut buf, fossilize::CRCCheck::WithCRC).is_ok() {
for i in 0..(chunks_size / 16) {
let offs = i * 16;
let chunk_id = u128::from_le_bytes(copy_into_array(&buf[offs..offs + 16]));
chunks.push((VIDEOCONV_FOZ_TAG_VIDEODATA, chunk_id));
}
}
}
chunks.push((VIDEOCONV_FOZ_TAG_STREAM, stream_id));
}
}
if fozdb.discard_entries(&chunks).is_err() {
self.close();
}
}
}
}
self.already_cleaned = true;
}
}
static DUMP_FOZDB: Lazy<Mutex<VideoConverterDumpFozdb>> = Lazy::new(|| {
Mutex::new(VideoConverterDumpFozdb::new())
});
struct PadReader<'a> {
pad: &'a gst::Pad,
offs: usize,
chunk: Box<[u8; HASH_CHUNK_SIZE]>,
chunk_offs: usize,
chunk_end: usize,
stride: usize, /* set to usize::max_value() to skip no bytes */
}
impl<'a> PadReader<'a> {
fn new_with_stride(pad: &'a gst::Pad, stride: usize) -> Self {
PadReader {
pad,
offs: 0,
chunk: box_array![0u8; HASH_CHUNK_SIZE],
chunk_offs: 0,
chunk_end: 0,
stride
}
}
fn new(pad: &'a gst::Pad) -> Self {
Self::new_with_stride(pad, usize::max_value())
}
}
impl<'a> Read for PadReader<'a> {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
if self.chunk_offs >= self.chunk_end {
self.chunk_offs = 0;
self.chunk_end = 0;
let buf = self.pad.pull_range(self.offs as u64, HASH_CHUNK_SIZE as u32);
match buf {
Err(err) => {
/* on Eos, keep going; we'll return later */
if err != gst::FlowError::Eos {
return Err(io::Error::new(io::ErrorKind::Other, "upstream pull_range failed" /* TODO can we print our gst err here? */));
}
},
Ok(buf) => {
let to_copy;
if self.offs + buf.size() < self.stride {
to_copy = buf.size();
self.offs += to_copy;
}else if self.offs < self.stride {
to_copy = self.stride - self.offs;
self.offs = self.stride;
}else{
to_copy = buf.size();
self.offs += self.stride;
};
if out.len() >= to_copy {
/* copy directly into out buffer and return */
return Ok(
match buf.copy_to_slice(0, &mut out[0..to_copy]) {
Err(c) => c,
Ok(_) => to_copy,
});
} else {
self.chunk_end = match buf.copy_to_slice(0, &mut (*self.chunk)[0..to_copy]) {
Err(c) => c,
Ok(_) => to_copy,
};
}
}
}
}
if self.chunk_offs >= self.chunk_end {
return Ok(0);
}
let to_copy = std::cmp::min(self.chunk_end - self.chunk_offs, out.len());
if to_copy == 0 {
return Ok(0);
}
out[..to_copy].copy_from_slice(&self.chunk[self.chunk_offs..(self.chunk_offs + to_copy)]);
self.chunk_offs += to_copy;
Ok(to_copy)
}
}
struct VideoConvState {
transcode_hash: Option<u128>,
read_fozdb: Option<fossilize::StreamArchive>,
upstream_duration: Option<u64>,
our_duration: Option<u64>,
transcoded_tag: u32,
need_stream_start: bool,
}
impl VideoConvState {
fn new() -> VideoConvState {
let read_fozdb_path = std::env::var("MEDIACONV_VIDEO_TRANSCODED_FILE");
if read_fozdb_path.is_err() {
gst::error!(CAT, "MEDIACONV_VIDEO_TRANSCODED_FILE is not set!")
}
let read_fozdb = match read_fozdb_path {
Ok(path) => match fossilize::StreamArchive::new(&path,
OpenOptions::new().read(true),
true /* read-only? */,
VIDEOCONV_FOZ_NUM_TAGS)
{
Ok(s) => Some(s),
Err(_) => None
},
Err(_) => None
};
VideoConvState {
transcode_hash: None,
read_fozdb,
upstream_duration: None,
our_duration: None,
transcoded_tag: VIDEOCONV_FOZ_TAG_MKVDATA,
need_stream_start: true,
}
}
/* true if the file is transcoded; false if not */
fn begin_transcode(&mut self, hash: u128) -> bool {
if let Some(read_fozdb) = &mut self.read_fozdb {
if let Ok(transcoded_size) = read_fozdb.entry_size(VIDEOCONV_FOZ_TAG_MKVDATA, hash) {
gst::log!(CAT, "Found an MKV video for hash {}", format_hash(hash));
self.transcode_hash = Some(hash);
self.our_duration = Some(transcoded_size as u64);
self.transcoded_tag = VIDEOCONV_FOZ_TAG_MKVDATA;
return true;
}
if let Ok(transcoded_size) = read_fozdb.entry_size(VIDEOCONV_FOZ_TAG_OGVDATA, hash) {
gst::log!(CAT, "Found an OGV video for hash {}", format_hash(hash));
self.transcode_hash = Some(hash);
self.our_duration = Some(transcoded_size as u64);
self.transcoded_tag = VIDEOCONV_FOZ_TAG_OGVDATA;
return true;
}
}
gst::log!(CAT, "No transcoded video for {}. Substituting a blank video.", format_hash(hash));
self.transcode_hash = None;
self.our_duration = Some(include_bytes!("../../blank.mkv").len() as u64);
match steam_compat_shader_path() {
None => gst::log!(CAT, "env STEAM_COMPAT_SHADER_PATH not set"),
Some(mut path) => {
path.push("placeholder-video-used");
if let Err(e) = touch_file(path) { gst::log!(CAT, "Failed to touch placeholder-video-used file: {:?}", e) }
},
};
false
}
fn fill_buffer(&mut self, offs: usize, out: &mut [u8]) -> Result<usize, gst::LoggableError> {
match self.transcode_hash {
Some(hash) => {
let read_fozdb = self.read_fozdb.as_mut().unwrap();
read_fozdb.read_entry(self.transcoded_tag, hash, offs as u64, out, fossilize::CRCCheck::WithoutCRC)
.map_err(|e| loggable_error!(CAT, "Error reading ogvdata: {:?}", e))
},
None => {
let blank = include_bytes!("../../blank.mkv");
let to_copy = std::cmp::min(blank.len() - offs, out.len());
out[..to_copy].copy_from_slice(&blank[offs..(offs + to_copy)]);
Ok(to_copy)
},
}
}
}
pub struct VideoConv {
state: Mutex<Option<VideoConvState>>,
sinkpad: gst::Pad,
srcpad: gst::Pad,
}
#[glib::object_subclass]
impl ObjectSubclass for VideoConv {
const NAME: &'static str = "ProtonVideoConverter";
type Type = super::VideoConv;
type ParentType = gst::Element;
fn with_class(klass: &Self::Class) -> Self {
let templ = klass.pad_template("sink").unwrap();
let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink"))
.event_function(|pad, parent, event| {
VideoConv::catch_panic_pad_function(
parent,
|| false,
|videoconv| videoconv.sink_event(pad, event)
)
}).build();
let templ = klass.pad_template("src").unwrap();
let srcpad = gst::Pad::builder_with_template(&templ, Some("src"))
.getrange_function(|pad, parent, offset, in_buf, size| {
VideoConv::catch_panic_pad_function(
parent,
|| Err(gst::FlowError::Error),
|videoconv| videoconv.range(pad, offset, in_buf, size)
)
})
.query_function(|pad, parent, query| {
VideoConv::catch_panic_pad_function(
parent,
|| false,
|videoconv| videoconv.src_query(pad, query)
)
})
.activatemode_function(|pad, parent, mode, active| {
VideoConv::catch_panic_pad_function(
parent,
|| Err(loggable_error!(CAT, "Panic activating srcpad with mode")),
|videoconv| videoconv.src_activatemode(pad, mode, active)
)
}).build();
VideoConv {
state: Mutex::new(None),
sinkpad,
srcpad,
}
}
}
impl ObjectImpl for VideoConv {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.add_pad(&self.sinkpad).unwrap();
obj.add_pad(&self.srcpad).unwrap();
}
}
impl GstObjectImpl for VideoConv { }
impl ElementImpl for VideoConv {
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
gst::subclass::ElementMetadata::new(
"Proton video converter",
"Codec/Demuxer",
"Converts video for Proton",
"Andrew Eikum <aeikum@codeweavers.com>"
)
});
Some(&*ELEMENT_METADATA)
}
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
let mut caps = gst::Caps::new_empty();
{
let caps = caps.get_mut().unwrap();
caps.append(gst::Caps::builder("video/x-ms-asf").build());
caps.append(gst::Caps::builder("video/x-msvideo").build());
caps.append(gst::Caps::builder("video/mpeg").build());
caps.append(gst::Caps::builder("video/quicktime").build());
}
let sink_pad_template = gst::PadTemplate::new(
"sink",
gst::PadDirection::Sink,
gst::PadPresence::Always,
&caps).unwrap();
let mut caps = gst::Caps::new_empty();
{
let caps = caps.get_mut().unwrap();
caps.append(gst::Caps::builder("video/x-matroska").build());
caps.append(gst::Caps::builder("application/ogg").build());
}
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&caps).unwrap();
vec![src_pad_template, sink_pad_template]
});
PAD_TEMPLATES.as_ref()
}
fn change_state(
&self,
transition: gst::StateChange
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
gst::log!(CAT, imp: self, "State transition: {:?}", transition);
match transition {
gst::StateChange::NullToReady => {
/* do runtime setup */
let new_state = VideoConvState::new();
let mut state = self.state.lock().unwrap();
assert!((*state).is_none());
*state = Some(new_state);
},
gst::StateChange::ReadyToNull => {
/* do runtime teardown */
let _ = self.state.lock().unwrap().take(); // dispose of state
},
_ => (),
};
self.parent_change_state(transition)
/* XXX on ReadyToNull, sodium drops state _again_ here... why? */
}
}
struct StreamSerializer<'a> {
stream: &'a [u128],
cur_idx: usize,
}
impl<'a> StreamSerializer<'a> {
fn new(stream: &'a [u128]) -> Self {
StreamSerializer {
stream,
cur_idx: 0,
}
}
}
impl <'a> Read for StreamSerializer<'a> {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
if self.cur_idx >= self.stream.len() {
return Ok(0)
}
out[0..std::mem::size_of::<u128>()].copy_from_slice(&self.stream[self.cur_idx].to_le_bytes());
self.cur_idx += 1;
Ok(std::mem::size_of::<u128>())
}
}
impl VideoConv {
fn get_upstream_range(&self, offset: u64, requested_size: u32) -> Result<(u64, u32), gst::FlowError> {
let mut state = self.state.lock().unwrap();
let state = match &mut *state {
Some(s) => s,
None => { return Err(gst::FlowError::Error); }
};
if state.upstream_duration.is_none() {
self.query_upstream_duration(state);
}
let ups_offset = self.duration_ours_to_upstream(state, offset).unwrap();
let ups_requested_size = self.duration_ours_to_upstream(state, requested_size as u64).unwrap() as u32;
Ok((ups_offset, ups_requested_size))
}
fn range(
&self,
_pad: &gst::Pad,
offset: u64,
in_buf: Option<&mut gst::BufferRef>,
requested_size: u32,
) -> Result<gst::PadGetRangeSuccess, gst::FlowError> {
let (ups_offset, ups_requested_size) = self.get_upstream_range(offset, requested_size)?;
let mut state = self.state.lock().unwrap();
let state = match &mut *state {
Some(s) => s,
None => { return Err(gst::FlowError::Error); }
};
/* read and ignore upstream bytes */
self.sinkpad.pull_range(ups_offset, ups_requested_size)?;
match in_buf {
Some(buf) => {
let readed;
{
let mut map = buf.map_writable().unwrap();
readed = state.fill_buffer(offset as usize, map.as_mut())
.map_err(|e| { e.log(); gst::FlowError::Error })?;
}
if readed > 0 || buf.size() == 0 {
buf.set_size(readed);
return Ok(gst::PadGetRangeSuccess::FilledBuffer);
}
Err(gst::FlowError::Eos)
},
None => {
/* XXX: can we use a buffer cache here? */
let b = gst::Buffer::with_size(requested_size as usize)
.map_err(|_| gst::FlowError::Error)?;
let mut map = b.into_mapped_buffer_writable().unwrap();
let readed = state.fill_buffer(offset as usize, map.as_mut())
.map_err(|e| { e.log(); gst::FlowError::Error })?;
let mut b = map.into_buffer();
if readed > 0 || b.size() == 0 {
b.get_mut().unwrap().set_size(readed);
return Ok(gst::PadGetRangeSuccess::NewBuffer(b));
}
Err(gst::FlowError::Eos)
}
}
}
fn sink_event(
&self,
pad: &gst::Pad,
event: gst::Event
) -> bool {
gst::log!(CAT, obj:pad, "Got an event {:?}", event);
match event.view() {
EventView::Caps(_) => {
/* push_event, below, can also grab state and cause a deadlock, so make sure it's
* released before calling */
let caps = {
let mut state = self.state.lock().unwrap();
let state = match &mut *state {
Some(s) => s,
None => { gst::error!(CAT, "VideoConv not yet in READY state?"); return false; },
};
if self.sinkpad.activate_mode(gst::PadMode::Pull, true).is_err() {
gst::error!(CAT, "Failed to activate sinkpad in pull mode");
return false;
}
self.init_transcode(state);
match state.transcoded_tag {
VIDEOCONV_FOZ_TAG_MKVDATA => gst::Caps::builder("video/x-matroska").build(),
VIDEOCONV_FOZ_TAG_OGVDATA => gst::Caps::builder("application/ogg").build(),
_ => { return false; },
}
};
self.srcpad.push_event(gst::event::Caps::new(&caps))
}
_ => gst::Pad::event_default(pad, Some(&*self.obj()), event)
}
}
fn query_upstream_duration(&self, state: &mut VideoConvState) {
let mut query = gst::query::Duration::new(gst::Format::Bytes);
if self.sinkpad.peer_query(&mut query) {
state.upstream_duration = match query.result() {
gst::GenericFormattedValue::Bytes(Some(size)) => Some(*size),
_ => None,
}
}else{
gst::warning!(CAT, "upstream duration query failure");
}
}
fn duration_ours_to_upstream(&self, state: &VideoConvState, pos: u64) -> Option<u64> {
if let Some(our) = state.our_duration {
if let Some(upstream) = state.upstream_duration {
return Some(pos * upstream / our);
}
}
None
}
fn src_query(
&self,
pad: &gst::Pad,
query: &mut gst::QueryRef) -> bool
{
gst::log!(CAT, obj: pad, "got query: {:?}", query);
match query.view_mut() {
QueryViewMut::Scheduling(q) => {
let mut peer_query = gst::query::Scheduling::new();
let res = self.sinkpad.peer_query(&mut peer_query);
if ! res {
return res;
}
let (flags, min, max, align) = peer_query.result();
q.set(flags, min, max, align);
q.add_scheduling_modes(&[gst::PadMode::Pull]);
true
},
QueryViewMut::Duration(ref mut q) => {
if q.format() != gst::Format::Bytes { return false };
let mut state = self.state.lock().unwrap();
let state = match &mut *state {
Some(s) => s,
None => { return false; }
};
if state.upstream_duration.is_none() {
self.query_upstream_duration(state);
}
if let Some(sz) = state.our_duration {
q.set(gst::format::Bytes::from_u64(sz));
return true
}
false
}
_ => gst::Pad::query_default(pad, Some(&*self.obj()), query)
}
}
fn hash_upstream_data(&self) -> io::Result<u128> {
murmur3_128(&mut PadReader::new_with_stride(&self.sinkpad, HASH_STRIDE), HASH_SEED)
}
fn dump_upstream_data(&self, hash: u128) -> io::Result<()> {
let db = &mut (*DUMP_FOZDB).lock().unwrap();
let mut db = &mut db.open(true).fozdb;
let db = match &mut db {
Some(d) => d,
None => { gst::error!(CAT, "Unable to open fozdb!"); return Err(io::Error::new(io::ErrorKind::Other, "unable to open fozdb")); },
};
let mut chunks = Vec::<u128>::new();
let mut reader = PadReader::new(&self.sinkpad);
let mut buf = box_array![0u8; HASH_CHUNK_SIZE];
loop {
let readed = reader.read(&mut *buf)?;
if readed == 0 {
break;
}
let chunk_hash = murmur3_128(&mut BufferedReader::new(&*buf, readed), HASH_SEED)?;
chunks.push(chunk_hash);
db.write_entry(VIDEOCONV_FOZ_TAG_VIDEODATA, chunk_hash, &mut BufferedReader::new(&*buf, readed), fossilize::CRCCheck::WithCRC)
.map_err(|e| { gst::warning!(CAT, "Error writing video data to fozdb: {:?}", e); io::Error::new(io::ErrorKind::Other, "error writing video data to fozdb") } )?;
}
db.write_entry(VIDEOCONV_FOZ_TAG_STREAM, hash, &mut StreamSerializer::new(&chunks), fossilize::CRCCheck::WithCRC)
.map_err(|e| { gst::warning!(CAT, "Error writing stream data to fozdb: {:?}", e); io::Error::new(io::ErrorKind::Other, "error writing stream data to fozdb") } )?;
Ok(())
}
fn init_transcode(&self, state: &mut VideoConvState) {
if state.transcode_hash.is_none() {
(*DUMP_FOZDB).lock().unwrap().discard_transcoded();
let hash = self.hash_upstream_data();
if let Ok(hash) = hash {
if !state.begin_transcode(hash) {
match self.dump_upstream_data(hash) {
Ok(_) => { },
Err(e) => { gst::error!(CAT, "{}", e.to_string())}
}
}
}
}
}
fn src_activatemode(
&self,
_pad: &gst::Pad,
mode: gst::PadMode,
active: bool
) -> Result<(), gst::LoggableError> {
self.sinkpad
.activate_mode(mode, active)?;
if mode == gst::PadMode::Pull {
let need_stream_start;
let hash;
/* push_event, below, can also grab state and cause a deadlock, so make sure it's
* released before calling */
match &mut *self.state.lock().unwrap() {
Some(state) => {
self.init_transcode(state);
need_stream_start = state.need_stream_start;
hash = state.transcode_hash;
},
None => { return Err(loggable_error!(CAT, "VideoConv not yet in READY state?")); }
};
if need_stream_start && active && hash.is_some() {
let stream_id = format!("{:032x}", hash.unwrap());
self.srcpad.push_event(gst::event::StreamStart::new(&stream_id));
match &mut *self.state.lock().unwrap() {
Some(state) => { state.need_stream_start = false },
None => { return Err(loggable_error!(CAT, "VideoConv not yet in READY state?")); }
};
}
}
Ok(())
}
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2020, 2021, 2022 Valve Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
use gst::glib;
use gst::prelude::*;
mod imp;
glib::wrapper! {
pub struct VideoConv(ObjectSubclass<imp::VideoConv>) @extends gst::Element, gst::Object;
}
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
gst::Element::register(
Some(plugin),
"protonvideoconverter",
gst::Rank::Marginal,
VideoConv::static_type())
}