diff --git a/Cargo.lock b/Cargo.lock index d4bc9d7d..d00eaf0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,9 +90,9 @@ checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-tzdata" @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "arbitrary" @@ -177,7 +177,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -188,9 +188,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-channel" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" dependencies = [ "concurrent-queue", "event-listener", @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07dbbf24db18d609b1462965249abdf49129ccad073ec257da372adc83259c60" +checksum = "4e9eabd7a98fe442131a17c316bd9349c43695e49e730c3c8e12cfb5f4da2693" dependencies = [ "flate2", "futures-core", @@ -214,13 +214,13 @@ dependencies = [ [[package]] name = "async-recursion" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -242,24 +242,24 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] name = "async-task" -version = "4.7.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -914,15 +914,15 @@ checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "built" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d17f4d6e4dc36d1a02fbedc2753a096848e7c1b0772f7654eab8e2c927dd53" +checksum = "41bfbdb21256b87a8b5e80fab81a8eed158178e812fd7ba451907518b2742f16" [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-slice-cast" @@ -960,8 +960,8 @@ dependencies = [ [[package]] name = "cairo-rs" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "bitflags 2.5.0", "cairo-sys-rs", @@ -972,8 +972,8 @@ dependencies = [ [[package]] name = "cairo-sys-rs" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "glib-sys", "libc", @@ -982,12 +982,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.91" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd97381a8cc6493395a5afc4c691c1084b3768db713b73aa215217aa245d153" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -1008,9 +1009,9 @@ dependencies = [ [[package]] name = "cea708-types" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7f33493cb6f19aa19c6e688708f66bf792bc2c75137da2a03c7ebbdf7a44f9" +checksum = "82b825228dce83e7156c7cd189bcfe5ef8014320deca7dd619787fe594946426" dependencies = [ "env_logger 0.10.2", "log", @@ -1021,9 +1022,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", "target-lexicon", @@ -1037,9 +1038,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1047,7 +1048,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1090,7 +1091,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -1134,9 +1135,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -1364,7 +1365,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -1375,14 +1376,14 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] name = "dash-mpd" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cafa2c33eff2857e1a14c38aa9a432aa565a01e77804a541fce7aec3affb8f8" +checksum = "79b4bdd5f1c0c7493d780c645f0bff5b9361e6408210fa88910adb181efca64c" dependencies = [ "base64 0.22.0", "base64-serde", @@ -1402,19 +1403,6 @@ dependencies = [ "xattr", ] -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.3", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "dasp_frame" version = "0.11.0" @@ -1432,9 +1420,9 @@ checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "dav1d" @@ -1504,9 +1492,9 @@ dependencies = [ [[package]] name = "dssim-core" -version = "3.2.8" +version = "3.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fafad37c1f4f168243f3ac1b4cae0d358c528ac695670100337314e38d54b486" +checksum = "0c074fca6cdf5e3faaaf03f71e29cd5d92ea533b1432cf78910dafffc2ce872b" dependencies = [ "imgref", "itertools 0.12.1", @@ -1549,9 +1537,9 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "elliptic-curve" @@ -1575,9 +1563,9 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -1636,9 +1624,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ "event-listener", "pin-project-lite", @@ -1652,9 +1640,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdeflate" @@ -1703,9 +1691,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "4556222738635b7a3417ae6130d8f52201e45a0c4d1a907f0826383adb5f85e7" dependencies = [ "crc32fast", "miniz_oxide", @@ -1832,7 +1820,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -1867,8 +1855,8 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "gdk-pixbuf-sys", "gio", @@ -1878,8 +1866,8 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "gio-sys", "glib-sys", @@ -1890,8 +1878,8 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -1904,8 +1892,8 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1920,8 +1908,8 @@ dependencies = [ [[package]] name = "gdk4-wayland" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "gdk4", "gdk4-wayland-sys", @@ -1932,8 +1920,8 @@ dependencies = [ [[package]] name = "gdk4-wayland-sys" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "glib-sys", "libc", @@ -1942,8 +1930,8 @@ dependencies = [ [[package]] name = "gdk4-win32" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "gdk4", "gdk4-win32-sys", @@ -1955,8 +1943,8 @@ dependencies = [ [[package]] name = "gdk4-win32-sys" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "gdk4-sys", "glib-sys", @@ -1966,8 +1954,8 @@ dependencies = [ [[package]] name = "gdk4-x11" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "gdk4", "gdk4-x11-sys", @@ -1978,8 +1966,8 @@ dependencies = [ [[package]] name = "gdk4-x11-sys" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "gdk4-sys", "glib-sys", @@ -2037,8 +2025,8 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gio" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "futures-channel", "futures-core", @@ -2054,8 +2042,8 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "glib-sys", "gobject-sys", @@ -2066,8 +2054,8 @@ dependencies = [ [[package]] name = "glib" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "bitflags 2.5.0", "futures-channel", @@ -2087,20 +2075,20 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "heck 0.5.0", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] name = "glib-sys" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "libc", "system-deps", @@ -2114,8 +2102,8 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "gobject-sys" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "glib-sys", "libc", @@ -2124,8 +2112,8 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "glib", "graphene-sys", @@ -2134,8 +2122,8 @@ dependencies = [ [[package]] name = "graphene-sys" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "glib-sys", "libc", @@ -2156,8 +2144,8 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "cairo-rs", "gdk4", @@ -2170,8 +2158,8 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -2424,6 +2412,7 @@ dependencies = [ "gdk4-x11", "gst-plugin-version-helper", "gstreamer", + "gstreamer-allocators", "gstreamer-base", "gstreamer-gl", "gstreamer-gl-egl", @@ -2647,11 +2636,11 @@ dependencies = [ "gstreamer-base", "headers 0.4.0", "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "mime", "once_cell", "pin-project-lite", - "reqwest 0.12.3", + "reqwest 0.12.4", "tokio", "url", ] @@ -2844,7 +2833,7 @@ name = "gst-plugin-version-helper" version = "0.8.2" dependencies = [ "chrono", - "toml_edit 0.22.9", + "toml_edit 0.22.12", ] [[package]] @@ -2979,14 +2968,14 @@ dependencies = [ "gstreamer-webrtc", "once_cell", "parse_link_header", - "reqwest 0.12.3", + "reqwest 0.12.4", "tokio", ] [[package]] name = "gstreamer" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "cfg-if", "futures-channel", @@ -3009,10 +2998,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "gstreamer-allocators" +version = "0.22.4" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" +dependencies = [ + "glib", + "gstreamer", + "gstreamer-allocators-sys", + "libc", + "once_cell", +] + +[[package]] +name = "gstreamer-allocators-sys" +version = "0.22.4" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + [[package]] name = "gstreamer-app" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "futures-core", "futures-sink", @@ -3026,7 +3039,7 @@ dependencies = [ [[package]] name = "gstreamer-app-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gstreamer-base-sys", @@ -3038,7 +3051,7 @@ dependencies = [ [[package]] name = "gstreamer-audio" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "cfg-if", "glib", @@ -3054,7 +3067,7 @@ dependencies = [ [[package]] name = "gstreamer-audio-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gobject-sys", @@ -3067,7 +3080,7 @@ dependencies = [ [[package]] name = "gstreamer-base" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "atomic_refcell", "cfg-if", @@ -3080,7 +3093,7 @@ dependencies = [ [[package]] name = "gstreamer-base-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gobject-sys", @@ -3092,7 +3105,7 @@ dependencies = [ [[package]] name = "gstreamer-check" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib", "gstreamer", @@ -3102,7 +3115,7 @@ dependencies = [ [[package]] name = "gstreamer-check-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gobject-sys", @@ -3114,7 +3127,7 @@ dependencies = [ [[package]] name = "gstreamer-gl" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib", "gstreamer", @@ -3128,7 +3141,7 @@ dependencies = [ [[package]] name = "gstreamer-gl-egl" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib", "gstreamer", @@ -3140,7 +3153,7 @@ dependencies = [ [[package]] name = "gstreamer-gl-egl-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gstreamer-gl-sys", @@ -3151,7 +3164,7 @@ dependencies = [ [[package]] name = "gstreamer-gl-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gobject-sys", @@ -3165,7 +3178,7 @@ dependencies = [ [[package]] name = "gstreamer-gl-wayland" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib", "gstreamer", @@ -3177,7 +3190,7 @@ dependencies = [ [[package]] name = "gstreamer-gl-wayland-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gstreamer-gl-sys", @@ -3188,7 +3201,7 @@ dependencies = [ [[package]] name = "gstreamer-gl-x11" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib", "gstreamer", @@ -3200,7 +3213,7 @@ dependencies = [ [[package]] name = "gstreamer-gl-x11-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gstreamer-gl-sys", @@ -3211,7 +3224,7 @@ dependencies = [ [[package]] name = "gstreamer-net" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "gio", "glib", @@ -3222,7 +3235,7 @@ dependencies = [ [[package]] name = "gstreamer-net-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "gio-sys", "glib-sys", @@ -3234,7 +3247,7 @@ dependencies = [ [[package]] name = "gstreamer-pbutils" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib", "gstreamer", @@ -3248,7 +3261,7 @@ dependencies = [ [[package]] name = "gstreamer-pbutils-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gobject-sys", @@ -3262,7 +3275,7 @@ dependencies = [ [[package]] name = "gstreamer-rtp" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib", "gstreamer", @@ -3273,7 +3286,7 @@ dependencies = [ [[package]] name = "gstreamer-rtp-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gstreamer-base-sys", @@ -3285,7 +3298,7 @@ dependencies = [ [[package]] name = "gstreamer-sdp" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib", "gstreamer", @@ -3295,7 +3308,7 @@ dependencies = [ [[package]] name = "gstreamer-sdp-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gstreamer-sys", @@ -3306,7 +3319,7 @@ dependencies = [ [[package]] name = "gstreamer-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gobject-sys", @@ -3317,7 +3330,7 @@ dependencies = [ [[package]] name = "gstreamer-utils" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "gstreamer", "gstreamer-app", @@ -3329,7 +3342,7 @@ dependencies = [ [[package]] name = "gstreamer-video" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "cfg-if", "futures-channel", @@ -3346,7 +3359,7 @@ dependencies = [ [[package]] name = "gstreamer-video-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gobject-sys", @@ -3359,7 +3372,7 @@ dependencies = [ [[package]] name = "gstreamer-webrtc" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib", "gstreamer", @@ -3371,7 +3384,7 @@ dependencies = [ [[package]] name = "gstreamer-webrtc-sys" version = "0.22.4" -source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#f2d3128bf9d0170288ce479bea021f0886617fac" +source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=0.22#a0a23c6f453ee206a20943b508a086a8fc35cb28" dependencies = [ "glib-sys", "gstreamer-sdp-sys", @@ -3382,8 +3395,8 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "cairo-rs", "field-offset", @@ -3402,21 +3415,19 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ - "anyhow", "proc-macro-crate", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.60", ] [[package]] name = "gtk4-sys" -version = "0.8.1" -source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#05d26f6a97a0ccf856b565b4fd796cdf57059084" +version = "0.8.2" +source = "git+https://github.com/gtk-rs/gtk4-rs?branch=0.8#cf84b5cd36fc1aa31175bc24ff45e8ceb0710dec" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -3477,9 +3488,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -3704,7 +3715,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.6", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -3713,9 +3724,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", @@ -3784,7 +3795,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "hyper-util", "native-tls", "tokio", @@ -3803,7 +3814,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.0", - "hyper 1.2.0", + "hyper 1.3.1", "pin-project-lite", "socket2 0.5.6", "tokio", @@ -3943,7 +3954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "serde", ] @@ -3955,7 +3966,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -4010,9 +4021,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -4366,9 +4377,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -4386,7 +4397,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -4672,7 +4683,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -4770,7 +4781,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -4825,8 +4836,8 @@ dependencies = [ [[package]] name = "pango" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "gio", "glib", @@ -4836,8 +4847,8 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "glib-sys", "gobject-sys", @@ -4847,8 +4858,8 @@ dependencies = [ [[package]] name = "pangocairo" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "cairo-rs", "glib", @@ -4859,8 +4870,8 @@ dependencies = [ [[package]] name = "pangocairo-sys" -version = "0.19.3" -source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#5e944f2eff8b344591666754f7ea3b359943ee8c" +version = "0.19.5" +source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#a7c5a9b6850f864cb3d30f679a827492b86247f1" dependencies = [ "cairo-sys-rs", "glib-sys", @@ -4877,9 +4888,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -4887,9 +4898,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "backtrace", "cfg-if", @@ -4898,7 +4909,7 @@ dependencies = [ "redox_syscall", "smallvec", "thread-id", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -4999,7 +5010,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5051,9 +5062,9 @@ checksum = "c135f38778ad324d9e9ee68690bac2c1a51f340fdf96ca13e2ab3914eb2e51d8" [[package]] name = "polling" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" dependencies = [ "cfg-if", "concurrent-queue", @@ -5093,7 +5104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5150,9 +5161,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -5173,7 +5184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5194,7 +5205,7 @@ checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" dependencies = [ "bytes", "heck 0.4.1", - "itertools 0.12.1", + "itertools 0.11.0", "log", "multimap 0.10.0", "once_cell", @@ -5203,7 +5214,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.58", + "syn 2.0.60", "tempfile", ] @@ -5214,10 +5225,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5282,9 +5293,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -5402,11 +5413,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] @@ -5501,9 +5512,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ "async-compression", "base64 0.22.0", @@ -5517,7 +5528,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "hyper-tls 0.6.0", "hyper-util", "ipnet", @@ -5657,9 +5668,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -5670,9 +5681,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", @@ -5713,9 +5724,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" [[package]] name = "rustls-webpki" @@ -5742,6 +5753,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96560eea317a9cc4e0bb1f6a2c93c09a19b8c4fc5cb3fcc0ec1c094cd783e2" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.23" @@ -5773,6 +5793,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sdd" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" + [[package]] name = "sdp-types" version = "0.1.6" @@ -5828,9 +5854,9 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" dependencies = [ "serde_derive", ] @@ -5846,20 +5872,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -5899,11 +5925,11 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.7.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "chrono", "hex", "indexmap 1.9.3", @@ -5917,39 +5943,39 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.7.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] name = "serial_test" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ad9342b3aaca7cb43c45c097dd008d4907070394bd0751a0aa8817e5a018d" +checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" dependencies = [ - "dashmap", "futures", - "lazy_static", "log", + "once_cell", "parking_lot", + "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212" +checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -6023,9 +6049,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -6158,15 +6184,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.58" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -6258,7 +6283,7 @@ checksum = "7ba277e77219e9eea169e8508942db1bf5d8a41ff2db9b20aab5a5aadc9fa25d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -6271,7 +6296,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -6288,22 +6313,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -6328,9 +6353,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -6349,9 +6374,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -6399,7 +6424,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -6482,7 +6507,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.9", + "toml_edit 0.22.12", ] [[package]] @@ -6507,15 +6532,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.9" +version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.5", + "winnow 0.6.7", ] [[package]] @@ -6566,7 +6591,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -6708,9 +6733,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "untrusted" @@ -6904,7 +6929,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", "wasm-bindgen-shared", ] @@ -6938,7 +6963,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6983,11 +7008,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -7002,7 +7027,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -7020,7 +7045,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -7040,17 +7065,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -7061,9 +7087,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -7073,9 +7099,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -7085,9 +7111,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -7097,9 +7129,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -7109,9 +7141,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -7121,9 +7153,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -7133,9 +7165,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" @@ -7148,9 +7180,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" dependencies = [ "memchr", ] @@ -7240,7 +7272,7 @@ checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -7251,7 +7283,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d334f7f1..562312b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,6 +130,7 @@ gdk-wayland = { package = "gdk4-wayland", git = "https://github.com/gtk-rs/gtk4- gdk-x11 = { package = "gdk4-x11", git = "https://github.com/gtk-rs/gtk4-rs", branch = "0.8", version = "0.8"} gdk-win32 = { package = "gdk4-win32", git = "https://github.com/gtk-rs/gtk4-rs", branch = "0.8", version = "0.8"} gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.22", version = "0.22" } +gst-allocators = { package = "gstreamer-allocators", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.22", version = "0.22" } gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.22", version = "0.22" } gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.22", version = "0.22" } gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.22", version = "0.22" } diff --git a/audio/audiofx/src/hrtfrender/imp.rs b/audio/audiofx/src/hrtfrender/imp.rs index 06d49689..5ca5253e 100644 --- a/audio/audiofx/src/hrtfrender/imp.rs +++ b/audio/audiofx/src/hrtfrender/imp.rs @@ -649,7 +649,7 @@ impl BaseTransformImpl for HrtfRender { if direction == gst::PadDirection::Sink { s.set("channels", 2); - s.set("channel-mask", 0x3); + s.set("channel-mask", gst::Bitmask(0x3)); } else { let settings = self.settings.lock().unwrap(); if let Some(objs) = &settings.spatial_objects { diff --git a/deny.toml b/deny.toml index e3d68df0..f348841c 100644 --- a/deny.toml +++ b/deny.toml @@ -102,11 +102,6 @@ version = "0.21" name = "socket2" version = "0.4" -# Various crates depend on an older version of syn -[[bans.skip]] -name = "syn" -version = "1.0" - # Various crates depend on an older version of bitflags [[bans.skip]] name = "bitflags" diff --git a/docs/plugins/gst_plugins_cache.json b/docs/plugins/gst_plugins_cache.json index b247e99e..9799df11 100644 --- a/docs/plugins/gst_plugins_cache.json +++ b/docs/plugins/gst_plugins_cache.json @@ -79,6 +79,18 @@ "type": "gchararray", "writable": true }, + "force-path-style": { + "blurb": "Force client to use path-style addressing for buckets", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, "hlssink": { "blurb": "The underlying HLS sink being used", "conditionally-available": false, @@ -315,6 +327,18 @@ "type": "gboolean", "writable": true }, + "force-path-style": { + "blurb": "Force client to use path-style addressing for buckets", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, "key": { "blurb": "The key of the file to write", "conditionally-available": false, @@ -529,6 +553,18 @@ "type": "gchararray", "writable": true }, + "force-path-style": { + "blurb": "Force client to use path-style addressing for buckets", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, "key": { "blurb": "The key of the file to write", "conditionally-available": false, @@ -747,6 +783,18 @@ "type": "gchararray", "writable": true }, + "force-path-style": { + "blurb": "Force client to use path-style addressing for buckets", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, "request-timeout": { "blurb": "Timeout for each S3 request (in ms, set to -1 for infinity)", "conditionally-available": false, @@ -2038,7 +2086,7 @@ "long-name": "ISOFMP4Mux", "pad-templates": { "sink_%%u": { - "caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp8:\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-av1:\n stream-format: obu-stream\n alignment: tu\n profile: { (string)main, (string)high, (string)professional }\n chroma-format: { (string)4:0:0, (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\n rate: [ 1, 2147483647 ]\n", + "caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp8:\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-av1:\n stream-format: obu-stream\n alignment: tu\n profile: { (string)main, (string)high, (string)professional }\n chroma-format: { (string)4:0:0, (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\n rate: [ 1, 2147483647 ]\naudio/x-flac:\n framed: true\n channels: [ 1, 8 ]\n rate: [ 1, 655350 ]\n", "direction": "sink", "presence": "request", "type": "GstFMP4MuxPad" @@ -2357,11 +2405,14 @@ "GInitiallyUnowned", "GObject" ], + "interfaces": [ + "GstChildProxy" + ], "klass": "Sink/Video", "long-name": "GTK 4 Paintable Sink", "pad-templates": { "sink": { - "caps": "video/x-raw:\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n\nvideo/x-raw(memory:GLMemory, meta:GstVideoOverlayComposition):\n format: { RGBA, RGB }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n texture-target: 2D\n\nvideo/x-raw(memory:GLMemory):\n format: { RGBA, RGB }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n texture-target: 2D\n\nvideo/x-raw(memory:SystemMemory, meta:GstVideoOverlayComposition):\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n\nvideo/x-raw(meta:GstVideoOverlayComposition):\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n", + "caps": "video/x-raw(memory:GLMemory, meta:GstVideoOverlayComposition):\n format: { RGBA, RGB }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n texture-target: 2D\n\nvideo/x-raw(memory:GLMemory):\n format: { RGBA, RGB }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n texture-target: 2D\n\nvideo/x-raw(memory:SystemMemory, meta:GstVideoOverlayComposition):\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n\nvideo/x-raw(meta:GstVideoOverlayComposition):\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\nvideo/x-raw:\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n", "direction": "sink", "presence": "always" } @@ -3251,7 +3302,7 @@ "klass": "Codec/Muxer", "pad-templates": { "sink_%%u": { - "caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp8:\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-av1:\n stream-format: obu-stream\n alignment: tu\n profile: { (string)main, (string)high, (string)professional }\n chroma-format: { (string)4:0:0, (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\n rate: [ 1, 2147483647 ]\n", + "caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp8:\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-av1:\n stream-format: obu-stream\n alignment: tu\n profile: { (string)main, (string)high, (string)professional }\n chroma-format: { (string)4:0:0, (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\n rate: [ 1, 2147483647 ]\naudio/x-flac:\n framed: true\n channels: [ 1, 8 ]\n rate: [ 1, 655350 ]\n", "direction": "sink", "presence": "request", "type": "GstRsMP4MuxPad" diff --git a/meson.build b/meson.build index 46a3f8aa..bbf4428b 100644 --- a/meson.build +++ b/meson.build @@ -301,6 +301,23 @@ if get_option('gtk4').allowed() gtk4_features += 'winegl' endif endif + + gst_allocators_dep = dependency('gstreamer-allocators-1.0', version: '>=1.24', required: false) + gtk_dep = dependency('gtk4', version: '>=4.6', required: get_option('gtk4')) + if gtk_dep.found() + if host_system == 'linux' and gtk_dep.version().version_compare('>=4.14') and gst_allocators_dep.found() + gtk4_features += 'dmabuf' + endif + + if gtk_dep.version().version_compare('>=4.14') + gtk4_features += 'gtk_v4_14' + elif gtk_dep.version().version_compare('>=4.12') + gtk4_features += 'gtk_v4_12' + elif gtk_dep.version().version_compare('>=4.10') + gtk4_features += 'gtk_v4_10' + endif + endif + plugins += { 'gtk4': { 'library': 'libgstgtk4', diff --git a/mux/fmp4/src/fmp4mux/boxes.rs b/mux/fmp4/src/fmp4mux/boxes.rs index 55c8c520..e2fdeae7 100644 --- a/mux/fmp4/src/fmp4mux/boxes.rs +++ b/mux/fmp4/src/fmp4mux/boxes.rs @@ -9,6 +9,7 @@ use gst::prelude::*; use anyhow::{anyhow, bail, Context, Error}; +use std::convert::TryFrom; use super::Buffer; @@ -604,9 +605,8 @@ fn write_tkhd( // Volume let s = stream.caps.structure(0).unwrap(); match s.name().as_str() { - "audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => { - v.extend((1u16 << 8).to_be_bytes()) - } + "audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw" + | "audio/x-adpcm" => v.extend((1u16 << 8).to_be_bytes()), _ => v.extend(0u16.to_be_bytes()), } @@ -745,9 +745,8 @@ fn write_hdlr( let (handler_type, name) = match s.name().as_str() { "video/x-h264" | "video/x-h265" | "video/x-vp8" | "video/x-vp9" | "video/x-av1" | "image/jpeg" => (b"vide", b"VideoHandler\0".as_slice()), - "audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => { - (b"soun", b"SoundHandler\0".as_slice()) - } + "audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw" + | "audio/x-adpcm" => (b"soun", b"SoundHandler\0".as_slice()), "application/x-onvif-metadata" => (b"meta", b"MetadataHandler\0".as_slice()), _ => unreachable!(), }; @@ -777,7 +776,8 @@ fn write_minf( // Flags are always 1 for unspecified reasons write_full_box(v, b"vmhd", FULL_BOX_VERSION_0, 1, |v| write_vmhd(v, cfg))? } - "audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => { + "audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw" + | "audio/x-adpcm" => { write_full_box(v, b"smhd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| { write_smhd(v, cfg) })? @@ -886,9 +886,8 @@ fn write_stsd( match s.name().as_str() { "video/x-h264" | "video/x-h265" | "video/x-vp8" | "video/x-vp9" | "video/x-av1" | "image/jpeg" => write_visual_sample_entry(v, cfg, stream)?, - "audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => { - write_audio_sample_entry(v, cfg, stream)? - } + "audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw" + | "audio/x-adpcm" => write_audio_sample_entry(v, cfg, stream)?, "application/x-onvif-metadata" => write_xml_meta_data_sample_entry(v, cfg, stream)?, _ => unreachable!(), } @@ -1262,6 +1261,7 @@ fn write_audio_sample_entry( let fourcc = match s.name().as_str() { "audio/mpeg" => b"mp4a", "audio/x-opus" => b"Opus", + "audio/x-flac" => b"fLaC", "audio/x-alaw" => b"alaw", "audio/x-mulaw" => b"ulaw", "audio/x-adpcm" => { @@ -1280,6 +1280,10 @@ fn write_audio_sample_entry( let bitrate = s.get::("bitrate").context("no ADPCM bitrate field")?; (bitrate / 8000) as u16 } + "audio/x-flac" => with_flac_metadata(&stream.caps, |streaminfo, _| { + 1 + (u16::from_be_bytes([streaminfo[16], streaminfo[17]]) >> 4 & 0b11111) + }) + .context("FLAC metadata error")?, _ => 16u16, }; @@ -1322,6 +1326,9 @@ fn write_audio_sample_entry( "audio/x-opus" => { write_dops(v, &stream.caps)?; } + "audio/x-flac" => { + write_dfla(v, &stream.caps)?; + } "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => { // Nothing to do here } @@ -1516,6 +1523,35 @@ fn write_dops(v: &mut Vec, caps: &gst::Caps) -> Result<(), Error> { }) } +fn with_flac_metadata( + caps: &gst::Caps, + cb: impl FnOnce(&[u8], &[gst::glib::SendValue]) -> R, +) -> Result { + let caps = caps.structure(0).unwrap(); + let header = caps.get::("streamheader").unwrap(); + let (streaminfo, remainder) = header.as_ref().split_first().unwrap(); + let streaminfo = streaminfo.get::<&gst::BufferRef>().unwrap(); + let streaminfo = streaminfo.map_readable().unwrap(); + // 13 bytes for the Ogg/FLAC prefix and 38 for the streaminfo itself. + match <&[_; 13 + 38]>::try_from(streaminfo.as_slice()) { + Ok(i) if i.starts_with(b"\x7FFLAC\x01\x00") => Ok(cb(&i[13..], remainder)), + Ok(_) | Err(_) => bail!("Unknown streamheader format"), + } +} + +fn write_dfla(v: &mut Vec, caps: &gst::Caps) -> Result<(), Error> { + write_full_box(v, b"dfLa", 0, 0, move |v| { + with_flac_metadata(caps, |streaminfo, remainder| { + v.extend(streaminfo); + for metadata in remainder { + let metadata = metadata.get::<&gst::BufferRef>().unwrap(); + let metadata = metadata.map_readable().unwrap(); + v.extend(&metadata[..]); + } + }) + }) +} + fn write_xml_meta_data_sample_entry( v: &mut Vec, _cfg: &super::HeaderConfiguration, diff --git a/mux/fmp4/src/fmp4mux/imp.rs b/mux/fmp4/src/fmp4mux/imp.rs index 592b42dd..501a482e 100644 --- a/mux/fmp4/src/fmp4mux/imp.rs +++ b/mux/fmp4/src/fmp4mux/imp.rs @@ -205,6 +205,8 @@ struct Stream { caps: gst::Caps, /// Whether this stream is intra-only and has frame reordering. delta_frames: DeltaFrames, + /// Whether this stream might have header frames without timestamps that should be ignored. + discard_header_buffers: bool, /// Currently queued GOPs, including incomplete ones. queued_gops: VecDeque, @@ -271,11 +273,17 @@ pub(crate) struct FMP4Mux { impl FMP4Mux { /// Checks if a buffer is valid according to the stream configuration. - fn check_buffer( - buffer: &gst::BufferRef, - sinkpad: &super::FMP4MuxPad, - delta_frames: super::DeltaFrames, - ) -> Result<(), gst::FlowError> { + fn check_buffer(buffer: &gst::BufferRef, stream: &Stream) -> Result<(), gst::FlowError> { + let Stream { + sinkpad, + delta_frames, + discard_header_buffers, + .. + } = stream; + if *discard_header_buffers && buffer.flags().contains(gst::BufferFlags::HEADER) { + return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA); + } + if delta_frames.requires_dts() && buffer.dts().is_none() { gst::error!(CAT, obj: sinkpad, "Require DTS for video streams"); return Err(gst::FlowError::Error); @@ -314,12 +322,10 @@ impl FMP4Mux { } // Pop buffer here, it will be stored in the pre-queue after calculating its timestamps - let mut buffer = match stream.sinkpad.pop_buffer() { - None => return Ok(None), - Some(buffer) => buffer, + let Some(mut buffer) = stream.sinkpad.pop_buffer() else { + return Ok(None); }; - - Self::check_buffer(&buffer, &stream.sinkpad, stream.delta_frames)?; + Self::check_buffer(&buffer, stream)?; let segment = match stream.sinkpad.segment().downcast::().ok() { Some(segment) => segment, @@ -2555,6 +2561,7 @@ impl FMP4Mux { let s = caps.structure(0).unwrap(); let mut delta_frames = DeltaFrames::IntraOnly; + let mut discard_header_buffers = false; match s.name().as_str() { "video/x-h264" | "video/x-h265" => { if !s.has_field_with_type("codec_data", gst::Buffer::static_type()) { @@ -2598,6 +2605,13 @@ impl FMP4Mux { return Err(gst::FlowError::NotNegotiated); } } + "audio/x-flac" => { + discard_header_buffers = true; + if let Err(e) = s.get::("streamheader") { + gst::error!(CAT, obj: pad, "Muxing FLAC into MP4 needs streamheader: {}", e); + return Err(gst::FlowError::NotNegotiated); + }; + } "audio/x-alaw" | "audio/x-mulaw" => (), "audio/x-adpcm" => (), "application/x-onvif-metadata" => (), @@ -2608,6 +2622,7 @@ impl FMP4Mux { sinkpad: pad, caps, delta_frames, + discard_header_buffers, pre_queue: VecDeque::new(), queued_gops: VecDeque::new(), fragment_filled: false, @@ -3465,6 +3480,11 @@ impl ElementImpl for ISOFMP4Mux { .field("channels", gst::IntRange::new(1i32, 8)) .field("rate", gst::IntRange::new(1, i32::MAX)) .build(), + gst::Structure::builder("audio/x-flac") + .field("framed", true) + .field("channels", gst::IntRange::::new(1, 8)) + .field("rate", gst::IntRange::::new(1, 10 * u16::MAX as i32)) + .build(), ] .into_iter() .collect::(), diff --git a/mux/fmp4/tests/tests.rs b/mux/fmp4/tests/tests.rs index a32e65fc..5beba426 100644 --- a/mux/fmp4/tests/tests.rs +++ b/mux/fmp4/tests/tests.rs @@ -19,6 +19,33 @@ fn init() { }); } +fn to_completion(pipeline: &gst::Pipeline) { + pipeline + .set_state(gst::State::Playing) + .expect("Unable to set the pipeline to the `Playing` state"); + + for msg in pipeline.bus().unwrap().iter_timed(gst::ClockTime::NONE) { + use gst::MessageView; + + match msg.view() { + MessageView::Eos(..) => break, + MessageView::Error(err) => { + panic!( + "Error from {:?}: {} ({:?})", + err.src().map(|s| s.path_string()), + err.error(), + err.debug() + ); + } + _ => (), + } + } + + pipeline + .set_state(gst::State::Null) + .expect("Unable to set the pipeline to the `Null` state"); +} + fn test_buffer_flags_single_stream(cmaf: bool, set_dts: bool, caps: gst::Caps) { let mut h = if cmaf { gst_check::Harness::new("cmafmux") @@ -1993,3 +2020,21 @@ fn test_chunking_single_stream_gops_after_fragment_end_after_next_chunk_end() { let ev = h.pull_event().unwrap(); assert_eq!(ev.type_(), gst::EventType::Eos); } + +#[test] +fn test_roundtrip_vp9_flac() { + init(); + + let pipeline = gst::parse::launch( + r#" + videotestsrc num-buffers=99 ! vp9enc ! vp9parse ! mux. + audiotestsrc num-buffers=149 ! flacenc ! flacparse ! mux. + isofmp4mux name=mux ! qtdemux name=demux + demux.audio_0 ! queue ! flacdec ! fakesink + demux.video_0 ! queue ! vp9dec ! fakesink + "#, + ) + .unwrap(); + let pipeline = pipeline.downcast().unwrap(); + to_completion(&pipeline); +} diff --git a/mux/mp4/src/mp4mux/boxes.rs b/mux/mp4/src/mp4mux/boxes.rs index 4e2188b0..170fbc9c 100644 --- a/mux/mp4/src/mp4mux/boxes.rs +++ b/mux/mp4/src/mp4mux/boxes.rs @@ -9,7 +9,7 @@ use gst::prelude::*; use anyhow::{anyhow, bail, Context, Error}; - +use std::convert::TryFrom; use std::str::FromStr; fn write_box) -> Result>( @@ -382,9 +382,8 @@ fn write_tkhd( // Volume let s = stream.caps.structure(0).unwrap(); match s.name().as_str() { - "audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => { - v.extend((1u16 << 8).to_be_bytes()) - } + "audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw" + | "audio/x-adpcm" => v.extend((1u16 << 8).to_be_bytes()), _ => v.extend(0u16.to_be_bytes()), } @@ -514,9 +513,8 @@ fn write_hdlr( let (handler_type, name) = match s.name().as_str() { "video/x-h264" | "video/x-h265" | "video/x-vp8" | "video/x-vp9" | "video/x-av1" | "image/jpeg" => (b"vide", b"VideoHandler\0".as_slice()), - "audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => { - (b"soun", b"SoundHandler\0".as_slice()) - } + "audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw" + | "audio/x-adpcm" => (b"soun", b"SoundHandler\0".as_slice()), "application/x-onvif-metadata" => (b"meta", b"MetadataHandler\0".as_slice()), _ => unreachable!(), }; @@ -546,7 +544,8 @@ fn write_minf( // Flags are always 1 for unspecified reasons write_full_box(v, b"vmhd", FULL_BOX_VERSION_0, 1, |v| write_vmhd(v, header))? } - "audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => { + "audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw" + | "audio/x-adpcm" => { write_full_box(v, b"smhd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| { write_smhd(v, header) })? @@ -703,9 +702,8 @@ fn write_stsd( match s.name().as_str() { "video/x-h264" | "video/x-h265" | "video/x-vp8" | "video/x-vp9" | "video/x-av1" | "image/jpeg" => write_visual_sample_entry(v, header, stream)?, - "audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => { - write_audio_sample_entry(v, header, stream)? - } + "audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw" + | "audio/x-adpcm" => write_audio_sample_entry(v, header, stream)?, "application/x-onvif-metadata" => write_xml_meta_data_sample_entry(v, header, stream)?, _ => unreachable!(), } @@ -1079,6 +1077,7 @@ fn write_audio_sample_entry( let fourcc = match s.name().as_str() { "audio/mpeg" => b"mp4a", "audio/x-opus" => b"Opus", + "audio/x-flac" => b"fLaC", "audio/x-alaw" => b"alaw", "audio/x-mulaw" => b"ulaw", "audio/x-adpcm" => { @@ -1097,6 +1096,10 @@ fn write_audio_sample_entry( let bitrate = s.get::("bitrate").context("no ADPCM bitrate field")?; (bitrate / 8000) as u16 } + "audio/x-flac" => with_flac_metadata(&stream.caps, |streaminfo, _| { + 1 + (u16::from_be_bytes([streaminfo[16], streaminfo[17]]) >> 4 & 0b11111) + }) + .context("FLAC metadata error")?, _ => 16u16, }; @@ -1139,6 +1142,9 @@ fn write_audio_sample_entry( "audio/x-opus" => { write_dops(v, &stream.caps)?; } + "audio/x-flac" => { + write_dfla(v, &stream.caps)?; + } "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => { // Nothing to do here } @@ -1333,6 +1339,35 @@ fn write_dops(v: &mut Vec, caps: &gst::Caps) -> Result<(), Error> { }) } +fn with_flac_metadata( + caps: &gst::Caps, + cb: impl FnOnce(&[u8], &[gst::glib::SendValue]) -> R, +) -> Result { + let caps = caps.structure(0).unwrap(); + let header = caps.get::("streamheader").unwrap(); + let (streaminfo, remainder) = header.as_ref().split_first().unwrap(); + let streaminfo = streaminfo.get::<&gst::BufferRef>().unwrap(); + let streaminfo = streaminfo.map_readable().unwrap(); + // 13 bytes for the Ogg/FLAC prefix and 38 for the streaminfo itself. + match <&[_; 13 + 38]>::try_from(streaminfo.as_slice()) { + Ok(i) if i.starts_with(b"\x7FFLAC\x01\x00") => Ok(cb(&i[13..], remainder)), + Ok(_) | Err(_) => bail!("Unknown streamheader format"), + } +} + +fn write_dfla(v: &mut Vec, caps: &gst::Caps) -> Result<(), Error> { + write_full_box(v, b"dfLa", 0, 0, move |v| { + with_flac_metadata(caps, |streaminfo, remainder| { + v.extend(streaminfo); + for metadata in remainder { + let metadata = metadata.get::<&gst::BufferRef>().unwrap(); + let metadata = metadata.map_readable().unwrap(); + v.extend(&metadata[..]); + } + }) + }) +} + fn write_xml_meta_data_sample_entry( v: &mut Vec, _header: &super::Header, diff --git a/mux/mp4/src/mp4mux/imp.rs b/mux/mp4/src/mp4mux/imp.rs index 24c06173..bb9a791d 100644 --- a/mux/mp4/src/mp4mux/imp.rs +++ b/mux/mp4/src/mp4mux/imp.rs @@ -108,6 +108,8 @@ struct Stream { caps: gst::Caps, /// Whether this stream is intra-only and has frame reordering. delta_frames: super::DeltaFrames, + /// Whether this stream might have header frames without timestamps that should be ignored. + discard_header_buffers: bool, /// Already written out chunks with their samples for this stream chunks: Vec, @@ -165,7 +167,12 @@ impl MP4Mux { buffer: &gst::BufferRef, sinkpad: &super::MP4MuxPad, delta_frames: super::DeltaFrames, + discard_headers: bool, ) -> Result<(), gst::FlowError> { + if discard_headers && buffer.flags().contains(gst::BufferFlags::HEADER) { + return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA); + } + if delta_frames.requires_dts() && buffer.dts().is_none() { gst::error!(CAT, obj: sinkpad, "Require DTS for video streams"); return Err(gst::FlowError::Error); @@ -188,6 +195,7 @@ impl MP4Mux { &self, sinkpad: &super::MP4MuxPad, delta_frames: super::DeltaFrames, + discard_headers: bool, pre_queue: &mut VecDeque<(gst::FormattedSegment, gst::Buffer)>, running_time_utc_time_mapping: &Option<(gst::Signed, gst::ClockTime)>, ) -> Result, gst::Buffer)>, gst::FlowError> { @@ -195,13 +203,10 @@ impl MP4Mux { return Ok(Some((segment.clone(), buffer.clone()))); } - let mut buffer = match sinkpad.peek_buffer() { - None => return Ok(None), - Some(buffer) => buffer, + let Some(mut buffer) = sinkpad.peek_buffer() else { + return Ok(None); }; - - Self::check_buffer(&buffer, sinkpad, delta_frames)?; - + Self::check_buffer(&buffer, sinkpad, delta_frames, discard_headers)?; let mut segment = match sinkpad.segment().downcast::().ok() { Some(segment) => segment, None => { @@ -276,19 +281,20 @@ impl MP4Mux { fn pop_buffer( &self, - sinkpad: &super::MP4MuxPad, - delta_frames: super::DeltaFrames, - pre_queue: &mut VecDeque<(gst::FormattedSegment, gst::Buffer)>, - running_time_utc_time_mapping: &mut Option<(gst::Signed, gst::ClockTime)>, + stream: &mut Stream, ) -> Result, gst::Buffer)>, gst::FlowError> { + let Stream { + sinkpad, pre_queue, .. + } = stream; + // In ONVIF mode we need to get UTC times for each buffer and synchronize based on that. // Queue up to 6s of data to get the first UTC time and then backdate. if self.obj().class().as_ref().variant == super::Variant::ONVIF - && running_time_utc_time_mapping.is_none() + && stream.running_time_utc_time_mapping.is_none() { if let Some((last, first)) = Option::zip(pre_queue.back(), pre_queue.front()) { // Existence of PTS/DTS checked below - let (last, first) = if delta_frames.requires_dts() { + let (last, first) = if stream.delta_frames.requires_dts() { ( last.0.to_running_time_full(last.1.dts()).unwrap(), first.0.to_running_time_full(first.1.dts()).unwrap(), @@ -312,19 +318,20 @@ impl MP4Mux { } } - let buffer = match sinkpad.pop_buffer() { - None => { - if sinkpad.is_eos() { - gst::error!(CAT, obj: sinkpad, "Got no UTC time before EOS"); - return Err(gst::FlowError::Error); - } else { - return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA); - } + let Some(buffer) = sinkpad.pop_buffer() else { + if sinkpad.is_eos() { + gst::error!(CAT, obj: sinkpad, "Got no UTC time before EOS"); + return Err(gst::FlowError::Error); + } else { + return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA); } - Some(buffer) => buffer, }; - - Self::check_buffer(&buffer, sinkpad, delta_frames)?; + Self::check_buffer( + &buffer, + sinkpad, + stream.delta_frames, + stream.discard_header_buffers, + )?; let segment = match sinkpad.segment().downcast::().ok() { Some(segment) => segment, @@ -350,7 +357,7 @@ impl MP4Mux { ); let mapping = (running_time, utc_time); - *running_time_utc_time_mapping = Some(mapping); + stream.running_time_utc_time_mapping = Some(mapping); // Push the buffer onto the pre-queue and re-timestamp it and all other buffers // based on the mapping above. @@ -391,7 +398,7 @@ impl MP4Mux { // Fall through below and pop the first buffer finally } - if let Some((segment, buffer)) = pre_queue.pop_front() { + if let Some((segment, buffer)) = stream.pre_queue.pop_front() { return Ok(Some((segment, buffer))); } @@ -400,23 +407,26 @@ impl MP4Mux { // for calculating the duration to the previous buffer, and then put into the pre-queue // - or this is the very first buffer and we just put it into the queue overselves above if self.obj().class().as_ref().variant == super::Variant::ONVIF { - if sinkpad.is_eos() { + if stream.sinkpad.is_eos() { return Ok(None); } unreachable!(); } - let buffer = match sinkpad.pop_buffer() { - None => return Ok(None), - Some(buffer) => buffer, + let Some(buffer) = stream.sinkpad.pop_buffer() else { + return Ok(None); }; + Self::check_buffer( + &buffer, + &stream.sinkpad, + stream.delta_frames, + stream.discard_header_buffers, + )?; - Self::check_buffer(&buffer, sinkpad, delta_frames)?; - - let segment = match sinkpad.segment().downcast::().ok() { + let segment = match stream.sinkpad.segment().downcast::().ok() { Some(segment) => segment, None => { - gst::error!(CAT, obj: sinkpad, "Got buffer before segment"); + gst::error!(CAT, obj: stream.sinkpad, "Got buffer before segment"); return Err(gst::FlowError::Error); } }; @@ -442,6 +452,12 @@ impl MP4Mux { Some(PendingBuffer { duration: Some(_), .. }) => return Ok(()), + Some(PendingBuffer { ref buffer, .. }) + if stream.discard_header_buffers + && buffer.flags().contains(gst::BufferFlags::HEADER) => + { + return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA); + } Some(PendingBuffer { timestamp, pts, @@ -449,13 +465,15 @@ impl MP4Mux { ref mut duration, .. }) => { - // Already have a pending buffer but no duration, so try to get that now - let (segment, buffer) = match self.peek_buffer( + let peek_outcome = self.peek_buffer( &stream.sinkpad, stream.delta_frames, + stream.discard_header_buffers, &mut stream.pre_queue, &stream.running_time_utc_time_mapping, - )? { + )?; + // Already have a pending buffer but no duration, so try to get that now + let (segment, buffer) = match peek_outcome { Some(res) => res, None => { if stream.sinkpad.is_eos() { @@ -532,12 +550,7 @@ impl MP4Mux { None => { // Have no buffer queued at all yet - let (segment, buffer) = match self.pop_buffer( - &stream.sinkpad, - stream.delta_frames, - &mut stream.pre_queue, - &mut stream.running_time_utc_time_mapping, - )? { + let (segment, buffer) = match self.pop_buffer(stream)? { Some(res) => res, None => { if stream.sinkpad.is_eos() { @@ -870,6 +883,7 @@ impl MP4Mux { let s = caps.structure(0).unwrap(); let mut delta_frames = super::DeltaFrames::IntraOnly; + let mut discard_header_buffers = false; match s.name().as_str() { "video/x-h264" | "video/x-h265" => { if !s.has_field_with_type("codec_data", gst::Buffer::static_type()) { @@ -913,6 +927,13 @@ impl MP4Mux { return Err(gst::FlowError::NotNegotiated); } } + "audio/x-flac" => { + discard_header_buffers = true; + if let Err(e) = s.get::("streamheader") { + gst::error!(CAT, obj: pad, "Muxing FLAC into MP4 needs streamheader: {}", e); + return Err(gst::FlowError::NotNegotiated); + }; + } "audio/x-alaw" | "audio/x-mulaw" => (), "audio/x-adpcm" => (), "application/x-onvif-metadata" => (), @@ -924,6 +945,7 @@ impl MP4Mux { pre_queue: VecDeque::new(), caps, delta_frames, + discard_header_buffers, chunks: Vec::new(), pending_buffer: None, queued_chunk_time: gst::ClockTime::ZERO, @@ -1523,6 +1545,11 @@ impl ElementImpl for ISOMP4Mux { .field("channels", gst::IntRange::new(1i32, 8)) .field("rate", gst::IntRange::new(1, i32::MAX)) .build(), + gst::Structure::builder("audio/x-flac") + .field("framed", true) + .field("channels", gst::IntRange::::new(1, 8)) + .field("rate", gst::IntRange::::new(1, 10 * u16::MAX as i32)) + .build(), ] .into_iter() .collect::(), diff --git a/mux/mp4/tests/tests.rs b/mux/mp4/tests/tests.rs index fc6e360f..f3f2ece3 100644 --- a/mux/mp4/tests/tests.rs +++ b/mux/mp4/tests/tests.rs @@ -7,6 +7,8 @@ // SPDX-License-Identifier: MPL-2.0 // +use std::path::Path; + use gst::prelude::*; use gst_pbutils::prelude::*; @@ -20,33 +22,57 @@ fn init() { }); } -#[test] -fn test_basic() { - init(); +struct Pipeline(gst::Pipeline); +impl std::ops::Deref for Pipeline { + type Target = gst::Pipeline; - struct Pipeline(gst::Pipeline); - impl std::ops::Deref for Pipeline { - type Target = gst::Pipeline; - - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 } - impl Drop for Pipeline { - fn drop(&mut self) { - let _ = self.0.set_state(gst::State::Null); - } +} +impl Drop for Pipeline { + fn drop(&mut self) { + let _ = self.0.set_state(gst::State::Null); } +} - let pipeline = match gst::parse::launch( - "videotestsrc num-buffers=99 ! x264enc ! mux. \ - audiotestsrc num-buffers=140 ! fdkaacenc ! mux. \ - isomp4mux name=mux ! filesink name=sink \ - ", - ) { - Ok(pipeline) => Pipeline(pipeline.downcast::().unwrap()), - Err(_) => return, +impl Pipeline { + fn into_completion(self) { + self.set_state(gst::State::Playing) + .expect("Unable to set the pipeline to the `Playing` state"); + + for msg in self.bus().unwrap().iter_timed(gst::ClockTime::NONE) { + use gst::MessageView; + + match msg.view() { + MessageView::Eos(..) => break, + MessageView::Error(err) => { + panic!( + "Error from {:?}: {} ({:?})", + err.src().map(|s| s.path_string()), + err.error(), + err.debug() + ); + } + _ => (), + } + } + + self.set_state(gst::State::Null) + .expect("Unable to set the pipeline to the `Null` state"); + } +} + +fn test_basic_with(video_enc: &str, audio_enc: &str, cb: impl FnOnce(&Path)) { + let Ok(pipeline) = gst::parse::launch(&format!( + "videotestsrc num-buffers=99 ! {video_enc} ! mux. \ + audiotestsrc num-buffers=140 ! {audio_enc} ! mux. \ + isomp4mux name=mux ! filesink name=sink" + )) else { + println!("could not build encoding pipeline"); + return; }; + let pipeline = Pipeline(pipeline.downcast::().unwrap()); let dir = tempfile::TempDir::new().unwrap(); let mut location = dir.path().to_owned(); @@ -54,73 +80,75 @@ fn test_basic() { let sink = pipeline.by_name("sink").unwrap(); sink.set_property("location", location.to_str().expect("Non-UTF8 filename")); + pipeline.into_completion(); - pipeline - .set_state(gst::State::Playing) - .expect("Unable to set the pipeline to the `Playing` state"); - - for msg in pipeline.bus().unwrap().iter_timed(gst::ClockTime::NONE) { - use gst::MessageView; - - match msg.view() { - MessageView::Eos(..) => break, - MessageView::Error(err) => { - panic!( - "Error from {:?}: {} ({:?})", - err.src().map(|s| s.path_string()), - err.error(), - err.debug() - ); - } - _ => (), - } - } - - pipeline - .set_state(gst::State::Null) - .expect("Unable to set the pipeline to the `Null` state"); - - drop(pipeline); - - let discoverer = gst_pbutils::Discoverer::new(gst::ClockTime::from_seconds(5)) - .expect("Failed to create discoverer"); - let info = discoverer - .discover_uri( - url::Url::from_file_path(&location) - .expect("Failed to convert filename to URL") - .as_str(), - ) - .expect("Failed to discover MP4 file"); - - assert_eq!(info.duration(), Some(gst::ClockTime::from_mseconds(3_300))); - - let audio_streams = info.audio_streams(); - assert_eq!(audio_streams.len(), 1); - let audio_stream = &audio_streams[0]; - assert_eq!(audio_stream.channels(), 1); - assert_eq!(audio_stream.sample_rate(), 44_100); - let caps = audio_stream.caps().unwrap(); - assert!( - caps.can_intersect( - &gst::Caps::builder("audio/mpeg") - .any_features() - .field("mpegversion", 4i32) - .build() - ), - "Unexpected audio caps {caps:?}" - ); - - let video_streams = info.video_streams(); - assert_eq!(video_streams.len(), 1); - let video_stream = &video_streams[0]; - assert_eq!(video_stream.width(), 320); - assert_eq!(video_stream.height(), 240); - assert_eq!(video_stream.framerate(), gst::Fraction::new(30, 1)); - assert_eq!(video_stream.par(), gst::Fraction::new(1, 1)); - assert!(!video_stream.is_interlaced()); - let caps = video_stream.caps().unwrap(); - assert!( - caps.can_intersect(&gst::Caps::builder("video/x-h264").any_features().build()), - "Unexpected video caps {caps:?}" - ); + cb(&location) +} + +#[test] +fn test_basic_x264_aac() { + init(); + test_basic_with("x264enc", "fdkaacenc", |location| { + let discoverer = gst_pbutils::Discoverer::new(gst::ClockTime::from_seconds(5)) + .expect("Failed to create discoverer"); + let info = discoverer + .discover_uri( + url::Url::from_file_path(location) + .expect("Failed to convert filename to URL") + .as_str(), + ) + .expect("Failed to discover MP4 file"); + + assert_eq!(info.duration(), Some(gst::ClockTime::from_mseconds(3_300))); + + let audio_streams = info.audio_streams(); + assert_eq!(audio_streams.len(), 1); + let audio_stream = &audio_streams[0]; + assert_eq!(audio_stream.channels(), 1); + assert_eq!(audio_stream.sample_rate(), 44_100); + let caps = audio_stream.caps().unwrap(); + assert!( + caps.can_intersect( + &gst::Caps::builder("audio/mpeg") + .any_features() + .field("mpegversion", 4i32) + .build() + ), + "Unexpected audio caps {caps:?}" + ); + + let video_streams = info.video_streams(); + assert_eq!(video_streams.len(), 1); + let video_stream = &video_streams[0]; + assert_eq!(video_stream.width(), 320); + assert_eq!(video_stream.height(), 240); + assert_eq!(video_stream.framerate(), gst::Fraction::new(30, 1)); + assert_eq!(video_stream.par(), gst::Fraction::new(1, 1)); + assert!(!video_stream.is_interlaced()); + let caps = video_stream.caps().unwrap(); + assert!( + caps.can_intersect(&gst::Caps::builder("video/x-h264").any_features().build()), + "Unexpected video caps {caps:?}" + ); + }) +} + +#[test] +fn test_roundtrip_vp9_flac() { + init(); + test_basic_with("vp9enc ! vp9parse", "flacenc ! flacparse", |location| { + let Ok(pipeline) = gst::parse::launch( + "filesrc name=src ! qtdemux name=demux \ + demux.audio_0 ! queue ! flacdec ! fakesink \ + demux.video_0 ! queue ! vp9dec ! fakesink", + ) else { + panic!("could not build decoding pipeline") + }; + let pipeline = Pipeline(pipeline.downcast::().unwrap()); + pipeline + .by_name("src") + .unwrap() + .set_property("location", location.display().to_string()); + pipeline.into_completion(); + }) } diff --git a/net/aws/src/s3hlssink/imp.rs b/net/aws/src/s3hlssink/imp.rs index 6302fdfd..2f35bb97 100644 --- a/net/aws/src/s3hlssink/imp.rs +++ b/net/aws/src/s3hlssink/imp.rs @@ -39,6 +39,7 @@ const S3_CHANNEL_SIZE: usize = 32; const S3_ACL_DEFAULT: ObjectCannedAcl = ObjectCannedAcl::Private; const DEFAULT_RETRY_ATTEMPTS: u32 = 5; const DEFAULT_TIMEOUT_IN_MSECS: u64 = 15000; +const DEFAULT_FORCE_PATH_STYLE: bool = false; struct Settings { access_key: Option, @@ -57,6 +58,7 @@ struct Settings { video_sink: bool, config: Option, endpoint_uri: Option, + force_path_style: bool, } impl Default for Settings { @@ -79,6 +81,7 @@ impl Default for Settings { video_sink: false, config: None, endpoint_uri: None, + force_path_style: DEFAULT_FORCE_PATH_STYLE, } } } @@ -376,6 +379,7 @@ impl S3HlsSink { let sdk_config = settings.config.as_ref().expect("SDK config must be set"); let config_builder = config::Builder::from(sdk_config) + .force_path_style(settings.force_path_style) .region(settings.s3_region.clone()) .retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts)); @@ -529,6 +533,11 @@ impl ObjectImpl for S3HlsSink { .blurb("The S3 endpoint URI to use") .mutable_ready() .build(), + glib::ParamSpecBoolean::builder("force-path-style") + .nick("Force path style") + .blurb("Force client to use path-style addressing for buckets") + .default_value(DEFAULT_FORCE_PATH_STYLE) + .build(), ] }); @@ -586,6 +595,9 @@ impl ObjectImpl for S3HlsSink { .get::>() .expect("type checked upstream"); } + "force-path-style" => { + settings.force_path_style = value.get::().expect("type checked upstream"); + } _ => unimplemented!(), } } @@ -606,6 +618,7 @@ impl ObjectImpl for S3HlsSink { "request-timeout" => (settings.request_timeout.as_millis() as u64).to_value(), "stats" => self.create_stats().to_value(), "endpoint-uri" => settings.endpoint_uri.to_value(), + "force-path-style" => settings.force_path_style.to_value(), _ => unimplemented!(), } } diff --git a/net/aws/src/s3sink/multipartsink.rs b/net/aws/src/s3sink/multipartsink.rs index 5554fb09..38751546 100644 --- a/net/aws/src/s3sink/multipartsink.rs +++ b/net/aws/src/s3sink/multipartsink.rs @@ -38,6 +38,7 @@ use crate::s3utils::{self, duration_from_millis, duration_to_millis, WaitError}; use super::OnError; +const DEFAULT_FORCE_PATH_STYLE: bool = false; const DEFAULT_RETRY_ATTEMPTS: u32 = 5; const DEFAULT_BUFFER_SIZE: u64 = 5 * 1024 * 1024; const DEFAULT_MULTIPART_UPLOAD_ON_ERROR: OnError = OnError::DoNothing; @@ -114,6 +115,7 @@ struct Settings { multipart_upload_on_error: OnError, request_timeout: Duration, endpoint_uri: Option, + force_path_style: bool, } impl Settings { @@ -168,6 +170,7 @@ impl Default for Settings { multipart_upload_on_error: DEFAULT_MULTIPART_UPLOAD_ON_ERROR, request_timeout: Duration::from_millis(DEFAULT_REQUEST_TIMEOUT_MSEC), endpoint_uri: None, + force_path_style: DEFAULT_FORCE_PATH_STYLE, } } } @@ -524,6 +527,7 @@ impl S3Sink { })?; let config_builder = config::Builder::from(&sdk_config) + .force_path_style(settings.force_path_style) .retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts)); let config = if let Some(ref uri) = settings.endpoint_uri { @@ -775,6 +779,11 @@ impl ObjectImpl for S3Sink { .nick("content-disposition") .blurb("Content-Disposition header to set for uploaded object") .build(), + glib::ParamSpecBoolean::builder("force-path-style") + .nick("Force path style") + .blurb("Force client to use path-style addressing for buckets") + .default_value(DEFAULT_FORCE_PATH_STYLE) + .build(), ] }); @@ -888,6 +897,9 @@ impl ObjectImpl for S3Sink { .get::>() .expect("type checked upstream"); } + "force-path-style" => { + settings.force_path_style = value.get::().expect("type checked upstream"); + } _ => unimplemented!(), } } @@ -929,6 +941,7 @@ impl ObjectImpl for S3Sink { "endpoint-uri" => settings.endpoint_uri.to_value(), "content-type" => settings.content_type.to_value(), "content-disposition" => settings.content_disposition.to_value(), + "force-path-style" => settings.force_path_style.to_value(), _ => unimplemented!(), } } diff --git a/net/aws/src/s3sink/putobjectsink.rs b/net/aws/src/s3sink/putobjectsink.rs index 8b13d5f3..fb3ecd35 100644 --- a/net/aws/src/s3sink/putobjectsink.rs +++ b/net/aws/src/s3sink/putobjectsink.rs @@ -36,6 +36,7 @@ const DEFAULT_FLUSH_INTERVAL_BUFFERS: u64 = 1; const DEFAULT_FLUSH_INTERVAL_BYTES: u64 = 0; const DEFAULT_FLUSH_INTERVAL_TIME: gst::ClockTime = gst::ClockTime::from_nseconds(0); const DEFAULT_FLUSH_ON_ERROR: bool = false; +const DEFAULT_FORCE_PATH_STYLE: bool = false; // General setting for create / abort requests const DEFAULT_REQUEST_TIMEOUT_MSEC: u64 = 15_000; @@ -80,6 +81,7 @@ struct Settings { retry_attempts: u32, request_timeout: Duration, endpoint_uri: Option, + force_path_style: bool, flush_interval_buffers: u64, flush_interval_bytes: u64, flush_interval_time: Option, @@ -136,6 +138,7 @@ impl Default for Settings { retry_attempts: DEFAULT_RETRY_ATTEMPTS, request_timeout: Duration::from_millis(DEFAULT_REQUEST_TIMEOUT_MSEC), endpoint_uri: None, + force_path_style: DEFAULT_FORCE_PATH_STYLE, flush_interval_buffers: DEFAULT_FLUSH_INTERVAL_BUFFERS, flush_interval_bytes: DEFAULT_FLUSH_INTERVAL_BYTES, flush_interval_time: Some(DEFAULT_FLUSH_INTERVAL_TIME), @@ -293,6 +296,7 @@ impl S3PutObjectSink { })?; let config_builder = config::Builder::from(&sdk_config) + .force_path_style(settings.force_path_style) .retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts)); let config = if let Some(ref uri) = settings.endpoint_uri { @@ -446,6 +450,11 @@ impl ObjectImpl for S3PutObjectSink { .blurb("Whether to write out the data on error (like stopping without an EOS)") .default_value(DEFAULT_FLUSH_ON_ERROR) .build(), + glib::ParamSpecBoolean::builder("force-path-style") + .nick("Force path style") + .blurb("Force client to use path-style addressing for buckets") + .default_value(DEFAULT_FORCE_PATH_STYLE) + .build(), ] }); @@ -542,6 +551,9 @@ impl ObjectImpl for S3PutObjectSink { "flush-on-error" => { settings.flush_on_error = value.get::().expect("type checked upstream"); } + "force-path-style" => { + settings.force_path_style = value.get::().expect("type checked upstream"); + } _ => unimplemented!(), } } @@ -575,6 +587,7 @@ impl ObjectImpl for S3PutObjectSink { "flush-interval-bytes" => settings.flush_interval_bytes.to_value(), "flush-interval-time" => settings.flush_interval_time.to_value(), "flush-on-error" => settings.flush_on_error.to_value(), + "force-path-style" => settings.force_path_style.to_value(), _ => unimplemented!(), } } diff --git a/net/aws/src/s3src/imp.rs b/net/aws/src/s3src/imp.rs index bfb8b76f..25e6f7cd 100644 --- a/net/aws/src/s3src/imp.rs +++ b/net/aws/src/s3src/imp.rs @@ -29,6 +29,7 @@ use gst_base::subclass::prelude::*; use crate::s3url::*; use crate::s3utils::{self, duration_from_millis, duration_to_millis, WaitError}; +const DEFAULT_FORCE_PATH_STYLE: bool = false; const DEFAULT_RETRY_ATTEMPTS: u32 = 5; const DEFAULT_REQUEST_TIMEOUT_MSEC: u64 = 15000; const DEFAULT_RETRY_DURATION_MSEC: u64 = 60_000; @@ -53,6 +54,7 @@ struct Settings { retry_attempts: u32, request_timeout: Duration, endpoint_uri: Option, + force_path_style: bool, } impl Default for Settings { @@ -66,6 +68,7 @@ impl Default for Settings { retry_attempts: DEFAULT_RETRY_ATTEMPTS, request_timeout: duration, endpoint_uri: None, + force_path_style: DEFAULT_FORCE_PATH_STYLE, } } } @@ -128,6 +131,7 @@ impl S3Src { })?; let config_builder = config::Builder::from(&sdk_config) + .force_path_style(settings.force_path_style) .retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts)); let config = if let Some(ref uri) = settings.endpoint_uri { @@ -316,6 +320,11 @@ impl ObjectImpl for S3Src { .nick("S3 endpoint URI") .blurb("The S3 endpoint URI to use") .build(), + glib::ParamSpecBoolean::builder("force-path-style") + .nick("Force path style") + .blurb("Force client to use path-style addressing for buckets") + .default_value(DEFAULT_FORCE_PATH_STYLE) + .build(), ] }); @@ -365,6 +374,9 @@ impl ObjectImpl for S3Src { .get::>() .expect("type checked upstream"); } + "force-path-style" => { + settings.force_path_style = value.get::().expect("type checked upstream"); + } _ => unimplemented!(), } } @@ -391,6 +403,7 @@ impl ObjectImpl for S3Src { } "retry-attempts" => settings.retry_attempts.to_value(), "endpoint-uri" => settings.endpoint_uri.to_value(), + "force-path-style" => settings.force_path_style.to_value(), _ => unimplemented!(), } } diff --git a/net/rtp/src/gcc/imp.rs b/net/rtp/src/gcc/imp.rs index 30ca1697..e1181fff 100644 --- a/net/rtp/src/gcc/imp.rs +++ b/net/rtp/src/gcc/imp.rs @@ -26,6 +26,7 @@ use std::{ fmt::Debug, mem, sync::Mutex, + time::Instant, }; use time::Duration; @@ -268,7 +269,7 @@ struct Detector { last_received_packets: BTreeMap, // Order by seqnums, front is the newest, back is the oldest // Last loss update - last_loss_update: Option, + last_loss_update: Option, // Moving average of the packet loss loss_average: f64, @@ -280,13 +281,13 @@ struct Detector { // Threshold fields threshold: Duration, - last_threshold_update: Option, + last_threshold_update: Option, num_deltas: i64, // Overuse related fields increasing_counter: u32, last_overuse_estimate: Duration, - last_use_detector_update: time::Instant, + last_use_detector_update: Instant, increasing_duration: Duration, // round-trip-time estimations @@ -337,7 +338,7 @@ impl Detector { last_threshold_update: None, num_deltas: 0, - last_use_detector_update: time::Instant::now(), + last_use_detector_update: Instant::now(), increasing_counter: 0, last_overuse_estimate: Duration::ZERO, increasing_duration: Duration::ZERO, @@ -519,11 +520,14 @@ impl Detector { } fn compute_loss_average(&mut self, loss_fraction: f64) { - let now = time::Instant::now(); + let now = Instant::now(); if let Some(ref last_update) = self.last_loss_update { self.loss_average = loss_fraction - + (-(now - *last_update).whole_milliseconds() as f64).exp() + + (-Duration::try_from(now - *last_update) + .unwrap() + .whole_milliseconds() as f64) + .exp() * (self.loss_average - loss_fraction); } @@ -588,7 +592,7 @@ impl Detector { const K_D: f64 = 0.00018; // Table1. Coefficient for the adaptive threshold const MAX_TIME_DELTA: Duration = Duration::milliseconds(100); - let now = time::Instant::now(); + let now = Instant::now(); if self.last_threshold_update.is_none() { self.last_threshold_update = Some(now); } @@ -604,7 +608,9 @@ impl Detector { } else { K_U }; - let time_delta = (now - self.last_threshold_update.unwrap()).min(MAX_TIME_DELTA); + let time_delta = Duration::try_from(now - self.last_threshold_update.unwrap()) + .unwrap() + .min(MAX_TIME_DELTA); let d = abs_estimate - self.threshold; let add = k * d.whole_milliseconds() as f64 * time_delta.whole_milliseconds() as f64; @@ -616,7 +622,7 @@ impl Detector { fn overuse_filter(&mut self) { let (th_usage, estimate) = self.compare_threshold(); - let now = time::Instant::now(); + let now = Instant::now(); let delta = now - self.last_use_detector_update; self.last_use_detector_update = now; match th_usage { @@ -695,14 +701,14 @@ struct State { /// Used in additive mode to track last control time, influences /// calculation of added value according to gcc section 5.5 - last_increase_on_delay: Option, - last_decrease_on_delay: time::Instant, + last_increase_on_delay: Option, + last_decrease_on_delay: Instant, /// Bitrate target based on loss for all video streams. target_bitrate_on_loss: Bitrate, - last_increase_on_loss: time::Instant, - last_decrease_on_loss: time::Instant, + last_increase_on_loss: Instant, + last_decrease_on_loss: Instant, /// Exponential moving average, updated when bitrate is /// decreased @@ -723,7 +729,7 @@ struct State { budget_offset: i64, flow_return: Result, - last_push: time::Instant, + last_push: Instant, } impl Default for State { @@ -731,11 +737,11 @@ impl Default for State { Self { target_bitrate_on_delay: DEFAULT_ESTIMATED_BITRATE, target_bitrate_on_loss: DEFAULT_ESTIMATED_BITRATE, - last_increase_on_loss: time::Instant::now(), - last_decrease_on_loss: time::Instant::now(), + last_increase_on_loss: Instant::now(), + last_decrease_on_loss: Instant::now(), ema: Default::default(), last_increase_on_delay: None, - last_decrease_on_delay: time::Instant::now(), + last_decrease_on_delay: Instant::now(), min_bitrate: DEFAULT_MIN_BITRATE, max_bitrate: DEFAULT_MAX_BITRATE, detector: Detector::new(), @@ -744,7 +750,7 @@ impl Default for State { last_control_op: BandwidthEstimationOp::Increase("Initial increase".into()), flow_return: Err(gst::FlowError::Flushing), clock_entry: None, - last_push: time::Instant::now(), + last_push: Instant::now(), budget_offset: 0, } } @@ -753,8 +759,8 @@ impl Default for State { impl State { // 4. sending engine implementing a "leaky bucket" fn create_buffer_list(&mut self, bwe: &super::BandwidthEstimator) -> BufferList { - let now = time::Instant::now(); - let elapsed = now - self.last_push; + let now = Instant::now(); + let elapsed = Duration::try_from(now - self.last_push).unwrap(); let mut budget = (elapsed.whole_nanoseconds() as i64) .mul_div_round( self.estimated_bitrate as i64, @@ -803,7 +809,7 @@ impl State { } fn compute_increased_rate(&mut self, bwe: &super::BandwidthEstimator) -> Option { - let now = time::Instant::now(); + let now = Instant::now(); let target_bitrate = self.target_bitrate_on_delay as f64; let effective_bitrate = self.detector.effective_bitrate(); let time_since_last_update_ms = match self.last_increase_on_delay { @@ -813,7 +819,7 @@ impl State { return None; } - (now - prev).whole_milliseconds() as f64 + Duration::try_from(now - prev).unwrap().whole_milliseconds() as f64 } }; @@ -950,7 +956,7 @@ impl State { fn loss_control(&mut self, bwe: &super::BandwidthEstimator) -> bool { let loss_ratio = self.detector.loss_ratio(); - let now = time::Instant::now(); + let now = Instant::now(); if loss_ratio > LOSS_DECREASE_THRESHOLD && (now - self.last_decrease_on_loss) > LOSS_UPDATE_INTERVAL @@ -993,7 +999,7 @@ impl State { _ => (), }, NetworkUsage::Over => { - let now = time::Instant::now(); + let now = Instant::now(); if now - self.last_decrease_on_delay > DELAY_UPDATE_INTERVAL { let effective_bitrate = self.detector.effective_bitrate(); let target = @@ -1096,7 +1102,9 @@ impl BandwidthEstimator { if !list.is_empty() { if let Err(err) = bwe.imp().push_list(list) { - gst::error!(CAT, obj: bwe, "pause task, reason: {err:?}"); + if err != gst::FlowError::Flushing { + gst::error!(CAT, obj: bwe, "pause task, reason: {err:?}"); + } pause() } } diff --git a/net/webrtc/src/webrtcsink/homegrown_cc.rs b/net/webrtc/src/webrtcsink/homegrown_cc.rs index f8ea66db..22b07cd9 100644 --- a/net/webrtc/src/webrtcsink/homegrown_cc.rs +++ b/net/webrtc/src/webrtcsink/homegrown_cc.rs @@ -413,10 +413,11 @@ impl CongestionController { let fec_percentage = (fec_ratio * 50f64) as u32; for encoder in encoders.iter_mut() { - encoder.set_bitrate(element, target_bitrate); - encoder - .transceiver - .set_property("fec-percentage", fec_percentage); + if encoder.set_bitrate(element, target_bitrate).is_ok() { + encoder + .transceiver + .set_property("fec-percentage", fec_percentage); + } } } } diff --git a/net/webrtc/src/webrtcsink/imp.rs b/net/webrtc/src/webrtcsink/imp.rs index 20b0d1d4..2f2549f5 100644 --- a/net/webrtc/src/webrtcsink/imp.rs +++ b/net/webrtc/src/webrtcsink/imp.rs @@ -952,8 +952,24 @@ impl VideoEncoder { }) } - fn bitrate(&self) -> i32 { - match self.factory_name.as_str() { + fn is_bitrate_supported(factory_name: &str) -> bool { + matches!( + factory_name, + "vp8enc" + | "vp9enc" + | "x264enc" + | "nvh264enc" + | "vaapih264enc" + | "vaapivp8enc" + | "qsvh264enc" + | "nvv4l2h264enc" + | "nvv4l2vp8enc" + | "nvv4l2vp9enc" + ) + } + + fn bitrate(&self) -> Result { + let bitrate = match self.factory_name.as_str() { "vp8enc" | "vp9enc" => self.element.property::("target-bitrate"), "x264enc" | "nvh264enc" | "vaapih264enc" | "vaapivp8enc" | "qsvh264enc" => { (self.element.property::("bitrate") * 1000) as i32 @@ -961,8 +977,10 @@ impl VideoEncoder { "nvv4l2h264enc" | "nvv4l2vp8enc" | "nvv4l2vp9enc" => { (self.element.property::("bitrate")) as i32 } - factory => unimplemented!("Factory {} is currently not supported", factory), - } + _ => return Err(WebRTCSinkError::BitrateNotSupported), + }; + + Ok(bitrate) } fn scale_height_round_2(&self, height: i32) -> i32 { @@ -979,16 +997,21 @@ impl VideoEncoder { (width + 1) & !1 } - pub(crate) fn set_bitrate(&mut self, element: &super::BaseWebRTCSink, bitrate: i32) { + pub(crate) fn set_bitrate( + &mut self, + element: &super::BaseWebRTCSink, + bitrate: i32, + ) -> Result<(), WebRTCSinkError> { match self.factory_name.as_str() { "vp8enc" | "vp9enc" => self.element.set_property("target-bitrate", bitrate), - "x264enc" | "nvh264enc" | "vaapih264enc" | "vaapivp8enc" | "qsvh264enc" => self - .element - .set_property("bitrate", (bitrate / 1000) as u32), + "x264enc" | "nvh264enc" | "vaapih264enc" | "vaapivp8enc" | "qsvh264enc" => { + self.element + .set_property("bitrate", (bitrate / 1000) as u32); + } "nvv4l2h264enc" | "nvv4l2vp8enc" | "nvv4l2vp9enc" => { self.element.set_property("bitrate", bitrate as u32) } - factory => unimplemented!("Factory {} is currently not supported", factory), + _ => return Err(WebRTCSinkError::BitrateNotSupported), } let current_caps = self.filter.property::("caps"); @@ -1052,11 +1075,13 @@ impl VideoEncoder { self.filter.set_property("caps", caps); } + + Ok(()) } fn gather_stats(&self) -> gst::Structure { gst::Structure::builder("application/x-webrtcsink-video-encoder-stats") - .field("bitrate", self.bitrate()) + .field("bitrate", self.bitrate().unwrap_or(0i32)) .field("mitigation-mode", self.mitigation_mode) .field("codec-name", self.codec_name.as_str()) .field( @@ -1339,19 +1364,21 @@ impl Session { WebRTCSinkCongestionControl::Disabled => { // If congestion control is disabled, we simply use the highest // known "safe" value for the bitrate. - enc.set_bitrate(element, self.cc_info.max_bitrate as i32); + let _ = enc.set_bitrate(element, self.cc_info.max_bitrate as i32); enc.transceiver.set_property("fec-percentage", 50u32); } WebRTCSinkCongestionControl::Homegrown => { if let Some(congestion_controller) = self.congestion_controller.as_mut() { - congestion_controller.target_bitrate_on_delay += enc.bitrate(); - congestion_controller.target_bitrate_on_loss = - congestion_controller.target_bitrate_on_delay; - enc.transceiver.set_property("fec-percentage", 0u32); + if let Ok(bitrate) = enc.bitrate() { + congestion_controller.target_bitrate_on_delay += bitrate; + congestion_controller.target_bitrate_on_loss = + congestion_controller.target_bitrate_on_delay; + enc.transceiver.set_property("fec-percentage", 0u32); + } } else { /* If congestion control is disabled, we simply use the highest * known "safe" value for the bitrate. */ - enc.set_bitrate(element, self.cc_info.max_bitrate as i32); + let _ = enc.set_bitrate(element, self.cc_info.max_bitrate as i32); enc.transceiver.set_property("fec-percentage", 50u32); } } @@ -1497,6 +1524,7 @@ impl BaseWebRTCSink { fn configure_congestion_control( &self, payloader: &gst::Element, + codec: &Codec, extension_configuration_type: ExtensionConfigurationType, ) -> Result<(), Error> { if let ExtensionConfigurationType::Skip = extension_configuration_type { @@ -1505,6 +1533,16 @@ impl BaseWebRTCSink { let settings = self.settings.lock().unwrap(); + if codec.is_video() { + if let Some(enc_name) = codec.encoder_name().as_deref() { + if !VideoEncoder::is_bitrate_supported(enc_name) { + gst::error!(CAT, imp: self, "Bitrate handling is not supported yet for {enc_name}"); + + return Ok(()); + } + } + } + if settings.cc_info.heuristic == WebRTCSinkCongestionControl::Disabled { return Ok(()); } @@ -1620,7 +1658,7 @@ impl BaseWebRTCSink { payloader.set_property("ssrc", ssrc); } - self.configure_congestion_control(payloader, extension_configuration_type) + self.configure_congestion_control(payloader, codec, extension_configuration_type) } fn generate_ssrc( @@ -2951,10 +2989,11 @@ impl BaseWebRTCSink { } for encoder in session.encoders.iter_mut() { - encoder.set_bitrate(element, encoders_bitrate); - encoder - .transceiver - .set_property("fec-percentage", (fec_percentage as u32).min(100)); + if encoder.set_bitrate(element, encoders_bitrate).is_ok() { + encoder + .transceiver + .set_property("fec-percentage", (fec_percentage as u32).min(100)); + } } } } @@ -3400,7 +3439,7 @@ impl BaseWebRTCSink { let is_video = match sink_caps.structure(0).unwrap().name().as_str() { "video/x-raw" => true, "audio/x-raw" => false, - _ => panic!("expected audio or video raw caps: {sink_caps}"), + _ => anyhow::bail!("Unsupported caps: {}", discovery_info.caps), }; codecs diff --git a/net/webrtc/src/webrtcsink/mod.rs b/net/webrtc/src/webrtcsink/mod.rs index f566868a..616ca67a 100644 --- a/net/webrtc/src/webrtcsink/mod.rs +++ b/net/webrtc/src/webrtcsink/mod.rs @@ -87,6 +87,8 @@ pub enum WebRTCSinkError { peer_id: String, details: String, }, + #[error("Bitrate handling currently not supported for requested encoder")] + BitrateNotSupported, } impl Default for BaseWebRTCSink { diff --git a/net/webrtc/src/webrtcsrc/imp.rs b/net/webrtc/src/webrtcsrc/imp.rs index 6923a99a..b779facf 100644 --- a/net/webrtc/src/webrtcsrc/imp.rs +++ b/net/webrtc/src/webrtcsrc/imp.rs @@ -240,7 +240,7 @@ impl ObjectImpl for BaseWebRTCSrc { */ glib::subclass::Signal::builder("request-encoded-filter") .param_types([ - String::static_type(), + Option::::static_type(), String::static_type(), Option::::static_type(), ]) diff --git a/video/gtk4/Cargo.toml b/video/gtk4/Cargo.toml index fce75fd9..ce011d65 100644 --- a/video/gtk4/Cargo.toml +++ b/video/gtk4/Cargo.toml @@ -17,6 +17,7 @@ gst = { workspace = true, features = ["v1_16"] } gst-base.workspace = true gst-video.workspace = true gst-gl = { workspace = true, features = ["v1_16"], optional = true } +gst-allocators = { workspace = true, features = ["v1_24"], optional = true } gst-gl-wayland = { workspace = true, features = ["v1_16"], optional = true } gst-gl-x11 = { workspace = true, features = ["v1_16"], optional = true } @@ -50,6 +51,7 @@ wayland = ["gtk/v4_6", "gdk-wayland", "gst-gl", "gst-gl-wayland"] x11glx = ["gtk/v4_6", "gdk-x11", "gst-gl", "gst-gl-x11"] x11egl = ["gtk/v4_6", "gdk-x11", "gst-gl", "gst-gl-egl"] winegl = ["gdk-win32/egl", "gst-gl-egl"] +dmabuf = ["gst-allocators", "wayland", "gtk_v4_14", "gst-video/v1_24"] capi = [] doc = ["gst/v1_18"] gtk_v4_10 = ["gtk/v4_10"] diff --git a/video/gtk4/README.md b/video/gtk4/README.md index 4955d6a9..aaf66f33 100644 --- a/video/gtk4/README.md +++ b/video/gtk4/README.md @@ -1,10 +1,20 @@ -# Gtk 4 Sink & Paintable +# GTK 4 Sink & Paintable GTK 4 provides `gtk::Video` & `gtk::Picture` for rendering media such as videos. As the default `gtk::Video` widget doesn't offer the possibility to use a custom `gst::Pipeline`. The plugin provides a `gst_video::VideoSink` along with a `gdk::Paintable` that's capable of rendering the sink's frames. -The Sink can generate GL Textures if the system is capable of it, but it needs to be compiled -with either `wayland`, `x11glx` or `x11egl` cargo features. +The sink can generate GL Textures if the system is capable of it, but it needs +to be compiled with either `wayland`, `x11glx` or `x11egl` cargo features. On +Windows and macOS this is enabled by default. + +Additionally, the sink can render DMABufs directly on Linux if GTK 4.14 or +newer is used. For this the `dmabuf` feature needs to be enabled. + +Depending on the GTK version that is used and should be supported as minimum, +new features or more efficient processing can be opted in with the `gtk_v4_10`, +`gtk_v4_12` and `gtk_v4_14` features. The minimum GTK version required by the +sink is GTK 4.4 on Linux without GL support, and 4.6 on Windows and macOS, and +on Linux with GL support. # Flatpak Integration @@ -44,7 +54,7 @@ To build and include the plugin in a Flatpak manifest, you can add the following { "type": "git", "url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs", - "branch": "0.10" + "branch": "0.12" } ], "build-options": { diff --git a/video/gtk4/examples/gtksink.rs b/video/gtk4/examples/gtksink.rs index b431b5c5..abda23c6 100644 --- a/video/gtk4/examples/gtksink.rs +++ b/video/gtk4/examples/gtksink.rs @@ -6,13 +6,6 @@ use gtk::{gdk, gio, glib}; use std::cell::RefCell; fn create_ui(app: >k::Application) { - let window = gtk::ApplicationWindow::new(app); - window.set_default_size(640, 480); - - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - let picture = gtk::Picture::new(); - let label = gtk::Label::new(Some("Position: 00:00:00")); - let pipeline = gst::Pipeline::new(); let overlay = gst::ElementFactory::make("clockoverlay") @@ -64,8 +57,26 @@ fn create_ui(app: >k::Application) { src.link_filtered(&overlay, &caps).unwrap(); overlay.link(&sink).unwrap(); + let window = gtk::ApplicationWindow::new(app); + window.set_default_size(640, 480); + + let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); + + let picture = gtk::Picture::new(); picture.set_paintable(Some(&paintable)); - vbox.append(&picture); + + #[cfg(feature = "gtk_v4_14")] + { + let offload = gtk::GraphicsOffload::new(Some(&picture)); + offload.set_enabled(gtk::GraphicsOffloadEnabled::Enabled); + vbox.append(&offload); + } + #[cfg(not(feature = "gtk_v4_14"))] + { + vbox.append(&picture); + } + + let label = gtk::Label::new(Some("Position: 00:00:00")); vbox.append(&label); window.set_child(Some(&vbox)); diff --git a/video/gtk4/src/sink/frame.rs b/video/gtk4/src/sink/frame.rs index aaa7e224..f9f1c61b 100644 --- a/video/gtk4/src/sink/frame.rs +++ b/video/gtk4/src/sink/frame.rs @@ -14,7 +14,61 @@ use gst_video::prelude::*; #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] use gst_gl::prelude::*; use gtk::{gdk, glib}; -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + ops, +}; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum VideoInfo { + VideoInfo(gst_video::VideoInfo), + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + DmaDrm(gst_video::VideoInfoDmaDrm), +} + +impl From for VideoInfo { + fn from(v: gst_video::VideoInfo) -> Self { + VideoInfo::VideoInfo(v) + } +} + +#[cfg(all(target_os = "linux", feature = "dmabuf"))] +impl From for VideoInfo { + fn from(v: gst_video::VideoInfoDmaDrm) -> Self { + VideoInfo::DmaDrm(v) + } +} + +impl ops::Deref for VideoInfo { + type Target = gst_video::VideoInfo; + + fn deref(&self) -> &Self::Target { + match self { + VideoInfo::VideoInfo(info) => info, + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + VideoInfo::DmaDrm(info) => info, + } + } +} + +impl VideoInfo { + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + fn dma_drm(&self) -> Option<&gst_video::VideoInfoDmaDrm> { + match self { + VideoInfo::VideoInfo(..) => None, + VideoInfo::DmaDrm(info) => Some(info), + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum TextureCacheId { + Memory(usize), + #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] + GL(usize), + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + DmaBuf([i32; 4]), +} #[derive(Debug)] enum MappedFrame { @@ -24,6 +78,17 @@ enum MappedFrame { frame: gst_gl::GLVideoFrame, wrapped_context: gst_gl::GLContext, }, + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + DmaBuf { + buffer: gst::Buffer, + info: gst_video::VideoInfoDmaDrm, + n_planes: u32, + fds: [i32; 4], + offsets: [usize; 4], + strides: [usize; 4], + width: u32, + height: u32, + }, } impl MappedFrame { @@ -32,6 +97,8 @@ impl MappedFrame { MappedFrame::SysMem(frame) => frame.buffer(), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] MappedFrame::GL { frame, .. } => frame.buffer(), + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + MappedFrame::DmaBuf { buffer, .. } => buffer, } } @@ -40,6 +107,8 @@ impl MappedFrame { MappedFrame::SysMem(frame) => frame.width(), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] MappedFrame::GL { frame, .. } => frame.width(), + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + MappedFrame::DmaBuf { info, .. } => info.width(), } } @@ -48,6 +117,8 @@ impl MappedFrame { MappedFrame::SysMem(frame) => frame.height(), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] MappedFrame::GL { frame, .. } => frame.height(), + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + MappedFrame::DmaBuf { info, .. } => info.height(), } } @@ -56,6 +127,8 @@ impl MappedFrame { MappedFrame::SysMem(frame) => frame.format_info(), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] MappedFrame::GL { frame, .. } => frame.format_info(), + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + MappedFrame::DmaBuf { info, .. } => info.format_info(), } } } @@ -108,16 +181,16 @@ fn video_format_to_memory_format(f: gst_video::VideoFormat) -> gdk::MemoryFormat fn video_frame_to_memory_texture( frame: gst_video::VideoFrame, - cached_textures: &mut HashMap, - used_textures: &mut HashSet, + cached_textures: &mut HashMap, + used_textures: &mut HashSet, ) -> (gdk::Texture, f64) { - let texture_id = frame.plane_data(0).unwrap().as_ptr() as usize; + let ptr = frame.plane_data(0).unwrap().as_ptr() as usize; let pixel_aspect_ratio = (frame.info().par().numer() as f64) / (frame.info().par().denom() as f64); - if let Some(texture) = cached_textures.get(&texture_id) { - used_textures.insert(texture_id); + if let Some(texture) = cached_textures.get(&TextureCacheId::Memory(ptr)) { + used_textures.insert(TextureCacheId::Memory(ptr)); return (texture.clone(), pixel_aspect_ratio); } @@ -135,8 +208,8 @@ fn video_frame_to_memory_texture( ) .upcast::(); - cached_textures.insert(texture_id, texture.clone()); - used_textures.insert(texture_id); + cached_textures.insert(TextureCacheId::Memory(ptr), texture.clone()); + used_textures.insert(TextureCacheId::Memory(ptr)); (texture, pixel_aspect_ratio) } @@ -144,8 +217,8 @@ fn video_frame_to_memory_texture( #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] fn video_frame_to_gl_texture( frame: gst_gl::GLVideoFrame, - cached_textures: &mut HashMap, - used_textures: &mut HashSet, + cached_textures: &mut HashMap, + used_textures: &mut HashSet, gdk_context: &gdk::GLContext, #[allow(unused)] wrapped_context: &gst_gl::GLContext, ) -> (gdk::Texture, f64) { @@ -154,8 +227,8 @@ fn video_frame_to_gl_texture( let pixel_aspect_ratio = (frame.info().par().numer() as f64) / (frame.info().par().denom() as f64); - if let Some(texture) = cached_textures.get(&(texture_id)) { - used_textures.insert(texture_id); + if let Some(texture) = cached_textures.get(&TextureCacheId::GL(texture_id)) { + used_textures.insert(TextureCacheId::GL(texture_id)); return (texture.clone(), pixel_aspect_ratio); } @@ -237,18 +310,64 @@ fn video_frame_to_gl_texture( .upcast::() }; - cached_textures.insert(texture_id, texture.clone()); - used_textures.insert(texture_id); + cached_textures.insert(TextureCacheId::GL(texture_id), texture.clone()); + used_textures.insert(TextureCacheId::GL(texture_id)); (texture, pixel_aspect_ratio) } +#[cfg(all(target_os = "linux", feature = "dmabuf"))] +#[allow(clippy::too_many_arguments)] +fn video_frame_to_dmabuf_texture( + buffer: gst::Buffer, + cached_textures: &mut HashMap, + used_textures: &mut HashSet, + info: &gst_video::VideoInfoDmaDrm, + n_planes: u32, + fds: &[i32; 4], + offsets: &[usize; 4], + strides: &[usize; 4], + width: u32, + height: u32, +) -> Result<(gdk::Texture, f64), glib::Error> { + let pixel_aspect_ratio = (info.par().numer() as f64) / (info.par().denom() as f64); + + if let Some(texture) = cached_textures.get(&TextureCacheId::DmaBuf(*fds)) { + used_textures.insert(TextureCacheId::DmaBuf(*fds)); + return Ok((texture.clone(), pixel_aspect_ratio)); + } + + let builder = gdk::DmabufTextureBuilder::new(); + builder.set_display(&gdk::Display::default().unwrap()); + builder.set_fourcc(info.fourcc()); + builder.set_modifier(info.modifier()); + builder.set_width(width); + builder.set_height(height); + builder.set_n_planes(n_planes); + for plane in 0..(n_planes as usize) { + builder.set_fd(plane as u32, fds[plane]); + builder.set_offset(plane as u32, offsets[plane] as u32); + builder.set_stride(plane as u32, strides[plane] as u32); + } + + let texture = unsafe { + builder.build_with_release_func(move || { + drop(buffer); + })? + }; + + cached_textures.insert(TextureCacheId::DmaBuf(*fds), texture.clone()); + used_textures.insert(TextureCacheId::DmaBuf(*fds)); + + Ok((texture, pixel_aspect_ratio)) +} + impl Frame { pub(crate) fn into_textures( self, #[allow(unused_variables)] gdk_context: Option<&gdk::GLContext>, - cached_textures: &mut HashMap, - ) -> Vec { + cached_textures: &mut HashMap, + ) -> Result, glib::Error> { let mut textures = Vec::with_capacity(1 + self.overlays.len()); let mut used_textures = HashSet::with_capacity(1 + self.overlays.len()); @@ -278,6 +397,28 @@ impl Frame { &wrapped_context, ) } + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + MappedFrame::DmaBuf { + buffer, + info, + n_planes, + fds, + offsets, + strides, + width, + height, + } => video_frame_to_dmabuf_texture( + buffer, + cached_textures, + &mut used_textures, + &info, + n_planes, + &fds, + &offsets, + &strides, + width, + height, + )?, }; textures.push(Texture { @@ -309,14 +450,14 @@ impl Frame { // Remove textures that were not used this time cached_textures.retain(|id, _| used_textures.contains(id)); - textures + Ok(textures) } } impl Frame { pub(crate) fn new( buffer: &gst::Buffer, - info: &gst_video::VideoInfo, + info: &VideoInfo, #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] wrapped_context: Option< &gst_gl::GLContext, >, @@ -327,77 +468,125 @@ impl Frame { // Empty buffers get filtered out in show_frame debug_assert!(buffer.n_memory() > 0); - let mut frame; + #[allow(unused_mut)] + let mut frame = None; - #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst-gl")))] + #[cfg(all(target_os = "linux", feature = "dmabuf"))] { - frame = Self { - frame: MappedFrame::SysMem( + // Check we received a buffer with dmabuf memory and if so do some checks before + // passing it onwards + if frame.is_none() + && buffer + .peek_memory(0) + .is_memory_type::() + { + if let Some((vmeta, info)) = + Option::zip(buffer.meta::(), info.dma_drm()) + { + let mut fds = [-1i32; 4]; + let mut offsets = [0; 4]; + let mut strides = [0; 4]; + let n_planes = vmeta.n_planes() as usize; + + let vmeta_offsets = vmeta.offset(); + let vmeta_strides = vmeta.stride(); + + for plane in 0..n_planes { + let Some((range, skip)) = + buffer.find_memory(vmeta_offsets[plane]..(vmeta_offsets[plane] + 1)) + else { + break; + }; + + let mem = buffer.peek_memory(range.start); + let Some(mem) = mem.downcast_memory_ref::() + else { + break; + }; + + let fd = mem.fd(); + fds[plane] = fd; + offsets[plane] = mem.offset() + skip; + strides[plane] = vmeta_strides[plane] as usize; + } + + // All fds valid? + if fds[0..n_planes].iter().all(|fd| *fd != -1) { + frame = Some(MappedFrame::DmaBuf { + buffer: buffer.clone(), + info: info.clone(), + n_planes: n_planes as u32, + fds, + offsets, + strides, + width: vmeta.width(), + height: vmeta.height(), + }); + } + } + } + #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] + { + if frame.is_none() { + // Check we received a buffer with GL memory and if the context of that memory + // can share with the wrapped context around the GDK GL context. + // + // If not it has to be uploaded to the GPU. + let memory_ctx = buffer + .peek_memory(0) + .downcast_memory_ref::() + .and_then(|m| { + let ctx = m.context(); + if wrapped_context + .map_or(false, |wrapped_context| wrapped_context.can_share(ctx)) + { + Some(ctx) + } else { + None + } + }); + + if let Some(memory_ctx) = memory_ctx { + // If there is no GLSyncMeta yet then we need to add one here now, which requires + // obtaining a writable buffer. + let mapped_frame = if buffer.meta::().is_some() { + gst_gl::GLVideoFrame::from_buffer_readable(buffer.clone(), info) + .map_err(|_| gst::FlowError::Error)? + } else { + let mut buffer = buffer.clone(); + { + let buffer = buffer.make_mut(); + gst_gl::GLSyncMeta::add(buffer, memory_ctx); + } + gst_gl::GLVideoFrame::from_buffer_readable(buffer, info) + .map_err(|_| gst::FlowError::Error)? + }; + + // Now that it's guaranteed that there is a sync meta and the frame is mapped, set + // a sync point so we can ensure that the texture is ready later when making use of + // it as gdk::GLTexture. + let meta = mapped_frame.buffer().meta::().unwrap(); + meta.set_sync_point(memory_ctx); + + frame = Some(MappedFrame::GL { + frame: mapped_frame, + wrapped_context: wrapped_context.unwrap().clone(), + }); + } + } + } + } + + let mut frame = Self { + frame: match frame { + Some(frame) => frame, + None => MappedFrame::SysMem( gst_video::VideoFrame::from_buffer_readable(buffer.clone(), info) .map_err(|_| gst::FlowError::Error)?, ), - overlays: vec![], - }; - } - #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] - { - // Check we received a buffer with GL memory and if the context of that memory - // can share with the wrapped context around the GDK GL context. - // - // If not it has to be uploaded to the GPU. - let memory_ctx = buffer - .peek_memory(0) - .downcast_memory_ref::() - .and_then(|m| { - let ctx = m.context(); - if wrapped_context - .map_or(false, |wrapped_context| wrapped_context.can_share(ctx)) - { - Some(ctx) - } else { - None - } - }); - - if let Some(memory_ctx) = memory_ctx { - // If there is no GLSyncMeta yet then we need to add one here now, which requires - // obtaining a writable buffer. - let mapped_frame = if buffer.meta::().is_some() { - gst_gl::GLVideoFrame::from_buffer_readable(buffer.clone(), info) - .map_err(|_| gst::FlowError::Error)? - } else { - let mut buffer = buffer.clone(); - { - let buffer = buffer.make_mut(); - gst_gl::GLSyncMeta::add(buffer, memory_ctx); - } - gst_gl::GLVideoFrame::from_buffer_readable(buffer, info) - .map_err(|_| gst::FlowError::Error)? - }; - - // Now that it's guaranteed that there is a sync meta and the frame is mapped, set - // a sync point so we can ensure that the texture is ready later when making use of - // it as gdk::GLTexture. - let meta = mapped_frame.buffer().meta::().unwrap(); - meta.set_sync_point(memory_ctx); - - frame = Self { - frame: MappedFrame::GL { - frame: mapped_frame, - wrapped_context: wrapped_context.unwrap().clone(), - }, - overlays: vec![], - }; - } else { - frame = Self { - frame: MappedFrame::SysMem( - gst_video::VideoFrame::from_buffer_readable(buffer.clone(), info) - .map_err(|_| gst::FlowError::Error)?, - ), - overlays: vec![], - }; - } - } + }, + overlays: vec![], + }; frame.overlays = frame .frame diff --git a/video/gtk4/src/sink/imp.rs b/video/gtk4/src/sink/imp.rs index dcad8d42..8b98c4cb 100644 --- a/video/gtk4/src/sink/imp.rs +++ b/video/gtk4/src/sink/imp.rs @@ -1,7 +1,7 @@ // // Copyright (C) 2021 Bilal Elmoussaoui // Copyright (C) 2021 Jordan Petridis -// Copyright (C) 2021 Sebastian Dröge +// Copyright (C) 2021-2024 Sebastian Dröge // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at @@ -62,7 +62,7 @@ pub(crate) static CAT: Lazy = Lazy::new(|| { pub struct PaintableSink { paintable: Mutex>>, window: Mutex>>, - info: Mutex>, + info: Mutex>, sender: Mutex>>, pending_frame: Mutex>, cached_caps: Mutex>, @@ -82,6 +82,7 @@ impl ObjectSubclass for PaintableSink { const NAME: &'static str = "GstGtk4PaintableSink"; type Type = super::PaintableSink; type ParentType = gst_video::VideoSink; + type Interfaces = (gst::ChildProxy,); } impl ObjectImpl for PaintableSink { @@ -110,12 +111,14 @@ impl ObjectImpl for PaintableSink { return None::<&gdk::Paintable>.to_value(); } - let mut paintable = self.paintable.lock().unwrap(); - if paintable.is_none() { - self.create_paintable(&mut paintable); + let mut paintable_guard = self.paintable.lock().unwrap(); + let mut created = false; + if paintable_guard.is_none() { + created = true; + self.create_paintable(&mut paintable_guard); } - let paintable = match &*paintable { + let paintable = match &*paintable_guard { Some(ref paintable) => paintable, None => { gst::error!(CAT, imp: self, "Failed to create paintable"); @@ -124,16 +127,31 @@ impl ObjectImpl for PaintableSink { }; // Getter must be called from the main thread - if paintable.is_owner() { - paintable.get_ref().to_value() - } else { + if !paintable.is_owner() { gst::error!( CAT, imp: self, "Can't retrieve Paintable from non-main thread" ); - None::<&gdk::Paintable>.to_value() + return None::<&gdk::Paintable>.to_value(); } + + let paintable = paintable.get_ref().clone(); + drop(paintable_guard); + + if created { + let self_ = self.to_owned(); + glib::MainContext::default().invoke(move || { + let paintable_guard = self_.paintable.lock().unwrap(); + if let Some(paintable) = &*paintable_guard { + let paintable_clone = paintable.get_ref().clone(); + drop(paintable_guard); + self_.obj().child_added(&paintable_clone, "paintable"); + } + }); + } + + paintable.to_value() } _ => unimplemented!(), } @@ -163,53 +181,99 @@ impl ElementImpl for PaintableSink { { let caps = caps.get_mut().unwrap(); + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + { + for features in [ + [ + gst_allocators::CAPS_FEATURE_MEMORY_DMABUF, + gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, + ] + .as_slice(), + [gst_allocators::CAPS_FEATURE_MEMORY_DMABUF].as_slice(), + ] { + let c = gst_video::VideoCapsBuilder::new() + .format(gst_video::VideoFormat::DmaDrm) + .features(features.iter().copied()) + .build(); + caps.append(c); + } + } + for features in [ - None, #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] Some(gst::CapsFeatures::new([ - "memory:GLMemory", - "meta:GstVideoOverlayComposition", + gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY, + gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, ])), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] - Some(gst::CapsFeatures::new(["memory:GLMemory"])), + Some(gst::CapsFeatures::new([ + gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY, + ])), Some(gst::CapsFeatures::new([ "memory:SystemMemory", - "meta:GstVideoOverlayComposition", + gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, ])), - Some(gst::CapsFeatures::new(["meta:GstVideoOverlayComposition"])), + Some(gst::CapsFeatures::new([ + gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, + ])), + None, ] { - const GL_FORMATS: &[gst_video::VideoFormat] = - &[gst_video::VideoFormat::Rgba, gst_video::VideoFormat::Rgb]; - const NON_GL_FORMATS: &[gst_video::VideoFormat] = &[ - gst_video::VideoFormat::Bgra, - gst_video::VideoFormat::Argb, - gst_video::VideoFormat::Rgba, - gst_video::VideoFormat::Abgr, - gst_video::VideoFormat::Rgb, - gst_video::VideoFormat::Bgr, - ]; - - let formats = if features - .as_ref() - .is_some_and(|features| features.contains("memory:GLMemory")) + #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { - GL_FORMATS - } else { - NON_GL_FORMATS - }; + const GL_FORMATS: &[gst_video::VideoFormat] = + &[gst_video::VideoFormat::Rgba, gst_video::VideoFormat::Rgb]; + const NON_GL_FORMATS: &[gst_video::VideoFormat] = &[ + gst_video::VideoFormat::Bgra, + gst_video::VideoFormat::Argb, + gst_video::VideoFormat::Rgba, + gst_video::VideoFormat::Abgr, + gst_video::VideoFormat::Rgb, + gst_video::VideoFormat::Bgr, + ]; - let mut c = gst_video::video_make_raw_caps(formats).build(); + let formats = if features.as_ref().is_some_and(|features| { + features.contains(gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY) + }) { + GL_FORMATS + } else { + NON_GL_FORMATS + }; - if let Some(features) = features { - let c = c.get_mut().unwrap(); + let mut c = gst_video::video_make_raw_caps(formats).build(); - if features.contains("memory:GLMemory") { - c.set("texture-target", "2D") + if let Some(features) = features { + let c = c.get_mut().unwrap(); + + if features.contains(gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY) { + c.set("texture-target", "2D") + } + c.set_features_simple(Some(features)); } - c.set_features_simple(Some(features)); + caps.append(c); } + #[cfg(not(any( + target_os = "macos", + target_os = "windows", + feature = "gst-gl" + )))] + { + const FORMATS: &[gst_video::VideoFormat] = &[ + gst_video::VideoFormat::Bgra, + gst_video::VideoFormat::Argb, + gst_video::VideoFormat::Rgba, + gst_video::VideoFormat::Abgr, + gst_video::VideoFormat::Rgb, + gst_video::VideoFormat::Bgr, + ]; - caps.append(c); + let mut c = gst_video::video_make_raw_caps(FORMATS).build(); + + if let Some(features) = features { + let c = c.get_mut().unwrap(); + c.set_features_simple(Some(features)); + } + caps.append(c); + } } } @@ -244,18 +308,31 @@ impl ElementImpl for PaintableSink { } } - let mut paintable = self.paintable.lock().unwrap(); - - if paintable.is_none() { - self.create_paintable(&mut paintable); + let mut paintable_guard = self.paintable.lock().unwrap(); + let mut created = false; + if paintable_guard.is_none() { + created = true; + self.create_paintable(&mut paintable_guard); } - if paintable.is_none() { + if paintable_guard.is_none() { gst::error!(CAT, imp: self, "Failed to create paintable"); return Err(gst::StateChangeError); } - drop(paintable); + drop(paintable_guard); + + if created { + let self_ = self.to_owned(); + glib::MainContext::default().invoke(move || { + let paintable_guard = self_.paintable.lock().unwrap(); + if let Some(paintable) = &*paintable_guard { + let paintable_clone = paintable.get_ref().clone(); + drop(paintable_guard); + self_.obj().child_added(&paintable_clone, "paintable"); + } + }); + } // Notify the pipeline about the GL display and wrapped context so that any other // elements in the pipeline ideally use the same / create GL contexts that are @@ -361,8 +438,21 @@ impl BaseSinkImpl for PaintableSink { fn set_caps(&self, caps: &gst::Caps) -> Result<(), gst::LoggableError> { gst::debug!(CAT, imp: self, "Setting caps {caps:?}"); - let video_info = gst_video::VideoInfo::from_caps(caps) - .map_err(|_| gst::loggable_error!(CAT, "Invalid caps"))?; + #[allow(unused_mut)] + let mut video_info = None; + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + { + if let Ok(info) = gst_video::VideoInfoDmaDrm::from_caps(caps) { + video_info = Some(info.into()); + } + } + + let video_info = match video_info { + Some(info) => info, + None => gst_video::VideoInfo::from_caps(caps) + .map_err(|_| gst::loggable_error!(CAT, "Invalid caps"))? + .into(), + }; self.info.lock().unwrap().replace(video_info); @@ -516,10 +606,11 @@ impl PaintableSink { match action { SinkEvent::FrameChanged => { + let Some(frame) = self.pending_frame() else { + return glib::ControlFlow::Continue; + }; gst::trace!(CAT, imp: self, "Frame changed"); - paintable - .get_ref() - .handle_frame_changed(self.pending_frame()) + paintable.get_ref().handle_frame_changed(&self.obj(), frame); } } @@ -530,13 +621,59 @@ impl PaintableSink { #[allow(unused_mut)] let mut tmp_caps = Self::pad_templates()[0].caps().clone(); + #[cfg(all(target_os = "linux", feature = "dmabuf"))] + { + let formats = utils::invoke_on_main_thread(move || { + let Some(display) = gdk::Display::default() else { + return vec![]; + }; + let dmabuf_formats = display.dmabuf_formats(); + + let mut formats = vec![]; + let n_formats = dmabuf_formats.n_formats(); + for i in 0..n_formats { + let (fourcc, modifier) = dmabuf_formats.format(i); + + if fourcc == 0 || modifier == (u64::MAX >> 8) { + continue; + } + + formats.push(gst_video::dma_drm_fourcc_to_string(fourcc, modifier)); + } + + formats + }); + + if formats.is_empty() { + // Filter out dmabufs caps from the template pads if we have no supported formats + if !matches!(&*GL_CONTEXT.lock().unwrap(), GLContext::Initialized { .. }) { + tmp_caps = tmp_caps + .iter_with_features() + .filter(|(_, features)| { + !features.contains(gst_allocators::CAPS_FEATURE_MEMORY_DMABUF) + }) + .map(|(s, c)| (s.to_owned(), c.to_owned())) + .collect::(); + } + } else { + let tmp_caps = tmp_caps.make_mut(); + for (s, f) in tmp_caps.iter_with_features_mut() { + if f.contains(gst_allocators::CAPS_FEATURE_MEMORY_DMABUF) { + s.set("drm-format", gst::List::new(&formats)); + } + } + } + } + #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { // Filter out GL caps from the template pads if we have no context if !matches!(&*GL_CONTEXT.lock().unwrap(), GLContext::Initialized { .. }) { tmp_caps = tmp_caps .iter_with_features() - .filter(|(_, features)| !features.contains("memory:GLMemory")) + .filter(|(_, features)| { + !features.contains(gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY) + }) .map(|(s, c)| (s.to_owned(), c.to_owned())) .collect::(); } @@ -564,7 +701,17 @@ impl PaintableSink { let window = gtk::Window::new(); let picture = gtk::Picture::new(); picture.set_paintable(Some(&paintable)); - window.set_child(Some(&picture)); + + #[cfg(feature = "gtk_v4_14")] + { + let offload = gtk::GraphicsOffload::new(Some(&picture)); + offload.set_enabled(gtk::GraphicsOffloadEnabled::Enabled); + window.set_child(Some(&offload)); + } + #[cfg(not(feature = "gtk_v4_14"))] + { + window.set_child(Some(&picture)); + } window.set_default_size(640, 480); window.connect_close_request({ @@ -1073,3 +1220,33 @@ impl PaintableSink { } } } + +impl ChildProxyImpl for PaintableSink { + fn child_by_index(&self, index: u32) -> Option { + if index != 0 { + return None; + } + + let paintable = self.paintable.lock().unwrap(); + paintable + .as_ref() + .filter(|p| p.is_owner()) + .map(|p| p.get_ref().upcast_ref::().clone()) + } + + fn child_by_name(&self, name: &str) -> Option { + if name == "paintable" { + return self.child_by_index(0); + } + None + } + + fn children_count(&self) -> u32 { + let paintable = self.paintable.lock().unwrap(); + if paintable.is_some() { + 1 + } else { + 0 + } + } +} diff --git a/video/gtk4/src/sink/mod.rs b/video/gtk4/src/sink/mod.rs index 96ff9299..5008ed00 100644 --- a/video/gtk4/src/sink/mod.rs +++ b/video/gtk4/src/sink/mod.rs @@ -22,7 +22,8 @@ enum SinkEvent { glib::wrapper! { pub struct PaintableSink(ObjectSubclass) - @extends gst_video::VideoSink, gst_base::BaseSink, gst::Element, gst::Object; + @extends gst_video::VideoSink, gst_base::BaseSink, gst::Element, gst::Object, + @implements gst::ChildProxy; } impl PaintableSink { diff --git a/video/gtk4/src/sink/paintable/imp.rs b/video/gtk4/src/sink/paintable/imp.rs index eccff6b8..dd9b6749 100644 --- a/video/gtk4/src/sink/paintable/imp.rs +++ b/video/gtk4/src/sink/paintable/imp.rs @@ -31,12 +31,13 @@ static CAT: Lazy = Lazy::new(|| { #[derive(Debug)] pub struct Paintable { paintables: RefCell>, - cached_textures: RefCell>, + cached_textures: RefCell>, gl_context: RefCell>, background_color: Cell, #[cfg(feature = "gtk_v4_10")] scaling_filter: Cell, use_scaling_filter: Cell, + force_aspect_ratio: Cell, #[cfg(not(feature = "gtk_v4_10"))] premult_shader: gsk::GLShader, } @@ -51,6 +52,7 @@ impl Default for Paintable { #[cfg(feature = "gtk_v4_10")] scaling_filter: Cell::new(gsk::ScalingFilter::Linear), use_scaling_filter: Cell::new(false), + force_aspect_ratio: Cell::new(false), #[cfg(not(feature = "gtk_v4_10"))] premult_shader: gsk::GLShader::from_bytes(&glib::Bytes::from_static(include_bytes!( "premult.glsl" @@ -94,6 +96,11 @@ impl ObjectImpl for Paintable { .blurb("Use selected scaling filter or GTK default for rendering") .default_value(false) .build(), + glib::ParamSpecBoolean::builder("force-aspect-ratio") + .nick("Force Aspect Ratio") + .blurb("When enabled, scaling will respect original aspect ratio") + .default_value(true) + .build(), ] }); @@ -117,6 +124,7 @@ impl ObjectImpl for Paintable { "scaling-filter" => self.scaling_filter.get().to_value(), #[cfg(feature = "gtk_v4_10")] "use-scaling-filter" => self.use_scaling_filter.get().to_value(), + "force-aspect-ratio" => self.force_aspect_ratio.get().to_value(), _ => unimplemented!(), } } @@ -139,6 +147,7 @@ impl ObjectImpl for Paintable { "scaling-filter" => self.scaling_filter.set(value.get().unwrap()), #[cfg(feature = "gtk_v4_10")] "use-scaling-filter" => self.use_scaling_filter.set(value.get().unwrap()), + "force-aspect-ratio" => self.force_aspect_ratio.set(value.get().unwrap()), _ => unimplemented!(), } } @@ -173,40 +182,66 @@ impl PaintableImpl for Paintable { let snapshot = snapshot.downcast_ref::().unwrap(); let background_color = self.background_color.get(); + let force_aspect_ratio = self.force_aspect_ratio.get(); let paintables = self.paintables.borrow(); - if !paintables.is_empty() { - gst::trace!(CAT, imp: self, "Snapshotting frame"); - - let (frame_width, frame_height) = - paintables.first().map(|p| (p.width, p.height)).unwrap(); - - let mut scale_x = width / frame_width as f64; - let mut scale_y = height / frame_height as f64; - let mut trans_x = 0.0; - let mut trans_y = 0.0; - - // TODO: Property for keeping aspect ratio or not - if (scale_x - scale_y).abs() > f64::EPSILON { - if scale_x > scale_y { - trans_x = - ((frame_width as f64 * scale_x) - (frame_width as f64 * scale_y)) / 2.0; - scale_x = scale_y; - } else { - trans_y = - ((frame_height as f64 * scale_y) - (frame_height as f64 * scale_x)) / 2.0; - scale_y = scale_x; - } - } - + let Some(first_paintable) = paintables.first() else { + gst::trace!(CAT, imp: self, "Snapshotting black frame"); snapshot.append_color( &background_color, &graphene::Rect::new(0f32, 0f32, width as f32, height as f32), ); - snapshot.translate(&graphene::Point::new(trans_x as f32, trans_y as f32)); + return; + }; - for Texture { + gst::trace!(CAT, imp: self, "Snapshotting frame"); + + // The first paintable is the actual video frame and defines the overall size. + // + // Based on its size relative to the snapshot width/height, all other paintables are + // scaled accordingly. + let (frame_width, frame_height) = (first_paintable.width, first_paintable.height); + + let mut scale_x = width / frame_width as f64; + let mut scale_y = height / frame_height as f64; + + // Usually the caller makes sure that the aspect ratio is preserved. To enforce this here + // optionally, we scale the frame equally in both directions and center it. In addition the + // background color is drawn behind the frame to fill the gaps. + // + // This is not done by default for performance reasons and usually would draw a <1px + // background. + if force_aspect_ratio { + let mut trans_x = 0.0; + let mut trans_y = 0.0; + + if (scale_x - scale_y).abs() > f64::EPSILON { + if scale_x > scale_y { + trans_x = (width - (frame_width as f64 * scale_y)) / 2.0; + scale_x = scale_y; + } else { + trans_y = (height - (frame_height as f64 * scale_x)) / 2.0; + scale_y = scale_x; + } + } + + if !background_color.is_clear() && (trans_x > f64::EPSILON || trans_y > f64::EPSILON) { + snapshot.append_color( + &background_color, + &graphene::Rect::new(0f32, 0f32, width as f32, height as f32), + ); + } + snapshot.translate(&graphene::Point::new(trans_x as f32, trans_y as f32)); + } + + // Make immutable + let scale_x = scale_x; + let scale_y = scale_y; + + for ( + idx, + Texture { texture, x, y, @@ -214,151 +249,159 @@ impl PaintableImpl for Paintable { height: paintable_height, global_alpha, has_alpha, - } in &*paintables + }, + ) in paintables.iter().enumerate() + { + snapshot.push_opacity(*global_alpha as f64); + + let bounds = if !force_aspect_ratio && idx == 0 { + // While this should end up with width again, be explicit in this case to avoid + // rounding errors and fill the whole area with the video frame. + graphene::Rect::new(0.0, 0.0, width as f32, height as f32) + } else { + // Scale texture position and size with the same scale factor as the main video + // frame, and make sure to not render outside (0, 0, width, height). + let x = f32::clamp(*x * scale_x as f32, 0.0, width as f32); + let y = f32::clamp(*y * scale_y as f32, 0.0, height as f32); + let texture_width = f32::min(*paintable_width * scale_x as f32, width as f32); + let texture_height = f32::min(*paintable_height * scale_y as f32, height as f32); + graphene::Rect::new(x, y, texture_width, texture_height) + }; + + // Only premultiply GL textures that expect to be in premultiplied RGBA format. + // + // For GTK 4.14 or newer we use the correct format directly when building the + // texture, but only if a GLES3+ context is used. In that case the NGL renderer is + // used by GTK, which supports non-premultiplied formats correctly and fast. + // + // For GTK 4.10-4.12, or 4.14 and newer if a GLES2 context is used, we use a + // self-mask to pre-multiply the alpha. + // + // For GTK before 4.10, we use a GL shader and hope that it works. + #[cfg(feature = "gtk_v4_10")] { - snapshot.push_opacity(*global_alpha as f64); - - let texture_width = *paintable_width * scale_x as f32; - let texture_height = *paintable_height * scale_y as f32; - let x = *x * scale_x as f32; - let y = *y * scale_y as f32; - let bounds = graphene::Rect::new(x, y, texture_width, texture_height); - - // Only premultiply GL textures that expect to be in premultiplied RGBA format. - // - // For GTK 4.14 or newer we use the correct format directly when building the - // texture, but only if a GLES3+ context is used. In that case the NGL renderer is - // used by GTK, which supports non-premultiplied formats correctly and fast. - // - // For GTK 4.10-4.12, or 4.14 and newer if a GLES2 context is used, we use a - // self-mask to pre-multiply the alpha. - // - // For GTK before 4.10, we use a GL shader and hope that it works. - #[cfg(feature = "gtk_v4_10")] - { - let context_requires_premult = { - #[cfg(feature = "gtk_v4_14")] - { - self.gl_context.borrow().as_ref().map_or(false, |context| { - context.api() != gdk::GLAPI::GLES || context.version().0 < 3 - }) - } - - #[cfg(not(feature = "gtk_v4_14"))] - { - true - } - }; - - let do_premult = - context_requires_premult && texture.is::() && *has_alpha; - if do_premult { - snapshot.push_mask(gsk::MaskMode::Alpha); - if self.use_scaling_filter.get() { - #[cfg(feature = "gtk_v4_10")] - snapshot.append_scaled_texture( - texture, - self.scaling_filter.get(), - &bounds, - ); - } else { - snapshot.append_texture(texture, &bounds); - } - snapshot.pop(); // pop mask - - // color matrix to set alpha of the source to 1.0 as it was - // already applied via the mask just above. - snapshot.push_color_matrix( - &graphene::Matrix::from_float({ - [ - 1.0, 0.0, 0.0, 0.0, // - 0.0, 1.0, 0.0, 0.0, // - 0.0, 0.0, 1.0, 0.0, // - 0.0, 0.0, 0.0, 0.0, - ] - }), - &graphene::Vec4::new(0.0, 0.0, 0.0, 1.0), - ); + let context_requires_premult = { + #[cfg(feature = "gtk_v4_14")] + { + self.gl_context.borrow().as_ref().map_or(false, |context| { + context.api() != gdk::GLAPI::GLES || context.version().0 < 3 + }) } + #[cfg(not(feature = "gtk_v4_14"))] + { + true + } + }; + + let do_premult = + context_requires_premult && texture.is::() && *has_alpha; + if do_premult { + snapshot.push_mask(gsk::MaskMode::Alpha); if self.use_scaling_filter.get() { #[cfg(feature = "gtk_v4_10")] snapshot.append_scaled_texture(texture, self.scaling_filter.get(), &bounds); } else { snapshot.append_texture(texture, &bounds); } + snapshot.pop(); // pop mask - if do_premult { - snapshot.pop(); // pop color matrix - snapshot.pop(); // pop mask 2 - } - } - #[cfg(not(feature = "gtk_v4_10"))] - { - let do_premult = - texture.is::() && *has_alpha && gtk::micro_version() < 13; - if do_premult { - snapshot.push_gl_shader( - &self.premult_shader, - &bounds, - gsk::ShaderArgsBuilder::new(&self.premult_shader, None).to_args(), - ); - } - - if self.use_scaling_filter.get() { - #[cfg(feature = "gtk_v4_10")] - snapshot.append_scaled_texture(texture, self.scaling_filter.get(), &bounds); - } else { - snapshot.append_texture(texture, &bounds); - } - - if do_premult { - snapshot.gl_shader_pop_texture(); // pop texture appended above from the shader - snapshot.pop(); // pop shader - } + // color matrix to set alpha of the source to 1.0 as it was + // already applied via the mask just above. + snapshot.push_color_matrix( + &graphene::Matrix::from_float({ + [ + 1.0, 0.0, 0.0, 0.0, // + 0.0, 1.0, 0.0, 0.0, // + 0.0, 0.0, 1.0, 0.0, // + 0.0, 0.0, 0.0, 0.0, + ] + }), + &graphene::Vec4::new(0.0, 0.0, 0.0, 1.0), + ); } - snapshot.pop(); // pop opacity + if self.use_scaling_filter.get() { + #[cfg(feature = "gtk_v4_10")] + snapshot.append_scaled_texture(texture, self.scaling_filter.get(), &bounds); + } else { + snapshot.append_texture(texture, &bounds); + } + + if do_premult { + snapshot.pop(); // pop color matrix + snapshot.pop(); // pop mask 2 + } } - } else { - gst::trace!(CAT, imp: self, "Snapshotting black frame"); - snapshot.append_color( - &background_color, - &graphene::Rect::new(0f32, 0f32, width as f32, height as f32), - ); + #[cfg(not(feature = "gtk_v4_10"))] + { + let do_premult = + texture.is::() && *has_alpha && gtk::micro_version() < 13; + if do_premult { + snapshot.push_gl_shader( + &self.premult_shader, + &bounds, + gsk::ShaderArgsBuilder::new(&self.premult_shader, None).to_args(), + ); + } + + if self.use_scaling_filter.get() { + #[cfg(feature = "gtk_v4_10")] + snapshot.append_scaled_texture(texture, self.scaling_filter.get(), &bounds); + } else { + snapshot.append_texture(texture, &bounds); + } + + if do_premult { + snapshot.gl_shader_pop_texture(); // pop texture appended above from the shader + snapshot.pop(); // pop shader + } + } + + snapshot.pop(); // pop opacity } } } impl Paintable { - pub(super) fn handle_frame_changed(&self, frame: Option) { + pub(super) fn handle_frame_changed(&self, sink: &crate::PaintableSink, frame: Frame) { let context = self.gl_context.borrow(); - if let Some(frame) = frame { - gst::trace!(CAT, imp: self, "Received new frame"); - let new_paintables = - frame.into_textures(context.as_ref(), &mut self.cached_textures.borrow_mut()); - let new_size = new_paintables - .first() - .map(|p| (f32::round(p.width) as u32, f32::round(p.height) as u32)) - .unwrap(); + gst::trace!(CAT, imp: self, "Received new frame"); - let old_paintables = self.paintables.replace(new_paintables); - let old_size = old_paintables - .first() - .map(|p| (f32::round(p.width) as u32, f32::round(p.height) as u32)); + let new_paintables = + match frame.into_textures(context.as_ref(), &mut self.cached_textures.borrow_mut()) { + Ok(textures) => textures, + Err(err) => { + gst::element_error!( + sink, + gst::ResourceError::Failed, + ["Failed to transform frame into textures: {err}"] + ); + return; + } + }; - if Some(new_size) != old_size { - gst::debug!( - CAT, - imp: self, - "Size changed from {old_size:?} to {new_size:?}", - ); - self.obj().invalidate_size(); - } + let new_size = new_paintables + .first() + .map(|p| (f32::round(p.width) as u32, f32::round(p.height) as u32)) + .unwrap(); - self.obj().invalidate_contents(); + let old_paintables = self.paintables.replace(new_paintables); + let old_size = old_paintables + .first() + .map(|p| (f32::round(p.width) as u32, f32::round(p.height) as u32)); + + if Some(new_size) != old_size { + gst::debug!( + CAT, + imp: self, + "Size changed from {old_size:?} to {new_size:?}", + ); + self.obj().invalidate_size(); } + + self.obj().invalidate_contents(); } pub(super) fn handle_flush_frames(&self) { diff --git a/video/gtk4/src/sink/paintable/mod.rs b/video/gtk4/src/sink/paintable/mod.rs index 835c43de..63e42b3d 100644 --- a/video/gtk4/src/sink/paintable/mod.rs +++ b/video/gtk4/src/sink/paintable/mod.rs @@ -30,8 +30,8 @@ impl Paintable { } impl Paintable { - pub(crate) fn handle_frame_changed(&self, frame: Option) { - self.imp().handle_frame_changed(frame); + pub(crate) fn handle_frame_changed(&self, sink: &crate::PaintableSink, frame: Frame) { + self.imp().handle_frame_changed(sink, frame); } pub(crate) fn handle_flush_frames(&self) {