media-converter: Remove media-converter.
Remove it since it has been integrated into winegstreamer. CW-Bug-Id: #23225
This commit is contained in:
parent
d72fb93507
commit
da36c977f9
19 changed files with 0 additions and 4547 deletions
21
Makefile.in
21
Makefile.in
|
@ -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
|
||||||
##
|
##
|
||||||
|
|
791
media-converter/Cargo.lock
generated
791
media-converter/Cargo.lock
generated
|
@ -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"
|
|
|
@ -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" }
|
|
|
@ -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 $@
|
|
|
@ -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.
|
@ -1,5 +0,0 @@
|
||||||
extern crate gst_plugin_version_helper;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
gst_plugin_version_helper::info()
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
);
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
}
|
|
Loading…
Reference in a new issue