From a5bb687a6b4c7d62bde50757ec493a66cf370a9d Mon Sep 17 00:00:00 2001 From: Teodor Wozniak Date: Tue, 10 Sep 2024 15:56:52 +0200 Subject: [PATCH] introducing alsa_pcm_inferno plugin, rx almost works, tx not yet --- .gitmodules | 3 + Cargo.lock | 1070 ++++++++------------- Cargo.toml | 5 +- alsa-sys-all | 1 + alsa_pcm_inferno/Cargo.toml | 19 + alsa_pcm_inferno/asoundrc | 10 + alsa_pcm_inferno/src/lib.rs | 211 ++++ alsa_pcm_inferno/test.sh | 7 + inferno2pipe/save_to_file | 2 +- inferno_aoip/Cargo.toml | 6 +- inferno_aoip/src/channels_subscriber.rs | 168 +++- inferno_aoip/src/common.rs | 12 + inferno_aoip/src/device_server.rs | 73 +- inferno_aoip/src/flows_rx.rs | 128 +-- inferno_aoip/src/flows_tx.rs | 20 +- inferno_aoip/src/lib.rs | 8 +- inferno_aoip/src/media_clock.rs | 115 +-- inferno_aoip/src/real_time_box_channel.rs | 4 +- inferno_aoip/src/ring_buffer.rs | 602 ++++++++++++ inferno_aoip/src/samples_collector.rs | 77 +- 20 files changed, 1593 insertions(+), 948 deletions(-) create mode 160000 alsa-sys-all create mode 100644 alsa_pcm_inferno/Cargo.toml create mode 100644 alsa_pcm_inferno/asoundrc create mode 100644 alsa_pcm_inferno/src/lib.rs create mode 100755 alsa_pcm_inferno/test.sh create mode 100644 inferno_aoip/src/ring_buffer.rs diff --git a/.gitmodules b/.gitmodules index 37076c1..12c5060 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "searchfire"] path = searchfire url = https://gitlab.com/lumifaza/searchfire +[submodule "alsa-sys-all"] + path = alsa-sys-all + url = https://gitlab.com/lumifaza/alsa-sys-all diff --git a/Cargo.lock b/Cargo.lock index 28f333e..88c3920 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -19,13 +19,35 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "alsa-sys-all" +version = "0.3.3" +dependencies = [ + "bindgen", + "libc", + "pkg-config", +] + +[[package]] +name = "alsa_pcm_inferno" +version = "0.1.0" +dependencies = [ + "alsa-sys-all", + "env_logger", + "futures-util", + "inferno_aoip", + "libc", + "log", + "tokio", +] + [[package]] name = "annotate-snippets" version = "0.9.2" @@ -38,47 +60,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -86,57 +109,39 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "async-channel" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener 5.0.0", - "event-listener-strategy 0.5.0", + "event-listener-strategy", "futures-core", "pin-project-lite", ] -[[package]] -name = "async-lock" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" -dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", - "pin-project-lite", -] - [[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.77" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.77", ] -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - [[package]] name = "atomic" version = "0.6.0" @@ -169,28 +174,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9a3820bc9e9aaf60c8389c2a4808548599f4ff254ce6bdb608ac3631d4ad76" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -218,18 +212,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "annotate-snippets", - "bitflags 2.4.2", + "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools", "lazy_static", "lazycell", + "log", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.49", + "syn 2.0.77", + "which", ] [[package]] @@ -240,46 +237,46 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blocking" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ "async-channel", - "async-lock", "async-task", - "fastrand", "futures-io", "futures-lite", "piper", - "tracing", ] [[package]] -name = "bumpalo" -version = "3.15.0" +name = "bool_vec" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f" +checksum = "011015ff6ce0f153a783d2785e66bbc926e5a37cd95a5c627e1db525956af43e" +dependencies = [ + "count-macro", +] [[package]] name = "bytebuffer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7bfaf7cd08cacd74cdc6b521c37ac39cbc92692e5ab5c21ed5657a749b577c" +checksum = "2618ff1f07e000264e355904bff5c0527091fcbad883ff7f71383c5428915f4b" dependencies = [ "byteorder", ] [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" [[package]] name = "byteorder" @@ -289,23 +286,17 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.0.83" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -319,9 +310,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", @@ -334,22 +325,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "cirb" -version = "0.1.0" -dependencies = [ - "atomic 0.5.3", - "criterion", - "quickcheck", - "quickcheck_macros", - "testbench", - "thiserror", -] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -358,20 +343,9 @@ dependencies = [ [[package]] name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "bitflags 1.3.2", - "textwrap", - "unicode-width", -] - -[[package]] -name = "clap" -version = "4.5.0" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", "clap_derive", @@ -379,9 +353,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.0" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstream", "anstyle", @@ -391,36 +365,36 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.77", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "clock-steering" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8078994a6ef3fefa4c9cc2e69af9ee94cac7804e4ec065d794cd36728c12c02" +checksum = "2410abf030f2eb46cba840d343455078538e7c8755507b37ffd4074064f08f62" dependencies = [ "libc", ] [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "colored" @@ -434,9 +408,9 @@ dependencies = [ [[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", ] @@ -452,91 +426,28 @@ dependencies = [ [[package]] name = "cookie-factory" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" - -[[package]] -name = "criterion" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" -dependencies = [ - "atty", - "cast", - "clap 2.34.0", - "criterion-plot", - "csv", - "itertools 0.10.5", - "lazy_static", - "num-traits", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_cbor", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" -dependencies = [ - "cast", - "itertools 0.10.5", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", + "futures", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "count-macro" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "70bff9a5ca25686b643ba9c7e86a52812ffb19c2679443e09a4a651331b477d5" dependencies = [ - "crossbeam-utils", + "once_cell", + "regex", ] [[package]] name = "crossbeam-utils" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" - -[[package]] -name = "csv" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.11" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" -dependencies = [ - "memchr", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "custom_error" @@ -546,9 +457,9 @@ checksum = "4f8a51dd197fa6ba5b4dc98a990a43cc13693c23eb0089ebb0fcc1f04152bca6" [[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 = "deranged" @@ -598,9 +509,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "either" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "endian-type" @@ -614,7 +525,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 1.0.109", @@ -622,19 +533,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.8.4" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", @@ -642,9 +543,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c012a26a7f605efc424dd53697843a72be7dc86ad2d01f7814337794a12231d" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", @@ -660,21 +561,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "event-listener" -version = "4.0.3" +name = "errno" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", + "libc", + "windows-sys 0.52.0", ] [[package]] name = "event-listener" -version = "5.0.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72557800024fabbaa2449dd4bf24e37b93702d457a4d4f2b0dd1f0f039f20c1" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -683,29 +583,19 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.0.0", + "event-listener", "pin-project-lite", ] [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "form_urlencoded" @@ -766,9 +656,9 @@ checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "futures-core", "pin-project-lite", @@ -782,7 +672,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.77", ] [[package]] @@ -817,9 +707,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -828,9 +718,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "glob" @@ -838,17 +728,11 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - [[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" [[package]] name = "heck" @@ -857,19 +741,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -877,6 +758,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "humantime" version = "2.1.0" @@ -916,9 +806,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -928,8 +818,8 @@ dependencies = [ name = "inferno2pipe" version = "0.1.0" dependencies = [ - "clap 4.5.0", - "env_logger 0.11.2", + "clap", + "env_logger", "inferno_aoip", "log", "tokio", @@ -939,18 +829,19 @@ dependencies = [ name = "inferno_aoip" version = "0.1.0" dependencies = [ - "atomic 0.6.0", + "atomic", "atomic_enum", "atomicbox", "binary-layout", + "bool_vec", "bytebuffer", - "cirb", + "bytemuck", "clock-steering", "custom_error", "futures", "hex", "interprocess", - "itertools 0.12.1", + "itertools", "local-ip-address", "log", "mio", @@ -962,14 +853,15 @@ dependencies = [ "thread-priority", "tokio", "toml", + "usrvclock", ] [[package]] name = "inferno_wired" version = "0.1.0" dependencies = [ - "clap 4.5.0", - "env_logger 0.11.2", + "clap", + "env_logger", "futures-util", "inferno_aoip", "log", @@ -1011,13 +903,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] -name = "itertools" -version = "0.10.5" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -1030,24 +919,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" - -[[package]] -name = "js-sys" -version = "0.3.68" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" -dependencies = [ - "wasm-bindgen", -] +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -1057,29 +937,28 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.6", ] [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "libc", - "redox_syscall", ] [[package]] @@ -1088,13 +967,13 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65f3a4b81b2a2d8c7f300643676202debd1b7c929dbf5c9bb89402ea11d19810" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cc", "convert_case", "cookie-factory", "libc", "libspa-sys", - "nix", + "nix 0.27.1", "nom", "system-deps", ] @@ -1110,6 +989,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "local-ip-address" version = "0.5.7" @@ -1124,9 +1009,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", @@ -1134,9 +1019,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "matches" @@ -1146,9 +1031,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" @@ -1158,23 +1043,24 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi", "libc", "log", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1217,11 +1103,23 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if", "libc", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1238,25 +1136,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-traits" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.6", - "libc", -] - [[package]] name = "num_threads" version = "0.1.7" @@ -1268,9 +1147,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] @@ -1281,12 +1160,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "oorandom" -version = "11.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" - [[package]] name = "parking" version = "2.2.0" @@ -1295,9 +1168,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" @@ -1307,9 +1180,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1319,9 +1192,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", "fastrand", @@ -1335,11 +1208,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08e645ba5c45109106d56610b3ee60eb13a6f2beb8b74f8dc8186cf261788dda" dependencies = [ "anyhow", - "bitflags 2.4.2", + "bitflags 2.6.0", "libc", "libspa", "libspa-sys", - "nix", + "nix 0.27.1", "once_cell", "pipewire-sys", "thiserror", @@ -1372,50 +1245,35 @@ dependencies = [ ] [[package]] -name = "plotters" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.5" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "plotters-svg" -version = "0.3.5" +name = "ppv-lite86" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "plotters-backend", + "zerocopy", ] [[package]] -name = "powerfmt" -version = "0.2.0" +name = "prettyplease" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn 2.0.77", +] [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1426,33 +1284,11 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quickcheck" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" -dependencies = [ - "env_logger 0.8.4", - "log", - "rand", -] - -[[package]] -name = "quickcheck_macros" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1497,40 +1333,11 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rayon" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", @@ -1539,9 +1346,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -1551,9 +1358,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -1562,15 +1369,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -1580,33 +1387,31 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - -[[package]] -name = "ryu" -version = "1.0.16" +name = "rustix" +version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] [[package]] -name = "same-file" -version = "1.0.6" +name = "rustversion" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "scopeguard" @@ -1632,56 +1437,35 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", -] - -[[package]] -name = "serde_json" -version = "1.0.113" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" -dependencies = [ - "itoa", - "ryu", - "serde", + "syn 2.0.77", ] [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -1694,9 +1478,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[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", ] @@ -1724,9 +1508,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -1740,12 +1524,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1759,9 +1543,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" @@ -1776,9 +1560,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.49" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -1787,12 +1571,12 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.2.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", - "heck", + "heck 0.5.0", "pkg-config", "toml", "version-compare", @@ -1800,46 +1584,28 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" - -[[package]] -name = "testbench" -version = "0.9.0" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc5c5d972bbb571a0e13cceb8f68957d5a8d77ff61085f0e2fcabbc378fa2c6" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.77", ] [[package]] @@ -1858,9 +1624,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", @@ -1881,29 +1647,19 @@ 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", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -1922,38 +1678,37 @@ checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" [[package]] name = "tokio" -version = "1.36.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2 0.5.7", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.77", ] [[package]] name = "toml" -version = "0.8.10" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -1963,18 +1718,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", @@ -2002,7 +1757,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.77", ] [[package]] @@ -2074,9 +1829,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -2089,15 +1844,15 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna 0.5.0", @@ -2105,26 +1860,26 @@ dependencies = [ ] [[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +name = "usrvclock" +version = "0.1.0" +source = "git+https://gitlab.com/lumifaza/usrvclock-rs#4bd7b0455d20e87f40edecc75862cdc07a52aae3" +dependencies = [ + "custom_error", + "nix 0.29.0", + "tokio", +] [[package]] -name = "version-compare" -version = "0.1.1" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "walkdir" -version = "2.4.0" +name = "version-compare" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" -dependencies = [ - "same-file", - "winapi-util", -] +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "wasi" @@ -2133,67 +1888,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.91" +name = "which" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ - "bumpalo", - "log", + "either", + "home", "once_cell", - "proc-macro2", - "quote", - "syn 2.0.49", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.49", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" - -[[package]] -name = "web-sys" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" -dependencies = [ - "js-sys", - "wasm-bindgen", + "rustix", ] [[package]] @@ -2212,15 +1915,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2242,7 +1936,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -2262,17 +1956,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2283,9 +1978,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2295,9 +1990,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2307,9 +2002,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2319,9 +2020,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2331,9 +2032,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2343,9 +2044,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2355,15 +2056,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.1" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d90f4e0f530c4c69f62b80d839e9ef3855edc9cba471a160c4d692deed62b401" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -2376,3 +2077,24 @@ checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" dependencies = [ "winapi", ] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] diff --git a/Cargo.toml b/Cargo.toml index 5677339..8015b0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,8 @@ resolver = "2" members = [ "inferno_aoip", - "cirb", "searchfire", "inferno2pipe", - "inferno_wired"] + "inferno_wired", + "alsa_pcm_inferno", +] diff --git a/alsa-sys-all b/alsa-sys-all new file mode 160000 index 0000000..83324fc --- /dev/null +++ b/alsa-sys-all @@ -0,0 +1 @@ +Subproject commit 83324fc89b831f3da107e6d704bc7149f08b4e0f diff --git a/alsa_pcm_inferno/Cargo.toml b/alsa_pcm_inferno/Cargo.toml new file mode 100644 index 0000000..92983ca --- /dev/null +++ b/alsa_pcm_inferno/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "alsa_pcm_inferno" +version = "0.1.0" +edition = "2021" + +[lib] +name = "asound_module_pcm_inferno" +crate-type = ["cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +alsa-sys-all = { path = "../alsa-sys-all" } +libc = "0.2.153" +inferno_aoip = { path = "../inferno_aoip" } +env_logger = "0.11.2" +log = "0.4.20" +tokio = { version = "1.36.0", features = ["sync"] } +futures-util = "0.3.30" \ No newline at end of file diff --git a/alsa_pcm_inferno/asoundrc b/alsa_pcm_inferno/asoundrc new file mode 100644 index 0000000..ff184fe --- /dev/null +++ b/alsa_pcm_inferno/asoundrc @@ -0,0 +1,10 @@ +pcm.!default { + type inferno + #card 0 +} + +ctl.!default { + type inferno + #card 0 +} + diff --git a/alsa_pcm_inferno/src/lib.rs b/alsa_pcm_inferno/src/lib.rs new file mode 100644 index 0000000..0ab4ea2 --- /dev/null +++ b/alsa_pcm_inferno/src/lib.rs @@ -0,0 +1,211 @@ +extern crate alsa_sys_all; +extern crate libc; +use alsa_sys_all::*; + +use futures_util::FutureExt; +use inferno_aoip::utils::{run_future_in_new_thread, LogAndForget}; +use inferno_aoip::{AtomicSample, DeviceInfo, DeviceServer, ExternalBufferParameters, MediaClock, RealTimeClockReceiver, Sample, SelfInfoBuilder}; +use libc::{c_void, c_int, c_uint, c_char, free, malloc}; +use std::borrow::BorrowMut; +use std::ptr::{null_mut, null}; +use std::mem::zeroed; +use std::sync::{Arc, RwLock}; +use std::time::Instant; + +struct StreamInfo { + boundary: usize, +} + +#[repr(C)] +struct MyIOPlug { + io: snd_pcm_ioplug_t, + ref_time: Instant, + stream_info: Option, + buffers_valid: Arc>, + media_clock: MediaClock, + clock_receiver: Option, + start_time: Option, + stop_tx: Option>, +} + +unsafe fn get_private<'a>(io: *mut snd_pcm_ioplug_t) -> &'a mut MyIOPlug { + &mut *((*io).private_data as *mut MyIOPlug) +} + +unsafe extern "C" fn plugin_pointer(io: *mut snd_pcm_ioplug_t) -> snd_pcm_sframes_t { + let this = get_private(io); + if let Some(clock_receiver) = &mut this.clock_receiver { + if clock_receiver.update() { + if let Some(overlay) = clock_receiver.get() { + this.media_clock.update_overlay(*overlay); + } + } + } + let now_samples_opt = this.media_clock.now_in_timebase((*io).rate as u64); + if now_samples_opt.is_some() && this.start_time.is_none() { + this.start_time = now_samples_opt; + } + now_samples_opt.map(|now_samples| now_samples.wrapping_sub(this.start_time.unwrap())).unwrap_or(0) as i64 +} + +fn get_app_name() -> Option { + Some(std::env::current_exe().ok()?.file_name()?.to_string_lossy().to_string()) +} + +unsafe extern "C" fn plugin_prepare(io: *mut snd_pcm_ioplug_t) -> c_int { + println!("plugin_prepare called"); + let this = get_private(io); + + let channels_areas = snd_pcm_ioplug_mmap_areas(io); + if channels_areas.is_null() { + panic!("snd_pcm_ioplug_mmap_areas returned null, unable to get audio memory addresses"); + } + + let bits_per_sample = (8 * size_of::()) as u32; + let channels_areas = std::slice::from_raw_parts(channels_areas, (*io).channels as usize); + for area in channels_areas { + println!("got address {:x} with first {}b, step {}b, size {} samples * {} channels", area.addr as usize, area.first, area.step, (*io).buffer_size, channels_areas.len()); + if (area.first % 8) != 0 || (area.step % 8) != 0 { + panic!("sample size is not measured in whole bytes, unsupported"); + } + if (area.first % bits_per_sample) != 0 || (area.step % bits_per_sample) != 0 { + panic!("samples not aligned, unsupported"); + } + } + + let rx_channels_buffers = channels_areas.iter().map(|area| { + ExternalBufferParameters::::new( + area.addr.byte_offset((area.first/8) as isize) as *const AtomicSample, + ((*io).buffer_size as usize) * channels_areas.len() - ((area.first/bits_per_sample) as usize), + (area.step/bits_per_sample) as usize, + this.buffers_valid.clone() + ) + }).collect(); + + let mut swparams = std::ptr::null_mut::(); + if snd_pcm_sw_params_malloc(&mut swparams) != 0 { + panic!("snd_pcm_sw_params_malloc failed"); + } + let boundary = if snd_pcm_sw_params_current((*io).pcm, swparams) == 0 { + let mut value = 0; + snd_pcm_sw_params_get_boundary(swparams, &mut value); + value + } else { + panic!("snd_pcm_sw_params_current failed"); + }; + snd_pcm_sw_params_free(swparams); + assert!(boundary != 0); + println!("boundary: {boundary}"); + this.stream_info = Some(StreamInfo { + boundary: boundary as usize + }); + + let app_name = get_app_name().unwrap_or(std::process::id().to_string()); + let logenv = env_logger::Env::default().default_filter_or("debug"); + env_logger::builder().parse_env(logenv).format_timestamp_micros().init(); + + let self_info = DeviceInfo::new_self(&format!("{app_name} (via Inferno-AoIP)"), &app_name, None).make_rx_channels(2).make_tx_channels(32); + // TODO make tx & rx channels based on (*io).channels + // this requires a complicated refactor to allow adding channels to the Dante network dynamically at any time, not just on DeviceServer start + // because we don't know beforehand whether the app will be capture&playback, playback-only or capture-only + // so we don't know whether we should wait for the second prepare call to gather all channels counts + + assert_eq!(self_info.sample_rate, (*io).rate); // TODO set self_info.sample_rate based on (*io).rate + + let (tx, rx) = std::sync::mpsc::channel(); + + let (stop_tx, stop_rx) = tokio::sync::oneshot::channel::<()>(); + + let inferno_thread = run_future_in_new_thread("Inferno main", move || async move { + let (mut device_server, clock_receiver) = DeviceServer::start_with_external_buffering(self_info, rx_channels_buffers).await; + //let tx_inputs = device_server.take_tx_inputs(); + let self_info = device_server.self_info.clone(); + tx.send((self_info, clock_receiver)).log_and_forget(); + stop_rx.await; + device_server.shutdown().await; + }.boxed_local()); + + let (self_info, clock_receiver) = rx.recv().unwrap(); + this.clock_receiver = Some(clock_receiver); + this.stop_tx = Some(stop_tx); + *this.buffers_valid.write().unwrap() = true; + + 0 +} + +unsafe extern "C" fn plugin_start(io: *mut snd_pcm_ioplug_t) -> c_int { + println!("plugin_start called"); + 0 +} + +unsafe extern "C" fn plugin_stop(io: *mut snd_pcm_ioplug_t) -> c_int { + println!("plugin_stop called"); + 0 +} + +unsafe extern "C" fn plugin_transfer(io: *mut snd_pcm_ioplug_t, areas: *const snd_pcm_channel_area_t, offset: snd_pcm_uframes_t, size: snd_pcm_uframes_t) -> snd_pcm_sframes_t { + println!("plugin_transfer called, size: {:?}", size); + size as snd_pcm_sframes_t +} + +unsafe extern "C" fn plugin_close(io: *mut snd_pcm_ioplug_t) -> c_int { + println!("plugin_close called"); + let this = get_private(io); + *this.buffers_valid.write().unwrap() = false; + this.stop_tx.take().map(|tx|tx.send(())); + 0 +} + + + +unsafe extern "C" fn plugin_define(pcmp: *mut *mut snd_pcm_t, _name: *const c_char, _root: *const snd_config_t, _conf: *const snd_config_t, _stream: snd_pcm_stream_t, _mode: c_int) -> c_int { + let myio = Box::into_raw(Box::new(MyIOPlug { + io: zeroed(), + ref_time: Instant::now(), + stream_info: None, + buffers_valid: Arc::new(RwLock::new(false)), + media_clock: MediaClock::new(), + clock_receiver: None, + start_time: None, + stop_tx: None, + })); + + let io = &mut (*myio).io; + io.version = (1<<16) | (0<<8) | 2; + io.name = b"Inferno virtual device\0".as_ptr() as *const _; + io.callback = &snd_pcm_ioplug_callback_t { + prepare: Some(plugin_prepare), + start: Some(plugin_start), + stop: Some(plugin_stop), + pointer: Some(plugin_pointer), + transfer: Some(plugin_transfer), + close: Some(plugin_close), + ..zeroed() + }; + io.mmap_rw = 1; + io.private_data = myio as *mut _; + + // TODO error handling + snd_pcm_ioplug_create(io, _name, _stream, _mode, 0); + + snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT as i32, 1, [SND_PCM_FORMAT_S32 as u32].as_ptr()); + //snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS as i32, 2, [SND_PCM_ACCESS_MMAP_INTERLEAVED as u32, SND_PCM_ACCESS_MMAP_NONINTERLEAVED as u32].as_ptr()); + + *pcmp = (*myio).io.pcm; + 0 +} + +#[no_mangle] +pub extern "C" fn _snd_pcm_inferno_open(pcmp: *mut *mut snd_pcm_t, name: *const c_char, root: *const snd_config_t, conf: *const snd_config_t, stream: snd_pcm_stream_t, mode: c_int) -> c_int { + unsafe { plugin_define(pcmp, name, root, conf, stream, mode) } +} + +#[no_mangle] +pub extern "C" fn __snd_pcm_inferno_open_dlsym_pcm_001() { +} + + +#[link(name = "asound")] +extern "C" { + fn snd_pcm_ioplug_create(io: *mut snd_pcm_ioplug_t, name: *const c_char, stream: snd_pcm_stream_t, mode: c_int, flags: c_uint) -> c_int; +} diff --git a/alsa_pcm_inferno/test.sh b/alsa_pcm_inferno/test.sh new file mode 100755 index 0000000..b43fe96 --- /dev/null +++ b/alsa_pcm_inferno/test.sh @@ -0,0 +1,7 @@ +#!/bin/bash +basedir="$(dirname "$0")" + +export ALSA_CONFIG_PATH=$basedir/asoundrc +export ALSA_PLUGIN_DIR=$basedir/../target/debug + +arecord -vvv -D default -r 48000 -f s32 -c 2 test.wav diff --git a/inferno2pipe/save_to_file b/inferno2pipe/save_to_file index 2934372..054e69e 100755 --- a/inferno2pipe/save_to_file +++ b/inferno2pipe/save_to_file @@ -22,4 +22,4 @@ fi ffmpeg -nostdin -fflags nobuffer -f s32le -sample_rate $srate -channels $num_channels -i $fifo $showvol_flags -c:a pcm_s24le $output_file & -INFERNO_SAMPLE_RATE=$srate RUST_LOG=debug RUST_BACKTRACE=1 cargo run --release -- -c $num_channels -o $fifo +INFERNO_SAMPLE_RATE=$srate RUST_LOG=debug RUST_BACKTRACE=1 cargo run -- -c $num_channels -o $fifo diff --git a/inferno_aoip/Cargo.toml b/inferno_aoip/Cargo.toml index 4771443..c619361 100644 --- a/inferno_aoip/Cargo.toml +++ b/inferno_aoip/Cargo.toml @@ -20,7 +20,6 @@ itertools = "0.12" log = "0.4" atomic_enum = "0.2" local-ip-address = "0.5" -cirb = { path = "../cirb" } atomic = "0.6" rand = { version="0.8", features=["small_rng"] } toml = "0.8" @@ -31,7 +30,10 @@ interprocess = { version = "1.2", features = ["tokio_support"] } thiserror = "1.0.57" thread-priority = "0.15" atomicbox = "0.4.0" -mio = { version = "0.8.10", features = ["net", "os-ext", "os-poll"] } +mio = { version = "1.0.2", features = ["net", "os-ext", "os-poll"] } +bool_vec = "0.2.1" +bytemuck = "1.17.1" +usrvclock = { git = "https://gitlab.com/lumifaza/usrvclock-rs", version = "0.1.0" } [dependencies.serde] version = "1.0.159" diff --git a/inferno_aoip/src/channels_subscriber.rs b/inferno_aoip/src/channels_subscriber.rs index 467c07d..05de7ad 100644 --- a/inferno_aoip/src/channels_subscriber.rs +++ b/inferno_aoip/src/channels_subscriber.rs @@ -1,6 +1,7 @@ use crate::net_utils::create_mio_udp_socket; use crate::samples_collector::SamplesCollector; use crate::state_storage::StateStorage; +use crate::MediaClock; use crate::{ common::*, mdns_client::AdvertisedChannel, protocol::flows_control::FlowHandle, @@ -12,8 +13,9 @@ use crate::{ protocol::flows_control::FlowsControlClient, }; -use cirb::Output as RBOutput; +use crate::ring_buffer::{self, ExternalBuffer, ExternalBufferParameters, OwnedBuffer, ProxyToSamplesBuffer, RBInput, RBOutput}; +use atomic::Atomic; use futures::{future::join_all, Future, FutureExt}; use itertools::Itertools; use std::collections::btree_map::Entry; @@ -74,12 +76,13 @@ struct SavedChannelsState { } impl ChannelsSubscriber { - pub fn new( + pub fn new + Send + Sync + 'static>( self_info: Arc, - flows_recv: Arc, + media_clock: Arc>, + flows_recv: Arc>, mdns_client: Arc, mcast: mpsc::Sender, - samples_collector: Arc, + channels_buffering: B, state_storage: Arc, ref_instant: Instant, ) -> (Self, Pin + Send + 'static>>) { @@ -88,14 +91,15 @@ impl ChannelsSubscriber { commands_sender: tx, subscriptions_info: Arc::new(RwLock::new(vec![None; self_info.rx_channels.len()])), }; - let mut internal = ChannelsSubscriberInternal::new( + let mut internal = ChannelsSubscriberInternal::::new( rx, self_info, + media_clock, flows_recv, mdns_client, r.subscriptions_info.clone(), mcast, - samples_collector, + channels_buffering, state_storage, ref_instant, ); @@ -134,17 +138,90 @@ impl ChannelsSubscriber { } } -struct SamplesQueue { - source: RBOutput, + + +pub trait ChannelsBuffering { + fn get_io(&self, start_time: Clock, channel_index: usize) -> (RBInput, Option>); + //fn samples_collector(&self) -> Option>>; + fn connect_channel(&self, start_time: Clock, rb_output: &mut Option>, channel_index: usize, latency_samples: usize) -> Option>; + fn disconnect_channel(&self, channel_index: usize); +} + +pub struct OwnedBuffering { + buffer_length: usize, + hole_fix_wait: usize, + samples_collector: Arc>>>, } -struct Flow { +impl OwnedBuffering { + pub fn new(buffer_length: usize, hole_fix_wait: usize, samples_collector: Arc>>>) -> Self { + Self { buffer_length, hole_fix_wait, samples_collector } + } +} + +impl ChannelsBuffering>> for OwnedBuffering { + fn get_io(&self, start_time: Clock, _channel_index: usize) -> (RBInput>>, Option>>>) { + let (input, output) = ring_buffer::new_owned::(self.buffer_length, start_time, self.hole_fix_wait); + (input, Some(output)) + } + /* fn samples_collector(&self) -> Option>>>> { + Some(self.samples_collector.clone()) + } */ + fn connect_channel(&self, start_time: Clock, rb_output: &mut Option>>>, channel_index: usize, latency_samples: usize) -> Option>>> { + let (sink, source) = if rb_output.is_none() { + let (sink, source) = + ring_buffer::new_owned::(self.buffer_length, start_time, self.hole_fix_wait); + *rb_output = Some(source.clone()); + (Some(sink), source) + } else { + (None, rb_output.as_ref().unwrap().clone()) + }; + let sc = self.samples_collector.clone(); + tokio::spawn(async move { + sc.connect_channel(channel_index, source, latency_samples).await; + }); + sink + } + fn disconnect_channel(&self, channel_index: usize) { + let sc = self.samples_collector.clone(); + tokio::spawn(async move { + sc.disconnect_channel(channel_index).await; + }); + } +} + +pub struct ExternalBuffering { + channels: Vec>, + hole_fix_wait: usize, +} + +impl ExternalBuffering { + pub fn new(channels: Vec>, hole_fix_wait: usize) -> Self { + Self { + channels, + hole_fix_wait + } + } +} + +impl ChannelsBuffering>> for ExternalBuffering { + fn get_io(&self, start_time: Clock, channel_index: usize) -> (RBInput>>, Option>>>) { + (ring_buffer::wrap_external_sink(&self.channels[channel_index], start_time, self.hole_fix_wait), None) + } + fn connect_channel(&self, start_time: Clock, rb_output: &mut Option>>>, channel_index: usize, latency_samples: usize) -> Option>>> { + debug_assert!(rb_output.is_none()); + Some(ring_buffer::wrap_external_sink(&self.channels[channel_index], start_time, self.hole_fix_wait)) + } + fn disconnect_channel(&self, _channel_index: usize) { + } +} + +struct Flow { local_id: usize, control_remote_addr: SocketAddr, // TODO change to enum that will include multicasts handle: Option, - //channels_occupied: Vec, channels_refcount: Vec, - channels_queues: Vec>, + channels_rb_outputs: Vec>>, tx_channels: Vec>, dbcp1: u16, latency_samples: usize, @@ -153,7 +230,7 @@ struct Flow { creation_time: usize, } -impl Flow { +impl Flow

{ fn new( local_id: usize, control_remote_addr: SocketAddr, @@ -162,16 +239,16 @@ impl Flow { dbcp1: u16, latency_samples: usize, now: usize, - ) -> Flow { + ) -> Self { let chs = vec![0; total_channels]; let mut tx_channels = tx_channels.into_iter().map(|ch| Some(ch)).collect_vec(); tx_channels.resize(total_channels, None); - Flow { + Self { local_id, control_remote_addr, handle: None, channels_refcount: chs, - channels_queues: (0..total_channels).map(|_| None).collect(), + channels_rb_outputs: (0..total_channels).map(|_| None).collect(), tx_channels, dbcp1, latency_samples, @@ -199,34 +276,37 @@ struct ChannelSubscription { remote: Option, } -struct ChannelsSubscriberInternal { +struct ChannelsSubscriberInternal> { commands_receiver: mpsc::Receiver, self_info: Arc, + media_clock: Arc>, channels: Vec>, - flows: Arc>>>, - flows_recv: Arc, - buffered_samples_per_channel: usize, + flows: Arc>>>>, + flows_recv: Arc>, + //buffered_samples_per_channel: usize, min_latency_ns: usize, control_client: Arc, mdns_client: Arc, subscriptions_info: Arc>>>, mcast: mpsc::Sender, - samples_collector: Arc, + channels_buffering: B, + //samples_collector: Arc>, state_storage: Arc, ref_instant: Instant, needs_resolving: bool, resolve_now: bool, } -impl ChannelsSubscriberInternal { +impl> ChannelsSubscriberInternal { pub fn new( commands_receiver: mpsc::Receiver, self_info: Arc, - flows_recv: Arc, + media_clock: Arc>, + flows_recv: Arc>, mdns_client: Arc, subscriptions_info: Arc>>>, mcast: mpsc::Sender, - samples_collector: Arc, + channels_buffering: B, state_storage: Arc, ref_instant: Instant, ) -> Self { @@ -234,16 +314,17 @@ impl ChannelsSubscriberInternal { return Self { commands_receiver, self_info: self_info.clone(), + media_clock, channels: vec![None; num_channels], flows: Arc::new(Mutex::new(vec![])), flows_recv, - buffered_samples_per_channel: 524288, + //buffered_samples_per_channel: 524288, min_latency_ns: 30_000_000, // TODO dehardcode control_client: Arc::new(FlowsControlClient::new(self_info)), mdns_client, subscriptions_info, mcast, - samples_collector, + channels_buffering, state_storage, ref_instant, needs_resolving: false, @@ -271,7 +352,7 @@ impl ChannelsSubscriberInternal { .log_and_forget(); } pub async fn unsubscribe(&mut self, local_channel_index: usize, remove_from_info: bool) { - self.samples_collector.disconnect_channel(local_channel_index).await; + self.channels_buffering.disconnect_channel(local_channel_index); if let Some(subscription) = &self.channels[local_channel_index] { if let Some(remote) = subscription.remote.as_ref() { match &mut self.flows.lock().unwrap()[remote.local_flow_index] { @@ -697,6 +778,14 @@ impl ChannelsSubscriberInternal { while let Some(updates) = rx.recv().await { trace!("recv something"); let mut changed_channels = vec![]; + let now = self.media_clock.read().unwrap().now_in_timebase(self.self_info.sample_rate as u64); + let now = match now { + Some(v) => v, + None => { + error!("can't subscribe, clock not ready"); + continue; + } + }; for upd in updates { let first_index = upd.local_channel_indices[0]; info!( @@ -713,24 +802,18 @@ impl ChannelsSubscriberInternal { let flow = flows[remote.local_flow_index].as_mut().unwrap(); flow.tx_channels[remote.channel_in_flow] = Some(remote.tx_channel_id); flow.channels_refcount[remote.channel_in_flow] += 1; - if flow.channels_queues[remote.channel_in_flow].is_none() { - let (sink, source) = - cirb::RTHistory::::new(self.buffered_samples_per_channel, REORDER_WAIT_SAMPLES).split(); - let flows_recv = self.flows_recv.clone(); + if flow.channels_rb_outputs[remote.channel_in_flow].is_none() { + // ringbuffer doesn't exist yet (this channel in flow wasn't used before) let local_id = flow.local_id; debug_assert!(local_id == remote.local_flow_index+1); + } + let sink_opt = self.channels_buffering.connect_channel(now, &mut flow.channels_rb_outputs[remote.channel_in_flow], chi, flow.latency_samples); + if let Some(sink) = sink_opt { + let flows_recv = self.flows_recv.clone(); tokio::spawn(async move { flows_recv.connect_channel(remote.local_flow_index, remote.channel_in_flow, sink).await; }); - flow.channels_queues[remote.channel_in_flow] = Some(SamplesQueue { source }) } - let samples_source = - flow.channels_queues[remote.channel_in_flow].as_ref().unwrap().source.clone(); - let latency_samples = flow.latency_samples; - let samples_collector = self.samples_collector.clone(); - tokio::spawn(async move { - samples_collector.connect_channel(chi, samples_source, latency_samples).await; - }); subscription.remote = Some(remote); subinfos[chi] = Some(SubscriptionInfo { tx_channel_name: subscription.tx_channel_name.clone(), @@ -749,6 +832,10 @@ impl ChannelsSubscriberInternal { return true; } + /* fn get_ring_buffer(&self, local_channel_index: usize) -> (RBInput, Option>) { + + } */ + async fn scan_flows(&mut self) -> bool { let mut destroy_futures_per_remote: BTreeMap< SocketAddr, @@ -797,10 +884,7 @@ impl ChannelsSubscriberInternal { subi.status = SubscriptionStatus::Unresolved; channels_changed.push(chi); } - let samples_collector = self.samples_collector.clone(); - tokio::spawn(async move { - samples_collector.disconnect_channel(chi).await; - }); + self.channels_buffering.disconnect_channel(chi); } else if receiving { if let Some(subi) = self.subscriptions_info.write().unwrap()[chi].as_mut() { diff --git a/inferno_aoip/src/common.rs b/inferno_aoip/src/common.rs index 92f578c..9378541 100644 --- a/inferno_aoip/src/common.rs +++ b/inferno_aoip/src/common.rs @@ -4,6 +4,18 @@ pub use log::{debug, error, info, trace, warn}; pub type Sample = i32; pub type USample = u32; +/// Audio clock (number of samples since arbitrary epoch) +pub type Clock = usize; + +/// Signed version of the clock. For clock deltas. +pub type ClockDiff = isize; + +/// Subtract clocks and return the result as a signed number. +/// Hint: wrapped `a > b` is equivalent to `wrapped_diff(a, b) > 0` +pub fn wrapped_diff(a: Clock, b: Clock) -> ClockDiff { + (a as ClockDiff).wrapping_sub(b as ClockDiff) +} + pub trait LogAndForget { fn log_and_forget(&self); } diff --git a/inferno_aoip/src/device_server.rs b/inferno_aoip/src/device_server.rs index fd5ca03..f9d1aee 100644 --- a/inferno_aoip/src/device_server.rs +++ b/inferno_aoip/src/device_server.rs @@ -1,13 +1,16 @@ -use crate::channels_subscriber::ChannelsSubscriber; +use crate::channels_subscriber::{ChannelsBuffering, ChannelsSubscriber, ExternalBuffering, OwnedBuffering}; use crate::flows_tx::FlowsTransmitter; -use crate::media_clock::{async_clock_receiver_to_realtime, start_clock_receiver, ClockOverlay}; +use crate::media_clock::{async_clock_receiver_to_realtime, make_shared_media_clock, start_clock_receiver, ClockReceiver}; use crate::real_time_box_channel::RealTimeBoxReceiver; use crate::samples_collector::{RealTimeSamplesReceiver, SamplesCallback, SamplesCollector}; use crate::state_storage::StateStorage; +use crate::ring_buffer::{ExternalBuffer, ExternalBufferParameters, OwnedBuffer, ProxyToBuffer, ProxyToSamplesBuffer, RBInput}; +use atomic::Atomic; use futures::{Future, FutureExt}; use itertools::Itertools; use tokio::sync::broadcast::Receiver; use tokio::task::JoinHandle; +use usrvclock::ClockOverlay; use std::fs::File; use std::io::Write; @@ -20,11 +23,10 @@ use std::sync::{Arc, Mutex}; use std::net::IpAddr; use std::time::Instant; use tokio::sync::{broadcast as broadcast_queue, mpsc}; -use cirb::Input as RBInput; use crate::device_info::{Channel, DeviceInfo}; -use crate::common::*; +use crate::{common::*, RealTimeClockReceiver}; pub trait SelfInfoBuilder { fn new_self(app_name: &str, short_app_name: &str, my_ip: Option) -> DeviceInfo; @@ -93,27 +95,37 @@ impl SelfInfoBuilder for DeviceInfo { pub struct DeviceServer { pub self_info: Arc, - tx_inputs: Vec>, + //tx_inputs: Vec>, shutdown_todo: Pin + Send>> } impl DeviceServer { pub async fn start_with_recv_callback(self_info: DeviceInfo, samples_callback: SamplesCallback) -> Self { - Self::start(self_info, |si: &Arc, _| { - SamplesCollector::new_with_callback(si.clone(), Box::new(samples_callback)) + Self::start::>, OwnedBuffering>(self_info, |si: &Arc, workers, _| { + let (sc, future) = SamplesCollector::>>::new_with_callback(si.clone(), Box::new(samples_callback)); + workers.push(tokio::spawn(future)); + OwnedBuffering::new(524288 /*TODO*/, 4800 /*TODO*/, Arc::new(sc)) }).await } - pub async fn start_with_realtime_receiver(self_info: DeviceInfo) -> (Self, RealTimeSamplesReceiver, RealTimeBoxReceiver>) { + pub async fn start_with_realtime_receiver(self_info: DeviceInfo) -> (Self, RealTimeSamplesReceiver>>, RealTimeBoxReceiver>) { let mut rt_recv = None; let mut clk = None; - (Self::start(self_info, |si: &Arc, clkrcv: &broadcast_queue::Sender| { + (Self::start(self_info, |si: &Arc, workers, clkrcv: &ClockReceiver| { let (col, col_fut, rtr) = SamplesCollector::new_realtime(si.clone(), clkrcv.subscribe()); rt_recv = Some(rtr); clk = Some(clkrcv.subscribe()); - (col, col_fut) + workers.push(tokio::spawn(col_fut)); + OwnedBuffering::new(524288 /*TODO*/, 4800 /*TODO*/, Arc::new(col)) }).await, rt_recv.unwrap(), async_clock_receiver_to_realtime(clk.unwrap())) } - pub async fn start(self_info: DeviceInfo, create_collector: impl FnOnce(&Arc, &broadcast_queue::Sender) -> (SamplesCollector, Pin + Send + 'static>>)) -> Self { + pub async fn start_with_external_buffering(self_info: DeviceInfo, rx_channels_buffers: Vec>) -> (Self, RealTimeClockReceiver) { + let mut clk = None; + (Self::start::>, ExternalBuffering>(self_info, |si: &Arc, workers, clkrcv| { + clk = Some(clkrcv.subscribe()); + ExternalBuffering::new(rx_channels_buffers, 4800 /*TODO*/) + }).await, async_clock_receiver_to_realtime(clk.unwrap())) + } + pub async fn start + Send + Sync + 'static>(self_info: DeviceInfo, create_rx_buffering: impl FnOnce(&Arc, &mut Vec>, &ClockReceiver) -> B) -> Self { let self_info = Arc::new(self_info); let state_storage = Arc::new(StateStorage::new(&self_info)); let ref_instant = Instant::now(); @@ -124,32 +136,35 @@ impl DeviceServer { let shdn_recv4 = shutdown_send.subscribe(); let mdns_handle = crate::mdns_server::start_server(self_info.clone()); - let (clock_tx, clock_rx) = broadcast_queue::channel(100); - let (flows_rx_handle, flows_rx_thread) = crate::flows_rx::FlowsReceiver::start(self_info.clone(), ref_instant); let flows_rx_handle = Arc::new(flows_rx_handle); let mdns_client = Arc::new(crate::mdns_client::MdnsClient::new(self_info.ip_address)); let (mcast_tx, mcast_rx) = mpsc::channel(100); - let (samples_collector, samples_collector_worker) = create_collector(&self_info, &clock_tx); - let samples_collector = Arc::new(samples_collector); + let clock_receiver = start_clock_receiver(); + + info!("waiting for clock"); + clock_receiver.subscribe().recv().await.unwrap(); + info!("clock ready"); + + let mut tasks = vec![]; + let channels_buffering = create_rx_buffering(&self_info, &mut tasks, &clock_receiver); let (channels_sub_handle, channels_sub_worker) = ChannelsSubscriber::new( self_info.clone(), + make_shared_media_clock(&clock_receiver), flows_rx_handle.clone(), mdns_client, mcast_tx, - samples_collector.clone(), + channels_buffering, state_storage, ref_instant, ); let channels_sub_handle = Arc::new(channels_sub_handle); - let (flows_tx_handle, tx_inputs, flows_tx_thread) = FlowsTransmitter::start(self_info.clone(), clock_rx); - - start_clock_receiver(clock_tx, shutdown_send.subscribe()).await; + //let (flows_tx_handle, tx_inputs, flows_tx_thread) = FlowsTransmitter::start(self_info.clone(), clock_rx); - let tasks = [ + tasks.append(&mut vec![ tokio::spawn(crate::arc_server::run_server( self_info.clone(), channels_sub_handle.clone(), @@ -157,10 +172,9 @@ impl DeviceServer { )), tokio::spawn(crate::cmc_server::run_server(self_info.clone(), shdn_recv2)), tokio::spawn(crate::info_mcast_server::run_server(self_info.clone(), mcast_rx, shdn_recv3)), - tokio::spawn(crate::flows_control_server::run_server(self_info.clone(), flows_tx_handle, shdn_recv4)), + //tokio::spawn(crate::flows_control_server::run_server(self_info.clone(), flows_tx_handle, shdn_recv4)), tokio::spawn(channels_sub_worker), - tokio::spawn(samples_collector_worker), - ]; + ]); info!("all tasks spawned"); @@ -169,27 +183,28 @@ impl DeviceServer { info!("shutting down"); shutdown_send.send(()).unwrap(); mdns_handle.shutdown().unwrap(); + clock_receiver.stop().await.unwrap(); flows_rx_handle.shutdown().await; - samples_collector.shutdown().await; channels_sub_handle1.shutdown().await; for task in tasks { task.await.unwrap(); } flows_rx_thread.join().unwrap(); - flows_tx_thread.join().unwrap(); + //flows_tx_thread.join().unwrap(); info!("shutdown ok"); }.boxed(); Self { self_info, - tx_inputs, + //tx_inputs, shutdown_todo } } - pub fn take_tx_inputs(&mut self) -> Vec> { - std::mem::take(&mut self.tx_inputs) - } + /* pub fn take_tx_inputs(&mut self) -> Vec> { + unimplemented!() + //std::mem::take(&mut self.tx_inputs) + } */ pub async fn shutdown(self) { self.shutdown_todo.await; diff --git a/inferno_aoip/src/flows_rx.rs b/inferno_aoip/src/flows_rx.rs index 5e95174..a808b07 100644 --- a/inferno_aoip/src/flows_rx.rs +++ b/inferno_aoip/src/flows_rx.rs @@ -3,9 +3,11 @@ use crate::device_info::DeviceInfo; use crate::net_utils::MTU; use crate::os_utils::set_current_thread_realtime; use crate::samples_utils::*; +use crate::ring_buffer::{ProxyToSamplesBuffer, RBInput}; use crate::thread_utils::run_future_in_new_thread; use std::collections::BTreeMap; +use std::io::ErrorKind::WouldBlock; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::pin::Pin; use std::sync::atomic::AtomicUsize; @@ -14,7 +16,6 @@ use std::thread::JoinHandle; use std::time::{Duration, Instant}; use atomic::Ordering; -use cirb::Input as RBInput; use futures::future::select_all; use futures::{Future, FutureExt}; use itertools::Itertools; @@ -29,90 +30,98 @@ const KEEPALIVE_CONTENT: [u8; 2] = [0x13, 0x37]; //pub type PacketCallback = Box; -struct Channel { - sink: RBInput, +struct Channel { + sink: RBInput, } -struct SocketData { +struct SocketData { socket: UdpSocket, last_source: Option, last_packet_time: Arc, bytes_per_sample: usize, - channels: Vec>, + channels: Vec>>, } -enum Command { +enum Command { NoOp, Shutdown, - AddSocket { index: usize, socket: SocketData }, + AddSocket { index: usize, socket: SocketData

}, RemoveSocket { index: usize }, - ConnectChannel { socket_index: usize, channel_index: usize, sink: RBInput }, + ConnectChannel { socket_index: usize, channel_index: usize, sink: RBInput }, DisconnectChannel { socket_index: usize, channel_index: usize }, } -struct FlowsReceiverInternal { - commands_receiver: mpsc::Receiver, +struct FlowsReceiverInternal { + commands_receiver: mpsc::Receiver>, poll: mio::Poll, - sockets: Vec>, + sockets: Vec>>, sample_rate: u32, ref_instant: Instant, } -impl FlowsReceiverInternal { - fn receive(sd: &mut SocketData, sample_rate: u32, ref_instant: Instant) -> Command { +impl FlowsReceiverInternal

{ + fn receive(sd: &mut SocketData

, sample_rate: u32, ref_instant: Instant) -> Command

{ let mut buf = [0; MTU]; - match sd.socket.recv_from(&mut buf) { - Ok((recv_size, src)) => { - if recv_size < 9 { - error!("received corrupted (too small) packet on flow socket"); - return Command::NoOp; - } + loop { + match sd.socket.recv_from(&mut buf) { + Ok((recv_size, src)) => { + if recv_size < 9 { + error!("received corrupted (too small) packet on flow socket"); + return Command::NoOp; + } + //debug!("received packet"); - let num_channels = sd.channels.len(); - sd.last_packet_time.store(ref_instant.elapsed().as_secs() as _, Ordering::Relaxed); + let num_channels = sd.channels.len(); + sd.last_packet_time.store(ref_instant.elapsed().as_secs() as _, Ordering::Relaxed); - let _total_num_samples = (recv_size - 9) / sd.bytes_per_sample; - //let audio_bytes = &buf[9..9+total_num_samples*sd.bytes_per_sample]; - let audio_bytes = &buf[9..recv_size]; - let timestamp = (u32::from_be_bytes([buf[1], buf[2], buf[3], buf[4]]) as usize) - .wrapping_mul(sample_rate as usize) - .wrapping_add(u32::from_be_bytes([buf[5], buf[6], buf[7], buf[8]]) as usize); + let _total_num_samples = (recv_size - 9) / sd.bytes_per_sample; + //let audio_bytes = &buf[9..9+total_num_samples*sd.bytes_per_sample]; + let audio_bytes = &buf[9..recv_size]; + let timestamp = (u32::from_be_bytes([buf[1], buf[2], buf[3], buf[4]]) as usize) + .wrapping_mul(sample_rate as usize) + .wrapping_add(u32::from_be_bytes([buf[5], buf[6], buf[7], buf[8]]) as usize) as Clock; - // TODO: add timestamp sanity checks with PTP clock + // TODO: add timestamp sanity checks with PTP clock - let stride = num_channels * sd.bytes_per_sample; - let samples_count = audio_bytes.len() / stride; - //info!("first byte = {}, assuming {} samples in {} channels", buf[0], samples_count, num_channels); - for (i, ch) in sd.channels.iter_mut().enumerate() { - if let Some(ch) = ch { - let reader = SamplesReader { - bytes: audio_bytes, - read_pos: i * sd.bytes_per_sample, - stride, - remaining_samples: samples_count, - }; - match sd.bytes_per_sample { - 2 => ch.sink.write_from_at(timestamp, S16ReaderIterator(reader)), - 3 => ch.sink.write_from_at(timestamp, S24ReaderIterator(reader)), - 4 => ch.sink.write_from_at(timestamp, S32ReaderIterator(reader)), - other => { - error!("BUG: unsupported bytes per sample {}", other); - return Command::NoOp; + let stride = num_channels * sd.bytes_per_sample; + let samples_count = audio_bytes.len() / stride; + //info!("first byte = {}, assuming {} samples in {} channels", buf[0], samples_count, num_channels); + for (i, ch) in sd.channels.iter_mut().enumerate() { + if let Some(ch) = ch { + let reader = SamplesReader { + bytes: audio_bytes, + read_pos: i * sd.bytes_per_sample, + stride, + remaining_samples: samples_count, + }; + match sd.bytes_per_sample { + // FIXME: in ALSA plugin timestamp should be shifted by channel latency into the future to avoid dropouts + // FIXME: in ALSA plugin there can be more than 1 sink per input channel because we skip SamplesCollector !!! + 2 => ch.sink.write_from_at(timestamp, S16ReaderIterator(reader)), + 3 => ch.sink.write_from_at(timestamp, S24ReaderIterator(reader)), + 4 => ch.sink.write_from_at(timestamp, S32ReaderIterator(reader)), + other => { + error!("BUG: unsupported bytes per sample {}", other); + return Command::NoOp; + } } } } + //(sd.callback)(src, &buf[..recv_size]); + sd.last_source = Some(src); + } + Err(e) => { + if e.kind() != WouldBlock { + error!("flow socket receive error: {:?}", e); + // TODO recreate socket? + } + break; } - //(sd.callback)(src, &buf[..recv_size]); - sd.last_source = Some(src); - } - Err(e) => { - error!("flow socket receive error: {:?}", e); - // TODO recreate socket? } }; return Command::NoOp; } - async fn take_command(receiver: &mut mpsc::Receiver) -> Command { + async fn take_command(receiver: &mut mpsc::Receiver>) -> Command

{ receiver.recv().await.unwrap_or(Command::Shutdown) } fn run(&mut self) { @@ -139,6 +148,7 @@ impl FlowsReceiverInternal { Ok(command) => match command { Command::Shutdown => break, Command::AddSocket { index: id, mut socket } => { + debug!("adding socket"); self.poll.registry().register(&mut socket.socket, mio::Token(id), mio::Interest::READABLE).unwrap(); let previous = std::mem::replace(&mut self.sockets[id], Some(socket)); debug_assert!(previous.is_none()); @@ -170,6 +180,8 @@ impl FlowsReceiverInternal { if let Some(src) = sd.last_source { if let Err(e) = sd.socket.send_to(&KEEPALIVE_CONTENT, src) { error!("failed to send keepalive to {src:?}: {e:?}"); + } else { + trace!("sent keepalive"); } } } @@ -183,13 +195,13 @@ impl FlowsReceiverInternal { } } -pub struct FlowsReceiver { - commands_sender: mpsc::Sender, +pub struct FlowsReceiver { + commands_sender: mpsc::Sender>, waker: mio::Waker } -impl FlowsReceiver { - fn run(rx: mpsc::Receiver, poll: mio::Poll, sample_rate: u32, ref_instant: Instant) { +impl FlowsReceiver

{ + fn run(rx: mpsc::Receiver>, poll: mio::Poll, sample_rate: u32, ref_instant: Instant) { let mut internal = FlowsReceiverInternal { commands_receiver: rx, sockets: (0..MAX_FLOWS).map(|_|None).collect_vec(), poll, sample_rate, ref_instant }; internal.run(); @@ -237,7 +249,7 @@ impl FlowsReceiver { self.commands_sender.send(Command::RemoveSocket { index: local_index }).await.log_and_forget(); self.waker.wake().log_and_forget(); } - pub async fn connect_channel(&self, local_index: usize, channel_index: usize, sink: RBInput) { + pub async fn connect_channel(&self, local_index: usize, channel_index: usize, sink: RBInput) { debug!("connecting channel: flow index={local_index}, channel in flow: {channel_index}"); self .commands_sender diff --git a/inferno_aoip/src/flows_tx.rs b/inferno_aoip/src/flows_tx.rs index 946d445..d1b26cd 100644 --- a/inferno_aoip/src/flows_tx.rs +++ b/inferno_aoip/src/flows_tx.rs @@ -8,7 +8,6 @@ use std::thread::JoinHandle; use std::{collections::BTreeMap, net::SocketAddr, sync::atomic::AtomicU32, time::Duration}; use atomic::Ordering; -use cirb::{wrapped_diff, Clock}; use futures::FutureExt; use itertools::Itertools; use rand::rngs::{SmallRng, ThreadRng}; @@ -21,8 +20,7 @@ use crate::thread_utils::run_future_in_new_thread; use crate::{common::*, DeviceInfo}; use crate::{media_clock::{ClockOverlay, MediaClock}, net_utils::MTU, protocol::flows_control::FlowHandle, Sample}; use crate::samples_utils::*; -use cirb::Output as RBOutput; -use cirb::Input as RBInput; +use crate::ring_buffer::{ProxyToSamplesBuffer, RBInput, RBOutput}; pub const FPP_MIN: u16 = 2; pub const FPP_MAX: u16 = 256; @@ -71,17 +69,17 @@ enum Command { UpdateClockOverlay(ClockOverlay), } -struct FlowsTransmitterInternal { +struct FlowsTransmitterInternal { commands_receiver: mpsc::Receiver, sample_rate: u32, flows: Vec>, clock: MediaClock, - channels_sources: Vec>, + channels_sources: Vec>, send_latency_samples: usize, //callback: SamplesRequestCallback, } -impl FlowsTransmitterInternal { +impl FlowsTransmitterInternal

{ fn now(&self) -> Option { self.clock.now_in_timebase(self.sample_rate as u64) } @@ -121,6 +119,7 @@ impl FlowsTransmitterInternal { for (index_in_flow, &ch_opt) in flow.channel_indices.iter().enumerate() { if let Some(ch_index) = ch_opt { //(self.callback)(flow.next_ts, ch_index, &mut tmp_samples[0..flow.fpp]); + // TODO remove not really necessary copy to tmp_samples, write_*_samples could read directly from ring buffer let r = self.channels_sources[ch_index].read_at(start_ts, &mut tmp_samples[0..flow.fpp]); if r.useful_start_index != 0 || r.useful_end_index != flow.fpp { error!("didn't have enough samples, transmitting silence. {} {}", r.useful_start_index, flow.fpp-r.useful_end_index); @@ -271,7 +270,7 @@ fn split_handle(h: FlowHandle) -> (u32, u16) { } impl FlowsTransmitter { - async fn run(rx: mpsc::Receiver, sample_rate: u32, latency_ns: usize, channels_outputs: Vec>) { + async fn run(rx: mpsc::Receiver, sample_rate: u32, latency_ns: usize, channels_outputs: Vec>) { let mut internal = FlowsTransmitterInternal { commands_receiver: rx, sample_rate, @@ -290,7 +289,7 @@ impl FlowsTransmitter { }; internal.run().await; } - pub fn start(self_info: Arc, mut media_clock_receiver: broadcast::Receiver) -> (Self, Vec>, JoinHandle<()>) { + pub fn start(self_info: Arc, mut media_clock_receiver: broadcast::Receiver, channels_outputs: Vec>) -> (Self, JoinHandle<()>) { let (tx, rx) = mpsc::channel(100); let tx1 = tx.clone(); tokio::spawn(async move { @@ -310,9 +309,6 @@ impl FlowsTransmitter { } }); let srate = self_info.sample_rate; - let (channels_inputs, channels_outputs) = (0..self_info.tx_channels.len()).map(|_| { - cirb::RTHistory::::new(BUFFERED_SAMPLES_PER_CHANNEL, 0).split() - }).unzip(); // TODO dehardcode latency_ns let thread_join = run_future_in_new_thread("flows TX", move || Self::run(rx, srate, 30_000_000, channels_outputs).boxed_local()); return (Self { @@ -321,7 +317,7 @@ impl FlowsTransmitter { flow_seq_id: 0.into(), flows: BTreeMap::new(), ip_port_to_id: BTreeMap::new() - }, channels_inputs, thread_join); + }, thread_join); } pub async fn shutdown(&self) { self.commands_sender.send(Command::Shutdown).await.log_and_forget(); diff --git a/inferno_aoip/src/lib.rs b/inferno_aoip/src/lib.rs index aa5aa38..bf609ba 100644 --- a/inferno_aoip/src/lib.rs +++ b/inferno_aoip/src/lib.rs @@ -1,5 +1,5 @@ // Inferno-AoIP -// Copyright (C) 2023 Teodor Woźniak +// Copyright (C) 2023-2024 Teodor Woźniak // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -45,6 +45,7 @@ //! #[tokio::main(flavor = "current_thread")] //! async fn main() { //! let self_info = DeviceInfo::new_self("My Inferno device", "MyInferno", None).make_rx_channels(16); +//! // TODO: update line below with proper start_* function //! let server = DeviceServer::start(self_info, Box::new(audio_callback)).await; //! let _ = tokio::signal::ctrl_c().await; //! server.shutdown().await; @@ -71,6 +72,7 @@ mod net_utils; mod os_utils; mod protocol; mod real_time_box_channel; +mod ring_buffer; mod samples_collector; mod samples_utils; mod state_storage; @@ -79,7 +81,9 @@ mod thread_utils; pub use common::Sample; pub use device_info::DeviceInfo; pub use device_server::{DeviceServer, SelfInfoBuilder}; -pub use media_clock::MediaClock; +pub use media_clock::{MediaClock, RealTimeClockReceiver}; +pub use ring_buffer::ExternalBufferParameters; +pub type AtomicSample = atomic::Atomic; pub mod utils { pub use crate::os_utils::set_current_thread_realtime; diff --git a/inferno_aoip/src/media_clock.rs b/inferno_aoip/src/media_clock.rs index 8f4c260..63eb1b8 100644 --- a/inferno_aoip/src/media_clock.rs +++ b/inferno_aoip/src/media_clock.rs @@ -1,55 +1,28 @@ +use std::sync::Arc; +use std::sync::RwLock; use std::time::Duration; use clock_steering::unix::UnixClock; use clock_steering::Clock as _; use custom_error::custom_error; -use cirb::{Clock, ClockDiff}; use futures::AsyncWriteExt; use interprocess::local_socket::tokio::LocalSocketStream; use tokio::select; use tokio::sync::broadcast; use futures::io::AsyncReadExt; +pub use usrvclock::ClockOverlay; +pub use usrvclock::AsyncClient as ClockReceiver; use crate::{common::*, real_time_box_channel}; use crate::real_time_box_channel::RealTimeBoxReceiver; +pub type RealTimeClockReceiver = RealTimeBoxReceiver>; // it's better to have the clock in the past than in the future - otherwise Dante devices receiving from us go mad and fart const CLOCK_OFFSET_NS: ClockDiff = -500_000; -#[derive(Clone, Copy, Debug)] -pub struct ClockOverlay { - last_sync: Clock, - shift: ClockDiff, - freq_scale: f64, -} - -custom_error! { pub ClockDecodeError - BufferTooShort = "buffer too short", - InvalidMagicNumber = "invalid magic number" -} - -impl ClockOverlay { - fn from_packet_buffer(buffer: &[u8]) -> Result { - let buf: [u8; 32] = buffer.try_into().map_err(|_|ClockDecodeError::BufferTooShort)?; - if buf[0..8] != *b"TAIovl\x00\x01" { - return Err(ClockDecodeError::InvalidMagicNumber); - } - Ok(Self { - last_sync: Clock::from_ne_bytes(buf[8..16].try_into().unwrap()), - shift: ClockDiff::from_ne_bytes(buf[16..24].try_into().unwrap()), - freq_scale: f64::from_ne_bytes(buf[24..32].try_into().unwrap()) - }) - } - fn timestamp_from_underlying(&self, ts: Clock) -> Clock { - let elapsed = (ts as ClockDiff).wrapping_sub(self.last_sync as ClockDiff); - let corr = (elapsed as f64 * self.freq_scale) as ClockDiff; - (ts as ClockDiff).wrapping_add(self.shift).wrapping_add(corr) as Clock - } -} #[derive(Clone)] pub struct MediaClock { - clock: UnixClock, overlay: Option, } @@ -61,22 +34,17 @@ fn timestamp_to_clock_value(ts: clock_steering::Timestamp) -> Clock { impl MediaClock { pub fn new() -> Self { Self { - clock: UnixClock::CLOCK_TAI, overlay: None } } pub fn is_ready(&self) -> bool { self.overlay.is_some() } - fn now_underlying(&self) -> Clock { - timestamp_to_clock_value(self.clock.now().unwrap()) - } pub fn update_overlay(&mut self, mut overlay: ClockOverlay) { - overlay.shift = overlay.shift.wrapping_add(CLOCK_OFFSET_NS); + overlay.shift = overlay.shift.wrapping_add(CLOCK_OFFSET_NS as i64); if let Some(cur_overlay) = self.overlay { - let ro_now = self.now_underlying(); - let cur_ovl_time = cur_overlay.timestamp_from_underlying(ro_now); - let new_ovl_time = overlay.timestamp_from_underlying(ro_now); + let cur_ovl_time = cur_overlay.now_ns(); + let new_ovl_time = overlay.now_ns(); let diff = (new_ovl_time as ClockDiff).wrapping_sub(cur_ovl_time as ClockDiff); if diff.abs() > 200_000_000 { error!("clock is trying to jump dangerously by {diff} ns, ignoring update"); @@ -87,13 +55,12 @@ impl MediaClock { } #[inline(always)] pub fn now_ns(&self) -> Option { - self.overlay.map(|overlay| { - overlay.timestamp_from_underlying(self.now_underlying()) - }) + self.overlay.map(|overlay| { overlay.now_ns() as Clock }) } #[inline(always)] pub fn now_in_timebase(&self, timebase_hz: u64) -> Option { self.now_ns().map(|ns| { + // TODO it will jump when underlying wraps ((ns as u128) * (timebase_hz as u128) / 1_000_000_000u128) as Clock }) } @@ -112,58 +79,30 @@ impl MediaClock { } -pub async fn start_clock_receiver(tx: broadcast::Sender, mut shutdown: broadcast::Receiver<()>) { +pub fn start_clock_receiver() -> ClockReceiver { + ClockReceiver::start(usrvclock::DEFAULT_SERVER_SOCKET_PATH.into(), Box::new(|e| warn!("clock receive error: {e:?}"))) +} + +pub fn make_shared_media_clock(receiver: &ClockReceiver) -> Arc> { + let mut rx = receiver.subscribe(); + let media_clock = Arc::new(RwLock::new(MediaClock::new())); + let media_clock1 = media_clock.clone(); tokio::spawn(async move { - let mut buff = [0u8; 32]; - async fn read_from_stream(stream_opt: &mut Option, buff: &mut [u8]) { - loop { - let stream = loop { - if stream_opt.is_none() { - let result = LocalSocketStream::connect("/tmp/ptp-clock-overlay").await; - match result { - Ok(stream) => { - *stream_opt = Some(stream); - }, - Err(e) => { - warn!("could not open clock: {e:?}"); - tokio::time::sleep(Duration::from_secs(1)).await; - } - } - } - if let Some(stream) = stream_opt { - break stream; - } - }; - match stream.read_exact(buff).await { - Ok(_) => { break; }, - Err(e) => { - warn!("could not read clock: {e:?}, will reopen stream"); - *stream_opt = None; - } - } - } - } - let mut stream_opt = None; loop { - select! { - _ = read_from_stream(&mut stream_opt, &mut buff) => { - match ClockOverlay::from_packet_buffer(&buff) { - Ok(overlay) => { - tx.send(overlay); - }, - Err(e) => { - warn!("failed to receive clock from PTP daemon: {e:?}"); - } - } - }, - _ = shutdown.recv() => { + match rx.recv().await { + Ok(overlay) => { + media_clock.write().unwrap().update_overlay(overlay); + } + Err(broadcast::error::RecvError::Closed) => { break; + }, + Err(e) => { + warn!("clock receive error {e:?}"); } } - //break; // was testing whether the clock is to blame for tx buffer underruns } - warn!("clock receiver exiting"); }); + media_clock1 } pub fn async_clock_receiver_to_realtime(mut receiver: broadcast::Receiver) -> RealTimeBoxReceiver> { diff --git a/inferno_aoip/src/real_time_box_channel.rs b/inferno_aoip/src/real_time_box_channel.rs index 2a95602..a3a5592 100644 --- a/inferno_aoip/src/real_time_box_channel.rs +++ b/inferno_aoip/src/real_time_box_channel.rs @@ -1,5 +1,5 @@ -/// Used for decoupling memory allocation and deallocation from realtime thread. -/// Sender is non-real-time, receiver is real-time. +//! Used for decoupling memory allocation and deallocation from realtime thread. +//! Sender is non-real-time, receiver is real-time. use std::{mem, sync::Arc}; diff --git a/inferno_aoip/src/ring_buffer.rs b/inferno_aoip/src/ring_buffer.rs new file mode 100644 index 0000000..5c0f3bd --- /dev/null +++ b/inferno_aoip/src/ring_buffer.rs @@ -0,0 +1,602 @@ +use crate::common::*; +use std::{marker::PhantomData, slice, sync::{atomic::AtomicUsize, Arc, RwLock}}; +use atomic::{Atomic, Ordering}; +use bool_vec::{boolvec, BoolVec}; +use bytemuck::NoUninit; +use itertools::Itertools; + +pub trait ProxyToBuffer { + fn len(&self) -> usize; + /// If buffer is available, executes `cb` with buffer's slice as an argument and returns Some with its result + /// If buffer is unavailable, returns None + fn map(&self, cb: impl FnOnce(&[T]) -> R) -> Option; +} + +pub trait ProxyToSamplesBuffer: ProxyToBuffer> { +} + +/// A buffer which owns its data, stored as a `Vec`` +pub struct OwnedBuffer(pub Vec); + +impl OwnedBuffer { + fn new(length: usize) -> Self { + Self((0..length).map(|_| T::default()).collect_vec()) + } +} + +impl ProxyToBuffer for OwnedBuffer { + #[inline(always)] + fn len(&self) -> usize { + self.0.len() + } + #[inline(always)] + fn map(&self, cb: impl FnOnce(&[T]) -> R) -> Option { + Some(cb(self.0.as_slice())) + } +} + +impl ProxyToSamplesBuffer for OwnedBuffer> { +} + +/// Buffer which can be invalidated at any time by an external force. +/// Intended for buffers managed by libraries not written in Rust. +pub struct ExternalBuffer { + ptr: *const T, + length: usize, + // we're using separate flag because ExternalBuffer may be one of the views of the same interleaved buffer with different offsets + valid: Arc>, +} + +// safety: is guaranteed by lockable valid flag +unsafe impl Send for ExternalBuffer {} +unsafe impl Sync for ExternalBuffer {} + +impl ExternalBuffer { + /// `ptr` is pointer to start of the buffer + /// + /// `length` is buffer length in items (not in bytes) + /// + /// `valid` is shared and locked reference to flag which should be set to `false` when we are notified that buffer is no longer valid. + /// (it can't be atomic_bool by design - it must be locked all time the buffer is in use) + /// + /// Safety: user must ensure that ptr & length correspond to a valid memory region containing a slice `[T; length]` + unsafe fn new(ptr: *const T, length: usize, valid: Arc>) -> Self { + Self { ptr, length, valid } + } +} + +impl ProxyToBuffer for ExternalBuffer { + fn len(&self) -> usize { + self.length + } + fn map(&self, cb: impl FnOnce(&[T]) -> R) -> Option { + if let Ok(guard) = self.valid.try_read() { + let valid = *guard; + if valid { + Some(cb(unsafe { slice::from_raw_parts(self.ptr, self.length) })) + } else { + None + } + } else { + None + } + } +} + +impl ProxyToSamplesBuffer for ExternalBuffer> { +} + +struct RingBufferShared>> { + _t: PhantomData, + buffer: P, + stride: usize, + items_size: usize, + read_pos: AtomicUsize, // TODO remove + readable_pos: AtomicUsize, + writing_pos: AtomicUsize, + holes_count: AtomicUsize, +} + +impl>> RingBufferShared { + fn new(storage: P, stride: usize, start_time: Clock) -> Arc { + let items_size = (storage.len()+stride-1) / stride; + Arc::new(RingBufferShared { + _t: Default::default(), + buffer: storage, + stride, + items_size, + read_pos: start_time.into(), + readable_pos: start_time.into(), + writing_pos: start_time.into(), + holes_count: 0.into() + }) + } + fn item_to_buffer_index(&self, i: Clock) -> Clock { + // TODO change to more optimized log2 implementation when we're sure that buffer length will always be power of 2 + (i % self.items_size) * self.stride + } +} + +#[inline(always)] +fn for_in_ring(length: usize, start: usize, end: usize, mut cb: impl FnMut(usize)) { + if start==end { + return; + } + let w_start = start % length; + let w_end = end % length; + if w_start < w_end { + for i in w_start..w_end { + cb(i); + } + } else { + for i in w_start..length { + cb(i); + } + for i in 0..w_end { + cb(i); + } + } +} + +pub struct RBInput>> { + rb: Arc>, + item_ready: BoolVec, + hole_fix_wait: usize, +} + +impl>> RBInput { + pub fn write_from_at(&mut self, start_timestamp: Clock, mut input: impl ExactSizeIterator) { + let input_len = input.len(); + assert!(input_len < self.rb.items_size); + let hole = wrapped_diff(start_timestamp, self.rb.writing_pos.load(Ordering::Relaxed)) > 0; + //let wrapped_start_ts = start_timestamp % self.rb.items_size; + if hole { + //let wrapped_writing_pos = self.rb.writing_pos.load(Ordering::Relaxed) % self.rb.items_size; + for_in_ring(self.rb.items_size, self.rb.writing_pos.load(Ordering::Relaxed), start_timestamp, |i| { + self.item_ready.set(i, false); + }); + } + // FIXME wrapping_add % items_size may result in unexpected behaviour if items_size is not power of 2 + // TODO ensure that items_size is power of 2 + let end_ts = start_timestamp.wrapping_add(input_len); + //let wrapped_end_ts = end_ts % self.rb.items_size; + + self.rb.buffer.map(|buffer| { + let mut hole_in_past = self.rb.readable_pos.load(Ordering::Relaxed) != self.rb.writing_pos.load(Ordering::Relaxed); + + if wrapped_diff(end_ts, self.rb.writing_pos.load(Ordering::Relaxed)) > 0 { + self.rb.writing_pos.store(end_ts, Ordering::Release); + } + for_in_ring(self.rb.items_size, start_timestamp, end_ts, |i| { + //debug!("writing to RB index {i}"); + buffer[i * self.rb.stride].store(input.next().unwrap(), Ordering::Relaxed); + self.item_ready.set(i, true); + }); + atomic::fence(Ordering::Release); + + if hole_in_past { + if self.rb.writing_pos.load(Ordering::Relaxed).wrapping_sub(self.rb.readable_pos.load(Ordering::Relaxed)) > self.hole_fix_wait { + self.rb.holes_count.fetch_add(1, Ordering::Release); + let new_readable_pos = self.rb.writing_pos.load(Ordering::Relaxed).wrapping_sub(self.hole_fix_wait); + for_in_ring(self.rb.items_size, self.rb.readable_pos.load(Ordering::Relaxed), new_readable_pos, |i| { + if !self.item_ready.get(i).unwrap() { + buffer[i * self.rb.stride].store(T::default(), Ordering::Relaxed); + } + }); + atomic::fence(Ordering::Release); + self.rb.readable_pos.store(new_readable_pos, Ordering::Release); + } + //let wrapped_readable_pos = self.rb.readable_pos.load(Ordering::Relaxed) % self.rb.items_size; + //let wrapped_writing_pos = self.rb.writing_pos.load(Ordering::Relaxed) % self.rb.items_size; + let mut ready = true; + let mut new_write_pos = self.rb.readable_pos.load(Ordering::Relaxed); + for_in_ring(self.rb.items_size, self.rb.readable_pos.load(Ordering::Relaxed), self.rb.writing_pos.load(Ordering::Relaxed), |i| { + if !ready { return; } + if self.item_ready.get(i).unwrap() { + new_write_pos += 1; + } else { + ready = false; + } + }); + if ready { + hole_in_past = false; + } + self.rb.readable_pos.store(new_write_pos, Ordering::Release); + } + let new_writing_pos = start_timestamp.wrapping_add(input_len); + if wrapped_diff(new_writing_pos, self.rb.writing_pos.load(Ordering::Relaxed) /* really? */) > 0 { + self.rb.writing_pos.store(new_writing_pos, Ordering::Release); + } + if !(hole || hole_in_past) { + // inform the reader(s) that new data is readable + self.rb.readable_pos.store(self.rb.writing_pos.load(Ordering::Relaxed), Ordering::Release); + } + }); + } +} + + + +/// Result of the `RBOutput::read_at` function. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ReadResult { + /// start index of useful data in output slice + pub useful_start_index: usize, + /// end index of useful data in output slice + pub useful_end_index: usize, +} + +pub struct RBOutput>> { + rb: Arc>, +} + +impl>> Clone for RBOutput { + // https://stackoverflow.com/questions/72150623/deriveclone-seems-to-wrongfully-enforce-generic-to-be-clone + fn clone(&self) -> Self { + Self { rb: self.rb.clone() } + } +} + +impl>> RBOutput { + pub fn readable_until(&self) -> Clock { + self.rb.readable_pos.load(Ordering::Acquire) + } + /// Because single ring buffer with its read position pointer can be shared between multiple `RBOutput`s, + /// this method, unlike `RBInput::write_from_at`, does not advance the read position. + /// Use the method `read_done` when reads of all `RBOutput`s sharing the same ring buffer are done. + pub fn read_at(&self, start_timestamp: Clock, output: &mut [T]) -> ReadResult { + // in normal case: start_ts < readable_pos <= writing_pos + let readable = wrapped_diff(self.rb.readable_pos.load(Ordering::Acquire), start_timestamp); + if readable < 0 || readable > self.rb.items_size.try_into().unwrap() { + debug!("readable {readable}, items_size {}", self.rb.items_size); + // we're trying to read not yet written data, or we're lagging behind so much that data at that timestamp has already been overwritten + return ReadResult { useful_start_index: 0, useful_end_index: 0 }; + } + let readable = readable as usize; + let original_output_len = output.len(); + let output_len = original_output_len.min(readable); + + //let wrapped_start_ts = start_timestamp % self.rb.items_size; + //let wrapped_end_ts = start_timestamp.wrapping_add(output_len) % self.rb.items_size; + let mut out_index = 0; + + if self.rb.buffer.map(|buffer| { + atomic::fence(Ordering::Acquire); + for_in_ring(self.rb.items_size, start_timestamp, start_timestamp.wrapping_add(output_len), |rb_index| { + //debug!("reading from RB index {rb_index}"); + output[out_index] = buffer[rb_index * self.rb.stride].load(Ordering::Relaxed); + out_index += 1; + }); + }).is_none() { + return ReadResult { useful_start_index: 0, useful_end_index: 0 }; + } + + let writing_pos = self.rb.writing_pos.load(Ordering::Acquire); + let diff = wrapped_diff(writing_pos, start_timestamp); + if diff > self.rb.items_size.try_into().unwrap() { + // data has been overwritten in the meantime, so some data at the beginning of the buffer may be wrong + let overwritten = diff as usize - self.rb.items_size; + return ReadResult { useful_start_index: overwritten.min(output_len), useful_end_index: output_len }; + } + + ReadResult { useful_start_index: 0, useful_end_index: output_len } + } + + pub fn read_done(&self, until: Clock) { + self.rb.read_pos.store(until, Ordering::Release); + } + pub fn holes_count(&self) -> usize { + self.rb.holes_count.load(Ordering::Acquire) + } +} + +pub fn new_owned(length: usize, start_time: Clock, hole_fix_wait: usize) -> (RBInput>>, RBOutput>>) { + let shared = RingBufferShared::new(OwnedBuffer::>::new(length), 1, start_time); + ( + RBInput{ rb: shared.clone(), item_ready: boolvec![false; shared.items_size], hole_fix_wait }, + RBOutput{ rb: shared } + ) +} + +pub struct ExternalRBInput { + rb: Arc>>>, + margin: usize, +} + +impl ExternalRBInput { + fn advance(&self, new_position: Clock) { + self.rb.writing_pos.store(new_position, Ordering::Release); + self.rb.readable_pos.store(new_position.wrapping_sub(self.margin), Ordering::Release); + } + fn position(&self, clock: Clock) -> Clock { + clock + } +} + +pub struct ExternalRBOutput { + rb: Arc>>>, +} + +impl ExternalRBOutput { + fn position(&self, _clock: Clock) -> Clock { + self.rb.readable_pos.load(Ordering::Acquire) + // TODO when no data is received and so readable_pos can't be advanced by normal means, fill with silence + } +} + +//#[derive(Clone)] +pub struct ExternalBufferParameters { + ptr: *const Atomic, + length: usize, + stride: usize, + valid: Arc>, + +} + +// safety: is guaranteed by lockable valid flag +unsafe impl Send for ExternalBufferParameters {} +unsafe impl Sync for ExternalBufferParameters {} + + +impl ExternalBufferParameters { + pub unsafe fn new(ptr: *const Atomic, length: usize, stride: usize, valid: Arc>) -> Self { + Self { ptr, length, stride, valid } + } +} + + +// safety: ExternalBufferParameters::new is unsafe so user acknowledges the dangers when creating the `par` struct +pub fn wrap_external_source(par: &ExternalBufferParameters, start_time: Clock) -> RBOutput>> { + let external = unsafe { ExternalBuffer::>::new(par.ptr, par.length, par.valid.clone()) }; + let shared = RingBufferShared::new(external, par.stride, start_time); + RBOutput{ rb: shared } +} + +pub fn wrap_external_sink(par: &ExternalBufferParameters, start_time: Clock, hole_fix_wait: usize) -> RBInput>> { + let external = unsafe { ExternalBuffer::>::new(par.ptr, par.length, par.valid.clone()) }; + let shared = RingBufferShared::new(external, par.stride, start_time); + RBInput{ rb: shared.clone(), item_ready: boolvec![false; shared.items_size], hole_fix_wait } +} + + + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::{Arc, Barrier}; + use std::thread; + + #[test] + fn test_sequential_single_write_read() { + let (mut input, output) = new_owned(16, 0, 4); + + let writer = thread::spawn(move || { + for i in 0..16 { + input.write_from_at(i, std::iter::once(i as i32)); + } + }); + + let reader = thread::spawn(move || { + let mut read_values = vec![0; 16]; + for i in 0..16 { + while output.read_at(i, &mut read_values[i..i+1]) != (ReadResult{useful_start_index: 0, useful_end_index: 1}) { + thread::yield_now(); + } + } + assert_eq!(read_values, (0..16).collect::>()); + }); + + writer.join().unwrap(); + reader.join().unwrap(); + } + + #[test] + fn test_non_sequential_write_single_read() { + let (mut input, output) = new_owned(16, 0, 4); + + let barrier = Arc::new(Barrier::new(2)); + let barrier_writer = barrier.clone(); + let barrier_reader = barrier.clone(); + + let writer = thread::spawn(move || { + barrier_writer.wait(); + input.write_from_at(4, (4..8).map(|x| x as i32)); + input.write_from_at(0, (0..4).map(|x| x as i32)); + }); + + let reader = thread::spawn(move || { + barrier_reader.wait(); + let mut read_values = vec![0; 8]; + for i in 0..8 { + while output.read_at(i, &mut read_values[i..i+1]) != (ReadResult{useful_start_index: 0, useful_end_index: 1}) { + thread::yield_now(); + } + } + assert_eq!(read_values, (0..8).collect::>()); + }); + + writer.join().unwrap(); + reader.join().unwrap(); + } + + #[test] + fn test_fixed_hole_single_read() { + let (mut input, output) = new_owned(16, 0, 4); + + let barrier = Arc::new(Barrier::new(2)); + let barrier_writer = barrier.clone(); + let barrier_reader = barrier.clone(); + + let writer = thread::spawn(move || { + barrier_writer.wait(); + input.write_from_at(0, (0..2).map(|x| x as i32)); // write 0, 1 + input.write_from_at(4, (4..8).map(|x| x as i32)); // write 4, 5, 6, 7 + thread::sleep(std::time::Duration::from_millis(100)); + input.write_from_at(2, (2..4).map(|x| x as i32)); // write 2, 3 to fix the hole + }); + + let reader = thread::spawn(move || { + barrier_reader.wait(); + let mut read_values = vec![0; 8]; + for i in 0..8 { + while output.read_at(i, &mut read_values[i..i+1]) != (ReadResult{useful_start_index: 0, useful_end_index: 1}) { + thread::yield_now(); + } + } + assert_eq!(read_values, (0..8).collect::>()); + }); + + writer.join().unwrap(); + reader.join().unwrap(); + } + + #[test] + fn test_write_read() { + let (mut input, output) = new_owned(16, 0, 4); + + let writer = thread::spawn(move || { + input.write_from_at(0, (0..8).map(|x| x as i32)); // write 8 items at once + input.write_from_at(8, (8..16).map(|x| x as i32)); // write another 8 items at once + }); + + let reader = thread::spawn(move || { + let mut read_values = vec![0; 16]; + while output.read_at(0, &mut read_values[0..8]) != (ReadResult{useful_start_index: 0, useful_end_index: 8}) { + thread::yield_now(); + } + while output.read_at(8, &mut read_values[8..16]) != (ReadResult{useful_start_index: 0, useful_end_index: 8}) { + thread::yield_now(); + } + assert_eq!(read_values, (0..16).collect::>()); + }); + + writer.join().unwrap(); + reader.join().unwrap(); + } + + #[test] + fn test_wraparound_separate() { + let (mut input, output) = new_owned(16, 0, 4); + + let barrier = Arc::new(Barrier::new(2)); + let barrier_writer = barrier.clone(); + let barrier_reader = barrier.clone(); + + let writer = thread::spawn(move || { + barrier_writer.wait(); + input.write_from_at(12, (12..16).map(|x| x as i32)); // write near the end + input.write_from_at(16, (16..20).map(|x| x as i32)); // wrap around to the beginning + }); + + let reader = thread::spawn(move || { + barrier_reader.wait(); + let mut read_values = vec![0; 8]; + while output.read_at(12, &mut read_values[0..4]) != (ReadResult{useful_start_index: 0, useful_end_index: 4}) { + thread::yield_now(); + } + while output.read_at(16, &mut read_values[4..8]) != (ReadResult{useful_start_index: 0, useful_end_index: 4}) { + thread::yield_now(); + } + let expected: Vec = (12..20).collect(); + assert_eq!(&read_values[0..4], &expected[0..4]); + assert_eq!(&read_values[4..8], &expected[4..8]); + }); + + writer.join().unwrap(); + reader.join().unwrap(); + } + + #[test] + fn test_wraparound() { + let (mut input, output) = new_owned(16, 0, 4); + + let barrier = Arc::new(Barrier::new(2)); + let barrier_writer = barrier.clone(); + let barrier_reader = barrier.clone(); + + let writer = thread::spawn(move || { + barrier_writer.wait(); + input.write_from_at(14, (14..18).map(|x| x as i32)); // write across the boundary + input.write_from_at(18, (100..102).map(|x| x as i32)); // fix hole 0..14, TODO: handle it properly in write_from_at + }); + + let reader = thread::spawn(move || { + barrier_reader.wait(); + let mut read_values = vec![0; 4]; + while output.read_at(14, &mut read_values) != (ReadResult{useful_start_index: 0, useful_end_index: 4}) { + thread::yield_now(); + } + let expected: Vec = (14..18).collect(); + assert_eq!(read_values, expected); + }); + + writer.join().unwrap(); + reader.join().unwrap(); + } + + #[test] + fn test_wraparound_hole_fix_with_0() { + let (mut input, output) = new_owned(16, 0, 4); + + let barrier = Arc::new(Barrier::new(2)); + let barrier_writer = barrier.clone(); + let barrier_reader = barrier.clone(); + + let writer = thread::spawn(move || { + barrier_writer.wait(); + input.write_from_at(14, (14..18).map(|x| x as i32)); // write across the boundary + thread::sleep(std::time::Duration::from_millis(100)); + input.write_from_at(18, (18..22).map(|x| x as i32)); // fix the hole and wrap around the start + }); + + let reader = thread::spawn(move || { + barrier_reader.wait(); + let mut read_values = vec![0; 10]; + while output.read_at(12, &mut read_values[0..6]) != (ReadResult{useful_start_index: 0, useful_end_index: 6}) { + thread::yield_now(); + } + while output.read_at(18, &mut read_values[6..10]) != (ReadResult{useful_start_index: 0, useful_end_index: 4}) { + thread::yield_now(); + } + let expected: Vec = vec![0; 2].into_iter().chain(14..22).collect(); + assert_eq!(read_values, expected); + }); + + writer.join().unwrap(); + reader.join().unwrap(); + } + + #[test] + fn test_hole_fix_with_0() { + let (mut input, output) = new_owned(16, 0, 4); + + let barrier = Arc::new(Barrier::new(2)); + let barrier_writer = barrier.clone(); + let barrier_reader = barrier.clone(); + + let writer = thread::spawn(move || { + barrier_writer.wait(); + input.write_from_at(0, (0..4).map(|x| x as i32)); // write some items + input.write_from_at(8, (8..12).map(|x| x as i32)); // write items creating a hole + thread::sleep(std::time::Duration::from_millis(200)); + input.write_from_at(12, (12..16).map(|x| x as i32)); // write items after hole_fix_wait + }); + + let reader = thread::spawn(move || { + barrier_reader.wait(); + let mut read_values = vec![0; 16]; + while output.read_at(0, &mut read_values[0..16]) != (ReadResult{useful_start_index: 0, useful_end_index: 16}) { + thread::yield_now(); + } + let expected: Vec = (0..4) + .chain(vec![0; 4]) // default values for the hole + .chain(8..16) + .collect(); + assert_eq!(read_values, expected); + }); + + writer.join().unwrap(); + reader.join().unwrap(); + } + +} diff --git a/inferno_aoip/src/samples_collector.rs b/inferno_aoip/src/samples_collector.rs index 922a76c..8b4f477 100644 --- a/inferno_aoip/src/samples_collector.rs +++ b/inferno_aoip/src/samples_collector.rs @@ -1,7 +1,6 @@ use std::{pin::Pin, sync::Arc, time::Duration}; -use crate::{common::*, device_info::DeviceInfo, media_clock::{async_clock_receiver_to_realtime, ClockOverlay, MediaClock}, real_time_box_channel::{self, RealTimeBoxReceiver, RealTimeBoxSender}}; -use cirb::{wrapped_diff, Clock, Output as RBOutput}; +use crate::{common::*, device_info::DeviceInfo, media_clock::{async_clock_receiver_to_realtime, ClockOverlay, MediaClock}, real_time_box_channel::{self, RealTimeBoxReceiver, RealTimeBoxSender}, ring_buffer::{wrap_external_source, ExternalBufferParameters, ProxyToBuffer, ProxyToSamplesBuffer, RBOutput}}; use futures::{Future, FutureExt}; use itertools::Itertools; @@ -15,15 +14,15 @@ const MAX_LAG_SAMPLES: usize = 9600; pub type SamplesCallback = Box>) + Send + 'static>; -struct Channel { +struct Channel { id: usize, - source: RBOutput, + source: RBOutput, prev_holes_count: usize, latency_samples: Clock, was_connected: bool, } -impl Channel { +impl Channel

{ fn report_lost_samples(&self, timestamp: Clock, num_samples: usize, reason: &str) { error!("Lost {num_samples} samples at timestamp {timestamp} in channel id {} ({reason})", self.id); } @@ -63,19 +62,21 @@ impl Channel { } } if !good { - warn!("wanted {start_timestamp}..{} but has ..{}", start_timestamp + buffer.len(), self.source.readable_until().unwrap_or(0)); + warn!("wanted {start_timestamp}..{} but has ..{}", start_timestamp + buffer.len(), self.source.readable_until()); } good } } -pub struct RealTimeSamplesReceiver { - channels: Vec>>, +pub struct RealTimeSamplesReceiver { + channels: Vec>>>, clock: MediaClock, clock_recv: RealTimeBoxReceiver>, } -impl RealTimeSamplesReceiver { +// MAYBE TODO move timestamp checks to separate module + +impl RealTimeSamplesReceiver

{ fn get_min_max_end_timestamps(&self) -> Option<(Clock, Clock)> { get_min_max_end_timestamps(self.channels.iter().map(|chrecv|chrecv.get())) } @@ -111,19 +112,19 @@ impl RealTimeSamplesReceiver { } -enum Command { +enum Command { NoOp, Shutdown, - ConnectChannel { channel_index: usize, source: RBOutput, latency_samples: usize }, + ConnectChannel { channel_index: usize, source: RBOutput, latency_samples: usize }, DisconnectChannel { channel_index: usize }, } -struct ToRealTime { - commands_receiver: mpsc::Receiver, - senders: Vec>>, +struct ToRealTime { + commands_receiver: mpsc::Receiver>, + senders: Vec>>>, } -impl ToRealTime { +impl ToRealTime

{ async fn run(&mut self) { loop { let command_opt = self.commands_receiver.recv().await; @@ -135,7 +136,7 @@ impl ToRealTime { } } } - async fn handle_command(&mut self, command_opt: Option) -> bool { + async fn handle_command(&mut self, command_opt: Option>) -> bool { let command = command_opt.unwrap_or(Command::Shutdown); match command { Command::ConnectChannel{channel_index, source, latency_samples } => { @@ -163,18 +164,17 @@ impl ToRealTime { -struct PeriodicSamplesCollector { - commands_receiver: mpsc::Receiver, - channels: Vec>, +struct PeriodicSamplesCollector { + commands_receiver: mpsc::Receiver>, + channels: Vec>>, callback: SamplesCallback, } -fn get_min_max_end_timestamps<'a>(channels: impl IntoIterator>) -> Option<(Clock, Clock)> { +fn get_min_max_end_timestamps<'a, P: ProxyToSamplesBuffer + 'a>(channels: impl IntoIterator>>) -> Option<(Clock, Clock)> { let clocks = channels .into_iter() .filter_map(|opt| opt.as_ref()) .map(|ch| ch.source.readable_until()) - .filter_map(|opt| opt) .collect_vec(); Some(( clocks.iter().min_by(|&&a, &&b| wrapped_diff(a, b).cmp(&0))?.to_owned(), @@ -182,7 +182,7 @@ fn get_min_max_end_timestamps<'a>(channels: impl IntoIterator PeriodicSamplesCollector

{ fn get_min_max_end_timestamps(&self) -> Option<(Clock, Clock)> { get_min_max_end_timestamps(&self.channels) } @@ -230,13 +230,10 @@ impl PeriodicSamplesCollector { // or lag prevention logic triggers, // it would break our assumption that each channel has *at least* readable_samples_count readable. // that's why we need this check. - match ch.source.readable_until() { - Some(ts) => if wrapped_diff(ts, readable_until) < 0 { - None - } else { - Some(ch) - } - None => None + if wrapped_diff(ch.source.readable_until(), readable_until) < 0 { + None + } else { + Some(ch) } } else { None @@ -255,6 +252,11 @@ impl PeriodicSamplesCollector { readable_until } }; + for chi in 0..self.channels.len() { + if let Some(ch) = &mut self.channels[chi] { + ch.source.read_done(new_clock); + } + } clock = Some(new_clock); } } @@ -266,7 +268,7 @@ impl PeriodicSamplesCollector { } } } - async fn handle_command(&mut self, command_opt: Option) -> bool { + async fn handle_command(&mut self, command_opt: Option>) -> bool { let command = command_opt.unwrap_or(Command::Shutdown); match command { Command::ConnectChannel{channel_index, source, latency_samples } => { @@ -287,12 +289,11 @@ impl PeriodicSamplesCollector { } -pub struct SamplesCollector { - commands_sender: mpsc::Sender, +pub struct SamplesCollector { + commands_sender: mpsc::Sender>, } -impl SamplesCollector { - +impl SamplesCollector

{ pub fn new_with_callback( self_info: Arc, callback: SamplesCallback, @@ -306,7 +307,7 @@ impl SamplesCollector { return (Self { commands_sender: tx }, async move { internal.run().await }.boxed()); } - pub fn new_realtime(self_info: Arc, mut media_clock_receiver: broadcast::Receiver) -> (Self, Pin + Send + 'static>>, RealTimeSamplesReceiver) { + pub fn new_realtime(self_info: Arc, media_clock_receiver: broadcast::Receiver) -> (Self, Pin + Send + 'static>>, RealTimeSamplesReceiver

) { let (tx, rx) = mpsc::channel(100); let (senders, receivers) = (0..self_info.rx_channels.len()).map(|chi| { real_time_box_channel::channel(Box::new(None)) @@ -324,7 +325,11 @@ impl SamplesCollector { }) } - pub async fn connect_channel(&self, channel_index: usize, source: RBOutput, latency_samples: usize) { + /* pub fn new_external(self_info: Arc, external_channels: impl IntoIterator>) -> (Self, Pin + Send + 'static>>) { + + } */ + + pub async fn connect_channel(&self, channel_index: usize, source: RBOutput, latency_samples: usize) { self .commands_sender .send(Command::ConnectChannel { channel_index, source, latency_samples })