From 9bb2f0837d77ba22b282f0ade106146c3cccbc23 Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:20:13 +0300 Subject: [PATCH 01/15] tokio-test dev-dependencies is used for only webrtc-util --- Cargo.lock | 200 +++++++++++++++++++++++++----------------------- Cargo.toml | 3 - util/Cargo.toml | 2 +- 3 files changed, 104 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae9b3f0ff..8aca3503c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arc-swap" @@ -235,9 +235,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -245,7 +245,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -340,9 +340,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.37" +version = "1.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ "find-msvc-tools", "shlex", @@ -400,7 +400,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -458,18 +458,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstyle", "clap_lex 0.7.5", @@ -541,7 +541,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.47", + "clap 4.5.48", "criterion-plot", "futures", "is-terminal", @@ -691,9 +691,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", ] @@ -866,9 +866,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" [[package]] name = "fnv" @@ -1020,7 +1020,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.5+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] @@ -1035,9 +1035,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "group" @@ -1062,7 +1062,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.11.1", + "indexmap 2.11.4", "slab", "tokio", "tokio-util", @@ -1087,9 +1087,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hermit-abi" @@ -1331,12 +1331,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.1" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.16.0", ] [[package]] @@ -1444,9 +1444,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.78" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -1460,9 +1460,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "litemap" @@ -1498,9 +1498,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memoffset" @@ -1602,9 +1602,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -1906,9 +1906,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -2002,9 +2002,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", @@ -2014,9 +2014,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -2103,9 +2103,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" dependencies = [ "once_cell", "ring", @@ -2126,9 +2126,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.5" +version = "0.103.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8" +checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" dependencies = [ "ring", "rustls-pki-types", @@ -2189,24 +2189,34 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2215,14 +2225,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -2428,11 +2439,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.43" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", @@ -2715,27 +2727,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.5+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ "wasip2", ] [[package]] name = "wasip2" -version = "1.0.0+wasi-0.2.4" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -2746,9 +2758,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -2760,9 +2772,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2770,9 +2782,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -2783,18 +2795,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.101" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.78" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -3014,7 +3026,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.1", ] [[package]] @@ -3025,22 +3037,22 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.62.0" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.0", + "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" dependencies = [ "proc-macro2", "quote", @@ -3049,21 +3061,15 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.0" @@ -3076,7 +3082,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -3085,7 +3091,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -3112,16 +3118,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.4", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -3142,11 +3148,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" dependencies = [ - "windows-link 0.1.3", + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -3255,9 +3261,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "wit-bindgen" -version = "0.45.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" diff --git a/Cargo.toml b/Cargo.toml index 79ae8baf0..f38c32a89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,5 @@ members = [ ] resolver = "2" -[workspace.dependencies] -tokio-test = { version = "0.4.4" } - [profile.dev] opt-level = 0 diff --git a/util/Cargo.toml b/util/Cargo.toml index a68cc715d..aad744375 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -54,7 +54,7 @@ winapi = { version = "0.3.9", features = [ ] } [dev-dependencies] -tokio-test.workspace = true +tokio-test = { version = "0.4.4" } env_logger = "0.11.3" chrono = "0.4.28" criterion = { version = "0.5", features = ["async_futures"] } From 807c7c4a21cc095694c79fb3fe323fb4bfd1c029 Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Sun, 5 Oct 2025 12:47:50 +0300 Subject: [PATCH 02/15] =?UTF-8?q?=D0=98=D0=B7=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=D1=81=D1=8F=20=D0=BE=D1=82=20=D0=B0=D1=82=D1=82=D1=80?= =?UTF-8?q?=D0=B8=D0=B1=D1=83=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 + interceptor/src/lib.rs | 63 ++--- interceptor/src/mock/mock_stream.rs | 39 +-- .../src/nack/generator/generator_stream.rs | 10 +- interceptor/src/nack/generator/mod.rs | 7 +- interceptor/src/nack/responder/mod.rs | 14 +- .../src/nack/responder/responder_stream.rs | 6 +- interceptor/src/noop.rs | 13 +- interceptor/src/report/receiver/mod.rs | 10 +- .../src/report/receiver/receiver_stream.rs | 12 +- interceptor/src/report/sender/mod.rs | 3 +- .../src/report/sender/sender_stream.rs | 6 +- interceptor/src/stats/interceptor.rs | 31 +- interceptor/src/stream_reader.rs | 15 +- interceptor/src/twcc/receiver/mod.rs | 3 +- .../src/twcc/receiver/receiver_stream.rs | 10 +- interceptor/src/twcc/sender/mod.rs | 2 +- interceptor/src/twcc/sender/sender_stream.rs | 4 +- webrtc/Cargo.toml | 1 + webrtc/src/error.rs | 3 + webrtc/src/peer_connection/mod.rs | 5 +- .../peer_connection_internal.rs | 16 +- .../peer_connection/peer_connection_test.rs | 2 +- .../src/rtp_transceiver/rtp_receiver/mod.rs | 58 ++-- webrtc/src/rtp_transceiver/rtp_sender/mod.rs | 29 +- .../rtp_sender/rtp_sender_test.rs | 2 +- .../src/rtp_transceiver/srtp_writer_future.rs | 9 +- webrtc/src/track/track_local/mod.rs | 29 +- webrtc/src/track/track_local/packet_cache.rs | 79 ++++++ .../track_local/track_local_static_rtp.rs | 265 +++++++++++++++--- webrtc/src/track/track_remote/mod.rs | 33 +-- 31 files changed, 458 insertions(+), 323 deletions(-) create mode 100644 webrtc/src/track/track_local/packet_cache.rs diff --git a/Cargo.lock b/Cargo.lock index 8aca3503c..ccd13ff55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,6 +399,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -2819,6 +2820,7 @@ dependencies = [ "arc-swap", "async-trait", "bytes", + "chrono", "dtls", "env_logger", "hex", diff --git a/interceptor/src/lib.rs b/interceptor/src/lib.rs index 98008d405..55dda4130 100644 --- a/interceptor/src/lib.rs +++ b/interceptor/src/lib.rs @@ -79,14 +79,11 @@ pub trait Interceptor { #[async_trait] pub trait RTPWriter { /// write a rtp packet - async fn write(&self, pkt: &rtp::packet::Packet, attributes: &Attributes) -> Result; + async fn write(&self, pkt: &rtp::packet::Packet) -> Result; } pub type RTPWriterBoxFn = Box< - dyn (Fn( - &rtp::packet::Packet, - &Attributes, - ) -> Pin> + Send + Sync>>) + dyn (Fn(&rtp::packet::Packet) -> Pin> + Send + Sync>>) + Send + Sync, >; @@ -95,8 +92,8 @@ pub struct RTPWriterFn(pub RTPWriterBoxFn); #[async_trait] impl RTPWriter for RTPWriterFn { /// write a rtp packet - async fn write(&self, pkt: &rtp::packet::Packet, attributes: &Attributes) -> Result { - self.0(pkt, attributes).await + async fn write(&self, pkt: &rtp::packet::Packet) -> Result { + self.0(pkt).await } } @@ -104,19 +101,11 @@ impl RTPWriter for RTPWriterFn { #[async_trait] pub trait RTPReader { /// read a rtp packet - async fn read( - &self, - buf: &mut [u8], - attributes: &Attributes, - ) -> Result<(rtp::packet::Packet, Attributes)>; + async fn read(&self, buf: &mut [u8]) -> Result; } pub type RTPReaderBoxFn = Box< - dyn (Fn( - &mut [u8], - &Attributes, - ) - -> Pin> + Send + Sync>>) + dyn (Fn(&mut [u8]) -> Pin> + Send + Sync>>) + Send + Sync, >; @@ -125,12 +114,8 @@ pub struct RTPReaderFn(pub RTPReaderBoxFn); #[async_trait] impl RTPReader for RTPReaderFn { /// read a rtp packet - async fn read( - &self, - buf: &mut [u8], - attributes: &Attributes, - ) -> Result<(rtp::packet::Packet, Attributes)> { - self.0(buf, attributes).await + async fn read(&self, buf: &mut [u8]) -> Result { + self.0(buf).await } } @@ -138,17 +123,12 @@ impl RTPReader for RTPReaderFn { #[async_trait] pub trait RTCPWriter { /// write a batch of rtcp packets - async fn write( - &self, - pkts: &[Box], - attributes: &Attributes, - ) -> Result; + async fn write(&self, pkts: &[Box]) -> Result; } pub type RTCPWriterBoxFn = Box< dyn (Fn( &[Box], - &Attributes, ) -> Pin> + Send + Sync>>) + Send + Sync, @@ -159,12 +139,8 @@ pub struct RTCPWriterFn(pub RTCPWriterBoxFn); #[async_trait] impl RTCPWriter for RTCPWriterFn { /// write a batch of rtcp packets - async fn write( - &self, - pkts: &[Box], - attributes: &Attributes, - ) -> Result { - self.0(pkts, attributes).await + async fn write(&self, pkts: &[Box]) -> Result { + self.0(pkts).await } } @@ -175,22 +151,16 @@ pub trait RTCPReader { async fn read( &self, buf: &mut [u8], - attributes: &Attributes, - ) -> Result<(Vec>, Attributes)>; + ) -> Result>>; } pub type RTCPReaderBoxFn = Box< dyn (Fn( &mut [u8], - &Attributes, ) -> Pin< Box< - dyn Future< - Output = Result<( - Vec>, - Attributes, - )>, - > + Send + dyn Future>>> + + Send + Sync, >, >) + Send @@ -205,9 +175,8 @@ impl RTCPReader for RTCPReaderFn { async fn read( &self, buf: &mut [u8], - attributes: &Attributes, - ) -> Result<(Vec>, Attributes)> { - self.0(buf, attributes).await + ) -> Result>> { + self.0(buf).await } } diff --git a/interceptor/src/mock/mock_stream.rs b/interceptor/src/mock/mock_stream.rs index 436597e9d..b001d48a5 100644 --- a/interceptor/src/mock/mock_stream.rs +++ b/interceptor/src/mock/mock_stream.rs @@ -6,7 +6,7 @@ use util::Marshal; use crate::error::{Error, Result}; use crate::stream_info::StreamInfo; -use crate::{Attributes, Interceptor, RTCPReader, RTCPWriter, RTPReader, RTPWriter}; +use crate::{Interceptor, RTCPReader, RTCPWriter, RTPReader, RTPWriter}; type RTCPPackets = Vec>; @@ -91,10 +91,9 @@ impl MockStream { .await; tokio::spawn(async move { let mut buf = vec![0u8; 1500]; - let a = Attributes::new(); loop { - let pkts = match rtcp_reader.read(&mut buf, &a).await { - Ok((n, _)) => n, + let pkts = match rtcp_reader.read(&mut buf).await { + Ok(n) => n, Err(err) => { let _ = rtcp_in_modified_tx.send(Err(err)).await; break; @@ -113,10 +112,9 @@ impl MockStream { .await; tokio::spawn(async move { let mut buf = vec![0u8; 1500]; - let a = Attributes::new(); loop { - let pkt = match rtp_reader.read(&mut buf, &a).await { - Ok((pkt, _)) => pkt, + let pkt = match rtp_reader.read(&mut buf).await { + Ok(pkt) => pkt, Err(err) => { let _ = rtp_in_modified_tx.send(Err(err)).await; break; @@ -135,10 +133,9 @@ impl MockStream { &self, pkt: &[Box], ) -> Result { - let a = Attributes::new(); let rtcp_writer = self.rtcp_writer.lock().await; if let Some(writer) = &*rtcp_writer { - writer.write(pkt, &a).await + writer.write(pkt).await } else { Err(Error::Other("invalid rtcp_writer".to_owned())) } @@ -146,10 +143,9 @@ impl MockStream { /// write_rtp writes an rtp packet to the stream, using the interceptor pub async fn write_rtp(&self, pkt: &rtp::packet::Packet) -> Result { - let a = Attributes::new(); let rtp_writer = self.rtp_writer.lock().await; if let Some(writer) = &*rtp_writer { - writer.write(pkt, &a).await + writer.write(pkt).await } else { Err(Error::Other("invalid rtp_writer".to_owned())) } @@ -229,11 +225,7 @@ impl MockStream { #[async_trait] impl RTCPWriter for MockStreamInternal { - async fn write( - &self, - pkts: &[Box], - _attributes: &Attributes, - ) -> Result { + async fn write(&self, pkts: &[Box]) -> Result { let _ = self.rtcp_out_modified_tx.send(pkts.to_vec()).await; Ok(0) @@ -245,8 +237,7 @@ impl RTCPReader for MockStreamInternal { async fn read( &self, buf: &mut [u8], - a: &Attributes, - ) -> Result<(Vec>, Attributes)> { + ) -> Result>> { let pkts = { let mut rtcp_in = self.rtcp_in_rx.lock().await; rtcp_in.recv().await.ok_or(Error::ErrIoEOF)? @@ -259,13 +250,13 @@ impl RTCPReader for MockStreamInternal { } buf[..n].copy_from_slice(&marshaled); - Ok((pkts, a.clone())) + Ok(pkts) } } #[async_trait] impl RTPWriter for MockStreamInternal { - async fn write(&self, pkt: &rtp::packet::Packet, _a: &Attributes) -> Result { + async fn write(&self, pkt: &rtp::packet::Packet) -> Result { let _ = self.rtp_out_modified_tx.send(pkt.clone()).await; Ok(0) } @@ -273,11 +264,7 @@ impl RTPWriter for MockStreamInternal { #[async_trait] impl RTPReader for MockStreamInternal { - async fn read( - &self, - buf: &mut [u8], - a: &Attributes, - ) -> Result<(rtp::packet::Packet, Attributes)> { + async fn read(&self, buf: &mut [u8]) -> Result { let pkt = { let mut rtp_in = self.rtp_in_rx.lock().await; rtp_in.recv().await.ok_or(Error::ErrIoEOF)? @@ -290,7 +277,7 @@ impl RTPReader for MockStreamInternal { } buf[..n].copy_from_slice(&marshaled); - Ok((pkt, a.clone())) + Ok(pkt) } } diff --git a/interceptor/src/nack/generator/generator_stream.rs b/interceptor/src/nack/generator/generator_stream.rs index 8cd5a108c..ce8930e83 100644 --- a/interceptor/src/nack/generator/generator_stream.rs +++ b/interceptor/src/nack/generator/generator_stream.rs @@ -150,16 +150,12 @@ impl GeneratorStream { #[async_trait] impl RTPReader for GeneratorStream { /// read a rtp packet - async fn read( - &self, - buf: &mut [u8], - a: &Attributes, - ) -> Result<(rtp::packet::Packet, Attributes)> { - let (pkt, attr) = self.parent_rtp_reader.read(buf, a).await?; + async fn read(&self, buf: &mut [u8]) -> Result { + let pkt = self.parent_rtp_reader.read(buf).await?; self.add(pkt.header.sequence_number); - Ok((pkt, attr)) + Ok(pkt) } } diff --git a/interceptor/src/nack/generator/mod.rs b/interceptor/src/nack/generator/mod.rs index 16b665879..f1942c14e 100644 --- a/interceptor/src/nack/generator/mod.rs +++ b/interceptor/src/nack/generator/mod.rs @@ -17,9 +17,7 @@ use waitgroup::WaitGroup; use crate::error::{Error, Result}; use crate::nack::stream_support_nack; use crate::stream_info::StreamInfo; -use crate::{ - Attributes, Interceptor, InterceptorBuilder, RTCPReader, RTCPWriter, RTPReader, RTPWriter, -}; +use crate::{Interceptor, InterceptorBuilder, RTCPReader, RTCPWriter, RTPReader, RTPWriter}; /// GeneratorBuilder can be used to configure Generator Interceptor #[derive(Default)] @@ -136,9 +134,8 @@ impl Generator { nacks }; - let a = Attributes::new(); for nack in nacks{ - if let Err(err) = rtcp_writer.write(&[Box::new(nack)], &a).await{ + if let Err(err) = rtcp_writer.write(&[Box::new(nack)]).await{ log::warn!("failed sending nack: {err}"); } } diff --git a/interceptor/src/nack/responder/mod.rs b/interceptor/src/nack/responder/mod.rs index fa54a694a..80f08e779 100644 --- a/interceptor/src/nack/responder/mod.rs +++ b/interceptor/src/nack/responder/mod.rs @@ -15,9 +15,7 @@ use tokio::sync::Mutex; use crate::error::Result; use crate::nack::stream_support_nack; use crate::stream_info::StreamInfo; -use crate::{ - Attributes, Interceptor, InterceptorBuilder, RTCPReader, RTCPWriter, RTPReader, RTPWriter, -}; +use crate::{Interceptor, InterceptorBuilder, RTCPReader, RTCPWriter, RTPReader, RTPWriter}; /// GeneratorBuilder can be used to configure Responder Interceptor #[derive(Default)] @@ -73,8 +71,7 @@ impl ResponderInternal { let stream3 = Arc::clone(&stream2); Box::pin(async move { if let Some(p) = stream3.get(seq).await { - let a = Attributes::new(); - if let Err(err) = stream3.next_rtp_writer.write(&p, &a).await { + if let Err(err) = stream3.next_rtp_writer.write(&p).await { log::warn!("failed resending nacked packet: {err}"); } } @@ -101,9 +98,8 @@ impl RTCPReader for ResponderRtcpReader { async fn read( &self, buf: &mut [u8], - a: &Attributes, - ) -> Result<(Vec>, Attributes)> { - let (pkts, attr) = { self.parent_rtcp_reader.read(buf, a).await? }; + ) -> Result>> { + let pkts = { self.parent_rtcp_reader.read(buf).await? }; for p in &pkts { if let Some(nack) = p.as_any().downcast_ref::() { let nack = nack.clone(); @@ -114,7 +110,7 @@ impl RTCPReader for ResponderRtcpReader { } } - Ok((pkts, attr)) + Ok(pkts) } } diff --git a/interceptor/src/nack/responder/responder_stream.rs b/interceptor/src/nack/responder/responder_stream.rs index ec714da6d..9ad6cb50f 100644 --- a/interceptor/src/nack/responder/responder_stream.rs +++ b/interceptor/src/nack/responder/responder_stream.rs @@ -5,7 +5,7 @@ use tokio::sync::Mutex; use crate::error::Result; use crate::nack::UINT16SIZE_HALF; -use crate::{Attributes, RTPWriter}; +use crate::RTPWriter; struct ResponderStreamInternal { packets: Vec>, @@ -90,10 +90,10 @@ impl ResponderStream { #[async_trait] impl RTPWriter for ResponderStream { /// write a rtp packet - async fn write(&self, pkt: &rtp::packet::Packet, a: &Attributes) -> Result { + async fn write(&self, pkt: &rtp::packet::Packet) -> Result { self.add(pkt).await; - self.next_rtp_writer.write(pkt, a).await + self.next_rtp_writer.write(pkt).await } } diff --git a/interceptor/src/noop.rs b/interceptor/src/noop.rs index 597c57a8f..c6ff1b216 100644 --- a/interceptor/src/noop.rs +++ b/interceptor/src/noop.rs @@ -59,12 +59,8 @@ impl Interceptor for NoOp { #[async_trait] impl RTPReader for NoOp { - async fn read( - &self, - _buf: &mut [u8], - a: &Attributes, - ) -> Result<(rtp::packet::Packet, Attributes)> { - Ok((rtp::packet::Packet::default(), a.clone())) + async fn read(&self, _buf: &mut [u8]) -> Result { + Ok(rtp::packet::Packet::default()) } } @@ -73,8 +69,7 @@ impl RTCPReader for NoOp { async fn read( &self, _buf: &mut [u8], - a: &Attributes, - ) -> Result<(Vec>, Attributes)> { - Ok((vec![], a.clone())) + ) -> Result>> { + Ok(vec![]) } } diff --git a/interceptor/src/report/receiver/mod.rs b/interceptor/src/report/receiver/mod.rs index 5a5e21579..fd80227c8 100644 --- a/interceptor/src/report/receiver/mod.rs +++ b/interceptor/src/report/receiver/mod.rs @@ -30,9 +30,8 @@ impl RTCPReader for ReceiverReportRtcpReader { async fn read( &self, buf: &mut [u8], - a: &Attributes, - ) -> Result<(Vec>, Attributes)> { - let (pkts, attr) = self.parent_rtcp_reader.read(buf, a).await?; + ) -> Result>> { + let pkts = self.parent_rtcp_reader.read(buf).await?; let now = if let Some(f) = &self.internal.now { f() @@ -55,7 +54,7 @@ impl RTCPReader for ReceiverReportRtcpReader { } } - Ok((pkts, attr)) + Ok(pkts) } } @@ -112,8 +111,7 @@ impl ReceiverReport { for stream in streams { let pkt = stream.generate_report(now); - let a = Attributes::new(); - if let Err(err) = rtcp_writer.write(&[Box::new(pkt)], &a).await{ + if let Err(err) = rtcp_writer.write(&[Box::new(pkt)]).await{ log::warn!("failed sending: {err}"); } } diff --git a/interceptor/src/report/receiver/receiver_stream.rs b/interceptor/src/report/receiver/receiver_stream.rs index d170922e8..741da9258 100644 --- a/interceptor/src/report/receiver/receiver_stream.rs +++ b/interceptor/src/report/receiver/receiver_stream.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use util::sync::Mutex; use super::*; -use crate::{Attributes, RTPReader}; +use crate::RTPReader; struct ReceiverStreamInternal { ssrc: u32, @@ -208,12 +208,8 @@ impl ReceiverStream { #[async_trait] impl RTPReader for ReceiverStream { /// read a rtp packet - async fn read( - &self, - buf: &mut [u8], - a: &Attributes, - ) -> Result<(rtp::packet::Packet, Attributes)> { - let (pkt, attr) = self.parent_rtp_reader.read(buf, a).await?; + async fn read(&self, buf: &mut [u8]) -> Result { + let pkt = self.parent_rtp_reader.read(buf).await?; let now = if let Some(f) = &self.now { f() @@ -222,6 +218,6 @@ impl RTPReader for ReceiverStream { }; self.process_rtp(now, &pkt); - Ok((pkt, attr)) + Ok(pkt) } } diff --git a/interceptor/src/report/sender/mod.rs b/interceptor/src/report/sender/mod.rs index f46c54933..94c7de630 100644 --- a/interceptor/src/report/sender/mod.rs +++ b/interceptor/src/report/sender/mod.rs @@ -72,8 +72,7 @@ impl SenderReport { for stream in streams { let pkt = stream.generate_report(now).await; - let a = Attributes::new(); - if let Err(err) = rtcp_writer.write(&[Box::new(pkt)], &a).await{ + if let Err(err) = rtcp_writer.write(&[Box::new(pkt)]).await{ log::warn!("failed sending: {err}"); } } diff --git a/interceptor/src/report/sender/sender_stream.rs b/interceptor/src/report/sender/sender_stream.rs index df0602afa..446ae40fc 100644 --- a/interceptor/src/report/sender/sender_stream.rs +++ b/interceptor/src/report/sender/sender_stream.rs @@ -7,7 +7,7 @@ use rtp::extension::abs_send_time_extension::unix2ntp; use tokio::sync::Mutex; use super::*; -use crate::{Attributes, RTPWriter}; +use crate::RTPWriter; struct SenderStreamInternal { ssrc: u32, @@ -92,7 +92,7 @@ impl SenderStream { #[async_trait] impl RTPWriter for SenderStream { /// write a rtp packet - async fn write(&self, pkt: &rtp::packet::Packet, a: &Attributes) -> Result { + async fn write(&self, pkt: &rtp::packet::Packet) -> Result { let now = if let Some(f) = &self.now { f() } else { @@ -100,7 +100,7 @@ impl RTPWriter for SenderStream { }; self.process_rtp(now, pkt).await; - self.next_rtp_writer.write(pkt, a).await + self.next_rtp_writer.write(pkt).await } } diff --git a/interceptor/src/stats/interceptor.rs b/interceptor/src/stats/interceptor.rs index a80af97ef..df2cbcef0 100644 --- a/interceptor/src/stats/interceptor.rs +++ b/interceptor/src/stats/interceptor.rs @@ -19,7 +19,7 @@ use util::MarshalSize; use super::{inbound, outbound, StatsContainer}; use crate::error::Result; use crate::stream_info::StreamInfo; -use crate::{Attributes, Interceptor, RTCPReader, RTCPWriter, RTPReader, RTPWriter}; +use crate::{Interceptor, RTCPReader, RTCPWriter, RTPReader, RTPWriter}; #[derive(Debug)] enum Message { @@ -390,9 +390,8 @@ where async fn read( &self, buf: &mut [u8], - attributes: &Attributes, - ) -> Result<(Vec>, Attributes)> { - let (pkts, attributes) = self.rtcp_reader.read(buf, attributes).await?; + ) -> Result>> { + let pkts = self.rtcp_reader.read(buf).await?; // Middle 32 bits let now = (unix2ntp((self.now_gen)()) >> 16) as u32; @@ -595,7 +594,7 @@ where futures::future::join_all(futures).await; } - Ok((pkts, attributes)) + Ok(pkts) } } @@ -610,11 +609,7 @@ impl RTCPWriter for RTCPWriteInterceptor where F: Fn() -> SystemTime + Send + Sync, { - async fn write( - &self, - pkts: &[Box], - attributes: &Attributes, - ) -> Result { + async fn write(&self, pkts: &[Box]) -> Result { #[derive(Default, Debug)] struct Entry { fir_count: Option, @@ -691,7 +686,7 @@ where } } - self.rtcp_writer.write(pkts, attributes).await + self.rtcp_writer.write(pkts).await } } @@ -714,12 +709,8 @@ impl fmt::Debug for RTPReadRecorder { #[async_trait] impl RTPReader for RTPReadRecorder { - async fn read( - &self, - buf: &mut [u8], - attributes: &Attributes, - ) -> Result<(rtp::packet::Packet, Attributes)> { - let (pkt, attributes) = self.rtp_reader.read(buf, attributes).await?; + async fn read(&self, buf: &mut [u8]) -> Result { + let pkt = self.rtp_reader.read(buf).await?; let _ = self .tx @@ -734,7 +725,7 @@ impl RTPReader for RTPReadRecorder { }) .await; - Ok((pkt, attributes)) + Ok(pkt) } } @@ -758,8 +749,8 @@ impl fmt::Debug for RTPWriteRecorder { #[async_trait] impl RTPWriter for RTPWriteRecorder { /// write a rtp packet - async fn write(&self, pkt: &rtp::packet::Packet, attributes: &Attributes) -> Result { - let n = self.rtp_writer.write(pkt, attributes).await?; + async fn write(&self, pkt: &rtp::packet::Packet) -> Result { + let n = self.rtp_writer.write(pkt).await?; let _ = self .tx diff --git a/interceptor/src/stream_reader.rs b/interceptor/src/stream_reader.rs index 4f4578631..80882bb73 100644 --- a/interceptor/src/stream_reader.rs +++ b/interceptor/src/stream_reader.rs @@ -2,16 +2,12 @@ use async_trait::async_trait; use srtp::stream::Stream; use crate::error::Result; -use crate::{Attributes, RTCPReader, RTPReader}; +use crate::{RTCPReader, RTPReader}; #[async_trait] impl RTPReader for Stream { - async fn read( - &self, - buf: &mut [u8], - a: &Attributes, - ) -> Result<(rtp::packet::Packet, Attributes)> { - Ok((self.read_rtp(buf).await?, a.clone())) + async fn read(&self, buf: &mut [u8]) -> Result { + Ok(self.read_rtp(buf).await?) } } @@ -20,8 +16,7 @@ impl RTCPReader for Stream { async fn read( &self, buf: &mut [u8], - a: &Attributes, - ) -> Result<(Vec>, Attributes)> { - Ok((self.read_rtcp(buf).await?, a.clone())) + ) -> Result>> { + Ok(self.read_rtcp(buf).await?) } } diff --git a/interceptor/src/twcc/receiver/mod.rs b/interceptor/src/twcc/receiver/mod.rs index 3126122e8..be8abb05b 100644 --- a/interceptor/src/twcc/receiver/mod.rs +++ b/interceptor/src/twcc/receiver/mod.rs @@ -113,7 +113,6 @@ impl Receiver { } }; - let a = Attributes::new(); let mut ticker = tokio::time::interval(internal.interval); ticker.set_missed_tick_behavior(MissedTickBehavior::Skip); loop { @@ -138,7 +137,7 @@ impl Receiver { continue; } - if let Err(err) = rtcp_writer.write(&pkts, &a).await{ + if let Err(err) = rtcp_writer.write(&pkts).await{ log::error!("rtcp_writer.write got err: {err}"); } } diff --git a/interceptor/src/twcc/receiver/receiver_stream.rs b/interceptor/src/twcc/receiver/receiver_stream.rs index 764b26c9d..0df8ce14f 100644 --- a/interceptor/src/twcc/receiver/receiver_stream.rs +++ b/interceptor/src/twcc/receiver/receiver_stream.rs @@ -30,12 +30,8 @@ impl ReceiverStream { #[async_trait] impl RTPReader for ReceiverStream { /// read a rtp packet - async fn read( - &self, - buf: &mut [u8], - attributes: &Attributes, - ) -> Result<(rtp::packet::Packet, Attributes)> { - let (pkt, attr) = self.parent_rtp_reader.read(buf, attributes).await?; + async fn read(&self, buf: &mut [u8]) -> Result { + let pkt = self.parent_rtp_reader.read(buf).await?; if let Some(mut ext) = pkt.header.get_extension(self.hdr_ext_id) { let tcc_ext = TransportCcExtension::unmarshal(&mut ext)?; @@ -52,6 +48,6 @@ impl RTPReader for ReceiverStream { .await; } - Ok((pkt, attr)) + Ok(pkt) } } diff --git a/interceptor/src/twcc/sender/mod.rs b/interceptor/src/twcc/sender/mod.rs index d3ed5673d..d9afd4fd2 100644 --- a/interceptor/src/twcc/sender/mod.rs +++ b/interceptor/src/twcc/sender/mod.rs @@ -11,7 +11,7 @@ use sender_stream::SenderStream; use tokio::sync::Mutex; use util::Marshal; -use crate::{Attributes, RTPWriter, *}; +use crate::{RTPWriter, *}; pub(crate) const TRANSPORT_CC_URI: &str = "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"; diff --git a/interceptor/src/twcc/sender/sender_stream.rs b/interceptor/src/twcc/sender/sender_stream.rs index 29754070d..132a18bdf 100644 --- a/interceptor/src/twcc/sender/sender_stream.rs +++ b/interceptor/src/twcc/sender/sender_stream.rs @@ -24,7 +24,7 @@ impl SenderStream { #[async_trait] impl RTPWriter for SenderStream { /// write a rtp packet - async fn write(&self, pkt: &rtp::packet::Packet, a: &Attributes) -> Result { + async fn write(&self, pkt: &rtp::packet::Packet) -> Result { let sequence_number = self.next_sequence_nr.fetch_add(1, Ordering::SeqCst); let tcc_ext = TransportCcExtension { @@ -35,6 +35,6 @@ impl RTPWriter for SenderStream { let mut pkt = pkt.clone(); pkt.header.set_extension(self.hdr_ext_id, tcc_payload)?; - self.next_rtp_writer.write(&pkt, a).await + self.next_rtp_writer.write(&pkt).await } } diff --git a/webrtc/Cargo.toml b/webrtc/Cargo.toml index 93f5ce05a..e9930e7ef 100644 --- a/webrtc/Cargo.toml +++ b/webrtc/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/webrtc-rs/webrtc" readme = "../README.md" [dependencies] +chrono = { version = "0.4.40", features = ["serde"] } data = { version = "0.12.0", path = "../data", package = "webrtc-data" } dtls = { version = "0.13.0", path = "../dtls" } ice = { version = "0.14.0", path = "../ice", package = "webrtc-ice" } diff --git a/webrtc/src/error.rs b/webrtc/src/error.rs index 22b712bfb..039f0c916 100644 --- a/webrtc/src/error.rs +++ b/webrtc/src/error.rs @@ -389,6 +389,9 @@ pub enum Error { #[error("SCTP is not established")] ErrSCTPNotEstablished, + #[error("LocalTrack binding not found")] + LocalTrackBindingNotFound, + #[error("DataChannel is not opened")] ErrClosedPipe, #[error("Interceptor is not bind")] diff --git a/webrtc/src/peer_connection/mod.rs b/webrtc/src/peer_connection/mod.rs index bed6f9f81..41a2c7a51 100644 --- a/webrtc/src/peer_connection/mod.rs +++ b/webrtc/src/peer_connection/mod.rs @@ -27,7 +27,7 @@ use ::sdp::description::session::*; use ::sdp::util::ConnectionRole; use arc_swap::ArcSwapOption; use async_trait::async_trait; -use interceptor::{stats, Attributes, Interceptor, RTCPWriter}; +use interceptor::{stats, Interceptor, RTCPWriter}; use peer_connection_internal::*; use portable_atomic::{AtomicBool, AtomicU64, AtomicU8}; use rand::{rng, Rng}; @@ -1917,8 +1917,7 @@ impl RTCPeerConnection { &self, pkts: &[Box], ) -> Result { - let a = Attributes::new(); - Ok(self.interceptor_rtcp_writer.write(pkts, &a).await?) + Ok(self.interceptor_rtcp_writer.write(pkts).await?) } /// close ends the PeerConnection diff --git a/webrtc/src/peer_connection/peer_connection_internal.rs b/webrtc/src/peer_connection/peer_connection_internal.rs index 722a39e4b..a1421be81 100644 --- a/webrtc/src/peer_connection/peer_connection_internal.rs +++ b/webrtc/src/peer_connection/peer_connection_internal.rs @@ -1097,20 +1097,18 @@ impl PeerConnectionInternal { // Packets that we read as part of simulcast probing that we need to make available // if we do find a track later. - let mut buffered_packets: VecDeque<(rtp::packet::Packet, Attributes)> = VecDeque::default(); + let mut buffered_packets: VecDeque = VecDeque::default(); let mut buf = vec![0u8; self.setting_engine.get_receive_mtu()]; for _ in 0..=SIMULCAST_PROBE_COUNT { - let (pkt, a) = rtp_interceptor - .read(&mut buf, &stream_info.attributes) - .await?; + let pkt = rtp_interceptor.read(&mut buf).await?; let (mid, rid, rsid) = get_stream_mid_rid( &pkt.header, mid_extension_id as u8, sid_extension_id as u8, rsid_extension_id as u8, )?; - buffered_packets.push_back((pkt, a.clone())); + buffered_packets.push_back(pkt); if mid.is_empty() || (rid.is_empty() && rsid.is_empty()) { continue; @@ -1192,7 +1190,7 @@ impl PeerConnectionInternal { tokio::spawn(async move { let mut b = vec![0u8; receive_mtu]; let pkt = match track.peek(&mut b).await { - Ok((pkt, _)) => pkt, + Ok(pkt) => pkt, Err(err) => { log::warn!( "Could not determine PayloadType for SSRC {} ({})", @@ -1531,11 +1529,7 @@ type IResult = std::result::Result; #[async_trait] impl RTCPWriter for PeerConnectionInternal { - async fn write( - &self, - pkts: &[Box], - _a: &Attributes, - ) -> IResult { + async fn write(&self, pkts: &[Box]) -> IResult { Ok(self.dtls_transport.write_rtcp(pkts).await?) } } diff --git a/webrtc/src/peer_connection/peer_connection_test.rs b/webrtc/src/peer_connection/peer_connection_test.rs index cb001fe4b..32d06314c 100644 --- a/webrtc/src/peer_connection/peer_connection_test.rs +++ b/webrtc/src/peer_connection/peer_connection_test.rs @@ -315,7 +315,7 @@ async fn test_get_stats() -> Result<()> { pc_answer.on_track(Box::new(move |track, _, _| { let packet_tx = packet_tx.clone(); tokio::spawn(async move { - while let Ok((pkt, _)) = track.read_rtp().await { + while let Ok(pkt) = track.read_rtp().await { dbg!(&pkt); let last = pkt.payload[pkt.payload.len() - 1]; diff --git a/webrtc/src/rtp_transceiver/rtp_receiver/mod.rs b/webrtc/src/rtp_transceiver/rtp_receiver/mod.rs index 97306a4a0..077f64751 100644 --- a/webrtc/src/rtp_transceiver/rtp_receiver/mod.rs +++ b/webrtc/src/rtp_transceiver/rtp_receiver/mod.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use arc_swap::ArcSwapOption; use interceptor::stream_info::{AssociatedStreamInfo, RTPHeaderExtension}; -use interceptor::{Attributes, Interceptor}; +use interceptor::Interceptor; use log::trace; use smol_str::SmolStr; use tokio::sync::{watch, Mutex, RwLock}; @@ -162,10 +162,7 @@ pub struct RTPReceiverInternal { impl RTPReceiverInternal { /// read reads incoming RTCP for this RTPReceiver - async fn read( - &self, - b: &mut [u8], - ) -> Result<(Vec>, Attributes)> { + async fn read(&self, b: &mut [u8]) -> Result>> { let mut state_watch_rx = self.state_tx.subscribe(); // Ensure we are running or paused. When paused we still receive RTCP even if RTP traffic // isn't flowing. @@ -174,13 +171,12 @@ impl RTPReceiverInternal { let tracks = self.tracks.read().await; if let Some(t) = tracks.first() { if let Some(rtcp_interceptor) = &t.stream.rtcp_interceptor { - let a = Attributes::new(); loop { tokio::select! { res = State::error_on_close(&mut state_watch_rx) => { res? } - result = rtcp_interceptor.read(b, &a) => { + result = rtcp_interceptor.read(b) => { return Ok(result?) } } @@ -198,7 +194,7 @@ impl RTPReceiverInternal { &self, b: &mut [u8], rid: &str, - ) -> Result<(Vec>, Attributes)> { + ) -> Result>> { let mut state_watch_rx = self.state_tx.subscribe(); // Ensure we are running or paused. When paused we still receive RTCP even if RTP traffic @@ -209,14 +205,12 @@ impl RTPReceiverInternal { for t in &*tracks { if t.track.rid() == rid { if let Some(rtcp_interceptor) = &t.stream.rtcp_interceptor { - let a = Attributes::new(); - loop { tokio::select! { res = State::error_on_close(&mut state_watch_rx) => { res? } - result = rtcp_interceptor.read(b, &a) => { + result = rtcp_interceptor.read(b) => { return Ok(result?); } } @@ -234,11 +228,11 @@ impl RTPReceiverInternal { async fn read_rtcp( &self, receive_mtu: usize, - ) -> Result<(Vec>, Attributes)> { + ) -> Result>> { let mut b = vec![0u8; receive_mtu]; - let (pkts, attributes) = self.read(&mut b).await?; + let pkts = self.read(&mut b).await?; - Ok((pkts, attributes)) + Ok(pkts) } /// read_simulcast_rtcp is a convenience method that wraps ReadSimulcast and unmarshal for you @@ -246,18 +240,14 @@ impl RTPReceiverInternal { &self, rid: &str, receive_mtu: usize, - ) -> Result<(Vec>, Attributes)> { + ) -> Result>> { let mut b = vec![0u8; receive_mtu]; - let (pkts, attributes) = self.read_simulcast(&mut b, rid).await?; + let pkts = self.read_simulcast(&mut b, rid).await?; - Ok((pkts, attributes)) + Ok(pkts) } - pub(crate) async fn read_rtp( - &self, - b: &mut [u8], - tid: usize, - ) -> Result<(rtp::packet::Packet, Attributes)> { + pub(crate) async fn read_rtp(&self, b: &mut [u8], tid: usize) -> Result { let mut state_watch_rx = self.state_tx.subscribe(); // Ensure we are running. @@ -283,7 +273,6 @@ impl RTPReceiverInternal { );*/ if let Some(rtp_interceptor) = rtp_interceptor { - let a = Attributes::new(); //println!( // "read_rtp rtp_interceptor.read enter with tid {} ssrc {}", // tid, ssrc @@ -299,11 +288,11 @@ impl RTPReceiverInternal { } current_state = new_state; } - result = rtp_interceptor.read(b, &a) => { + result = rtp_interceptor.read(b) => { let result = result?; if current_state == State::Paused { - trace!("Dropping {} read bytes received while RTPReceiver was paused", result.0); + trace!("Dropping {} read bytes received while RTPReceiver was paused", result); continue; } return Ok(result); @@ -630,7 +619,7 @@ impl RTCRtpReceiver { pub async fn read( &self, b: &mut [u8], - ) -> Result<(Vec>, Attributes)> { + ) -> Result>> { self.internal.read(b).await } @@ -639,15 +628,13 @@ impl RTCRtpReceiver { &self, b: &mut [u8], rid: &str, - ) -> Result<(Vec>, Attributes)> { + ) -> Result>> { self.internal.read_simulcast(b, rid).await } /// read_rtcp is a convenience method that wraps Read and unmarshal for you. /// It also runs any configured interceptors. - pub async fn read_rtcp( - &self, - ) -> Result<(Vec>, Attributes)> { + pub async fn read_rtcp(&self) -> Result>> { self.internal.read_rtcp(self.receive_mtu).await } @@ -655,7 +642,7 @@ impl RTCRtpReceiver { pub async fn read_simulcast_rtcp( &self, rid: &str, - ) -> Result<(Vec>, Attributes)> { + ) -> Result>> { self.internal .read_simulcast_rtcp(rid, self.receive_mtu) .await @@ -755,11 +742,7 @@ impl RTCRtpReceiver { } /// read_rtp should only be called by a track, this only exists so we can keep state in one place - pub(crate) async fn read_rtp( - &self, - b: &mut [u8], - tid: usize, - ) -> Result<(rtp::packet::Packet, Attributes)> { + pub(crate) async fn read_rtp(&self, b: &mut [u8], tid: usize) -> Result { self.internal.read_rtp(b, tid).await } @@ -807,12 +790,11 @@ impl RTCRtpReceiver { let receive_mtu = self.receive_mtu; let track = t.clone(); tokio::spawn(async move { - let a = Attributes::new(); let mut b = vec![0u8; receive_mtu]; while let Some(repair_rtp_interceptor) = &track.repair_stream.rtp_interceptor { //TODO: cancel repair_rtp_interceptor.read gracefully //println!("repair_rtp_interceptor read begin with ssrc={}", ssrc); - if repair_rtp_interceptor.read(&mut b, &a).await.is_err() { + if repair_rtp_interceptor.read(&mut b).await.is_err() { break; } } diff --git a/webrtc/src/rtp_transceiver/rtp_sender/mod.rs b/webrtc/src/rtp_transceiver/rtp_sender/mod.rs index d1e35d18f..f2a46ad5d 100644 --- a/webrtc/src/rtp_transceiver/rtp_sender/mod.rs +++ b/webrtc/src/rtp_transceiver/rtp_sender/mod.rs @@ -6,7 +6,7 @@ use std::sync::{Arc, Weak}; use ice::rand::generate_crypto_random_string; use interceptor::stream_info::{AssociatedStreamInfo, StreamInfo}; -use interceptor::{Attributes, Interceptor, RTCPReader, RTPWriter}; +use interceptor::{Interceptor, RTCPReader, RTPWriter}; use portable_atomic::AtomicBool; use tokio::select; use tokio::sync::{watch, Mutex, Notify}; @@ -539,11 +539,10 @@ impl RTCRtpSender { let stop_called_rx = self.internal.stop_called_rx.clone(); tokio::spawn(async move { - let attrs = Attributes::new(); let mut b = vec![0u8; receive_mtu]; while !stop_called_signal.load(Ordering::SeqCst) { select! { - r = rtcp_reader.read(&mut b, &attrs) => { + r = rtcp_reader.read(&mut b) => { if r.is_err() { break } @@ -591,17 +590,16 @@ impl RTCRtpSender { pub async fn read( &self, b: &mut [u8], - ) -> Result<(Vec>, Attributes)> { + ) -> Result>> { tokio::select! { _ = self.wait_for_send() => { let rtcp_interceptor = { let track_encodings = self.track_encodings.lock().await; track_encodings.first().map(|e|e.rtcp_interceptor.clone()) }.ok_or(Error::ErrInterceptorNotBind)?; - let a = Attributes::new(); tokio::select! { _ = self.internal.stop_called_rx.notified() => Err(Error::ErrClosedPipe), - result = rtcp_interceptor.read(b, &a) => Ok(result?), + result = rtcp_interceptor.read(b) => Ok(result?), } } _ = self.internal.stop_called_rx.notified() => Err(Error::ErrClosedPipe), @@ -609,13 +607,11 @@ impl RTCRtpSender { } /// read_rtcp is a convenience method that wraps Read and unmarshals for you. - pub async fn read_rtcp( - &self, - ) -> Result<(Vec>, Attributes)> { + pub async fn read_rtcp(&self) -> Result>> { let mut b = vec![0u8; self.receive_mtu]; - let (pkts, attributes) = self.read(&mut b).await?; + let pkts = self.read(&mut b).await?; - Ok((pkts, attributes)) + Ok(pkts) } /// ReadSimulcast reads incoming RTCP for this RTPSender for given rid @@ -623,17 +619,16 @@ impl RTCRtpSender { &self, b: &mut [u8], rid: &str, - ) -> Result<(Vec>, Attributes)> { + ) -> Result>> { tokio::select! { _ = self.wait_for_send() => { let rtcp_interceptor = { let track_encodings = self.track_encodings.lock().await; track_encodings.iter().find(|e| e.track.rid() == Some(rid)).map(|e| e.rtcp_interceptor.clone()) }.ok_or(Error::ErrRTPSenderNoTrackForRID)?; - let a = Attributes::new(); tokio::select! { _ = self.internal.stop_called_rx.notified() => Err(Error::ErrClosedPipe), - result = rtcp_interceptor.read(b, &a) => Ok(result?), + result = rtcp_interceptor.read(b) => Ok(result?), } } _ = self.internal.stop_called_rx.notified() => Err(Error::ErrClosedPipe), @@ -644,11 +639,11 @@ impl RTCRtpSender { pub async fn read_rtcp_simulcast( &self, rid: &str, - ) -> Result<(Vec>, Attributes)> { + ) -> Result>> { let mut b = vec![0u8; self.receive_mtu]; - let (pkts, attributes) = self.read_simulcast(&mut b, rid).await?; + let pkts = self.read_simulcast(&mut b, rid).await?; - Ok((pkts, attributes)) + Ok(pkts) } /// Enables overriding outgoing `RTP` packets' `sequence number`s. diff --git a/webrtc/src/rtp_transceiver/rtp_sender/rtp_sender_test.rs b/webrtc/src/rtp_transceiver/rtp_sender/rtp_sender_test.rs index 9feda0428..d865d7c53 100644 --- a/webrtc/src/rtp_transceiver/rtp_sender/rtp_sender_test.rs +++ b/webrtc/src/rtp_transceiver/rtp_sender/rtp_sender_test.rs @@ -72,7 +72,7 @@ async fn test_rtp_sender_replace_track() -> Result<()> { let seen_packet_b_tx2 = Arc::clone(&seen_packet_b_tx); Box::pin(async move { let pkt = match track.read_rtp().await { - Ok((pkt, _)) => pkt, + Ok(pkt) => pkt, Err(err) => { //assert!(errors.Is(io.EOF, err)) log::debug!("{err}"); diff --git a/webrtc/src/rtp_transceiver/srtp_writer_future.rs b/webrtc/src/rtp_transceiver/srtp_writer_future.rs index ff4361c78..e5fbe30c9 100644 --- a/webrtc/src/rtp_transceiver/srtp_writer_future.rs +++ b/webrtc/src/rtp_transceiver/srtp_writer_future.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, Weak}; use async_trait::async_trait; use bytes::Bytes; -use interceptor::{Attributes, RTCPReader, RTPWriter}; +use interceptor::{RTCPReader, RTPWriter}; use portable_atomic::AtomicBool; use srtp::session::Session; use srtp::stream::Stream; @@ -263,18 +263,17 @@ impl RTCPReader for SrtpWriterFuture { async fn read( &self, buf: &mut [u8], - a: &Attributes, - ) -> IResult<(Vec>, Attributes)> { + ) -> IResult>> { let read = self.read(buf).await?; let pkt = rtcp::packet::unmarshal(&mut &buf[..read])?; - Ok((pkt, a.clone())) + Ok(pkt) } } #[async_trait] impl RTPWriter for SrtpWriterFuture { - async fn write(&self, pkt: &rtp::packet::Packet, _a: &Attributes) -> IResult { + async fn write(&self, pkt: &rtp::packet::Packet) -> IResult { Ok( match self.seq_trans.seq_number(pkt.header.sequence_number) { Some(seq_num) => { diff --git a/webrtc/src/track/track_local/mod.rs b/webrtc/src/track/track_local/mod.rs index 9963e491a..2b02c13f0 100644 --- a/webrtc/src/track/track_local/mod.rs +++ b/webrtc/src/track/track_local/mod.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod track_local_static_test; +pub mod packet_cache; pub mod track_local_static_rtp; pub mod track_local_static_sample; @@ -10,7 +11,7 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use async_trait::async_trait; -use interceptor::{Attributes, RTPWriter}; +use interceptor::RTPWriter; use portable_atomic::AtomicBool; use smol_str::SmolStr; use tokio::sync::Mutex; @@ -23,25 +24,21 @@ use crate::rtp_transceiver::*; /// TrackLocalWriter is the Writer for outbound RTP Packets #[async_trait] pub trait TrackLocalWriter: fmt::Debug { + fn as_any(&self) -> &dyn Any; + /// write_rtp_with_attributes encrypts a RTP packet and writes to the connection. /// attributes are delivered to the interceptor chain - async fn write_rtp_with_attributes( - &self, - pkt: &rtp::packet::Packet, - attr: &Attributes, - ) -> Result; + async fn write_rtp_with_attributes(&self, pkt: &rtp::packet::Packet) -> Result; /// write_rtp encrypts a RTP packet and writes to the connection async fn write_rtp(&self, pkt: &rtp::packet::Packet) -> Result { - let attr = Attributes::new(); - self.write_rtp_with_attributes(pkt, &attr).await + self.write_rtp_with_attributes(pkt).await } /// write encrypts and writes a full RTP packet async fn write(&self, mut b: &[u8]) -> Result { let pkt = rtp::packet::Packet::unmarshal(&mut b)?; - let attr = Attributes::new(); - self.write_rtp_with_attributes(&pkt, &attr).await + self.write_rtp_with_attributes(&pkt).await } } @@ -174,18 +171,18 @@ impl std::fmt::Debug for InterceptorToTrackLocalWriter { #[async_trait] impl TrackLocalWriter for InterceptorToTrackLocalWriter { - async fn write_rtp_with_attributes( - &self, - pkt: &rtp::packet::Packet, - attr: &Attributes, - ) -> Result { + fn as_any(&self) -> &dyn Any { + self + } + + async fn write_rtp_with_attributes(&self, pkt: &rtp::packet::Packet) -> Result { if self.is_sender_paused() { return Ok(0); } let interceptor_rtp_writer = self.interceptor_rtp_writer.lock().await; if let Some(writer) = &*interceptor_rtp_writer { - Ok(writer.write(pkt, attr).await?) + Ok(writer.write(pkt).await?) } else { Ok(0) } diff --git a/webrtc/src/track/track_local/packet_cache.rs b/webrtc/src/track/track_local/packet_cache.rs new file mode 100644 index 000000000..85d813fda --- /dev/null +++ b/webrtc/src/track/track_local/packet_cache.rs @@ -0,0 +1,79 @@ +use rtcp::transport_feedbacks::transport_layer_nack::NackPair; +use rtp::packet::Packet; +use std::time::{Duration, Instant}; +use util::sync::RwLock; + +#[derive(Clone, Debug)] +pub struct PCache { + // Финальный пакет, уже с переписанными SSRC/SN/TS и всеми нужными расширениями + // Можно добавить счётчик/время ретрансляций для отладочной метрики (AtomicU32/Option) + pub packet: Packet, // полный RTP (ingress) + pub first_sent_at: Instant, // для TTL +} + +#[derive(Debug)] +pub struct PCacheBuffer { + ttl: Duration, + capacity: usize, // степень двойки предпочтительно; capacity - 1, если capacity — power-of-two + slots: RwLock>>, +} + +impl PCacheBuffer { + pub fn new(ttl: Duration, capacity_pow2: usize) -> Self { + assert!(capacity_pow2.is_power_of_two()); + Self { + ttl, + slots: RwLock::new(vec![None; capacity_pow2]), + capacity: capacity_pow2 - 1, + } + } + + #[inline] + fn idx(&self, seq: u16) -> usize { + (seq as usize) & self.capacity + } + + pub fn put(&self, pkt: &Packet) { + let seq = pkt.header.sequence_number; + let idx = self.idx(seq); + let mut slots = self.slots.write(); + slots[idx] = Some(PCache { + packet: pkt.clone(), + first_sent_at: Instant::now(), + }); + } + + pub fn get(&self, seq: u16) -> Option { + let idx = self.idx(seq); + let slots = self.slots.read(); + let some = slots.get(idx)?.as_ref()?; + if some.packet.header.sequence_number != seq { + println!("Коллизия кольца"); + return None; // коллизия кольца (wrap) + } + if some.first_sent_at.elapsed() > self.ttl { + println!("Пакет просрочен"); + return None; // просрочен + } + Some(some.packet.clone()) + } +} + +// Вспомогательная функция разворачивания NACK-пар (packet_id + bitmask -> список seq) +pub fn expand_nack_pairs(pairs: &[NackPair]) -> Vec { + let mut out = Vec::with_capacity(pairs.len() * 8); + for p in pairs { + let base = p.packet_id; + out.push(base); + let mut mask = p.lost_packets; + let mut i = 0; + while mask != 0 { + if (mask & 1) != 0 { + out.push(base.wrapping_add(i + 1)); + } + mask >>= 1; + i += 1; + } + } + out +} diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index 44e6de8de..5db6a099e 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -1,11 +1,66 @@ -use std::collections::HashMap; - use bytes::{Bytes, BytesMut}; +use std::any::Any; +use std::{borrow::Cow, collections::HashMap, time::Duration}; use tokio::sync::Mutex; use util::{Marshal, MarshalSize}; use super::*; -use crate::error::flatten_errs; +use crate::{error::flatten_errs, track::track_local::packet_cache::PCacheBuffer}; + +#[derive(Debug)] +pub struct TrackState { + last_out_seq: u16, // переживает все переключения источников + last_out_ts: u32, // переживает все переключения источников + started_at_ts: i64, + + out_offset: Option<( + u16, /* смещение порядкового номера */ + u32, /* смещение временной метки timestamp */ + )>, +} + +impl TrackState { + pub fn new() -> Self { + TrackState { + // Порядковый номер начинается с 0 + last_out_seq: 0, + // время трека начинается с 0 + last_out_ts: 0, + // Сохраняем начало трека в реальной временной шкале дла последующей синхронизации + started_at_ts: chrono::Utc::now().timestamp(), + out_offset: None, + } + } + + pub fn get_out_offset(&mut self, pkt_sequence_number: u16, pkt_timestamp: u32) -> (u16, u32) { + match self.out_offset { + Some((seq_num_offset, ts_offset)) => { + self.last_out_seq = pkt_sequence_number.wrapping_add(seq_num_offset); + self.last_out_ts = pkt_timestamp.wrapping_add(ts_offset); + (self.last_out_seq, self.last_out_ts) + } + None => { + println!( + "Смещения перезаписаны seq_num: {}; ts: {}", + pkt_sequence_number, pkt_timestamp + ); + let seq_num_offset = self + .last_out_seq + .wrapping_sub(pkt_sequence_number) + .wrapping_add(1); + let ts_offset = self + .last_out_ts + .wrapping_sub(pkt_timestamp) + .wrapping_add(90000); + self.out_offset = Some((seq_num_offset, ts_offset)); + + self.last_out_seq = pkt_sequence_number.wrapping_add(seq_num_offset); + self.last_out_ts = pkt_timestamp.wrapping_add(ts_offset); + (self.last_out_seq, self.last_out_ts) + } + } + } +} /// TrackLocalStaticRTP is a TrackLocal that has a pre-set codec and accepts RTP Packets. /// If you wish to send a media.Sample use TrackLocalStaticSample @@ -16,6 +71,9 @@ pub struct TrackLocalStaticRTP { id: String, rid: Option, stream_id: String, + + state: Mutex, + pub rtp_cache: Arc, } impl TrackLocalStaticRTP { @@ -27,6 +85,9 @@ impl TrackLocalStaticRTP { id, rid: None, stream_id, + + state: Mutex::new(TrackState::new()), + rtp_cache: Arc::new(PCacheBuffer::new(Duration::from_millis(500), 1024)), } } @@ -43,6 +104,9 @@ impl TrackLocalStaticRTP { id, rid: Some(rid), stream_id, + + state: Mutex::new(TrackState::new()), + rtp_cache: Arc::new(PCacheBuffer::new(Duration::from_millis(500), 1024)), } } @@ -65,6 +129,29 @@ impl TrackLocalStaticRTP { .all(|b| b.sender_paused.load(Ordering::SeqCst)) } + /// Выполняется, когда мы изменяем источник данных для трека + pub async fn replace_remote(&self) { + let mut s = self.state.lock().await; + s.out_offset = None; + } + + /// write_rtp_to writes a RTP Packet to specific binding inside the TrackLocalStaticRTP + /// If it fails it just return Ok(0) + /// The error message will contain the ID of the failed + /// PeerConnections so you can remove them + /// + /// If the RTCRtpSender direction is such that no packets should be sent, any call to this + /// function are blocked internally. Care must be taken to not increase the sequence number + /// while the sender is paused. While the actual _sending_ is blocked, the receiver will + /// miss out when the sequence number "rolls over", which in turn will break SRTP. + pub async fn write_rtp_to( + &self, + pkt: &rtp::packet::Packet, + binding_ssrc: u32, + ) -> Result { + self.write_rtp_with_attributes_to(pkt, binding_ssrc).await + } + /// write_rtp_with_extensions writes a RTP Packet to the TrackLocalStaticRTP /// If one PeerConnection fails the packets will still be sent to /// all PeerConnections. The error message will contain the ID of the failed @@ -82,20 +169,73 @@ impl TrackLocalStaticRTP { p: &rtp::packet::Packet, extensions: &[rtp::extension::HeaderExtension], ) -> Result { - let attr = Attributes::new(); - self.write_rtp_with_extensions_attributes(p, extensions, &attr) + self.write_rtp_with_extensions_attributes(p, extensions) .await } + pub async fn write_rtp_with_extensions_attributes_to( + &self, + p: &rtp::packet::Packet, + extensions: &[rtp::extension::HeaderExtension], + binding_ssrc: u32, + ) -> Result { + let binding = { + let bindings = self.bindings.lock().await; + bindings + .iter() + .find(|b| b.ssrc == binding_ssrc) + .map(|b| b.clone()) + }; + + if let Some(b) = binding { + // Prepare the extensions data + let mut extension_error = None; + let extension_data: HashMap<_, _> = extensions + .iter() + .flat_map(|extension| { + let buf = { + let mut buf = BytesMut::with_capacity(extension.marshal_size()); + buf.resize(extension.marshal_size(), 0); + if let Err(err) = extension.marshal_to(&mut buf) { + extension_error = Some(Error::Util(err)); + return None; + } + + buf.freeze() + }; + + Some((extension.uri(), buf)) + }) + .collect(); + if let Some(err) = extension_error { + return Err(err); + } + + self.write_rtp_with_extensions_attributes_to_binding(p, &extension_data, b) + .await + } else { + // Must return Ok(usize) to be consistent with write_rtp_with_extensions_attributes + Err(Error::LocalTrackBindingNotFound) + } + } + pub async fn write_rtp_with_extensions_attributes( &self, p: &rtp::packet::Packet, extensions: &[rtp::extension::HeaderExtension], - attr: &Attributes, ) -> Result { + let (seq_number, ts) = { + let mut st = self.state.lock().await; + st.get_out_offset(p.header.sequence_number, p.header.timestamp) + }; + + let mut pkt = p.clone(); + pkt.header.sequence_number = seq_number; + pkt.header.timestamp = ts; + self.rtp_cache.put(&pkt); + let mut n = 0; let mut write_errs = vec![]; - let mut pkt = p.clone(); let bindings = { let bindings = self.bindings.lock().await; @@ -121,38 +261,12 @@ impl TrackLocalStaticRTP { .collect(); for b in bindings.into_iter() { - if b.is_sender_paused() { - // See caveat in function doc. - continue; - } - pkt.header.ssrc = b.ssrc; - pkt.header.payload_type = b.payload_type; - - for ext in b.hdr_ext_ids.iter() { - let payload = ext.payload.to_owned(); - if let Err(err) = pkt.header.set_extension(ext.id, payload) { - write_errs.push(Error::Rtp(err)); - } - } - - for (uri, data) in extension_data.iter() { - if let Some(id) = b - .params - .header_extensions - .iter() - .find(|ext| &ext.uri == uri) - .map(|ext| ext.id) - { - if let Err(err) = pkt.header.set_extension(id as u8, data.clone()) { - write_errs.push(Error::Rtp(err)); - continue; - } - } - } - - match b.write_stream.write_rtp_with_attributes(&pkt, attr).await { - Ok(m) => { - n += m; + match self + .write_rtp_with_extensions_attributes_to_binding(&pkt, &extension_data, b) + .await + { + Ok(one_or_zero) => { + n += one_or_zero; } Err(err) => { write_errs.push(err); @@ -163,6 +277,54 @@ impl TrackLocalStaticRTP { flatten_errs(write_errs)?; Ok(n) } + + async fn write_rtp_with_attributes_to( + &self, + pkt: &rtp::packet::Packet, + binding_ssrc: u32, + ) -> Result { + self.write_rtp_with_extensions_attributes_to(pkt, &[], binding_ssrc) + .await + } + + async fn write_rtp_with_extensions_attributes_to_binding( + &self, + p: &rtp::packet::Packet, + extension_data: &HashMap, Bytes>, + binidng: Arc, + ) -> Result { + let mut pkt = p.clone(); + + if binidng.is_sender_paused() { + // See caveat in function doc. + return Ok(0); + } + pkt.header.ssrc = binidng.ssrc; + pkt.header.payload_type = binidng.payload_type; + + for ext in binidng.hdr_ext_ids.iter() { + let payload = ext.payload.to_owned(); + if let Err(err) = pkt.header.set_extension(ext.id, payload) { + return Err(Error::Rtp(err)); + } + } + + for (uri, data) in extension_data.iter() { + if let Some(id) = binidng + .params + .header_extensions + .iter() + .find(|ext| &ext.uri == uri) + .map(|ext| ext.id) + { + if let Err(err) = pkt.header.set_extension(id as u8, data.clone()) { + return Err(Error::Rtp(err)); + } + } + } + + binidng.write_stream.write_rtp_with_attributes(&pkt).await + } } #[async_trait] @@ -171,6 +333,18 @@ impl TrackLocal for TrackLocalStaticRTP { /// This asserts that the code requested is supported by the remote peer. /// If so it setups all the state (SSRC and PayloadType) to have a call async fn bind(&self, t: &TrackLocalContext) -> Result { + if let Some(ittlw) = t + .write_stream + .as_any() + .downcast_ref::() + { + let ptr = &ittlw.interceptor_rtp_writer as *const _ as *const u8; + unsafe { + let a_ptr = ptr as *const i32; + println!("a: {}", *a_ptr); // 42 (но это не гарантировано!) + } + } + let parameters = RTCRtpCodecParameters { capability: self.codec.clone(), ..Default::default() @@ -277,6 +451,10 @@ impl TrackLocal for TrackLocalStaticRTP { #[async_trait] impl TrackLocalWriter for TrackLocalStaticRTP { + fn as_any(&self) -> &dyn Any { + self + } + /// `write_rtp_with_attributes` writes a RTP Packet to the TrackLocalStaticRTP /// If one PeerConnection fails the packets will still be sent to /// all PeerConnections. The error message will contain the ID of the failed @@ -286,13 +464,8 @@ impl TrackLocalWriter for TrackLocalStaticRTP { /// function are blocked internally. Care must be taken to not increase the sequence number /// while the sender is paused. While the actual _sending_ is blocked, the receiver will /// miss out when the sequence number "rolls over", which in turn will break SRTP. - async fn write_rtp_with_attributes( - &self, - pkt: &rtp::packet::Packet, - attr: &Attributes, - ) -> Result { - self.write_rtp_with_extensions_attributes(pkt, &[], attr) - .await + async fn write_rtp_with_attributes(&self, pkt: &rtp::packet::Packet) -> Result { + self.write_rtp_with_extensions_attributes(pkt, &[]).await } /// write writes a RTP Packet as a buffer to the TrackLocalStaticRTP diff --git a/webrtc/src/track/track_remote/mod.rs b/webrtc/src/track/track_remote/mod.rs index 7d0565cf9..64c6ccb47 100644 --- a/webrtc/src/track/track_remote/mod.rs +++ b/webrtc/src/track/track_remote/mod.rs @@ -5,7 +5,7 @@ use std::sync::atomic::Ordering; use std::sync::{Arc, Weak}; use arc_swap::ArcSwapOption; -use interceptor::{Attributes, Interceptor}; +use interceptor::Interceptor; use portable_atomic::{AtomicU32, AtomicU8, AtomicUsize}; use smol_str::SmolStr; use tokio::sync::Mutex; @@ -32,7 +32,7 @@ struct Handlers { #[derive(Default)] struct TrackRemoteInternal { - peeked: VecDeque<(rtp::packet::Packet, Attributes)>, + peeked: VecDeque, } /// TrackRemote represents a single inbound source of media @@ -214,14 +214,14 @@ impl TrackRemote { /// /// **Cancel Safety:** This method is not cancel safe. Dropping the resulting [`Future`] before /// it returns [`std::task::Poll::Ready`] will cause data loss. - pub async fn read(&self, b: &mut [u8]) -> Result<(rtp::packet::Packet, Attributes)> { + pub async fn read(&self, b: &mut [u8]) -> Result { { // Internal lock scope let mut internal = self.internal.lock().await; - if let Some((pkt, attributes)) = internal.peeked.pop_front() { + if let Some(pkt) = internal.peeked.pop_front() { self.check_and_update_track(&pkt).await?; - return Ok((pkt, attributes)); + return Ok(pkt); } }; @@ -230,9 +230,9 @@ impl TrackRemote { None => return Err(Error::ErrRTPReceiverNil), }; - let (pkt, attributes) = receiver.read_rtp(b, self.tid).await?; + let pkt = receiver.read_rtp(b, self.tid).await?; self.check_and_update_track(&pkt).await?; - Ok((pkt, attributes)) + Ok(pkt) } /// check_and_update_track checks payloadType for every incoming packet @@ -269,25 +269,25 @@ impl TrackRemote { } /// read_rtp is a convenience method that wraps Read and unmarshals for you. - pub async fn read_rtp(&self) -> Result<(rtp::packet::Packet, Attributes)> { + pub async fn read_rtp(&self) -> Result { let mut b = vec![0u8; self.receive_mtu]; - let (pkt, attributes) = self.read(&mut b).await?; + let pkt = self.read(&mut b).await?; - Ok((pkt, attributes)) + Ok(pkt) } /// peek is like Read, but it doesn't discard the packet read - pub(crate) async fn peek(&self, b: &mut [u8]) -> Result<(rtp::packet::Packet, Attributes)> { - let (pkt, a) = self.read(b).await?; + pub(crate) async fn peek(&self, b: &mut [u8]) -> Result { + let pkt = self.read(b).await?; // this might overwrite data if somebody peeked between the Read // and us getting the lock. Oh well, we'll just drop a packet in // that case. { let mut internal = self.internal.lock().await; - internal.peeked.push_back((pkt.clone(), a.clone())); + internal.peeked.push_back(pkt.clone()); } - Ok((pkt, a)) + Ok(pkt) } /// Set the initially peeked data for this track. @@ -295,10 +295,7 @@ impl TrackRemote { /// This is useful when a track is first created to populate data read from the track in the /// process of identifying the track as part of simulcast probing. Using this during other /// parts of the track's lifecycle is probably an error. - pub(crate) async fn prepopulate_peeked_data( - &self, - data: VecDeque<(rtp::packet::Packet, Attributes)>, - ) { + pub(crate) async fn prepopulate_peeked_data(&self, data: VecDeque) { let mut internal = self.internal.lock().await; internal.peeked = data; } From 2eb254779f77f25dda7a0bccdb9423ef7b56c921 Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Mon, 6 Oct 2025 19:30:50 +0300 Subject: [PATCH 03/15] =?UTF-8?q?=D0=92=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B7=D0=B0=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D1=81=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20ssrc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webrtc/src/peer_connection/mod.rs | 17 ++- .../peer_connection_internal.rs | 102 +++++++++++++++ webrtc/src/rtp_transceiver/rtp_sender/mod.rs | 118 ++++++++++++++++++ webrtc/src/track/track_local/mod.rs | 19 +-- .../track/track_local/track_local_simple.rs | 67 ++++++++++ .../track_local/track_local_static_rtp.rs | 79 +++--------- 6 files changed, 323 insertions(+), 79 deletions(-) create mode 100644 webrtc/src/track/track_local/track_local_simple.rs diff --git a/webrtc/src/peer_connection/mod.rs b/webrtc/src/peer_connection/mod.rs index 41a2c7a51..487a56e00 100644 --- a/webrtc/src/peer_connection/mod.rs +++ b/webrtc/src/peer_connection/mod.rs @@ -76,7 +76,9 @@ use crate::peer_connection::sdp::*; use crate::peer_connection::signaling_state::{ check_next_signaling_state, RTCSignalingState, StateChangeOp, }; -use crate::rtp_transceiver::rtp_codec::{RTCRtpHeaderExtensionCapability, RTPCodecType}; +use crate::rtp_transceiver::rtp_codec::{ + RTCRtpCodecParameters, RTCRtpHeaderExtensionCapability, RTPCodecType, +}; use crate::rtp_transceiver::rtp_receiver::RTCRtpReceiver; use crate::rtp_transceiver::rtp_sender::RTCRtpSender; use crate::rtp_transceiver::rtp_transceiver_direction::RTCRtpTransceiverDirection; @@ -1805,6 +1807,19 @@ impl RTCPeerConnection { self.internal.add_transceiver_from_kind(kind, init).await } + pub async fn add_transceiver_from_params( + &self, + direction: RTCRtpTransceiverDirection, + kind: RTPCodecType, + stream_id: String, + ssrc: u32, + codecs: Vec, + ) -> Result> { + self.internal + .add_transceiver_from_params(direction, kind, stream_id, ssrc, codecs) + .await + } + /// add_transceiver_from_track Create a new RtpTransceiver(SendRecv or SendOnly) and add it to the set of transceivers. pub async fn add_transceiver_from_track( &self, diff --git a/webrtc/src/peer_connection/peer_connection_internal.rs b/webrtc/src/peer_connection/peer_connection_internal.rs index a1421be81..d66d6e80f 100644 --- a/webrtc/src/peer_connection/peer_connection_internal.rs +++ b/webrtc/src/peer_connection/peer_connection_internal.rs @@ -3,6 +3,7 @@ use std::sync::Weak; use super::*; use crate::api::setting_engine::SctpMaxMessageSize; +use crate::rtp_transceiver::rtp_codec::RTCRtpCodecParameters; use crate::rtp_transceiver::{create_stream_info, PayloadType}; use crate::stats::stats_collector::StatsCollector; use crate::stats::{ @@ -633,6 +634,107 @@ impl PeerConnectionInternal { .await) } + pub(super) async fn add_transceiver_from_params( + &self, + direction: RTCRtpTransceiverDirection, + kind: RTPCodecType, + stream_id: String, + ssrc: u32, + codecs: Vec, + ) -> Result> { + if self.is_closed.load(Ordering::SeqCst) { + return Err(Error::ErrConnectionClosed); + } + + let t = match direction { + RTCRtpTransceiverDirection::Sendonly | RTCRtpTransceiverDirection::Sendrecv => { + let interceptor = self + .interceptor + .upgrade() + .ok_or(Error::ErrInterceptorNotBind)?; + + if direction == RTCRtpTransceiverDirection::Unspecified { + return Err(Error::ErrPeerConnAddTransceiverFromTrackSupport); + } + + let r = Arc::new(RTCRtpReceiver::new( + self.setting_engine.get_receive_mtu(), + kind, + Arc::clone(&self.dtls_transport), + Arc::clone(&self.media_engine), + Arc::clone(&interceptor), + )); + + let s = Arc::new( + RTCRtpSender::new_by_ssrc( + kind, + Arc::clone(&self.dtls_transport), + Arc::clone(&self.media_engine), + Arc::clone(&self.setting_engine), + Arc::clone(&interceptor), + false, + stream_id, + ssrc, + ) + .await, + ); + + RTCRtpTransceiver::new( + r, + s, + direction, + kind, + codecs, + Arc::clone(&self.media_engine), + Some(Box::new(self.make_negotiation_needed_trigger())), + ) + .await + } + RTCRtpTransceiverDirection::Recvonly => { + let interceptor = self + .interceptor + .upgrade() + .ok_or(Error::ErrInterceptorNotBind)?; + let receiver = Arc::new(RTCRtpReceiver::new( + self.setting_engine.get_receive_mtu(), + kind, + Arc::clone(&self.dtls_transport), + Arc::clone(&self.media_engine), + Arc::clone(&interceptor), + )); + + let sender = Arc::new( + RTCRtpSender::new( + None, + kind, + Arc::clone(&self.dtls_transport), + Arc::clone(&self.media_engine), + Arc::clone(&self.setting_engine), + interceptor, + false, + ) + .await, + ); + + RTCRtpTransceiver::new( + receiver, + sender, + direction, + kind, + vec![], + Arc::clone(&self.media_engine), + Some(Box::new(self.make_negotiation_needed_trigger())), + ) + .await + } + _ => return Err(Error::ErrPeerConnAddTransceiverFromKindSupport), + }; + + self.add_rtp_transceiver(Arc::clone(&t)).await; + + Ok(t) + } + /// add_rtp_transceiver appends t into rtp_transceivers /// and fires onNegotiationNeeded; /// caller of this method should hold `self.mu` lock diff --git a/webrtc/src/rtp_transceiver/rtp_sender/mod.rs b/webrtc/src/rtp_transceiver/rtp_sender/mod.rs index f2a46ad5d..bacc966e9 100644 --- a/webrtc/src/rtp_transceiver/rtp_sender/mod.rs +++ b/webrtc/src/rtp_transceiver/rtp_sender/mod.rs @@ -25,6 +25,7 @@ use crate::rtp_transceiver::{ create_stream_info, PayloadType, RTCRtpEncodingParameters, RTCRtpSendParameters, RTCRtpTransceiver, SSRC, }; +use crate::track::track_local::track_local_simple::TrackLocalSimple; use crate::track::track_local::{InterceptorToTrackLocalWriter, TrackLocal, TrackLocalContext}; pub(crate) struct RTPSenderInternal { @@ -180,6 +181,77 @@ impl RTCRtpSender { ret } + pub async fn new_by_ssrc( + kind: RTPCodecType, + transport: Arc, + media_engine: Arc, + setting_engine: Arc, + interceptor: Arc, + start_paused: bool, + stream_id: String, + ssrc: u32, + ) -> Self { + let id = generate_crypto_random_string( + 32, + b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + ); + let (send_called, _) = watch::channel(false); + let stop_called_tx = Arc::new(Notify::new()); + let stop_called_rx = stop_called_tx.clone(); + let stop_called_signal = Arc::new(AtomicBool::new(false)); + + let internal = Arc::new(RTPSenderInternal { + stop_called_rx, + stop_called_signal: Arc::clone(&stop_called_signal), + }); + + let seq_trans = Arc::new(SequenceTransformer::new()); + let rtx_seq_trans = Arc::new(SequenceTransformer::new()); + + let stream_ids = vec![stream_id.clone()]; + let ret = Self { + track_encodings: Mutex::new(vec![]), + + seq_trans, + rtx_seq_trans, + + transport, + + kind, + payload_type: 0, + receive_mtu: setting_engine.get_receive_mtu(), + enable_rtx: setting_engine.enable_sender_rtx, + + negotiated: AtomicBool::new(false), + + media_engine, + interceptor, + + id, + initial_track_id: std::sync::Mutex::new(None), + associated_media_stream_ids: std::sync::Mutex::new(stream_ids), + + rtp_transceiver: SyncMutex::new(None), + + send_called, + stop_called_tx, + stop_called_signal, + + paused: Arc::new(AtomicBool::new(start_paused)), + + internal, + }; + + { + let mut track_encodings = ret.track_encodings.lock().await; + let _ = ret + .add_encoding_internal_by_kind(&mut track_encodings, kind, ssrc, stream_id) + .await; + } + + ret + } + /// AddEncoding adds an encoding to RTPSender. Used by simulcast senders. pub async fn add_encoding(&self, track: Arc) -> Result<()> { let mut track_encodings = self.track_encodings.lock().await; @@ -297,6 +369,52 @@ impl RTCRtpSender { Ok(()) } + async fn add_encoding_internal_by_kind( + &self, + track_encodings: &mut Vec, + kind: RTPCodecType, + ssrc: u32, + stream_id: String, + ) -> Result<()> { + let srtp_stream = Arc::new(SrtpWriterFuture { + closed: AtomicBool::new(false), + ssrc, + rtp_sender: Arc::downgrade(&self.internal), + rtp_transport: Arc::clone(&self.transport), + rtcp_read_stream: Mutex::new(None), + rtp_write_session: Mutex::new(None), + seq_trans: Arc::clone(&self.seq_trans), + }); + + let srtp_rtcp_reader = Arc::clone(&srtp_stream) as Arc; + let rtcp_interceptor = self.interceptor.bind_rtcp_reader(srtp_rtcp_reader).await; + + let write_stream = Arc::new(InterceptorToTrackLocalWriter::new(self.paused.clone())); + let context = TrackLocalContext { + id: self.id.clone(), + params: super::RTCRtpParameters::default(), + ssrc: 0, + write_stream, + paused: self.paused.clone(), + mid: None, + }; + + let track = TrackLocalSimple::new(kind, format!("{stream_id}_{kind}"), stream_id, ssrc); + let encoding = TrackEncoding { + track: Arc::new(track), + srtp_stream, + rtcp_interceptor, + stream_info: StreamInfo::default(), + context, + ssrc, + rtx: None, + }; + + track_encodings.push(encoding); + + Ok(()) + } + pub(crate) fn is_negotiated(&self) -> bool { self.negotiated.load(Ordering::SeqCst) } diff --git a/webrtc/src/track/track_local/mod.rs b/webrtc/src/track/track_local/mod.rs index 2b02c13f0..4ccc4e618 100644 --- a/webrtc/src/track/track_local/mod.rs +++ b/webrtc/src/track/track_local/mod.rs @@ -2,6 +2,7 @@ mod track_local_static_test; pub mod packet_cache; +pub mod track_local_simple; pub mod track_local_static_rtp; pub mod track_local_static_sample; @@ -24,21 +25,13 @@ use crate::rtp_transceiver::*; /// TrackLocalWriter is the Writer for outbound RTP Packets #[async_trait] pub trait TrackLocalWriter: fmt::Debug { - fn as_any(&self) -> &dyn Any; - - /// write_rtp_with_attributes encrypts a RTP packet and writes to the connection. - /// attributes are delivered to the interceptor chain - async fn write_rtp_with_attributes(&self, pkt: &rtp::packet::Packet) -> Result; - /// write_rtp encrypts a RTP packet and writes to the connection - async fn write_rtp(&self, pkt: &rtp::packet::Packet) -> Result { - self.write_rtp_with_attributes(pkt).await - } + async fn write_rtp(&self, pkt: &rtp::packet::Packet) -> Result; /// write encrypts and writes a full RTP packet async fn write(&self, mut b: &[u8]) -> Result { let pkt = rtp::packet::Packet::unmarshal(&mut b)?; - self.write_rtp_with_attributes(&pkt).await + self.write_rtp(&pkt).await } } @@ -171,11 +164,7 @@ impl std::fmt::Debug for InterceptorToTrackLocalWriter { #[async_trait] impl TrackLocalWriter for InterceptorToTrackLocalWriter { - fn as_any(&self) -> &dyn Any { - self - } - - async fn write_rtp_with_attributes(&self, pkt: &rtp::packet::Packet) -> Result { + async fn write_rtp(&self, pkt: &rtp::packet::Packet) -> Result { if self.is_sender_paused() { return Ok(0); } diff --git a/webrtc/src/track/track_local/track_local_simple.rs b/webrtc/src/track/track_local/track_local_simple.rs new file mode 100644 index 000000000..d1f494253 --- /dev/null +++ b/webrtc/src/track/track_local/track_local_simple.rs @@ -0,0 +1,67 @@ +use super::*; +use std::any::Any; + +/// TrackLocalSimple is simples mock of TrackLocal +#[derive(Debug)] +pub struct TrackLocalSimple { + kind: RTPCodecType, + id: String, + stream_id: String, + ssrc: u32, +} + +impl TrackLocalSimple { + /// returns a TrackLocalStaticRTP without rid. + pub fn new(kind: RTPCodecType, id: String, stream_id: String, ssrc: u32) -> Self { + TrackLocalSimple { + kind, + id, + stream_id, + ssrc, + } + } +} + +#[async_trait] +impl TrackLocal for TrackLocalSimple { + async fn bind(&self, _t: &TrackLocalContext) -> Result { + println!( + "TrackLocalSimple.bind: mid - {:?}; {:?}", + _t.mid(), + _t.ssrc() + ); + Ok(RTCRtpCodecParameters { + ..Default::default() + }) + } + + async fn unbind(&self, _t: &TrackLocalContext) -> Result<()> { + println!( + "TrackLocalSimple.unbind: mid-{:?}; {:?}", + _t.mid(), + _t.ssrc() + ); + Ok(()) + } + + fn id(&self) -> &str { + self.id.as_str() + } + + fn rid(&self) -> Option<&str> { + None + } + + fn stream_id(&self) -> &str { + self.stream_id.as_str() + } + + /// kind controls if this TrackLocal is audio or video + fn kind(&self) -> RTPCodecType { + self.kind.clone() + } + + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index 5db6a099e..9870551ad 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -135,45 +135,12 @@ impl TrackLocalStaticRTP { s.out_offset = None; } - /// write_rtp_to writes a RTP Packet to specific binding inside the TrackLocalStaticRTP - /// If it fails it just return Ok(0) - /// The error message will contain the ID of the failed - /// PeerConnections so you can remove them - /// - /// If the RTCRtpSender direction is such that no packets should be sent, any call to this - /// function are blocked internally. Care must be taken to not increase the sequence number - /// while the sender is paused. While the actual _sending_ is blocked, the receiver will - /// miss out when the sequence number "rolls over", which in turn will break SRTP. - pub async fn write_rtp_to( - &self, - pkt: &rtp::packet::Packet, - binding_ssrc: u32, - ) -> Result { - self.write_rtp_with_attributes_to(pkt, binding_ssrc).await - } - - /// write_rtp_with_extensions writes a RTP Packet to the TrackLocalStaticRTP - /// If one PeerConnection fails the packets will still be sent to - /// all PeerConnections. The error message will contain the ID of the failed - /// PeerConnections so you can remove them - /// - /// If the RTCRtpSender direction is such that no packets should be sent, any call to this - /// function are blocked internally. Care must be taken to not increase the sequence number - /// while the sender is paused. While the actual _sending_ is blocked, the receiver will - /// miss out when the sequence number "rolls over", which in turn will break SRTP. - /// - /// Extensions that are already configured on the packet are overwritten by extensions in - /// `extensions`. - pub async fn write_rtp_with_extensions( - &self, - p: &rtp::packet::Packet, - extensions: &[rtp::extension::HeaderExtension], - ) -> Result { - self.write_rtp_with_extensions_attributes(p, extensions) - .await + pub async fn bindings_ssrc(&self) -> Vec { + let bindings = self.bindings.lock().await; + bindings.iter().map(|b| b.ssrc).collect() } - pub async fn write_rtp_with_extensions_attributes_to( + pub async fn write_rtp_with_extensions_to( &self, p: &rtp::packet::Packet, extensions: &[rtp::extension::HeaderExtension], @@ -211,7 +178,7 @@ impl TrackLocalStaticRTP { return Err(err); } - self.write_rtp_with_extensions_attributes_to_binding(p, &extension_data, b) + self.write_rtp_with_extensions_to_binding(p, &extension_data, b) .await } else { // Must return Ok(usize) to be consistent with write_rtp_with_extensions_attributes @@ -219,7 +186,7 @@ impl TrackLocalStaticRTP { } } - pub async fn write_rtp_with_extensions_attributes( + pub async fn write_rtp_with_extensions( &self, p: &rtp::packet::Packet, extensions: &[rtp::extension::HeaderExtension], @@ -262,7 +229,7 @@ impl TrackLocalStaticRTP { for b in bindings.into_iter() { match self - .write_rtp_with_extensions_attributes_to_binding(&pkt, &extension_data, b) + .write_rtp_with_extensions_to_binding(&pkt, &extension_data, b) .await { Ok(one_or_zero) => { @@ -278,16 +245,16 @@ impl TrackLocalStaticRTP { Ok(n) } - async fn write_rtp_with_attributes_to( + pub async fn write_rtp_to( &self, pkt: &rtp::packet::Packet, binding_ssrc: u32, ) -> Result { - self.write_rtp_with_extensions_attributes_to(pkt, &[], binding_ssrc) + self.write_rtp_with_extensions_to(pkt, &[], binding_ssrc) .await } - async fn write_rtp_with_extensions_attributes_to_binding( + async fn write_rtp_with_extensions_to_binding( &self, p: &rtp::packet::Packet, extension_data: &HashMap, Bytes>, @@ -323,7 +290,7 @@ impl TrackLocalStaticRTP { } } - binidng.write_stream.write_rtp_with_attributes(&pkt).await + binidng.write_stream.write_rtp(&pkt).await } } @@ -333,18 +300,7 @@ impl TrackLocal for TrackLocalStaticRTP { /// This asserts that the code requested is supported by the remote peer. /// If so it setups all the state (SSRC and PayloadType) to have a call async fn bind(&self, t: &TrackLocalContext) -> Result { - if let Some(ittlw) = t - .write_stream - .as_any() - .downcast_ref::() - { - let ptr = &ittlw.interceptor_rtp_writer as *const _ as *const u8; - unsafe { - let a_ptr = ptr as *const i32; - println!("a: {}", *a_ptr); // 42 (но это не гарантировано!) - } - } - + println!("bind: mid - {:?}; {:?}", t.mid(), t.ssrc()); let parameters = RTCRtpCodecParameters { capability: self.codec.clone(), ..Default::default() @@ -400,6 +356,7 @@ impl TrackLocal for TrackLocalStaticRTP { /// unbind implements the teardown logic when the track is no longer needed. This happens /// because a track has been stopped. async fn unbind(&self, t: &TrackLocalContext) -> Result<()> { + println!("unbind: mid-{:?}; {:?}", t.mid(), t.ssrc()); let mut bindings = self.bindings.lock().await; let mut idx = None; for (index, binding) in bindings.iter().enumerate() { @@ -451,11 +408,7 @@ impl TrackLocal for TrackLocalStaticRTP { #[async_trait] impl TrackLocalWriter for TrackLocalStaticRTP { - fn as_any(&self) -> &dyn Any { - self - } - - /// `write_rtp_with_attributes` writes a RTP Packet to the TrackLocalStaticRTP + /// `write_rtp` writes a RTP Packet to the TrackLocalStaticRTP /// If one PeerConnection fails the packets will still be sent to /// all PeerConnections. The error message will contain the ID of the failed /// PeerConnections so you can remove them @@ -464,8 +417,8 @@ impl TrackLocalWriter for TrackLocalStaticRTP { /// function are blocked internally. Care must be taken to not increase the sequence number /// while the sender is paused. While the actual _sending_ is blocked, the receiver will /// miss out when the sequence number "rolls over", which in turn will break SRTP. - async fn write_rtp_with_attributes(&self, pkt: &rtp::packet::Packet) -> Result { - self.write_rtp_with_extensions_attributes(pkt, &[]).await + async fn write_rtp(&self, pkt: &rtp::packet::Packet) -> Result { + self.write_rtp_with_extensions(pkt, &[]).await } /// write writes a RTP Packet as a buffer to the TrackLocalStaticRTP From 421605880cd450af7a79c9fb1cbc3a312982dc7c Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Thu, 9 Oct 2025 20:50:50 +0300 Subject: [PATCH 04/15] =?UTF-8?q?=D0=A3=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5,=20=D0=B2=D0=B5=D1=80=D0=BE=D1=8F?= =?UTF-8?q?=D1=82=D0=BD=D0=BE,=20=D0=BB=D0=BE=D0=B6=D0=BD=D0=BE-=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=BE=D0=B6=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ice/src/candidate/candidate_base.rs | 4 +- sdp/src/description/media.rs | 17 ++ .../peer_connection_internal.rs | 18 ++- webrtc/src/track/track_local/packet_cache.rs | 20 ++- .../track_local/track_local_static_rtp.rs | 152 +++++++++++++++--- 5 files changed, 176 insertions(+), 35 deletions(-) diff --git a/ice/src/candidate/candidate_base.rs b/ice/src/candidate/candidate_base.rs index 08125672c..8cc34d018 100644 --- a/ice/src/candidate/candidate_base.rs +++ b/ice/src/candidate/candidate_base.rs @@ -240,7 +240,9 @@ impl Candidate for CandidateBase { { let mut closed_ch = self.closed_ch.lock().await; if closed_ch.is_none() { - return Err(Error::ErrClosed); + // Если кандидат уже был ранее закрыт, не возвращать ошибку, а просто вернуть успех + return Ok(()); + // return Err(Error::ErrClosed); } closed_ch.take(); } diff --git a/sdp/src/description/media.rs b/sdp/src/description/media.rs index 47aa4905a..7fd0b35cd 100644 --- a/sdp/src/description/media.rs +++ b/sdp/src/description/media.rs @@ -80,6 +80,23 @@ impl MediaDescription { None } + pub fn attributes(&self, key: &str) -> Vec { + let mut atrs = vec![]; + for a in &self.attributes { + if a.key == key { + match a.value.as_ref().map(|s| s.to_owned()) { + Some(atr_value) => { + atrs.push(atr_value); + } + None => { + atrs.push(String::new()); + } + } + } + } + atrs + } + /// new_jsep_media_description creates a new MediaName with /// some settings that are required by the JSEP spec. pub fn new_jsep_media_description(codec_type: String, _codec_prefs: Vec<&str>) -> Self { diff --git a/webrtc/src/peer_connection/peer_connection_internal.rs b/webrtc/src/peer_connection/peer_connection_internal.rs index d66d6e80f..3a3860e4e 100644 --- a/webrtc/src/peer_connection/peer_connection_internal.rs +++ b/webrtc/src/peer_connection/peer_connection_internal.rs @@ -335,7 +335,14 @@ impl PeerConnectionInternal { return; } Err(err) => { - log::warn!("Failed to accept RTP {err}"); + match err { + // Если агент уже закрыт, то не показываем сообщение об ошибке + srtp::Error::SessionSrtpAlreadyClosed => {} + _ => { + log::warn!("Failed to accept RTP {err}"); + } + } + return; } }; @@ -401,7 +408,14 @@ impl PeerConnectionInternal { ); } Err(err) => { - log::warn!("Failed to accept RTCP {err}"); + match err { + // TODO: убедиться, что нет проблемы с этим кодом + srtp::Error::SessionSrtpAlreadyClosed => {} + _ => { + log::warn!("Failed to accept RTCP {err}"); + } + } + return; } }; diff --git a/webrtc/src/track/track_local/packet_cache.rs b/webrtc/src/track/track_local/packet_cache.rs index 85d813fda..15901a405 100644 --- a/webrtc/src/track/track_local/packet_cache.rs +++ b/webrtc/src/track/track_local/packet_cache.rs @@ -33,12 +33,11 @@ impl PCacheBuffer { (seq as usize) & self.capacity } - pub fn put(&self, pkt: &Packet) { - let seq = pkt.header.sequence_number; - let idx = self.idx(seq); + pub fn put(&self, packet: Packet) { + let idx = self.idx(packet.header.sequence_number); let mut slots = self.slots.write(); slots[idx] = Some(PCache { - packet: pkt.clone(), + packet, first_sent_at: Instant::now(), }); } @@ -48,11 +47,18 @@ impl PCacheBuffer { let slots = self.slots.read(); let some = slots.get(idx)?.as_ref()?; if some.packet.header.sequence_number != seq { - println!("Коллизия кольца"); + println!( + "Коллизия кольца: запрошен seq={seq}. В кеше seq={}", + some.packet.header.sequence_number + ); return None; // коллизия кольца (wrap) } - if some.first_sent_at.elapsed() > self.ttl { - println!("Пакет просрочен"); + let elapsed = some.first_sent_at.elapsed(); + if elapsed > self.ttl { + println!( + "Пакет просрочен. Прошло {:?}, что больше ttl = {:?}", + elapsed, self.ttl + ); return None; // просрочен } Some(some.packet.clone()) diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index 9870551ad..2829b3f33 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -1,10 +1,13 @@ use bytes::{Bytes, BytesMut}; +use rtp::packet::Packet; use std::any::Any; use std::{borrow::Cow, collections::HashMap, time::Duration}; -use tokio::sync::Mutex; +use tokio::sync::mpsc::error::TrySendError; +use tokio::sync::{mpsc, Mutex}; use util::{Marshal, MarshalSize}; use super::*; +use crate::track::track_remote::TrackRemote; use crate::{error::flatten_errs, track::track_local::packet_cache::PCacheBuffer}; #[derive(Debug)] @@ -32,7 +35,12 @@ impl TrackState { } } - pub fn get_out_offset(&mut self, pkt_sequence_number: u16, pkt_timestamp: u32) -> (u16, u32) { + pub fn apply_offset( + &mut self, + kind: RTPCodecType, + pkt_sequence_number: u16, + pkt_timestamp: u32, + ) -> (u16, u32) { match self.out_offset { Some((seq_num_offset, ts_offset)) => { self.last_out_seq = pkt_sequence_number.wrapping_add(seq_num_offset); @@ -40,26 +48,38 @@ impl TrackState { (self.last_out_seq, self.last_out_ts) } None => { - println!( - "Смещения перезаписаны seq_num: {}; ts: {}", - pkt_sequence_number, pkt_timestamp - ); let seq_num_offset = self .last_out_seq .wrapping_sub(pkt_sequence_number) .wrapping_add(1); - let ts_offset = self - .last_out_ts - .wrapping_sub(pkt_timestamp) - .wrapping_add(90000); + let ts_offset = + self.last_out_ts + .wrapping_sub(pkt_timestamp) + .wrapping_add(match kind { + RTPCodecType::Audio => 900, // стандартное значение для звука + RTPCodecType::Video => 3750, // 90000 clock_rate / 24 кадра + _ => 3750, + }); self.out_offset = Some((seq_num_offset, ts_offset)); self.last_out_seq = pkt_sequence_number.wrapping_add(seq_num_offset); self.last_out_ts = pkt_timestamp.wrapping_add(ts_offset); + + println!( + "Смещения перезаписаны seq_num: {pkt_sequence_number} -> {}; ts: {pkt_timestamp} -> {}", + self.last_out_seq, self.last_out_ts + ); (self.last_out_seq, self.last_out_ts) } } } + + pub fn origin_seq(&self, modified_seq: u16) -> u16 { + match self.out_offset { + Some((seq_num_offset, _)) => modified_seq.wrapping_sub(seq_num_offset), + None => modified_seq, + } + } } /// TrackLocalStaticRTP is a TrackLocal that has a pre-set codec and accepts RTP Packets. @@ -72,10 +92,16 @@ pub struct TrackLocalStaticRTP { rid: Option, stream_id: String, - state: Mutex, + pub state: Mutex, pub rtp_cache: Arc, } +/// Количество пакетов в кэше +const CAPACITY: usize = 128; // если 24 пакета в секунду, то на 3 секунды нужно 72 ячейки кэша + +/// TTL в миллисекундах, время через которое кэш становится невалидным +const TTL_MILLIS: u64 = 3000; + impl TrackLocalStaticRTP { /// returns a TrackLocalStaticRTP without rid. pub fn new(codec: RTCRtpCodecCapability, id: String, stream_id: String) -> Self { @@ -87,7 +113,10 @@ impl TrackLocalStaticRTP { stream_id, state: Mutex::new(TrackState::new()), - rtp_cache: Arc::new(PCacheBuffer::new(Duration::from_millis(500), 1024)), + rtp_cache: Arc::new(PCacheBuffer::new( + Duration::from_millis(TTL_MILLIS), + CAPACITY, + )), } } @@ -106,7 +135,10 @@ impl TrackLocalStaticRTP { stream_id, state: Mutex::new(TrackState::new()), - rtp_cache: Arc::new(PCacheBuffer::new(Duration::from_millis(500), 1024)), + rtp_cache: Arc::new(PCacheBuffer::new( + Duration::from_millis(TTL_MILLIS), + CAPACITY, + )), } } @@ -130,11 +162,56 @@ impl TrackLocalStaticRTP { } /// Выполняется, когда мы изменяем источник данных для трека - pub async fn replace_remote(&self) { - let mut s = self.state.lock().await; - s.out_offset = None; + pub async fn replace_remote(self: Arc, remote_track: Arc) { + // 1. Приводим исходящее смещение к начальному состоянию, + // чтоб определить его заново в момент первого пришедшего пакета + { + let mut s = self.state.lock().await; + s.out_offset = None; + } + + // 2. Запись из mpsc канала в local_track + // здесь должен быть минимальный буфер, + // т.к. лучше потом отправить из кеша, чем пытаться отправить застрявший пакет из очереди + let (rtp_sender, mut rtp_rx) = mpsc::channel::(64); + let local_track = Arc::downgrade(&self); + let rtp_writer = tokio::spawn(async move { + while let Some(pkt) = rtp_rx.recv().await { + if let Some(local_track) = local_track.upgrade() { + if let Err(err) = local_track.write_rtp(&pkt).await { + eprintln!("Ошибка записи данных в исходящий трек: {:?}", err); + } + } else { + break; + } + } + println!("Запись данных в трек остановлена!"); + }); + + // 3. Чтение из remote_track в mpsc канал + while let Ok(rtp) = remote_track.read_rtp().await { + // 1. Сохраняем в кэш оригинальный rtp без смещений! Так быстрее происходит сохранение в кэш + // При восстановлении кеша нужно вернуть порядковый номер к оригинальному, чтоб найти его + self.rtp_cache.put(rtp.clone()); + + // 2. Пытаемся отправить, если переполнен буфер, не ждём и позже в ответ на NACK берём из кэша + // Без ожиданий, чтоб не замедлять процесс получения пакетов + match rtp_sender.try_send(rtp) { + Err(TrySendError::Closed(_)) => { + break; + } + Err(TrySendError::Full(_)) => { + eprintln!("Ошибка отправки RTP данных: Буфер переполнен"); + } + _ => {} + } + } + + // 4. Если remote_track перестал слать пакеты, то перестаём и записывать их + rtp_writer.abort(); } + /// Получаем ssrc всех RTCPeerConnection подключений к этому треку pub async fn bindings_ssrc(&self) -> Vec { let bindings = self.bindings.lock().await; bindings.iter().map(|b| b.ssrc).collect() @@ -188,18 +265,22 @@ impl TrackLocalStaticRTP { pub async fn write_rtp_with_extensions( &self, - p: &rtp::packet::Packet, + pkt: &rtp::packet::Packet, extensions: &[rtp::extension::HeaderExtension], ) -> Result { - let (seq_number, ts) = { + let mut pkt = pkt.clone(); + + let (seq_number, timestamp) = { let mut st = self.state.lock().await; - st.get_out_offset(p.header.sequence_number, p.header.timestamp) + st.apply_offset( + self.kind(), + pkt.header.sequence_number, + pkt.header.timestamp, + ) }; - let mut pkt = p.clone(); pkt.header.sequence_number = seq_number; - pkt.header.timestamp = ts; - self.rtp_cache.put(&pkt); + pkt.header.timestamp = timestamp; let mut n = 0; let mut write_errs = vec![]; @@ -250,7 +331,20 @@ impl TrackLocalStaticRTP { pkt: &rtp::packet::Packet, binding_ssrc: u32, ) -> Result { - self.write_rtp_with_extensions_to(pkt, &[], binding_ssrc) + let mut pkt = pkt.clone(); + + let (seq_number, timestamp) = { + let mut st = self.state.lock().await; + st.apply_offset( + self.kind(), + pkt.header.sequence_number, + pkt.header.timestamp, + ) + }; + + pkt.header.sequence_number = seq_number; + pkt.header.timestamp = timestamp; + self.write_rtp_with_extensions_to(&pkt, &[], binding_ssrc) .await } @@ -300,7 +394,11 @@ impl TrackLocal for TrackLocalStaticRTP { /// This asserts that the code requested is supported by the remote peer. /// If so it setups all the state (SSRC and PayloadType) to have a call async fn bind(&self, t: &TrackLocalContext) -> Result { - println!("bind: mid - {:?}; {:?}", t.mid(), t.ssrc()); + println!( + "TrackLocalStaticRTP.bind: mid={:?}; ssrc={:?}", + t.mid(), + t.ssrc() + ); let parameters = RTCRtpCodecParameters { capability: self.codec.clone(), ..Default::default() @@ -356,7 +454,11 @@ impl TrackLocal for TrackLocalStaticRTP { /// unbind implements the teardown logic when the track is no longer needed. This happens /// because a track has been stopped. async fn unbind(&self, t: &TrackLocalContext) -> Result<()> { - println!("unbind: mid-{:?}; {:?}", t.mid(), t.ssrc()); + println!( + "TrackLocalStaticRTP.unbind: mid={:?}; ssrc={:?}", + t.mid(), + t.ssrc() + ); let mut bindings = self.bindings.lock().await; let mut idx = None; for (index, binding) in bindings.iter().enumerate() { From 16acb58f2f878ddffd36bb28ea13670cb6d4017c Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Fri, 10 Oct 2025 19:33:53 +0300 Subject: [PATCH 05/15] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BA=D1=8D=D1=88=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webrtc/src/track/track_local/mod.rs | 4 ++ webrtc/src/track/track_local/packet_cache.rs | 1 + .../track_local/track_local_static_rtp.rs | 69 ++++++++++++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/webrtc/src/track/track_local/mod.rs b/webrtc/src/track/track_local/mod.rs index 4ccc4e618..8b1c1ae4d 100644 --- a/webrtc/src/track/track_local/mod.rs +++ b/webrtc/src/track/track_local/mod.rs @@ -136,6 +136,10 @@ impl TrackBinding { pub fn is_sender_paused(&self) -> bool { self.sender_paused.load(Ordering::SeqCst) } + + pub fn set_sender_paused(&self, sender_paused: bool) { + self.sender_paused.store(sender_paused, Ordering::SeqCst); + } } pub(crate) struct InterceptorToTrackLocalWriter { diff --git a/webrtc/src/track/track_local/packet_cache.rs b/webrtc/src/track/track_local/packet_cache.rs index 15901a405..0c4d219a2 100644 --- a/webrtc/src/track/track_local/packet_cache.rs +++ b/webrtc/src/track/track_local/packet_cache.rs @@ -66,6 +66,7 @@ impl PCacheBuffer { } // Вспомогательная функция разворачивания NACK-пар (packet_id + bitmask -> список seq) +// Разворачиваем список потерянных SN из NACK-пар pub fn expand_nack_pairs(pairs: &[NackPair]) -> Vec { let mut out = Vec::with_capacity(pairs.len() * 8); for p in pairs { diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index 2829b3f33..22d3e8686 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -1,6 +1,7 @@ use bytes::{Bytes, BytesMut}; use rtp::packet::Packet; use std::any::Any; +use std::sync::atomic::AtomicU64; use std::{borrow::Cow, collections::HashMap, time::Duration}; use tokio::sync::mpsc::error::TrySendError; use tokio::sync::{mpsc, Mutex}; @@ -94,10 +95,13 @@ pub struct TrackLocalStaticRTP { pub state: Mutex, pub rtp_cache: Arc, + + pli_last_ms: AtomicU64, + pli_interval_ms: u64, } /// Количество пакетов в кэше -const CAPACITY: usize = 128; // если 24 пакета в секунду, то на 3 секунды нужно 72 ячейки кэша +const CAPACITY: usize = 256; // если 24 пакета в секунду, то на 3 секунды нужно 72 ячейки кэша /// TTL в миллисекундах, время через которое кэш становится невалидным const TTL_MILLIS: u64 = 3000; @@ -117,6 +121,9 @@ impl TrackLocalStaticRTP { Duration::from_millis(TTL_MILLIS), CAPACITY, )), + + pli_last_ms: AtomicU64::new(0), + pli_interval_ms: 500, } } @@ -139,6 +146,9 @@ impl TrackLocalStaticRTP { Duration::from_millis(TTL_MILLIS), CAPACITY, )), + + pli_last_ms: AtomicU64::new(0), + pli_interval_ms: 500, } } @@ -161,6 +171,19 @@ impl TrackLocalStaticRTP { .all(|b| b.sender_paused.load(Ordering::SeqCst)) } + pub async fn is_binding_active(&self, binding_ssrc: u32) -> bool { + match { + let bindings = self.bindings.lock().await; + bindings + .iter() + .find(|b| b.ssrc == binding_ssrc) + .map(|b| b.clone()) + } { + Some(b) => !b.is_sender_paused(), + None => false, + } + } + /// Выполняется, когда мы изменяем источник данных для трека pub async fn replace_remote(self: Arc, remote_track: Arc) { // 1. Приводим исходящее смещение к начальному состоянию, @@ -217,6 +240,11 @@ impl TrackLocalStaticRTP { bindings.iter().map(|b| b.ssrc).collect() } + pub async fn bindings_ids(&self) -> Vec { + let bindings = self.bindings.lock().await; + bindings.iter().map(|b| b.id.clone()).collect() + } + pub async fn write_rtp_with_extensions_to( &self, p: &rtp::packet::Packet, @@ -348,6 +376,45 @@ impl TrackLocalStaticRTP { .await } + pub async fn set_muted(&self, muted: bool) { + let bindings = { + let bindings = self.bindings.lock().await; + bindings.clone() + }; + bindings.iter().for_each(|b| { + b.set_sender_paused(muted); + }); + } + + pub async fn set_muted_for(&self, muted: bool, bindings_ssrc: Vec) { + let bindings = { + let bindings = self.bindings.lock().await; + bindings.clone() + }; + bindings.iter().for_each(|b| { + if bindings_ssrc.contains(&b.ssrc) { + b.set_sender_paused(muted); + } + }); + } + + pub fn should_fire_pli(&self, now_ms: u64) -> bool { + loop { + let prev = self.pli_last_ms.load(Ordering::Relaxed); + if now_ms.saturating_sub(prev) < self.pli_interval_ms { + return false; + } + if self + .pli_last_ms + .compare_exchange(prev, now_ms, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + return true; + } + // кто-то другой успел обновить last_ms — пробуем снова + } + } + async fn write_rtp_with_extensions_to_binding( &self, p: &rtp::packet::Packet, From 45845cc0e3bae77edf68e5b2fd5e329f8c1db387 Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Sat, 11 Oct 2025 19:39:56 +0300 Subject: [PATCH 06/15] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B8=20mut?= =?UTF-8?q?ed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webrtc/src/track/track_local/track_local_static_rtp.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index 22d3e8686..d0a0f3bf1 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -386,14 +386,14 @@ impl TrackLocalStaticRTP { }); } - pub async fn set_muted_for(&self, muted: bool, bindings_ssrc: Vec) { + pub async fn set_muted_for(&self, bindings_ssrc: Vec<(u32, bool)>) { let bindings = { let bindings = self.bindings.lock().await; bindings.clone() }; bindings.iter().for_each(|b| { - if bindings_ssrc.contains(&b.ssrc) { - b.set_sender_paused(muted); + if let Some((_, muted)) = bindings_ssrc.iter().find(|(ssrc, _)| *ssrc == b.ssrc) { + b.set_sender_paused(*muted); } }); } From e1ba19a65c8ff2b013b5938ab4b96386068f51aa Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Tue, 14 Oct 2025 09:19:30 +0300 Subject: [PATCH 07/15] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D1=8B=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B8=D0=B7=D0=B2=D0=BE=D0=B4=D0=B8=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D0=B8=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B8=20=D1=87=D1=82=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20rtp/rtcp=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 39 +- interceptor/src/report/receiver/mod.rs | 2 + interceptor/src/report/sender/mod.rs | 2 + webrtc/Cargo.toml | 4 + .../fmtp copy/generic/generic_test.rs | 160 +++ .../rtp_transceiver/fmtp copy/generic/mod.rs | 63 ++ .../fmtp copy/h264/h264_test.rs | 163 ++++ .../src/rtp_transceiver/fmtp copy/h264/mod.rs | 99 ++ webrtc/src/rtp_transceiver/fmtp copy/mod.rs | 58 ++ .../src/rtp_transceiver/rtp_receiver/mod.rs | 203 ++-- .../rtp_transceiver/rtp_receiver/mod_copy.rs | 908 ++++++++++++++++++ .../rtp_transceiver/rtp_receiver/mod_copy2.rs | 877 +++++++++++++++++ .../rtp_receiver/rtp_receiver_test.rs | 319 ++++++ webrtc/src/track/track_local/mod.rs | 3 - webrtc/src/track/track_local/packet_cache.rs | 267 ++++- .../track/track_local/track_local_simple.rs | 10 - .../track_local/track_local_static_rtp.rs | 308 +++--- .../track_local/track_local_static_test.rs | 434 --------- webrtc/src/track/track_remote/mod.rs | 11 + 19 files changed, 3243 insertions(+), 687 deletions(-) create mode 100644 webrtc/src/rtp_transceiver/fmtp copy/generic/generic_test.rs create mode 100644 webrtc/src/rtp_transceiver/fmtp copy/generic/mod.rs create mode 100644 webrtc/src/rtp_transceiver/fmtp copy/h264/h264_test.rs create mode 100644 webrtc/src/rtp_transceiver/fmtp copy/h264/mod.rs create mode 100644 webrtc/src/rtp_transceiver/fmtp copy/mod.rs create mode 100644 webrtc/src/rtp_transceiver/rtp_receiver/mod_copy.rs create mode 100644 webrtc/src/rtp_transceiver/rtp_receiver/mod_copy2.rs delete mode 100644 webrtc/src/track/track_local/track_local_static_test.rs diff --git a/Cargo.lock b/Cargo.lock index ccd13ff55..d829402bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -659,6 +659,20 @@ dependencies = [ "syn", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.9.0" @@ -1086,6 +1100,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.16.0" @@ -1473,11 +1493,10 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -1723,9 +1742,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1733,15 +1752,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -2821,12 +2840,16 @@ dependencies = [ "async-trait", "bytes", "chrono", + "crossbeam-epoch", + "crossbeam-utils", + "dashmap", "dtls", "env_logger", "hex", "interceptor", "lazy_static", "log", + "parking_lot", "pem", "portable-atomic", "rand", diff --git a/interceptor/src/report/receiver/mod.rs b/interceptor/src/report/receiver/mod.rs index fd80227c8..b13b0fe11 100644 --- a/interceptor/src/report/receiver/mod.rs +++ b/interceptor/src/report/receiver/mod.rs @@ -85,6 +85,8 @@ impl ReceiverReport { internal: Arc, ) -> Result<()> { let mut ticker = tokio::time::interval(internal.interval); + ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + let mut close_rx = { let mut close_rx = internal.close_rx.lock().await; if let Some(close) = close_rx.take() { diff --git a/interceptor/src/report/sender/mod.rs b/interceptor/src/report/sender/mod.rs index 94c7de630..76e92f1dc 100644 --- a/interceptor/src/report/sender/mod.rs +++ b/interceptor/src/report/sender/mod.rs @@ -47,6 +47,8 @@ impl SenderReport { internal: Arc, ) -> Result<()> { let mut ticker = tokio::time::interval(internal.interval); + ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + let mut close_rx = { let mut close_rx = internal.close_rx.lock().await; if let Some(close) = close_rx.take() { diff --git a/webrtc/Cargo.toml b/webrtc/Cargo.toml index e9930e7ef..a5560bfdd 100644 --- a/webrtc/Cargo.toml +++ b/webrtc/Cargo.toml @@ -12,6 +12,7 @@ readme = "../README.md" [dependencies] chrono = { version = "0.4.40", features = ["serde"] } +dashmap = "6.1.0" data = { version = "0.12.0", path = "../data", package = "webrtc-data" } dtls = { version = "0.13.0", path = "../dtls" } ice = { version = "0.14.0", path = "../ice", package = "webrtc-ice" } @@ -59,6 +60,9 @@ hex = "0.4" pem = { version = "3", optional = true } portable-atomic = "1.6" unicase = "2.8" +parking_lot = "0.12.5" +crossbeam-utils = "0.8.21" +crossbeam-epoch = "0.9.18" [dev-dependencies] env_logger = "0.11.3" diff --git a/webrtc/src/rtp_transceiver/fmtp copy/generic/generic_test.rs b/webrtc/src/rtp_transceiver/fmtp copy/generic/generic_test.rs new file mode 100644 index 000000000..f37c4af5c --- /dev/null +++ b/webrtc/src/rtp_transceiver/fmtp copy/generic/generic_test.rs @@ -0,0 +1,160 @@ +use super::*; + +#[test] +fn test_generic_fmtp_parse() { + let tests: Vec<(&str, &str, Box)> = vec![ + ( + "OneParam", + "key-name=value", + Box::new(GenericFmtp { + mime_type: "generic".to_owned(), + parameters: [("key-name".to_owned(), "value".to_owned())] + .iter() + .cloned() + .collect(), + }), + ), + ( + "OneParamWithWhiteSpeces", + "\tkey-name=value ", + Box::new(GenericFmtp { + mime_type: "generic".to_owned(), + parameters: [("key-name".to_owned(), "value".to_owned())] + .iter() + .cloned() + .collect(), + }), + ), + ( + "TwoParams", + "key-name=value;key2=value2", + Box::new(GenericFmtp { + mime_type: "generic".to_owned(), + parameters: [ + ("key-name".to_owned(), "value".to_owned()), + ("key2".to_owned(), "value2".to_owned()), + ] + .iter() + .cloned() + .collect(), + }), + ), + ( + "TwoParamsWithWhiteSpeces", + "key-name=value; \n\tkey2=value2 ", + Box::new(GenericFmtp { + mime_type: "generic".to_owned(), + parameters: [ + ("key-name".to_owned(), "value".to_owned()), + ("key2".to_owned(), "value2".to_owned()), + ] + .iter() + .cloned() + .collect(), + }), + ), + ]; + + for (name, input, expected) in tests { + let f = parse("generic", input); + assert_eq!(&f, &expected, "{name} failed"); + + assert_eq!(f.mime_type(), "generic"); + } +} + +#[test] +fn test_generic_fmtp_compare() { + let consist_string: HashMap = [ + (true, "consist".to_owned()), + (false, "inconsist".to_owned()), + ] + .iter() + .cloned() + .collect(); + + let tests = vec![ + ( + "Equal", + "key1=value1;key2=value2;key3=value3", + "key1=value1;key2=value2;key3=value3", + true, + ), + ( + "EqualWithWhitespaceVariants", + "key1=value1;key2=value2;key3=value3", + " key1=value1; \nkey2=value2;\t\nkey3=value3", + true, + ), + ( + "EqualWithCase", + "key1=value1;key2=value2;key3=value3", + "key1=value1;key2=Value2;Key3=value3", + true, + ), + ( + "OneHasExtraParam", + "key1=value1;key2=value2;key3=value3", + "key1=value1;key2=value2;key3=value3;key4=value4", + true, + ), + ( + "Inconsistent", + "key1=value1;key2=value2;key3=value3", + "key1=value1;key2=different_value;key3=value3", + false, + ), + ( + "Inconsistent_OneHasExtraParam", + "key1=value1;key2=value2;key3=value3;key4=value4", + "key1=value1;key2=different_value;key3=value3", + false, + ), + ]; + + for (name, a, b, consist) in tests { + let check = |a, b| { + let aa = parse("", a); + let bb = parse("", b); + + // test forward case here + let c = aa.match_fmtp(&*bb); + assert_eq!( + c, + consist, + "{}: '{}' and '{}' are expected to be {:?}, but treated as {:?}", + name, + a, + b, + consist_string.get(&consist), + consist_string.get(&c), + ); + + // test reverse case here + let c = bb.match_fmtp(&*aa); + assert_eq!( + c, + consist, + "{}: '{}' and '{}' are expected to be {:?}, but treated as {:?}", + name, + a, + b, + consist_string.get(&consist), + consist_string.get(&c), + ); + }; + + check(a, b); + } +} + +#[test] +fn test_generic_fmtp_compare_mime_type_case_mismatch() { + let a = parse("video/vp8", ""); + let b = parse("video/VP8", ""); + + assert!( + b.match_fmtp(&*a), + "fmtp lines should match even if they use different casing" + ); +} diff --git a/webrtc/src/rtp_transceiver/fmtp copy/generic/mod.rs b/webrtc/src/rtp_transceiver/fmtp copy/generic/mod.rs new file mode 100644 index 000000000..f604a5ed3 --- /dev/null +++ b/webrtc/src/rtp_transceiver/fmtp copy/generic/mod.rs @@ -0,0 +1,63 @@ +#[cfg(test)] +mod generic_test; + +use unicase::UniCase; + +use super::*; + +/// fmtp_consist checks that two FMTP parameters are not inconsistent. +fn fmtp_consist(a: &HashMap, b: &HashMap) -> bool { + for (k, v) in a { + if let Some(vb) = b.get(k) { + if UniCase::new(v) != UniCase::new(vb) { + return false; + } + } + } + for (k, v) in b { + if let Some(va) = a.get(k) { + if UniCase::new(v) != UniCase::new(va) { + return false; + } + } + } + true +} + +#[derive(Debug, PartialEq)] +pub(crate) struct GenericFmtp { + pub(crate) mime_type: String, + pub(crate) parameters: HashMap, +} + +impl Fmtp for GenericFmtp { + fn mime_type(&self) -> &str { + self.mime_type.as_str() + } + + /// Match returns true if g and b are compatible fmtp descriptions + /// The generic implementation is used for MimeTypes that are not defined + fn match_fmtp(&self, f: &dyn Fmtp) -> bool { + if let Some(c) = f.as_any().downcast_ref::() { + if self.mime_type.to_lowercase() != c.mime_type().to_lowercase() { + return false; + } + + fmtp_consist(&self.parameters, &c.parameters) + } else { + false + } + } + + fn parameter(&self, key: &str) -> Option<&String> { + self.parameters.get(key) + } + + fn equal(&self, other: &dyn Fmtp) -> bool { + other.as_any().downcast_ref::() == Some(self) + } + + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/webrtc/src/rtp_transceiver/fmtp copy/h264/h264_test.rs b/webrtc/src/rtp_transceiver/fmtp copy/h264/h264_test.rs new file mode 100644 index 000000000..fe97dbe05 --- /dev/null +++ b/webrtc/src/rtp_transceiver/fmtp copy/h264/h264_test.rs @@ -0,0 +1,163 @@ +use super::*; + +#[test] +fn test_h264_fmtp_parse() { + let tests: Vec<(&str, &str, Box)> = vec![ + ( + "OneParam", + "key-name=value", + Box::new(H264Fmtp { + parameters: [("key-name".to_owned(), "value".to_owned())] + .iter() + .cloned() + .collect(), + }), + ), + ( + "OneParamWithWhiteSpeces", + "\tkey-name=value ", + Box::new(H264Fmtp { + parameters: [("key-name".to_owned(), "value".to_owned())] + .iter() + .cloned() + .collect(), + }), + ), + ( + "TwoParams", + "key-name=value;key2=value2", + Box::new(H264Fmtp { + parameters: [ + ("key-name".to_owned(), "value".to_owned()), + ("key2".to_owned(), "value2".to_owned()), + ] + .iter() + .cloned() + .collect(), + }), + ), + ( + "TwoParamsWithWhiteSpeces", + "key-name=value; \n\tkey2=value2 ", + Box::new(H264Fmtp { + parameters: [ + ("key-name".to_owned(), "value".to_owned()), + ("key2".to_owned(), "value2".to_owned()), + ] + .iter() + .cloned() + .collect(), + }), + ), + ]; + + for (name, input, expected) in tests { + let f = parse("video/h264", input); + assert_eq!(&f, &expected, "{name} failed"); + + assert_eq!(f.mime_type(), "video/h264"); + } +} + +#[test] +fn test_h264_fmtp_compare() { + let consist_string: HashMap = [ + (true, "consist".to_owned()), + (false, "inconsist".to_owned()), + ] + .iter() + .cloned() + .collect(); + + let tests = vec![ + ( + "Equal", + "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", + "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", + true, + ), + ( + "EqualWithWhitespaceVariants", + "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", + " level-asymmetry-allowed=1; \npacketization-mode=1;\t\nprofile-level-id=42e01f", + true, + ), + ( + "EqualWithCase", + "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", + "level-asymmetry-allowed=1;packetization-mode=1;PROFILE-LEVEL-ID=42e01f", + true, + ), + ( + "OneHasExtraParam", + "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", + "packetization-mode=1;profile-level-id=42e01f", + true, + ), + ( + "DifferentProfileLevelIDVersions", + "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", + "packetization-mode=1;profile-level-id=42e029", + true, + ), + ( + "Inconsistent", + "packetization-mode=1;profile-level-id=42e029", + "packetization-mode=0;profile-level-id=42e029", + false, + ), + ( + "Inconsistent_MissingPacketizationMode", + "packetization-mode=1;profile-level-id=42e029", + "profile-level-id=42e029", + false, + ), + ( + "Inconsistent_MissingProfileLevelID", + "packetization-mode=1;profile-level-id=42e029", + "packetization-mode=1", + false, + ), + ( + "Inconsistent_InvalidProfileLevelID", + "packetization-mode=1;profile-level-id=42e029", + "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=41e029", + false, + ), + ]; + + for (name, a, b, consist) in tests { + let check = |a, b| { + let aa = parse("video/h264", a); + let bb = parse("video/h264", b); + + // test forward case here + let c = aa.match_fmtp(&*bb); + assert_eq!( + c, + consist, + "{}: '{}' and '{}' are expected to be {:?}, but treated as {:?}", + name, + a, + b, + consist_string.get(&consist), + consist_string.get(&c), + ); + + // test reverse case here + let c = bb.match_fmtp(&*aa); + assert_eq!( + c, + consist, + "{}: '{}' and '{}' are expected to be {:?}, but treated as {:?}", + name, + a, + b, + consist_string.get(&consist), + consist_string.get(&c), + ); + }; + + check(a, b); + } +} diff --git a/webrtc/src/rtp_transceiver/fmtp copy/h264/mod.rs b/webrtc/src/rtp_transceiver/fmtp copy/h264/mod.rs new file mode 100644 index 000000000..878ed73a0 --- /dev/null +++ b/webrtc/src/rtp_transceiver/fmtp copy/h264/mod.rs @@ -0,0 +1,99 @@ +#[cfg(test)] +mod h264_test; + +use super::*; + +fn profile_level_id_matches(a: &str, b: &str) -> bool { + let aa = match hex::decode(a) { + Ok(aa) => { + if aa.len() < 2 { + return false; + } + aa + } + Err(_) => return false, + }; + + let bb = match hex::decode(b) { + Ok(bb) => { + if bb.len() < 2 { + return false; + } + bb + } + Err(_) => return false, + }; + + aa[0] == bb[0] && aa[1] == bb[1] +} + +#[derive(Debug, PartialEq)] +pub(crate) struct H264Fmtp { + pub(crate) parameters: HashMap, +} + +impl Fmtp for H264Fmtp { + fn mime_type(&self) -> &str { + "video/h264" + } + + /// Match returns true if h and b are compatible fmtp descriptions + /// Based on RFC6184 Section 8.2.2: + /// The parameters identifying a media format configuration for H.264 + /// are profile-level-id and packetization-mode. These media format + /// configuration parameters (except for the level part of profile- + /// level-id) MUST be used symmetrically; that is, the answerer MUST + /// either maintain all configuration parameters or remove the media + /// format (payload type) completely if one or more of the parameter + /// values are not supported. + /// Informative note: The requirement for symmetric use does not + /// apply for the level part of profile-level-id and does not apply + /// for the other stream properties and capability parameters. + fn match_fmtp(&self, f: &dyn Fmtp) -> bool { + if let Some(c) = f.as_any().downcast_ref::() { + // test packetization-mode + let hpmode = match self.parameters.get("packetization-mode") { + Some(s) => s, + None => return false, + }; + let cpmode = match c.parameters.get("packetization-mode") { + Some(s) => s, + None => return false, + }; + + if hpmode != cpmode { + return false; + } + + // test profile-level-id + let hplid = match self.parameters.get("profile-level-id") { + Some(s) => s, + None => return false, + }; + let cplid = match c.parameters.get("profile-level-id") { + Some(s) => s, + None => return false, + }; + + if !profile_level_id_matches(hplid, cplid) { + return false; + } + + true + } else { + false + } + } + + fn parameter(&self, key: &str) -> Option<&String> { + self.parameters.get(key) + } + + fn equal(&self, other: &dyn Fmtp) -> bool { + other.as_any().downcast_ref::() == Some(self) + } + + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/webrtc/src/rtp_transceiver/fmtp copy/mod.rs b/webrtc/src/rtp_transceiver/fmtp copy/mod.rs new file mode 100644 index 000000000..92757fa03 --- /dev/null +++ b/webrtc/src/rtp_transceiver/fmtp copy/mod.rs @@ -0,0 +1,58 @@ +pub(crate) mod generic; +pub(crate) mod h264; + +use std::any::Any; +use std::collections::HashMap; +use std::fmt; + +use crate::rtp_transceiver::fmtp::generic::GenericFmtp; +use crate::rtp_transceiver::fmtp::h264::H264Fmtp; + +/// Fmtp interface for implementing custom +/// Fmtp parsers based on mime_type +pub trait Fmtp: fmt::Debug { + /// mime_type returns the mime_type associated with + /// the fmtp + fn mime_type(&self) -> &str; + + /// match_fmtp compares two fmtp descriptions for + /// compatibility based on the mime_type + fn match_fmtp(&self, f: &dyn Fmtp) -> bool; + + /// parameter returns a value for the associated key + /// if contained in the parsed fmtp string + fn parameter(&self, key: &str) -> Option<&String>; + + fn equal(&self, other: &dyn Fmtp) -> bool; + fn as_any(&self) -> &dyn Any; +} + +impl PartialEq for dyn Fmtp { + fn eq(&self, other: &Self) -> bool { + self.equal(other) + } +} + +/// parse parses an fmtp string based on the MimeType +pub fn parse(mime_type: &str, line: &str) -> Box { + let mut parameters = HashMap::new(); + for p in line.split(';').collect::>() { + let pp: Vec<&str> = p.trim().splitn(2, '=').collect(); + let key = pp[0].to_lowercase(); + let value = if pp.len() > 1 { + pp[1].to_owned() + } else { + String::new() + }; + parameters.insert(key, value); + } + + if mime_type.to_uppercase() == "video/h264".to_uppercase() { + Box::new(H264Fmtp { parameters }) + } else { + Box::new(GenericFmtp { + mime_type: mime_type.to_owned(), + parameters, + }) + } +} diff --git a/webrtc/src/rtp_transceiver/rtp_receiver/mod.rs b/webrtc/src/rtp_transceiver/rtp_receiver/mod.rs index 077f64751..2b606d007 100644 --- a/webrtc/src/rtp_transceiver/rtp_receiver/mod.rs +++ b/webrtc/src/rtp_transceiver/rtp_receiver/mod.rs @@ -7,7 +7,6 @@ use std::sync::Arc; use arc_swap::ArcSwapOption; use interceptor::stream_info::{AssociatedStreamInfo, RTPHeaderExtension}; use interceptor::Interceptor; -use log::trace; use smol_str::SmolStr; use tokio::sync::{watch, Mutex, RwLock}; @@ -168,24 +167,51 @@ impl RTPReceiverInternal { // isn't flowing. State::wait_for(&mut state_watch_rx, &[State::Started, State::Paused]).await?; - let tracks = self.tracks.read().await; - if let Some(t) = tracks.first() { - if let Some(rtcp_interceptor) = &t.stream.rtcp_interceptor { - loop { - tokio::select! { - res = State::error_on_close(&mut state_watch_rx) => { - res? - } - result = rtcp_interceptor.read(b) => { - return Ok(result?) - } - } + // let tracks = self.tracks.read().await; + // if let Some(t) = tracks.first() { + // if let Some(rtcp_interceptor) = &t.stream.rtcp_interceptor { + // loop { + // tokio::select! { + // res = State::error_on_close(&mut state_watch_rx) => { + // res? + // } + // result = rtcp_interceptor.read(b) => { + // return Ok(result?) + // } + // } + // } + // } else { + // Err(Error::ErrInterceptorNotBind) + // } + // } else { + // Err(Error::ErrExistingTrack) + // } + + // Это работает чуть быстрее, чем если использовать код выше + let rtcp_interceptor = { + let tracks = self.tracks.read().await; // Блокировка захвачена + + // TODO: возможно, нужна логика выбора трека на основе симулкаст предпочтения + // Вместо того, чтоб просто брать первый трек, или читать оповещения от всех треков + tracks + .first() + .ok_or(Error::ErrExistingTrack)? + .stream + .rtcp_interceptor + .as_ref() + .ok_or(Error::ErrInterceptorNotBind)? + .clone() + }; + + loop { + tokio::select! { + res = State::error_on_close(&mut state_watch_rx) => { + res? + } + result = rtcp_interceptor.read(b) => { + return Ok(result?) } - } else { - Err(Error::ErrInterceptorNotBind) } - } else { - Err(Error::ErrExistingTrack) } } @@ -230,9 +256,8 @@ impl RTPReceiverInternal { receive_mtu: usize, ) -> Result>> { let mut b = vec![0u8; receive_mtu]; - let pkts = self.read(&mut b).await?; - Ok(pkts) + Ok(self.read(&mut b).await?) } /// read_simulcast_rtcp is a convenience method that wraps ReadSimulcast and unmarshal for you @@ -248,60 +273,122 @@ impl RTPReceiverInternal { } pub(crate) async fn read_rtp(&self, b: &mut [u8], tid: usize) -> Result { + // println!("Начали чтение"); + // let mut state_watch_rx = self.state_tx.subscribe(); + + // // Ensure we are running. + // State::wait_for(&mut state_watch_rx, &[State::Started]).await?; + + // //log::debug!("read_rtp enter tracks tid {}", tid); + // let mut rtp_interceptor = None; + // //let mut ssrc = 0; + // { + // let tracks = self.tracks.read().await; + // for t in &*tracks { + // if t.track.tid() == tid { + // rtp_interceptor.clone_from(&t.stream.rtp_interceptor); + // //ssrc = t.track.ssrc(); + // break; + // } + // } + // }; + // /*log::debug!( + // "read_rtp exit tracks with rtp_interceptor {} with tid {}", + // rtp_interceptor.is_some(), + // tid, + // );*/ + // if let Some(rtp_interceptor) = rtp_interceptor { + // //println!( + // // "read_rtp rtp_interceptor.read enter with tid {} ssrc {}", + // // tid, ssrc + // //); + // let mut current_state = *state_watch_rx.borrow(); + // loop { + // tokio::select! { + // _ = state_watch_rx.changed() => { + // let new_state = *state_watch_rx.borrow(); + + // if new_state == State::Stopped { + // return Err(Error::ErrClosedPipe); + // } + // current_state = new_state; + // } + // result = rtp_interceptor.read(b) => { + // let result = result?; + + // if current_state == State::Paused { + // log::trace!("Dropping {} read bytes received while RTPReceiver was paused", result); + // continue; + // } + // return Ok(result); + // } + // } + // } + // } else { + // //log::debug!("read_rtp exit tracks with ErrRTPReceiverWithSSRCTrackStreamNotFound"); + // Err(Error::ErrRTPReceiverWithSSRCTrackStreamNotFound) + // } + let mut state_watch_rx = self.state_tx.subscribe(); // Ensure we are running. State::wait_for(&mut state_watch_rx, &[State::Started]).await?; - //log::debug!("read_rtp enter tracks tid {}", tid); - let mut rtp_interceptor = None; - //let mut ssrc = 0; - { + // println!("read_rtp enter tracks tid {}", tid); + let rtp_interceptor = { let tracks = self.tracks.read().await; - for t in &*tracks { - if t.track.tid() == tid { - rtp_interceptor.clone_from(&t.stream.rtp_interceptor); - //ssrc = t.track.ssrc(); - break; + tracks + .iter() + .find(|t| t.track.tid() == tid) + .ok_or(Error::ErrRTPReceiverWithSSRCTrackStreamNotFound)? + .stream + .rtp_interceptor + .as_ref() + .ok_or(Error::ErrRTPReceiverWithSSRCTrackStreamNotFound)? + .clone() + }; + + loop { + let current_state = *state_watch_rx.borrow(); + + match current_state { + State::Stopped => { + return Err(Error::ErrClosedPipe); + } + State::Started => {} + _ => { + State::wait_for(&mut state_watch_rx, &[State::Started]).await?; } } - }; - /*log::debug!( - "read_rtp exit tracks with rtp_interceptor {} with tid {}", - rtp_interceptor.is_some(), - tid, - );*/ - - if let Some(rtp_interceptor) = rtp_interceptor { - //println!( - // "read_rtp rtp_interceptor.read enter with tid {} ssrc {}", - // tid, ssrc - //); - let mut current_state = *state_watch_rx.borrow(); - loop { - tokio::select! { - _ = state_watch_rx.changed() => { - let new_state = *state_watch_rx.borrow(); - - if new_state == State::Stopped { - return Err(Error::ErrClosedPipe); - } - current_state = new_state; + + tokio::select! { + res = state_watch_rx.changed() => { + if let Err(_) = res { + return Err(Error::ErrClosedPipe); + } + let new_state = *state_watch_rx.borrow(); + + if new_state == State::Stopped { + return Err(Error::ErrClosedPipe); } - result = rtp_interceptor.read(b) => { - let result = result?; + continue; + } + result = rtp_interceptor.read(b) => { + let result = result?; - if current_state == State::Paused { - trace!("Dropping {} read bytes received while RTPReceiver was paused", result); + let current_state = *state_watch_rx.borrow(); + match current_state { + State::Stopped => { + return Err(Error::ErrClosedPipe); + } + State::Paused => { continue; } - return Ok(result); + _ => {} } + return Ok(result); } } - } else { - //log::debug!("read_rtp exit tracks with ErrRTPReceiverWithSSRCTrackStreamNotFound"); - Err(Error::ErrRTPReceiverWithSSRCTrackStreamNotFound) } } diff --git a/webrtc/src/rtp_transceiver/rtp_receiver/mod_copy.rs b/webrtc/src/rtp_transceiver/rtp_receiver/mod_copy.rs new file mode 100644 index 000000000..45025e17b --- /dev/null +++ b/webrtc/src/rtp_transceiver/rtp_receiver/mod_copy.rs @@ -0,0 +1,908 @@ +#[cfg(test)] +mod rtp_receiver_test; + +use std::fmt; +use std::sync::Arc; + +use arc_swap::ArcSwapOption; +use dashmap::DashMap; +use interceptor::stream_info::{AssociatedStreamInfo, RTPHeaderExtension}; +use interceptor::Interceptor; +use log::trace; +use smol_str::SmolStr; +use tokio::sync::{watch, Mutex}; + +use crate::api::media_engine::MediaEngine; +use crate::dtls_transport::RTCDtlsTransport; +use crate::error::{flatten_errs, Error, Result}; +use crate::peer_connection::sdp::TrackDetails; +use crate::rtp_transceiver::rtp_codec::{ + codec_parameters_fuzzy_search, CodecMatch, RTCRtpCodecParameters, RTCRtpParameters, + RTPCodecType, +}; +use crate::rtp_transceiver::rtp_transceiver_direction::RTCRtpTransceiverDirection; +use crate::rtp_transceiver::{ + codec_rtx_search, create_stream_info, RTCRtpDecodingParameters, RTCRtpReceiveParameters, SSRC, +}; +use crate::track::track_remote::TrackRemote; +use crate::track::{TrackStream, TrackStreams}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum State { + /// We haven't started yet. + Unstarted = 0, + /// We haven't started yet and additionally we've been paused. + UnstartedPaused = 1, + + /// We have started and are running. + Started = 2, + + /// We have been paused after starting. + Paused = 3, + + /// We have been stopped. + Stopped = 4, +} + +impl From for State { + fn from(value: u8) -> Self { + match value { + v if v == State::Unstarted as u8 => State::Unstarted, + v if v == State::UnstartedPaused as u8 => State::UnstartedPaused, + v if v == State::Started as u8 => State::Started, + v if v == State::Paused as u8 => State::Paused, + v if v == State::Stopped as u8 => State::Stopped, + _ => unreachable!( + "Invalid serialization of {}: {}", + std::any::type_name::(), + value + ), + } + } +} + +impl fmt::Display for State { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + State::Unstarted => write!(f, "Unstarted"), + State::UnstartedPaused => write!(f, "UnstartedPaused"), + State::Started => write!(f, "Running"), + State::Paused => write!(f, "Paused"), + State::Stopped => write!(f, "Closed"), + } + } +} + +impl State { + fn transition(to: Self, tx: &watch::Sender) -> Result<()> { + let current = *tx.borrow(); + if current == to { + // Already in this state + return Ok(()); + } + + match current { + Self::Unstarted + if matches!(to, Self::Started | Self::Stopped | Self::UnstartedPaused) => + { + let _ = tx.send(to); + return Ok(()); + } + Self::UnstartedPaused + if matches!(to, Self::Unstarted | Self::Stopped | Self::Paused) => + { + let _ = tx.send(to); + return Ok(()); + } + State::Started if matches!(to, Self::Paused | Self::Stopped) => { + let _ = tx.send(to); + return Ok(()); + } + State::Paused if matches!(to, Self::Started | Self::Stopped) => { + let _ = tx.send(to); + return Ok(()); + } + _ => {} + } + + Err(Error::ErrRTPReceiverStateChangeInvalid { from: current, to }) + } + + async fn wait_for(rx: &mut watch::Receiver, states: &[State]) -> Result<()> { + loop { + let state = *rx.borrow(); + + match state { + _ if states.contains(&state) => return Ok(()), + State::Stopped => { + return Err(Error::ErrClosedPipe); + } + _ => {} + } + + if rx.changed().await.is_err() { + return Err(Error::ErrClosedPipe); + } + } + } + + async fn error_on_close(rx: &mut watch::Receiver) -> Result<()> { + if rx.changed().await.is_err() { + return Err(Error::ErrClosedPipe); + } + + let state = *rx.borrow(); + if state == State::Stopped { + return Err(Error::ErrClosedPipe); + } + + Ok(()) + } + + fn is_started(&self) -> bool { + matches!(self, Self::Started | Self::Paused) + } +} + +pub struct RTPReceiverInternal { + pub(crate) kind: RTPCodecType, + + // State is stored within the channel + state_tx: watch::Sender, + state_rx: watch::Receiver, + + tracks: DashMap, + + transceiver_codecs: ArcSwapOption>>, + + transport: Arc, + media_engine: Arc, + interceptor: Arc, +} + +impl RTPReceiverInternal { + /// read reads incoming RTCP for this RTPReceiver + async fn read(&self, b: &mut [u8]) -> Result>> { + let mut state_watch_rx = self.state_tx.subscribe(); + // Ensure we are running or paused. When paused we still receive RTCP even if RTP traffic + // isn't flowing. + State::wait_for(&mut state_watch_rx, &[State::Started, State::Paused]).await?; + + // let tracks = self.tracks.read().await; + // if let Some(t) = tracks.first() { + // if let Some(rtcp_interceptor) = &t.stream.rtcp_interceptor { + // loop { + // tokio::select! { + // res = State::error_on_close(&mut state_watch_rx) => { + // res? + // } + // result = rtcp_interceptor.read(b) => { + // return Ok(result?) + // } + // } + // } + // } else { + // Err(Error::ErrInterceptorNotBind) + // } + // } else { + // Err(Error::ErrExistingTrack) + // } + + let rtcp_interceptor = { + let t = self.tracks.get(&0).ok_or(Error::ErrExistingTrack)?; + + // TODO: возможно, нужна логика выбора трека на основе симулкаст предпочтения + // Вместо того, чтоб просто брать первый трек, или читать оповещения от всех треков + t.stream + .rtcp_interceptor + .as_ref() + .ok_or(Error::ErrInterceptorNotBind)? + .clone() + }; + + loop { + tokio::select! { + res = State::error_on_close(&mut state_watch_rx) => { + res? + } + result = rtcp_interceptor.read(b) => { + return Ok(result?) + } + } + } + } + + /// read_simulcast reads incoming RTCP for this RTPReceiver for given rid + async fn read_simulcast( + &self, + b: &mut [u8], + rid: &str, + ) -> Result>> { + let mut state_watch_rx = self.state_tx.subscribe(); + + // Ensure we are running or paused. When paused we still receive RTCP even if RTP traffic + // isn't flowing. + State::wait_for(&mut state_watch_rx, &[State::Started, State::Paused]).await?; + + // let tracks = self.tracks.read().await; + let tracks = self.tracks.iter_mut().collect::>(); + for mut tv in tracks { + let t = tv.value_mut(); + if t.track.rid() == rid { + if let Some(rtcp_interceptor) = &t.stream.rtcp_interceptor { + loop { + tokio::select! { + res = State::error_on_close(&mut state_watch_rx) => { + res? + } + result = rtcp_interceptor.read(b) => { + return Ok(result?); + } + } + } + } else { + return Err(Error::ErrInterceptorNotBind); + } + } + } + Err(Error::ErrRTPReceiverForRIDTrackStreamNotFound) + } + + /// read_rtcp is a convenience method that wraps Read and unmarshal for you. + /// It also runs any configured interceptors. + async fn read_rtcp( + &self, + receive_mtu: usize, + ) -> Result>> { + let mut b = vec![0u8; receive_mtu]; + let pkts = self.read(&mut b).await?; + + Ok(pkts) + } + + /// read_simulcast_rtcp is a convenience method that wraps ReadSimulcast and unmarshal for you + async fn read_simulcast_rtcp( + &self, + rid: &str, + receive_mtu: usize, + ) -> Result>> { + let mut b = vec![0u8; receive_mtu]; + let pkts = self.read_simulcast(&mut b, rid).await?; + + Ok(pkts) + } + + pub(crate) async fn read_rtp(&self, b: &mut [u8], tid: usize) -> Result { + let mut state_watch_rx = self.state_tx.subscribe(); + + // Ensure we are running. + State::wait_for(&mut state_watch_rx, &[State::Started]).await?; + + //log::debug!("read_rtp enter tracks tid {}", tid); + let mut rtp_interceptor = None; + //let mut ssrc = 0; + { + let tracks = self.tracks.iter_mut().collect::>(); + for mut tv in tracks { + let t = tv.value_mut(); + if t.track.tid() == tid { + rtp_interceptor.clone_from(&t.stream.rtp_interceptor); + //ssrc = t.track.ssrc(); + break; + } + } + }; + /*log::debug!( + "read_rtp exit tracks with rtp_interceptor {} with tid {}", + rtp_interceptor.is_some(), + tid, + );*/ + + if let Some(rtp_interceptor) = rtp_interceptor { + //println!( + // "read_rtp rtp_interceptor.read enter with tid {} ssrc {}", + // tid, ssrc + //); + let mut current_state = *state_watch_rx.borrow(); + loop { + tokio::select! { + _ = state_watch_rx.changed() => { + let new_state = *state_watch_rx.borrow(); + + if new_state == State::Stopped { + return Err(Error::ErrClosedPipe); + } + current_state = new_state; + } + result = rtp_interceptor.read(b) => { + let result = result?; + + if current_state == State::Paused { + trace!("Dropping {} read bytes received while RTPReceiver was paused", result); + continue; + } + return Ok(result); + } + } + } + } else { + //log::debug!("read_rtp exit tracks with ErrRTPReceiverWithSSRCTrackStreamNotFound"); + Err(Error::ErrRTPReceiverWithSSRCTrackStreamNotFound) + } + } + + async fn get_parameters(&self) -> RTCRtpParameters { + let mut parameters = self + .media_engine + .get_rtp_parameters_by_kind(self.kind, RTCRtpTransceiverDirection::Recvonly); + + let transceiver_codecs = self.transceiver_codecs.load(); + if let Some(codecs) = &*transceiver_codecs { + let mut c = codecs.lock().await; + parameters.codecs = + RTPReceiverInternal::get_codecs(&mut c, self.kind, &self.media_engine); + } + + parameters + } + + pub(crate) fn get_codecs( + codecs: &mut [RTCRtpCodecParameters], + kind: RTPCodecType, + media_engine: &Arc, + ) -> Vec { + let media_engine_codecs = media_engine.get_codecs_by_kind(kind); + if codecs.is_empty() { + return media_engine_codecs; + } + let mut filtered_codecs = vec![]; + for codec in codecs { + let (c, match_type) = codec_parameters_fuzzy_search(codec, &media_engine_codecs); + if match_type != CodecMatch::None { + if codec.payload_type == 0 { + codec.payload_type = c.payload_type; + } + filtered_codecs.push(codec.clone()); + } + } + + filtered_codecs + } + + // State + + /// Get the current state and a receiver for the next state change. + pub(crate) fn current_state(&self) -> State { + *self.state_rx.borrow() + } + + pub(crate) fn start(&self) -> Result<()> { + State::transition(State::Started, &self.state_tx) + } + + pub(crate) fn pause(&self) -> Result<()> { + let current = self.current_state(); + + match current { + State::Unstarted => State::transition(State::UnstartedPaused, &self.state_tx), + State::Started => State::transition(State::Paused, &self.state_tx), + _ => Ok(()), + } + } + + pub(crate) fn resume(&self) -> Result<()> { + let current = self.current_state(); + + match current { + State::UnstartedPaused => State::transition(State::Unstarted, &self.state_tx), + State::Paused => State::transition(State::Started, &self.state_tx), + _ => Ok(()), + } + } + + pub(crate) fn close(&self) -> Result<()> { + State::transition(State::Stopped, &self.state_tx) + } +} + +/// RTPReceiver allows an application to inspect the receipt of a TrackRemote +/// +/// ## Specifications +/// +/// * [MDN] +/// * [W3C] +/// +/// [MDN]: https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpReceiver +/// [W3C]: https://w3c.github.io/webrtc-pc/#rtcrtpreceiver-interface +pub struct RTCRtpReceiver { + receive_mtu: usize, + + pub internal: Arc, +} + +impl std::fmt::Debug for RTCRtpReceiver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RTCRtpReceiver") + .field("kind", &self.internal.kind) + .finish() + } +} + +impl RTCRtpReceiver { + pub fn new( + receive_mtu: usize, + kind: RTPCodecType, + transport: Arc, + media_engine: Arc, + interceptor: Arc, + ) -> Self { + let (state_tx, state_rx) = watch::channel(State::Unstarted); + + RTCRtpReceiver { + receive_mtu, + + internal: Arc::new(RTPReceiverInternal { + kind, + + // tracks: RwLock::new(vec![]), + tracks: DashMap::with_capacity(3), + transport, + media_engine, + interceptor, + + state_tx, + state_rx, + + transceiver_codecs: ArcSwapOption::new(None), + }), + } + } + + pub fn kind(&self) -> RTPCodecType { + self.internal.kind + } + + pub(crate) fn set_transceiver_codecs( + &self, + codecs: Option>>>, + ) { + self.internal.transceiver_codecs.store(codecs); + } + + /// transport returns the currently-configured *DTLSTransport or nil + /// if one has not yet been configured + pub fn transport(&self) -> Arc { + Arc::clone(&self.internal.transport) + } + + /// get_parameters describes the current configuration for the encoding and + /// transmission of media on the receiver's track. + pub async fn get_parameters(&self) -> RTCRtpParameters { + self.internal.get_parameters().await + } + + /// SetRTPParameters applies provided RTPParameters the RTPReceiver's tracks. + /// This method is part of the ORTC API. It is not + /// meant to be used together with the basic WebRTC API. + /// The amount of provided codecs must match the number of tracks on the receiver. + pub async fn set_rtp_parameters(&self, params: RTCRtpParameters) { + let mut header_extensions = vec![]; + for h in ¶ms.header_extensions { + header_extensions.push(RTPHeaderExtension { + id: h.id, + uri: h.uri.clone(), + }); + } + + // let mut tracks = self.internal.tracks.write().await; + for (idx, codec) in params.codecs.iter().enumerate() { + // let t = &mut tracks[idx]; + // if let Some(stream_info) = &mut t.stream.stream_info { + // stream_info + // .rtp_header_extensions + // .clone_from(&header_extensions); + // } + + // let current_track = &t.track; + // current_track.set_codec(codec.clone()); + // current_track.set_params(params.clone()); + + if let Some(mut t) = self.internal.tracks.get_mut(&idx) { + if let Some(stream_info) = &mut t.stream.stream_info { + stream_info + .rtp_header_extensions + .clone_from(&header_extensions); + } + + t.track.set_codec(codec.clone()); + t.track.set_params(params.clone()); + } + } + } + + /// tracks returns the RtpTransceiver traclockks + /// A RTPReceiver to support Simulcast may now have multiple tracks + pub async fn tracks(&self) -> Vec> { + self.internal + .tracks + .iter() + .map(|t| Arc::clone(&t.value().track)) + .collect() + } + + /// receive initialize the track and starts all the transports + pub async fn receive(&self, parameters: &RTCRtpReceiveParameters) -> Result<()> { + let receiver = Arc::downgrade(&self.internal); + + let current_state = self.internal.current_state(); + if current_state.is_started() { + return Err(Error::ErrRTPReceiverReceiveAlreadyCalled); + } + self.internal.start()?; + + let (global_params, interceptor, media_engine) = { + ( + self.internal.get_parameters().await, + Arc::clone(&self.internal.interceptor), + Arc::clone(&self.internal.media_engine), + ) + }; + + let codec = if let Some(codec) = global_params.codecs.first() { + codec.clone() + } else { + RTCRtpCodecParameters::default() + }; + + for (idx, encoding) in parameters.encodings.iter().enumerate() { + let (stream_info, rtp_read_stream, rtp_interceptor, rtcp_read_stream, rtcp_interceptor) = + if encoding.ssrc != 0 { + let stream_info = create_stream_info( + "".to_owned(), + encoding.ssrc, + 0, + codec.capability.clone(), + &global_params.header_extensions, + None, + ); + let (rtp_read_stream, rtp_interceptor, rtcp_read_stream, rtcp_interceptor) = + self.internal + .transport + .streams_for_ssrc(encoding.ssrc, &stream_info, &interceptor) + .await?; + + ( + Some(stream_info), + Some(rtp_read_stream), + Some(rtp_interceptor), + Some(rtcp_read_stream), + Some(rtcp_interceptor), + ) + } else { + (None, None, None, None, None) + }; + + let t = TrackStreams { + track: Arc::new(TrackRemote::new( + self.receive_mtu, + self.internal.kind, + encoding.ssrc, + encoding.rid.clone(), + receiver.clone(), + Arc::clone(&media_engine), + Arc::clone(&interceptor), + )), + stream: TrackStream { + stream_info, + rtp_read_stream, + rtp_interceptor, + rtcp_read_stream, + rtcp_interceptor, + }, + + repair_stream: TrackStream { + stream_info: None, + rtp_read_stream: None, + rtp_interceptor: None, + rtcp_read_stream: None, + rtcp_interceptor: None, + }, + }; + + { + // let mut tracks = self.internal.tracks.write().await; + // tracks.push(t.clone()); + + self.internal.tracks.insert(idx, t); + }; + + let rtx_ssrc = encoding.rtx.ssrc; + if rtx_ssrc != 0 { + let rtx_info = AssociatedStreamInfo { + ssrc: encoding.ssrc, + payload_type: 0, + }; + + let rtx_codec = + codec_rtx_search(&codec, &global_params.codecs).unwrap_or(codec.clone()); + + let stream_info = create_stream_info( + "".to_owned(), + rtx_ssrc, + 0, + rtx_codec.capability, + &global_params.header_extensions, + Some(rtx_info), + ); + let (rtp_read_stream, rtp_interceptor, rtcp_read_stream, rtcp_interceptor) = self + .internal + .transport + .streams_for_ssrc(rtx_ssrc, &stream_info, &interceptor) + .await?; + + self.receive_for_rtx( + rtx_ssrc, + "".to_owned(), + TrackStream { + stream_info: Some(stream_info), + rtp_read_stream: Some(rtp_read_stream), + rtp_interceptor: Some(rtp_interceptor), + rtcp_read_stream: Some(rtcp_read_stream), + rtcp_interceptor: Some(rtcp_interceptor), + }, + ) + .await?; + } + } + + Ok(()) + } + + /// read reads incoming RTCP for this RTPReceiver + pub async fn read( + &self, + b: &mut [u8], + ) -> Result>> { + self.internal.read(b).await + } + + /// read_simulcast reads incoming RTCP for this RTPReceiver for given rid + pub async fn read_simulcast( + &self, + b: &mut [u8], + rid: &str, + ) -> Result>> { + self.internal.read_simulcast(b, rid).await + } + + /// read_rtcp is a convenience method that wraps Read and unmarshal for you. + /// It also runs any configured interceptors. + pub async fn read_rtcp(&self) -> Result>> { + self.internal.read_rtcp(self.receive_mtu).await + } + + /// read_simulcast_rtcp is a convenience method that wraps ReadSimulcast and unmarshal for you + pub async fn read_simulcast_rtcp( + &self, + rid: &str, + ) -> Result>> { + self.internal + .read_simulcast_rtcp(rid, self.receive_mtu) + .await + } + + pub(crate) async fn have_received(&self) -> bool { + self.internal.current_state().is_started() + } + + pub(crate) async fn start(&self, incoming: &TrackDetails) { + let mut encoding_size = incoming.ssrcs.len(); + if incoming.rids.len() >= encoding_size { + encoding_size = incoming.rids.len(); + }; + + let mut encodings = vec![RTCRtpDecodingParameters::default(); encoding_size]; + for (i, encoding) in encodings.iter_mut().enumerate() { + if incoming.rids.len() > i { + encoding.rid = incoming.rids[i].clone(); + } + if incoming.ssrcs.len() > i { + encoding.ssrc = incoming.ssrcs[i]; + } + + encoding.rtx.ssrc = incoming.repair_ssrc; + } + + if let Err(err) = self.receive(&RTCRtpReceiveParameters { encodings }).await { + log::warn!("RTPReceiver Receive failed {err}"); + return; + } + + // set track id and label early so they can be set as new track information + // is received from the SDP. + let is_unpaused = self.current_state() == State::Started; + for track_remote in &self.tracks().await { + track_remote.set_id(incoming.id.clone()); + track_remote.set_stream_id(incoming.stream_id.clone()); + + if is_unpaused { + track_remote.fire_onunmute().await; + } + } + } + + /// Stop irreversibly stops the RTPReceiver + pub async fn stop(&self) -> Result<()> { + let previous_state = self.internal.current_state(); + self.internal.close()?; + + let mut errs = vec![]; + let was_ever_started = previous_state.is_started(); + if was_ever_started { + // let tracks = self.internal.tracks.write().await; + let keys: Vec<_> = self + .internal + .tracks + .iter() + .map(|kv| kv.key().clone()) + .collect(); + for k in keys { + if let Some(tv) = self.internal.tracks.get(&k) { + let t = tv.value(); + if let Some(rtcp_read_stream) = &t.stream.rtcp_read_stream { + if let Err(err) = rtcp_read_stream.close().await { + errs.push(err); + } + } + + if let Some(rtp_read_stream) = &t.stream.rtp_read_stream { + if let Err(err) = rtp_read_stream.close().await { + errs.push(err); + } + } + + if let Some(repair_rtcp_read_stream) = &t.repair_stream.rtcp_read_stream { + if let Err(err) = repair_rtcp_read_stream.close().await { + errs.push(err); + } + } + + if let Some(repair_rtp_read_stream) = &t.repair_stream.rtp_read_stream { + if let Err(err) = repair_rtp_read_stream.close().await { + errs.push(err); + } + } + + if let Some(stream_info) = &t.stream.stream_info { + self.internal + .interceptor + .unbind_remote_stream(stream_info) + .await; + } + + if let Some(repair_stream_info) = &t.repair_stream.stream_info { + self.internal + .interceptor + .unbind_remote_stream(repair_stream_info) + .await; + } + } + } + } + + flatten_errs(errs) + } + + /// read_rtp should only be called by a track, this only exists so we can keep state in one place + pub(crate) async fn read_rtp(&self, b: &mut [u8], tid: usize) -> Result { + self.internal.read_rtp(b, tid).await + } + + /// receive_for_rid is the sibling of Receive expect for RIDs instead of SSRCs + /// It populates all the internal state for the given RID + pub(crate) async fn receive_for_rid( + &self, + rid: SmolStr, + params: RTCRtpParameters, + stream: TrackStream, + ) -> Result> { + let tracks = self.internal.tracks.iter_mut().collect::>(); + for mut tv in tracks { + let t = tv.value_mut(); + if *t.track.rid() == rid { + t.track.set_kind(self.internal.kind); + if let Some(codec) = params.codecs.first() { + t.track.set_codec(codec.clone()); + } + t.track.set_params(params.clone()); + t.track + .set_ssrc(stream.stream_info.as_ref().map_or(0, |s| s.ssrc)); + t.stream = stream; + return Ok(Arc::clone(&t.track)); + } + } + + Err(Error::ErrRTPReceiverForRIDTrackStreamNotFound) + } + + /// receiveForRtx starts a routine that processes the repair stream + /// These packets aren't exposed to the user yet, but we need to process them for + /// TWCC + pub(crate) async fn receive_for_rtx( + &self, + ssrc: SSRC, + rsid: String, + repair_stream: TrackStream, + ) -> Result<()> { + let tracks = self.internal.tracks.iter_mut().collect::>(); + let l = tracks.len(); + for mut tv in tracks { + let t = tv.value_mut(); + if (ssrc != 0 && l == 1) || t.track.rid() == rsid { + t.repair_stream = repair_stream; + + let receive_mtu = self.receive_mtu; + let track = t.clone(); + tokio::spawn(async move { + let mut b = vec![0u8; receive_mtu]; + while let Some(repair_rtp_interceptor) = &track.repair_stream.rtp_interceptor { + //TODO: cancel repair_rtp_interceptor.read gracefully + //println!("repair_rtp_interceptor read begin with ssrc={}", ssrc); + if repair_rtp_interceptor.read(&mut b).await.is_err() { + break; + } + } + }); + + return Ok(()); + } + } + + Err(Error::ErrRTPReceiverForRIDTrackStreamNotFound) + } + + // State + + pub(crate) fn current_state(&self) -> State { + self.internal.current_state() + } + + pub(crate) async fn pause(&self) -> Result<()> { + self.internal.pause()?; + + if !self.internal.current_state().is_started() { + return Ok(()); + } + + let streams = self.internal.tracks.iter_mut().collect::>(); + + for mut vk in streams { + let stream = vk.value_mut(); + // TODO: If we introduce futures as a direct dependency this and other futures could be + // ran concurrently with [`join_all`](https://docs.rs/futures/0.3.21/futures/future/fn.join_all.html) + stream.track.fire_onmute().await; + } + + Ok(()) + } + + pub(crate) async fn resume(&self) -> Result<()> { + self.internal.resume()?; + + if !self.internal.current_state().is_started() { + return Ok(()); + } + + let streams = self.internal.tracks.iter_mut().collect::>(); + + for mut vk in streams { + let stream = vk.value_mut(); + // TODO: If we introduce futures as a direct dependency this and other futures could be + // ran concurrently with [`join_all`](https://docs.rs/futures/0.3.21/futures/future/fn.join_all.html) + stream.track.fire_onunmute().await; + } + + Ok(()) + } +} diff --git a/webrtc/src/rtp_transceiver/rtp_receiver/mod_copy2.rs b/webrtc/src/rtp_transceiver/rtp_receiver/mod_copy2.rs new file mode 100644 index 000000000..b74395da8 --- /dev/null +++ b/webrtc/src/rtp_transceiver/rtp_receiver/mod_copy2.rs @@ -0,0 +1,877 @@ +#[cfg(test)] +mod rtp_receiver_test; + +use std::fmt; +use std::sync::Arc; + +use arc_swap::ArcSwapOption; +use interceptor::stream_info::{AssociatedStreamInfo, RTPHeaderExtension}; +use interceptor::Interceptor; +use log::trace; +use smol_str::SmolStr; +use tokio::sync::{watch, Mutex, RwLock}; + +use crate::api::media_engine::MediaEngine; +use crate::dtls_transport::RTCDtlsTransport; +use crate::error::{flatten_errs, Error, Result}; +use crate::peer_connection::sdp::TrackDetails; +use crate::rtp_transceiver::rtp_codec::{ + codec_parameters_fuzzy_search, CodecMatch, RTCRtpCodecParameters, RTCRtpParameters, + RTPCodecType, +}; +use crate::rtp_transceiver::rtp_transceiver_direction::RTCRtpTransceiverDirection; +use crate::rtp_transceiver::{ + codec_rtx_search, create_stream_info, RTCRtpDecodingParameters, RTCRtpReceiveParameters, SSRC, +}; +use crate::track::track_remote::TrackRemote; +use crate::track::{TrackStream, TrackStreams}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum State { + /// We haven't started yet. + Unstarted = 0, + /// We haven't started yet and additionally we've been paused. + UnstartedPaused = 1, + + /// We have started and are running. + Started = 2, + + /// We have been paused after starting. + Paused = 3, + + /// We have been stopped. + Stopped = 4, +} + +impl From for State { + fn from(value: u8) -> Self { + match value { + v if v == State::Unstarted as u8 => State::Unstarted, + v if v == State::UnstartedPaused as u8 => State::UnstartedPaused, + v if v == State::Started as u8 => State::Started, + v if v == State::Paused as u8 => State::Paused, + v if v == State::Stopped as u8 => State::Stopped, + _ => unreachable!( + "Invalid serialization of {}: {}", + std::any::type_name::(), + value + ), + } + } +} + +impl fmt::Display for State { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + State::Unstarted => write!(f, "Unstarted"), + State::UnstartedPaused => write!(f, "UnstartedPaused"), + State::Started => write!(f, "Running"), + State::Paused => write!(f, "Paused"), + State::Stopped => write!(f, "Closed"), + } + } +} + +impl State { + fn transition(to: Self, tx: &watch::Sender) -> Result<()> { + let current = *tx.borrow(); + if current == to { + // Already in this state + return Ok(()); + } + + match current { + Self::Unstarted + if matches!(to, Self::Started | Self::Stopped | Self::UnstartedPaused) => + { + let _ = tx.send(to); + return Ok(()); + } + Self::UnstartedPaused + if matches!(to, Self::Unstarted | Self::Stopped | Self::Paused) => + { + let _ = tx.send(to); + return Ok(()); + } + State::Started if matches!(to, Self::Paused | Self::Stopped) => { + let _ = tx.send(to); + return Ok(()); + } + State::Paused if matches!(to, Self::Started | Self::Stopped) => { + let _ = tx.send(to); + return Ok(()); + } + _ => {} + } + + Err(Error::ErrRTPReceiverStateChangeInvalid { from: current, to }) + } + + async fn wait_for(rx: &mut watch::Receiver, states: &[State]) -> Result<()> { + loop { + let state = *rx.borrow(); + + match state { + _ if states.contains(&state) => return Ok(()), + State::Stopped => { + return Err(Error::ErrClosedPipe); + } + _ => {} + } + + if rx.changed().await.is_err() { + return Err(Error::ErrClosedPipe); + } + } + } + + async fn error_on_close(rx: &mut watch::Receiver) -> Result<()> { + if rx.changed().await.is_err() { + return Err(Error::ErrClosedPipe); + } + + let state = *rx.borrow(); + if state == State::Stopped { + return Err(Error::ErrClosedPipe); + } + + Ok(()) + } + + fn is_started(&self) -> bool { + matches!(self, Self::Started | Self::Paused) + } +} + +pub struct RTPReceiverInternal { + pub(crate) kind: RTPCodecType, + + // State is stored within the channel + state_tx: watch::Sender, + state_rx: watch::Receiver, + + tracks: RwLock>, + + transceiver_codecs: ArcSwapOption>>, + + transport: Arc, + media_engine: Arc, + interceptor: Arc, +} + +impl RTPReceiverInternal { + /// read reads incoming RTCP for this RTPReceiver + async fn read(&self, b: &mut [u8]) -> Result>> { + let mut state_watch_rx = self.state_tx.subscribe(); + // Ensure we are running or paused. When paused we still receive RTCP even if RTP traffic + // isn't flowing. + State::wait_for(&mut state_watch_rx, &[State::Started, State::Paused]).await?; + + let tracks = self.tracks.read().await; + if let Some(t) = tracks.first() { + if let Some(rtcp_interceptor) = &t.stream.rtcp_interceptor { + loop { + tokio::select! { + res = State::error_on_close(&mut state_watch_rx) => { + res? + } + result = rtcp_interceptor.read(b) => { + return Ok(result?) + } + } + } + } else { + Err(Error::ErrInterceptorNotBind) + } + } else { + Err(Error::ErrExistingTrack) + } + + // let rtcp_interceptor = { + // let tracks = self.tracks.read().await; // Блокировка захвачена + + // // TODO: возможно, нужна логика выбора трека на основе симулкаст предпочтения + // // Вместо того, чтоб просто брать первый трек, или читать оповещения от всех треков + // tracks + // .first() + // .ok_or(Error::ErrExistingTrack)? + // .stream + // .rtcp_interceptor + // .as_ref() + // .ok_or(Error::ErrInterceptorNotBind)? + // .clone() + // }; + + // loop { + // tokio::select! { + // res = State::error_on_close(&mut state_watch_rx) => { + // res? + // } + // result = rtcp_interceptor.read(b) => { + // return Ok(result?) + // } + // } + // } + } + + /// read_simulcast reads incoming RTCP for this RTPReceiver for given rid + async fn read_simulcast( + &self, + b: &mut [u8], + rid: &str, + ) -> Result>> { + let mut state_watch_rx = self.state_tx.subscribe(); + + // Ensure we are running or paused. When paused we still receive RTCP even if RTP traffic + // isn't flowing. + State::wait_for(&mut state_watch_rx, &[State::Started, State::Paused]).await?; + + let tracks = self.tracks.read().await; + for t in &*tracks { + if t.track.rid() == rid { + if let Some(rtcp_interceptor) = &t.stream.rtcp_interceptor { + loop { + tokio::select! { + res = State::error_on_close(&mut state_watch_rx) => { + res? + } + result = rtcp_interceptor.read(b) => { + return Ok(result?); + } + } + } + } else { + return Err(Error::ErrInterceptorNotBind); + } + } + } + Err(Error::ErrRTPReceiverForRIDTrackStreamNotFound) + } + + /// read_rtcp is a convenience method that wraps Read and unmarshal for you. + /// It also runs any configured interceptors. + async fn read_rtcp( + &self, + receive_mtu: usize, + ) -> Result>> { + let mut b = vec![0u8; receive_mtu]; + let pkts = self.read(&mut b).await?; + + Ok(pkts) + } + + /// read_simulcast_rtcp is a convenience method that wraps ReadSimulcast and unmarshal for you + async fn read_simulcast_rtcp( + &self, + rid: &str, + receive_mtu: usize, + ) -> Result>> { + let mut b = vec![0u8; receive_mtu]; + let pkts = self.read_simulcast(&mut b, rid).await?; + + Ok(pkts) + } + + pub(crate) async fn read_rtp(&self, b: &mut [u8], tid: usize) -> Result { + let mut state_watch_rx = self.state_tx.subscribe(); + + // Ensure we are running. + State::wait_for(&mut state_watch_rx, &[State::Started]).await?; + + //log::debug!("read_rtp enter tracks tid {}", tid); + let mut rtp_interceptor = None; + //let mut ssrc = 0; + { + let tracks = self.tracks.read().await; + for t in &*tracks { + if t.track.tid() == tid { + rtp_interceptor.clone_from(&t.stream.rtp_interceptor); + //ssrc = t.track.ssrc(); + break; + } + } + }; + /*log::debug!( + "read_rtp exit tracks with rtp_interceptor {} with tid {}", + rtp_interceptor.is_some(), + tid, + );*/ + + if let Some(rtp_interceptor) = rtp_interceptor { + //println!( + // "read_rtp rtp_interceptor.read enter with tid {} ssrc {}", + // tid, ssrc + //); + let mut current_state = *state_watch_rx.borrow(); + loop { + tokio::select! { + _ = state_watch_rx.changed() => { + let new_state = *state_watch_rx.borrow(); + + if new_state == State::Stopped { + return Err(Error::ErrClosedPipe); + } + current_state = new_state; + } + result = rtp_interceptor.read(b) => { + let result = result?; + + if current_state == State::Paused { + trace!("Dropping {} read bytes received while RTPReceiver was paused", result); + continue; + } + return Ok(result); + } + } + } + } else { + //log::debug!("read_rtp exit tracks with ErrRTPReceiverWithSSRCTrackStreamNotFound"); + Err(Error::ErrRTPReceiverWithSSRCTrackStreamNotFound) + } + } + + async fn get_parameters(&self) -> RTCRtpParameters { + let mut parameters = self + .media_engine + .get_rtp_parameters_by_kind(self.kind, RTCRtpTransceiverDirection::Recvonly); + + let transceiver_codecs = self.transceiver_codecs.load(); + if let Some(codecs) = &*transceiver_codecs { + let mut c = codecs.lock().await; + parameters.codecs = + RTPReceiverInternal::get_codecs(&mut c, self.kind, &self.media_engine); + } + + parameters + } + + pub(crate) fn get_codecs( + codecs: &mut [RTCRtpCodecParameters], + kind: RTPCodecType, + media_engine: &Arc, + ) -> Vec { + let media_engine_codecs = media_engine.get_codecs_by_kind(kind); + if codecs.is_empty() { + return media_engine_codecs; + } + let mut filtered_codecs = vec![]; + for codec in codecs { + let (c, match_type) = codec_parameters_fuzzy_search(codec, &media_engine_codecs); + if match_type != CodecMatch::None { + if codec.payload_type == 0 { + codec.payload_type = c.payload_type; + } + filtered_codecs.push(codec.clone()); + } + } + + filtered_codecs + } + + // State + + /// Get the current state and a receiver for the next state change. + pub(crate) fn current_state(&self) -> State { + *self.state_rx.borrow() + } + + pub(crate) fn start(&self) -> Result<()> { + State::transition(State::Started, &self.state_tx) + } + + pub(crate) fn pause(&self) -> Result<()> { + let current = self.current_state(); + + match current { + State::Unstarted => State::transition(State::UnstartedPaused, &self.state_tx), + State::Started => State::transition(State::Paused, &self.state_tx), + _ => Ok(()), + } + } + + pub(crate) fn resume(&self) -> Result<()> { + let current = self.current_state(); + + match current { + State::UnstartedPaused => State::transition(State::Unstarted, &self.state_tx), + State::Paused => State::transition(State::Started, &self.state_tx), + _ => Ok(()), + } + } + + pub(crate) fn close(&self) -> Result<()> { + State::transition(State::Stopped, &self.state_tx) + } +} + +/// RTPReceiver allows an application to inspect the receipt of a TrackRemote +/// +/// ## Specifications +/// +/// * [MDN] +/// * [W3C] +/// +/// [MDN]: https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpReceiver +/// [W3C]: https://w3c.github.io/webrtc-pc/#rtcrtpreceiver-interface +pub struct RTCRtpReceiver { + receive_mtu: usize, + + pub internal: Arc, +} + +impl std::fmt::Debug for RTCRtpReceiver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RTCRtpReceiver") + .field("kind", &self.internal.kind) + .finish() + } +} + +impl RTCRtpReceiver { + pub fn new( + receive_mtu: usize, + kind: RTPCodecType, + transport: Arc, + media_engine: Arc, + interceptor: Arc, + ) -> Self { + let (state_tx, state_rx) = watch::channel(State::Unstarted); + + RTCRtpReceiver { + receive_mtu, + + internal: Arc::new(RTPReceiverInternal { + kind, + + tracks: RwLock::new(vec![]), + transport, + media_engine, + interceptor, + + state_tx, + state_rx, + + transceiver_codecs: ArcSwapOption::new(None), + }), + } + } + + pub fn kind(&self) -> RTPCodecType { + self.internal.kind + } + + pub(crate) fn set_transceiver_codecs( + &self, + codecs: Option>>>, + ) { + self.internal.transceiver_codecs.store(codecs); + } + + /// transport returns the currently-configured *DTLSTransport or nil + /// if one has not yet been configured + pub fn transport(&self) -> Arc { + Arc::clone(&self.internal.transport) + } + + /// get_parameters describes the current configuration for the encoding and + /// transmission of media on the receiver's track. + pub async fn get_parameters(&self) -> RTCRtpParameters { + self.internal.get_parameters().await + } + + /// SetRTPParameters applies provided RTPParameters the RTPReceiver's tracks. + /// This method is part of the ORTC API. It is not + /// meant to be used together with the basic WebRTC API. + /// The amount of provided codecs must match the number of tracks on the receiver. + pub async fn set_rtp_parameters(&self, params: RTCRtpParameters) { + let mut header_extensions = vec![]; + for h in ¶ms.header_extensions { + header_extensions.push(RTPHeaderExtension { + id: h.id, + uri: h.uri.clone(), + }); + } + + let mut tracks = self.internal.tracks.write().await; + for (idx, codec) in params.codecs.iter().enumerate() { + let t = &mut tracks[idx]; + if let Some(stream_info) = &mut t.stream.stream_info { + stream_info + .rtp_header_extensions + .clone_from(&header_extensions); + } + + let current_track = &t.track; + current_track.set_codec(codec.clone()); + current_track.set_params(params.clone()); + } + } + + /// tracks returns the RtpTransceiver traclockks + /// A RTPReceiver to support Simulcast may now have multiple tracks + pub async fn tracks(&self) -> Vec> { + let tracks = self.internal.tracks.read().await; + tracks.iter().map(|t| Arc::clone(&t.track)).collect() + } + + /// receive initialize the track and starts all the transports + pub async fn receive(&self, parameters: &RTCRtpReceiveParameters) -> Result<()> { + let receiver = Arc::downgrade(&self.internal); + + let current_state = self.internal.current_state(); + if current_state.is_started() { + return Err(Error::ErrRTPReceiverReceiveAlreadyCalled); + } + self.internal.start()?; + + let (global_params, interceptor, media_engine) = { + ( + self.internal.get_parameters().await, + Arc::clone(&self.internal.interceptor), + Arc::clone(&self.internal.media_engine), + ) + }; + + let codec = if let Some(codec) = global_params.codecs.first() { + codec.clone() + } else { + RTCRtpCodecParameters::default() + }; + + for encoding in ¶meters.encodings { + let (stream_info, rtp_read_stream, rtp_interceptor, rtcp_read_stream, rtcp_interceptor) = + if encoding.ssrc != 0 { + let stream_info = create_stream_info( + "".to_owned(), + encoding.ssrc, + 0, + codec.capability.clone(), + &global_params.header_extensions, + None, + ); + let (rtp_read_stream, rtp_interceptor, rtcp_read_stream, rtcp_interceptor) = + self.internal + .transport + .streams_for_ssrc(encoding.ssrc, &stream_info, &interceptor) + .await?; + + ( + Some(stream_info), + Some(rtp_read_stream), + Some(rtp_interceptor), + Some(rtcp_read_stream), + Some(rtcp_interceptor), + ) + } else { + (None, None, None, None, None) + }; + + let t = TrackStreams { + track: Arc::new(TrackRemote::new( + self.receive_mtu, + self.internal.kind, + encoding.ssrc, + encoding.rid.clone(), + receiver.clone(), + Arc::clone(&media_engine), + Arc::clone(&interceptor), + )), + stream: TrackStream { + stream_info, + rtp_read_stream, + rtp_interceptor, + rtcp_read_stream, + rtcp_interceptor, + }, + + repair_stream: TrackStream { + stream_info: None, + rtp_read_stream: None, + rtp_interceptor: None, + rtcp_read_stream: None, + rtcp_interceptor: None, + }, + }; + + { + let mut tracks = self.internal.tracks.write().await; + tracks.push(t); + }; + + let rtx_ssrc = encoding.rtx.ssrc; + if rtx_ssrc != 0 { + let rtx_info = AssociatedStreamInfo { + ssrc: encoding.ssrc, + payload_type: 0, + }; + + let rtx_codec = + codec_rtx_search(&codec, &global_params.codecs).unwrap_or(codec.clone()); + + let stream_info = create_stream_info( + "".to_owned(), + rtx_ssrc, + 0, + rtx_codec.capability, + &global_params.header_extensions, + Some(rtx_info), + ); + let (rtp_read_stream, rtp_interceptor, rtcp_read_stream, rtcp_interceptor) = self + .internal + .transport + .streams_for_ssrc(rtx_ssrc, &stream_info, &interceptor) + .await?; + + self.receive_for_rtx( + rtx_ssrc, + "".to_owned(), + TrackStream { + stream_info: Some(stream_info), + rtp_read_stream: Some(rtp_read_stream), + rtp_interceptor: Some(rtp_interceptor), + rtcp_read_stream: Some(rtcp_read_stream), + rtcp_interceptor: Some(rtcp_interceptor), + }, + ) + .await?; + } + } + + Ok(()) + } + + /// read reads incoming RTCP for this RTPReceiver + pub async fn read( + &self, + b: &mut [u8], + ) -> Result>> { + self.internal.read(b).await + } + + /// read_simulcast reads incoming RTCP for this RTPReceiver for given rid + pub async fn read_simulcast( + &self, + b: &mut [u8], + rid: &str, + ) -> Result>> { + self.internal.read_simulcast(b, rid).await + } + + /// read_rtcp is a convenience method that wraps Read and unmarshal for you. + /// It also runs any configured interceptors. + pub async fn read_rtcp(&self) -> Result>> { + self.internal.read_rtcp(self.receive_mtu).await + } + + /// read_simulcast_rtcp is a convenience method that wraps ReadSimulcast and unmarshal for you + pub async fn read_simulcast_rtcp( + &self, + rid: &str, + ) -> Result>> { + self.internal + .read_simulcast_rtcp(rid, self.receive_mtu) + .await + } + + pub(crate) async fn have_received(&self) -> bool { + self.internal.current_state().is_started() + } + + pub(crate) async fn start(&self, incoming: &TrackDetails) { + let mut encoding_size = incoming.ssrcs.len(); + if incoming.rids.len() >= encoding_size { + encoding_size = incoming.rids.len(); + }; + + let mut encodings = vec![RTCRtpDecodingParameters::default(); encoding_size]; + for (i, encoding) in encodings.iter_mut().enumerate() { + if incoming.rids.len() > i { + encoding.rid = incoming.rids[i].clone(); + } + if incoming.ssrcs.len() > i { + encoding.ssrc = incoming.ssrcs[i]; + } + + encoding.rtx.ssrc = incoming.repair_ssrc; + } + + if let Err(err) = self.receive(&RTCRtpReceiveParameters { encodings }).await { + log::warn!("RTPReceiver Receive failed {err}"); + return; + } + + // set track id and label early so they can be set as new track information + // is received from the SDP. + let is_unpaused = self.current_state() == State::Started; + for track_remote in &self.tracks().await { + track_remote.set_id(incoming.id.clone()); + track_remote.set_stream_id(incoming.stream_id.clone()); + + if is_unpaused { + track_remote.fire_onunmute().await; + } + } + } + + /// Stop irreversibly stops the RTPReceiver + pub async fn stop(&self) -> Result<()> { + let previous_state = self.internal.current_state(); + self.internal.close()?; + + let mut errs = vec![]; + let was_ever_started = previous_state.is_started(); + if was_ever_started { + let tracks = self.internal.tracks.write().await; + for t in &*tracks { + if let Some(rtcp_read_stream) = &t.stream.rtcp_read_stream { + if let Err(err) = rtcp_read_stream.close().await { + errs.push(err); + } + } + + if let Some(rtp_read_stream) = &t.stream.rtp_read_stream { + if let Err(err) = rtp_read_stream.close().await { + errs.push(err); + } + } + + if let Some(repair_rtcp_read_stream) = &t.repair_stream.rtcp_read_stream { + if let Err(err) = repair_rtcp_read_stream.close().await { + errs.push(err); + } + } + + if let Some(repair_rtp_read_stream) = &t.repair_stream.rtp_read_stream { + if let Err(err) = repair_rtp_read_stream.close().await { + errs.push(err); + } + } + + if let Some(stream_info) = &t.stream.stream_info { + self.internal + .interceptor + .unbind_remote_stream(stream_info) + .await; + } + + if let Some(repair_stream_info) = &t.repair_stream.stream_info { + self.internal + .interceptor + .unbind_remote_stream(repair_stream_info) + .await; + } + } + } + + flatten_errs(errs) + } + + /// read_rtp should only be called by a track, this only exists so we can keep state in one place + pub(crate) async fn read_rtp(&self, b: &mut [u8], tid: usize) -> Result { + self.internal.read_rtp(b, tid).await + } + + /// receive_for_rid is the sibling of Receive expect for RIDs instead of SSRCs + /// It populates all the internal state for the given RID + pub(crate) async fn receive_for_rid( + &self, + rid: SmolStr, + params: RTCRtpParameters, + stream: TrackStream, + ) -> Result> { + let mut tracks = self.internal.tracks.write().await; + for t in &mut *tracks { + if *t.track.rid() == rid { + t.track.set_kind(self.internal.kind); + if let Some(codec) = params.codecs.first() { + t.track.set_codec(codec.clone()); + } + t.track.set_params(params.clone()); + t.track + .set_ssrc(stream.stream_info.as_ref().map_or(0, |s| s.ssrc)); + t.stream = stream; + return Ok(Arc::clone(&t.track)); + } + } + + Err(Error::ErrRTPReceiverForRIDTrackStreamNotFound) + } + + /// receiveForRtx starts a routine that processes the repair stream + /// These packets aren't exposed to the user yet, but we need to process them for + /// TWCC + pub(crate) async fn receive_for_rtx( + &self, + ssrc: SSRC, + rsid: String, + repair_stream: TrackStream, + ) -> Result<()> { + let mut tracks = self.internal.tracks.write().await; + let l = tracks.len(); + for t in &mut *tracks { + if (ssrc != 0 && l == 1) || t.track.rid() == rsid { + t.repair_stream = repair_stream; + + let receive_mtu = self.receive_mtu; + let track = t.clone(); + tokio::spawn(async move { + let mut b = vec![0u8; receive_mtu]; + while let Some(repair_rtp_interceptor) = &track.repair_stream.rtp_interceptor { + //TODO: cancel repair_rtp_interceptor.read gracefully + //println!("repair_rtp_interceptor read begin with ssrc={}", ssrc); + if repair_rtp_interceptor.read(&mut b).await.is_err() { + break; + } + } + }); + + return Ok(()); + } + } + + Err(Error::ErrRTPReceiverForRIDTrackStreamNotFound) + } + + // State + + pub(crate) fn current_state(&self) -> State { + self.internal.current_state() + } + + pub(crate) async fn pause(&self) -> Result<()> { + self.internal.pause()?; + + if !self.internal.current_state().is_started() { + return Ok(()); + } + + let streams = self.internal.tracks.read().await; + + for stream in streams.iter() { + // TODO: If we introduce futures as a direct dependency this and other futures could be + // ran concurrently with [`join_all`](https://docs.rs/futures/0.3.21/futures/future/fn.join_all.html) + stream.track.fire_onmute().await; + } + + Ok(()) + } + + pub(crate) async fn resume(&self) -> Result<()> { + self.internal.resume()?; + + if !self.internal.current_state().is_started() { + return Ok(()); + } + + let streams = self.internal.tracks.read().await; + + for stream in streams.iter() { + // TODO: If we introduce futures as a direct dependency this and other futures could be + // ran concurrently with [`join_all`](https://docs.rs/futures/0.3.21/futures/future/fn.join_all.html) + stream.track.fire_onunmute().await; + } + + Ok(()) + } +} diff --git a/webrtc/src/rtp_transceiver/rtp_receiver/rtp_receiver_test.rs b/webrtc/src/rtp_transceiver/rtp_receiver/rtp_receiver_test.rs index 451b6e2cf..2a821912b 100644 --- a/webrtc/src/rtp_transceiver/rtp_receiver/rtp_receiver_test.rs +++ b/webrtc/src/rtp_transceiver/rtp_receiver/rtp_receiver_test.rs @@ -1,5 +1,8 @@ +use std::time::Instant; + use bytes::Bytes; use media::Sample; +use rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication; use tokio::sync::mpsc; use tokio::time::Duration; use waitgroup::WaitGroup; @@ -231,3 +234,319 @@ async fn test_rtp_receiver_set_read_deadline() -> Result<()> { Ok(()) } + +// This test uses VNet since we must have zero loss +#[tokio::test] +async fn test_rtp_receiver_internal_read_single_rtcp_latency() -> Result<()> { + // 1) Пара peer'ов на виртуальной сети + let (mut sender_pc, mut receiver_pc, wan) = create_vnet_pair().await?; + + // 2) Локальный трек как в примере + let track: Arc = Arc::new(TrackLocalStaticSample::new( + RTCRtpCodecCapability { + mime_type: MIME_TYPE_VP8.to_owned(), + ..Default::default() + }, + "video".to_owned(), + "webrtc-rs".to_owned(), + )); + sender_pc.add_track(Arc::clone(&track)).await?; + + // 3) Канал для получения RTCRtpReceiver из on_track + let (rx_tx, mut rx_rx) = mpsc::channel(1); + receiver_pc.on_track(Box::new(move |_remote_track, rtp_receiver, _| { + let rx_tx = rx_tx.clone(); + Box::pin(async move { + // Отдадим наружу хэндл ресивера + let _ = rx_tx.send(rtp_receiver).await; + }) + })); + + // 4) Сведение + let wg = WaitGroup::new(); + until_connection_state(&mut sender_pc, &wg, RTCPeerConnectionState::Connected).await; + until_connection_state(&mut receiver_pc, &wg, RTCPeerConnectionState::Connected).await; + signal_pair(&mut sender_pc, &mut receiver_pc).await?; + wg.wait().await; + + // 5) Отправим ОДИН RTP-семпл, чтобы триггернуть on_track на приёмнике + if let Some(v) = track.as_any().downcast_ref::() { + v.write_sample(&Sample { + data: Bytes::from_static(&[0x90, 0x90, 0x90]), // произвольный крошечный payload + duration: Duration::from_millis(33), + ..Default::default() + }) + .await?; + } else { + panic!("unexpected track type"); + } + + // 6) Теперь можно корректно дождаться RTCRtpReceiver из on_track + let rtp_receiver = tokio::time::timeout(Duration::from_secs(2), rx_rx.recv()) + .await + .expect("on_track did not fire in time") // timeout + .expect("on_track channel closed"); // channel closed + + let mut ssrc = None; + + // На PC могут быть несколько sender'ов — найдём тот, который несёт наш track + for s in sender_pc.get_senders().await { + // track() вернёт Some для медиасендеров + if let Some(t) = s.track().await { + // Сопоставим по id, чтобы быть уверенными, что это именно наш TrackLocalStaticSample + if t.id() == track.id() { + let params = s.get_parameters().await; + if let Some(enc) = params.encodings.get(0) { + ssrc = Some(enc.ssrc); + } + break; + } + } + } + let ssrc = ssrc.expect("failed to resolve media SSRC for the track"); + + // 7) Запустим цикл чтения RTCP: после каждого возврата шлём отметку времени + use tokio::time::{Duration, Instant}; + const N: usize = 10000; + + let (done_tx, mut done_rx) = mpsc::channel::(N); + let reader = rtp_receiver.clone(); + tokio::spawn(async move { + for _ in 0..N { + let _ = reader.read_rtcp().await; // внутри дойдёт до RTPReceiverInternal::read(...) + let _ = done_tx.send(Instant::now()).await; + } + }); + + // Небольшой yield, чтобы reader гарантированно вошёл в select! и ждал + // tokio::time::sleep(Duration::from_millis(20)).await; + + // 8) Сформируем одиночный PLI и отправим его с отправителя + let mut latencies = Vec::with_capacity(N); + for _ in 0..N { + let pli = PictureLossIndication { + sender_ssrc: ssrc, + media_ssrc: ssrc, // критично: совпадает с media SSRC трека + }; + let pkts: Vec> = vec![Box::new(pli)]; + + // Засекаем момент перед отправкой конкретного пакета + let t0 = Instant::now(); + sender_pc.write_rtcp(&pkts).await?; + + // 9) Дождёмся окончания чтения для ЭТОГО пакета и запишем задержку + let t_read = tokio::time::timeout(Duration::from_secs(2), done_rx.recv()) + .await + .expect("read_rtcp() timed out") + .expect("done channel closed"); + + latencies.push(t_read.duration_since(t0)); + } + + let total_ns: u128 = latencies.iter().map(|d| d.as_nanos()).sum(); + let avg_ns = total_ns / (latencies.len() as u128); + + let max_ns = latencies.iter().map(|d| d.as_nanos()).max().unwrap_or(0); + let min_ns = latencies.iter().map(|d| d.as_nanos()).min().unwrap_or(0); + + eprintln!( + "[read RTCP ×{N}] avg = {:.3} µs, min = {:.3} µs, max = {:.3} µs", + (avg_ns as f64) / 1_000.0, + (min_ns as f64) / 1_000.0, + (max_ns as f64) / 1_000.0 + ); + + // SLA по средней задержке — подстрой под ваш CI + let sla_avg = Duration::from_micros(50); + assert!( + Duration::from_nanos(avg_ns as u64) <= sla_avg, + "Average RTCP read latency {:.1} µs exceeded SLA {:?}", + (avg_ns as f64) / 1_000.0, + sla_avg + ); + + // 11) Корректное завершение + { + let mut w = wan.lock().await; + w.stop().await?; + } + close_pair_now(&sender_pc, &receiver_pc).await; + + Ok(()) +} + +const TAG: &[u8; 8] = b"WRSSEQ01"; + +// Ищем TAG в payload и читаем u32 (BE) после него +fn extract_seq_from_payload(payload: &[u8]) -> Option { + payload + .windows(TAG.len()) + .position(|w| w == TAG) + .and_then(|pos| { + let start = pos + TAG.len(); + if payload.len() >= start + 4 { + Some(u32::from_be_bytes([ + payload[start], + payload[start + 1], + payload[start + 2], + payload[start + 3], + ])) + } else { + None + } + }) +} + +#[tokio::test] +async fn test_rtp_receiver_internal_read_single_rtp_latency() -> Result<()> { + // 1) Пара peer'ов на виртуальной сети + let (mut sender_pc, mut receiver_pc, wan) = create_vnet_pair().await?; + + // 2) Локальный трек (как в твоём примере) + let track: Arc = Arc::new(TrackLocalStaticSample::new( + RTCRtpCodecCapability { + mime_type: MIME_TYPE_VP8.to_owned(), + ..Default::default() + }, + "video".to_owned(), + "webrtc-rs".to_owned(), + )); + sender_pc.add_track(Arc::clone(&track)).await?; + + // 3) Получим TrackRemote из on_track + let (trk_tx, mut trk_rx) = mpsc::channel(1); + receiver_pc.on_track(Box::new(move |remote_track, _rtp_receiver, _| { + let trk_tx = trk_tx.clone(); + Box::pin(async move { + let _ = trk_tx.send(remote_track).await; + }) + })); + + // 4) Сведение + let wg = WaitGroup::new(); + until_connection_state(&mut sender_pc, &wg, RTCPeerConnectionState::Connected).await; + until_connection_state(&mut receiver_pc, &wg, RTCPeerConnectionState::Connected).await; + signal_pair(&mut sender_pc, &mut receiver_pc).await?; + wg.wait().await; + + // 5) Отправим один крошечный сэмпл, чтобы триггернуть on_track + if let Some(v) = track.as_any().downcast_ref::() { + v.write_sample(&Sample { + data: Bytes::from_static(&[0xAB]), // очень маленький payload -> один RTP-пакет + duration: Duration::from_millis(20), + ..Default::default() + }) + .await?; + } else { + panic!("unexpected track type"); + } + + // 6) Дождёмся TrackRemote из on_track + let remote_track = tokio::time::timeout(Duration::from_secs(2), trk_rx.recv()) + .await + .expect("on_track did not fire in time") + .expect("on_track channel closed"); + + // 7) Воркер: ожидаем строго последовательные seq в payload и шлём Instant только для них + + const N: usize = 1000; // настрои под свою длительность теста + let (done_tx, mut done_rx) = mpsc::channel::<(u32, Instant)>(N); + + let rt_for_reader = remote_track.clone(); + tokio::spawn(async move { + let mut expected: u32 = 0; + + // Игнорируем любые не-тестовые пакеты (например, тот, что триггерил on_track) + // Читаем, пока не увидим N "наших" пакетов по порядку + while (expected as usize) < N { + match rt_for_reader.read_rtp().await { + Ok(pkt) => { + if let Some(seq) = extract_seq_from_payload(&pkt.payload) { + if seq == expected { + let _ = done_tx.send((seq, Instant::now())).await; + expected = expected.wrapping_add(1); + } else { + // чужой или «будущий» пакет — игнорируем + } + } else { + // не наш пакет — игнорируем + } + } + Err(_) => break, + } + } + }); + + // 8) Основной цикл: отправляем N семплов с TAG+seq и меряем t_read - t0 + let mut latencies = Vec::with_capacity(N); + + for i in 0..N { + let seq = i as u32; + + // payload = TAG || seq_be || небольшой наполнитель + let mut payload = Vec::with_capacity(TAG.len() + 4 + 4); + payload.extend_from_slice(TAG); + payload.extend_from_slice(&seq.to_be_bytes()); + payload.extend_from_slice(&[0x90, 0x90, 0x90, 0x90]); // filler; всё равно уместится в 1 RTP + + let sample = Sample { + data: Bytes::from(payload), + duration: Duration::from_millis(16), + ..Default::default() + }; + + let t0 = Instant::now(); + if let Some(v) = track.as_any().downcast_ref::() { + v.write_sample(&sample).await?; + } else { + panic!("unexpected track type"); + } + + // Дождёмся квитанции именно за этот seq (воркер отправит их по порядку) + let (seq_back, t_read) = tokio::time::timeout(Duration::from_secs(2), done_rx.recv()) + .await + .expect("read_rtp() timed out") + .expect("done channel closed"); + + assert_eq!( + seq_back, seq, + "seq mismatch: sent {}, got {}", + seq, seq_back + ); + + latencies.push(t_read.duration_since(t0)); + + // Больше не нужен sleep — ассоциация send→recv теперь детерминированная + } + + // 9) Статистика: avg / min / max (в мкс) + let total_ns: u128 = latencies.iter().map(|d| d.as_nanos()).sum(); + let avg_ns = total_ns / (latencies.len() as u128); + let max_ns = latencies.iter().map(|d| d.as_nanos()).max().unwrap_or(0); + let min_ns = latencies.iter().map(|d| d.as_nanos()).min().unwrap_or(0); + + eprintln!( + "[read RTP ×{N}] avg = {:.3} µs, min = {:.3} µs, max = {:.3} µs", + (avg_ns as f64) / 1_000.0, + (min_ns as f64) / 1_000.0, + (max_ns as f64) / 1_000.0 + ); + + // 10) SLA по средней задержке — подстрой под свой CI/окружение + let sla_avg = Duration::from_micros(50); // пример: 2 ms + assert!( + Duration::from_nanos(avg_ns as u64) <= sla_avg, + "Average RTP read latency {:.1} µs exceeded SLA {:?}", + (avg_ns as f64) / 1_000.0, + sla_avg + ); + + // 11) Корректное завершение (как было) + { + let mut w = wan.lock().await; + w.stop().await?; + } + close_pair_now(&sender_pc, &receiver_pc).await; + + Ok(()) +} diff --git a/webrtc/src/track/track_local/mod.rs b/webrtc/src/track/track_local/mod.rs index 8b1c1ae4d..1928bfd5a 100644 --- a/webrtc/src/track/track_local/mod.rs +++ b/webrtc/src/track/track_local/mod.rs @@ -1,6 +1,3 @@ -#[cfg(test)] -mod track_local_static_test; - pub mod packet_cache; pub mod track_local_simple; pub mod track_local_static_rtp; diff --git a/webrtc/src/track/track_local/packet_cache.rs b/webrtc/src/track/track_local/packet_cache.rs index 0c4d219a2..c987bb675 100644 --- a/webrtc/src/track/track_local/packet_cache.rs +++ b/webrtc/src/track/track_local/packet_cache.rs @@ -1,86 +1,255 @@ -use rtcp::transport_feedbacks::transport_layer_nack::NackPair; +use arc_swap::ArcSwapOption; +use crossbeam_epoch::{self as epoch, Atomic, Owned}; +use crossbeam_utils::CachePadded; +use log::warn; use rtp::packet::Packet; -use std::time::{Duration, Instant}; -use util::sync::RwLock; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct PCache { - // Финальный пакет, уже с переписанными SSRC/SN/TS и всеми нужными расширениями - // Можно добавить счётчик/время ретрансляций для отладочной метрики (AtomicU32/Option) - pub packet: Packet, // полный RTP (ingress) + pub rtp: Packet, // готовый RTP-пакет для ретрансляции pub first_sent_at: Instant, // для TTL } #[derive(Debug)] pub struct PCacheBuffer { ttl: Duration, - capacity: usize, // степень двойки предпочтительно; capacity - 1, если capacity — power-of-two - slots: RwLock>>, + mask: usize, // capacity_pow2 - 1 + slots: Vec>>, } impl PCacheBuffer { pub fn new(ttl: Duration, capacity_pow2: usize) -> Self { assert!(capacity_pow2.is_power_of_two()); + let slots = (0..capacity_pow2) + .map(|_| CachePadded::new(ArcSwapOption::from(None))) + .collect(); Self { ttl, - slots: RwLock::new(vec![None; capacity_pow2]), - capacity: capacity_pow2 - 1, + mask: capacity_pow2 - 1, + slots, } } #[inline] fn idx(&self, seq: u16) -> usize { - (seq as usize) & self.capacity + (seq as usize) & self.mask + } + + pub fn put(&self, entry: Arc) { + let idx = self.idx(entry.rtp.header.sequence_number); + self.slots[idx].store(Some(entry)); + } + + // Нулевая копия: лучше отдавать Arc, а не Packet + pub fn get_arc(&self, seq: u16) -> Option> { + let cell = &self.slots[self.idx(seq)]; + + match cell.load_full() { + Some(entry) => { + if entry.rtp.header.sequence_number != seq { + warn!( + "Коллизия кольца entry.seq {} != seq{}", + entry.rtp.header.sequence_number, seq + ); + return None; + } + if entry.first_sent_at.elapsed() > self.ttl { + warn!( + "Пакет просрочен {:?} > {:?}", + entry.first_sent_at.elapsed(), + self.ttl + ); + return None; + } + Some(entry) + } + None => { + warn!("LoadFull is None"); + None + } + } + } + + // Совместимо с текущим API: отдаём Packet по значению (клон) + pub fn get(&self, seq: u16) -> Option { + self.get_arc(seq).map(|e| e.rtp.clone()) + } +} + +#[derive(Debug)] +pub struct PCache2 { + pub rtp: Packet, // полный RTP (ingress) + pub first_sent_at: Instant, // для TTL +} + +#[derive(Debug)] +pub struct PCacheBuffer2 { + ttl: Duration, + mask: usize, // capacity_pow2 - 1 + slots: Vec>>, +} + +impl PCacheBuffer2 { + pub fn new(ttl: Duration, capacity_pow2: usize) -> Self { + assert!(capacity_pow2.is_power_of_two()); + let slots = (0..capacity_pow2) + .map(|_| CachePadded::new(Atomic::null())) + .collect(); + Self { + ttl, + mask: capacity_pow2 - 1, + slots, + } + } + + #[inline(always)] + fn idx(&self, seq: u16) -> usize { + (seq as usize) & self.mask } - pub fn put(&self, packet: Packet) { - let idx = self.idx(packet.header.sequence_number); - let mut slots = self.slots.write(); - slots[idx] = Some(PCache { - packet, + // Один писатель: атомарная замена указателя, без refcount + pub fn put(&self, rtp: Packet) { + let idx = self.idx(rtp.header.sequence_number); + let guard = &epoch::pin(); + + let new = Owned::new(PCache { + rtp, first_sent_at: Instant::now(), }); + + // swap возвращает старый Shared + let old = self.slots[idx].swap(new, std::sync::atomic::Ordering::Release, guard); + + // Безопасно отложим уничтожение старого + if !old.is_null() { + unsafe { guard.defer_destroy(old) }; + } } pub fn get(&self, seq: u16) -> Option { let idx = self.idx(seq); - let slots = self.slots.read(); - let some = slots.get(idx)?.as_ref()?; - if some.packet.header.sequence_number != seq { - println!( - "Коллизия кольца: запрошен seq={seq}. В кеше seq={}", - some.packet.header.sequence_number - ); - return None; // коллизия кольца (wrap) + let guard = &epoch::pin(); + + let shared = self.slots[idx].load(std::sync::atomic::Ordering::Acquire, guard); + let pc = unsafe { shared.as_ref()? }; + + // Защита от коллизий кольца + if pc.rtp.header.sequence_number != seq { + return None; } - let elapsed = some.first_sent_at.elapsed(); - if elapsed > self.ttl { - println!( - "Пакет просрочен. Прошло {:?}, что больше ttl = {:?}", - elapsed, self.ttl - ); - return None; // просрочен + // TTL + if pc.first_sent_at.elapsed() > self.ttl { + return None; } - Some(some.packet.clone()) + Some(pc.rtp.clone()) } } -// Вспомогательная функция разворачивания NACK-пар (packet_id + bitmask -> список seq) -// Разворачиваем список потерянных SN из NACK-пар -pub fn expand_nack_pairs(pairs: &[NackPair]) -> Vec { - let mut out = Vec::with_capacity(pairs.len() * 8); - for p in pairs { - let base = p.packet_id; - out.push(base); - let mut mask = p.lost_packets; - let mut i = 0; - while mask != 0 { - if (mask & 1) != 0 { - out.push(base.wrapping_add(i + 1)); - } - mask >>= 1; - i += 1; +#[cfg(test)] +mod packet_cache_test { + use super::*; + use bytes::Bytes; + use rtp::{ + header::{Extension, Header}, + packet::Packet, + }; + use std::time::Duration; + + fn get_packet(sequence_number: u16) -> Packet { + Packet { + header: Header { + version: 2, + padding: false, + extension: true, + marker: true, + payload_type: 96, + sequence_number, + timestamp: 10000000, + ssrc: 100030001, + csrc: vec![], + extension_profile: 1, + extensions: vec![Extension { + id: 0, + payload: Bytes::from_static(&[0xFF, 0xFF, 0xFF, 0xFF]), + }], + ..Default::default() + }, + payload: Bytes::from_static(&[0x98, 0x36, 0xbe, 0x88, 0x9e]), + } + } + + fn get_p_cache(sequence_number: u16) -> Arc { + Arc::new(PCache { + rtp: get_packet(sequence_number), + first_sent_at: Instant::now(), + }) + } + + #[test] + fn it_packet_cache1() { + let cache_buf = PCacheBuffer::new(Duration::from_millis(3000), 2); + + let p1 = get_p_cache(1); + let p2 = get_p_cache(2); + let p3 = get_p_cache(3); + + cache_buf.put(p1.clone()); + cache_buf.put(p2.clone()); + cache_buf.put(p3.clone()); + if let Some(p) = cache_buf.get(3) { + assert_eq!(p.header.sequence_number, 3, "Неверный seq_num"); + } else { + assert!(false, "Нет пакета 3"); + } + + if let Some(p) = cache_buf.get(2) { + assert_eq!(p.header.sequence_number, 2, "Неверный seq_num"); + } else { + assert!(false, "Нет пакета 2"); + } + + if let Some(p) = cache_buf.get(1) { + assert!( + false, + "Найдет пакет 1, которого быть не должно, в нём seq={}", + p.header.sequence_number + ); + } + } + + #[test] + fn it_packet_cache2() { + let cache_buf = PCacheBuffer2::new(Duration::from_millis(3000), 2); + + let p1 = get_packet(1); + let p2 = get_packet(2); + let p3 = get_packet(3); + + cache_buf.put(p1.clone()); + cache_buf.put(p2.clone()); + cache_buf.put(p3.clone()); + if let Some(p) = cache_buf.get(3) { + assert_eq!(p.header.sequence_number, 3, "Неверный seq_num"); + } else { + assert!(false, "Нет пакета 3"); + } + + if let Some(p) = cache_buf.get(2) { + assert_eq!(p.header.sequence_number, 2, "Неверный seq_num"); + } else { + assert!(false, "Нет пакета 2"); + } + + if let Some(p) = cache_buf.get(1) { + assert!( + false, + "Найдет пакет 1, которого быть не должно, в нём seq={}", + p.header.sequence_number + ); } } - out } diff --git a/webrtc/src/track/track_local/track_local_simple.rs b/webrtc/src/track/track_local/track_local_simple.rs index d1f494253..6f719defa 100644 --- a/webrtc/src/track/track_local/track_local_simple.rs +++ b/webrtc/src/track/track_local/track_local_simple.rs @@ -25,22 +25,12 @@ impl TrackLocalSimple { #[async_trait] impl TrackLocal for TrackLocalSimple { async fn bind(&self, _t: &TrackLocalContext) -> Result { - println!( - "TrackLocalSimple.bind: mid - {:?}; {:?}", - _t.mid(), - _t.ssrc() - ); Ok(RTCRtpCodecParameters { ..Default::default() }) } async fn unbind(&self, _t: &TrackLocalContext) -> Result<()> { - println!( - "TrackLocalSimple.unbind: mid-{:?}; {:?}", - _t.mid(), - _t.ssrc() - ); Ok(()) } diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index d0a0f3bf1..a31505c7a 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -1,28 +1,39 @@ use bytes::{Bytes, BytesMut}; -use rtp::packet::Packet; +use dashmap::DashMap; use std::any::Any; use std::sync::atomic::AtomicU64; +use std::time::Instant; use std::{borrow::Cow, collections::HashMap, time::Duration}; +use tokio::sync::mpsc; use tokio::sync::mpsc::error::TrySendError; -use tokio::sync::{mpsc, Mutex}; +use tokio::sync::Mutex; use util::{Marshal, MarshalSize}; use super::*; +use crate::track::track_local::packet_cache::PCache; use crate::track::track_remote::TrackRemote; use crate::{error::flatten_errs, track::track_local::packet_cache::PCacheBuffer}; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct TrackState { last_out_seq: u16, // переживает все переключения источников last_out_ts: u32, // переживает все переключения источников started_at_ts: i64, - + marker: bool, // marker = true означает, что это последний пакет видеофрейма. + // Это сигнал для джиттер-буфера и декодера, что можно собрать все полученные пакеты этого фрейма и отправить их на декодирование. + // Используется после паузы out_offset: Option<( u16, /* смещение порядкового номера */ u32, /* смещение временной метки timestamp */ )>, } +pub struct PkgAttrs { + sequence_number: u16, + timestamp: u32, + marker: bool, +} + impl TrackState { pub fn new() -> Self { TrackState { @@ -33,20 +44,77 @@ impl TrackState { // Сохраняем начало трека в реальной временной шкале дла последующей синхронизации started_at_ts: chrono::Utc::now().timestamp(), out_offset: None, + marker: false, } } - pub fn apply_offset( + pub fn get_pkg_attrs( &mut self, kind: RTPCodecType, pkt_sequence_number: u16, pkt_timestamp: u32, - ) -> (u16, u32) { + ) -> PkgAttrs { + match self.out_offset { + Some((seq_num_offset, ts_offset)) => PkgAttrs { + sequence_number: pkt_sequence_number.wrapping_add(seq_num_offset), + timestamp: pkt_timestamp.wrapping_add(ts_offset), + marker: self.marker, + }, + None => { + let seq_num_offset = self + .last_out_seq + .wrapping_sub(pkt_sequence_number) + .wrapping_add(1); + let ts_offset = + self.last_out_ts + .wrapping_sub(pkt_timestamp) + .wrapping_add(match kind { + RTPCodecType::Audio => 900, // стандартное значение для звука + RTPCodecType::Video => 3750, // 90000 clock_rate / 24 кадра + _ => 3750, + }); + self.out_offset = Some((seq_num_offset, ts_offset)); + + PkgAttrs { + sequence_number: pkt_sequence_number.wrapping_add(seq_num_offset), + timestamp: pkt_timestamp.wrapping_add(ts_offset), + marker: self.marker, + } + } + } + } + + pub fn set_last_out(&mut self, pkg_attrs: PkgAttrs) { + self.last_out_seq = pkg_attrs.sequence_number; + self.last_out_ts = pkg_attrs.timestamp; + self.marker = false; + } + + pub fn shift_offset(&mut self, pkt_sequence_number: u16, pkt_timestamp: u32) { + self.marker = true; + self.out_offset = Some(( + self.last_out_seq.wrapping_sub(pkt_sequence_number), + self.last_out_ts.wrapping_sub(pkt_timestamp), + )) + } + + pub fn get_pkg_attrs_set_last_out( + &mut self, + kind: RTPCodecType, + pkt_sequence_number: u16, + pkt_timestamp: u32, + ) -> PkgAttrs { match self.out_offset { Some((seq_num_offset, ts_offset)) => { + // новый = пришедший + смещение + // старый = новый - (новый - старый) self.last_out_seq = pkt_sequence_number.wrapping_add(seq_num_offset); self.last_out_ts = pkt_timestamp.wrapping_add(ts_offset); - (self.last_out_seq, self.last_out_ts) + PkgAttrs { + sequence_number: self.last_out_seq, + timestamp: self.last_out_ts, + marker: self.marker, + } } None => { let seq_num_offset = self @@ -58,19 +126,23 @@ impl TrackState { .wrapping_sub(pkt_timestamp) .wrapping_add(match kind { RTPCodecType::Audio => 900, // стандартное значение для звука - RTPCodecType::Video => 3750, // 90000 clock_rate / 24 кадра - _ => 3750, + RTPCodecType::Video => 6000, // 90000 clock_rate / 24 кадра + _ => 6000, }); self.out_offset = Some((seq_num_offset, ts_offset)); self.last_out_seq = pkt_sequence_number.wrapping_add(seq_num_offset); self.last_out_ts = pkt_timestamp.wrapping_add(ts_offset); - println!( - "Смещения перезаписаны seq_num: {pkt_sequence_number} -> {}; ts: {pkt_timestamp} -> {}", - self.last_out_seq, self.last_out_ts - ); - (self.last_out_seq, self.last_out_ts) + // println!( + // "Смещения перезаписаны seq_num: {pkt_sequence_number} -> {}; ts: {pkt_timestamp} -> {}", + // self.last_out_seq, self.last_out_ts + // ); + PkgAttrs { + sequence_number: self.last_out_seq, + timestamp: self.last_out_ts, + marker: self.marker, + } } } } @@ -87,7 +159,7 @@ impl TrackState { /// If you wish to send a media.Sample use TrackLocalStaticSample #[derive(Debug)] pub struct TrackLocalStaticRTP { - pub(crate) bindings: Mutex>>, + pub(crate) bindings: DashMap>, codec: RTCRtpCodecCapability, id: String, rid: Option, @@ -111,7 +183,7 @@ impl TrackLocalStaticRTP { pub fn new(codec: RTCRtpCodecCapability, id: String, stream_id: String) -> Self { TrackLocalStaticRTP { codec, - bindings: Mutex::new(vec![]), + bindings: DashMap::with_capacity(10), id, rid: None, stream_id, @@ -123,7 +195,7 @@ impl TrackLocalStaticRTP { )), pli_last_ms: AtomicU64::new(0), - pli_interval_ms: 500, + pli_interval_ms: 1000, } } @@ -136,7 +208,7 @@ impl TrackLocalStaticRTP { ) -> Self { TrackLocalStaticRTP { codec, - bindings: Mutex::new(vec![]), + bindings: DashMap::with_capacity(10), id, rid: Some(rid), stream_id, @@ -158,32 +230,45 @@ impl TrackLocalStaticRTP { } pub async fn any_binding_paused(&self) -> bool { - let bindings = self.bindings.lock().await; - bindings - .iter() - .any(|b| b.sender_paused.load(Ordering::SeqCst)) + self.bindings.iter().any(|entry| { + let binding = entry.value(); + binding.sender_paused.load(Ordering::Relaxed) + }) } pub async fn all_binding_paused(&self) -> bool { - let bindings = self.bindings.lock().await; - bindings - .iter() - .all(|b| b.sender_paused.load(Ordering::SeqCst)) + self.bindings.iter().all(|entry| { + let binding = entry.value(); + binding.sender_paused.load(Ordering::Relaxed) + }) } - pub async fn is_binding_active(&self, binding_ssrc: u32) -> bool { - match { - let bindings = self.bindings.lock().await; - bindings - .iter() - .find(|b| b.ssrc == binding_ssrc) - .map(|b| b.clone()) - } { - Some(b) => !b.is_sender_paused(), + pub fn all_binding_paused_sync(&self) -> bool { + self.bindings.iter().all(|entry| { + let binding = entry.value(); + binding.sender_paused.load(Ordering::Relaxed) + }) + } + + pub fn is_binding_active(&self, binding_ssrc: u32) -> bool { + match self.bindings.get(&binding_ssrc) { + // 2. Если элемент найден... + Some(binding_ref) => !binding_ref.value().is_sender_paused(), + // 4. Если элемент не найден, возвращаем false. None => false, } } + /// seq - Последовательный номер в терминах получателей + pub async fn cache_get(&self, seq: u16) -> Option> { + // В кэше хранятся данные без изменений + self.rtp_cache.get_arc({ + let st = self.state.lock().await; + // трансформируем в последовательный номер в терминах отправителя + st.origin_seq(seq) + }) + } + /// Выполняется, когда мы изменяем источник данных для трека pub async fn replace_remote(self: Arc, remote_track: Arc) { // 1. Приводим исходящее смещение к начальному состоянию, @@ -196,35 +281,39 @@ impl TrackLocalStaticRTP { // 2. Запись из mpsc канала в local_track // здесь должен быть минимальный буфер, // т.к. лучше потом отправить из кеша, чем пытаться отправить застрявший пакет из очереди - let (rtp_sender, mut rtp_rx) = mpsc::channel::(64); + let (rtp_sender, mut rtp_rx) = mpsc::channel::>(64); let local_track = Arc::downgrade(&self); let rtp_writer = tokio::spawn(async move { - while let Some(pkt) = rtp_rx.recv().await { + while let Some(p_cache) = rtp_rx.recv().await { if let Some(local_track) = local_track.upgrade() { - if let Err(err) = local_track.write_rtp(&pkt).await { - eprintln!("Ошибка записи данных в исходящий трек: {:?}", err); + if let Err(_err) = local_track.write_rtp(&p_cache.rtp).await { + // eprintln!("Ошибка записи данных в исходящий трек: {:?}", _err); } } else { break; } } - println!("Запись данных в трек остановлена!"); + // println!("Запись данных в трек остановлена!"); }); // 3. Чтение из remote_track в mpsc канал - while let Ok(rtp) = remote_track.read_rtp().await { + while let Ok(rtp) = remote_track.read_rtp_raw().await { // 1. Сохраняем в кэш оригинальный rtp без смещений! Так быстрее происходит сохранение в кэш // При восстановлении кеша нужно вернуть порядковый номер к оригинальному, чтоб найти его - self.rtp_cache.put(rtp.clone()); + let p_cache = Arc::new(PCache { + rtp, + first_sent_at: Instant::now(), + }); + self.rtp_cache.put(Arc::clone(&p_cache)); // 2. Пытаемся отправить, если переполнен буфер, не ждём и позже в ответ на NACK берём из кэша // Без ожиданий, чтоб не замедлять процесс получения пакетов - match rtp_sender.try_send(rtp) { + match rtp_sender.try_send(Arc::clone(&p_cache)) { Err(TrySendError::Closed(_)) => { break; } Err(TrySendError::Full(_)) => { - eprintln!("Ошибка отправки RTP данных: Буфер переполнен"); + // eprintln!("Ошибка отправки RTP данных: Буфер переполнен"); } _ => {} } @@ -235,31 +324,22 @@ impl TrackLocalStaticRTP { } /// Получаем ssrc всех RTCPeerConnection подключений к этому треку - pub async fn bindings_ssrc(&self) -> Vec { - let bindings = self.bindings.lock().await; - bindings.iter().map(|b| b.ssrc).collect() + pub fn bindings_ssrc(&self) -> Vec { + self.bindings.iter().map(|b| b.key().clone()).collect() } - pub async fn bindings_ids(&self) -> Vec { - let bindings = self.bindings.lock().await; - bindings.iter().map(|b| b.id.clone()).collect() + pub fn bindings_ids(&self) -> Vec { + self.bindings.iter().map(|b| b.value().id.clone()).collect() } pub async fn write_rtp_with_extensions_to( &self, p: &rtp::packet::Packet, + pkt_attrs: &PkgAttrs, extensions: &[rtp::extension::HeaderExtension], binding_ssrc: u32, ) -> Result { - let binding = { - let bindings = self.bindings.lock().await; - bindings - .iter() - .find(|b| b.ssrc == binding_ssrc) - .map(|b| b.clone()) - }; - - if let Some(b) = binding { + if let Some(b) = self.bindings.get(&binding_ssrc).map(|b| b.value().clone()) { // Prepare the extensions data let mut extension_error = None; let extension_data: HashMap<_, _> = extensions @@ -283,7 +363,7 @@ impl TrackLocalStaticRTP { return Err(err); } - self.write_rtp_with_extensions_to_binding(p, &extension_data, b) + self.write_rtp_with_extensions_to_binding(p, pkt_attrs, &extension_data, b) .await } else { // Must return Ok(usize) to be consistent with write_rtp_with_extensions_attributes @@ -296,27 +376,26 @@ impl TrackLocalStaticRTP { pkt: &rtp::packet::Packet, extensions: &[rtp::extension::HeaderExtension], ) -> Result { - let mut pkt = pkt.clone(); - - let (seq_number, timestamp) = { + if self.all_binding_paused_sync() { + // Если никто пакеты не получил, то меняем смещение так, чтоб не было пропущенных пакетов + let mut st = self.state.lock().await; + st.shift_offset(pkt.header.sequence_number, pkt.header.timestamp); + return Ok(0); + } + let pkg_attrs = { let mut st = self.state.lock().await; - st.apply_offset( + st.get_pkg_attrs_set_last_out( self.kind(), pkt.header.sequence_number, pkt.header.timestamp, ) }; - pkt.header.sequence_number = seq_number; - pkt.header.timestamp = timestamp; - let mut n = 0; let mut write_errs = vec![]; - let bindings = { - let bindings = self.bindings.lock().await; - bindings.clone() - }; + let bindings: Vec> = + self.bindings.iter().map(|b| b.value().clone()).collect(); // Prepare the extensions data let extension_data: HashMap<_, _> = extensions .iter() @@ -338,7 +417,7 @@ impl TrackLocalStaticRTP { for b in bindings.into_iter() { match self - .write_rtp_with_extensions_to_binding(&pkt, &extension_data, b) + .write_rtp_with_extensions_to_binding(&pkt, &pkg_attrs, &extension_data, b) .await { Ok(one_or_zero) => { @@ -359,38 +438,30 @@ impl TrackLocalStaticRTP { pkt: &rtp::packet::Packet, binding_ssrc: u32, ) -> Result { - let mut pkt = pkt.clone(); - - let (seq_number, timestamp) = { + let pkg_attrs = { let mut st = self.state.lock().await; - st.apply_offset( + st.get_pkg_attrs( self.kind(), pkt.header.sequence_number, pkt.header.timestamp, ) }; - pkt.header.sequence_number = seq_number; - pkt.header.timestamp = timestamp; - self.write_rtp_with_extensions_to(&pkt, &[], binding_ssrc) + self.write_rtp_with_extensions_to(&pkt, &pkg_attrs, &[], binding_ssrc) .await } pub async fn set_muted(&self, muted: bool) { - let bindings = { - let bindings = self.bindings.lock().await; - bindings.clone() - }; + let bindings: Vec> = + self.bindings.iter().map(|b| b.value().clone()).collect(); bindings.iter().for_each(|b| { b.set_sender_paused(muted); }); } pub async fn set_muted_for(&self, bindings_ssrc: Vec<(u32, bool)>) { - let bindings = { - let bindings = self.bindings.lock().await; - bindings.clone() - }; + let bindings: Vec> = + self.bindings.iter().map(|b| b.value().clone()).collect(); bindings.iter().for_each(|b| { if let Some((_, muted)) = bindings_ssrc.iter().find(|(ssrc, _)| *ssrc == b.ssrc) { b.set_sender_paused(*muted); @@ -406,27 +477,33 @@ impl TrackLocalStaticRTP { } if self .pli_last_ms - .compare_exchange(prev, now_ms, Ordering::Relaxed, Ordering::Relaxed) + .compare_exchange_weak(prev, now_ms, Ordering::Relaxed, Ordering::Relaxed) .is_ok() { return true; } // кто-то другой успел обновить last_ms — пробуем снова + + // Мы проиграли гонку. Вместо того чтобы сразу бросаться в новую итерацию, + // дадим процессору подсказку. + std::hint::spin_loop(); } } async fn write_rtp_with_extensions_to_binding( &self, p: &rtp::packet::Packet, + pkg_attrs: &PkgAttrs, extension_data: &HashMap, Bytes>, binidng: Arc, ) -> Result { - let mut pkt = p.clone(); - if binidng.is_sender_paused() { - // See caveat in function doc. return Ok(0); } + + let mut pkt = p.clone(); + pkt.header.sequence_number = pkg_attrs.sequence_number; + pkt.header.timestamp = pkg_attrs.timestamp; pkt.header.ssrc = binidng.ssrc; pkt.header.payload_type = binidng.payload_type; @@ -461,11 +538,6 @@ impl TrackLocal for TrackLocalStaticRTP { /// This asserts that the code requested is supported by the remote peer. /// If so it setups all the state (SSRC and PayloadType) to have a call async fn bind(&self, t: &TrackLocalContext) -> Result { - println!( - "TrackLocalStaticRTP.bind: mid={:?}; ssrc={:?}", - t.mid(), - t.ssrc() - ); let parameters = RTCRtpCodecParameters { capability: self.codec.clone(), ..Default::default() @@ -500,16 +572,18 @@ impl TrackLocal for TrackLocalStaticRTP { let (codec, match_type) = codec_parameters_fuzzy_search(¶meters, t.codec_parameters()); if match_type != CodecMatch::None { { - let mut bindings = self.bindings.lock().await; - bindings.push(Arc::new(TrackBinding { - id: t.id(), - ssrc: t.ssrc(), - payload_type: codec.payload_type, - params: t.params.clone(), - write_stream: t.write_stream(), - sender_paused: t.paused.clone(), - hdr_ext_ids, - })); + self.bindings.insert( + t.ssrc(), + Arc::new(TrackBinding { + id: t.id(), + ssrc: t.ssrc(), + payload_type: codec.payload_type, + params: t.params.clone(), + write_stream: t.write_stream(), + sender_paused: t.paused.clone(), + hdr_ext_ids, + }), + ); } Ok(codec) @@ -521,25 +595,9 @@ impl TrackLocal for TrackLocalStaticRTP { /// unbind implements the teardown logic when the track is no longer needed. This happens /// because a track has been stopped. async fn unbind(&self, t: &TrackLocalContext) -> Result<()> { - println!( - "TrackLocalStaticRTP.unbind: mid={:?}; ssrc={:?}", - t.mid(), - t.ssrc() - ); - let mut bindings = self.bindings.lock().await; - let mut idx = None; - for (index, binding) in bindings.iter().enumerate() { - if binding.id == t.id() { - idx = Some(index); - break; - } - } - if let Some(index) = idx { - bindings.remove(index); - Ok(()) - } else { - Err(Error::ErrUnbindFailed) - } + self.bindings.remove(&t.ssrc()); + + Ok(()) } /// id is the unique identifier for this Track. This should be unique for the diff --git a/webrtc/src/track/track_local/track_local_static_test.rs b/webrtc/src/track/track_local/track_local_static_test.rs deleted file mode 100644 index b385d98ac..000000000 --- a/webrtc/src/track/track_local/track_local_static_test.rs +++ /dev/null @@ -1,434 +0,0 @@ -use std::sync::Arc; - -use bytes::Bytes; -use tokio::sync::{mpsc, Mutex}; - -use super::track_local_static_rtp::*; -use super::track_local_static_sample::*; -use super::*; -use crate::api::media_engine::{MediaEngine, MIME_TYPE_VP8}; -use crate::api::APIBuilder; -use crate::peer_connection::configuration::RTCConfiguration; -use crate::peer_connection::peer_connection_test::*; - -// If a remote doesn't support a Codec used by a `TrackLocalStatic` -// an error should be returned to the user -#[tokio::test] -async fn test_track_local_static_no_codec_intersection() -> Result<()> { - let track: Arc = Arc::new(TrackLocalStaticSample::new( - RTCRtpCodecCapability { - mime_type: "video/vp8".to_owned(), - ..Default::default() - }, - "video".to_owned(), - "webrtc-rs".to_owned(), - )); - - let mut m = MediaEngine::default(); - m.register_default_codecs()?; - let api = APIBuilder::new().with_media_engine(m).build(); - - //"Offerer" - { - let mut pc = api.new_peer_connection(RTCConfiguration::default()).await?; - - let mut no_codec_pc = APIBuilder::new() - .build() - .new_peer_connection(RTCConfiguration::default()) - .await?; - - pc.add_track(Arc::clone(&track)).await?; - - if let Err(err) = signal_pair(&mut pc, &mut no_codec_pc).await { - assert_eq!(err, Error::ErrUnsupportedCodec); - } else { - panic!(); - } - - close_pair_now(&no_codec_pc, &pc).await; - } - - //"Answerer" - { - let mut pc = api.new_peer_connection(RTCConfiguration::default()).await?; - - let mut m = MediaEngine::default(); - m.register_codec( - RTCRtpCodecParameters { - capability: RTCRtpCodecCapability { - mime_type: "video/VP9".to_owned(), - clock_rate: 90000, - channels: 0, - sdp_fmtp_line: "".to_owned(), - rtcp_feedback: vec![], - }, - payload_type: 96, - ..Default::default() - }, - RTPCodecType::Video, - )?; - let mut vp9only_pc = APIBuilder::new() - .with_media_engine(m) - .build() - .new_peer_connection(RTCConfiguration::default()) - .await?; - - vp9only_pc - .add_transceiver_from_kind(RTPCodecType::Video, None) - .await?; - - pc.add_track(Arc::clone(&track)).await?; - - if let Err(err) = signal_pair(&mut vp9only_pc, &mut pc).await { - assert_eq!( - err, - Error::ErrUnsupportedCodec, - "expected {}, but got {}", - Error::ErrUnsupportedCodec, - err - ); - } else { - panic!(); - } - - close_pair_now(&vp9only_pc, &pc).await; - } - - //"Local" - { - let (mut offerer, mut answerer) = new_pair(&api).await?; - - let invalid_codec_track = TrackLocalStaticSample::new( - RTCRtpCodecCapability { - mime_type: "video/invalid-codec".to_owned(), - ..Default::default() - }, - "video".to_owned(), - "webrtc-rs".to_owned(), - ); - - offerer.add_track(Arc::new(invalid_codec_track)).await?; - - if let Err(err) = signal_pair(&mut offerer, &mut answerer).await { - assert_eq!(err, Error::ErrUnsupportedCodec); - } else { - panic!(); - } - - close_pair_now(&offerer, &answerer).await; - } - - Ok(()) -} - -// Assert that Bind/Unbind happens when expected -#[tokio::test] -async fn test_track_local_static_closed() -> Result<()> { - let mut m = MediaEngine::default(); - m.register_default_codecs()?; - let api = APIBuilder::new().with_media_engine(m).build(); - - let (mut pc_offer, mut pc_answer) = new_pair(&api).await?; - - pc_answer - .add_transceiver_from_kind(RTPCodecType::Video, None) - .await?; - - let vp8writer: Arc = Arc::new(TrackLocalStaticRTP::new( - RTCRtpCodecCapability { - mime_type: "video/vp8".to_owned(), - ..Default::default() - }, - "video".to_owned(), - "webrtc-rs".to_owned(), - )); - - pc_offer.add_track(Arc::clone(&vp8writer)).await?; - - if let Some(v) = vp8writer.as_any().downcast_ref::() { - let bindings = v.bindings.lock().await; - assert_eq!( - bindings.len(), - 0, - "No binding should exist before signaling" - ); - } else { - panic!(); - } - - signal_pair(&mut pc_offer, &mut pc_answer).await?; - - if let Some(v) = vp8writer.as_any().downcast_ref::() { - let bindings = v.bindings.lock().await; - assert_eq!(bindings.len(), 1, "binding should exist after signaling"); - } else { - panic!(); - } - - close_pair_now(&pc_offer, &pc_answer).await; - - if let Some(v) = vp8writer.as_any().downcast_ref::() { - let bindings = v.bindings.lock().await; - assert_eq!(bindings.len(), 0, "No binding should exist after close"); - } else { - panic!(); - } - - Ok(()) -} - -//use log::LevelFilter; -//use std::io::Write; - -#[tokio::test] -async fn test_track_local_static_payload_type() -> Result<()> { - /*env_logger::Builder::new() - .format(|buf, record| { - writeln!( - buf, - "{}:{} [{}] {} - {}", - record.file().unwrap_or("unknown"), - record.line().unwrap_or(0), - record.level(), - chrono::Local::now().format("%H:%M:%S.%6f"), - record.args() - ) - }) - .filter(None, LevelFilter::Trace) - .init();*/ - - let mut media_engine_one = MediaEngine::default(); - media_engine_one.register_codec( - RTCRtpCodecParameters { - capability: RTCRtpCodecCapability { - mime_type: MIME_TYPE_VP8.to_owned(), - clock_rate: 90000, - channels: 0, - sdp_fmtp_line: "".to_owned(), - rtcp_feedback: vec![], - }, - payload_type: 100, - ..Default::default() - }, - RTPCodecType::Video, - )?; - - let mut media_engine_two = MediaEngine::default(); - media_engine_two.register_codec( - RTCRtpCodecParameters { - capability: RTCRtpCodecCapability { - mime_type: MIME_TYPE_VP8.to_owned(), - clock_rate: 90000, - channels: 0, - sdp_fmtp_line: "".to_owned(), - rtcp_feedback: vec![], - }, - payload_type: 200, - ..Default::default() - }, - RTPCodecType::Video, - )?; - - let mut offerer = APIBuilder::new() - .with_media_engine(media_engine_one) - .build() - .new_peer_connection(RTCConfiguration::default()) - .await?; - let mut answerer = APIBuilder::new() - .with_media_engine(media_engine_two) - .build() - .new_peer_connection(RTCConfiguration::default()) - .await?; - - let track = Arc::new(TrackLocalStaticSample::new( - RTCRtpCodecCapability { - mime_type: MIME_TYPE_VP8.to_owned(), - ..Default::default() - }, - "video".to_owned(), - "webrtc-rs".to_owned(), - )); - offerer - .add_transceiver_from_kind(RTPCodecType::Video, None) - .await?; - - answerer - .add_track(Arc::clone(&track) as Arc) - .await?; - - let (on_track_fired_tx, on_track_fired_rx) = mpsc::channel::<()>(1); - let on_track_fired_tx = Arc::new(Mutex::new(Some(on_track_fired_tx))); - offerer.on_track(Box::new(move |track, _, _| { - let on_track_fired_tx2 = Arc::clone(&on_track_fired_tx); - Box::pin(async move { - assert_eq!(track.payload_type(), 100); - assert_eq!(track.codec().capability.mime_type, MIME_TYPE_VP8); - { - log::debug!("onTrackFiredFunc!!!"); - let mut done = on_track_fired_tx2.lock().await; - done.take(); - } - }) - })); - - signal_pair(&mut offerer, &mut answerer).await?; - - send_video_until_done( - on_track_fired_rx, - vec![track], - Bytes::from_static(&[0x00]), - None, - ) - .await; - - close_pair_now(&offerer, &answerer).await; - - Ok(()) -} - -// Assert that writing to a Track doesn't modify the input -// Even though we can pass a pointer we shouldn't modify the incoming value -#[tokio::test] -async fn test_track_local_static_mutate_input() -> Result<()> { - /*env_logger::Builder::new() - .format(|buf, record| { - writeln!( - buf, - "{}:{} [{}] {} - {}", - record.file().unwrap_or("unknown"), - record.line().unwrap_or(0), - record.level(), - chrono::Local::now().format("%H:%M:%S.%6f"), - record.args() - ) - }) - .filter(None, LevelFilter::Trace) - .init();*/ - - let mut m = MediaEngine::default(); - m.register_default_codecs()?; - let api = APIBuilder::new().with_media_engine(m).build(); - - let (mut pc_offer, mut pc_answer) = new_pair(&api).await?; - - let vp8writer: Arc = Arc::new(TrackLocalStaticRTP::new( - RTCRtpCodecCapability { - mime_type: MIME_TYPE_VP8.to_owned(), - ..Default::default() - }, - "video".to_owned(), - "webrtc-rs".to_owned(), - )); - - pc_offer.add_track(Arc::clone(&vp8writer)).await?; - - signal_pair(&mut pc_offer, &mut pc_answer).await?; - - let pkt = rtp::packet::Packet { - header: rtp::header::Header { - ssrc: 1, - payload_type: 1, - ..Default::default() - }, - ..Default::default() - }; - if let Some(v) = vp8writer.as_any().downcast_ref::() { - v.write_rtp(&pkt).await?; - } else { - panic!(); - } - - assert_eq!(pkt.header.ssrc, 1); - assert_eq!(pkt.header.payload_type, 1); - - close_pair_now(&pc_offer, &pc_answer).await; - - Ok(()) -} - -//use std::io::Write; -//use log::LevelFilter; - -// Assert that writing to a Track that has Binded (but not connected) -// does not block -#[tokio::test] -async fn test_track_local_static_binding_non_blocking() -> Result<()> { - /*env_logger::Builder::new() - .format(|buf, record| { - writeln!( - buf, - "{}:{} [{}] {} - {}", - record.file().unwrap_or("unknown"), - record.line().unwrap_or(0), - record.level(), - chrono::Local::now().format("%H:%M:%S.%6f"), - record.args() - ) - }) - .filter(None, LevelFilter::Trace) - .init();*/ - - let mut m = MediaEngine::default(); - m.register_default_codecs()?; - let api = APIBuilder::new().with_media_engine(m).build(); - - let (pc_offer, pc_answer) = new_pair(&api).await?; - - pc_offer - .add_transceiver_from_kind(RTPCodecType::Video, None) - .await?; - - let vp8writer: Arc = Arc::new(TrackLocalStaticRTP::new( - RTCRtpCodecCapability { - mime_type: MIME_TYPE_VP8.to_owned(), - ..Default::default() - }, - "video".to_owned(), - "webrtc-rs".to_owned(), - )); - - pc_answer.add_track(Arc::clone(&vp8writer)).await?; - - let offer = pc_offer.create_offer(None).await?; - pc_answer.set_remote_description(offer).await?; - - let answer = pc_answer.create_answer(None).await?; - pc_answer.set_local_description(answer).await?; - - if let Some(v) = vp8writer.as_any().downcast_ref::() { - v.write(&[0u8; 20]).await?; - } else { - panic!(); - } - - close_pair_now(&pc_offer, &pc_answer).await; - - Ok(()) -} - -/* -//TODO: func BenchmarkTrackLocalWrite(b *testing.B) { - offerPC, answerPC, err := newPair() - defer closePairNow(b, offerPC, answerPC) - if err != nil { - b.Fatalf("Failed to create a PC pair for testing") - } - - track, err := NewTrackLocalStaticRTP(RTPCodecCapability{mime_type: MIME_TYPE_VP8}, "video", "pion") - assert.NoError(b, err) - - _, err = offerPC.AddTrack(track) - assert.NoError(b, err) - - _, err = answerPC.AddTransceiverFromKind(RTPCodecTypeVideo) - assert.NoError(b, err) - - b.SetBytes(1024) - - buf := make([]byte, 1024) - for i := 0; i < b.N; i++ { - _, err := track.Write(buf) - assert.NoError(b, err) - } -} -*/ diff --git a/webrtc/src/track/track_remote/mod.rs b/webrtc/src/track/track_remote/mod.rs index 64c6ccb47..d95543896 100644 --- a/webrtc/src/track/track_remote/mod.rs +++ b/webrtc/src/track/track_remote/mod.rs @@ -276,6 +276,17 @@ impl TrackRemote { Ok(pkt) } + // Не вызывает лишние блокировки, а всегда читает данные из receiver + pub async fn read_rtp_raw(&self) -> Result { + let receiver = match self.receiver.as_ref().and_then(|r| r.upgrade()) { + Some(r) => r, + None => return Err(Error::ErrRTPReceiverNil), + }; + + let mut b = vec![0u8; self.receive_mtu]; + Ok(receiver.read_rtp(&mut b, self.tid).await?) + } + /// peek is like Read, but it doesn't discard the packet read pub(crate) async fn peek(&self, b: &mut [u8]) -> Result { let pkt = self.read(b).await?; From 7ded2ae0f30a39ab9dd8186e63ac785cbc834756 Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Wed, 15 Oct 2025 23:52:04 +0300 Subject: [PATCH 08/15] =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=82=D1=80=D0=B5=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webrtc/src/track/track_local/bitrate_state.rs | 114 +++++++++++ webrtc/src/track/track_local/loss_stats.rs | 90 +++++++++ webrtc/src/track/track_local/mod.rs | 2 + webrtc/src/track/track_local/packet_cache.rs | 22 +- .../track_local/track_local_static_rtp.rs | 191 +++++++++++++++++- 5 files changed, 405 insertions(+), 14 deletions(-) create mode 100644 webrtc/src/track/track_local/bitrate_state.rs create mode 100644 webrtc/src/track/track_local/loss_stats.rs diff --git a/webrtc/src/track/track_local/bitrate_state.rs b/webrtc/src/track/track_local/bitrate_state.rs new file mode 100644 index 000000000..32231c4f5 --- /dev/null +++ b/webrtc/src/track/track_local/bitrate_state.rs @@ -0,0 +1,114 @@ +use std::time::{Duration, Instant}; + +#[derive(Debug, Clone, Copy)] +pub struct BitrateSample { + timestamp: Instant, + bitrate_bps: u32, +} + +const BITRATE_HISTORY_SIZE: usize = 15; // 5 сек / 333 мс ≈ 15 samples + +#[derive(Debug, Clone)] +pub struct BitrateState { + last_octet_count: u32, + last_timestamp: Option, + /// Текущий битрейт измеряется в битах в секунду + current_bitrate_bps: u32, + + // Кольцевой буфер фиксированного размера. + // Несколько записанных изменений подряд. Используются для расчёта среднего битрейта + history: [BitrateSample; BITRATE_HISTORY_SIZE], + start_index: usize, // Индекс первого валидного элемента + count: usize, // Количество валидных элементов + /// Для отчётов RR для адаптивного битрейта лучше использовать 3 секунды + max_history_duration: Duration, +} + +impl BitrateState { + pub fn new() -> Self { + let default_sample = BitrateSample { + timestamp: Instant::now(), + bitrate_bps: 0, + }; + Self { + last_octet_count: 0, + last_timestamp: None, + current_bitrate_bps: 0, + history: [default_sample; BITRATE_HISTORY_SIZE], + start_index: 0, + count: 0, + max_history_duration: Duration::from_secs(5), + } + } + + pub fn with_max_duration(mut self, duration: Duration) -> Self { + self.max_history_duration = duration; + self + } + + pub fn current_bitrate(&self) -> u32 { + self.current_bitrate_bps + } + + pub fn update_with_sr(&mut self, octet_count: u32) { + let now = Instant::now(); + + if let Some(last_ts) = self.last_timestamp { + let time_diff = now.duration_since(last_ts).as_secs_f64(); + + if time_diff > 0.0 { + let octet_diff = octet_count.wrapping_sub(self.last_octet_count); + self.current_bitrate_bps = (octet_diff as f64 * 8.0 / time_diff) as u32; + + // Добавляем текущее измерение в историю + self.add_sample(now, self.current_bitrate_bps); + } + } + + self.last_octet_count = octet_count; + self.last_timestamp = Some(now); + } + + fn add_sample(&mut self, timestamp: Instant, bitrate_bps: u32) { + let cutoff_time = timestamp - self.max_history_duration; + + // Очищаем устаревшие samples (двигаем start_index) + while self.count > 0 { + let oldest_sample = &self.history[self.start_index]; + if oldest_sample.timestamp < cutoff_time { + self.start_index = (self.start_index + 1) % BITRATE_HISTORY_SIZE; + self.count -= 1; + } else { + break; + } + } + + // Добавляем новый sample + let insert_index = (self.start_index + self.count) % BITRATE_HISTORY_SIZE; + self.history[insert_index] = BitrateSample { + timestamp, + bitrate_bps, + }; + + if self.count < BITRATE_HISTORY_SIZE { + self.count += 1; + } else { + // Буфер полный - перезаписываем самый старый (двигаем start_index) + self.start_index = (self.start_index + 1) % BITRATE_HISTORY_SIZE; + } + } + + pub fn average_bitrate(&self) -> u32 { + if self.count == 0 { + return 0; + } + + let mut sum: u64 = 0; + for i in 0..self.count { + let index = (self.start_index + i) % BITRATE_HISTORY_SIZE; + sum += self.history[index].bitrate_bps as u64; + } + + (sum / self.count as u64) as u32 + } +} diff --git a/webrtc/src/track/track_local/loss_stats.rs b/webrtc/src/track/track_local/loss_stats.rs new file mode 100644 index 000000000..efd75a393 --- /dev/null +++ b/webrtc/src/track/track_local/loss_stats.rs @@ -0,0 +1,90 @@ +use std::{collections::HashMap, time::Instant}; + +#[derive(Debug)] +pub struct SingleReceiverStats { + fraction_lost: u8, + packets_lost: i32, + last_report_time: Instant, + report_count: u32, +} + +#[derive(Debug)] +pub struct ReceiverLossStats { + pub average_fraction_lost: u8, // Средние потери + pub worst_fraction_lost: u8, // Наихудшие потери среди всех + receiver_stats: HashMap, // Key: SSRC получателя + last_update: Instant, +} + +pub enum ReduceIncrease { + Reduce(u32), + Increase, + NoChange, +} + +impl ReceiverLossStats { + pub fn new() -> Self { + Self { + receiver_stats: HashMap::new(), + worst_fraction_lost: 0, + average_fraction_lost: 0, + last_update: Instant::now(), + } + } + + pub fn update_with_rr(&mut self, receiver_ssrc: u32, fraction_lost: u8, packets_lost: i32) { + // Обновляем статистику для конкретного получателя + let receiver_stat = + self.receiver_stats + .entry(receiver_ssrc) + .or_insert_with(|| SingleReceiverStats { + fraction_lost: 0, + packets_lost: 0, + last_report_time: Instant::now(), + report_count: 0, + }); + + receiver_stat.fraction_lost = fraction_lost; + receiver_stat.packets_lost = packets_lost; + receiver_stat.last_report_time = Instant::now(); + receiver_stat.report_count += 1; + + // Пересчитываем агрегированную статистику + self.recalculate_aggregates(); + self.last_update = Instant::now(); + } + + fn recalculate_aggregates(&mut self) { + if self.receiver_stats.is_empty() { + self.worst_fraction_lost = 0; + self.average_fraction_lost = 0; + return; + } + + // Находим наихудшие потери + self.worst_fraction_lost = self + .receiver_stats + .values() + .map(|stats| stats.fraction_lost) + .max() + .unwrap_or(0); + + // Вычисляем средние потери + let sum: u32 = self + .receiver_stats + .values() + .map(|stats| stats.fraction_lost as u32) + .sum(); + self.average_fraction_lost = (sum / self.receiver_stats.len() as u32) as u8; + } + + pub fn should_reduce(&self) -> ReduceIncrease { + if self.worst_fraction_lost > 10 { + ReduceIncrease::Reduce(std::cmp::max(20, self.average_fraction_lost as u32)) + } else if self.worst_fraction_lost <= 2 { + ReduceIncrease::Increase + } else { + ReduceIncrease::NoChange + } + } +} diff --git a/webrtc/src/track/track_local/mod.rs b/webrtc/src/track/track_local/mod.rs index 1928bfd5a..510d6093d 100644 --- a/webrtc/src/track/track_local/mod.rs +++ b/webrtc/src/track/track_local/mod.rs @@ -1,3 +1,5 @@ +pub mod bitrate_state; +pub mod loss_stats; pub mod packet_cache; pub mod track_local_simple; pub mod track_local_static_rtp; diff --git a/webrtc/src/track/track_local/packet_cache.rs b/webrtc/src/track/track_local/packet_cache.rs index c987bb675..d2b85372c 100644 --- a/webrtc/src/track/track_local/packet_cache.rs +++ b/webrtc/src/track/track_local/packet_cache.rs @@ -1,7 +1,7 @@ use arc_swap::ArcSwapOption; use crossbeam_epoch::{self as epoch, Atomic, Owned}; use crossbeam_utils::CachePadded; -use log::warn; +// use log::warn; use rtp::packet::Packet; use std::{ sync::Arc, @@ -51,24 +51,24 @@ impl PCacheBuffer { match cell.load_full() { Some(entry) => { if entry.rtp.header.sequence_number != seq { - warn!( - "Коллизия кольца entry.seq {} != seq{}", - entry.rtp.header.sequence_number, seq - ); + // warn!( + // "Коллизия кольца entry.seq {} != seq{}", + // entry.rtp.header.sequence_number, seq + // ); return None; } if entry.first_sent_at.elapsed() > self.ttl { - warn!( - "Пакет просрочен {:?} > {:?}", - entry.first_sent_at.elapsed(), - self.ttl - ); + // warn!( + // "Пакет просрочен {:?} > {:?}", + // entry.first_sent_at.elapsed(), + // self.ttl + // ); return None; } Some(entry) } None => { - warn!("LoadFull is None"); + // warn!("LoadFull is None"); None } } diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index a31505c7a..20baac666 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -1,7 +1,8 @@ use bytes::{Bytes, BytesMut}; use dashmap::DashMap; +use rtcp::reception_report::ReceptionReport; use std::any::Any; -use std::sync::atomic::AtomicU64; +use std::sync::atomic::{AtomicU32, AtomicU64}; use std::time::Instant; use std::{borrow::Cow, collections::HashMap, time::Duration}; use tokio::sync::mpsc; @@ -10,6 +11,9 @@ use tokio::sync::Mutex; use util::{Marshal, MarshalSize}; use super::*; +use crate::api::media_engine::{MIME_TYPE_H264, MIME_TYPE_OPUS, MIME_TYPE_VP8}; +use crate::track::track_local::bitrate_state::BitrateState; +use crate::track::track_local::loss_stats::{ReceiverLossStats, ReduceIncrease}; use crate::track::track_local::packet_cache::PCache; use crate::track::track_remote::TrackRemote; use crate::{error::flatten_errs, track::track_local::packet_cache::PCacheBuffer}; @@ -170,6 +174,16 @@ pub struct TrackLocalStaticRTP { pli_last_ms: AtomicU64, pli_interval_ms: u64, + + pub bitrate: Mutex, + pub loss_stats: Mutex, + + pub max_bitrate_bps: u32, + pub current_target_bitrate_bps: AtomicU32, // Добавляем это поле + + last_remb_sent: AtomicU64, // microseconds since start + start_remb_time: Instant, // Референсное время + min_remb_interval: Duration, } /// Количество пакетов в кэше @@ -178,9 +192,28 @@ const CAPACITY: usize = 256; // если 24 пакета в секунду, то /// TTL в миллисекундах, время через которое кэш становится невалидным const TTL_MILLIS: u64 = 3000; +const PLI_INTERVAL_MS: u64 = 500; + +const MAX_VIDEO_BITRATE_BPS: u32 = 1_000_000; +const MAX_AUDIO_BITRATE_BPS: u32 = 64_000; + +const REMB_INTERVAL: Duration = Duration::from_secs(1); + +fn get_max_bitrate(mime_type: &str) -> u32 { + match mime_type { + MIME_TYPE_OPUS => MAX_AUDIO_BITRATE_BPS, + MIME_TYPE_H264 | MIME_TYPE_VP8 => MAX_VIDEO_BITRATE_BPS, + x => panic!("Неподдерживаемый тип кодека! {}", x), + } +} + impl TrackLocalStaticRTP { /// returns a TrackLocalStaticRTP without rid. pub fn new(codec: RTCRtpCodecCapability, id: String, stream_id: String) -> Self { + let max_bitrate_bps = get_max_bitrate(&codec.mime_type); + let start_remb_time = Instant::now(); + // Начинаем с "10 секунд назад", чтобы можно было отправить сразу + let initial_offset = Duration::from_secs(10).as_micros() as u64; TrackLocalStaticRTP { codec, bindings: DashMap::with_capacity(10), @@ -195,7 +228,16 @@ impl TrackLocalStaticRTP { )), pli_last_ms: AtomicU64::new(0), - pli_interval_ms: 1000, + pli_interval_ms: PLI_INTERVAL_MS, + + bitrate: Mutex::new(BitrateState::new()), + loss_stats: Mutex::new(ReceiverLossStats::new()), + max_bitrate_bps, + current_target_bitrate_bps: AtomicU32::new(max_bitrate_bps / 2), + + last_remb_sent: AtomicU64::new(initial_offset), + start_remb_time, + min_remb_interval: REMB_INTERVAL, } } @@ -206,6 +248,10 @@ impl TrackLocalStaticRTP { rid: String, stream_id: String, ) -> Self { + let max_bitrate_bps = get_max_bitrate(&codec.mime_type); + let start_remb_time = Instant::now(); + // Начинаем с "10 секунд назад", чтобы можно было отправить сразу + let initial_offset = Duration::from_secs(10).as_micros() as u64; TrackLocalStaticRTP { codec, bindings: DashMap::with_capacity(10), @@ -220,7 +266,16 @@ impl TrackLocalStaticRTP { )), pli_last_ms: AtomicU64::new(0), - pli_interval_ms: 500, + pli_interval_ms: PLI_INTERVAL_MS, + + bitrate: Mutex::new(BitrateState::new()), + loss_stats: Mutex::new(ReceiverLossStats::new()), + max_bitrate_bps, + current_target_bitrate_bps: AtomicU32::new(max_bitrate_bps / 2), + + last_remb_sent: AtomicU64::new(initial_offset), + start_remb_time, + min_remb_interval: REMB_INTERVAL, } } @@ -530,6 +585,136 @@ impl TrackLocalStaticRTP { binidng.write_stream.write_rtp(&pkt).await } + + pub async fn handle_receiver_report(&self, receiver_ssrc: u32, report: &ReceptionReport) { + let mut loss_stats_guard = self.loss_stats.lock().await; + loss_stats_guard.update_with_rr( + receiver_ssrc, + report.fraction_lost, + report.total_lost as i32, + ); + + // log::warn!( + // "\nRR от получателя {} для SSRC {}: потери {}%", + // receiver_ssrc, + // report.ssrc, + // report.fraction_lost + // ); + } + + pub fn should_send_remb(&self) -> bool { + let now_micros = self.instant_to_micros(Instant::now()); + let last_micros = self.last_remb_sent.load(Ordering::Acquire); + + if now_micros >= last_micros + self.min_remb_interval.as_micros() as u64 { + // CAS для thread safety + self.last_remb_sent + .compare_exchange( + last_micros, + now_micros, + Ordering::Release, + Ordering::Relaxed, + ) + .is_ok() + } else { + false + } + } + + fn instant_to_micros(&self, instant: Instant) -> u64 { + instant.duration_since(self.start_remb_time).as_micros() as u64 + } + + // Используется для отправки REMB отчётов паблишеру трека + pub fn adapt_bitrate(&self) -> Option { + if !self.should_send_remb() { + return None; + } + + let current_target = self.current_target_bitrate_bps.load(Ordering::Relaxed); + // Применяем изменение только если оно существенное (>5% изменения) + let change_threshold = current_target / 20; + // Выбираем минимально возможный размер битрейта (он же, шаг от изменения) + let min_bitrate = self.max_bitrate_bps / 20; + + let real_bitrate = if let Ok(bitrate_guard) = self.bitrate.try_lock() { + bitrate_guard.average_bitrate() + } else { + return None; + }; + + // Защита от некорректных значений битрейта + if real_bitrate == 0 { + return None; + } + + // Если реальный битрейт скакнул, то считать реальным битрейтом целевой битрейт, + // т.к. скачёк, скорее всего, случайный. Ведь не должно быть битрейта больше целевого + let real_bitrate = std::cmp::min(real_bitrate, current_target); + + let should_reduce = if let Ok(loss_stats_guard) = self.loss_stats.try_lock() { + loss_stats_guard.should_reduce() + } else { + return None; + }; + + match should_reduce { + ReduceIncrease::Reduce(degradate) => { + // Уменьшаем на основе реального битрейта, но не ниже минимального порога + let new_bitrate = + std::cmp::max(real_bitrate * (100 - degradate) / 100, min_bitrate); + + if new_bitrate < current_target + && new_bitrate.abs_diff(current_target) > change_threshold + { + log::warn!( + "\nУменьшение битрейта: target={}bps -> {}bps, real={}bps", + current_target, + new_bitrate, + real_bitrate, + ); + // Сохраняем последнее время отправки + self.last_remb_sent + .store(self.instant_to_micros(Instant::now()), Ordering::SeqCst); + self.current_target_bitrate_bps + .store(new_bitrate, Ordering::SeqCst); + Some(new_bitrate) + } else { + None + } + } + ReduceIncrease::Increase => { + // Увеличиваем осторожно, проверяя что реальный битрейт может поддерживаться + let proposed_increase = real_bitrate + min_bitrate; + + // Не увеличиваем больше чем на 50% от реального битрейта за раз + // Битрейт не может быть больше максимального + let max_safe_increase = real_bitrate + (real_bitrate / 2); + let new_bitrate = std::cmp::min(proposed_increase, max_safe_increase); + let new_bitrate = std::cmp::min(new_bitrate, self.max_bitrate_bps); + + if new_bitrate > current_target + && new_bitrate.abs_diff(current_target) > change_threshold + { + log::warn!( + "\nУвеличение битрейта: target={}bps -> {}bps, real={}bps", + current_target, + new_bitrate, + real_bitrate, + ); + // Сохраняем последнее время отправки + self.last_remb_sent + .store(self.instant_to_micros(Instant::now()), Ordering::SeqCst); + self.current_target_bitrate_bps + .store(new_bitrate, Ordering::SeqCst); + Some(new_bitrate) + } else { + None + } + } + ReduceIncrease::NoChange => None, + } + } } #[async_trait] From 8b89b6718623380797d6b56bb1a487f92a0d2d80 Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Fri, 17 Oct 2025 15:52:06 +0300 Subject: [PATCH 09/15] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + examples/examples/broadcast/broadcast.rs | 4 +- .../insertable-streams/insertable-streams.rs | 2 +- .../play-from-disk-h264.rs | 4 +- .../play-from-disk-hevc.rs | 6 +- .../play-from-disk-renegotiation.rs | 2 +- .../play-from-disk-vpx/play-from-disk-vpx.rs | 4 +- examples/examples/reflect/reflect.rs | 4 +- .../examples/rtp-forwarder/rtp-forwarder.rs | 2 +- .../examples/rtp-to-webrtc/rtp-to-webrtc.rs | 2 +- .../save-to-disk-h264/save-to-disk-h264.rs | 2 +- .../save-to-disk-vpx/save-to-disk-vpx.rs | 2 +- examples/examples/simulcast/simulcast.rs | 4 +- examples/examples/swap-tracks/swap-tracks.rs | 4 +- webrtc/Cargo.toml | 1 + .../rtp_transceiver/rtp_receiver/mod_copy.rs | 302 ++++++++++-------- .../rtp_receiver/rtp_receiver_test.rs | 5 +- webrtc/src/track/track_local/mod.rs | 3 - .../track_local/track_local_static_rtp.rs | 243 +++----------- .../bitrate_state.rs | 0 .../loss_stats.rs | 0 .../packet_cache.rs | 0 .../track_local_static_rtp/track_state.rs | 142 ++++++++ 23 files changed, 382 insertions(+), 357 deletions(-) rename webrtc/src/track/track_local/{ => track_local_static_rtp}/bitrate_state.rs (100%) rename webrtc/src/track/track_local/{ => track_local_static_rtp}/loss_stats.rs (100%) rename webrtc/src/track/track_local/{ => track_local_static_rtp}/packet_cache.rs (100%) create mode 100644 webrtc/src/track/track_local/track_local_static_rtp/track_state.rs diff --git a/Cargo.lock b/Cargo.lock index d829402bf..48cd7b720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2845,6 +2845,7 @@ dependencies = [ "dashmap", "dtls", "env_logger", + "futures", "hex", "interceptor", "lazy_static", diff --git a/examples/examples/broadcast/broadcast.rs b/examples/examples/broadcast/broadcast.rs index 2626863d4..f97499eb3 100644 --- a/examples/examples/broadcast/broadcast.rs +++ b/examples/examples/broadcast/broadcast.rs @@ -163,7 +163,7 @@ async fn main() -> Result<()> { let _ = local_track_chan_tx2.send(Arc::clone(&local_track)).await; // Read RTP packets being sent to webrtc-rs - while let Ok((rtp, _)) = track.read_rtp().await { + while let Ok(rtp) = track.read_rtp().await { if let Err(err) = local_track.write_rtp(&rtp).await { if Error::ErrClosedPipe != err { print!("output track write_rtp got error: {err} and break"); @@ -260,7 +260,7 @@ async fn main() -> Result<()> { // like NACK this needs to be called. tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); diff --git a/examples/examples/insertable-streams/insertable-streams.rs b/examples/examples/insertable-streams/insertable-streams.rs index 4e45499fa..e17b787f9 100644 --- a/examples/examples/insertable-streams/insertable-streams.rs +++ b/examples/examples/insertable-streams/insertable-streams.rs @@ -140,7 +140,7 @@ async fn main() -> Result<()> { // like NACK this needs to be called. tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); diff --git a/examples/examples/play-from-disk-h264/play-from-disk-h264.rs b/examples/examples/play-from-disk-h264/play-from-disk-h264.rs index 94e770185..df4d115bc 100644 --- a/examples/examples/play-from-disk-h264/play-from-disk-h264.rs +++ b/examples/examples/play-from-disk-h264/play-from-disk-h264.rs @@ -163,7 +163,7 @@ async fn main() -> Result<()> { // like NACK this needs to be called. tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); @@ -239,7 +239,7 @@ async fn main() -> Result<()> { // like NACK this needs to be called. tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); diff --git a/examples/examples/play-from-disk-hevc/play-from-disk-hevc.rs b/examples/examples/play-from-disk-hevc/play-from-disk-hevc.rs index 4b29e8f0b..856f665e7 100644 --- a/examples/examples/play-from-disk-hevc/play-from-disk-hevc.rs +++ b/examples/examples/play-from-disk-hevc/play-from-disk-hevc.rs @@ -212,7 +212,7 @@ async fn main() -> Result<()> { } m = track.read_rtp() => { println!("rtp readed"); - if let Ok((p, _)) = m { + if let Ok(p) = m { let data = pck.depacketize(&p.payload).unwrap(); match pck.payload() { H265Payload::H265PACIPacket(p) => { @@ -398,7 +398,7 @@ async fn offer_worker( .await?; tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = video_rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = video_rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); let notify1 = notify_connect.clone(); @@ -471,7 +471,7 @@ async fn offer_worker( .await?; tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = audio_rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = audio_rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); let notify1 = notify_connect.clone(); diff --git a/examples/examples/play-from-disk-renegotiation/play-from-disk-renegotiation.rs b/examples/examples/play-from-disk-renegotiation/play-from-disk-renegotiation.rs index 503cd863d..3f853e404 100644 --- a/examples/examples/play-from-disk-renegotiation/play-from-disk-renegotiation.rs +++ b/examples/examples/play-from-disk-renegotiation/play-from-disk-renegotiation.rs @@ -187,7 +187,7 @@ async fn add_video( // like NACK this needs to be called. tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); diff --git a/examples/examples/play-from-disk-vpx/play-from-disk-vpx.rs b/examples/examples/play-from-disk-vpx/play-from-disk-vpx.rs index d3a578dc8..a963b2d3f 100644 --- a/examples/examples/play-from-disk-vpx/play-from-disk-vpx.rs +++ b/examples/examples/play-from-disk-vpx/play-from-disk-vpx.rs @@ -173,7 +173,7 @@ async fn main() -> Result<()> { // like NACK this needs to be called. tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); @@ -245,7 +245,7 @@ async fn main() -> Result<()> { // like NACK this needs to be called. tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); diff --git a/examples/examples/reflect/reflect.rs b/examples/examples/reflect/reflect.rs index c01875e42..092a893b3 100644 --- a/examples/examples/reflect/reflect.rs +++ b/examples/examples/reflect/reflect.rs @@ -178,7 +178,7 @@ async fn main() -> Result<()> { let m = s.to_owned(); tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = rtp_sender.read(&mut rtcp_buf).await {} println!("{m} rtp_sender.read loop exit"); Result::<()>::Ok(()) }); @@ -246,7 +246,7 @@ async fn main() -> Result<()> { track.codec().capability.mime_type ); // Read RTP packets being sent to webrtc-rs - while let Ok((rtp, _)) = track.read_rtp().await { + while let Ok(rtp) = track.read_rtp().await { if let Err(err) = output_track2.write_rtp(&rtp).await { println!("output track write_rtp got error: {err}"); break; diff --git a/examples/examples/rtp-forwarder/rtp-forwarder.rs b/examples/examples/rtp-forwarder/rtp-forwarder.rs index c6e548ae3..f2e40330b 100644 --- a/examples/examples/rtp-forwarder/rtp-forwarder.rs +++ b/examples/examples/rtp-forwarder/rtp-forwarder.rs @@ -209,7 +209,7 @@ async fn main() -> Result<()> { tokio::spawn(async move { let mut b = vec![0u8; 1500]; - while let Ok((mut rtp_packet, _)) = track.read(&mut b).await { + while let Ok(mut rtp_packet) = track.read(&mut b).await { // Update the PayloadType rtp_packet.header.payload_type = c.payload_type; diff --git a/examples/examples/rtp-to-webrtc/rtp-to-webrtc.rs b/examples/examples/rtp-to-webrtc/rtp-to-webrtc.rs index ddcb0555f..3221bd903 100644 --- a/examples/examples/rtp-to-webrtc/rtp-to-webrtc.rs +++ b/examples/examples/rtp-to-webrtc/rtp-to-webrtc.rs @@ -117,7 +117,7 @@ async fn main() -> Result<()> { // like NACK this needs to be called. tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); diff --git a/examples/examples/save-to-disk-h264/save-to-disk-h264.rs b/examples/examples/save-to-disk-h264/save-to-disk-h264.rs index 238320b37..3d1e0cba2 100644 --- a/examples/examples/save-to-disk-h264/save-to-disk-h264.rs +++ b/examples/examples/save-to-disk-h264/save-to-disk-h264.rs @@ -30,7 +30,7 @@ async fn save_to_disk( loop { tokio::select! { result = track.read_rtp() => { - if let Ok((rtp_packet, _)) = result { + if let Ok(rtp_packet) = result { let mut w = writer.lock().await; w.write_rtp(&rtp_packet)?; }else{ diff --git a/examples/examples/save-to-disk-vpx/save-to-disk-vpx.rs b/examples/examples/save-to-disk-vpx/save-to-disk-vpx.rs index 2b02986b6..3d141a4a8 100644 --- a/examples/examples/save-to-disk-vpx/save-to-disk-vpx.rs +++ b/examples/examples/save-to-disk-vpx/save-to-disk-vpx.rs @@ -31,7 +31,7 @@ async fn save_to_disk( loop { tokio::select! { result = track.read_rtp() => { - if let Ok((rtp_packet, _)) = result { + if let Ok(rtp_packet) = result { let mut w = writer.lock().await; w.write_rtp(&rtp_packet)?; }else{ diff --git a/examples/examples/simulcast/simulcast.rs b/examples/examples/simulcast/simulcast.rs index f47c7b620..ed5eb1fa9 100644 --- a/examples/examples/simulcast/simulcast.rs +++ b/examples/examples/simulcast/simulcast.rs @@ -136,7 +136,7 @@ async fn main() -> Result<()> { // like NACK this needs to be called. tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); @@ -193,7 +193,7 @@ async fn main() -> Result<()> { tokio::spawn(async move { // Read RTP packets being sent to webrtc-rs println!("enter track loop {}", track.rid()); - while let Ok((rtp, _)) = track.read_rtp().await { + while let Ok(rtp) = track.read_rtp().await { if let Err(err) = output_track.write_rtp(&rtp).await { if Error::ErrClosedPipe != err { println!("output track write_rtp got error: {err} and break"); diff --git a/examples/examples/swap-tracks/swap-tracks.rs b/examples/examples/swap-tracks/swap-tracks.rs index db8cd25d7..dc69511bb 100644 --- a/examples/examples/swap-tracks/swap-tracks.rs +++ b/examples/examples/swap-tracks/swap-tracks.rs @@ -118,7 +118,7 @@ async fn main() -> Result<()> { // like NACK this needs to be called. tokio::spawn(async move { let mut rtcp_buf = vec![0u8; 1500]; - while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} + while let Ok(_) = rtp_sender.read(&mut rtcp_buf).await {} Result::<()>::Ok(()) }); @@ -159,7 +159,7 @@ async fn main() -> Result<()> { let mut last_timestamp = 0; let mut is_curr_track = false; - while let Ok((mut rtp, _)) = track.read_rtp().await { + while let Ok(mut rtp) = track.read_rtp().await { // Change the timestamp to only be the delta let old_timestamp = rtp.header.timestamp; if last_timestamp == 0 { diff --git a/webrtc/Cargo.toml b/webrtc/Cargo.toml index a5560bfdd..feecff868 100644 --- a/webrtc/Cargo.toml +++ b/webrtc/Cargo.toml @@ -63,6 +63,7 @@ unicase = "2.8" parking_lot = "0.12.5" crossbeam-utils = "0.8.21" crossbeam-epoch = "0.9.18" +futures = "0.3.31" [dev-dependencies] env_logger = "0.11.3" diff --git a/webrtc/src/rtp_transceiver/rtp_receiver/mod_copy.rs b/webrtc/src/rtp_transceiver/rtp_receiver/mod_copy.rs index 45025e17b..2b606d007 100644 --- a/webrtc/src/rtp_transceiver/rtp_receiver/mod_copy.rs +++ b/webrtc/src/rtp_transceiver/rtp_receiver/mod_copy.rs @@ -5,12 +5,10 @@ use std::fmt; use std::sync::Arc; use arc_swap::ArcSwapOption; -use dashmap::DashMap; use interceptor::stream_info::{AssociatedStreamInfo, RTPHeaderExtension}; use interceptor::Interceptor; -use log::trace; use smol_str::SmolStr; -use tokio::sync::{watch, Mutex}; +use tokio::sync::{watch, Mutex, RwLock}; use crate::api::media_engine::MediaEngine; use crate::dtls_transport::RTCDtlsTransport; @@ -152,7 +150,7 @@ pub struct RTPReceiverInternal { state_tx: watch::Sender, state_rx: watch::Receiver, - tracks: DashMap, + tracks: RwLock>, transceiver_codecs: ArcSwapOption>>, @@ -189,12 +187,16 @@ impl RTPReceiverInternal { // Err(Error::ErrExistingTrack) // } + // Это работает чуть быстрее, чем если использовать код выше let rtcp_interceptor = { - let t = self.tracks.get(&0).ok_or(Error::ErrExistingTrack)?; + let tracks = self.tracks.read().await; // Блокировка захвачена // TODO: возможно, нужна логика выбора трека на основе симулкаст предпочтения // Вместо того, чтоб просто брать первый трек, или читать оповещения от всех треков - t.stream + tracks + .first() + .ok_or(Error::ErrExistingTrack)? + .stream .rtcp_interceptor .as_ref() .ok_or(Error::ErrInterceptorNotBind)? @@ -225,10 +227,8 @@ impl RTPReceiverInternal { // isn't flowing. State::wait_for(&mut state_watch_rx, &[State::Started, State::Paused]).await?; - // let tracks = self.tracks.read().await; - let tracks = self.tracks.iter_mut().collect::>(); - for mut tv in tracks { - let t = tv.value_mut(); + let tracks = self.tracks.read().await; + for t in &*tracks { if t.track.rid() == rid { if let Some(rtcp_interceptor) = &t.stream.rtcp_interceptor { loop { @@ -256,9 +256,8 @@ impl RTPReceiverInternal { receive_mtu: usize, ) -> Result>> { let mut b = vec![0u8; receive_mtu]; - let pkts = self.read(&mut b).await?; - Ok(pkts) + Ok(self.read(&mut b).await?) } /// read_simulcast_rtcp is a convenience method that wraps ReadSimulcast and unmarshal for you @@ -274,61 +273,122 @@ impl RTPReceiverInternal { } pub(crate) async fn read_rtp(&self, b: &mut [u8], tid: usize) -> Result { + // println!("Начали чтение"); + // let mut state_watch_rx = self.state_tx.subscribe(); + + // // Ensure we are running. + // State::wait_for(&mut state_watch_rx, &[State::Started]).await?; + + // //log::debug!("read_rtp enter tracks tid {}", tid); + // let mut rtp_interceptor = None; + // //let mut ssrc = 0; + // { + // let tracks = self.tracks.read().await; + // for t in &*tracks { + // if t.track.tid() == tid { + // rtp_interceptor.clone_from(&t.stream.rtp_interceptor); + // //ssrc = t.track.ssrc(); + // break; + // } + // } + // }; + // /*log::debug!( + // "read_rtp exit tracks with rtp_interceptor {} with tid {}", + // rtp_interceptor.is_some(), + // tid, + // );*/ + // if let Some(rtp_interceptor) = rtp_interceptor { + // //println!( + // // "read_rtp rtp_interceptor.read enter with tid {} ssrc {}", + // // tid, ssrc + // //); + // let mut current_state = *state_watch_rx.borrow(); + // loop { + // tokio::select! { + // _ = state_watch_rx.changed() => { + // let new_state = *state_watch_rx.borrow(); + + // if new_state == State::Stopped { + // return Err(Error::ErrClosedPipe); + // } + // current_state = new_state; + // } + // result = rtp_interceptor.read(b) => { + // let result = result?; + + // if current_state == State::Paused { + // log::trace!("Dropping {} read bytes received while RTPReceiver was paused", result); + // continue; + // } + // return Ok(result); + // } + // } + // } + // } else { + // //log::debug!("read_rtp exit tracks with ErrRTPReceiverWithSSRCTrackStreamNotFound"); + // Err(Error::ErrRTPReceiverWithSSRCTrackStreamNotFound) + // } + let mut state_watch_rx = self.state_tx.subscribe(); // Ensure we are running. State::wait_for(&mut state_watch_rx, &[State::Started]).await?; - //log::debug!("read_rtp enter tracks tid {}", tid); - let mut rtp_interceptor = None; - //let mut ssrc = 0; - { - let tracks = self.tracks.iter_mut().collect::>(); - for mut tv in tracks { - let t = tv.value_mut(); - if t.track.tid() == tid { - rtp_interceptor.clone_from(&t.stream.rtp_interceptor); - //ssrc = t.track.ssrc(); - break; + // println!("read_rtp enter tracks tid {}", tid); + let rtp_interceptor = { + let tracks = self.tracks.read().await; + tracks + .iter() + .find(|t| t.track.tid() == tid) + .ok_or(Error::ErrRTPReceiverWithSSRCTrackStreamNotFound)? + .stream + .rtp_interceptor + .as_ref() + .ok_or(Error::ErrRTPReceiverWithSSRCTrackStreamNotFound)? + .clone() + }; + + loop { + let current_state = *state_watch_rx.borrow(); + + match current_state { + State::Stopped => { + return Err(Error::ErrClosedPipe); + } + State::Started => {} + _ => { + State::wait_for(&mut state_watch_rx, &[State::Started]).await?; } } - }; - /*log::debug!( - "read_rtp exit tracks with rtp_interceptor {} with tid {}", - rtp_interceptor.is_some(), - tid, - );*/ - - if let Some(rtp_interceptor) = rtp_interceptor { - //println!( - // "read_rtp rtp_interceptor.read enter with tid {} ssrc {}", - // tid, ssrc - //); - let mut current_state = *state_watch_rx.borrow(); - loop { - tokio::select! { - _ = state_watch_rx.changed() => { - let new_state = *state_watch_rx.borrow(); - - if new_state == State::Stopped { - return Err(Error::ErrClosedPipe); - } - current_state = new_state; + + tokio::select! { + res = state_watch_rx.changed() => { + if let Err(_) = res { + return Err(Error::ErrClosedPipe); + } + let new_state = *state_watch_rx.borrow(); + + if new_state == State::Stopped { + return Err(Error::ErrClosedPipe); } - result = rtp_interceptor.read(b) => { - let result = result?; + continue; + } + result = rtp_interceptor.read(b) => { + let result = result?; - if current_state == State::Paused { - trace!("Dropping {} read bytes received while RTPReceiver was paused", result); + let current_state = *state_watch_rx.borrow(); + match current_state { + State::Stopped => { + return Err(Error::ErrClosedPipe); + } + State::Paused => { continue; } - return Ok(result); + _ => {} } + return Ok(result); } } - } else { - //log::debug!("read_rtp exit tracks with ErrRTPReceiverWithSSRCTrackStreamNotFound"); - Err(Error::ErrRTPReceiverWithSSRCTrackStreamNotFound) } } @@ -445,8 +505,7 @@ impl RTCRtpReceiver { internal: Arc::new(RTPReceiverInternal { kind, - // tracks: RwLock::new(vec![]), - tracks: DashMap::with_capacity(3), + tracks: RwLock::new(vec![]), transport, media_engine, interceptor, @@ -495,40 +554,26 @@ impl RTCRtpReceiver { }); } - // let mut tracks = self.internal.tracks.write().await; + let mut tracks = self.internal.tracks.write().await; for (idx, codec) in params.codecs.iter().enumerate() { - // let t = &mut tracks[idx]; - // if let Some(stream_info) = &mut t.stream.stream_info { - // stream_info - // .rtp_header_extensions - // .clone_from(&header_extensions); - // } - - // let current_track = &t.track; - // current_track.set_codec(codec.clone()); - // current_track.set_params(params.clone()); - - if let Some(mut t) = self.internal.tracks.get_mut(&idx) { - if let Some(stream_info) = &mut t.stream.stream_info { - stream_info - .rtp_header_extensions - .clone_from(&header_extensions); - } - - t.track.set_codec(codec.clone()); - t.track.set_params(params.clone()); + let t = &mut tracks[idx]; + if let Some(stream_info) = &mut t.stream.stream_info { + stream_info + .rtp_header_extensions + .clone_from(&header_extensions); } + + let current_track = &t.track; + current_track.set_codec(codec.clone()); + current_track.set_params(params.clone()); } } /// tracks returns the RtpTransceiver traclockks /// A RTPReceiver to support Simulcast may now have multiple tracks pub async fn tracks(&self) -> Vec> { - self.internal - .tracks - .iter() - .map(|t| Arc::clone(&t.value().track)) - .collect() + let tracks = self.internal.tracks.read().await; + tracks.iter().map(|t| Arc::clone(&t.track)).collect() } /// receive initialize the track and starts all the transports @@ -555,7 +600,7 @@ impl RTCRtpReceiver { RTCRtpCodecParameters::default() }; - for (idx, encoding) in parameters.encodings.iter().enumerate() { + for encoding in ¶meters.encodings { let (stream_info, rtp_read_stream, rtp_interceptor, rtcp_read_stream, rtcp_interceptor) = if encoding.ssrc != 0 { let stream_info = create_stream_info( @@ -611,10 +656,8 @@ impl RTCRtpReceiver { }; { - // let mut tracks = self.internal.tracks.write().await; - // tracks.push(t.clone()); - - self.internal.tracks.insert(idx, t); + let mut tracks = self.internal.tracks.write().await; + tracks.push(t); }; let rtx_ssrc = encoding.rtx.ssrc; @@ -740,53 +783,44 @@ impl RTCRtpReceiver { let mut errs = vec![]; let was_ever_started = previous_state.is_started(); if was_ever_started { - // let tracks = self.internal.tracks.write().await; - let keys: Vec<_> = self - .internal - .tracks - .iter() - .map(|kv| kv.key().clone()) - .collect(); - for k in keys { - if let Some(tv) = self.internal.tracks.get(&k) { - let t = tv.value(); - if let Some(rtcp_read_stream) = &t.stream.rtcp_read_stream { - if let Err(err) = rtcp_read_stream.close().await { - errs.push(err); - } + let tracks = self.internal.tracks.write().await; + for t in &*tracks { + if let Some(rtcp_read_stream) = &t.stream.rtcp_read_stream { + if let Err(err) = rtcp_read_stream.close().await { + errs.push(err); } + } - if let Some(rtp_read_stream) = &t.stream.rtp_read_stream { - if let Err(err) = rtp_read_stream.close().await { - errs.push(err); - } + if let Some(rtp_read_stream) = &t.stream.rtp_read_stream { + if let Err(err) = rtp_read_stream.close().await { + errs.push(err); } + } - if let Some(repair_rtcp_read_stream) = &t.repair_stream.rtcp_read_stream { - if let Err(err) = repair_rtcp_read_stream.close().await { - errs.push(err); - } + if let Some(repair_rtcp_read_stream) = &t.repair_stream.rtcp_read_stream { + if let Err(err) = repair_rtcp_read_stream.close().await { + errs.push(err); } + } - if let Some(repair_rtp_read_stream) = &t.repair_stream.rtp_read_stream { - if let Err(err) = repair_rtp_read_stream.close().await { - errs.push(err); - } + if let Some(repair_rtp_read_stream) = &t.repair_stream.rtp_read_stream { + if let Err(err) = repair_rtp_read_stream.close().await { + errs.push(err); } + } - if let Some(stream_info) = &t.stream.stream_info { - self.internal - .interceptor - .unbind_remote_stream(stream_info) - .await; - } + if let Some(stream_info) = &t.stream.stream_info { + self.internal + .interceptor + .unbind_remote_stream(stream_info) + .await; + } - if let Some(repair_stream_info) = &t.repair_stream.stream_info { - self.internal - .interceptor - .unbind_remote_stream(repair_stream_info) - .await; - } + if let Some(repair_stream_info) = &t.repair_stream.stream_info { + self.internal + .interceptor + .unbind_remote_stream(repair_stream_info) + .await; } } } @@ -807,9 +841,8 @@ impl RTCRtpReceiver { params: RTCRtpParameters, stream: TrackStream, ) -> Result> { - let tracks = self.internal.tracks.iter_mut().collect::>(); - for mut tv in tracks { - let t = tv.value_mut(); + let mut tracks = self.internal.tracks.write().await; + for t in &mut *tracks { if *t.track.rid() == rid { t.track.set_kind(self.internal.kind); if let Some(codec) = params.codecs.first() { @@ -835,10 +868,9 @@ impl RTCRtpReceiver { rsid: String, repair_stream: TrackStream, ) -> Result<()> { - let tracks = self.internal.tracks.iter_mut().collect::>(); + let mut tracks = self.internal.tracks.write().await; let l = tracks.len(); - for mut tv in tracks { - let t = tv.value_mut(); + for t in &mut *tracks { if (ssrc != 0 && l == 1) || t.track.rid() == rsid { t.repair_stream = repair_stream; @@ -875,10 +907,9 @@ impl RTCRtpReceiver { return Ok(()); } - let streams = self.internal.tracks.iter_mut().collect::>(); + let streams = self.internal.tracks.read().await; - for mut vk in streams { - let stream = vk.value_mut(); + for stream in streams.iter() { // TODO: If we introduce futures as a direct dependency this and other futures could be // ran concurrently with [`join_all`](https://docs.rs/futures/0.3.21/futures/future/fn.join_all.html) stream.track.fire_onmute().await; @@ -894,10 +925,9 @@ impl RTCRtpReceiver { return Ok(()); } - let streams = self.internal.tracks.iter_mut().collect::>(); + let streams = self.internal.tracks.read().await; - for mut vk in streams { - let stream = vk.value_mut(); + for stream in streams.iter() { // TODO: If we introduce futures as a direct dependency this and other futures could be // ran concurrently with [`join_all`](https://docs.rs/futures/0.3.21/futures/future/fn.join_all.html) stream.track.fire_onunmute().await; diff --git a/webrtc/src/rtp_transceiver/rtp_receiver/rtp_receiver_test.rs b/webrtc/src/rtp_transceiver/rtp_receiver/rtp_receiver_test.rs index 2a821912b..29a36351c 100644 --- a/webrtc/src/rtp_transceiver/rtp_receiver/rtp_receiver_test.rs +++ b/webrtc/src/rtp_transceiver/rtp_receiver/rtp_receiver_test.rs @@ -1,8 +1,7 @@ -use std::time::Instant; - use bytes::Bytes; use media::Sample; use rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication; +use std::time::Instant; use tokio::sync::mpsc; use tokio::time::Duration; use waitgroup::WaitGroup; @@ -533,7 +532,7 @@ async fn test_rtp_receiver_internal_read_single_rtp_latency() -> Result<()> { ); // 10) SLA по средней задержке — подстрой под свой CI/окружение - let sla_avg = Duration::from_micros(50); // пример: 2 ms + let sla_avg = Duration::from_micros(1); // пример: 2 ms assert!( Duration::from_nanos(avg_ns as u64) <= sla_avg, "Average RTP read latency {:.1} µs exceeded SLA {:?}", diff --git a/webrtc/src/track/track_local/mod.rs b/webrtc/src/track/track_local/mod.rs index 510d6093d..7f224d092 100644 --- a/webrtc/src/track/track_local/mod.rs +++ b/webrtc/src/track/track_local/mod.rs @@ -1,6 +1,3 @@ -pub mod bitrate_state; -pub mod loss_stats; -pub mod packet_cache; pub mod track_local_simple; pub mod track_local_static_rtp; pub mod track_local_static_sample; diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index 20baac666..c3b62403a 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -1,5 +1,11 @@ +mod bitrate_state; +mod loss_stats; +mod packet_cache; +mod track_state; + use bytes::{Bytes, BytesMut}; use dashmap::DashMap; +use futures::{stream, StreamExt}; use rtcp::reception_report::ReceptionReport; use std::any::Any; use std::sync::atomic::{AtomicU32, AtomicU64}; @@ -11,153 +17,13 @@ use tokio::sync::Mutex; use util::{Marshal, MarshalSize}; use super::*; -use crate::api::media_engine::{MIME_TYPE_H264, MIME_TYPE_OPUS, MIME_TYPE_VP8}; -use crate::track::track_local::bitrate_state::BitrateState; -use crate::track::track_local::loss_stats::{ReceiverLossStats, ReduceIncrease}; -use crate::track::track_local::packet_cache::PCache; +use crate::api::media_engine::{MIME_TYPE_H264, MIME_TYPE_OPUS, MIME_TYPE_VP8, MIME_TYPE_VP9}; +use crate::error::flatten_errs; use crate::track::track_remote::TrackRemote; -use crate::{error::flatten_errs, track::track_local::packet_cache::PCacheBuffer}; - -#[derive(Clone, Debug)] -pub struct TrackState { - last_out_seq: u16, // переживает все переключения источников - last_out_ts: u32, // переживает все переключения источников - started_at_ts: i64, - marker: bool, // marker = true означает, что это последний пакет видеофрейма. - // Это сигнал для джиттер-буфера и декодера, что можно собрать все полученные пакеты этого фрейма и отправить их на декодирование. - // Используется после паузы - out_offset: Option<( - u16, /* смещение порядкового номера */ - u32, /* смещение временной метки timestamp */ - )>, -} - -pub struct PkgAttrs { - sequence_number: u16, - timestamp: u32, - marker: bool, -} - -impl TrackState { - pub fn new() -> Self { - TrackState { - // Порядковый номер начинается с 0 - last_out_seq: 0, - // время трека начинается с 0 - last_out_ts: 0, - // Сохраняем начало трека в реальной временной шкале дла последующей синхронизации - started_at_ts: chrono::Utc::now().timestamp(), - out_offset: None, - marker: false, - } - } - - pub fn get_pkg_attrs( - &mut self, - kind: RTPCodecType, - pkt_sequence_number: u16, - pkt_timestamp: u32, - ) -> PkgAttrs { - match self.out_offset { - Some((seq_num_offset, ts_offset)) => PkgAttrs { - sequence_number: pkt_sequence_number.wrapping_add(seq_num_offset), - timestamp: pkt_timestamp.wrapping_add(ts_offset), - marker: self.marker, - }, - None => { - let seq_num_offset = self - .last_out_seq - .wrapping_sub(pkt_sequence_number) - .wrapping_add(1); - let ts_offset = - self.last_out_ts - .wrapping_sub(pkt_timestamp) - .wrapping_add(match kind { - RTPCodecType::Audio => 900, // стандартное значение для звука - RTPCodecType::Video => 3750, // 90000 clock_rate / 24 кадра - _ => 3750, - }); - self.out_offset = Some((seq_num_offset, ts_offset)); - - PkgAttrs { - sequence_number: pkt_sequence_number.wrapping_add(seq_num_offset), - timestamp: pkt_timestamp.wrapping_add(ts_offset), - marker: self.marker, - } - } - } - } - - pub fn set_last_out(&mut self, pkg_attrs: PkgAttrs) { - self.last_out_seq = pkg_attrs.sequence_number; - self.last_out_ts = pkg_attrs.timestamp; - self.marker = false; - } - - pub fn shift_offset(&mut self, pkt_sequence_number: u16, pkt_timestamp: u32) { - self.marker = true; - self.out_offset = Some(( - self.last_out_seq.wrapping_sub(pkt_sequence_number), - self.last_out_ts.wrapping_sub(pkt_timestamp), - )) - } - - pub fn get_pkg_attrs_set_last_out( - &mut self, - kind: RTPCodecType, - pkt_sequence_number: u16, - pkt_timestamp: u32, - ) -> PkgAttrs { - match self.out_offset { - Some((seq_num_offset, ts_offset)) => { - // новый = пришедший + смещение - // старый = новый - (новый - старый) - self.last_out_seq = pkt_sequence_number.wrapping_add(seq_num_offset); - self.last_out_ts = pkt_timestamp.wrapping_add(ts_offset); - PkgAttrs { - sequence_number: self.last_out_seq, - timestamp: self.last_out_ts, - marker: self.marker, - } - } - None => { - let seq_num_offset = self - .last_out_seq - .wrapping_sub(pkt_sequence_number) - .wrapping_add(1); - let ts_offset = - self.last_out_ts - .wrapping_sub(pkt_timestamp) - .wrapping_add(match kind { - RTPCodecType::Audio => 900, // стандартное значение для звука - RTPCodecType::Video => 6000, // 90000 clock_rate / 24 кадра - _ => 6000, - }); - self.out_offset = Some((seq_num_offset, ts_offset)); - - self.last_out_seq = pkt_sequence_number.wrapping_add(seq_num_offset); - self.last_out_ts = pkt_timestamp.wrapping_add(ts_offset); - - // println!( - // "Смещения перезаписаны seq_num: {pkt_sequence_number} -> {}; ts: {pkt_timestamp} -> {}", - // self.last_out_seq, self.last_out_ts - // ); - PkgAttrs { - sequence_number: self.last_out_seq, - timestamp: self.last_out_ts, - marker: self.marker, - } - } - } - } - - pub fn origin_seq(&self, modified_seq: u16) -> u16 { - match self.out_offset { - Some((seq_num_offset, _)) => modified_seq.wrapping_sub(seq_num_offset), - None => modified_seq, - } - } -} +use bitrate_state::BitrateState; +use loss_stats::{ReceiverLossStats, ReduceIncrease}; +use packet_cache::{PCache, PCacheBuffer}; +use track_state::{PkgAttrs, TrackState}; /// TrackLocalStaticRTP is a TrackLocal that has a pre-set codec and accepts RTP Packets. /// If you wish to send a media.Sample use TrackLocalStaticSample @@ -202,7 +68,7 @@ const REMB_INTERVAL: Duration = Duration::from_secs(1); fn get_max_bitrate(mime_type: &str) -> u32 { match mime_type { MIME_TYPE_OPUS => MAX_AUDIO_BITRATE_BPS, - MIME_TYPE_H264 | MIME_TYPE_VP8 => MAX_VIDEO_BITRATE_BPS, + MIME_TYPE_H264 | MIME_TYPE_VP8 | "video/vp8" | MIME_TYPE_VP9 => MAX_VIDEO_BITRATE_BPS, x => panic!("Неподдерживаемый тип кодека! {}", x), } } @@ -248,35 +114,9 @@ impl TrackLocalStaticRTP { rid: String, stream_id: String, ) -> Self { - let max_bitrate_bps = get_max_bitrate(&codec.mime_type); - let start_remb_time = Instant::now(); - // Начинаем с "10 секунд назад", чтобы можно было отправить сразу - let initial_offset = Duration::from_secs(10).as_micros() as u64; - TrackLocalStaticRTP { - codec, - bindings: DashMap::with_capacity(10), - id, - rid: Some(rid), - stream_id, - - state: Mutex::new(TrackState::new()), - rtp_cache: Arc::new(PCacheBuffer::new( - Duration::from_millis(TTL_MILLIS), - CAPACITY, - )), - - pli_last_ms: AtomicU64::new(0), - pli_interval_ms: PLI_INTERVAL_MS, - - bitrate: Mutex::new(BitrateState::new()), - loss_stats: Mutex::new(ReceiverLossStats::new()), - max_bitrate_bps, - current_target_bitrate_bps: AtomicU32::new(max_bitrate_bps / 2), - - last_remb_sent: AtomicU64::new(initial_offset), - start_remb_time, - min_remb_interval: REMB_INTERVAL, - } + let mut track = TrackLocalStaticRTP::new(codec, id, stream_id); + track.rid = Some(rid); + track } /// codec gets the Codec of the track @@ -379,6 +219,8 @@ impl TrackLocalStaticRTP { } /// Получаем ssrc всех RTCPeerConnection подключений к этому треку + /// в терминах отправителя трека. + /// Используется для отчёта pub fn bindings_ssrc(&self) -> Vec { self.bindings.iter().map(|b| b.key().clone()).collect() } @@ -418,7 +260,11 @@ impl TrackLocalStaticRTP { return Err(err); } - self.write_rtp_with_extensions_to_binding(p, pkt_attrs, &extension_data, b) + let mut p = p.clone(); + p.header.timestamp = pkt_attrs.timestamp; + p.header.sequence_number = pkt_attrs.sequence_number; + + self.write_rtp_with_extensions_to_binding(p, extension_data.clone(), b) .await } else { // Must return Ok(usize) to be consistent with write_rtp_with_extensions_attributes @@ -446,7 +292,7 @@ impl TrackLocalStaticRTP { ) }; - let mut n = 0; + // let mut n = 0; let mut write_errs = vec![]; let bindings: Vec> = @@ -470,17 +316,28 @@ impl TrackLocalStaticRTP { }) .collect(); - for b in bindings.into_iter() { - match self - .write_rtp_with_extensions_to_binding(&pkt, &pkg_attrs, &extension_data, b) - .await - { - Ok(one_or_zero) => { - n += one_or_zero; - } - Err(err) => { - write_errs.push(err); + let concurrency: usize = 4; // подберите под ваши ресурсы + let results = stream::iter(bindings) + .map(|b| { + let mut pkt = pkt.clone(); + pkt.header.timestamp = pkg_attrs.timestamp; + pkt.header.sequence_number = pkg_attrs.sequence_number; + let extension_data = extension_data.clone(); + async move { + self.write_rtp_with_extensions_to_binding(pkt, extension_data, b) + .await } + }) + .buffer_unordered(concurrency) + .collect::>() + .await; + + let mut n = 0; + // let mut write_errs = Vec::new(); + for res in results { + match res { + Ok(one_or_zero) => n += one_or_zero, + Err(e) => write_errs.push(e), } } @@ -547,18 +404,16 @@ impl TrackLocalStaticRTP { async fn write_rtp_with_extensions_to_binding( &self, - p: &rtp::packet::Packet, - pkg_attrs: &PkgAttrs, - extension_data: &HashMap, Bytes>, + mut pkt: rtp::packet::Packet, + extension_data: HashMap, Bytes>, binidng: Arc, ) -> Result { if binidng.is_sender_paused() { return Ok(0); } - let mut pkt = p.clone(); - pkt.header.sequence_number = pkg_attrs.sequence_number; - pkt.header.timestamp = pkg_attrs.timestamp; + // pkt.header.sequence_number = pkg_attrs.sequence_number; + // pkt.header.timestamp = pkg_attrs.timestamp; pkt.header.ssrc = binidng.ssrc; pkt.header.payload_type = binidng.payload_type; diff --git a/webrtc/src/track/track_local/bitrate_state.rs b/webrtc/src/track/track_local/track_local_static_rtp/bitrate_state.rs similarity index 100% rename from webrtc/src/track/track_local/bitrate_state.rs rename to webrtc/src/track/track_local/track_local_static_rtp/bitrate_state.rs diff --git a/webrtc/src/track/track_local/loss_stats.rs b/webrtc/src/track/track_local/track_local_static_rtp/loss_stats.rs similarity index 100% rename from webrtc/src/track/track_local/loss_stats.rs rename to webrtc/src/track/track_local/track_local_static_rtp/loss_stats.rs diff --git a/webrtc/src/track/track_local/packet_cache.rs b/webrtc/src/track/track_local/track_local_static_rtp/packet_cache.rs similarity index 100% rename from webrtc/src/track/track_local/packet_cache.rs rename to webrtc/src/track/track_local/track_local_static_rtp/packet_cache.rs diff --git a/webrtc/src/track/track_local/track_local_static_rtp/track_state.rs b/webrtc/src/track/track_local/track_local_static_rtp/track_state.rs new file mode 100644 index 000000000..8e3a666d0 --- /dev/null +++ b/webrtc/src/track/track_local/track_local_static_rtp/track_state.rs @@ -0,0 +1,142 @@ +use crate::rtp_transceiver::rtp_codec::RTPCodecType; + +#[derive(Clone, Debug)] +pub struct TrackState { + last_out_seq: u16, // переживает все переключения источников + last_out_ts: u32, // переживает все переключения источников + started_at_ts: i64, + marker: bool, // marker = true означает, что это последний пакет видеофрейма. + // Это сигнал для джиттер-буфера и декодера, что можно собрать все полученные пакеты этого фрейма и отправить их на декодирование. + // Используется после паузы + pub out_offset: Option<( + u16, /* смещение порядкового номера */ + u32, /* смещение временной метки timestamp */ + )>, +} + +pub struct PkgAttrs { + pub sequence_number: u16, + pub timestamp: u32, + pub marker: bool, +} + +impl TrackState { + pub fn new() -> Self { + TrackState { + // Порядковый номер начинается с 0 + last_out_seq: 0, + // время трека начинается с 0 + last_out_ts: 0, + // Сохраняем начало трека в реальной временной шкале дла последующей синхронизации + started_at_ts: chrono::Utc::now().timestamp(), + out_offset: None, + marker: false, + } + } + + pub fn get_pkg_attrs( + &mut self, + kind: RTPCodecType, + pkt_sequence_number: u16, + pkt_timestamp: u32, + ) -> PkgAttrs { + match self.out_offset { + Some((seq_num_offset, ts_offset)) => PkgAttrs { + sequence_number: pkt_sequence_number.wrapping_add(seq_num_offset), + timestamp: pkt_timestamp.wrapping_add(ts_offset), + marker: self.marker, + }, + None => { + let seq_num_offset = self + .last_out_seq + .wrapping_sub(pkt_sequence_number) + .wrapping_add(1); + let ts_offset = + self.last_out_ts + .wrapping_sub(pkt_timestamp) + .wrapping_add(match kind { + RTPCodecType::Audio => 900, // стандартное значение для звука + RTPCodecType::Video => 3750, // 90000 clock_rate / 24 кадра + _ => 3750, + }); + self.out_offset = Some((seq_num_offset, ts_offset)); + + PkgAttrs { + sequence_number: pkt_sequence_number.wrapping_add(seq_num_offset), + timestamp: pkt_timestamp.wrapping_add(ts_offset), + marker: self.marker, + } + } + } + } + + pub fn set_last_out(&mut self, pkg_attrs: PkgAttrs) { + self.last_out_seq = pkg_attrs.sequence_number; + self.last_out_ts = pkg_attrs.timestamp; + self.marker = false; + } + + pub fn shift_offset(&mut self, pkt_sequence_number: u16, pkt_timestamp: u32) { + self.marker = true; + self.out_offset = Some(( + self.last_out_seq.wrapping_sub(pkt_sequence_number), + self.last_out_ts.wrapping_sub(pkt_timestamp), + )) + } + + pub fn get_pkg_attrs_set_last_out( + &mut self, + kind: RTPCodecType, + pkt_sequence_number: u16, + pkt_timestamp: u32, + ) -> PkgAttrs { + match self.out_offset { + Some((seq_num_offset, ts_offset)) => { + // новый = пришедший + смещение + // старый = новый - (новый - старый) + self.last_out_seq = pkt_sequence_number.wrapping_add(seq_num_offset); + self.last_out_ts = pkt_timestamp.wrapping_add(ts_offset); + PkgAttrs { + sequence_number: self.last_out_seq, + timestamp: self.last_out_ts, + marker: self.marker, + } + } + None => { + let seq_num_offset = self + .last_out_seq + .wrapping_sub(pkt_sequence_number) + .wrapping_add(1); + let ts_offset = + self.last_out_ts + .wrapping_sub(pkt_timestamp) + .wrapping_add(match kind { + RTPCodecType::Audio => 900, // стандартное значение для звука + RTPCodecType::Video => 6000, // 90000 clock_rate / 24 кадра + _ => 6000, + }); + self.out_offset = Some((seq_num_offset, ts_offset)); + + self.last_out_seq = pkt_sequence_number.wrapping_add(seq_num_offset); + self.last_out_ts = pkt_timestamp.wrapping_add(ts_offset); + + // println!( + // "Смещения перезаписаны seq_num: {pkt_sequence_number} -> {}; ts: {pkt_timestamp} -> {}", + // self.last_out_seq, self.last_out_ts + // ); + PkgAttrs { + sequence_number: self.last_out_seq, + timestamp: self.last_out_ts, + marker: self.marker, + } + } + } + } + + pub fn origin_seq(&self, modified_seq: u16) -> u16 { + match self.out_offset { + Some((seq_num_offset, _)) => modified_seq.wrapping_sub(seq_num_offset), + None => modified_seq, + } + } +} From 5068f665e9f31cd5b145ab929b9a327884fd3689 Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Fri, 17 Oct 2025 20:42:36 +0300 Subject: [PATCH 10/15] =?UTF-8?q?=D0=9C=D0=B5=D0=BD=D1=8C=D1=88=D0=B5=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B5=D0=B4=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=D1=85=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D1=85=D0=B2=D0=B0=D1=82=D1=87=D0=B8=D0=BA=D0=BE=D0=B2,=20?= =?UTF-8?q?=D0=BE=D0=BD=D0=B8=20=D0=BB=D0=BE=D0=BC=D0=B0=D1=8E=D1=82=20?= =?UTF-8?q?=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webrtc/src/api/interceptor_registry/mod.rs | 6 +++--- webrtc/src/track/track_local/track_local_static_rtp.rs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/webrtc/src/api/interceptor_registry/mod.rs b/webrtc/src/api/interceptor_registry/mod.rs index ca4f54904..f7030246e 100644 --- a/webrtc/src/api/interceptor_registry/mod.rs +++ b/webrtc/src/api/interceptor_registry/mod.rs @@ -2,7 +2,7 @@ mod interceptor_registry_test; use interceptor::nack::generator::Generator; -use interceptor::nack::responder::Responder; +// use interceptor::nack::responder::Responder; use interceptor::registry::Registry; use interceptor::report::receiver::ReceiverReport; use interceptor::report::sender::SenderReport; @@ -57,8 +57,8 @@ pub fn configure_nack(mut registry: Registry, media_engine: &mut MediaEngine) -> ); let generator = Box::new(Generator::builder()); - let responder = Box::new(Responder::builder()); - registry.add(responder); + // let responder = Box::new(Responder::builder()); + // registry.add(responder); registry.add(generator); registry } diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index c3b62403a..fb942ac5a 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -6,6 +6,7 @@ mod track_state; use bytes::{Bytes, BytesMut}; use dashmap::DashMap; use futures::{stream, StreamExt}; +use log::warn; use rtcp::reception_report::ReceptionReport; use std::any::Any; use std::sync::atomic::{AtomicU32, AtomicU64}; @@ -495,6 +496,7 @@ impl TrackLocalStaticRTP { let real_bitrate = if let Ok(bitrate_guard) = self.bitrate.try_lock() { bitrate_guard.average_bitrate() } else { + warn!("Адаптация битрейта заблокирована BitrateState (self.bitrate)"); return None; }; @@ -510,6 +512,7 @@ impl TrackLocalStaticRTP { let should_reduce = if let Ok(loss_stats_guard) = self.loss_stats.try_lock() { loss_stats_guard.should_reduce() } else { + warn!("Адаптация битрейта заблокирована ReceiverLossStats (self.loss_stats)"); return None; }; From baf60ae46d912161186e19555b91c21e238f5024 Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Sat, 18 Oct 2025 18:52:56 +0300 Subject: [PATCH 11/15] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=81=D1=82=D0=B0=D0=BD=D0=B4=D0=B0=D1=80=D1=82?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D0=B1=D0=B8=D1=82=D1=80=D0=B5=D0=B9=D1=82?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../track_local/track_local_static_rtp.rs | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index fb942ac5a..eb1217e27 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -46,6 +46,8 @@ pub struct TrackLocalStaticRTP { pub loss_stats: Mutex, pub max_bitrate_bps: u32, + pub min_bitrate_bps: u32, + pub bitrate_step_bps: u32, pub current_target_bitrate_bps: AtomicU32, // Добавляем это поле last_remb_sent: AtomicU64, // microseconds since start @@ -61,7 +63,7 @@ const TTL_MILLIS: u64 = 3000; const PLI_INTERVAL_MS: u64 = 500; -const MAX_VIDEO_BITRATE_BPS: u32 = 1_000_000; +const MAX_VIDEO_BITRATE_BPS: u32 = 300_000; const MAX_AUDIO_BITRATE_BPS: u32 = 64_000; const REMB_INTERVAL: Duration = Duration::from_secs(1); @@ -74,10 +76,27 @@ fn get_max_bitrate(mime_type: &str) -> u32 { } } +fn get_min_bitrate(mime_type: &str) -> u32 { + match mime_type { + MIME_TYPE_OPUS => MAX_AUDIO_BITRATE_BPS / 4, + MIME_TYPE_H264 | MIME_TYPE_VP8 | "video/vp8" | MIME_TYPE_VP9 => MAX_VIDEO_BITRATE_BPS / 6, + x => panic!("Неподдерживаемый тип кодека! {}", x), + } +} + +fn get_bitrate_step(mime_type: &str) -> u32 { + match mime_type { + MIME_TYPE_OPUS => MAX_AUDIO_BITRATE_BPS / 20, + MIME_TYPE_H264 | MIME_TYPE_VP8 | "video/vp8" | MIME_TYPE_VP9 => MAX_VIDEO_BITRATE_BPS / 20, + x => panic!("Неподдерживаемый тип кодека! {}", x), + } +} + impl TrackLocalStaticRTP { /// returns a TrackLocalStaticRTP without rid. pub fn new(codec: RTCRtpCodecCapability, id: String, stream_id: String) -> Self { - let max_bitrate_bps = get_max_bitrate(&codec.mime_type); + let mime_type = codec.mime_type.clone(); + let max_bitrate_bps = get_max_bitrate(&mime_type); let start_remb_time = Instant::now(); // Начинаем с "10 секунд назад", чтобы можно было отправить сразу let initial_offset = Duration::from_secs(10).as_micros() as u64; @@ -100,6 +119,8 @@ impl TrackLocalStaticRTP { bitrate: Mutex::new(BitrateState::new()), loss_stats: Mutex::new(ReceiverLossStats::new()), max_bitrate_bps, + min_bitrate_bps: get_min_bitrate(&mime_type), + bitrate_step_bps: get_bitrate_step(&mime_type), current_target_bitrate_bps: AtomicU32::new(max_bitrate_bps / 2), last_remb_sent: AtomicU64::new(initial_offset), @@ -489,9 +510,8 @@ impl TrackLocalStaticRTP { let current_target = self.current_target_bitrate_bps.load(Ordering::Relaxed); // Применяем изменение только если оно существенное (>5% изменения) - let change_threshold = current_target / 20; + let change_threshold = std::cmp::min(current_target / 20, self.bitrate_step_bps); // Выбираем минимально возможный размер битрейта (он же, шаг от изменения) - let min_bitrate = self.max_bitrate_bps / 20; let real_bitrate = if let Ok(bitrate_guard) = self.bitrate.try_lock() { bitrate_guard.average_bitrate() @@ -520,7 +540,7 @@ impl TrackLocalStaticRTP { ReduceIncrease::Reduce(degradate) => { // Уменьшаем на основе реального битрейта, но не ниже минимального порога let new_bitrate = - std::cmp::max(real_bitrate * (100 - degradate) / 100, min_bitrate); + std::cmp::max(real_bitrate * (100 - degradate) / 100, self.min_bitrate_bps); if new_bitrate < current_target && new_bitrate.abs_diff(current_target) > change_threshold @@ -543,7 +563,7 @@ impl TrackLocalStaticRTP { } ReduceIncrease::Increase => { // Увеличиваем осторожно, проверяя что реальный битрейт может поддерживаться - let proposed_increase = real_bitrate + min_bitrate; + let proposed_increase = real_bitrate + self.bitrate_step_bps; // Не увеличиваем больше чем на 50% от реального битрейта за раз // Битрейт не может быть больше максимального From 65eb63624695aeb8482b59de9a880fc1762f3b83 Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Sun, 19 Oct 2025 14:00:53 +0300 Subject: [PATCH 12/15] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB?= =?UTF-8?q?=D0=B5=D0=BC=D1=8B=20peeked=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/nack/generator/generator_stream.rs | 1 + interceptor/src/nack/generator/mod.rs | 3 +- interceptor/src/twcc/mod.rs | 1 + webrtc/src/api/interceptor_registry/mod.rs | 2 +- .../track_local/track_local_static_rtp.rs | 7 +++- webrtc/src/track/track_remote/mod.rs | 39 ++++++++++++------- 6 files changed, 37 insertions(+), 16 deletions(-) diff --git a/interceptor/src/nack/generator/generator_stream.rs b/interceptor/src/nack/generator/generator_stream.rs index ce8930e83..fe555a087 100644 --- a/interceptor/src/nack/generator/generator_stream.rs +++ b/interceptor/src/nack/generator/generator_stream.rs @@ -152,6 +152,7 @@ impl RTPReader for GeneratorStream { /// read a rtp packet async fn read(&self, buf: &mut [u8]) -> Result { let pkt = self.parent_rtp_reader.read(buf).await?; + // log::warn!("NACK remote reader RTP: {:#?}", pkt); self.add(pkt.header.sequence_number); diff --git a/interceptor/src/nack/generator/mod.rs b/interceptor/src/nack/generator/mod.rs index f1942c14e..7daf85ebd 100644 --- a/interceptor/src/nack/generator/mod.rs +++ b/interceptor/src/nack/generator/mod.rs @@ -134,7 +134,8 @@ impl Generator { nacks }; - for nack in nacks{ + for nack in nacks { + log::warn!("NACK сгенерирован и отправлен: {:#?}", nack); if let Err(err) = rtcp_writer.write(&[Box::new(nack)]).await{ log::warn!("failed sending nack: {err}"); } diff --git a/interceptor/src/twcc/mod.rs b/interceptor/src/twcc/mod.rs index ed3fb10dc..a3cb0bdb8 100644 --- a/interceptor/src/twcc/mod.rs +++ b/interceptor/src/twcc/mod.rs @@ -86,6 +86,7 @@ impl Recorder { } self.received_packets.clear(); let p: Box = Box::new(feedback.get_rtcp()); + log::warn!("TWCC: p: {:#?}", p); pkts.push(p); pkts } diff --git a/webrtc/src/api/interceptor_registry/mod.rs b/webrtc/src/api/interceptor_registry/mod.rs index f7030246e..5a2924f8b 100644 --- a/webrtc/src/api/interceptor_registry/mod.rs +++ b/webrtc/src/api/interceptor_registry/mod.rs @@ -25,7 +25,7 @@ pub fn register_default_interceptors( registry = configure_rtcp_reports(registry); - registry = configure_twcc_receiver_only(registry, media_engine)?; + // registry = configure_twcc_receiver_only(registry, media_engine)?; Ok(registry) } diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index eb1217e27..d45e925f1 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -214,13 +214,18 @@ impl TrackLocalStaticRTP { }); // 3. Чтение из remote_track в mpsc канал - while let Ok(rtp) = remote_track.read_rtp_raw().await { + while let Ok(rtp) = remote_track.read_rtp().await { // 1. Сохраняем в кэш оригинальный rtp без смещений! Так быстрее происходит сохранение в кэш // При восстановлении кеша нужно вернуть порядковый номер к оригинальному, чтоб найти его + // if rtp.header.payload_type != 111 { + // log::warn!("RTP: {:#?}", rtp); + // } + let p_cache = Arc::new(PCache { rtp, first_sent_at: Instant::now(), }); + self.rtp_cache.put(Arc::clone(&p_cache)); // 2. Пытаемся отправить, если переполнен буфер, не ждём и позже в ответ на NACK берём из кэша diff --git a/webrtc/src/track/track_remote/mod.rs b/webrtc/src/track/track_remote/mod.rs index d95543896..04a6fa97d 100644 --- a/webrtc/src/track/track_remote/mod.rs +++ b/webrtc/src/track/track_remote/mod.rs @@ -141,7 +141,7 @@ impl TrackRemote { /// payload_type gets the PayloadType of the track pub fn payload_type(&self) -> PayloadType { - self.payload_type.load(Ordering::SeqCst) + self.payload_type.load(Ordering::Relaxed) } pub fn set_payload_type(&self, payload_type: PayloadType) { @@ -219,8 +219,11 @@ impl TrackRemote { // Internal lock scope let mut internal = self.internal.lock().await; if let Some(pkt) = internal.peeked.pop_front() { - self.check_and_update_track(&pkt).await?; - + // Этот код self.check_and_update_track здесь не нужен, + // т.к. проверка происходит только один раз при запуске получателя start_receiver в PeerConnection + // В дальнейшем тип нагрузки не может меняться без пересогласования + // self.check_and_update_track(&pkt).await?; + // log::warn!("track_remote.read 1. PKT: {:#?}", pkt); return Ok(pkt); } }; @@ -231,15 +234,22 @@ impl TrackRemote { }; let pkt = receiver.read_rtp(b, self.tid).await?; - self.check_and_update_track(&pkt).await?; + // Этот код self.check_and_update_track здесь не нужен, + // т.к. проверка происходит только один раз при запуске получателя start_receiver в PeerConnection + // В дальнейшем тип нагрузки не может меняться без пересогласования + // self.check_and_update_track(&pkt).await?; + // log::warn!("track_remote.read 2. PKT: {:#?}", pkt); Ok(pkt) } /// check_and_update_track checks payloadType for every incoming packet /// once a different payloadType is detected the track will be updated + /// Этот код выполняется только один раз, в момент запуска трека. + /// В другое время он не нужен, т.к. payload_type измениться не может pub(crate) async fn check_and_update_track(&self, pkt: &rtp::packet::Packet) -> Result<()> { let payload_type = pkt.header.payload_type; if payload_type != self.payload_type() { + // log::error!("track_remote.check_and_update_track: У трека изменился payload_type!!! {prev_payload_type} -> {payload_type}"); let p = self .media_engine .get_rtp_parameters_by_payload_type(payload_type) @@ -277,15 +287,18 @@ impl TrackRemote { } // Не вызывает лишние блокировки, а всегда читает данные из receiver - pub async fn read_rtp_raw(&self) -> Result { - let receiver = match self.receiver.as_ref().and_then(|r| r.upgrade()) { - Some(r) => r, - None => return Err(Error::ErrRTPReceiverNil), - }; - - let mut b = vec![0u8; self.receive_mtu]; - Ok(receiver.read_rtp(&mut b, self.tid).await?) - } + // Нельзя использовать этот метод, т.к. в момент запуска start_receiver в PeerConnection + // выполняется проверка чтения из трека и мы один пакет сохраняем в peeked + // pub async fn read_rtp_raw(&self) -> Result { + // let receiver = match self.receiver.as_ref().and_then(|r| r.upgrade()) { + // Some(r) => r, + // None => return Err(Error::ErrRTPReceiverNil), + // }; + + // let mut b = vec![0u8; self.receive_mtu]; + // let pkt = receiver.read_rtp(&mut b, self.tid).await?; + // Ok(pkt) + // } /// peek is like Read, but it doesn't discard the packet read pub(crate) async fn peek(&self, b: &mut [u8]) -> Result { From 35f86c295e2c29ed0c462c65af22b5c539600e71 Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Sat, 25 Oct 2025 18:27:25 +0300 Subject: [PATCH 13/15] =?UTF-8?q?=D0=97=D0=B0=D0=BF=D1=80=D0=B5=D1=82=20?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D1=82=D0=B0=20=D0=B1=D0=B8=D1=82=D1=80=D0=B5?= =?UTF-8?q?=D0=B9=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- interceptor/src/nack/generator/mod.rs | 1 + .../peer_connection/peer_connection_internal.rs | 2 ++ .../track/track_local/track_local_static_rtp.rs | 16 ++++++++-------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/interceptor/src/nack/generator/mod.rs b/interceptor/src/nack/generator/mod.rs index 7daf85ebd..d18c00e3d 100644 --- a/interceptor/src/nack/generator/mod.rs +++ b/interceptor/src/nack/generator/mod.rs @@ -112,6 +112,7 @@ impl Generator { .take() .ok_or(Error::ErrInvalidCloseRx)?; + // TODO: взять реальный sender_ssrc, о котором уже знает отправитель данных let sender_ssrc = rand::random::(); loop { tokio::select! { diff --git a/webrtc/src/peer_connection/peer_connection_internal.rs b/webrtc/src/peer_connection/peer_connection_internal.rs index 3a3860e4e..3c6d11b57 100644 --- a/webrtc/src/peer_connection/peer_connection_internal.rs +++ b/webrtc/src/peer_connection/peer_connection_internal.rs @@ -14,6 +14,7 @@ use crate::track::track_local::track_local_static_sample::TrackLocalStaticSample use crate::track::TrackStream; use crate::SDP_ATTRIBUTE_RID; use arc_swap::ArcSwapOption; +use log::warn; use portable_atomic::AtomicIsize; use smol_str::SmolStr; use tokio::time::Instant; @@ -1150,6 +1151,7 @@ impl PeerConnectionInternal { payload_type: PayloadType, ) -> Result<()> { let ssrc = rtp_stream.get_ssrc(); + warn!("Обработка входящего rtp stream ssrc={}", ssrc); let parsed = match self.remote_description().await.and_then(|rd| rd.parsed) { Some(r) => r, None => return Err(Error::ErrPeerConnRemoteDescriptionNil), diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index d45e925f1..7cda72d18 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -515,7 +515,7 @@ impl TrackLocalStaticRTP { let current_target = self.current_target_bitrate_bps.load(Ordering::Relaxed); // Применяем изменение только если оно существенное (>5% изменения) - let change_threshold = std::cmp::min(current_target / 20, self.bitrate_step_bps); + let change_min_step = std::cmp::min(current_target / 20, self.bitrate_step_bps); // Выбираем минимально возможный размер битрейта (он же, шаг от изменения) let real_bitrate = if let Ok(bitrate_guard) = self.bitrate.try_lock() { @@ -547,9 +547,8 @@ impl TrackLocalStaticRTP { let new_bitrate = std::cmp::max(real_bitrate * (100 - degradate) / 100, self.min_bitrate_bps); - if new_bitrate < current_target - && new_bitrate.abs_diff(current_target) > change_threshold - { + // Если битрейт уменьшился на величину не меньше, чем минимальный шаг + if (current_target - new_bitrate) > change_min_step { log::warn!( "\nУменьшение битрейта: target={}bps -> {}bps, real={}bps", current_target, @@ -574,11 +573,12 @@ impl TrackLocalStaticRTP { // Битрейт не может быть больше максимального let max_safe_increase = real_bitrate + (real_bitrate / 2); let new_bitrate = std::cmp::min(proposed_increase, max_safe_increase); - let new_bitrate = std::cmp::min(new_bitrate, self.max_bitrate_bps); + // Запретить рост битрейта больше половины от максимум по механизму потерянных пакетов + // Ещё больше он может расти ТОЛЬКО через механизм TWCC!!! + let new_bitrate = std::cmp::min(new_bitrate, self.max_bitrate_bps / 2); - if new_bitrate > current_target - && new_bitrate.abs_diff(current_target) > change_threshold - { + // Если битрейт вырос на более чем минимальный шаг, то увеличить его + if (new_bitrate - current_target) > change_min_step { log::warn!( "\nУвеличение битрейта: target={}bps -> {}bps, real={}bps", current_target, From de575bc9a39986e3103331076b330be50dd91c3c Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:55:57 +0300 Subject: [PATCH 14/15] track_local_static_rtp --- .../track/track_local/track_local_static_rtp.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index 7cda72d18..bafc62187 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -548,12 +548,15 @@ impl TrackLocalStaticRTP { std::cmp::max(real_bitrate * (100 - degradate) / 100, self.min_bitrate_bps); // Если битрейт уменьшился на величину не меньше, чем минимальный шаг - if (current_target - new_bitrate) > change_min_step { + let change_step = current_target - new_bitrate; + if current_target > new_bitrate && change_step > change_min_step { log::warn!( - "\nУменьшение битрейта: target={}bps -> {}bps, real={}bps", + "\nУменьшение битрейта: {}bps -> {}bps, real={}bps; step={}; min_step={}", current_target, new_bitrate, real_bitrate, + change_step, + change_min_step ); // Сохраняем последнее время отправки self.last_remb_sent @@ -578,12 +581,15 @@ impl TrackLocalStaticRTP { let new_bitrate = std::cmp::min(new_bitrate, self.max_bitrate_bps / 2); // Если битрейт вырос на более чем минимальный шаг, то увеличить его - if (new_bitrate - current_target) > change_min_step { + let change_step = new_bitrate - current_target; + if new_bitrate > current_target && change_step > change_min_step { log::warn!( - "\nУвеличение битрейта: target={}bps -> {}bps, real={}bps", + "\nУвеличение битрейта: {}bps -> {}bps, real={}bps; step={}; min_step={}", current_target, new_bitrate, real_bitrate, + change_step, + change_min_step ); // Сохраняем последнее время отправки self.last_remb_sent From 4c8273a67365b7903839b56136b3e85b021d1ff9 Mon Sep 17 00:00:00 2001 From: Pavel Belik <10917432+Razzwan@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:25:42 +0300 Subject: [PATCH 15/15] Comments --- interceptor/src/nack/generator/mod.rs | 8 ++++--- .../track_local/track_local_static_rtp.rs | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/interceptor/src/nack/generator/mod.rs b/interceptor/src/nack/generator/mod.rs index d18c00e3d..8c86e8e8a 100644 --- a/interceptor/src/nack/generator/mod.rs +++ b/interceptor/src/nack/generator/mod.rs @@ -113,7 +113,7 @@ impl Generator { .ok_or(Error::ErrInvalidCloseRx)?; // TODO: взять реальный sender_ssrc, о котором уже знает отправитель данных - let sender_ssrc = rand::random::(); + // let sender_ssrc = rand::random::(); loop { tokio::select! { _ = ticker.tick() =>{ @@ -127,7 +127,9 @@ impl Generator { } nacks.push(TransportLayerNack{ - sender_ssrc, + // Браузер Chrome генерирует отчёты с sender_ssrc: 1, + // Поэтому, предположительно, такой же sender_ssrc лучше устанавливать и нам + sender_ssrc: 1, media_ssrc: *ssrc, nacks: nack_pairs_from_sequence_numbers(&missing), }); @@ -136,7 +138,7 @@ impl Generator { }; for nack in nacks { - log::warn!("NACK сгенерирован и отправлен: {:#?}", nack); + log::warn!("NACK сгенерирован и отправлен generator interceptor: {:#?}", nack); if let Err(err) = rtcp_writer.write(&[Box::new(nack)]).await{ log::warn!("failed sending nack: {err}"); } diff --git a/webrtc/src/track/track_local/track_local_static_rtp.rs b/webrtc/src/track/track_local/track_local_static_rtp.rs index bafc62187..039762148 100644 --- a/webrtc/src/track/track_local/track_local_static_rtp.rs +++ b/webrtc/src/track/track_local/track_local_static_rtp.rs @@ -7,6 +7,7 @@ use bytes::{Bytes, BytesMut}; use dashmap::DashMap; use futures::{stream, StreamExt}; use log::warn; +use rtcp::payload_feedbacks::receiver_estimated_maximum_bitrate::ReceiverEstimatedMaximumBitrate; use rtcp::reception_report::ReceptionReport; use std::any::Any; use std::sync::atomic::{AtomicU32, AtomicU64}; @@ -604,6 +605,29 @@ impl TrackLocalStaticRTP { ReduceIncrease::NoChange => None, } } + + pub fn adapt_bitrate_for_receiver_remb( + &self, + remb: &ReceiverEstimatedMaximumBitrate, + ) -> Option { + if !self.should_send_remb() { + return None; + } + + let current_target = self.current_target_bitrate_bps.load(Ordering::Relaxed); + let requested_bitrate: u32 = remb.bitrate.round() as u32; + + if current_target > requested_bitrate { + self.last_remb_sent + .store(self.instant_to_micros(Instant::now()), Ordering::SeqCst); + self.current_target_bitrate_bps + .store(requested_bitrate, Ordering::SeqCst); + + Some(requested_bitrate) + } else { + None + } + } } #[async_trait]