├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── examples └── h264-browser │ ├── README.md │ ├── interactive-example │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs │ └── www │ ├── index.html │ ├── main.js │ └── style.css ├── src ├── lib.rs └── webrtcredux │ ├── imp.rs │ ├── mod.rs │ ├── sdp.rs │ └── sender │ ├── imp.rs │ └── mod.rs └── tests └── webrtcredux.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Dependencies 16 | run: sudo apt-get update && sudo apt-get install -y libunwind-dev && sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-libav gstreamer1.0-x gstreamer1.0-plugins-ugly gstreamer1.0-plugins-good 17 | - name: Build 18 | run: cargo build --verbose 19 | - name: Run tests 20 | run: cargo test --verbose 21 | - uses: actions/upload-artifact@v3 22 | with: 23 | path: "./target/debug/tests/*" 24 | 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /.idea 4 | examples/**/target/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gst-plugin-webrtcredux" 3 | version = "0.5.0" 4 | edition = "2021" 5 | repository = "https://github.com/ImTheSquid/gst-webrtcredux" 6 | homepage = "https://github.com/ImTheSquid/gst-webrtcredux" 7 | license = "MIT OR Apache-2.0" 8 | description = "A revived version of GStreamer's webrtcbin plugin built for modern protocols" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | # gst = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer", features = ["v1_16"] } 14 | # gst-app = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-app", features = ["v1_16"] } 15 | # gst-base = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-base", features = ["v1_16"] } 16 | # gst-video = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-video", features = ["v1_16"] } 17 | # gst-audio = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-audio", features = ["v1_16"] } 18 | gst = { package = "gstreamer", version = "0.19.3", features = ["v1_16"] } 19 | gst-app = { package = "gstreamer-app", version = "0.19.2", features = ["v1_16"] } 20 | gst-base = { package = "gstreamer-base", version = "0.19.3", features = ["v1_16"] } 21 | gst-video = { package = "gstreamer-video", version = "0.19.3", features = ["v1_16"] } 22 | gst-audio = { package = "gstreamer-audio", version = "0.19.3", features = ["v1_16"] } 23 | once_cell = "1.16" 24 | strum = "0.24" 25 | strum_macros = "0.24" 26 | futures = "0.3.25" 27 | tokio = { version = "1.22.0", default-features = false, features = ["time", "rt-multi-thread"] } 28 | webrtc = "0.6.0" 29 | interceptor = "0.8.2" 30 | bytes = "1.3.0" 31 | anyhow = "1.0.66" 32 | 33 | [lib] 34 | name = "webrtcredux" 35 | crate-type = ["cdylib", "rlib"] 36 | path = "src/lib.rs" 37 | 38 | [build-dependencies] 39 | gst-plugin-version-helper = "0.7.3" 40 | 41 | [dev-dependencies] 42 | indoc = "1.0.6" 43 | enum_dispatch = "0.3.8" 44 | 45 | [dependencies.xcb] 46 | version = "1" 47 | 48 | [dependencies.webrtc-srtp] 49 | version = "0.9.0" -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Jack Hogan 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jack Hogan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebRTC Redux 2 | A revived version of GStreamer's webrtcbin, built with modern standards in mind. 3 | 4 | This plugin provides a Rust API for Rust implementations. Support for C is planned. 5 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | gst_plugin_version_helper::info() 3 | } 4 | -------------------------------------------------------------------------------- /examples/h264-browser/README.md: -------------------------------------------------------------------------------- 1 | # WebRTC Redux - Interactive example 2 | This example consist in a videotestsrc encoded with x264enc streamed to a browser through webrtc. 3 | 4 | ## Instructions 5 | ### Run this example 6 | ``` 7 | cargo run 8 | ``` 9 | ### Open www/index.html in your browser 10 | ### Copy the base64 encoded string from the first box and press enter 11 | ### Wait for ```Base64 Session Description for the browser copied to the cliboard``` to appear on your console 12 | ### Paste your clipboard content in the second box 13 | ### Click on the "Start Session" button -------------------------------------------------------------------------------- /examples/h264-browser/interactive-example/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aead" 7 | version = "0.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" 10 | dependencies = [ 11 | "generic-array", 12 | ] 13 | 14 | [[package]] 15 | name = "aead" 16 | version = "0.4.3" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" 19 | dependencies = [ 20 | "generic-array", 21 | "rand_core 0.6.4", 22 | ] 23 | 24 | [[package]] 25 | name = "aes" 26 | version = "0.6.0" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" 29 | dependencies = [ 30 | "aes-soft", 31 | "aesni", 32 | "cipher 0.2.5", 33 | ] 34 | 35 | [[package]] 36 | name = "aes" 37 | version = "0.7.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" 40 | dependencies = [ 41 | "cfg-if", 42 | "cipher 0.3.0", 43 | "cpufeatures", 44 | "opaque-debug", 45 | ] 46 | 47 | [[package]] 48 | name = "aes-gcm" 49 | version = "0.8.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" 52 | dependencies = [ 53 | "aead 0.3.2", 54 | "aes 0.6.0", 55 | "cipher 0.2.5", 56 | "ctr 0.6.0", 57 | "ghash 0.3.1", 58 | "subtle", 59 | ] 60 | 61 | [[package]] 62 | name = "aes-gcm" 63 | version = "0.9.4" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" 66 | dependencies = [ 67 | "aead 0.4.3", 68 | "aes 0.7.5", 69 | "cipher 0.3.0", 70 | "ctr 0.8.0", 71 | "ghash 0.4.4", 72 | "subtle", 73 | ] 74 | 75 | [[package]] 76 | name = "aes-soft" 77 | version = "0.6.4" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" 80 | dependencies = [ 81 | "cipher 0.2.5", 82 | "opaque-debug", 83 | ] 84 | 85 | [[package]] 86 | name = "aesni" 87 | version = "0.10.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" 90 | dependencies = [ 91 | "cipher 0.2.5", 92 | "opaque-debug", 93 | ] 94 | 95 | [[package]] 96 | name = "aho-corasick" 97 | version = "0.7.20" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 100 | dependencies = [ 101 | "memchr", 102 | ] 103 | 104 | [[package]] 105 | name = "android_system_properties" 106 | version = "0.1.5" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 109 | dependencies = [ 110 | "libc", 111 | ] 112 | 113 | [[package]] 114 | name = "anyhow" 115 | version = "1.0.66" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" 118 | 119 | [[package]] 120 | name = "arc-swap" 121 | version = "1.5.1" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164" 124 | 125 | [[package]] 126 | name = "asn1-rs" 127 | version = "0.3.1" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "30ff05a702273012438132f449575dbc804e27b2f3cbe3069aa237d26c98fa33" 130 | dependencies = [ 131 | "asn1-rs-derive 0.1.0", 132 | "asn1-rs-impl", 133 | "displaydoc", 134 | "nom", 135 | "num-traits", 136 | "rusticata-macros", 137 | "thiserror", 138 | "time", 139 | ] 140 | 141 | [[package]] 142 | name = "asn1-rs" 143 | version = "0.5.1" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "cf6690c370453db30743b373a60ba498fc0d6d83b11f4abfd87a84a075db5dd4" 146 | dependencies = [ 147 | "asn1-rs-derive 0.4.0", 148 | "asn1-rs-impl", 149 | "displaydoc", 150 | "nom", 151 | "num-traits", 152 | "rusticata-macros", 153 | "thiserror", 154 | ] 155 | 156 | [[package]] 157 | name = "asn1-rs-derive" 158 | version = "0.1.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf" 161 | dependencies = [ 162 | "proc-macro2", 163 | "quote", 164 | "syn", 165 | "synstructure", 166 | ] 167 | 168 | [[package]] 169 | name = "asn1-rs-derive" 170 | version = "0.4.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" 173 | dependencies = [ 174 | "proc-macro2", 175 | "quote", 176 | "syn", 177 | "synstructure", 178 | ] 179 | 180 | [[package]] 181 | name = "asn1-rs-impl" 182 | version = "0.1.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" 185 | dependencies = [ 186 | "proc-macro2", 187 | "quote", 188 | "syn", 189 | ] 190 | 191 | [[package]] 192 | name = "async-trait" 193 | version = "0.1.59" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" 196 | dependencies = [ 197 | "proc-macro2", 198 | "quote", 199 | "syn", 200 | ] 201 | 202 | [[package]] 203 | name = "atomic-waker" 204 | version = "1.0.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" 207 | 208 | [[package]] 209 | name = "atomic_refcell" 210 | version = "0.1.8" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d" 213 | 214 | [[package]] 215 | name = "autocfg" 216 | version = "1.1.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 219 | 220 | [[package]] 221 | name = "base16ct" 222 | version = "0.1.1" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" 225 | 226 | [[package]] 227 | name = "base64" 228 | version = "0.13.1" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 231 | 232 | [[package]] 233 | name = "base64ct" 234 | version = "1.5.3" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" 237 | 238 | [[package]] 239 | name = "bincode" 240 | version = "1.3.3" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 243 | dependencies = [ 244 | "serde", 245 | ] 246 | 247 | [[package]] 248 | name = "bitflags" 249 | version = "1.3.2" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 252 | 253 | [[package]] 254 | name = "block" 255 | version = "0.1.6" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 258 | 259 | [[package]] 260 | name = "block-buffer" 261 | version = "0.9.0" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 264 | dependencies = [ 265 | "generic-array", 266 | ] 267 | 268 | [[package]] 269 | name = "block-buffer" 270 | version = "0.10.3" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 273 | dependencies = [ 274 | "generic-array", 275 | ] 276 | 277 | [[package]] 278 | name = "block-modes" 279 | version = "0.7.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" 282 | dependencies = [ 283 | "block-padding", 284 | "cipher 0.2.5", 285 | ] 286 | 287 | [[package]] 288 | name = "block-padding" 289 | version = "0.2.1" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" 292 | 293 | [[package]] 294 | name = "bumpalo" 295 | version = "3.11.1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" 298 | 299 | [[package]] 300 | name = "byteorder" 301 | version = "1.4.3" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 304 | 305 | [[package]] 306 | name = "bytes" 307 | version = "1.3.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" 310 | 311 | [[package]] 312 | name = "cc" 313 | version = "1.0.77" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" 316 | 317 | [[package]] 318 | name = "ccm" 319 | version = "0.3.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "5aca1a8fbc20b50ac9673ff014abfb2b5f4085ee1a850d408f14a159c5853ac7" 322 | dependencies = [ 323 | "aead 0.3.2", 324 | "cipher 0.2.5", 325 | "subtle", 326 | ] 327 | 328 | [[package]] 329 | name = "cfg-expr" 330 | version = "0.11.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" 333 | dependencies = [ 334 | "smallvec", 335 | ] 336 | 337 | [[package]] 338 | name = "cfg-if" 339 | version = "1.0.0" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 342 | 343 | [[package]] 344 | name = "chrono" 345 | version = "0.4.23" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" 348 | dependencies = [ 349 | "iana-time-zone", 350 | "num-integer", 351 | "num-traits", 352 | "winapi", 353 | ] 354 | 355 | [[package]] 356 | name = "cipher" 357 | version = "0.2.5" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" 360 | dependencies = [ 361 | "generic-array", 362 | ] 363 | 364 | [[package]] 365 | name = "cipher" 366 | version = "0.3.0" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 369 | dependencies = [ 370 | "generic-array", 371 | ] 372 | 373 | [[package]] 374 | name = "clipboard" 375 | version = "0.5.0" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7" 378 | dependencies = [ 379 | "clipboard-win", 380 | "objc", 381 | "objc-foundation", 382 | "objc_id", 383 | "x11-clipboard", 384 | ] 385 | 386 | [[package]] 387 | name = "clipboard-win" 388 | version = "2.2.0" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b" 391 | dependencies = [ 392 | "winapi", 393 | ] 394 | 395 | [[package]] 396 | name = "codespan-reporting" 397 | version = "0.11.1" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 400 | dependencies = [ 401 | "termcolor", 402 | "unicode-width", 403 | ] 404 | 405 | [[package]] 406 | name = "const-oid" 407 | version = "0.9.1" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" 410 | 411 | [[package]] 412 | name = "core-foundation-sys" 413 | version = "0.8.3" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 416 | 417 | [[package]] 418 | name = "cpufeatures" 419 | version = "0.2.5" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 422 | dependencies = [ 423 | "libc", 424 | ] 425 | 426 | [[package]] 427 | name = "cpuid-bool" 428 | version = "0.2.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" 431 | 432 | [[package]] 433 | name = "crc" 434 | version = "3.0.0" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" 437 | dependencies = [ 438 | "crc-catalog", 439 | ] 440 | 441 | [[package]] 442 | name = "crc-catalog" 443 | version = "2.1.0" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" 446 | 447 | [[package]] 448 | name = "crypto-bigint" 449 | version = "0.4.9" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" 452 | dependencies = [ 453 | "generic-array", 454 | "rand_core 0.6.4", 455 | "subtle", 456 | "zeroize", 457 | ] 458 | 459 | [[package]] 460 | name = "crypto-common" 461 | version = "0.1.6" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 464 | dependencies = [ 465 | "generic-array", 466 | "typenum", 467 | ] 468 | 469 | [[package]] 470 | name = "crypto-mac" 471 | version = "0.10.1" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" 474 | dependencies = [ 475 | "generic-array", 476 | "subtle", 477 | ] 478 | 479 | [[package]] 480 | name = "crypto-mac" 481 | version = "0.11.1" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" 484 | dependencies = [ 485 | "generic-array", 486 | "subtle", 487 | ] 488 | 489 | [[package]] 490 | name = "ctr" 491 | version = "0.6.0" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" 494 | dependencies = [ 495 | "cipher 0.2.5", 496 | ] 497 | 498 | [[package]] 499 | name = "ctr" 500 | version = "0.8.0" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" 503 | dependencies = [ 504 | "cipher 0.3.0", 505 | ] 506 | 507 | [[package]] 508 | name = "curve25519-dalek" 509 | version = "3.2.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" 512 | dependencies = [ 513 | "byteorder", 514 | "digest 0.9.0", 515 | "rand_core 0.5.1", 516 | "subtle", 517 | "zeroize", 518 | ] 519 | 520 | [[package]] 521 | name = "cxx" 522 | version = "1.0.83" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" 525 | dependencies = [ 526 | "cc", 527 | "cxxbridge-flags", 528 | "cxxbridge-macro", 529 | "link-cplusplus", 530 | ] 531 | 532 | [[package]] 533 | name = "cxx-build" 534 | version = "1.0.83" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" 537 | dependencies = [ 538 | "cc", 539 | "codespan-reporting", 540 | "once_cell", 541 | "proc-macro2", 542 | "quote", 543 | "scratch", 544 | "syn", 545 | ] 546 | 547 | [[package]] 548 | name = "cxxbridge-flags" 549 | version = "1.0.83" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" 552 | 553 | [[package]] 554 | name = "cxxbridge-macro" 555 | version = "1.0.83" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" 558 | dependencies = [ 559 | "proc-macro2", 560 | "quote", 561 | "syn", 562 | ] 563 | 564 | [[package]] 565 | name = "darling" 566 | version = "0.14.2" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" 569 | dependencies = [ 570 | "darling_core", 571 | "darling_macro", 572 | ] 573 | 574 | [[package]] 575 | name = "darling_core" 576 | version = "0.14.2" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" 579 | dependencies = [ 580 | "fnv", 581 | "ident_case", 582 | "proc-macro2", 583 | "quote", 584 | "strsim", 585 | "syn", 586 | ] 587 | 588 | [[package]] 589 | name = "darling_macro" 590 | version = "0.14.2" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" 593 | dependencies = [ 594 | "darling_core", 595 | "quote", 596 | "syn", 597 | ] 598 | 599 | [[package]] 600 | name = "data-encoding" 601 | version = "2.3.2" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" 604 | 605 | [[package]] 606 | name = "der" 607 | version = "0.6.0" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" 610 | dependencies = [ 611 | "const-oid", 612 | "pem-rfc7468", 613 | "zeroize", 614 | ] 615 | 616 | [[package]] 617 | name = "der-parser" 618 | version = "7.0.0" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "fe398ac75057914d7d07307bf67dc7f3f574a26783b4fc7805a20ffa9f506e82" 621 | dependencies = [ 622 | "asn1-rs 0.3.1", 623 | "displaydoc", 624 | "nom", 625 | "num-bigint", 626 | "num-traits", 627 | "rusticata-macros", 628 | ] 629 | 630 | [[package]] 631 | name = "der-parser" 632 | version = "8.1.0" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "42d4bc9b0db0a0df9ae64634ac5bdefb7afcb534e182275ca0beadbe486701c1" 635 | dependencies = [ 636 | "asn1-rs 0.5.1", 637 | "displaydoc", 638 | "nom", 639 | "num-traits", 640 | "rusticata-macros", 641 | ] 642 | 643 | [[package]] 644 | name = "derive_builder" 645 | version = "0.11.2" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3" 648 | dependencies = [ 649 | "derive_builder_macro", 650 | ] 651 | 652 | [[package]] 653 | name = "derive_builder_core" 654 | version = "0.11.2" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" 657 | dependencies = [ 658 | "darling", 659 | "proc-macro2", 660 | "quote", 661 | "syn", 662 | ] 663 | 664 | [[package]] 665 | name = "derive_builder_macro" 666 | version = "0.11.2" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" 669 | dependencies = [ 670 | "derive_builder_core", 671 | "syn", 672 | ] 673 | 674 | [[package]] 675 | name = "digest" 676 | version = "0.9.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 679 | dependencies = [ 680 | "generic-array", 681 | ] 682 | 683 | [[package]] 684 | name = "digest" 685 | version = "0.10.6" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 688 | dependencies = [ 689 | "block-buffer 0.10.3", 690 | "crypto-common", 691 | "subtle", 692 | ] 693 | 694 | [[package]] 695 | name = "displaydoc" 696 | version = "0.2.3" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" 699 | dependencies = [ 700 | "proc-macro2", 701 | "quote", 702 | "syn", 703 | ] 704 | 705 | [[package]] 706 | name = "ecdsa" 707 | version = "0.14.8" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" 710 | dependencies = [ 711 | "der", 712 | "elliptic-curve", 713 | "rfc6979", 714 | "signature", 715 | ] 716 | 717 | [[package]] 718 | name = "elliptic-curve" 719 | version = "0.12.3" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" 722 | dependencies = [ 723 | "base16ct", 724 | "crypto-bigint", 725 | "der", 726 | "digest 0.10.6", 727 | "ff", 728 | "generic-array", 729 | "group", 730 | "hkdf", 731 | "pem-rfc7468", 732 | "pkcs8", 733 | "rand_core 0.6.4", 734 | "sec1", 735 | "subtle", 736 | "zeroize", 737 | ] 738 | 739 | [[package]] 740 | name = "ff" 741 | version = "0.12.1" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" 744 | dependencies = [ 745 | "rand_core 0.6.4", 746 | "subtle", 747 | ] 748 | 749 | [[package]] 750 | name = "fnv" 751 | version = "1.0.7" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 754 | 755 | [[package]] 756 | name = "form_urlencoded" 757 | version = "1.1.0" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 760 | dependencies = [ 761 | "percent-encoding", 762 | ] 763 | 764 | [[package]] 765 | name = "futures" 766 | version = "0.3.25" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" 769 | dependencies = [ 770 | "futures-channel", 771 | "futures-core", 772 | "futures-executor", 773 | "futures-io", 774 | "futures-sink", 775 | "futures-task", 776 | "futures-util", 777 | ] 778 | 779 | [[package]] 780 | name = "futures-channel" 781 | version = "0.3.25" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" 784 | dependencies = [ 785 | "futures-core", 786 | "futures-sink", 787 | ] 788 | 789 | [[package]] 790 | name = "futures-core" 791 | version = "0.3.25" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" 794 | 795 | [[package]] 796 | name = "futures-executor" 797 | version = "0.3.25" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" 800 | dependencies = [ 801 | "futures-core", 802 | "futures-task", 803 | "futures-util", 804 | ] 805 | 806 | [[package]] 807 | name = "futures-io" 808 | version = "0.3.25" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" 811 | 812 | [[package]] 813 | name = "futures-macro" 814 | version = "0.3.25" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" 817 | dependencies = [ 818 | "proc-macro2", 819 | "quote", 820 | "syn", 821 | ] 822 | 823 | [[package]] 824 | name = "futures-sink" 825 | version = "0.3.25" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" 828 | 829 | [[package]] 830 | name = "futures-task" 831 | version = "0.3.25" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" 834 | 835 | [[package]] 836 | name = "futures-util" 837 | version = "0.3.25" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" 840 | dependencies = [ 841 | "futures-channel", 842 | "futures-core", 843 | "futures-io", 844 | "futures-macro", 845 | "futures-sink", 846 | "futures-task", 847 | "memchr", 848 | "pin-project-lite", 849 | "pin-utils", 850 | "slab", 851 | ] 852 | 853 | [[package]] 854 | name = "generic-array" 855 | version = "0.14.6" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 858 | dependencies = [ 859 | "typenum", 860 | "version_check", 861 | ] 862 | 863 | [[package]] 864 | name = "getrandom" 865 | version = "0.1.16" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 868 | dependencies = [ 869 | "cfg-if", 870 | "libc", 871 | "wasi 0.9.0+wasi-snapshot-preview1", 872 | ] 873 | 874 | [[package]] 875 | name = "getrandom" 876 | version = "0.2.8" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 879 | dependencies = [ 880 | "cfg-if", 881 | "libc", 882 | "wasi 0.11.0+wasi-snapshot-preview1", 883 | ] 884 | 885 | [[package]] 886 | name = "ghash" 887 | version = "0.3.1" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375" 890 | dependencies = [ 891 | "opaque-debug", 892 | "polyval 0.4.5", 893 | ] 894 | 895 | [[package]] 896 | name = "ghash" 897 | version = "0.4.4" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" 900 | dependencies = [ 901 | "opaque-debug", 902 | "polyval 0.5.3", 903 | ] 904 | 905 | [[package]] 906 | name = "gio-sys" 907 | version = "0.16.3" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" 910 | dependencies = [ 911 | "glib-sys", 912 | "gobject-sys", 913 | "libc", 914 | "system-deps", 915 | "winapi", 916 | ] 917 | 918 | [[package]] 919 | name = "glib" 920 | version = "0.16.4" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "d5204a4217749b385cefbfb7bf3a2fcde83e4ce6d0945f64440a1f5bd4010305" 923 | dependencies = [ 924 | "bitflags", 925 | "futures-channel", 926 | "futures-core", 927 | "futures-executor", 928 | "futures-task", 929 | "futures-util", 930 | "gio-sys", 931 | "glib-macros", 932 | "glib-sys", 933 | "gobject-sys", 934 | "libc", 935 | "once_cell", 936 | "smallvec", 937 | "thiserror", 938 | ] 939 | 940 | [[package]] 941 | name = "glib-macros" 942 | version = "0.16.3" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf" 945 | dependencies = [ 946 | "anyhow", 947 | "heck", 948 | "proc-macro-crate", 949 | "proc-macro-error", 950 | "proc-macro2", 951 | "quote", 952 | "syn", 953 | ] 954 | 955 | [[package]] 956 | name = "glib-sys" 957 | version = "0.16.3" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" 960 | dependencies = [ 961 | "libc", 962 | "system-deps", 963 | ] 964 | 965 | [[package]] 966 | name = "gobject-sys" 967 | version = "0.16.3" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" 970 | dependencies = [ 971 | "glib-sys", 972 | "libc", 973 | "system-deps", 974 | ] 975 | 976 | [[package]] 977 | name = "group" 978 | version = "0.12.1" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" 981 | dependencies = [ 982 | "ff", 983 | "rand_core 0.6.4", 984 | "subtle", 985 | ] 986 | 987 | [[package]] 988 | name = "gst-plugin-version-helper" 989 | version = "0.7.5" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "87921209945e5dc809848a100115fad65bd127671896f0206f45e272080cc4c9" 992 | dependencies = [ 993 | "chrono", 994 | ] 995 | 996 | [[package]] 997 | name = "gst-plugin-webrtcredux" 998 | version = "0.5.0" 999 | dependencies = [ 1000 | "anyhow", 1001 | "bytes", 1002 | "futures", 1003 | "gst-plugin-version-helper", 1004 | "gstreamer", 1005 | "gstreamer-app", 1006 | "gstreamer-audio", 1007 | "gstreamer-base", 1008 | "gstreamer-video", 1009 | "interceptor", 1010 | "once_cell", 1011 | "strum", 1012 | "strum_macros", 1013 | "tokio", 1014 | "webrtc", 1015 | "webrtc-srtp", 1016 | "xcb 1.2.0", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "gstreamer" 1021 | version = "0.19.3" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "e52247a9e1c5a2edb082fac77a59f8b40ec4c4eb719ccafe37072daabf4b56a9" 1024 | dependencies = [ 1025 | "bitflags", 1026 | "cfg-if", 1027 | "futures-channel", 1028 | "futures-core", 1029 | "futures-util", 1030 | "glib", 1031 | "gstreamer-sys", 1032 | "libc", 1033 | "muldiv", 1034 | "num-integer", 1035 | "num-rational", 1036 | "once_cell", 1037 | "option-operations", 1038 | "paste", 1039 | "pretty-hex", 1040 | "thiserror", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "gstreamer-app" 1045 | version = "0.19.2" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "c45202b4d565034d4fe5577c990d3a99eaf0c2bfd2cb3f73f70db14d58e0208c" 1048 | dependencies = [ 1049 | "bitflags", 1050 | "futures-core", 1051 | "futures-sink", 1052 | "glib", 1053 | "gstreamer", 1054 | "gstreamer-app-sys", 1055 | "gstreamer-base", 1056 | "libc", 1057 | "once_cell", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "gstreamer-app-sys" 1062 | version = "0.19.2" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "29b0159da8dd0672c1a5507445c70c8dc483abfb63a0295cabaedd396f1d67d1" 1065 | dependencies = [ 1066 | "glib-sys", 1067 | "gstreamer-base-sys", 1068 | "gstreamer-sys", 1069 | "libc", 1070 | "system-deps", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "gstreamer-audio" 1075 | version = "0.19.3" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "3cbc4c6212da52d86ebc3ae476be8037df143d5531dbe4307b0e9fd8972972e0" 1078 | dependencies = [ 1079 | "bitflags", 1080 | "cfg-if", 1081 | "glib", 1082 | "gstreamer", 1083 | "gstreamer-audio-sys", 1084 | "gstreamer-base", 1085 | "libc", 1086 | "once_cell", 1087 | ] 1088 | 1089 | [[package]] 1090 | name = "gstreamer-audio-sys" 1091 | version = "0.19.2" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "710a192fe979467ab12ad5dcee6f2235f8ce0d5a232b10880a6c4f73de1caea9" 1094 | dependencies = [ 1095 | "glib-sys", 1096 | "gobject-sys", 1097 | "gstreamer-base-sys", 1098 | "gstreamer-sys", 1099 | "libc", 1100 | "system-deps", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "gstreamer-base" 1105 | version = "0.19.3" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "a61a299f9ea2ca892b43e2e428b86c679875e95ba23f8ae06fd730308df630f0" 1108 | dependencies = [ 1109 | "atomic_refcell", 1110 | "bitflags", 1111 | "cfg-if", 1112 | "glib", 1113 | "gstreamer", 1114 | "gstreamer-base-sys", 1115 | "libc", 1116 | ] 1117 | 1118 | [[package]] 1119 | name = "gstreamer-base-sys" 1120 | version = "0.19.3" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "dbc3c4476e1503ae245c89fbe20060c30ec6ade5f44620bcc402cbc70a3911a1" 1123 | dependencies = [ 1124 | "glib-sys", 1125 | "gobject-sys", 1126 | "gstreamer-sys", 1127 | "libc", 1128 | "system-deps", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "gstreamer-sys" 1133 | version = "0.19.2" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "fd119152dff9472ef8fa76afe7fb85a8ec639a5388f5c4b68058ce794a765a2a" 1136 | dependencies = [ 1137 | "glib-sys", 1138 | "gobject-sys", 1139 | "libc", 1140 | "system-deps", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "gstreamer-video" 1145 | version = "0.19.3" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "065f78ce526608441f215182b94b624580a1817fe2982d1b1b439ab8d370af30" 1148 | dependencies = [ 1149 | "bitflags", 1150 | "cfg-if", 1151 | "futures-channel", 1152 | "glib", 1153 | "gstreamer", 1154 | "gstreamer-base", 1155 | "gstreamer-video-sys", 1156 | "libc", 1157 | "once_cell", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "gstreamer-video-sys" 1162 | version = "0.19.3" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "298b78c5980c7ca790c48bab740ca5ffe70d08997987da103bfbfc566bcfd2a9" 1165 | dependencies = [ 1166 | "glib-sys", 1167 | "gobject-sys", 1168 | "gstreamer-base-sys", 1169 | "gstreamer-sys", 1170 | "libc", 1171 | "system-deps", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "heck" 1176 | version = "0.4.0" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 1179 | 1180 | [[package]] 1181 | name = "hermit-abi" 1182 | version = "0.1.19" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 1185 | dependencies = [ 1186 | "libc", 1187 | ] 1188 | 1189 | [[package]] 1190 | name = "hex" 1191 | version = "0.4.3" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1194 | 1195 | [[package]] 1196 | name = "hkdf" 1197 | version = "0.12.3" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" 1200 | dependencies = [ 1201 | "hmac 0.12.1", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "hmac" 1206 | version = "0.10.1" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" 1209 | dependencies = [ 1210 | "crypto-mac 0.10.1", 1211 | "digest 0.9.0", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "hmac" 1216 | version = "0.11.0" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" 1219 | dependencies = [ 1220 | "crypto-mac 0.11.1", 1221 | "digest 0.9.0", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "hmac" 1226 | version = "0.12.1" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1229 | dependencies = [ 1230 | "digest 0.10.6", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "iana-time-zone" 1235 | version = "0.1.53" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" 1238 | dependencies = [ 1239 | "android_system_properties", 1240 | "core-foundation-sys", 1241 | "iana-time-zone-haiku", 1242 | "js-sys", 1243 | "wasm-bindgen", 1244 | "winapi", 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "iana-time-zone-haiku" 1249 | version = "0.1.1" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 1252 | dependencies = [ 1253 | "cxx", 1254 | "cxx-build", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "ident_case" 1259 | version = "1.0.1" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1262 | 1263 | [[package]] 1264 | name = "idna" 1265 | version = "0.3.0" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 1268 | dependencies = [ 1269 | "unicode-bidi", 1270 | "unicode-normalization", 1271 | ] 1272 | 1273 | [[package]] 1274 | name = "interactive-example" 1275 | version = "0.1.0" 1276 | dependencies = [ 1277 | "anyhow", 1278 | "base64", 1279 | "clipboard", 1280 | "gst-plugin-webrtcredux", 1281 | "gstreamer", 1282 | "tokio", 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "interceptor" 1287 | version = "0.8.2" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "1e8a11ae2da61704edada656798b61c94b35ecac2c58eb955156987d5e6be90b" 1290 | dependencies = [ 1291 | "async-trait", 1292 | "bytes", 1293 | "log", 1294 | "rand", 1295 | "rtcp", 1296 | "rtp", 1297 | "thiserror", 1298 | "tokio", 1299 | "waitgroup", 1300 | "webrtc-srtp", 1301 | "webrtc-util", 1302 | ] 1303 | 1304 | [[package]] 1305 | name = "ipnet" 1306 | version = "2.5.1" 1307 | source = "registry+https://github.com/rust-lang/crates.io-index" 1308 | checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" 1309 | 1310 | [[package]] 1311 | name = "itoa" 1312 | version = "1.0.4" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" 1315 | 1316 | [[package]] 1317 | name = "js-sys" 1318 | version = "0.3.60" 1319 | source = "registry+https://github.com/rust-lang/crates.io-index" 1320 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 1321 | dependencies = [ 1322 | "wasm-bindgen", 1323 | ] 1324 | 1325 | [[package]] 1326 | name = "lazy_static" 1327 | version = "1.4.0" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1330 | 1331 | [[package]] 1332 | name = "libc" 1333 | version = "0.2.138" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" 1336 | 1337 | [[package]] 1338 | name = "link-cplusplus" 1339 | version = "1.0.7" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" 1342 | dependencies = [ 1343 | "cc", 1344 | ] 1345 | 1346 | [[package]] 1347 | name = "lock_api" 1348 | version = "0.4.9" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 1351 | dependencies = [ 1352 | "autocfg", 1353 | "scopeguard", 1354 | ] 1355 | 1356 | [[package]] 1357 | name = "log" 1358 | version = "0.4.17" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 1361 | dependencies = [ 1362 | "cfg-if", 1363 | ] 1364 | 1365 | [[package]] 1366 | name = "malloc_buf" 1367 | version = "0.0.6" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 1370 | dependencies = [ 1371 | "libc", 1372 | ] 1373 | 1374 | [[package]] 1375 | name = "md-5" 1376 | version = "0.10.5" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" 1379 | dependencies = [ 1380 | "digest 0.10.6", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "memchr" 1385 | version = "2.5.0" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 1388 | 1389 | [[package]] 1390 | name = "memoffset" 1391 | version = "0.6.5" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 1394 | dependencies = [ 1395 | "autocfg", 1396 | ] 1397 | 1398 | [[package]] 1399 | name = "minimal-lexical" 1400 | version = "0.2.1" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1403 | 1404 | [[package]] 1405 | name = "mio" 1406 | version = "0.8.5" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 1409 | dependencies = [ 1410 | "libc", 1411 | "log", 1412 | "wasi 0.11.0+wasi-snapshot-preview1", 1413 | "windows-sys", 1414 | ] 1415 | 1416 | [[package]] 1417 | name = "muldiv" 1418 | version = "1.0.1" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" 1421 | 1422 | [[package]] 1423 | name = "nix" 1424 | version = "0.24.3" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" 1427 | dependencies = [ 1428 | "bitflags", 1429 | "cfg-if", 1430 | "libc", 1431 | "memoffset", 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "nom" 1436 | version = "7.1.1" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 1439 | dependencies = [ 1440 | "memchr", 1441 | "minimal-lexical", 1442 | ] 1443 | 1444 | [[package]] 1445 | name = "num-bigint" 1446 | version = "0.4.3" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 1449 | dependencies = [ 1450 | "autocfg", 1451 | "num-integer", 1452 | "num-traits", 1453 | ] 1454 | 1455 | [[package]] 1456 | name = "num-integer" 1457 | version = "0.1.45" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 1460 | dependencies = [ 1461 | "autocfg", 1462 | "num-traits", 1463 | ] 1464 | 1465 | [[package]] 1466 | name = "num-rational" 1467 | version = "0.4.1" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" 1470 | dependencies = [ 1471 | "autocfg", 1472 | "num-integer", 1473 | "num-traits", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "num-traits" 1478 | version = "0.2.15" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 1481 | dependencies = [ 1482 | "autocfg", 1483 | ] 1484 | 1485 | [[package]] 1486 | name = "num_cpus" 1487 | version = "1.14.0" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" 1490 | dependencies = [ 1491 | "hermit-abi", 1492 | "libc", 1493 | ] 1494 | 1495 | [[package]] 1496 | name = "objc" 1497 | version = "0.2.7" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 1500 | dependencies = [ 1501 | "malloc_buf", 1502 | ] 1503 | 1504 | [[package]] 1505 | name = "objc-foundation" 1506 | version = "0.1.1" 1507 | source = "registry+https://github.com/rust-lang/crates.io-index" 1508 | checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" 1509 | dependencies = [ 1510 | "block", 1511 | "objc", 1512 | "objc_id", 1513 | ] 1514 | 1515 | [[package]] 1516 | name = "objc_id" 1517 | version = "0.1.1" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" 1520 | dependencies = [ 1521 | "objc", 1522 | ] 1523 | 1524 | [[package]] 1525 | name = "oid-registry" 1526 | version = "0.4.0" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "38e20717fa0541f39bd146692035c37bedfa532b3e5071b35761082407546b2a" 1529 | dependencies = [ 1530 | "asn1-rs 0.3.1", 1531 | ] 1532 | 1533 | [[package]] 1534 | name = "oid-registry" 1535 | version = "0.6.0" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "7d4bda43fd1b844cbc6e6e54b5444e2b1bc7838bce59ad205902cccbb26d6761" 1538 | dependencies = [ 1539 | "asn1-rs 0.5.1", 1540 | ] 1541 | 1542 | [[package]] 1543 | name = "once_cell" 1544 | version = "1.16.0" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 1547 | 1548 | [[package]] 1549 | name = "opaque-debug" 1550 | version = "0.3.0" 1551 | source = "registry+https://github.com/rust-lang/crates.io-index" 1552 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 1553 | 1554 | [[package]] 1555 | name = "option-operations" 1556 | version = "0.5.0" 1557 | source = "registry+https://github.com/rust-lang/crates.io-index" 1558 | checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0" 1559 | dependencies = [ 1560 | "paste", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "p256" 1565 | version = "0.11.1" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" 1568 | dependencies = [ 1569 | "ecdsa", 1570 | "elliptic-curve", 1571 | "sha2 0.10.6", 1572 | ] 1573 | 1574 | [[package]] 1575 | name = "p384" 1576 | version = "0.11.2" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "dfc8c5bf642dde52bb9e87c0ecd8ca5a76faac2eeed98dedb7c717997e1080aa" 1579 | dependencies = [ 1580 | "ecdsa", 1581 | "elliptic-curve", 1582 | "sha2 0.10.6", 1583 | ] 1584 | 1585 | [[package]] 1586 | name = "parking_lot" 1587 | version = "0.12.1" 1588 | source = "registry+https://github.com/rust-lang/crates.io-index" 1589 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1590 | dependencies = [ 1591 | "lock_api", 1592 | "parking_lot_core", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "parking_lot_core" 1597 | version = "0.9.5" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" 1600 | dependencies = [ 1601 | "cfg-if", 1602 | "libc", 1603 | "redox_syscall", 1604 | "smallvec", 1605 | "windows-sys", 1606 | ] 1607 | 1608 | [[package]] 1609 | name = "paste" 1610 | version = "1.0.9" 1611 | source = "registry+https://github.com/rust-lang/crates.io-index" 1612 | checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" 1613 | 1614 | [[package]] 1615 | name = "pem" 1616 | version = "1.1.0" 1617 | source = "registry+https://github.com/rust-lang/crates.io-index" 1618 | checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" 1619 | dependencies = [ 1620 | "base64", 1621 | ] 1622 | 1623 | [[package]] 1624 | name = "pem-rfc7468" 1625 | version = "0.6.0" 1626 | source = "registry+https://github.com/rust-lang/crates.io-index" 1627 | checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" 1628 | dependencies = [ 1629 | "base64ct", 1630 | ] 1631 | 1632 | [[package]] 1633 | name = "percent-encoding" 1634 | version = "2.2.0" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 1637 | 1638 | [[package]] 1639 | name = "pin-project-lite" 1640 | version = "0.2.9" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 1643 | 1644 | [[package]] 1645 | name = "pin-utils" 1646 | version = "0.1.0" 1647 | source = "registry+https://github.com/rust-lang/crates.io-index" 1648 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1649 | 1650 | [[package]] 1651 | name = "pkcs8" 1652 | version = "0.9.0" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" 1655 | dependencies = [ 1656 | "der", 1657 | "spki", 1658 | ] 1659 | 1660 | [[package]] 1661 | name = "pkg-config" 1662 | version = "0.3.26" 1663 | source = "registry+https://github.com/rust-lang/crates.io-index" 1664 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 1665 | 1666 | [[package]] 1667 | name = "polyval" 1668 | version = "0.4.5" 1669 | source = "registry+https://github.com/rust-lang/crates.io-index" 1670 | checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" 1671 | dependencies = [ 1672 | "cpuid-bool", 1673 | "opaque-debug", 1674 | "universal-hash", 1675 | ] 1676 | 1677 | [[package]] 1678 | name = "polyval" 1679 | version = "0.5.3" 1680 | source = "registry+https://github.com/rust-lang/crates.io-index" 1681 | checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" 1682 | dependencies = [ 1683 | "cfg-if", 1684 | "cpufeatures", 1685 | "opaque-debug", 1686 | "universal-hash", 1687 | ] 1688 | 1689 | [[package]] 1690 | name = "ppv-lite86" 1691 | version = "0.2.17" 1692 | source = "registry+https://github.com/rust-lang/crates.io-index" 1693 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1694 | 1695 | [[package]] 1696 | name = "pretty-hex" 1697 | version = "0.3.0" 1698 | source = "registry+https://github.com/rust-lang/crates.io-index" 1699 | checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" 1700 | 1701 | [[package]] 1702 | name = "proc-macro-crate" 1703 | version = "1.2.1" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" 1706 | dependencies = [ 1707 | "once_cell", 1708 | "thiserror", 1709 | "toml", 1710 | ] 1711 | 1712 | [[package]] 1713 | name = "proc-macro-error" 1714 | version = "1.0.4" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1717 | dependencies = [ 1718 | "proc-macro-error-attr", 1719 | "proc-macro2", 1720 | "quote", 1721 | "syn", 1722 | "version_check", 1723 | ] 1724 | 1725 | [[package]] 1726 | name = "proc-macro-error-attr" 1727 | version = "1.0.4" 1728 | source = "registry+https://github.com/rust-lang/crates.io-index" 1729 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1730 | dependencies = [ 1731 | "proc-macro2", 1732 | "quote", 1733 | "version_check", 1734 | ] 1735 | 1736 | [[package]] 1737 | name = "proc-macro2" 1738 | version = "1.0.47" 1739 | source = "registry+https://github.com/rust-lang/crates.io-index" 1740 | checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" 1741 | dependencies = [ 1742 | "unicode-ident", 1743 | ] 1744 | 1745 | [[package]] 1746 | name = "quick-xml" 1747 | version = "0.22.0" 1748 | source = "registry+https://github.com/rust-lang/crates.io-index" 1749 | checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" 1750 | dependencies = [ 1751 | "memchr", 1752 | ] 1753 | 1754 | [[package]] 1755 | name = "quote" 1756 | version = "1.0.21" 1757 | source = "registry+https://github.com/rust-lang/crates.io-index" 1758 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 1759 | dependencies = [ 1760 | "proc-macro2", 1761 | ] 1762 | 1763 | [[package]] 1764 | name = "rand" 1765 | version = "0.8.5" 1766 | source = "registry+https://github.com/rust-lang/crates.io-index" 1767 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1768 | dependencies = [ 1769 | "libc", 1770 | "rand_chacha", 1771 | "rand_core 0.6.4", 1772 | ] 1773 | 1774 | [[package]] 1775 | name = "rand_chacha" 1776 | version = "0.3.1" 1777 | source = "registry+https://github.com/rust-lang/crates.io-index" 1778 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1779 | dependencies = [ 1780 | "ppv-lite86", 1781 | "rand_core 0.6.4", 1782 | ] 1783 | 1784 | [[package]] 1785 | name = "rand_core" 1786 | version = "0.5.1" 1787 | source = "registry+https://github.com/rust-lang/crates.io-index" 1788 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1789 | dependencies = [ 1790 | "getrandom 0.1.16", 1791 | ] 1792 | 1793 | [[package]] 1794 | name = "rand_core" 1795 | version = "0.6.4" 1796 | source = "registry+https://github.com/rust-lang/crates.io-index" 1797 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1798 | dependencies = [ 1799 | "getrandom 0.2.8", 1800 | ] 1801 | 1802 | [[package]] 1803 | name = "rcgen" 1804 | version = "0.9.3" 1805 | source = "registry+https://github.com/rust-lang/crates.io-index" 1806 | checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" 1807 | dependencies = [ 1808 | "pem", 1809 | "ring", 1810 | "time", 1811 | "x509-parser", 1812 | "yasna", 1813 | ] 1814 | 1815 | [[package]] 1816 | name = "redox_syscall" 1817 | version = "0.2.16" 1818 | source = "registry+https://github.com/rust-lang/crates.io-index" 1819 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1820 | dependencies = [ 1821 | "bitflags", 1822 | ] 1823 | 1824 | [[package]] 1825 | name = "regex" 1826 | version = "1.7.0" 1827 | source = "registry+https://github.com/rust-lang/crates.io-index" 1828 | checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" 1829 | dependencies = [ 1830 | "aho-corasick", 1831 | "memchr", 1832 | "regex-syntax", 1833 | ] 1834 | 1835 | [[package]] 1836 | name = "regex-syntax" 1837 | version = "0.6.28" 1838 | source = "registry+https://github.com/rust-lang/crates.io-index" 1839 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 1840 | 1841 | [[package]] 1842 | name = "rfc6979" 1843 | version = "0.3.1" 1844 | source = "registry+https://github.com/rust-lang/crates.io-index" 1845 | checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" 1846 | dependencies = [ 1847 | "crypto-bigint", 1848 | "hmac 0.12.1", 1849 | "zeroize", 1850 | ] 1851 | 1852 | [[package]] 1853 | name = "ring" 1854 | version = "0.16.20" 1855 | source = "registry+https://github.com/rust-lang/crates.io-index" 1856 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 1857 | dependencies = [ 1858 | "cc", 1859 | "libc", 1860 | "once_cell", 1861 | "spin", 1862 | "untrusted", 1863 | "web-sys", 1864 | "winapi", 1865 | ] 1866 | 1867 | [[package]] 1868 | name = "rtcp" 1869 | version = "0.7.2" 1870 | source = "registry+https://github.com/rust-lang/crates.io-index" 1871 | checksum = "1919efd6d4a6a85d13388f9487549bb8e359f17198cc03ffd72f79b553873691" 1872 | dependencies = [ 1873 | "bytes", 1874 | "thiserror", 1875 | "webrtc-util", 1876 | ] 1877 | 1878 | [[package]] 1879 | name = "rtp" 1880 | version = "0.6.8" 1881 | source = "registry+https://github.com/rust-lang/crates.io-index" 1882 | checksum = "a2a095411ff00eed7b12e4c6a118ba984d113e1079582570d56a5ee723f11f80" 1883 | dependencies = [ 1884 | "async-trait", 1885 | "bytes", 1886 | "rand", 1887 | "serde", 1888 | "thiserror", 1889 | "webrtc-util", 1890 | ] 1891 | 1892 | [[package]] 1893 | name = "rusticata-macros" 1894 | version = "4.1.0" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" 1897 | dependencies = [ 1898 | "nom", 1899 | ] 1900 | 1901 | [[package]] 1902 | name = "rustls" 1903 | version = "0.19.1" 1904 | source = "registry+https://github.com/rust-lang/crates.io-index" 1905 | checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" 1906 | dependencies = [ 1907 | "base64", 1908 | "log", 1909 | "ring", 1910 | "sct", 1911 | "webpki", 1912 | ] 1913 | 1914 | [[package]] 1915 | name = "rustversion" 1916 | version = "1.0.9" 1917 | source = "registry+https://github.com/rust-lang/crates.io-index" 1918 | checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" 1919 | 1920 | [[package]] 1921 | name = "ryu" 1922 | version = "1.0.11" 1923 | source = "registry+https://github.com/rust-lang/crates.io-index" 1924 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 1925 | 1926 | [[package]] 1927 | name = "scopeguard" 1928 | version = "1.1.0" 1929 | source = "registry+https://github.com/rust-lang/crates.io-index" 1930 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1931 | 1932 | [[package]] 1933 | name = "scratch" 1934 | version = "1.0.2" 1935 | source = "registry+https://github.com/rust-lang/crates.io-index" 1936 | checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" 1937 | 1938 | [[package]] 1939 | name = "sct" 1940 | version = "0.6.1" 1941 | source = "registry+https://github.com/rust-lang/crates.io-index" 1942 | checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" 1943 | dependencies = [ 1944 | "ring", 1945 | "untrusted", 1946 | ] 1947 | 1948 | [[package]] 1949 | name = "sdp" 1950 | version = "0.5.3" 1951 | source = "registry+https://github.com/rust-lang/crates.io-index" 1952 | checksum = "4d22a5ef407871893fd72b4562ee15e4742269b173959db4b8df6f538c414e13" 1953 | dependencies = [ 1954 | "rand", 1955 | "substring", 1956 | "thiserror", 1957 | "url", 1958 | ] 1959 | 1960 | [[package]] 1961 | name = "sec1" 1962 | version = "0.3.0" 1963 | source = "registry+https://github.com/rust-lang/crates.io-index" 1964 | checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" 1965 | dependencies = [ 1966 | "base16ct", 1967 | "der", 1968 | "generic-array", 1969 | "pkcs8", 1970 | "subtle", 1971 | "zeroize", 1972 | ] 1973 | 1974 | [[package]] 1975 | name = "serde" 1976 | version = "1.0.148" 1977 | source = "registry+https://github.com/rust-lang/crates.io-index" 1978 | checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" 1979 | dependencies = [ 1980 | "serde_derive", 1981 | ] 1982 | 1983 | [[package]] 1984 | name = "serde_derive" 1985 | version = "1.0.148" 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" 1987 | checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" 1988 | dependencies = [ 1989 | "proc-macro2", 1990 | "quote", 1991 | "syn", 1992 | ] 1993 | 1994 | [[package]] 1995 | name = "serde_json" 1996 | version = "1.0.89" 1997 | source = "registry+https://github.com/rust-lang/crates.io-index" 1998 | checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" 1999 | dependencies = [ 2000 | "itoa", 2001 | "ryu", 2002 | "serde", 2003 | ] 2004 | 2005 | [[package]] 2006 | name = "sha-1" 2007 | version = "0.9.8" 2008 | source = "registry+https://github.com/rust-lang/crates.io-index" 2009 | checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" 2010 | dependencies = [ 2011 | "block-buffer 0.9.0", 2012 | "cfg-if", 2013 | "cpufeatures", 2014 | "digest 0.9.0", 2015 | "opaque-debug", 2016 | ] 2017 | 2018 | [[package]] 2019 | name = "sha2" 2020 | version = "0.9.9" 2021 | source = "registry+https://github.com/rust-lang/crates.io-index" 2022 | checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" 2023 | dependencies = [ 2024 | "block-buffer 0.9.0", 2025 | "cfg-if", 2026 | "cpufeatures", 2027 | "digest 0.9.0", 2028 | "opaque-debug", 2029 | ] 2030 | 2031 | [[package]] 2032 | name = "sha2" 2033 | version = "0.10.6" 2034 | source = "registry+https://github.com/rust-lang/crates.io-index" 2035 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 2036 | dependencies = [ 2037 | "cfg-if", 2038 | "cpufeatures", 2039 | "digest 0.10.6", 2040 | ] 2041 | 2042 | [[package]] 2043 | name = "signal-hook-registry" 2044 | version = "1.4.0" 2045 | source = "registry+https://github.com/rust-lang/crates.io-index" 2046 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 2047 | dependencies = [ 2048 | "libc", 2049 | ] 2050 | 2051 | [[package]] 2052 | name = "signature" 2053 | version = "1.6.4" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" 2056 | dependencies = [ 2057 | "digest 0.10.6", 2058 | "rand_core 0.6.4", 2059 | ] 2060 | 2061 | [[package]] 2062 | name = "slab" 2063 | version = "0.4.7" 2064 | source = "registry+https://github.com/rust-lang/crates.io-index" 2065 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 2066 | dependencies = [ 2067 | "autocfg", 2068 | ] 2069 | 2070 | [[package]] 2071 | name = "smallvec" 2072 | version = "1.10.0" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 2075 | 2076 | [[package]] 2077 | name = "socket2" 2078 | version = "0.4.7" 2079 | source = "registry+https://github.com/rust-lang/crates.io-index" 2080 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 2081 | dependencies = [ 2082 | "libc", 2083 | "winapi", 2084 | ] 2085 | 2086 | [[package]] 2087 | name = "spin" 2088 | version = "0.5.2" 2089 | source = "registry+https://github.com/rust-lang/crates.io-index" 2090 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 2091 | 2092 | [[package]] 2093 | name = "spki" 2094 | version = "0.6.0" 2095 | source = "registry+https://github.com/rust-lang/crates.io-index" 2096 | checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" 2097 | dependencies = [ 2098 | "base64ct", 2099 | "der", 2100 | ] 2101 | 2102 | [[package]] 2103 | name = "strsim" 2104 | version = "0.10.0" 2105 | source = "registry+https://github.com/rust-lang/crates.io-index" 2106 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 2107 | 2108 | [[package]] 2109 | name = "strum" 2110 | version = "0.24.1" 2111 | source = "registry+https://github.com/rust-lang/crates.io-index" 2112 | checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" 2113 | 2114 | [[package]] 2115 | name = "strum_macros" 2116 | version = "0.24.3" 2117 | source = "registry+https://github.com/rust-lang/crates.io-index" 2118 | checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" 2119 | dependencies = [ 2120 | "heck", 2121 | "proc-macro2", 2122 | "quote", 2123 | "rustversion", 2124 | "syn", 2125 | ] 2126 | 2127 | [[package]] 2128 | name = "stun" 2129 | version = "0.4.4" 2130 | source = "registry+https://github.com/rust-lang/crates.io-index" 2131 | checksum = "a7e94b1ec00bad60e6410e058b52f1c66de3dc5fe4d62d09b3e52bb7d3b73e25" 2132 | dependencies = [ 2133 | "base64", 2134 | "crc", 2135 | "lazy_static", 2136 | "md-5", 2137 | "rand", 2138 | "ring", 2139 | "subtle", 2140 | "thiserror", 2141 | "tokio", 2142 | "url", 2143 | "webrtc-util", 2144 | ] 2145 | 2146 | [[package]] 2147 | name = "substring" 2148 | version = "1.4.5" 2149 | source = "registry+https://github.com/rust-lang/crates.io-index" 2150 | checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" 2151 | dependencies = [ 2152 | "autocfg", 2153 | ] 2154 | 2155 | [[package]] 2156 | name = "subtle" 2157 | version = "2.4.1" 2158 | source = "registry+https://github.com/rust-lang/crates.io-index" 2159 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 2160 | 2161 | [[package]] 2162 | name = "syn" 2163 | version = "1.0.105" 2164 | source = "registry+https://github.com/rust-lang/crates.io-index" 2165 | checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" 2166 | dependencies = [ 2167 | "proc-macro2", 2168 | "quote", 2169 | "unicode-ident", 2170 | ] 2171 | 2172 | [[package]] 2173 | name = "synstructure" 2174 | version = "0.12.6" 2175 | source = "registry+https://github.com/rust-lang/crates.io-index" 2176 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 2177 | dependencies = [ 2178 | "proc-macro2", 2179 | "quote", 2180 | "syn", 2181 | "unicode-xid", 2182 | ] 2183 | 2184 | [[package]] 2185 | name = "system-deps" 2186 | version = "6.0.3" 2187 | source = "registry+https://github.com/rust-lang/crates.io-index" 2188 | checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" 2189 | dependencies = [ 2190 | "cfg-expr", 2191 | "heck", 2192 | "pkg-config", 2193 | "toml", 2194 | "version-compare", 2195 | ] 2196 | 2197 | [[package]] 2198 | name = "termcolor" 2199 | version = "1.1.3" 2200 | source = "registry+https://github.com/rust-lang/crates.io-index" 2201 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 2202 | dependencies = [ 2203 | "winapi-util", 2204 | ] 2205 | 2206 | [[package]] 2207 | name = "thiserror" 2208 | version = "1.0.37" 2209 | source = "registry+https://github.com/rust-lang/crates.io-index" 2210 | checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" 2211 | dependencies = [ 2212 | "thiserror-impl", 2213 | ] 2214 | 2215 | [[package]] 2216 | name = "thiserror-impl" 2217 | version = "1.0.37" 2218 | source = "registry+https://github.com/rust-lang/crates.io-index" 2219 | checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" 2220 | dependencies = [ 2221 | "proc-macro2", 2222 | "quote", 2223 | "syn", 2224 | ] 2225 | 2226 | [[package]] 2227 | name = "time" 2228 | version = "0.3.17" 2229 | source = "registry+https://github.com/rust-lang/crates.io-index" 2230 | checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" 2231 | dependencies = [ 2232 | "itoa", 2233 | "serde", 2234 | "time-core", 2235 | "time-macros", 2236 | ] 2237 | 2238 | [[package]] 2239 | name = "time-core" 2240 | version = "0.1.0" 2241 | source = "registry+https://github.com/rust-lang/crates.io-index" 2242 | checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" 2243 | 2244 | [[package]] 2245 | name = "time-macros" 2246 | version = "0.2.6" 2247 | source = "registry+https://github.com/rust-lang/crates.io-index" 2248 | checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" 2249 | dependencies = [ 2250 | "time-core", 2251 | ] 2252 | 2253 | [[package]] 2254 | name = "tinyvec" 2255 | version = "1.6.0" 2256 | source = "registry+https://github.com/rust-lang/crates.io-index" 2257 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 2258 | dependencies = [ 2259 | "tinyvec_macros", 2260 | ] 2261 | 2262 | [[package]] 2263 | name = "tinyvec_macros" 2264 | version = "0.1.0" 2265 | source = "registry+https://github.com/rust-lang/crates.io-index" 2266 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 2267 | 2268 | [[package]] 2269 | name = "tokio" 2270 | version = "1.22.0" 2271 | source = "registry+https://github.com/rust-lang/crates.io-index" 2272 | checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" 2273 | dependencies = [ 2274 | "autocfg", 2275 | "bytes", 2276 | "libc", 2277 | "memchr", 2278 | "mio", 2279 | "num_cpus", 2280 | "parking_lot", 2281 | "pin-project-lite", 2282 | "signal-hook-registry", 2283 | "socket2", 2284 | "tokio-macros", 2285 | "winapi", 2286 | ] 2287 | 2288 | [[package]] 2289 | name = "tokio-macros" 2290 | version = "1.8.2" 2291 | source = "registry+https://github.com/rust-lang/crates.io-index" 2292 | checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" 2293 | dependencies = [ 2294 | "proc-macro2", 2295 | "quote", 2296 | "syn", 2297 | ] 2298 | 2299 | [[package]] 2300 | name = "toml" 2301 | version = "0.5.9" 2302 | source = "registry+https://github.com/rust-lang/crates.io-index" 2303 | checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 2304 | dependencies = [ 2305 | "serde", 2306 | ] 2307 | 2308 | [[package]] 2309 | name = "turn" 2310 | version = "0.6.1" 2311 | source = "registry+https://github.com/rust-lang/crates.io-index" 2312 | checksum = "4712ee30d123ec7ae26d1e1b218395a16c87cdbaf4b3925d170d684af62ea5e8" 2313 | dependencies = [ 2314 | "async-trait", 2315 | "base64", 2316 | "futures", 2317 | "log", 2318 | "md-5", 2319 | "rand", 2320 | "ring", 2321 | "stun", 2322 | "thiserror", 2323 | "tokio", 2324 | "webrtc-util", 2325 | ] 2326 | 2327 | [[package]] 2328 | name = "typenum" 2329 | version = "1.15.0" 2330 | source = "registry+https://github.com/rust-lang/crates.io-index" 2331 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 2332 | 2333 | [[package]] 2334 | name = "unicode-bidi" 2335 | version = "0.3.8" 2336 | source = "registry+https://github.com/rust-lang/crates.io-index" 2337 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 2338 | 2339 | [[package]] 2340 | name = "unicode-ident" 2341 | version = "1.0.5" 2342 | source = "registry+https://github.com/rust-lang/crates.io-index" 2343 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 2344 | 2345 | [[package]] 2346 | name = "unicode-normalization" 2347 | version = "0.1.22" 2348 | source = "registry+https://github.com/rust-lang/crates.io-index" 2349 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 2350 | dependencies = [ 2351 | "tinyvec", 2352 | ] 2353 | 2354 | [[package]] 2355 | name = "unicode-width" 2356 | version = "0.1.10" 2357 | source = "registry+https://github.com/rust-lang/crates.io-index" 2358 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 2359 | 2360 | [[package]] 2361 | name = "unicode-xid" 2362 | version = "0.2.4" 2363 | source = "registry+https://github.com/rust-lang/crates.io-index" 2364 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 2365 | 2366 | [[package]] 2367 | name = "universal-hash" 2368 | version = "0.4.1" 2369 | source = "registry+https://github.com/rust-lang/crates.io-index" 2370 | checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" 2371 | dependencies = [ 2372 | "generic-array", 2373 | "subtle", 2374 | ] 2375 | 2376 | [[package]] 2377 | name = "untrusted" 2378 | version = "0.7.1" 2379 | source = "registry+https://github.com/rust-lang/crates.io-index" 2380 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 2381 | 2382 | [[package]] 2383 | name = "url" 2384 | version = "2.3.1" 2385 | source = "registry+https://github.com/rust-lang/crates.io-index" 2386 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 2387 | dependencies = [ 2388 | "form_urlencoded", 2389 | "idna", 2390 | "percent-encoding", 2391 | ] 2392 | 2393 | [[package]] 2394 | name = "uuid" 2395 | version = "1.2.2" 2396 | source = "registry+https://github.com/rust-lang/crates.io-index" 2397 | checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" 2398 | dependencies = [ 2399 | "getrandom 0.2.8", 2400 | ] 2401 | 2402 | [[package]] 2403 | name = "version-compare" 2404 | version = "0.1.1" 2405 | source = "registry+https://github.com/rust-lang/crates.io-index" 2406 | checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" 2407 | 2408 | [[package]] 2409 | name = "version_check" 2410 | version = "0.9.4" 2411 | source = "registry+https://github.com/rust-lang/crates.io-index" 2412 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2413 | 2414 | [[package]] 2415 | name = "waitgroup" 2416 | version = "0.1.2" 2417 | source = "registry+https://github.com/rust-lang/crates.io-index" 2418 | checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" 2419 | dependencies = [ 2420 | "atomic-waker", 2421 | ] 2422 | 2423 | [[package]] 2424 | name = "wasi" 2425 | version = "0.9.0+wasi-snapshot-preview1" 2426 | source = "registry+https://github.com/rust-lang/crates.io-index" 2427 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 2428 | 2429 | [[package]] 2430 | name = "wasi" 2431 | version = "0.11.0+wasi-snapshot-preview1" 2432 | source = "registry+https://github.com/rust-lang/crates.io-index" 2433 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2434 | 2435 | [[package]] 2436 | name = "wasm-bindgen" 2437 | version = "0.2.83" 2438 | source = "registry+https://github.com/rust-lang/crates.io-index" 2439 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 2440 | dependencies = [ 2441 | "cfg-if", 2442 | "wasm-bindgen-macro", 2443 | ] 2444 | 2445 | [[package]] 2446 | name = "wasm-bindgen-backend" 2447 | version = "0.2.83" 2448 | source = "registry+https://github.com/rust-lang/crates.io-index" 2449 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 2450 | dependencies = [ 2451 | "bumpalo", 2452 | "log", 2453 | "once_cell", 2454 | "proc-macro2", 2455 | "quote", 2456 | "syn", 2457 | "wasm-bindgen-shared", 2458 | ] 2459 | 2460 | [[package]] 2461 | name = "wasm-bindgen-macro" 2462 | version = "0.2.83" 2463 | source = "registry+https://github.com/rust-lang/crates.io-index" 2464 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 2465 | dependencies = [ 2466 | "quote", 2467 | "wasm-bindgen-macro-support", 2468 | ] 2469 | 2470 | [[package]] 2471 | name = "wasm-bindgen-macro-support" 2472 | version = "0.2.83" 2473 | source = "registry+https://github.com/rust-lang/crates.io-index" 2474 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 2475 | dependencies = [ 2476 | "proc-macro2", 2477 | "quote", 2478 | "syn", 2479 | "wasm-bindgen-backend", 2480 | "wasm-bindgen-shared", 2481 | ] 2482 | 2483 | [[package]] 2484 | name = "wasm-bindgen-shared" 2485 | version = "0.2.83" 2486 | source = "registry+https://github.com/rust-lang/crates.io-index" 2487 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 2488 | 2489 | [[package]] 2490 | name = "web-sys" 2491 | version = "0.3.60" 2492 | source = "registry+https://github.com/rust-lang/crates.io-index" 2493 | checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" 2494 | dependencies = [ 2495 | "js-sys", 2496 | "wasm-bindgen", 2497 | ] 2498 | 2499 | [[package]] 2500 | name = "webpki" 2501 | version = "0.21.4" 2502 | source = "registry+https://github.com/rust-lang/crates.io-index" 2503 | checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" 2504 | dependencies = [ 2505 | "ring", 2506 | "untrusted", 2507 | ] 2508 | 2509 | [[package]] 2510 | name = "webrtc" 2511 | version = "0.6.0" 2512 | source = "registry+https://github.com/rust-lang/crates.io-index" 2513 | checksum = "2d3bc9049bdb2cea52f5fd4f6f728184225bdb867ed0dc2410eab6df5bdd67bb" 2514 | dependencies = [ 2515 | "arc-swap", 2516 | "async-trait", 2517 | "bytes", 2518 | "hex", 2519 | "interceptor", 2520 | "lazy_static", 2521 | "log", 2522 | "rand", 2523 | "rcgen", 2524 | "regex", 2525 | "ring", 2526 | "rtcp", 2527 | "rtp", 2528 | "rustls", 2529 | "sdp", 2530 | "serde", 2531 | "serde_json", 2532 | "sha2 0.10.6", 2533 | "stun", 2534 | "thiserror", 2535 | "time", 2536 | "tokio", 2537 | "turn", 2538 | "url", 2539 | "waitgroup", 2540 | "webrtc-data", 2541 | "webrtc-dtls", 2542 | "webrtc-ice", 2543 | "webrtc-mdns", 2544 | "webrtc-media", 2545 | "webrtc-sctp", 2546 | "webrtc-srtp", 2547 | "webrtc-util", 2548 | ] 2549 | 2550 | [[package]] 2551 | name = "webrtc-data" 2552 | version = "0.6.0" 2553 | source = "registry+https://github.com/rust-lang/crates.io-index" 2554 | checksum = "0ef36a4d12baa6e842582fe9ec16a57184ba35e1a09308307b67d43ec8883100" 2555 | dependencies = [ 2556 | "bytes", 2557 | "derive_builder", 2558 | "log", 2559 | "thiserror", 2560 | "tokio", 2561 | "webrtc-sctp", 2562 | "webrtc-util", 2563 | ] 2564 | 2565 | [[package]] 2566 | name = "webrtc-dtls" 2567 | version = "0.7.0" 2568 | source = "registry+https://github.com/rust-lang/crates.io-index" 2569 | checksum = "7021987ae0a2ed6c8cd33f68e98e49bb6e74ffe9543310267b48a1bbe3900e5f" 2570 | dependencies = [ 2571 | "aes 0.6.0", 2572 | "aes-gcm 0.8.0", 2573 | "async-trait", 2574 | "bincode", 2575 | "block-modes", 2576 | "byteorder", 2577 | "ccm", 2578 | "curve25519-dalek", 2579 | "der-parser 8.1.0", 2580 | "elliptic-curve", 2581 | "hkdf", 2582 | "hmac 0.10.1", 2583 | "log", 2584 | "oid-registry 0.6.0", 2585 | "p256", 2586 | "p384", 2587 | "rand", 2588 | "rand_core 0.6.4", 2589 | "rcgen", 2590 | "ring", 2591 | "rustls", 2592 | "sec1", 2593 | "serde", 2594 | "sha-1", 2595 | "sha2 0.9.9", 2596 | "signature", 2597 | "subtle", 2598 | "thiserror", 2599 | "tokio", 2600 | "webpki", 2601 | "webrtc-util", 2602 | "x25519-dalek", 2603 | "x509-parser", 2604 | ] 2605 | 2606 | [[package]] 2607 | name = "webrtc-ice" 2608 | version = "0.9.0" 2609 | source = "registry+https://github.com/rust-lang/crates.io-index" 2610 | checksum = "494483fbb2f5492620871fdc78b084aed8807377f6e3fe88b2e49f0a9c9c41d7" 2611 | dependencies = [ 2612 | "arc-swap", 2613 | "async-trait", 2614 | "crc", 2615 | "log", 2616 | "rand", 2617 | "serde", 2618 | "serde_json", 2619 | "stun", 2620 | "thiserror", 2621 | "tokio", 2622 | "turn", 2623 | "url", 2624 | "uuid", 2625 | "waitgroup", 2626 | "webrtc-mdns", 2627 | "webrtc-util", 2628 | ] 2629 | 2630 | [[package]] 2631 | name = "webrtc-mdns" 2632 | version = "0.5.1" 2633 | source = "registry+https://github.com/rust-lang/crates.io-index" 2634 | checksum = "2548ae970afc2ae8e0b0dbfdacd9602d426d4f0ff6cda4602a45c0fd7ceaa82a" 2635 | dependencies = [ 2636 | "log", 2637 | "socket2", 2638 | "thiserror", 2639 | "tokio", 2640 | "webrtc-util", 2641 | ] 2642 | 2643 | [[package]] 2644 | name = "webrtc-media" 2645 | version = "0.5.0" 2646 | source = "registry+https://github.com/rust-lang/crates.io-index" 2647 | checksum = "ee2a3c157a040324e5049bcbd644ffc9079e6738fa2cfab2bcff64e5cc4c00d7" 2648 | dependencies = [ 2649 | "byteorder", 2650 | "bytes", 2651 | "derive_builder", 2652 | "displaydoc", 2653 | "rand", 2654 | "rtp", 2655 | "thiserror", 2656 | "webrtc-util", 2657 | ] 2658 | 2659 | [[package]] 2660 | name = "webrtc-sctp" 2661 | version = "0.7.0" 2662 | source = "registry+https://github.com/rust-lang/crates.io-index" 2663 | checksum = "0d47adcd9427eb3ede33d5a7f3424038f63c965491beafcc20bc650a2f6679c0" 2664 | dependencies = [ 2665 | "arc-swap", 2666 | "async-trait", 2667 | "bytes", 2668 | "crc", 2669 | "log", 2670 | "rand", 2671 | "thiserror", 2672 | "tokio", 2673 | "webrtc-util", 2674 | ] 2675 | 2676 | [[package]] 2677 | name = "webrtc-srtp" 2678 | version = "0.9.1" 2679 | source = "registry+https://github.com/rust-lang/crates.io-index" 2680 | checksum = "6183edc4c1c6c0175f8812eefdce84dfa0aea9c3ece71c2bf6ddd3c964de3da5" 2681 | dependencies = [ 2682 | "aead 0.4.3", 2683 | "aes 0.7.5", 2684 | "aes-gcm 0.9.4", 2685 | "async-trait", 2686 | "byteorder", 2687 | "bytes", 2688 | "ctr 0.8.0", 2689 | "hmac 0.11.0", 2690 | "log", 2691 | "rtcp", 2692 | "rtp", 2693 | "sha-1", 2694 | "subtle", 2695 | "thiserror", 2696 | "tokio", 2697 | "webrtc-util", 2698 | ] 2699 | 2700 | [[package]] 2701 | name = "webrtc-util" 2702 | version = "0.7.0" 2703 | source = "registry+https://github.com/rust-lang/crates.io-index" 2704 | checksum = "93f1db1727772c05cf7a2cfece52c3aca8045ca1e176cd517d323489aa3c6d87" 2705 | dependencies = [ 2706 | "async-trait", 2707 | "bitflags", 2708 | "bytes", 2709 | "cc", 2710 | "ipnet", 2711 | "lazy_static", 2712 | "libc", 2713 | "log", 2714 | "nix", 2715 | "rand", 2716 | "thiserror", 2717 | "tokio", 2718 | "winapi", 2719 | ] 2720 | 2721 | [[package]] 2722 | name = "winapi" 2723 | version = "0.3.9" 2724 | source = "registry+https://github.com/rust-lang/crates.io-index" 2725 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2726 | dependencies = [ 2727 | "winapi-i686-pc-windows-gnu", 2728 | "winapi-x86_64-pc-windows-gnu", 2729 | ] 2730 | 2731 | [[package]] 2732 | name = "winapi-i686-pc-windows-gnu" 2733 | version = "0.4.0" 2734 | source = "registry+https://github.com/rust-lang/crates.io-index" 2735 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2736 | 2737 | [[package]] 2738 | name = "winapi-util" 2739 | version = "0.1.5" 2740 | source = "registry+https://github.com/rust-lang/crates.io-index" 2741 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 2742 | dependencies = [ 2743 | "winapi", 2744 | ] 2745 | 2746 | [[package]] 2747 | name = "winapi-x86_64-pc-windows-gnu" 2748 | version = "0.4.0" 2749 | source = "registry+https://github.com/rust-lang/crates.io-index" 2750 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2751 | 2752 | [[package]] 2753 | name = "windows-sys" 2754 | version = "0.42.0" 2755 | source = "registry+https://github.com/rust-lang/crates.io-index" 2756 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 2757 | dependencies = [ 2758 | "windows_aarch64_gnullvm", 2759 | "windows_aarch64_msvc", 2760 | "windows_i686_gnu", 2761 | "windows_i686_msvc", 2762 | "windows_x86_64_gnu", 2763 | "windows_x86_64_gnullvm", 2764 | "windows_x86_64_msvc", 2765 | ] 2766 | 2767 | [[package]] 2768 | name = "windows_aarch64_gnullvm" 2769 | version = "0.42.0" 2770 | source = "registry+https://github.com/rust-lang/crates.io-index" 2771 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 2772 | 2773 | [[package]] 2774 | name = "windows_aarch64_msvc" 2775 | version = "0.42.0" 2776 | source = "registry+https://github.com/rust-lang/crates.io-index" 2777 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 2778 | 2779 | [[package]] 2780 | name = "windows_i686_gnu" 2781 | version = "0.42.0" 2782 | source = "registry+https://github.com/rust-lang/crates.io-index" 2783 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 2784 | 2785 | [[package]] 2786 | name = "windows_i686_msvc" 2787 | version = "0.42.0" 2788 | source = "registry+https://github.com/rust-lang/crates.io-index" 2789 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 2790 | 2791 | [[package]] 2792 | name = "windows_x86_64_gnu" 2793 | version = "0.42.0" 2794 | source = "registry+https://github.com/rust-lang/crates.io-index" 2795 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 2796 | 2797 | [[package]] 2798 | name = "windows_x86_64_gnullvm" 2799 | version = "0.42.0" 2800 | source = "registry+https://github.com/rust-lang/crates.io-index" 2801 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 2802 | 2803 | [[package]] 2804 | name = "windows_x86_64_msvc" 2805 | version = "0.42.0" 2806 | source = "registry+https://github.com/rust-lang/crates.io-index" 2807 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 2808 | 2809 | [[package]] 2810 | name = "x11-clipboard" 2811 | version = "0.3.3" 2812 | source = "registry+https://github.com/rust-lang/crates.io-index" 2813 | checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea" 2814 | dependencies = [ 2815 | "xcb 0.8.2", 2816 | ] 2817 | 2818 | [[package]] 2819 | name = "x25519-dalek" 2820 | version = "2.0.0-pre.1" 2821 | source = "registry+https://github.com/rust-lang/crates.io-index" 2822 | checksum = "e5da623d8af10a62342bcbbb230e33e58a63255a58012f8653c578e54bab48df" 2823 | dependencies = [ 2824 | "curve25519-dalek", 2825 | "rand_core 0.6.4", 2826 | "zeroize", 2827 | ] 2828 | 2829 | [[package]] 2830 | name = "x509-parser" 2831 | version = "0.13.2" 2832 | source = "registry+https://github.com/rust-lang/crates.io-index" 2833 | checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c" 2834 | dependencies = [ 2835 | "asn1-rs 0.3.1", 2836 | "base64", 2837 | "data-encoding", 2838 | "der-parser 7.0.0", 2839 | "lazy_static", 2840 | "nom", 2841 | "oid-registry 0.4.0", 2842 | "ring", 2843 | "rusticata-macros", 2844 | "thiserror", 2845 | "time", 2846 | ] 2847 | 2848 | [[package]] 2849 | name = "xcb" 2850 | version = "0.8.2" 2851 | source = "registry+https://github.com/rust-lang/crates.io-index" 2852 | checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de" 2853 | dependencies = [ 2854 | "libc", 2855 | "log", 2856 | ] 2857 | 2858 | [[package]] 2859 | name = "xcb" 2860 | version = "1.2.0" 2861 | source = "registry+https://github.com/rust-lang/crates.io-index" 2862 | checksum = "0faeb4d7e2d54fff4a0584f61297e86b106914af2029778de7b427f72564d6c5" 2863 | dependencies = [ 2864 | "bitflags", 2865 | "libc", 2866 | "quick-xml", 2867 | ] 2868 | 2869 | [[package]] 2870 | name = "yasna" 2871 | version = "0.5.0" 2872 | source = "registry+https://github.com/rust-lang/crates.io-index" 2873 | checksum = "346d34a236c9d3e5f3b9b74563f238f955bbd05fa0b8b4efa53c130c43982f4c" 2874 | dependencies = [ 2875 | "time", 2876 | ] 2877 | 2878 | [[package]] 2879 | name = "zeroize" 2880 | version = "1.5.7" 2881 | source = "registry+https://github.com/rust-lang/crates.io-index" 2882 | checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" 2883 | dependencies = [ 2884 | "zeroize_derive", 2885 | ] 2886 | 2887 | [[package]] 2888 | name = "zeroize_derive" 2889 | version = "1.3.3" 2890 | source = "registry+https://github.com/rust-lang/crates.io-index" 2891 | checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" 2892 | dependencies = [ 2893 | "proc-macro2", 2894 | "quote", 2895 | "syn", 2896 | "synstructure", 2897 | ] 2898 | -------------------------------------------------------------------------------- /examples/h264-browser/interactive-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "interactive-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | # gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_16"] } 10 | gst = { package = "gstreamer", version = "0.19.3", features = ["v1_16"] } 11 | gst-plugin-webrtcredux = { path = "../../../" } 12 | base64 = "0.13.0" 13 | anyhow = "1.0.58" 14 | tokio = "1.20.1" 15 | clipboard = "0.5.0" -------------------------------------------------------------------------------- /examples/h264-browser/interactive-example/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use gst::{Element}; 3 | use gst::prelude::*; 4 | use anyhow::Result; 5 | use clipboard::{ClipboardContext, ClipboardProvider}; 6 | use tokio::io; 7 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 8 | use webrtcredux::{RTCIceConnectionState, RTCSdpType}; 9 | use tokio::runtime::Handle; 10 | use webrtcredux::sdp::LineEnding::LF; 11 | 12 | use webrtcredux::webrtcredux::{ 13 | sdp::{SDP}, 14 | RTCIceServer, WebRtcRedux, 15 | }; 16 | 17 | pub fn must_read_stdin() -> Result { 18 | let mut line = String::new(); 19 | 20 | std::io::stdin().read_line(&mut line)?; 21 | line = line.trim().to_owned(); 22 | println!(); 23 | 24 | Ok(line) 25 | } 26 | 27 | pub fn decode(s: &str) -> Result { 28 | let b = base64::decode(s)?; 29 | 30 | let s = String::from_utf8(b)?; 31 | Ok(s) 32 | } 33 | 34 | async fn pause() { 35 | let mut stdin = io::stdin(); 36 | let mut stdout = io::stdout(); 37 | 38 | // We want the cursor to stay at the end of the line, so we print without a newline and flush manually. 39 | stdout.write_all(b"\nPress Enter to continue...").await.unwrap(); 40 | stdout.flush().await.unwrap(); 41 | 42 | // Read a single byte and discard 43 | let _ = stdin.read(&mut [0u8]).await.unwrap(); 44 | } 45 | 46 | #[tokio::main] 47 | async fn main() -> Result<()> { 48 | gst::init().unwrap(); 49 | webrtcredux::plugin_register_static().unwrap(); 50 | 51 | let pipeline = gst::Pipeline::new(None); 52 | 53 | let webrtcredux = WebRtcRedux::default(); 54 | 55 | webrtcredux.set_tokio_runtime(Handle::current()); 56 | 57 | webrtcredux.add_ice_servers(vec![RTCIceServer { 58 | urls: vec!["stun:stun.comrex.com:3478".to_string()], 59 | ..Default::default() 60 | }]); 61 | 62 | pipeline 63 | .add(webrtcredux.upcast_ref::()) 64 | .expect("Failed to add webrtcredux to the pipeline"); 65 | 66 | let video_src = gst::ElementFactory::make("videotestsrc").build()?; 67 | 68 | let video_encoder = gst::ElementFactory::make("x264enc").build()?; 69 | 70 | video_encoder.set_property("threads", 12u32); 71 | video_encoder.set_property("bitrate", 2048000_u32 / 1000); 72 | video_encoder.set_property_from_str("tune", "zerolatency"); 73 | video_encoder.set_property_from_str("speed-preset", "ultrafast"); 74 | video_encoder.set_property("key-int-max", 2560u32); 75 | video_encoder.set_property("b-adapt", false); 76 | video_encoder.set_property("vbv-buf-capacity", 120u32); 77 | 78 | pipeline.add_many(&[&video_src, &video_encoder])?; 79 | 80 | Element::link_many(&[&video_src, &video_encoder])?; 81 | 82 | video_encoder.link(webrtcredux.upcast_ref::())?; 83 | 84 | //webrtcredux.set_stream_id("video_0", "webrtc-rs")?; 85 | 86 | let audio_src = gst::ElementFactory::make("audiotestsrc").build()?; 87 | 88 | audio_src.set_property_from_str("wave", "ticks"); 89 | audio_src.set_property_from_str("tick-interval", "500000000"); 90 | 91 | let audio_encoder = gst::ElementFactory::make("opusenc").build()?; 92 | 93 | let audio_capsfilter = gst::ElementFactory::make("capsfilter").build()?; 94 | 95 | //Create a vector containing the option of the gst caps 96 | let caps_options: Vec<(&str, &(dyn ToSendValue + Sync))> = 97 | vec![("channels", &2)]; 98 | 99 | audio_capsfilter.set_property( 100 | "caps", 101 | &gst::Caps::new_simple("audio/x-raw", caps_options.as_ref()), 102 | ); 103 | 104 | pipeline.add_many(&[&audio_src, &audio_capsfilter, &audio_encoder])?; 105 | 106 | Element::link_many(&[&audio_src, &audio_capsfilter, &audio_encoder])?; 107 | 108 | audio_encoder.link(webrtcredux.upcast_ref::())?; 109 | 110 | //webrtcredux.set_stream_id("audio_0", "webrtc-rs")?; 111 | 112 | let mut clipboard_handle = ClipboardContext::new().expect("Failed to create clipboard context"); 113 | 114 | print!("Please copy remote description to your clipboard"); 115 | pause().await; 116 | 117 | let line = clipboard_handle.get_contents().expect("Failed to get clipboard contents"); 118 | let sdp_offer_from_b64 = decode(line.as_str())?; 119 | let offer = SDP::from_str(&sdp_offer_from_b64).expect("Failed to parse SDP"); 120 | 121 | pipeline.set_state(gst::State::Playing)?; 122 | 123 | webrtcredux.on_peer_connection_state_change(Box::new(|state| { 124 | println!("Peer connection state has changed {}", state); 125 | 126 | Box::pin(async {}) 127 | }))?; 128 | 129 | webrtcredux.on_ice_connection_state_change(Box::new(move |connection_state: RTCIceConnectionState| { 130 | println!("Connection State has changed {}", connection_state); 131 | 132 | Box::pin(async {}) 133 | })) 134 | .await?; 135 | 136 | webrtcredux.wait_for_all_tracks().await; 137 | 138 | webrtcredux.set_remote_description(&offer, RTCSdpType::Offer).await?; 139 | 140 | let answer = webrtcredux.create_answer(None).await?; 141 | 142 | let mut gather_complete = webrtcredux.gathering_complete_promise().await?; 143 | 144 | webrtcredux.set_local_description(&answer, RTCSdpType::Answer).await?; 145 | 146 | let _ = gather_complete.recv().await; 147 | 148 | if let Ok(Some(local_desc)) = webrtcredux.local_description().await { 149 | let b64 = base64::encode(local_desc.to_string(LF)); 150 | clipboard_handle.set_contents(b64.clone()).expect("Failed to set clipboard contents"); 151 | println!("Base64 Session Description for the browser copied to the clipboard", ); 152 | println!("{}", b64); 153 | } else { 154 | println!("generate local_description failed!"); 155 | } 156 | 157 | tokio::signal::ctrl_c().await?; 158 | 159 | Ok(()) 160 | } 161 | -------------------------------------------------------------------------------- /examples/h264-browser/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Browser base64 Session Description
10 |
11 | 12 | Base64 Session Description from plugin
13 |
14 | 15 |
16 | 17 |
18 | 19 | Video
20 |
21 |
22 | 23 | Logs
24 |
25 | 26 | -------------------------------------------------------------------------------- /examples/h264-browser/www/main.js: -------------------------------------------------------------------------------- 1 | let pc = new RTCPeerConnection({ 2 | iceServers: [ 3 | { 4 | urls: 'stun:stun.comrex.com:3478' 5 | } 6 | ] 7 | }) 8 | let log = msg => { 9 | document.getElementById('div').innerHTML += msg + '
' 10 | } 11 | 12 | pc.ontrack = function (event) { 13 | var el = document.createElement(event.track.kind) 14 | el.srcObject = event.streams[0] 15 | el.autoplay = true 16 | el.controls = true 17 | 18 | document.getElementById('remoteVideos').appendChild(el) 19 | } 20 | 21 | pc.oniceconnectionstatechange = e => log(pc.iceConnectionState) 22 | pc.onicecandidate = event => { 23 | if (event.candidate === null) { 24 | document.getElementById('localSessionDescription').value = btoa(pc.localDescription.sdp) 25 | } 26 | } 27 | 28 | pc.addTransceiver('video', {'direction': 'sendrecv'}) 29 | pc.addTransceiver('audio', {'direction': 'sendrecv'}) 30 | 31 | pc.createOffer().then(d => pc.setLocalDescription(d)).catch(log) 32 | 33 | window.startSession = () => { 34 | let sd = document.getElementById('remoteSessionDescription').value 35 | if (sd === '') { 36 | return alert('Session Description must not be empty') 37 | } 38 | 39 | try { 40 | pc.setRemoteDescription(new RTCSessionDescription({type: 'answer', sdp: atob(sd)})) 41 | } catch (e) { 42 | alert(e) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/h264-browser/www/style.css: -------------------------------------------------------------------------------- 1 | textarea { 2 | width: 75%; 3 | min-height: 100px; 4 | } 5 | 6 | :root { 7 | color-scheme: dark light; 8 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use gst::glib; 2 | pub mod webrtcredux; 3 | pub use crate::webrtcredux::*; 4 | 5 | fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { 6 | webrtcredux::register(plugin)?; 7 | Ok(()) 8 | } 9 | 10 | gst::plugin_define!( 11 | webrtcredux, 12 | env!("CARGO_PKG_DESCRIPTION"), 13 | plugin_init, 14 | concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")), 15 | "MIT/X11", 16 | env!("CARGO_PKG_NAME"), 17 | env!("CARGO_PKG_NAME"), 18 | env!("CARGO_PKG_REPOSITORY"), 19 | env!("BUILD_REL_DATE") 20 | ); 21 | -------------------------------------------------------------------------------- /src/webrtcredux/imp.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::pin::Pin; 3 | use std::str::FromStr; 4 | use std::sync::{Arc, Mutex}; 5 | use futures::Future; 6 | use futures::executor::block_on; 7 | use tokio::sync::{Mutex as AsyncMutex, oneshot}; 8 | 9 | use anyhow::{Context, Error}; 10 | use gst::{debug, error, info, fixme, ErrorMessage, glib, prelude::*, traits::{ElementExt, GstObjectExt}, EventView}; 11 | use gst_video::subclass::prelude::*; 12 | use interceptor::registry::Registry; 13 | use once_cell::sync::Lazy; 14 | use strum_macros::EnumString; 15 | use tokio::runtime::{self, Handle}; 16 | use webrtc::api::{API, APIBuilder}; 17 | use webrtc::api::interceptor_registry::register_default_interceptors; 18 | use webrtc::api::media_engine::{MediaEngine, MIME_TYPE_G722, MIME_TYPE_H264, MIME_TYPE_OPUS, MIME_TYPE_PCMA, MIME_TYPE_PCMU, MIME_TYPE_VP8, MIME_TYPE_VP9}; 19 | pub use webrtc::data_channel::RTCDataChannel; 20 | pub use webrtc::data_channel::data_channel_init::RTCDataChannelInit; 21 | pub use webrtc::ice_transport::ice_candidate::{RTCIceCandidate, RTCIceCandidateInit}; 22 | pub use webrtc::ice_transport::ice_connection_state::RTCIceConnectionState; 23 | use webrtc::ice_transport::ice_gatherer::{OnLocalCandidateHdlrFn, OnICEGathererStateChangeHdlrFn}; 24 | pub use webrtc::ice_transport::ice_gatherer_state::RTCIceGathererState; 25 | pub use webrtc::ice_transport::ice_server::RTCIceServer; 26 | use webrtc::peer_connection::configuration::RTCConfiguration; 27 | pub use webrtc::peer_connection::offer_answer_options::RTCAnswerOptions; 28 | pub use webrtc::peer_connection::offer_answer_options::RTCOfferOptions; 29 | use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; 30 | use webrtc::peer_connection::{RTCPeerConnection, OnNegotiationNeededHdlrFn, OnICEConnectionStateChangeHdlrFn, OnPeerConnectionStateChangeHdlrFn}; 31 | pub use webrtc::peer_connection::policy::bundle_policy::RTCBundlePolicy; 32 | pub use webrtc::peer_connection::policy::sdp_semantics::RTCSdpSemantics; 33 | pub use webrtc::peer_connection::sdp::sdp_type::RTCSdpType; 34 | use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; 35 | pub use webrtc::rtp_transceiver::{RTCRtpTransceiverInit, RTCRtpTransceiver}; 36 | pub use webrtc::rtp_transceiver::rtp_codec::{RTCRtpCodecCapability, RTPCodecType}; 37 | use webrtc::track::track_local::TrackLocal; 38 | use webrtc::track::track_local::track_local_static_sample::TrackLocalStaticSample; 39 | use crate::sdp::LineEnding; 40 | use crate::webrtcredux::sender::WebRtcReduxSender; 41 | 42 | use super::sdp::SDP; 43 | 44 | pub static CAT: Lazy = Lazy::new(|| { 45 | gst::DebugCategory::new( 46 | "webrtcredux", 47 | gst::DebugColorFlags::empty(), 48 | Some("WebRTC Video and Audio Transmitter"), 49 | ) 50 | }); 51 | 52 | static RUNTIME: Lazy = Lazy::new(|| { 53 | runtime::Builder::new_multi_thread() 54 | .enable_all() 55 | .build() 56 | .unwrap() 57 | }); 58 | 59 | pub type OnAllTracksAddedFn = Box Pin + Send + 'static>> + Send + Sync>; 60 | 61 | #[derive(Debug, PartialEq, Eq, EnumString, Clone, Copy)] 62 | enum MediaType { 63 | #[strum( 64 | ascii_case_insensitive, 65 | serialize = "video/H264", 66 | serialize = "video/x-h264" 67 | )] 68 | H264, 69 | #[strum(ascii_case_insensitive, serialize = "video/x-vp8")] 70 | VP8, 71 | #[strum(ascii_case_insensitive, serialize = "video/x-vp9")] 72 | VP9, 73 | #[strum( 74 | ascii_case_insensitive, 75 | serialize = "audio/opus", 76 | serialize = "audio/x-opus" 77 | )] 78 | Opus, 79 | #[strum(ascii_case_insensitive, serialize = "audio/G722")] 80 | G722, 81 | #[strum( 82 | ascii_case_insensitive, 83 | serialize = "audio/PCMU", 84 | serialize = "audio/x-mulaw" 85 | )] 86 | Mulaw, 87 | #[strum( 88 | ascii_case_insensitive, 89 | serialize = "audio/PCMA", 90 | serialize = "audio/x-alaw" 91 | )] 92 | Alaw, 93 | } 94 | 95 | impl MediaType { 96 | fn webrtc_mime(self) -> &'static str { 97 | match self { 98 | MediaType::H264 => MIME_TYPE_H264, 99 | MediaType::VP8 => MIME_TYPE_VP8, 100 | MediaType::VP9 => MIME_TYPE_VP9, 101 | MediaType::Opus => MIME_TYPE_OPUS, 102 | MediaType::G722 => MIME_TYPE_G722, 103 | MediaType::Mulaw => MIME_TYPE_PCMU, 104 | MediaType::Alaw => MIME_TYPE_PCMA, 105 | } 106 | } 107 | } 108 | 109 | #[derive(Debug, Clone)] 110 | struct InputStream { 111 | sink_pad: gst::GhostPad, 112 | sender: Option, 113 | } 114 | 115 | pub fn make_element(element: &str) -> Result { 116 | gst::ElementFactory::make(element) 117 | .build() 118 | .with_context(|| format!("Failed to make element {}", element)) 119 | } 120 | 121 | impl InputStream { 122 | fn prepare(&mut self, element: &super::WebRtcRedux) -> Result<(), Error> { 123 | let sender = WebRtcReduxSender::default(); 124 | 125 | element.add(&sender).expect("Failed to add sender element"); 126 | 127 | element 128 | .sync_children_states() 129 | .with_context(|| format!("Linking input stream {}", self.sink_pad.name()))?; 130 | 131 | self.sink_pad 132 | .set_target(Some(&sender.static_pad("sink").unwrap())) 133 | .unwrap(); 134 | 135 | self.sender = Some(sender); 136 | 137 | Ok(()) 138 | } 139 | 140 | fn unprepare(&mut self, element: &super::WebRtcRedux) { 141 | self.sink_pad.set_target(None::<&gst::Pad>).unwrap(); 142 | 143 | if let Some(sender) = self.sender.take() { 144 | element.remove(&sender).unwrap(); 145 | sender.set_state(gst::State::Null).unwrap(); 146 | } 147 | } 148 | } 149 | 150 | struct WebRtcState { 151 | api: API, 152 | peer_connection: Option 153 | } 154 | 155 | impl Default for WebRtcState { 156 | fn default() -> Self { 157 | let mut media_engine = MediaEngine::default(); 158 | media_engine.register_default_codecs().expect("Failed to register default codecs"); 159 | let mut registry = Registry::new(); 160 | registry = register_default_interceptors(registry, &mut media_engine) 161 | .expect("Failed to register default interceptors"); 162 | 163 | WebRtcState { 164 | api: APIBuilder::new() 165 | .with_media_engine(media_engine) 166 | .with_interceptor_registry(registry) 167 | .build(), 168 | peer_connection: Default::default() 169 | } 170 | } 171 | } 172 | 173 | #[derive(Default)] 174 | struct State { 175 | video_state: HashMap, 176 | next_video_pad_id: usize, 177 | audio_state: HashMap, 178 | next_audio_pad_id: usize, 179 | streams: HashMap, 180 | handle: Option, 181 | on_all_tracks_added_send: Option>, 182 | on_all_tracks_added: Option>, 183 | on_peer_connection_send: Arc>>>>, 184 | on_peer_connection_fn: Arc>>, 185 | tracks: usize 186 | } 187 | 188 | struct WebRtcSettings { 189 | config: Option, 190 | } 191 | 192 | impl Default for WebRtcSettings { 193 | fn default() -> Self { 194 | WebRtcSettings { 195 | config: Some(RTCConfiguration::default()), 196 | } 197 | } 198 | } 199 | 200 | #[derive(Default)] 201 | pub struct WebRtcRedux { 202 | state: Mutex, 203 | webrtc_state: Arc>, 204 | webrtc_settings: Mutex, 205 | } 206 | 207 | impl WebRtcRedux { 208 | fn prepare(&self, element: &super::WebRtcRedux) -> Result<(), Error> { 209 | debug!(CAT, obj: element, "preparing"); 210 | 211 | self.state 212 | .lock() 213 | .unwrap() 214 | .streams 215 | .iter_mut() 216 | .try_for_each(|(_, stream)| stream.prepare(element))?; 217 | 218 | Ok(()) 219 | } 220 | 221 | fn unprepare(&self, element: &super::WebRtcRedux) -> Result<(), Error> { 222 | info!(CAT, obj: element, "unpreparing"); 223 | 224 | let mut state = self.state.lock().unwrap(); 225 | 226 | state 227 | .streams 228 | .iter_mut() 229 | .for_each(|(_, stream)| stream.unprepare(element)); 230 | Ok(()) 231 | } 232 | 233 | pub fn add_ice_servers(&self, mut ice_server: Vec) { 234 | let mut webrtc_settings = self.webrtc_settings.lock().unwrap(); 235 | 236 | match webrtc_settings.config { 237 | Some(ref mut config) => { 238 | config.ice_servers.append(&mut ice_server); 239 | } 240 | None => { 241 | error!(CAT, "Trying to add ice servers after starting"); 242 | } 243 | } 244 | } 245 | 246 | pub fn set_bundle_policy(&self, bundle_policy: RTCBundlePolicy) { 247 | let mut webrtc_settings = self.webrtc_settings.lock().unwrap(); 248 | 249 | match webrtc_settings.config { 250 | Some(ref mut config) => { 251 | config.bundle_policy = bundle_policy; 252 | } 253 | None => { 254 | error!(CAT, "Trying to set bundle policy after starting"); 255 | } 256 | } 257 | } 258 | 259 | fn sink_event(&self, pad: &gst::Pad, element: &super::WebRtcRedux, event: gst::Event) -> bool { 260 | if let EventView::Caps(caps) = event.view() { 261 | self.create_track(&pad.name(), caps); 262 | } 263 | gst::Pad::event_default(pad, Some(element), event) 264 | } 265 | 266 | fn create_track(&self, name: &str, caps: &gst::event::Caps) { 267 | let name_parts = name.split('_').collect::>(); 268 | let id: usize = name_parts[1].parse().unwrap(); 269 | 270 | let caps = caps.structure().unwrap().get::("caps").unwrap(); 271 | let structure = caps.structure(0).unwrap(); 272 | let mime = structure.name(); 273 | let duration = if name.starts_with("video") { 274 | let framerate = structure.get::("framerate").unwrap().0; 275 | Some(gst::ClockTime::from_mseconds(((*framerate.denom() as f64 / *framerate.numer() as f64) * 1000.0).round() as u64)) 276 | } else { 277 | None 278 | }; 279 | 280 | // TODO: Clean up 281 | let stream_id = if name.starts_with("video") { 282 | let state = self.state.lock().unwrap(); 283 | let value = state.video_state.get(&id); 284 | if let Some(value) = value { 285 | value.to_owned() 286 | } else { 287 | fixme!(CAT, "Using pad name as stream_id for video pad {}, consider setting before pipeline starts", name); 288 | format!("video_{}", id) 289 | } 290 | } else { 291 | let state = self.state.lock().unwrap(); 292 | let value = state.audio_state.get(&id); 293 | if let Some(value) = value { 294 | value.to_owned() 295 | } else { 296 | fixme!(CAT, "Using pad name as stream_id for video pad {}, consider setting before pipeline starts", name); 297 | format!("audio_{}", id) 298 | } 299 | }; 300 | 301 | let track = Arc::new(TrackLocalStaticSample::new( 302 | RTCRtpCodecCapability { 303 | mime_type: MediaType::from_str(mime).expect("Failed to parse mime type").webrtc_mime().to_string(), 304 | ..RTCRtpCodecCapability::default() 305 | }, 306 | name_parts[0].to_string(), 307 | stream_id 308 | )); 309 | 310 | let webrtc_state = self.webrtc_state.clone(); 311 | let track_arc = track.clone(); 312 | let handle = self.runtime_handle(); 313 | let inner = handle.clone(); 314 | let rtp_sender = block_on(async move { 315 | handle.spawn_blocking(move || { 316 | inner.block_on(async move { 317 | webrtc_state.lock().await.peer_connection.as_ref().unwrap().add_track(Arc::clone(&track_arc) as Arc).await 318 | }) 319 | }).await 320 | }).unwrap().unwrap(); 321 | 322 | self.runtime_handle().spawn(async move { 323 | let mut rtcp_buf = vec![0u8; 1500]; 324 | while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {} 325 | anyhow::Result::<()>::Ok(()) 326 | }); 327 | 328 | let media_type = match name_parts[0] { 329 | "video" => crate::webrtcredux::sender::MediaType::Video, 330 | "audio" => crate::webrtcredux::sender::MediaType::Audio, 331 | _ => unreachable!() 332 | }; 333 | 334 | // Moving this out of the add_info call fixed a lockup, I'm not gonna question why 335 | let handle = self.runtime_handle(); 336 | let (tx, rx) = oneshot::channel::<()>(); 337 | self.state.lock().unwrap().on_peer_connection_send.lock().unwrap().get_or_insert(vec![]).push(tx); 338 | self.state.lock().unwrap().streams.get(name).unwrap().sender.as_ref().unwrap().add_info(track, handle, media_type, duration, rx); 339 | 340 | self.state.lock().unwrap().tracks += 1; 341 | { 342 | let mut state = self.state.lock().unwrap(); 343 | if state.tracks == state.next_audio_pad_id + state.next_video_pad_id { 344 | debug!(CAT, "All {} tracks added", state.tracks); 345 | state.on_all_tracks_added_send.take().unwrap().send(()).unwrap(); 346 | } 347 | } 348 | } 349 | 350 | pub fn set_stream_id(&self, pad_name: &str, stream_id: &str) -> Result<(), ErrorMessage> { 351 | let split = pad_name.split('_').collect::>(); 352 | if split.len() != 2 { 353 | return Err(gst::error_msg!( 354 | gst::ResourceError::NotFound, 355 | [&format!("Pad with name '{}' is invalid", pad_name)] 356 | )); 357 | } 358 | 359 | let id: usize = match split[1].parse() { 360 | Ok(val) => val, 361 | Err(_) => { 362 | return Err(gst::error_msg!( 363 | gst::ResourceError::NotFound, 364 | [&format!("Couldn't parse '{}' into number", split[1])] 365 | )); 366 | } 367 | }; 368 | 369 | match split[0] { 370 | "video" => { 371 | if !self 372 | .state 373 | .lock() 374 | .unwrap() 375 | .video_state 376 | .contains_key(&id) 377 | { 378 | return Err(gst::error_msg!( 379 | gst::ResourceError::NotFound, 380 | [&format!("Invalid ID: {}", id)] 381 | )); 382 | } 383 | 384 | self.state.lock().unwrap() 385 | .video_state 386 | .insert(id, stream_id.to_string()); 387 | 388 | Ok(()) 389 | } 390 | "audio" => { 391 | if !self 392 | .state 393 | .lock() 394 | .unwrap() 395 | .audio_state 396 | .contains_key(&id) 397 | { 398 | return Err(gst::error_msg!( 399 | gst::ResourceError::NotFound, 400 | [&format!("Invalid ID: {}", id)] 401 | )); 402 | } 403 | 404 | self.state 405 | .lock() 406 | .unwrap() 407 | .audio_state 408 | .insert(id, stream_id.to_string()); 409 | 410 | Ok(()) 411 | } 412 | _ => Err(gst::error_msg!( 413 | gst::ResourceError::NotFound, 414 | [&format!("Pad with type '{}' not found", split[0])] 415 | )), 416 | } 417 | } 418 | 419 | pub async fn add_transceiver_from_kind( 420 | &self, 421 | codec_type: RTPCodecType, 422 | init_params: &[RTCRtpTransceiverInit] 423 | ) -> Result, ErrorMessage> { 424 | let webrtc_state = self.webrtc_state.lock().await; 425 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 426 | 427 | match peer_connection.add_transceiver_from_kind(codec_type, init_params).await 428 | { 429 | Ok(res) => Ok(res), 430 | Err(e) => Err(gst::error_msg!( 431 | gst::ResourceError::Failed, 432 | [&format!("Failed to create transceiver: {:?}", e)] 433 | )), 434 | } 435 | } 436 | 437 | pub async fn gathering_complete_promise(&self) -> Result, ErrorMessage> { 438 | let webrtc_state = self.webrtc_state.lock().await; 439 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 440 | 441 | Ok(peer_connection.gathering_complete_promise().await) 442 | } 443 | 444 | pub async fn create_offer( 445 | &self, 446 | options: Option, 447 | ) -> Result { 448 | let webrtc_state = self.webrtc_state.lock().await; 449 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 450 | 451 | match peer_connection.create_offer(options).await { 452 | Ok(res) => Ok(SDP::from_str(&res.sdp).unwrap()), 453 | Err(e) => Err(gst::error_msg!( 454 | gst::ResourceError::Failed, 455 | [&format!("Failed to create offer: {:?}", e)] 456 | )), 457 | } 458 | } 459 | 460 | pub async fn create_answer( 461 | &self, 462 | options: Option, 463 | ) -> Result { 464 | let webrtc_state = self.webrtc_state.lock().await; 465 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 466 | 467 | match peer_connection.create_answer(options).await { 468 | Ok(res) => Ok(SDP::from_str(&res.sdp).unwrap()), 469 | Err(e) => Err(gst::error_msg!( 470 | gst::ResourceError::Failed, 471 | [&format!("Failed to create answer: {:?}", e)] 472 | )), 473 | } 474 | } 475 | 476 | pub async fn local_description(&self) -> Result, ErrorMessage> { 477 | let webrtc_state = self.webrtc_state.lock().await; 478 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 479 | 480 | match peer_connection.local_description().await { 481 | None => Ok(None), 482 | Some(res) => Ok(Some(SDP::from_str(&res.sdp).unwrap())) 483 | } 484 | } 485 | 486 | pub async fn set_local_description(&self, sdp: &SDP, sdp_type: RTCSdpType) -> Result<(), ErrorMessage> { 487 | let webrtc_state = self.webrtc_state.lock().await; 488 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 489 | 490 | let mut default = RTCSessionDescription::default(); 491 | default.sdp = sdp.to_string(LineEnding::CRLF); 492 | default.sdp_type = sdp_type; 493 | 494 | if let Err(e) = peer_connection.set_local_description(default).await { 495 | return Err(gst::error_msg!( 496 | gst::ResourceError::Failed, 497 | [&format!("Failed to set local description: {:?}", e)] 498 | )); 499 | } 500 | 501 | Ok(()) 502 | } 503 | 504 | pub async fn remote_description(&self) -> Result, ErrorMessage> { 505 | let webrtc_state = self.webrtc_state.lock().await; 506 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 507 | 508 | match peer_connection.remote_description().await { 509 | None => Ok(None), 510 | Some(res) => Ok(Some(SDP::from_str(&res.sdp).unwrap())) 511 | } 512 | } 513 | 514 | pub async fn set_remote_description(&self, sdp: &SDP, sdp_type: RTCSdpType) -> Result<(), ErrorMessage> { 515 | let webrtc_state = self.webrtc_state.lock().await; 516 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 517 | 518 | let mut default = RTCSessionDescription::default(); 519 | default.sdp = sdp.to_string(LineEnding::CRLF); 520 | default.sdp_type = sdp_type; 521 | 522 | if let Err(e) = peer_connection.set_remote_description(default).await { 523 | return Err(gst::error_msg!( 524 | gst::ResourceError::Failed, 525 | [&format!("Failed to set remote description: {:?}", e)] 526 | )); 527 | } 528 | 529 | Ok(()) 530 | } 531 | 532 | pub async fn on_negotiation_needed(&self, f: OnNegotiationNeededHdlrFn) -> Result<(), ErrorMessage> 533 | { 534 | let webrtc_state = self.webrtc_state.lock().await; 535 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 536 | 537 | peer_connection 538 | .on_negotiation_needed(Box::new(f)); 539 | 540 | Ok(()) 541 | } 542 | 543 | pub async fn on_ice_candidate(&self, f: OnLocalCandidateHdlrFn) -> Result<(), ErrorMessage> 544 | { 545 | let webrtc_state = self.webrtc_state.lock().await; 546 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 547 | 548 | peer_connection 549 | .on_ice_candidate(Box::new(f)); 550 | 551 | Ok(()) 552 | } 553 | 554 | pub async fn on_ice_gathering_state_change(&self, f: OnICEGathererStateChangeHdlrFn) -> Result<(), ErrorMessage> 555 | { 556 | let webrtc_state = self.webrtc_state.lock().await; 557 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 558 | 559 | peer_connection 560 | .on_ice_gathering_state_change(Box::new(f)); 561 | 562 | Ok(()) 563 | } 564 | 565 | pub async fn on_ice_connection_state_change(&self, f: OnICEConnectionStateChangeHdlrFn) -> Result<(), ErrorMessage> 566 | { 567 | let webrtc_state = self.webrtc_state.lock().await; 568 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 569 | 570 | peer_connection 571 | .on_ice_connection_state_change(Box::new(f)); 572 | 573 | Ok(()) 574 | } 575 | 576 | pub fn on_peer_connection_state_change(&self, f: OnPeerConnectionStateChangeHdlrFn) -> Result<(), ErrorMessage> { 577 | // peer_connection 578 | // .on_peer_connection_state_change(Box::new(f)); 579 | let _ = self.state.lock().unwrap().on_peer_connection_fn.lock().unwrap().insert(f); 580 | 581 | Ok(()) 582 | } 583 | 584 | pub async fn add_ice_candidate( 585 | &self, 586 | candidate: RTCIceCandidateInit, 587 | ) -> Result<(), ErrorMessage> { 588 | let webrtc_state = self.webrtc_state.lock().await; 589 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 590 | 591 | if let Err(e) = peer_connection.add_ice_candidate(candidate).await { 592 | return Err(gst::error_msg!( 593 | gst::ResourceError::Failed, 594 | [&format!("Failed to add ICE candidate: {:?}", e)] 595 | )); 596 | } 597 | 598 | Ok(()) 599 | } 600 | 601 | pub async fn create_data_channel(&self, 602 | name: &str, 603 | init_params: Option 604 | ) -> Result, ErrorMessage> { 605 | let webrtc_state = self.webrtc_state.lock().await; 606 | let peer_connection = WebRtcRedux::get_peer_connection(&webrtc_state)?; 607 | 608 | match peer_connection.create_data_channel(name, init_params).await { 609 | Ok(res) => Ok(res), 610 | Err(e) => { 611 | Err(gst::error_msg!( 612 | gst::ResourceError::Failed, 613 | [&format!("Failed to create data channel: {:?}", e)] 614 | )) 615 | } 616 | } 617 | } 618 | 619 | pub fn set_tokio_runtime( 620 | &self, 621 | handle: Handle 622 | ) { 623 | let _ = self.state.lock().unwrap().handle.insert(handle); 624 | } 625 | 626 | pub async fn wait_for_all_tracks(&self) { 627 | let all = self.state.lock().unwrap().on_all_tracks_added.take().unwrap(); 628 | all.await.unwrap(); 629 | } 630 | 631 | fn runtime_handle(&self) -> Handle { 632 | self.state.lock().unwrap().handle.as_ref().unwrap_or(RUNTIME.handle()).clone() 633 | } 634 | 635 | fn get_peer_connection(state: &WebRtcState) -> Result<&RTCPeerConnection, ErrorMessage> { 636 | match &state.peer_connection { 637 | Some(conn) => Ok(conn), 638 | None => { 639 | Err(gst::error_msg!( 640 | gst::ResourceError::Failed, 641 | ["Peer connection is not set, make sure plugin is started"] 642 | )) 643 | } 644 | } 645 | } 646 | } 647 | 648 | #[glib::object_subclass] 649 | impl ObjectSubclass for WebRtcRedux { 650 | const NAME: &'static str = "WebRtcRedux"; 651 | type Type = super::WebRtcRedux; 652 | type ParentType = gst::Bin; 653 | } 654 | 655 | impl ElementImpl for WebRtcRedux { 656 | fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { 657 | static ELEMENT_METADATA: Lazy = Lazy::new(|| { 658 | gst::subclass::ElementMetadata::new( 659 | "WebRTC Broadcast Engine", 660 | "Sink/Video/Audio", 661 | "Broadcasts encoded video and audio", 662 | "Jack Hogan; Lorenzo Rizzotti ", 663 | ) 664 | }); 665 | 666 | Some(&*ELEMENT_METADATA) 667 | } 668 | 669 | fn pad_templates() -> &'static [gst::PadTemplate] { 670 | static PAD_TEMPLATES: Lazy> = Lazy::new(|| { 671 | let caps = gst::Caps::builder_full() 672 | .structure(gst::Structure::builder("video/x-h264").field("stream-format", "byte-stream").field("profile", "baseline").build()) 673 | .structure(gst::Structure::builder("video/x-vp8").build()) 674 | .structure(gst::Structure::builder("video/x-vp9").build()) 675 | .build(); 676 | let video_pad_template = gst::PadTemplate::new( 677 | "video_%u", 678 | gst::PadDirection::Sink, 679 | gst::PadPresence::Request, 680 | &caps, 681 | ) 682 | .unwrap(); 683 | 684 | let caps = gst::Caps::builder_full() 685 | .structure(gst::Structure::builder("audio/x-opus").build()) 686 | .structure(gst::Structure::builder("audio/G722").build()) 687 | .structure(gst::Structure::builder("audio/x-mulaw").build()) 688 | .structure(gst::Structure::builder("audio/x-alaw").build()) 689 | .build(); 690 | let audio_pad_template = gst::PadTemplate::new( 691 | "audio_%u", 692 | gst::PadDirection::Sink, 693 | gst::PadPresence::Request, 694 | &caps, 695 | ) 696 | .unwrap(); 697 | 698 | vec![video_pad_template, audio_pad_template] 699 | }); 700 | 701 | PAD_TEMPLATES.as_ref() 702 | } 703 | 704 | fn request_new_pad( 705 | &self, 706 | templ: &gst::PadTemplate, 707 | _name: Option<&str>, 708 | _caps: Option<&gst::Caps>, 709 | ) -> Option { 710 | let element = self.obj(); 711 | if element.current_state() > gst::State::Ready { 712 | error!(CAT, "element pads can only be requested before starting"); 713 | return None; 714 | } 715 | 716 | let mut state = self.state.lock().unwrap(); 717 | 718 | let name = if templ.name().starts_with("video_") { 719 | let name = format!("video_{}", state.next_video_pad_id); 720 | state.next_video_pad_id += 1; 721 | name 722 | } else { 723 | let name = format!("audio_{}", state.next_audio_pad_id); 724 | state.next_audio_pad_id += 1; 725 | name 726 | }; 727 | 728 | let sink_pad = gst::GhostPad::builder_with_template(templ, Some(name.as_str())) 729 | .event_function(|pad, parent, event| { 730 | WebRtcRedux::catch_panic_pad_function( 731 | parent, 732 | || false, 733 | |this| this.sink_event(pad.upcast_ref(), &this.obj(), event), 734 | ) 735 | }) 736 | .build(); 737 | 738 | sink_pad.set_active(true).unwrap(); 739 | sink_pad.use_fixed_caps(); 740 | element.add_pad(&sink_pad).unwrap(); 741 | 742 | state.streams.insert( 743 | name, 744 | InputStream { 745 | sink_pad: sink_pad.clone(), 746 | sender: None, 747 | }, 748 | ); 749 | 750 | Some(sink_pad.upcast()) 751 | } 752 | 753 | fn change_state( 754 | &self, 755 | transition: gst::StateChange, 756 | ) -> Result { 757 | let element = self.obj(); 758 | if let gst::StateChange::ReadyToPaused = transition { 759 | if let Err(err) = self.prepare(&element) { 760 | gst::element_error!( 761 | element, 762 | gst::StreamError::Failed, 763 | ["Failed to prepare: {}", err] 764 | ); 765 | return Err(gst::StateChangeError); 766 | } 767 | } 768 | 769 | let mut ret = self.parent_change_state(transition); 770 | 771 | match transition { 772 | gst::StateChange::NullToReady => { 773 | match self.webrtc_settings.lock().unwrap().config.take() { 774 | Some(config) => { 775 | //Acquiring lock before the future instead of cloning because we need to return a value which is dropped with it. 776 | let webrtc_state = self.webrtc_state.clone(); 777 | let on_pc_send = self.state.lock().unwrap().on_peer_connection_send.clone(); 778 | let on_pc_fn = self.state.lock().unwrap().on_peer_connection_fn.clone(); 779 | 780 | { 781 | let (tx, rx) = oneshot::channel(); 782 | let mut state = self.state.lock().unwrap(); 783 | let _ = state.on_all_tracks_added_send.insert(tx); 784 | let _ = state.on_all_tracks_added.insert(rx); 785 | } 786 | 787 | let handle = self.runtime_handle(); 788 | let inner = handle.clone(); 789 | 790 | block_on(async move { 791 | handle.spawn_blocking(move || { 792 | inner.block_on(async move { 793 | let mut webrtc_state = webrtc_state.lock().await; 794 | //TODO: Fix mutex with an async safe mutex 795 | let peer_connection = webrtc_state 796 | .api 797 | .new_peer_connection(config) 798 | .await 799 | .map_err(|e| { 800 | gst::error_msg!( 801 | gst::ResourceError::Failed, 802 | ["Failed to create PeerConnection: {:?}", e] 803 | ) 804 | }); 805 | 806 | match peer_connection { 807 | Ok(conn) => { 808 | conn.on_peer_connection_state_change(Box::new(move |state| { 809 | // Notify sender elements when peer is connected 810 | if state == RTCPeerConnectionState::Connected { 811 | if let Some(vec) = on_pc_send.lock().unwrap().take() { 812 | for send in vec.into_iter() { 813 | send.send(()).unwrap(); 814 | } 815 | } 816 | } 817 | 818 | // Run user-defined callback function if it exists 819 | let mut on_pc_fn = on_pc_fn.lock().unwrap(); 820 | if on_pc_fn.is_some() {on_pc_fn.as_mut().unwrap()(state)} else {Box::pin(async {})} 821 | })); 822 | 823 | let _ = webrtc_state.peer_connection.insert(conn); 824 | 825 | Ok(()) 826 | }, 827 | Err(e) => Err(e) 828 | } 829 | }).unwrap(); 830 | }).await 831 | }).unwrap(); 832 | } 833 | None => { 834 | return Err(gst::StateChangeError); 835 | } 836 | } 837 | } 838 | gst::StateChange::PausedToReady => { 839 | if let Err(err) = self.unprepare(&element) { 840 | gst::element_error!( 841 | element, 842 | gst::StreamError::Failed, 843 | ["Failed to unprepare: {}", err] 844 | ); 845 | return Err(gst::StateChangeError); 846 | } 847 | } 848 | gst::StateChange::ReadyToNull => { 849 | //Acquiring lock before the future instead of cloning because we need to return a value which is dropped with it. 850 | let webrtc_state = self.webrtc_state.clone(); 851 | 852 | let handle = self.runtime_handle(); 853 | let inner = handle.clone(); 854 | 855 | block_on(async move { 856 | handle.spawn_blocking(move || { 857 | inner.block_on(async move { 858 | let mut webrtc_state = webrtc_state.lock().await; 859 | //TODO: Fix mutex with an async safe mutex 860 | if let Some(conn) = webrtc_state.peer_connection.take() { 861 | conn.close().await 862 | } else { 863 | Ok(()) 864 | } 865 | }) 866 | }).await 867 | }).unwrap().unwrap(); 868 | } 869 | gst::StateChange::ReadyToPaused => { 870 | ret = Ok(gst::StateChangeSuccess::NoPreroll); 871 | } 872 | _ => (), 873 | } 874 | 875 | ret 876 | } 877 | } 878 | 879 | //TODO: Add signals 880 | impl ObjectImpl for WebRtcRedux {} 881 | 882 | impl GstObjectImpl for WebRtcRedux {} 883 | 884 | impl BinImpl for WebRtcRedux {} -------------------------------------------------------------------------------- /src/webrtcredux/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use gst::glib; 4 | use gst::prelude::*; 5 | use gst::subclass::prelude::ObjectSubclassExt; 6 | use gst::ErrorMessage; 7 | 8 | mod sender; 9 | 10 | mod imp; 11 | 12 | pub use imp::*; 13 | use tokio::runtime::Handle; 14 | use webrtc::data_channel::RTCDataChannel; 15 | use webrtc::data_channel::data_channel_init::RTCDataChannelInit; 16 | use webrtc::ice_transport::ice_gatherer::OnICEGathererStateChangeHdlrFn; 17 | use webrtc::ice_transport::ice_gatherer::OnLocalCandidateHdlrFn; 18 | use webrtc::peer_connection::OnICEConnectionStateChangeHdlrFn; 19 | use webrtc::peer_connection::OnNegotiationNeededHdlrFn; 20 | use webrtc::peer_connection::OnPeerConnectionStateChangeHdlrFn; 21 | use webrtc::rtp_transceiver::RTCRtpTransceiverInit; 22 | use webrtc::rtp_transceiver::rtp_codec::RTPCodecType; 23 | 24 | use self::sdp::SDP; 25 | pub mod sdp; 26 | 27 | glib::wrapper! { 28 | pub struct WebRtcRedux(ObjectSubclass) @extends gst::Bin, gst::Element, gst::Object; 29 | } 30 | 31 | impl Default for WebRtcRedux { 32 | fn default() -> Self { 33 | glib::Object::new::(&[]) 34 | } 35 | } 36 | 37 | unsafe impl Send for WebRtcRedux {} 38 | unsafe impl Sync for WebRtcRedux {} 39 | 40 | //TODO: Add signal for those methods for compatibility with other programing languages 41 | impl WebRtcRedux { 42 | pub fn add_ice_servers(&self, ice_servers: Vec) { 43 | imp::WebRtcRedux::from_instance(self).add_ice_servers(ice_servers); 44 | } 45 | 46 | pub fn set_bundle_policy(&self, bundle_policy: RTCBundlePolicy) { 47 | imp::WebRtcRedux::from_instance(self).set_bundle_policy(bundle_policy); 48 | } 49 | 50 | pub fn set_stream_id(&self, pad_name: &str, stream_id: &str) -> Result<(), ErrorMessage> { 51 | imp::WebRtcRedux::from_instance(self).set_stream_id(pad_name, stream_id) 52 | } 53 | 54 | pub async fn add_transceiver( 55 | &self, 56 | codec_type: RTPCodecType, 57 | init_params: &[RTCRtpTransceiverInit]) -> Result, ErrorMessage> 58 | { 59 | imp::WebRtcRedux::from_instance(self) 60 | .add_transceiver_from_kind(codec_type, init_params) 61 | .await 62 | } 63 | 64 | pub async fn create_offer( 65 | &self, 66 | options: Option, 67 | ) -> Result { 68 | imp::WebRtcRedux::from_instance(self) 69 | .create_offer(options) 70 | .await 71 | } 72 | 73 | pub async fn gathering_complete_promise(&self) -> Result, ErrorMessage> { 74 | imp::WebRtcRedux::from_instance(self).gathering_complete_promise().await 75 | } 76 | 77 | pub async fn create_answer( 78 | &self, 79 | options: Option 80 | ) -> Result { 81 | imp::WebRtcRedux::from_instance(self) 82 | .create_answer(options) 83 | .await 84 | } 85 | 86 | pub async fn local_description(&self) -> Result, ErrorMessage> { 87 | imp::WebRtcRedux::from_instance(self).local_description().await 88 | } 89 | 90 | pub async fn set_local_description(&self, sdp: &SDP, sdp_type: RTCSdpType) -> Result<(), ErrorMessage> { 91 | imp::WebRtcRedux::from_instance(self) 92 | .set_local_description(sdp, sdp_type) 93 | .await 94 | } 95 | 96 | pub async fn remote_description(&self) -> Result, ErrorMessage> { 97 | imp::WebRtcRedux::from_instance(self).remote_description().await 98 | } 99 | 100 | pub async fn set_remote_description(&self, sdp: &SDP, sdp_type: RTCSdpType) -> Result<(), ErrorMessage> { 101 | imp::WebRtcRedux::from_instance(self) 102 | .set_remote_description(sdp, sdp_type) 103 | .await 104 | } 105 | 106 | pub async fn on_negotiation_needed(&self, f: OnNegotiationNeededHdlrFn) -> Result<(), ErrorMessage> 107 | { 108 | imp::WebRtcRedux::from_instance(self) 109 | .on_negotiation_needed(f) 110 | .await 111 | } 112 | 113 | pub async fn on_ice_candidate(&self, f: OnLocalCandidateHdlrFn) -> Result<(), ErrorMessage> 114 | { 115 | imp::WebRtcRedux::from_instance(self) 116 | .on_ice_candidate(f) 117 | .await 118 | } 119 | 120 | pub async fn on_ice_gathering_state_change(&self, f: OnICEGathererStateChangeHdlrFn) -> Result<(), ErrorMessage> 121 | { 122 | imp::WebRtcRedux::from_instance(self) 123 | .on_ice_gathering_state_change(f) 124 | .await 125 | } 126 | 127 | pub async fn on_ice_connection_state_change(&self, f: OnICEConnectionStateChangeHdlrFn) -> Result<(), ErrorMessage> 128 | { 129 | imp::WebRtcRedux::from_instance(self) 130 | .on_ice_connection_state_change(f) 131 | .await 132 | } 133 | 134 | pub fn on_peer_connection_state_change(&self, f: OnPeerConnectionStateChangeHdlrFn) -> Result<(), ErrorMessage> 135 | { 136 | imp::WebRtcRedux::from_instance(self) 137 | .on_peer_connection_state_change(f) 138 | } 139 | 140 | pub async fn add_ice_candidate( 141 | &self, 142 | candidate: RTCIceCandidateInit, 143 | ) -> Result<(), ErrorMessage> { 144 | imp::WebRtcRedux::from_instance(self) 145 | .add_ice_candidate(candidate) 146 | .await 147 | } 148 | 149 | pub async fn create_data_channel(&self, name: &str, init_params: Option) -> Result, ErrorMessage> { 150 | imp::WebRtcRedux::from_instance(self) 151 | .create_data_channel(name, init_params) 152 | .await 153 | } 154 | 155 | pub fn set_tokio_runtime(&self, handle: Handle) { 156 | imp::WebRtcRedux::from_instance(self).set_tokio_runtime(handle); 157 | } 158 | 159 | pub async fn wait_for_all_tracks(&self) { 160 | imp::WebRtcRedux::from_instance(self).wait_for_all_tracks().await; 161 | } 162 | } 163 | 164 | pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { 165 | gst::Element::register( 166 | Some(plugin), 167 | "webrtcredux", 168 | gst::Rank::None, 169 | WebRtcRedux::static_type(), 170 | ) 171 | } 172 | -------------------------------------------------------------------------------- /src/webrtcredux/sdp.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Debug, 3 | num::{IntErrorKind, ParseIntError}, 4 | str::FromStr, 5 | }; 6 | 7 | #[derive(Debug, Clone, Copy)] 8 | pub enum LineEnding { 9 | CRLF, 10 | LF 11 | } 12 | 13 | impl LineEnding { 14 | fn string(&self) -> &'static str { 15 | match self { 16 | LineEnding::CRLF => "\r\n", 17 | LineEnding::LF => "\n", 18 | } 19 | } 20 | } 21 | 22 | #[derive(Debug, PartialEq, Eq, Clone)] 23 | pub enum MediaProp { 24 | Title(String), 25 | Connection { 26 | net_type: NetworkType, 27 | address_type: AddressType, 28 | address: String, 29 | ttl: Option, 30 | num_addresses: Option, 31 | /// Optional suffix to previous data 32 | suffix: Option, 33 | }, 34 | Bandwidth { 35 | r#type: BandwidthType, 36 | bandwidth: usize, 37 | }, 38 | EncryptionKeys(EncryptionKeyMethod), 39 | Attribute { 40 | key: String, 41 | value: Option, 42 | }, 43 | } 44 | 45 | impl FromStr for MediaProp { 46 | type Err = ParseError; 47 | 48 | fn from_str(s: &str) -> Result { 49 | let (key, value) = content_from_line(s)?; 50 | let tokens = value.split(' ').collect::>(); 51 | 52 | match key { 53 | 'i' => Ok(MediaProp::Title(value)), 54 | 'c' => { 55 | let address_split = tokens[2].split('/').collect::>(); 56 | let (address, ttl, num_addresses) = get_options_from_address_split(address_split)?; 57 | 58 | let suffix = if tokens.len() > 3 { 59 | Some(tokens[3..].join(" ")) 60 | } else { 61 | None 62 | }; 63 | 64 | Ok(MediaProp::Connection { 65 | net_type: NetworkType::from_str(tokens[0])?, 66 | address_type: AddressType::from_str(tokens[1])?, 67 | address: address.to_string(), 68 | ttl, 69 | num_addresses, 70 | suffix, 71 | }) 72 | } 73 | 'b' => { 74 | let tokens = value.split(':').collect::>(); 75 | 76 | Ok(MediaProp::Bandwidth { 77 | r#type: BandwidthType::from_str(tokens[0])?, 78 | bandwidth: tokens[1].parse()?, 79 | }) 80 | } 81 | 'k' => Ok(MediaProp::EncryptionKeys(EncryptionKeyMethod::from_str( 82 | &value, 83 | )?)), 84 | 'a' => { 85 | let tokens = value.split(':').collect::>(); 86 | 87 | Ok(if tokens.len() > 1 { 88 | MediaProp::Attribute { 89 | key: tokens[0].to_string(), 90 | value: Some(tokens[1..].join(":")), 91 | } 92 | } else { 93 | MediaProp::Attribute { 94 | key: value, 95 | value: None, 96 | } 97 | }) 98 | } 99 | _ => Err(ParseError::UnknownToken(s.to_string())), 100 | } 101 | } 102 | } 103 | 104 | impl ToString for MediaProp { 105 | fn to_string(&self) -> String { 106 | match self { 107 | MediaProp::Title(title) => format!("i={title}"), 108 | MediaProp::Connection { 109 | net_type, 110 | address_type, 111 | address, 112 | ttl, 113 | num_addresses, 114 | suffix, 115 | } => { 116 | // TTL is required for IPv4, but also apparently the major browsers don't like to follow specs so we're gonna ignore that 117 | let mut address = if let Some(ttl) = ttl { 118 | format!("{address}/{}", ttl) 119 | } else { 120 | address.clone() 121 | }; 122 | 123 | if let Some(num_addresses) = num_addresses { 124 | address = format!("{address}/{num_addresses}") 125 | } 126 | 127 | build_connection_lines(net_type, address_type, suffix, &address) 128 | } 129 | MediaProp::Bandwidth { r#type, bandwidth } => { 130 | format!("b={}:{}", r#type.to_string(), bandwidth) 131 | } 132 | MediaProp::EncryptionKeys(method) => format!("k={}", method.to_string()), 133 | MediaProp::Attribute { key, value } => { 134 | if let Some(value) = value { 135 | format!("a={}:{}", key, value) 136 | } else { 137 | format!("a={key}") 138 | } 139 | } 140 | } 141 | } 142 | } 143 | 144 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 145 | pub enum MediaType { 146 | Audio, 147 | Video, 148 | Text, 149 | Application, 150 | } 151 | 152 | impl FromStr for MediaType { 153 | type Err = ParseError; 154 | 155 | fn from_str(s: &str) -> Result { 156 | match s { 157 | "audio" => Ok(MediaType::Audio), 158 | "video" => Ok(MediaType::Video), 159 | "text" => Ok(MediaType::Text), 160 | "application" => Ok(MediaType::Application), 161 | _ => Err(ParseError::UnknownToken(s.to_string())), 162 | } 163 | } 164 | } 165 | 166 | impl ToString for MediaType { 167 | fn to_string(&self) -> String { 168 | match self { 169 | MediaType::Audio => "audio", 170 | MediaType::Video => "video", 171 | MediaType::Text => "text", 172 | MediaType::Application => "application", 173 | } 174 | .to_string() 175 | } 176 | } 177 | 178 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 179 | pub enum NetworkType { 180 | Internet, 181 | } 182 | 183 | impl FromStr for NetworkType { 184 | type Err = ParseError; 185 | 186 | fn from_str(s: &str) -> Result { 187 | match s { 188 | "IN" => Ok(NetworkType::Internet), 189 | _ => Err(ParseError::UnknownToken(s.to_string())), 190 | } 191 | } 192 | } 193 | 194 | impl ToString for NetworkType { 195 | fn to_string(&self) -> String { 196 | match self { 197 | NetworkType::Internet => "IN", 198 | } 199 | .to_string() 200 | } 201 | } 202 | 203 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 204 | pub enum AddressType { 205 | IPv4, 206 | IPv6, 207 | } 208 | 209 | impl FromStr for AddressType { 210 | type Err = ParseError; 211 | 212 | fn from_str(s: &str) -> Result { 213 | match s { 214 | "IP4" => Ok(AddressType::IPv4), 215 | "IP6" => Ok(AddressType::IPv6), 216 | _ => Err(ParseError::UnknownToken(s.to_string())), 217 | } 218 | } 219 | } 220 | 221 | impl ToString for AddressType { 222 | fn to_string(&self) -> String { 223 | match self { 224 | AddressType::IPv4 => "IP4", 225 | AddressType::IPv6 => "IP6", 226 | } 227 | .to_string() 228 | } 229 | } 230 | 231 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 232 | pub enum BandwidthType { 233 | ConferenceTotal, 234 | ApplicationSpecific, 235 | } 236 | 237 | impl FromStr for BandwidthType { 238 | type Err = ParseError; 239 | 240 | fn from_str(s: &str) -> Result { 241 | match s { 242 | "CT" => Ok(BandwidthType::ConferenceTotal), 243 | "AS" => Ok(BandwidthType::ApplicationSpecific), 244 | _ => Err(ParseError::UnknownToken(s.to_string())), 245 | } 246 | } 247 | } 248 | 249 | impl ToString for BandwidthType { 250 | fn to_string(&self) -> String { 251 | match self { 252 | BandwidthType::ConferenceTotal => "CT", 253 | BandwidthType::ApplicationSpecific => "AS", 254 | } 255 | .to_string() 256 | } 257 | } 258 | 259 | #[derive(Debug, PartialEq, Eq, Clone)] 260 | pub struct TimeZoneAdjustment { 261 | time: usize, 262 | offset: String, 263 | } 264 | 265 | #[derive(Debug, PartialEq, Eq, Clone)] 266 | pub enum EncryptionKeyMethod { 267 | Clear(String), 268 | Base64(String), 269 | Uri(String), 270 | Prompt, 271 | } 272 | 273 | impl FromStr for EncryptionKeyMethod { 274 | type Err = ParseError; 275 | 276 | fn from_str(s: &str) -> Result { 277 | if s == "prompt" { 278 | return Ok(EncryptionKeyMethod::Prompt); 279 | } 280 | 281 | let split = s.split(':').collect::>(); 282 | let key = split[1..].join(":"); 283 | match split[0] { 284 | "clear" => Ok(EncryptionKeyMethod::Clear(key)), 285 | "base64" => Ok(EncryptionKeyMethod::Base64(key)), 286 | "uri" => Ok(EncryptionKeyMethod::Uri(key)), 287 | _ => Err(ParseError::UnknownToken(s.to_string())), 288 | } 289 | } 290 | } 291 | 292 | impl ToString for EncryptionKeyMethod { 293 | fn to_string(&self) -> String { 294 | match self { 295 | EncryptionKeyMethod::Clear(key) => format!("clear:{key}"), 296 | EncryptionKeyMethod::Base64(key) => format!("base64:{key}"), 297 | EncryptionKeyMethod::Uri(key) => format!("uri:{key}"), 298 | EncryptionKeyMethod::Prompt => "prompt".to_string(), 299 | } 300 | } 301 | } 302 | 303 | // https://datatracker.ietf.org/doc/html/rfc4566#section-2 304 | #[derive(Debug, PartialEq, Eq, Clone)] 305 | pub enum SdpProp { 306 | Version(u8), 307 | Origin { 308 | username: String, 309 | session_id: String, 310 | session_version: usize, 311 | net_type: NetworkType, 312 | address_type: AddressType, 313 | address: String, 314 | }, 315 | SessionName(String), 316 | SessionInformation(String), 317 | Uri(String), 318 | Email(String), 319 | Phone(String), 320 | Connection { 321 | net_type: NetworkType, 322 | address_type: AddressType, 323 | address: String, 324 | ttl: Option, 325 | num_addresses: Option, 326 | /// Optional suffix to previous data 327 | suffix: Option, 328 | }, 329 | Bandwidth { 330 | r#type: BandwidthType, 331 | bandwidth: usize, 332 | }, 333 | Timing { 334 | start: usize, 335 | stop: usize, 336 | }, 337 | /// Can be either numbers or numbers with time modifiers (d, h, m, s) so should be strings 338 | RepeatTimes { 339 | interval: String, 340 | active_duration: String, 341 | start_offsets: Vec, 342 | }, 343 | TimeZone(Vec), 344 | EncryptionKeys(EncryptionKeyMethod), 345 | Attribute { 346 | key: String, 347 | value: Option, 348 | }, 349 | Media { 350 | r#type: MediaType, 351 | ports: Vec, 352 | protocol: String, 353 | format: String, 354 | props: Vec, 355 | }, 356 | } 357 | 358 | impl FromStr for SdpProp { 359 | type Err = ParseError; 360 | 361 | fn from_str(s: &str) -> Result { 362 | let (key, value) = content_from_line(s)?; 363 | let tokens = value.split(' ').collect::>(); 364 | 365 | // TODO: Cut down on code copying from SDPProp to MediaProp 366 | match key { 367 | 'v' => Ok(SdpProp::Version(value.parse()?)), 368 | 'o' => Ok(SdpProp::Origin { 369 | username: tokens[0].to_string(), 370 | session_id: tokens[1].to_string(), 371 | session_version: tokens[2].parse()?, 372 | net_type: NetworkType::from_str(tokens[3])?, 373 | address_type: AddressType::from_str(tokens[4])?, 374 | address: tokens[5].to_string(), 375 | }), 376 | 's' => Ok(SdpProp::SessionName(value)), 377 | 'i' => Ok(SdpProp::SessionInformation(value)), 378 | 'u' => Ok(SdpProp::Uri(value)), 379 | 'e' => Ok(SdpProp::Email(value)), 380 | 'p' => Ok(SdpProp::Phone(value)), 381 | 'c' => { 382 | let address_split = tokens[2].split('/').collect::>(); 383 | let (address, ttl, num_addresses) = get_options_from_address_split(address_split)?; 384 | 385 | let suffix = if tokens.len() > 3 { 386 | Some(tokens[3..].join(" ")) 387 | } else { 388 | None 389 | }; 390 | 391 | Ok(SdpProp::Connection { 392 | net_type: NetworkType::from_str(tokens[0])?, 393 | address_type: AddressType::from_str(tokens[1])?, 394 | address: address.to_string(), 395 | ttl, 396 | num_addresses, 397 | suffix, 398 | }) 399 | } 400 | 'b' => { 401 | let tokens = value.split(':').collect::>(); 402 | 403 | Ok(SdpProp::Bandwidth { 404 | r#type: BandwidthType::from_str(tokens[0])?, 405 | bandwidth: tokens[1].parse()?, 406 | }) 407 | } 408 | 't' => Ok(SdpProp::Timing { 409 | start: tokens[0].parse()?, 410 | stop: tokens[1].parse()?, 411 | }), 412 | 'r' => Ok(SdpProp::RepeatTimes { 413 | interval: tokens[0].to_string(), 414 | active_duration: tokens[1].to_string(), 415 | start_offsets: tokens[2..] 416 | .iter() 417 | .map(|t| t.to_string()) 418 | .collect::>(), 419 | }), 420 | 'z' => { 421 | let mut adjustments = Vec::new(); 422 | for group in tokens.chunks(2) { 423 | adjustments.push(TimeZoneAdjustment { 424 | time: group[0].to_string().parse()?, 425 | offset: group[1].to_string(), 426 | }); 427 | } 428 | 429 | Ok(SdpProp::TimeZone(adjustments)) 430 | } 431 | 'k' => Ok(SdpProp::EncryptionKeys(EncryptionKeyMethod::from_str( 432 | &value, 433 | )?)), 434 | 'a' => { 435 | let tokens = value.split(':').collect::>(); 436 | 437 | Ok(if tokens.len() > 1 { 438 | SdpProp::Attribute { 439 | key: tokens[0].to_string(), 440 | value: Some(tokens[1..].join(":")), 441 | } 442 | } else { 443 | SdpProp::Attribute { 444 | key: value, 445 | value: None, 446 | } 447 | }) 448 | } 449 | // Media lines can have their own attributes, the entire media block will be passed in 450 | 'm' => { 451 | let lines = value.split('\n').collect::>(); 452 | let tokens = lines[0].split(' ').collect::>(); 453 | 454 | Ok(SdpProp::Media { 455 | r#type: MediaType::from_str(tokens[0])?, 456 | ports: tokens[1] 457 | .split('/') 458 | .map(|port| port.parse()) 459 | .collect::, _>>()?, 460 | protocol: tokens[2].to_string(), 461 | format: tokens[3..].join(" "), 462 | props: lines[1..] 463 | .iter() 464 | .map(|line| MediaProp::from_str(line)) 465 | .collect::, _>>()?, 466 | }) 467 | } 468 | _ => Err(ParseError::UnknownKey(key, value)), 469 | } 470 | } 471 | } 472 | 473 | impl SdpProp { 474 | fn to_string(&self, ending: LineEnding) -> String { 475 | // TODO: Cut down on code copying from SDPProp to MediaProp 476 | match self { 477 | SdpProp::Version(v) => format!("v={v}"), 478 | SdpProp::Origin { 479 | username, 480 | session_id, 481 | session_version, 482 | net_type, 483 | address_type, 484 | address, 485 | } => format!( 486 | "o={username} {session_id} {session_version} {} {} {address}", 487 | net_type.to_string(), 488 | address_type.to_string() 489 | ), 490 | SdpProp::SessionName(name) => format!("s={name}"), 491 | SdpProp::SessionInformation(info) => format!("i={info}"), 492 | SdpProp::Uri(uri) => format!("u={uri}"), 493 | SdpProp::Email(email) => format!("e={email}"), 494 | SdpProp::Phone(phone) => format!("p={phone}"), 495 | SdpProp::Connection { 496 | net_type, 497 | address_type, 498 | address, 499 | ttl, 500 | num_addresses, 501 | suffix, 502 | } => { 503 | // TTL is required for IPv4 504 | let mut address = if *address_type == AddressType::IPv4 || ttl.is_some() { 505 | format!("{address}/{}", ttl.unwrap()) 506 | } else { 507 | address.clone() 508 | }; 509 | 510 | if let Some(num_addresses) = num_addresses { 511 | address = format!("{address}/{num_addresses}") 512 | } 513 | 514 | build_connection_lines(net_type, address_type, suffix, &address) 515 | } 516 | SdpProp::Bandwidth { r#type, bandwidth } => { 517 | format!("b={}:{}", r#type.to_string(), bandwidth) 518 | } 519 | SdpProp::Timing { start, stop } => format!("t={start} {stop}"), 520 | SdpProp::RepeatTimes { 521 | interval, 522 | active_duration, 523 | start_offsets, 524 | } => format!("r={interval} {active_duration} {}", start_offsets.join(" ")), 525 | SdpProp::TimeZone(adjustments) => format!( 526 | "z={}", 527 | adjustments 528 | .iter() 529 | .map(|adj| format!("{} {}", adj.time, adj.offset)) 530 | .collect::>() 531 | .join(" ") 532 | ), 533 | SdpProp::EncryptionKeys(method) => format!("k={}", method.to_string()), 534 | SdpProp::Attribute { key, value } => { 535 | if let Some(value) = value { 536 | format!("a={}:{}", key, value) 537 | } else { 538 | format!("a={key}") 539 | } 540 | } 541 | SdpProp::Media { 542 | r#type, 543 | ports, 544 | protocol, 545 | format, 546 | props, 547 | } => { 548 | let header = format!( 549 | "{} {} {} {}", 550 | r#type.to_string(), 551 | ports 552 | .iter() 553 | .map(|port| port.to_string()) 554 | .collect::>() 555 | .join("/"), 556 | protocol.to_string(), 557 | format 558 | ); 559 | 560 | format!( 561 | "m={header}{}", 562 | if props.is_empty() { 563 | "".to_string() 564 | } else { 565 | format!( 566 | "{}{}", 567 | ending.string(), 568 | props 569 | .iter() 570 | .map(|prop| prop.to_string()) 571 | .collect::>() 572 | .join(ending.string()) 573 | ) 574 | } 575 | ) 576 | } 577 | } 578 | } 579 | } 580 | 581 | #[derive(Debug)] 582 | pub enum ParseError { 583 | /// Unknown attribute key along with its value 584 | UnknownKey(char, String), 585 | UnknownToken(String), 586 | /// Failed to cast from String to another type 587 | TypeParseFailed(IntErrorKind), 588 | } 589 | 590 | impl From for ParseError { 591 | fn from(e: ParseIntError) -> Self { 592 | Self::TypeParseFailed(e.kind().clone()) 593 | } 594 | } 595 | 596 | #[derive(Debug, PartialEq, Eq)] 597 | pub struct SDP { 598 | pub props: Vec, 599 | } 600 | 601 | impl FromStr for SDP { 602 | type Err = ParseError; 603 | 604 | fn from_str(s: &str) -> Result { 605 | // Convert \r\n to \n 606 | let s = s.replace("\r\n", "\n"); 607 | 608 | // Split string 609 | let lines = s 610 | .split('\n') 611 | .map(|line| line.to_string()) 612 | .collect::>(); 613 | 614 | // Group media attributes 615 | // Find indexes of all media lines 616 | let m_indices = lines 617 | .iter() 618 | .enumerate() 619 | .filter(|(_, line)| line.starts_with('m')) 620 | .map(|(idx, _)| idx) 621 | .collect::>(); 622 | 623 | // Combine all media sections into one line per section 624 | let lines: Vec = 625 | lines 626 | .into_iter() 627 | .filter(|line| !line.is_empty()) 628 | .enumerate() 629 | .fold(Vec::new(), |mut acc, (idx, line)| { 630 | // If m-line detected or array empty, start a new section 631 | if acc.is_empty() 632 | || m_indices.contains(&idx) 633 | || m_indices.is_empty() 634 | || idx < m_indices[0] 635 | { 636 | acc.push(line); 637 | return acc; 638 | } 639 | 640 | // Add to current section 641 | *acc.last_mut().unwrap() = format!("{}\n{line}", acc.last_mut().unwrap()); 642 | 643 | acc 644 | }); 645 | 646 | Ok(Self { 647 | props: lines 648 | .into_iter() 649 | .map(|line| SdpProp::from_str(&line)) 650 | .collect::, _>>()?, 651 | }) 652 | } 653 | } 654 | 655 | impl SDP { 656 | pub fn to_string(&self, ending: LineEnding) -> String { 657 | format!("{}{}", self.props 658 | .iter() 659 | .map(|prop| prop.to_string(ending)) 660 | .collect::>() 661 | .join(ending.string()), ending.string()) 662 | } 663 | } 664 | 665 | fn content_from_line(line: &str) -> Result<(char, String), ParseError> { 666 | let split = line.split('=').collect::>(); 667 | if split.len() < 2 { 668 | return Err(ParseError::UnknownToken(line.to_string())); 669 | } 670 | Ok((split[0].chars().next().unwrap(), split[1..].join("="))) 671 | } 672 | 673 | fn get_options_from_address_split(address_split: Vec<&str>) -> Result<(&str, Option, Option), ParseError> { 674 | Ok(match address_split.len() { 675 | 1 => (address_split[0], None, None), 676 | 2 => (address_split[0], Some(address_split[1].parse()?), None), 677 | 3 => ( 678 | address_split[0], 679 | Some(address_split[1].parse()?), 680 | Some(address_split[2].parse()?), 681 | ), 682 | _ => unreachable!(), 683 | }) 684 | } 685 | 686 | fn build_connection_lines(net_type: &NetworkType, address_type: &AddressType, suffix: &Option, address: &String) -> String { 687 | if let Some(suffix) = suffix { 688 | format!( 689 | "c={} {} {} {}", 690 | net_type.to_string(), 691 | address_type.to_string(), 692 | address, 693 | suffix 694 | ) 695 | } else { 696 | format!( 697 | "c={} {} {}", 698 | net_type.to_string(), 699 | address_type.to_string(), 700 | address 701 | ) 702 | } 703 | } -------------------------------------------------------------------------------- /src/webrtcredux/sender/imp.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Mutex, Arc}; 2 | use std::time::Duration; 3 | 4 | use bytes::Bytes; 5 | use futures::executor::block_on; 6 | use gst::prelude::ClockExtManual; 7 | use gst::traits::{ClockExt, ElementExt}; 8 | use gst::{Buffer, FlowError, FlowSuccess, glib, trace, ClockTime, debug, error}; 9 | use gst::subclass::ElementMetadata; 10 | use gst::subclass::prelude::*; 11 | use gst_base::subclass::prelude::*; 12 | use once_cell::sync::Lazy; 13 | use tokio::runtime::Handle; 14 | use webrtc::media::Sample; 15 | use webrtc::track::track_local::track_local_static_sample::TrackLocalStaticSample; 16 | 17 | use crate::webrtcredux::CAT; 18 | 19 | #[derive(PartialEq, Eq)] 20 | pub enum MediaType { 21 | Video, 22 | Audio 23 | } 24 | 25 | #[derive(Default)] 26 | struct State { 27 | track: Option>, 28 | duration: Option, 29 | handle: Option, 30 | media_type: Option, 31 | async_complete: bool 32 | } 33 | 34 | #[derive(Default)] 35 | pub struct WebRtcReduxSender { 36 | state: Arc>, 37 | } 38 | 39 | impl WebRtcReduxSender { 40 | pub fn add_info(&self, track: Arc, handle: Handle, media_type: MediaType, duration: Option, on_connect: tokio::sync::oneshot::Receiver<()>) { 41 | let _ = self.state.lock().unwrap().track.insert(track); 42 | let _ = self.state.lock().unwrap().media_type.insert(media_type); 43 | self.state.lock().unwrap().duration = duration; 44 | 45 | let instance = self.instance().clone(); 46 | let state = self.state.clone(); 47 | handle.spawn(async move { 48 | if on_connect.await.is_err() { error!(CAT, "Error waiting for peer connection"); return; } 49 | state.lock().unwrap().async_complete = true; 50 | debug!(CAT, "Peer connection successful, finishing async transition"); 51 | instance.change_state(gst::StateChange::PausedToPlaying).unwrap(); 52 | }); 53 | let _ = self.state.lock().unwrap().handle.insert(handle); 54 | } 55 | } 56 | 57 | impl ElementImpl for WebRtcReduxSender { 58 | fn metadata() -> Option<&'static ElementMetadata> { 59 | static ELEMENT_METADATA: Lazy = Lazy::new(|| { 60 | gst::subclass::ElementMetadata::new( 61 | "WebRTC Broadcast Engine (Internal sender)", 62 | "Sink/Video/Audio", 63 | "Internal WebRtcRedux sender", 64 | "Jack Hogan; Lorenzo Rizzotti ", 65 | ) 66 | }); 67 | 68 | Some(&*ELEMENT_METADATA) 69 | } 70 | 71 | fn pad_templates() -> &'static [gst::PadTemplate] { 72 | static PAD_TEMPLATES: Lazy> = Lazy::new(|| { 73 | let caps = gst::Caps::builder_full() 74 | .structure(gst::Structure::builder("audio/x-opus").build()) 75 | .structure(gst::Structure::builder("audio/G722").build()) 76 | .structure(gst::Structure::builder("audio/x-mulaw").build()) 77 | .structure(gst::Structure::builder("audio/x-alaw").build()) 78 | .structure(gst::Structure::builder("video/x-h264").field("stream-format", "byte-stream").field("profile", "baseline").build()) 79 | .structure(gst::Structure::builder("video/x-vp8").build()) 80 | .structure(gst::Structure::builder("video/x-vp9").build()) 81 | .build(); 82 | let sink_pad_template = gst::PadTemplate::new( 83 | "sink", 84 | gst::PadDirection::Sink, 85 | gst::PadPresence::Always, 86 | &caps, 87 | ) 88 | .unwrap(); 89 | 90 | vec![sink_pad_template] 91 | }); 92 | 93 | PAD_TEMPLATES.as_ref() 94 | } 95 | 96 | fn change_state(&self, transition: gst::StateChange) -> Result { 97 | if transition == gst::StateChange::PausedToPlaying { 98 | if let Some(duration) = self.state.lock().unwrap().duration { 99 | self.set_clock(Some(&format_clock(duration))); 100 | } 101 | 102 | if !self.state.lock().unwrap().async_complete { 103 | return Ok(gst::StateChangeSuccess::Async); 104 | } 105 | } 106 | self.parent_change_state(transition) 107 | } 108 | } 109 | 110 | impl BaseSinkImpl for WebRtcReduxSender { 111 | fn render(&self, buffer: &Buffer) -> Result { 112 | let sample_duration = if *self.state.lock().unwrap().media_type.as_ref().unwrap() == MediaType::Video { 113 | Duration::from_secs(1) 114 | } else { 115 | Duration::from_millis(buffer.duration().unwrap().mseconds()) 116 | }; 117 | 118 | // If the clock hasn't been set, set it from the buffer timestamp 119 | if self.state.lock().unwrap().duration.is_none() { 120 | let _ = self.state.lock().unwrap().duration.insert(buffer.duration().unwrap()); 121 | self.set_clock(Some(&format_clock(buffer.duration().unwrap()))); 122 | } 123 | 124 | let map = buffer.map_readable().map_err(|_| gst::FlowError::Error)?; 125 | let media_type_str = if *self.state.lock().unwrap().media_type.as_ref().unwrap() == MediaType::Video { "VIDEO" } else { "AUDIO" }; 126 | trace!(CAT, "[{}] Rendering {} bytes for duration {} ms", media_type_str, map.size(), sample_duration.as_millis()); 127 | let bytes = Bytes::copy_from_slice(map.as_slice()); 128 | 129 | let handle = self.state.lock().unwrap().handle.as_ref().unwrap().clone(); 130 | let track = self.state.lock().unwrap().track.as_ref().unwrap().clone(); 131 | let inner = handle.clone(); 132 | block_on(async move { 133 | handle.spawn_blocking(move || { 134 | inner.block_on(async move { 135 | track.write_sample(&Sample { 136 | data: bytes, 137 | duration: sample_duration, 138 | ..Sample::default() 139 | }).await 140 | }) 141 | }).await 142 | }).unwrap().unwrap(); 143 | 144 | Ok(gst::FlowSuccess::Ok) 145 | } 146 | } 147 | 148 | #[glib::object_subclass] 149 | impl ObjectSubclass for WebRtcReduxSender { 150 | const NAME: &'static str = "WebRtcReduxSender"; 151 | type Type = super::WebRtcReduxSender; 152 | type ParentType = gst_base::BaseSink; 153 | } 154 | 155 | impl ObjectImpl for WebRtcReduxSender {} 156 | 157 | impl GstObjectImpl for WebRtcReduxSender {} 158 | 159 | fn format_clock(duration: ClockTime) -> gst::Clock { 160 | let clock = gst::SystemClock::obtain(); 161 | let _ = clock.new_periodic_id(clock.internal_time(), duration); 162 | 163 | clock 164 | } -------------------------------------------------------------------------------- /src/webrtcredux/sender/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use gst::{glib, ClockTime}; 4 | use gst::subclass::prelude::ObjectSubclassExt; 5 | 6 | mod imp; 7 | 8 | pub use imp::*; 9 | use tokio::runtime::Handle; 10 | use webrtc::track::track_local::track_local_static_sample::TrackLocalStaticSample; 11 | 12 | glib::wrapper! { 13 | pub struct WebRtcReduxSender(ObjectSubclass) @extends gst_base::BaseSink, gst::Element, gst::Object; 14 | } 15 | 16 | impl WebRtcReduxSender { 17 | pub fn add_info(&self, track: Arc, handle: Handle, media_type: MediaType, duration: Option, on_connect: tokio::sync::oneshot::Receiver<()>) { 18 | imp::WebRtcReduxSender::from_instance(self).add_info(track, handle, media_type, duration, on_connect); 19 | } 20 | } 21 | 22 | unsafe impl Send for WebRtcReduxSender {} 23 | 24 | impl Default for WebRtcReduxSender { 25 | fn default() -> Self { 26 | glib::Object::new::(&[]) 27 | } 28 | } -------------------------------------------------------------------------------- /tests/webrtcredux.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | use std::str::FromStr; 3 | 4 | use enum_dispatch::enum_dispatch; 5 | use gst::glib::BoolError; 6 | use gst::prelude::*; 7 | use gst::{debug_bin_to_dot_data, DebugGraphDetails, Element}; 8 | use indoc::indoc; 9 | use webrtcredux::sdp::LineEnding; 10 | use std::string::ToString; 11 | use strum::IntoEnumIterator; 12 | use strum_macros::Display; 13 | use strum_macros::EnumIter; 14 | 15 | use webrtcredux::webrtcredux::{ 16 | sdp::{AddressType, MediaProp, MediaType, NetworkType, SdpProp, SDP}, 17 | RTCIceServer, WebRtcRedux, 18 | }; 19 | 20 | //TODO: Implement a webrtc-rs server configured for receiving to test the plugin 21 | 22 | fn init() { 23 | use std::sync::Once; 24 | static INIT: Once = Once::new(); 25 | 26 | INIT.call_once(|| { 27 | gst::init().unwrap(); 28 | webrtcredux::plugin_register_static().unwrap(); 29 | }) 30 | } 31 | 32 | fn init_tests_dir() { 33 | use std::sync::Once; 34 | static INIT: Once = Once::new(); 35 | 36 | INIT.call_once(|| { 37 | let _ = std::fs::remove_dir_all("./target/debug/tests"); 38 | std::fs::create_dir_all("./target/debug/tests").expect("Failed to create tests dir"); 39 | }) 40 | } 41 | 42 | #[enum_dispatch(Encoder)] 43 | pub trait GstEncoder { 44 | fn to_gst_encoder(&self) -> Result; 45 | } 46 | 47 | #[derive(Debug, EnumIter, Display)] 48 | enum AudioEncoder { 49 | Opus, 50 | Mulaw, 51 | Alaw, 52 | G722, 53 | } 54 | 55 | impl GstEncoder for AudioEncoder { 56 | fn to_gst_encoder(&self) -> Result { 57 | match self { 58 | AudioEncoder::Opus => gst::ElementFactory::make("opusenc").build(), 59 | AudioEncoder::Mulaw => gst::ElementFactory::make("mulawenc").build(), 60 | AudioEncoder::Alaw => gst::ElementFactory::make("alawenc").build(), 61 | AudioEncoder::G722 => gst::ElementFactory::make("avenc_g722").build(), 62 | } 63 | } 64 | } 65 | 66 | #[derive(Debug, EnumIter, Display)] 67 | enum VideoEncoder { 68 | H264, 69 | VP8, 70 | VP9, 71 | } 72 | 73 | impl GstEncoder for VideoEncoder { 74 | fn to_gst_encoder(&self) -> Result { 75 | match self { 76 | VideoEncoder::H264 => gst::ElementFactory::make("x264enc").build(), 77 | VideoEncoder::VP8 => gst::ElementFactory::make("vp8enc").build(), 78 | VideoEncoder::VP9 => gst::ElementFactory::make("vp9enc").build(), 79 | } 80 | } 81 | } 82 | 83 | #[derive(Debug)] 84 | #[enum_dispatch] 85 | enum Encoder { 86 | Audio(AudioEncoder), 87 | Video(VideoEncoder), 88 | } 89 | 90 | impl Display for Encoder { 91 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 92 | match self { 93 | Encoder::Audio(encoder) => write!(f, "audio_{}", encoder.to_string()), 94 | Encoder::Video(encoder) => write!(f, "video_{}", encoder.to_string()), 95 | } 96 | } 97 | } 98 | 99 | #[test] 100 | fn pipeline_creation_h264() { 101 | pipeline_creation_test(vec![Encoder::Video(VideoEncoder::H264)]); 102 | } 103 | 104 | #[test] 105 | fn pipeline_creation_vp8() { 106 | pipeline_creation_test(vec![Encoder::Video(VideoEncoder::VP8)]); 107 | } 108 | 109 | #[test] 110 | fn pipeline_creation_vp9() { 111 | pipeline_creation_test(vec![Encoder::Video(VideoEncoder::VP9)]); 112 | } 113 | 114 | #[test] 115 | fn pipeline_creation_opus() { 116 | pipeline_creation_test(vec![Encoder::Audio(AudioEncoder::Opus)]); 117 | } 118 | 119 | #[test] 120 | fn pipeline_creation_mulaw() { 121 | pipeline_creation_test(vec![Encoder::Audio(AudioEncoder::Mulaw)]); 122 | } 123 | 124 | #[test] 125 | fn pipeline_creation_alaw() { 126 | pipeline_creation_test(vec![Encoder::Audio(AudioEncoder::Alaw)]); 127 | } 128 | 129 | #[test] 130 | fn pipeline_creation_combined() { 131 | let mut to_test = vec![]; 132 | for a_encoder in AudioEncoder::iter() { 133 | to_test.push(Encoder::Audio(a_encoder)); 134 | } 135 | for v_encoder in VideoEncoder::iter() { 136 | to_test.push(Encoder::Video(v_encoder)); 137 | } 138 | pipeline_creation_test(to_test); 139 | } 140 | 141 | fn pipeline_creation_test(encoders: Vec) { 142 | init(); 143 | let pipeline = gst::Pipeline::new(None); 144 | 145 | let webrtcredux = WebRtcRedux::default(); 146 | 147 | webrtcredux.add_ice_servers(vec![RTCIceServer { 148 | urls: vec!["stun:stun.l.google.com:19302".to_string()], 149 | ..Default::default() 150 | }]); 151 | 152 | pipeline 153 | .add(&webrtcredux) 154 | .expect("Failed to add webrtcredux to the pipeline"); 155 | 156 | for encoder_to_use in &encoders { 157 | let src = match encoder_to_use { 158 | Encoder::Audio(_) => gst::ElementFactory::make("audiotestsrc").build().unwrap(), 159 | Encoder::Video(_) => gst::ElementFactory::make("videotestsrc").build().unwrap(), 160 | }; 161 | 162 | let encoder = encoder_to_use.to_gst_encoder().unwrap(); 163 | 164 | pipeline 165 | .add_many(&[&src, &encoder]) 166 | .expect("Failed to add elements to the pipeline"); 167 | Element::link_many(&[&src, &encoder, webrtcredux.as_ref()]) 168 | .expect("Failed to link elements"); 169 | 170 | } 171 | 172 | pipeline.set_state(gst::State::Playing).expect("Failed to set pipeline state"); 173 | 174 | // Debug diagram 175 | let out = debug_bin_to_dot_data(&pipeline, DebugGraphDetails::ALL); 176 | init_tests_dir(); 177 | std::fs::write( 178 | format!( 179 | "./target/debug/tests/{}.dot", 180 | encoders 181 | .iter() 182 | .map(|e| e.to_string()) 183 | .collect::>() 184 | .join("-") 185 | ), 186 | out.as_str(), 187 | ) 188 | .unwrap(); 189 | } 190 | 191 | #[test] 192 | fn sdp_serialization() { 193 | let target = indoc!( 194 | "v=0 195 | o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5 196 | s=SDP Seminar 197 | i=A Seminar on the session description protocol 198 | u=http://www.example.com/seminars/sdp.pdf 199 | e=j.doe@example.com (Jane Doe) 200 | c=IN IP4 224.2.17.12/127 201 | t=2873397496 2873404696 202 | a=recvonly 203 | m=audio 49170 RTP/AVP 0 204 | m=video 51372 RTP/AVP 99 205 | a=rtpmap:99 h263-1998/90000 206 | "); 207 | 208 | let props = vec![ 209 | SdpProp::Version(0), 210 | SdpProp::Origin { 211 | username: "jdoe".to_string(), 212 | session_id: "2890844526".to_string(), 213 | session_version: 2890842807, 214 | net_type: NetworkType::Internet, 215 | address_type: AddressType::IPv4, 216 | address: "10.47.16.5".to_string(), 217 | }, 218 | SdpProp::SessionName("SDP Seminar".to_string()), 219 | SdpProp::SessionInformation("A Seminar on the session description protocol".to_string()), 220 | SdpProp::Uri("http://www.example.com/seminars/sdp.pdf".to_string()), 221 | SdpProp::Email("j.doe@example.com (Jane Doe)".to_string()), 222 | SdpProp::Connection { 223 | net_type: NetworkType::Internet, 224 | address_type: AddressType::IPv4, 225 | address: "224.2.17.12".to_string(), 226 | suffix: None, 227 | ttl: Some(127), 228 | num_addresses: None, 229 | }, 230 | SdpProp::Timing { 231 | start: 2873397496, 232 | stop: 2873404696, 233 | }, 234 | SdpProp::Attribute { 235 | key: "recvonly".to_string(), 236 | value: None, 237 | }, 238 | SdpProp::Media { 239 | r#type: MediaType::Audio, 240 | ports: vec![49170], 241 | protocol: "RTP/AVP".to_string(), 242 | format: "0".to_string(), 243 | props: vec![], 244 | }, 245 | SdpProp::Media { 246 | r#type: MediaType::Video, 247 | ports: vec![51372], 248 | protocol: "RTP/AVP".to_string(), 249 | format: "99".to_string(), 250 | props: vec![MediaProp::Attribute { 251 | key: "rtpmap".to_string(), 252 | value: Some("99 h263-1998/90000".to_string()), 253 | }], 254 | }, 255 | ]; 256 | 257 | let test = SDP { props }; 258 | 259 | assert_eq!(test.to_string(LineEnding::LF), target); 260 | } 261 | 262 | #[test] 263 | fn sdp_deserialization() { 264 | let props = vec![ 265 | SdpProp::Version(0), 266 | SdpProp::Origin { 267 | username: "jdoe".to_string(), 268 | session_id: "2890844526".to_string(), 269 | session_version: 2890842807, 270 | net_type: NetworkType::Internet, 271 | address_type: AddressType::IPv4, 272 | address: "10.47.16.5".to_string(), 273 | }, 274 | SdpProp::SessionName("SDP Seminar".to_string()), 275 | SdpProp::SessionInformation("A Seminar on the session description protocol".to_string()), 276 | SdpProp::Uri("http://www.example.com/seminars/sdp.pdf".to_string()), 277 | SdpProp::Email("j.doe@example.com (Jane Doe)".to_string()), 278 | SdpProp::Connection { 279 | net_type: NetworkType::Internet, 280 | address_type: AddressType::IPv4, 281 | address: "224.2.17.12".to_string(), 282 | suffix: None, 283 | ttl: Some(127), 284 | num_addresses: None, 285 | }, 286 | SdpProp::Timing { 287 | start: 2873397496, 288 | stop: 2873404696, 289 | }, 290 | SdpProp::Attribute { 291 | key: "recvonly".to_string(), 292 | value: None, 293 | }, 294 | SdpProp::Media { 295 | r#type: MediaType::Audio, 296 | ports: vec![49170], 297 | protocol: "RTP/AVP".to_string(), 298 | format: "0".to_string(), 299 | props: vec![], 300 | }, 301 | SdpProp::Media { 302 | r#type: MediaType::Video, 303 | ports: vec![51372], 304 | protocol: "RTP/AVP".to_string(), 305 | format: "99".to_string(), 306 | props: vec![MediaProp::Attribute { 307 | key: "rtpmap".to_string(), 308 | value: Some("99 h263-1998/90000".to_string()), 309 | }], 310 | }, 311 | ]; 312 | 313 | let target = SDP { props }; 314 | 315 | let test = indoc!( 316 | "v=0 317 | o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5 318 | s=SDP Seminar 319 | i=A Seminar on the session description protocol 320 | u=http://www.example.com/seminars/sdp.pdf 321 | e=j.doe@example.com (Jane Doe) 322 | c=IN IP4 224.2.17.12/127 323 | t=2873397496 2873404696 324 | a=recvonly 325 | m=audio 49170 RTP/AVP 0 326 | m=video 51372 RTP/AVP 99 327 | a=rtpmap:99 h263-1998/90000" 328 | ); 329 | 330 | let res = SDP::from_str(test); 331 | 332 | assert!( 333 | res.is_ok(), 334 | "Parse failed with error: {:?}", 335 | res.err().unwrap() 336 | ); 337 | 338 | assert_eq!(res.unwrap(), target); 339 | } 340 | 341 | #[test] 342 | fn complex_sdp() { 343 | let text = indoc!("v=0 344 | o=- 8488083020976882093 2 IN IP4 127.0.0.1 345 | s=- 346 | t=0 0 347 | a=group:BUNDLE 0 1 348 | a=extmap-allow-mixed 349 | a=msid-semantic: WMS 350 | m=video 55395 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 121 125 107 108 109 124 120 123 119 35 36 41 42 114 115 116 351 | c=IN IP4 2.39.73.41 352 | a=rtcp:9 IN IP4 0.0.0.0 353 | a=candidate:3859917557 1 udp 2113937151 44a9eba8-5284-45b5-8825-ed5f7001f62a.local 55395 typ host generation 0 network-cost 999 354 | a=candidate:842163049 1 udp 1677729535 2.39.73.41 55395 typ srflx raddr 0.0.0.0 rport 0 generation 0 network-cost 999 355 | a=ice-ufrag:nVwA 356 | a=ice-pwd:tyR7PZVvcMN4/aqQLrcBFuU5 357 | a=ice-options:trickle 358 | a=fingerprint:sha-256 62:E4:9A:F9:6A:F5:B4:E3:52:07:4F:8E:C4:9F:27:16:9B:DA:D1:18:00:19:5F:8A:69:E2:D9:F6:AC:F0:64:51 359 | a=setup:actpass 360 | a=mid:0 361 | a=extmap:1 urn:ietf:params:rtp-hdrext:toffset 362 | a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time 363 | a=extmap:3 urn:3gpp:video-orientation 364 | a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 365 | a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay 366 | a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type 367 | a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing 368 | a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space 369 | a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid 370 | a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id 371 | a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id 372 | a=sendrecv 373 | a=msid:- aef93e5f-0aeb-4c4d-807e-fadaf721fc63 374 | a=rtcp-mux 375 | a=rtcp-rsize 376 | a=rtpmap:96 VP8/90000 377 | a=rtcp-fb:96 goog-remb 378 | a=rtcp-fb:96 transport-cc 379 | a=rtcp-fb:96 ccm fir 380 | a=rtcp-fb:96 nack 381 | a=rtcp-fb:96 nack pli 382 | a=rtpmap:97 rtx/90000 383 | a=fmtp:97 apt=96 384 | a=rtpmap:98 VP9/90000 385 | a=rtcp-fb:98 goog-remb 386 | a=rtcp-fb:98 transport-cc 387 | a=rtcp-fb:98 ccm fir 388 | a=rtcp-fb:98 nack 389 | a=rtcp-fb:98 nack pli 390 | a=fmtp:98 profile-id=0 391 | a=rtpmap:99 rtx/90000 392 | a=fmtp:99 apt=98 393 | a=rtpmap:100 VP9/90000 394 | a=rtcp-fb:100 goog-remb 395 | a=rtcp-fb:100 transport-cc 396 | a=rtcp-fb:100 ccm fir 397 | a=rtcp-fb:100 nack 398 | a=rtcp-fb:100 nack pli 399 | a=fmtp:100 profile-id=2 400 | a=rtpmap:101 rtx/90000 401 | a=fmtp:101 apt=100 402 | a=rtpmap:127 H264/90000 403 | a=rtcp-fb:127 goog-remb 404 | a=rtcp-fb:127 transport-cc 405 | a=rtcp-fb:127 ccm fir 406 | a=rtcp-fb:127 nack 407 | a=rtcp-fb:127 nack pli 408 | a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f 409 | a=rtpmap:121 rtx/90000 410 | a=fmtp:121 apt=127 411 | a=rtpmap:125 H264/90000 412 | a=rtcp-fb:125 goog-remb 413 | a=rtcp-fb:125 transport-cc 414 | a=rtcp-fb:125 ccm fir 415 | a=rtcp-fb:125 nack 416 | a=rtcp-fb:125 nack pli 417 | a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f 418 | a=rtpmap:107 rtx/90000 419 | a=fmtp:107 apt=125 420 | a=rtpmap:108 H264/90000 421 | a=rtcp-fb:108 goog-remb 422 | a=rtcp-fb:108 transport-cc 423 | a=rtcp-fb:108 ccm fir 424 | a=rtcp-fb:108 nack 425 | a=rtcp-fb:108 nack pli 426 | a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f 427 | a=rtpmap:109 rtx/90000 428 | a=fmtp:109 apt=108 429 | a=rtpmap:124 H264/90000 430 | a=rtcp-fb:124 goog-remb 431 | a=rtcp-fb:124 transport-cc 432 | a=rtcp-fb:124 ccm fir 433 | a=rtcp-fb:124 nack 434 | a=rtcp-fb:124 nack pli 435 | a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f 436 | a=rtpmap:120 rtx/90000 437 | a=fmtp:120 apt=124 438 | a=rtpmap:123 H264/90000 439 | a=rtcp-fb:123 goog-remb 440 | a=rtcp-fb:123 transport-cc 441 | a=rtcp-fb:123 ccm fir 442 | a=rtcp-fb:123 nack 443 | a=rtcp-fb:123 nack pli 444 | a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f 445 | a=rtpmap:119 rtx/90000 446 | a=fmtp:119 apt=123 447 | a=rtpmap:35 H264/90000 448 | a=rtcp-fb:35 goog-remb 449 | a=rtcp-fb:35 transport-cc 450 | a=rtcp-fb:35 ccm fir 451 | a=rtcp-fb:35 nack 452 | a=rtcp-fb:35 nack pli 453 | a=fmtp:35 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f 454 | a=rtpmap:36 rtx/90000 455 | a=fmtp:36 apt=35 456 | a=rtpmap:41 AV1/90000 457 | a=rtcp-fb:41 goog-remb 458 | a=rtcp-fb:41 transport-cc 459 | a=rtcp-fb:41 ccm fir 460 | a=rtcp-fb:41 nack 461 | a=rtcp-fb:41 nack pli 462 | a=rtpmap:42 rtx/90000 463 | a=fmtp:42 apt=41 464 | a=rtpmap:114 red/90000 465 | a=rtpmap:115 rtx/90000 466 | a=fmtp:115 apt=114 467 | a=rtpmap:116 ulpfec/90000 468 | a=ssrc-group:FID 2188188946 3056071260 469 | a=ssrc:2188188946 cname:QGl7AJpaZdNMdnjK 470 | a=ssrc:2188188946 msid:- aef93e5f-0aeb-4c4d-807e-fadaf721fc63 471 | a=ssrc:3056071260 cname:QGl7AJpaZdNMdnjK 472 | a=ssrc:3056071260 msid:- aef93e5f-0aeb-4c4d-807e-fadaf721fc63 473 | m=audio 34179 UDP/TLS/RTP/SAVPF 111 63 103 104 9 0 8 106 105 13 110 112 113 126 474 | c=IN IP4 2.39.73.41 475 | a=rtcp:9 IN IP4 0.0.0.0 476 | a=candidate:3859917557 1 udp 2113937151 44a9eba8-5284-45b5-8825-ed5f7001f62a.local 34179 typ host generation 0 network-cost 999 477 | a=candidate:842163049 1 udp 1677729535 2.39.73.41 34179 typ srflx raddr 0.0.0.0 rport 0 generation 0 network-cost 999 478 | a=ice-ufrag:nVwA 479 | a=ice-pwd:tyR7PZVvcMN4/aqQLrcBFuU5 480 | a=ice-options:trickle 481 | a=fingerprint:sha-256 62:E4:9A:F9:6A:F5:B4:E3:52:07:4F:8E:C4:9F:27:16:9B:DA:D1:18:00:19:5F:8A:69:E2:D9:F6:AC:F0:64:51 482 | a=setup:actpass 483 | a=mid:1 484 | a=extmap:14 urn:ietf:params:rtp-hdrext:ssrc-audio-level 485 | a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time 486 | a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 487 | a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid 488 | a=sendrecv 489 | a=msid:- c8351fd3-2f5d-4d46-899d-9af77de86d9b 490 | a=rtcp-mux 491 | a=rtpmap:111 opus/48000/2 492 | a=rtcp-fb:111 transport-cc 493 | a=fmtp:111 minptime=10;useinbandfec=1 494 | a=rtpmap:63 red/48000/2 495 | a=fmtp:63 111/111 496 | a=rtpmap:103 ISAC/16000 497 | a=rtpmap:104 ISAC/32000 498 | a=rtpmap:9 G722/8000 499 | a=rtpmap:0 PCMU/8000 500 | a=rtpmap:8 PCMA/8000 501 | a=rtpmap:106 CN/32000 502 | a=rtpmap:105 CN/16000 503 | a=rtpmap:13 CN/8000 504 | a=rtpmap:110 telephone-event/48000 505 | a=rtpmap:112 telephone-event/32000 506 | a=rtpmap:113 telephone-event/16000 507 | a=rtpmap:126 telephone-event/8000 508 | a=ssrc:3846141828 cname:QGl7AJpaZdNMdnjK 509 | a=ssrc:3846141828 msid:- c8351fd3-2f5d-4d46-899d-9af77de86d9b"); 510 | 511 | let sdp = SDP::from_str(text); 512 | 513 | assert!(sdp.is_ok()); 514 | } 515 | 516 | #[test] 517 | fn complex_unformatted_sdp() { 518 | let text = "v=0\r\no=- 8488083020976882093 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\nm=video 55395 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 121 125 107 108 109 124 120 123 119 35 36 41 42 114 115 116\r\nc=IN IP4 2.39.73.41\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:3859917557 1 udp 2113937151 44a9eba8-5284-45b5-8825-ed5f7001f62a.local 55395 typ host generation 0 network-cost 999\r\na=candidate:842163049 1 udp 1677729535 2.39.73.41 55395 typ srflx raddr 0.0.0.0 rport 0 generation 0 network-cost 999\r\na=ice-ufrag:nVwA\r\na=ice-pwd:tyR7PZVvcMN4/aqQLrcBFuU5\r\na=ice-options:trickle\r\na=fingerprint:sha-256 62:E4:9A:F9:6A:F5:B4:E3:52:07:4F:8E:C4:9F:27:16:9B:DA:D1:18:00:19:5F:8A:69:E2:D9:F6:AC:F0:64:51\r\na=setup:actpass\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 urn:3gpp:video-orientation\r\na=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=sendrecv\r\na=msid:- aef93e5f-0aeb-4c4d-807e-fadaf721fc63\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 transport-cc\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=fmtp:98 profile-id=0\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=98\r\na=rtpmap:100 VP9/90000\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 transport-cc\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=fmtp:100 profile-id=2\r\na=rtpmap:101 rtx/90000\r\na=fmtp:101 apt=100\r\na=rtpmap:127 H264/90000\r\na=rtcp-fb:127 goog-remb\r\na=rtcp-fb:127 transport-cc\r\na=rtcp-fb:127 ccm fir\r\na=rtcp-fb:127 nack\r\na=rtcp-fb:127 nack pli\r\na=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\na=rtpmap:121 rtx/90000\r\na=fmtp:121 apt=127\r\na=rtpmap:125 H264/90000\r\na=rtcp-fb:125 goog-remb\r\na=rtcp-fb:125 transport-cc\r\na=rtcp-fb:125 ccm fir\r\na=rtcp-fb:125 nack\r\na=rtcp-fb:125 nack pli\r\na=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f\r\na=rtpmap:107 rtx/90000\r\na=fmtp:107 apt=125\r\na=rtpmap:108 H264/90000\r\na=rtcp-fb:108 goog-remb\r\na=rtcp-fb:108 transport-cc\r\na=rtcp-fb:108 ccm fir\r\na=rtcp-fb:108 nack\r\na=rtcp-fb:108 nack pli\r\na=fmtp:108 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\na=rtpmap:109 rtx/90000\r\na=fmtp:109 apt=108\r\na=rtpmap:124 H264/90000\r\na=rtcp-fb:124 goog-remb\r\na=rtcp-fb:124 transport-cc\r\na=rtcp-fb:124 ccm fir\r\na=rtcp-fb:124 nack\r\na=rtcp-fb:124 nack pli\r\na=fmtp:124 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f\r\na=rtpmap:120 rtx/90000\r\na=fmtp:120 apt=124\r\na=rtpmap:123 H264/90000\r\na=rtcp-fb:123 goog-remb\r\na=rtcp-fb:123 transport-cc\r\na=rtcp-fb:123 ccm fir\r\na=rtcp-fb:123 nack\r\na=rtcp-fb:123 nack pli\r\na=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f\r\na=rtpmap:119 rtx/90000\r\na=fmtp:119 apt=123\r\na=rtpmap:35 H264/90000\r\na=rtcp-fb:35 goog-remb\r\na=rtcp-fb:35 transport-cc\r\na=rtcp-fb:35 ccm fir\r\na=rtcp-fb:35 nack\r\na=rtcp-fb:35 nack pli\r\na=fmtp:35 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f\r\na=rtpmap:36 rtx/90000\r\na=fmtp:36 apt=35\r\na=rtpmap:41 AV1/90000\r\na=rtcp-fb:41 goog-remb\r\na=rtcp-fb:41 transport-cc\r\na=rtcp-fb:41 ccm fir\r\na=rtcp-fb:41 nack\r\na=rtcp-fb:41 nack pli\r\na=rtpmap:42 rtx/90000\r\na=fmtp:42 apt=41\r\na=rtpmap:114 red/90000\r\na=rtpmap:115 rtx/90000\r\na=fmtp:115 apt=114\r\na=rtpmap:116 ulpfec/90000\r\na=ssrc-group:FID 2188188946 3056071260\r\na=ssrc:2188188946 cname:QGl7AJpaZdNMdnjK\r\na=ssrc:2188188946 msid:- aef93e5f-0aeb-4c4d-807e-fadaf721fc63\r\na=ssrc:3056071260 cname:QGl7AJpaZdNMdnjK\r\na=ssrc:3056071260 msid:- aef93e5f-0aeb-4c4d-807e-fadaf721fc63\r\nm=audio 34179 UDP/TLS/RTP/SAVPF 111 63 103 104 9 0 8 106 105 13 110 112 113 126\r\nc=IN IP4 2.39.73.41\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:3859917557 1 udp 2113937151 44a9eba8-5284-45b5-8825-ed5f7001f62a.local 34179 typ host generation 0 network-cost 999\r\na=candidate:842163049 1 udp 1677729535 2.39.73.41 34179 typ srflx raddr 0.0.0.0 rport 0 generation 0 network-cost 999\r\na=ice-ufrag:nVwA\r\na=ice-pwd:tyR7PZVvcMN4/aqQLrcBFuU5\r\na=ice-options:trickle\r\na=fingerprint:sha-256 62:E4:9A:F9:6A:F5:B4:E3:52:07:4F:8E:C4:9F:27:16:9B:DA:D1:18:00:19:5F:8A:69:E2:D9:F6:AC:F0:64:51\r\na=setup:actpass\r\na=mid:1\r\na=extmap:14 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=sendrecv\r\na=msid:- c8351fd3-2f5d-4d46-899d-9af77de86d9b\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\na=rtpmap:63 red/48000/2\r\na=fmtp:63 111/111\r\na=rtpmap:103 ISAC/16000\r\na=rtpmap:104 ISAC/32000\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:106 CN/32000\r\na=rtpmap:105 CN/16000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:110 telephone-event/48000\r\na=rtpmap:112 telephone-event/32000\r\na=rtpmap:113 telephone-event/16000\r\na=rtpmap:126 telephone-event/8000\r\na=ssrc:3846141828 cname:QGl7AJpaZdNMdnjK\r\na=ssrc:3846141828 msid:- c8351fd3-2f5d-4d46-899d-9af77de86d9b\r\n"; 519 | 520 | let sdp = SDP::from_str(text); 521 | 522 | assert!(sdp.is_ok()); 523 | } 524 | 525 | #[test] 526 | fn sdp_symmetry() { 527 | let text = "v=0\r\no=- 9023059822302806521 801820409 IN IP4 0.0.0.0\r\ns=-\r\nt=0 0\r\na=fingerprint:sha-256 18:FB:AD:1F:CC:77:25:4F:6C:CD:3F:88:58:94:26:D5:B3:9B:72:CB:5A:9A:0E:A0:5D:C4:C8:E3:1D:5A:5A:6D\r\na=group:BUNDLE\r\nm=video 0 UDP/TLS/RTP/SAVPF 0\r\nm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n"; 528 | 529 | let sdp = SDP::from_str(text); 530 | 531 | assert!(sdp.is_ok()); 532 | 533 | assert_eq!(text, sdp.unwrap().to_string(LineEnding::CRLF)); 534 | } 535 | --------------------------------------------------------------------------------