├── bump.txt ├── werift ├── .dockerignore ├── client.sh ├── client-datachannel.sh ├── tsconfig.json ├── Dockerfile ├── package.json ├── README.md ├── server.ts └── client.ts ├── pion ├── client.sh ├── pion ├── go.mod ├── client │ ├── go.mod │ ├── README.md │ └── main.go ├── Dockerfile ├── README.md └── main.go ├── gstreamer ├── .gitignore ├── CMakeLists.txt ├── README.md ├── Dockerfile ├── Dockerfile-Gstreamerbuilder ├── cJSON-License ├── gstreamer-webrtc-echo.sln ├── gstreamer-webrtc-echo.vcxproj.user ├── index.html └── gstreamer-webrtc-echo.vcxproj ├── libwebrtc ├── .gitignore ├── libwebrtc-webrtc-echo.vcxproj.user ├── Dockerfile ├── buildtest.cpp ├── CMakeLists.txt ├── Dockerfile-Builder ├── HttpSimpleServer.h ├── libwebrtc-webrtc-echo.sln ├── PcObserver.cpp ├── PcFactory.h ├── Dockerfile-AllInOne ├── libwebrtc-webrtc-echo.vcxproj.filters ├── libwebrtc-webrtc-echo.cpp ├── webrtc_lib_link_test.cc ├── webrtc_lib_link_test_libevent.cc ├── PcObserver.h ├── HttpSimpleServer.cpp ├── README.md ├── libwebrtc_linux_build_results.txt ├── PcFactory.cpp └── fake_audio_capture_module.h ├── webrtc-rs ├── client.sh ├── client │ ├── .gitignore │ ├── Cargo.toml │ └── README.md ├── Dockerfile └── server │ ├── Cargo.toml │ └── README.md ├── libdatachannel ├── .gitignore ├── client.sh ├── client-datachannel.sh ├── Dockerfile ├── CMakeLists.txt ├── README.md ├── server.cpp └── client.cpp ├── aiortc ├── client.sh ├── Dockerfile ├── README.md ├── client.py └── server.py ├── sipsorcery ├── client.sh ├── client-datachannel.sh ├── .dockerignore ├── client │ ├── README.md │ ├── webrtc-echo-client.csproj │ └── webrtc-echo-client.sln ├── Dockerfile ├── server │ ├── webrtc-echo.csproj │ ├── README.md │ └── webrtc-echo.sln └── .gitignore ├── janus ├── entrypoint-dotnet.sh ├── Dockerfile ├── JanusWebRTCEcho.csproj.user ├── Properties │ └── PublishProfiles │ │ ├── FolderProfile.pubxml.user │ │ └── FolderProfile.pubxml ├── JanusWebRTCEcho.csproj ├── README.md ├── JanusWebRTCEcho.sln ├── JanusModel.cs └── Program.cs ├── kurento ├── entrypoint-dotnet.sh ├── Dockerfile ├── Properties │ └── PublishProfiles │ │ ├── FolderProfile.pubxml.user │ │ └── FolderProfile.pubxml ├── KurentoWebRTCEcho.csproj.user ├── KurentoWebRTCEcho.csproj ├── README.md ├── KurentoWebRTCEcho.sln └── KurentoModel.cs ├── .gitignore ├── .gitmodules ├── .github └── workflows │ ├── slash-command.yml │ └── datachannel_echo-test.yml ├── DataChannel_Echo_test_results.md ├── .vscode └── launch.json ├── doc ├── EchoTestDockerRequirements.md ├── DataChannelEchoTestSpecification.md └── PeerConnectionTestSpecification.md ├── test └── collate-results.py ├── PeerConnection_test_results.md └── html └── index.html /bump.txt: -------------------------------------------------------------------------------- 1 | 2024-10-07 23:09:00 -------------------------------------------------------------------------------- /werift/.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | -------------------------------------------------------------------------------- /pion/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /app/client $1 -------------------------------------------------------------------------------- /gstreamer/.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | bin/* 3 | x64/* -------------------------------------------------------------------------------- /libwebrtc/.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | bin/* 3 | x64/* -------------------------------------------------------------------------------- /webrtc-rs/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /app/client $1 -------------------------------------------------------------------------------- /libdatachannel/.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | bin/* 3 | x64/* -------------------------------------------------------------------------------- /aiortc/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python /app/client.py $1 3 | -------------------------------------------------------------------------------- /libdatachannel/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /app/client $1 3 | -------------------------------------------------------------------------------- /libdatachannel/client-datachannel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /app/client $1 -t 1 3 | -------------------------------------------------------------------------------- /pion/pion: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery/webrtc-interop/HEAD/pion/pion -------------------------------------------------------------------------------- /sipsorcery/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /usr/bin/dotnet /app/webrtc-echo-client.dll $1 -------------------------------------------------------------------------------- /werift/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DEBUG=werift* node -r ts-node/register client.ts $1 3 | -------------------------------------------------------------------------------- /janus/entrypoint-dotnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /janus-webrtc-echo & 3 | /usr/local/bin/janus 4 | -------------------------------------------------------------------------------- /kurento/entrypoint-dotnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /kurento-webrtc-echo & 3 | /entrypoint.sh 4 | -------------------------------------------------------------------------------- /sipsorcery/client-datachannel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /usr/bin/dotnet /app/webrtc-echo-client.dll $1 -t 1 -------------------------------------------------------------------------------- /werift/client-datachannel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DEBUG=werift* node -r ts-node/register client.ts $1 -t 1 3 | -------------------------------------------------------------------------------- /janus/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM canyan/janus-gateway:latest 2 | 3 | COPY ["bin/Release/net5/publish/JanusWebRTCEcho", "janus-webrtc-echo"] 4 | COPY ["entrypoint-dotnet.sh", "."] 5 | 6 | EXPOSE 8080 7 | ENTRYPOINT ["/entrypoint-dotnet.sh"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/*.exe 3 | **/package-lock.json 4 | .vscode 5 | **/.vs 6 | .ionide 7 | .idea/ 8 | webrtc-rs/client/target/ 9 | webrtc-rs/server/target/ 10 | webrtc-rs/client/Cargo.lock 11 | webrtc-rs/server/Cargo.lock -------------------------------------------------------------------------------- /kurento/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM kurento/kurento-media-server-dev 2 | 3 | COPY ["bin/Release/net5/publish/KurentoWebRTCEcho", "kurento-webrtc-echo"] 4 | COPY ["entrypoint-dotnet.sh", "."] 5 | 6 | EXPOSE 8080 7 | ENTRYPOINT ["/entrypoint-dotnet.sh"] -------------------------------------------------------------------------------- /pion/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sipsorcery/webrtc-echoes/pion 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/google/uuid v1.2.0 // indirect 7 | github.com/pion/webrtc/v3 v3.0.11 8 | golang.org/x/sys v0.0.0-20210217105451-b926d437f341 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /pion/client/go.mod: -------------------------------------------------------------------------------- 1 | module pion-echo 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/google/uuid v1.2.0 // indirect 7 | github.com/pion/rtcp v1.2.6 8 | github.com/pion/webrtc/v3 v3.0.11 9 | golang.org/x/sys v0.0.0-20210217105451-b926d437f341 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /libwebrtc/libwebrtc-webrtc-echo.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libdatachannel/deps/cpp-httplib"] 2 | path = libdatachannel/deps/cpp-httplib 3 | url = https://github.com/yhirose/cpp-httplib.git 4 | [submodule "libdatachannel/deps/libdatachannel"] 5 | path = libdatachannel/deps/libdatachannel 6 | url = https://github.com/paullouisageneau/libdatachannel 7 | -------------------------------------------------------------------------------- /kurento/Properties/PublishProfiles/FolderProfile.pubxml.user: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | True|2021-03-20T23:32:22.2919038Z; 8 | 9 | -------------------------------------------------------------------------------- /janus/JanusWebRTCEcho.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <_LastSelectedProfileId>C:\Dev\sipsorcery\sipsorcery-prototypes\janus-webrtc-echo\Properties\PublishProfiles\FolderProfile.pubxml 5 | 6 | -------------------------------------------------------------------------------- /kurento/KurentoWebRTCEcho.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <_LastSelectedProfileId>C:\Dev\sipsorcery\sipsorcery-prototypes\kurento-webrtc-echo\Properties\PublishProfiles\FolderProfile.pubxml 5 | 6 | -------------------------------------------------------------------------------- /aiortc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9 2 | RUN apt update && apt install -y libopus-dev libsrtp2-dev libavformat-dev libvpx-dev libavdevice-dev libavfilter-dev 3 | WORKDIR /app 4 | COPY ["aiortc", ""] 5 | COPY ["html", "../html/"] 6 | COPY ["aiortc/client.sh", "/client.sh"] 7 | RUN chmod +x /client.sh 8 | RUN pip install aiohttp aiortc 9 | EXPOSE 8080 10 | ENTRYPOINT python server.py 11 | -------------------------------------------------------------------------------- /.github/workflows/slash-command.yml: -------------------------------------------------------------------------------- 1 | name: Slash Command Dispatch 2 | on: 3 | issue_comment: 4 | types: [created] 5 | jobs: 6 | slashCommandDispatch: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Slash Command Dispatch 10 | uses: peter-evans/slash-command-dispatch@v2 11 | with: 12 | token: ${{ secrets.CR_PAT }} 13 | commands: | 14 | echo-test 15 | -------------------------------------------------------------------------------- /pion/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest AS build 2 | WORKDIR /src 3 | COPY ["pion", ""] 4 | RUN go build main.go 5 | WORKDIR /src/client 6 | RUN go build -o client main.go 7 | 8 | FROM golang:latest AS final 9 | WORKDIR /app 10 | COPY --from=build /src/main . 11 | COPY --from=build /src/client/client . 12 | COPY ["html", "../html/"] 13 | COPY ["./pion/client.sh", "/client.sh"] 14 | RUN chmod +x /client.sh 15 | EXPOSE 8080 16 | ENTRYPOINT /app/main -------------------------------------------------------------------------------- /webrtc-rs/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /.idea/ 5 | /crates/target/ 6 | /crates/.idea/ 7 | 8 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 9 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 10 | Cargo.lock 11 | /crates/Cargo.lock 12 | 13 | # These are backup files generated by rustfmt 14 | **/*.rs.bk 15 | -------------------------------------------------------------------------------- /werift/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "lib": ["esnext"], 6 | "strict": true, 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "importHelpers": true, 10 | "pretty": true, 11 | "noUnusedLocals": false, 12 | "skipLibCheck": true, 13 | "strictNullChecks": true, 14 | "noImplicitAny": false 15 | }, 16 | "include": ["."] 17 | } 18 | -------------------------------------------------------------------------------- /sipsorcery/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /pion/client/README.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | A Go application that acts as a peer for a WebRTC Echo Server. 4 | 5 | **Prerequisites** 6 | 7 | - Install the Go language SDK https://golang.org/doc/install 8 | 9 | **Usage** 10 | 11 | By default the in built client will attempt to POST its SDP offer to an echo server at `http://localhost:8080/`. 12 | 13 | - Make sure the echo test server is running. 14 | - `go run main.go` or `go build main.go && main.exe` 15 | -------------------------------------------------------------------------------- /janus/Properties/PublishProfiles/FolderProfile.pubxml.user: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | True|2021-03-25T21:40:42.6775567Z;True|2021-03-25T21:37:10.9546676+00:00;True|2021-03-25T21:32:24.1458745+00:00;True|2021-03-20T23:32:22.2919038+00:00; 8 | 9 | -------------------------------------------------------------------------------- /webrtc-rs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:latest AS build 2 | WORKDIR /src 3 | COPY ["webrtc-rs", ""] 4 | RUN cd /src/server; cargo build 5 | RUN cd /src/client; cargo build 6 | 7 | FROM ubuntu:latest AS final 8 | WORKDIR /app 9 | EXPOSE 8080 10 | #EXPOSE 49000-65535 11 | COPY ["html", "../html/"] 12 | COPY ["./webrtc-rs/client.sh", "/client.sh"] 13 | RUN chmod +x /client.sh 14 | COPY --from=build /src/client/target/debug/client . 15 | COPY --from=build /src/server/target/debug/server . 16 | ENTRYPOINT ["./server"] 17 | -------------------------------------------------------------------------------- /webrtc-rs/client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | webrtc = "=0.8.0" 10 | tokio = { version = "1.12.0", features = ["full"] } 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "1.0" 13 | chrono = "0.4.19" 14 | env_logger = "0.9.0" 15 | log = "0.4.14" 16 | clap = "2" 17 | hyper = { version = "0.14.13", features = ["full"] } 18 | anyhow = "1.0.44" -------------------------------------------------------------------------------- /werift/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | WORKDIR /app 3 | COPY ["werift", ""] 4 | COPY ["html", "../html/"] 5 | COPY ["werift/client.sh", "/client.sh"] 6 | RUN apt-get -y update && \ 7 | apt-get -y upgrade && \ 8 | apt-get -y install libgstreamer1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-pulseaudio 9 | RUN chmod +x /client.sh 10 | RUN npm install 11 | RUN npm run build 12 | EXPOSE 8080 13 | ENTRYPOINT node server.js 14 | -------------------------------------------------------------------------------- /gstreamer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(gstreamer-webrtc-echo VERSION 1.0) 3 | 4 | add_executable(gstreamer-webrtc-echo gstreamer-webrtc-echo.c) 5 | target_sources(gstreamer-webrtc-echo PRIVATE cJSON.c) 6 | 7 | target_include_directories(gstreamer-webrtc-echo PRIVATE 8 | /usr/local/include/gstreamer-1.0 9 | /usr/include/gstreamer-1.0 10 | /usr/include/glib-2.0 11 | /usr/lib/x86_64-linux-gnu/glib-2.0/include) 12 | 13 | TARGET_LINK_LIBRARIES(gstreamer-webrtc-echo 14 | glib-2.0 15 | gobject-2.0 16 | event 17 | gstreamer-full-1.0) 18 | -------------------------------------------------------------------------------- /pion/README.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | A Go application that runs a WebRTC Echo Server. 4 | 5 | **Prerequisites** 6 | 7 | - Install the Go language SDK https://golang.org/doc/install 8 | 9 | **Usage** 10 | 11 | By default the in built web server will listen on `http://*:8080/`. 12 | 13 | `go run main.go` 14 | 15 | An alternative on Windows to avoid the firewall prompting for a new executable accessing the network: 16 | 17 | `go build main.go && main.exe` 18 | 19 | POST an SDP offer to `http://*:8080/offer` or open `http://localhost:8080/` in a browser to use the included `index.html` demo page. 20 | -------------------------------------------------------------------------------- /sipsorcery/client/README.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | A .NET console application that acts as a peer for a WebRTC Echo Server. 4 | 5 | **Prerequisites** 6 | 7 | The .NET 8 SDK needs to be installed as per https://dotnet.microsoft.com/download/dotnet/8.0. 8 | 9 | Note the full SDK install is required, and not the runtime-only option, so as the console application can be built from source. 10 | 11 | **Usage** 12 | 13 | By default the in built client will attempt to POST its SDP offer to an echo server at `http://localhost:8080/`. 14 | 15 | - Make sure the echo test server is running. 16 | - `dotnet run` 17 | -------------------------------------------------------------------------------- /sipsorcery/client/webrtc-echo-client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | webrtc_echo_client 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /libdatachannel/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm AS build 2 | RUN apt-get update 3 | RUN DEBIAN_FRONTEND="noninteractive" apt-get install -y gcc g++ make cmake libssl-dev 4 | COPY libdatachannel /src/ 5 | WORKDIR /src 6 | RUN cmake -B build 7 | WORKDIR /src/build 8 | RUN make -j4 9 | FROM debian:bookworm AS final 10 | RUN apt-get update 11 | RUN DEBIAN_FRONTEND="noninteractive" apt-get install -y libstdc++6 libssl3 12 | WORKDIR /app 13 | COPY --from=build /src/build/client . 14 | COPY --from=build /src/build/server . 15 | COPY html ../html/ 16 | COPY libdatachannel/client.sh /client.sh 17 | RUN chmod +x /client.sh 18 | EXPOSE 8080 19 | ENTRYPOINT /app/server 20 | 21 | -------------------------------------------------------------------------------- /webrtc-rs/server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | webrtc = "=0.8.0" 10 | tokio = { version = "1.12.0", features = ["full"] } 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "1.0" 13 | chrono = "0.4.19" 14 | env_logger = "0.9.0" 15 | log = "0.4.14" 16 | clap = "2" 17 | hyper = { version = "0.14.13", features = ["full"] } 18 | anyhow = "1.0.44" 19 | lazy_static = "1.4" 20 | async-trait = "0.1.42" 21 | tokio-util = { version = "0.6.8", features = ["codec"] } 22 | -------------------------------------------------------------------------------- /sipsorcery/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build 2 | WORKDIR /src 3 | COPY ["./sipsorcery/server", ""] 4 | RUN dotnet publish "webrtc-echo.csproj" -c Release -o /app/publish 5 | WORKDIR /src/client 6 | COPY ["./sipsorcery/client", ""] 7 | RUN dotnet publish "webrtc-echo-client.csproj" -c Release -o /app/publish/client 8 | 9 | FROM mcr.microsoft.com/dotnet/runtime:8.0 AS final 10 | WORKDIR /app 11 | EXPOSE 8080 12 | #EXPOSE 49000-65535 13 | COPY ["html", "../html/"] 14 | COPY ["./sipsorcery/client.sh", "/client.sh"] 15 | RUN chmod +x /client.sh 16 | COPY --from=build /app/publish . 17 | COPY --from=build /app/publish/client . 18 | ENTRYPOINT ["dotnet", "webrtc-echo.dll"] 19 | -------------------------------------------------------------------------------- /janus/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net5\publish\ 10 | FileSystem 11 | net5 12 | linux-x64 13 | true 14 | True 15 | True 16 | 17 | -------------------------------------------------------------------------------- /kurento/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net5\publish\ 10 | FileSystem 11 | net5 12 | linux-x64 13 | true 14 | True 15 | True 16 | 17 | -------------------------------------------------------------------------------- /kurento/KurentoWebRTCEcho.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /aiortc/README.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | A Python console application that runs a WebRTC Echo Server. 4 | 5 | **Prerequisites** 6 | 7 | - python3 8 | - `pip install aiohttp aiortc --upgrade` 9 | 10 | The latest version of aiortc can be found on [pypi.org](https://pypi.org/project/aiort). To check version of the aiortc module: 11 | 12 | - pip freeze | grep aiortc 13 | 14 | **Usage** 15 | 16 | By default the in built web server will listen on `http://*:8080/`. 17 | 18 | `python server.py` 19 | 20 | POST an SDP offer to `http://*:8080/offer` or open `http://localhost:8080/` in a browser to use the included `index.html` demo page. 21 | 22 | **Additional Usage** 23 | 24 | The listening URL can be adjusted with a command line argument: 25 | 26 | `python server.py --port 8081` 27 | -------------------------------------------------------------------------------- /kurento/README.md: -------------------------------------------------------------------------------- 1 | ## Building the kurento webrtc echo test docker image 2 | 3 | `docker build -t kurento-webrtc-echo:0.1 --progress=plain .` 4 | 5 | ## Running docker image 6 | 7 | `docker run -it --init --rm -p 8080:8080 kurento-webrtc-echo:0.1` 8 | 9 | ## Building dotnet app 10 | 11 | For some as yet unknown reason `dotnet` freezes on first run when installed on the `kurento/kurento-media-server-dev` image. The alternative is to publish the application and copy across the binaries. 12 | 13 | The publish command to build the app is: 14 | 15 | `dotnet publish -r linux-x64 --self-contained` 16 | 17 | ** This publish command still produces a long list of individual files. Using the publish profile in Visual Studio is able to produce a single file. Work out how to do this from the command line. 18 | -------------------------------------------------------------------------------- /gstreamer/README.md: -------------------------------------------------------------------------------- 1 | ## Building docker image for static gstreamer build 2 | 3 | `docker build -t gstreamer-builder:0.1 -f Dockerfile-Gstreamerbuilder --progress=plain .` 4 | 5 | ## Building docker image 6 | 7 | `docker build -t gstreamer-webrtc-echo:0.x --progress=plain .` 8 | 9 | ## Running docker image 10 | 11 | `docker run -it --init --rm -p 8080:8080 ghcr.io/sipsorcery/gstreamer-webrtc-echo:latest` 12 | 13 | Set a gstreamer environment variable for additional logging: 14 | 15 | `docker run -it --init --rm -p 8080:8080 -e "GST_DEBUG=4,dtls*:7" ghcr.io/sipsorcery/gstreamer-webrtc-echo:latest` 16 | 17 | Override the gstreamer-echo-app and start a bash shell plus add a local volume mapping: 18 | 19 | `docker run -it -p 8080:8080 -v %cd%:/pcdodo --entrypoint /bin/bash ghcr.io/sipsorcery/gstreamer-webrtc-echo:latest` 20 | -------------------------------------------------------------------------------- /werift/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "werift-echo", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "client": "DEBUG=werift* ts-node-dev client", 9 | "server": "DEBUG=werift* ts-node-dev server", 10 | "type": "tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "debug": "^4.3.1", 14 | "express": "^4.17.1", 15 | "got": "^11.8.2", 16 | "werift": "^0.15.0", 17 | "yargs": "^16.2.0" 18 | }, 19 | "devDependencies": { 20 | "@types/axios": "^0.14.0", 21 | "@types/express": "^4.17.11", 22 | "@types/node": "^16.10.2", 23 | "@types/yargs": "^16.0.0", 24 | "ts-node": "^9.1.1", 25 | "ts-node-dev": "^1.1.1", 26 | "tslib": "^2.1.0", 27 | "typescript": "^4.1.5" 28 | }, 29 | "engines": { 30 | "node": ">=14" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /janus/JanusWebRTCEcho.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /webrtc-rs/client/README.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | A Rust application that acts as a peer for a WebRTC Echo Server. 4 | 5 | **Usage** 6 | 7 | By default the in built client will attempt to POST its SDP offer to an echo server at `http://localhost:8080/`. 8 | 9 | - Make sure the echo test server is running. 10 | - `cd webrtc-rs/client` 11 | - `cargo run` or `cargo build && ./target/debug/client` 12 | - For more details, run './target/debug/client -h' 13 | ``` 14 | An example of webrtc-rs echo client. 15 | 16 | USAGE: 17 | client [FLAGS] [OPTIONS] 18 | 19 | FLAGS: 20 | --fullhelp Prints more detailed help information 21 | -d, --debug Prints debug log information 22 | -h, --help Prints help information 23 | -V, --version Prints version information 24 | 25 | OPTIONS: 26 | --server Echo HTTP server is hosted on. [default: localhost:8080] 27 | ``` -------------------------------------------------------------------------------- /DataChannel_Echo_test_results.md: -------------------------------------------------------------------------------- 1 | Test run at 2025-07-18 20:57:07.759641 2 | 3 | | Server | libdatachannel | sipsorcery | werift | 4 | |--------|--------|--------|--------| 5 | | libdatachannel | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 6 | | sipsorcery | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 7 | | werift | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 8 | -------------------------------------------------------------------------------- /sipsorcery/server/webrtc-echo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | webrtc_echo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /webrtc-rs/server/README.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | A Rust application that runs a WebRTC Echo Server. 4 | 5 | **Usage** 6 | 7 | By default the in built web server will listen on `http://*:8080/`. 8 | 9 | - `cd webrtc-rs/server` 10 | - `cargo run` or `cargo build && ./target/debug/server` 11 | 12 | POST an SDP offer to `http://*:8080/offer` or open `http://localhost:8080/` in a browser to use the included `index.html` demo page. 13 | 14 | ``` 15 | An example of webrtc-rs echo server. 16 | 17 | USAGE: 18 | server [FLAGS] [OPTIONS] 19 | 20 | FLAGS: 21 | --fullhelp Prints more detailed help information 22 | -d, --debug Prints debug log information 23 | -h, --help Prints help information 24 | -V, --version Prints version information 25 | 26 | OPTIONS: 27 | --host Echo HTTP server is hosted on. [default: 0.0.0.0:8080] 28 | --html-file Html file to be displayed. [default: ../../html/index.html] 29 | ``` -------------------------------------------------------------------------------- /sipsorcery/server/README.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | A .NET console application that runs a WebRTC Echo Server. 4 | 5 | **Prerequisites** 6 | 7 | The .NET 8 SDK needs to be installed as per https://dotnet.microsoft.com/download/dotnet/8.0. 8 | 9 | Note the full SDK install is required, and not the runtime-only option, so as the console application can be built from source. 10 | 11 | **Usage** 12 | 13 | By default the in built web server will listen on `http://*:8080/`. 14 | 15 | `dotnet run` 16 | 17 | POST an SDP offer to `http://*:8080/offer` or open `http://localhost:8080/` in a browser to use the included `index.html` demo page. 18 | 19 | **Additional Usage** 20 | 21 | The listening URL can be adjusted with a command line argument: 22 | 23 | `dotnet run -- http://*:8081` 24 | 25 | Preset IP addresses can also be provided to insert static ICE candidates into the SDP answer generated by the Echo Server: 26 | 27 | `dotnet run -- http://*:8080 20.73.112.13 2603:1020:203:3::6` 28 | -------------------------------------------------------------------------------- /gstreamer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gstreamer-builder:latest as builder 2 | FROM ubuntu:latest as final 3 | 4 | # Install packages with the required runtime libraries. 5 | RUN apt update && DEBIAN_FRONTEND="noninteractive" apt install -y \ 6 | build-essential cmake libglib2.0-dev libevent-dev \ 7 | libsoup2.4-1 libvpx-dev libgupnp-igd-1.0-4 libsrtp2-dev \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | COPY --from=builder /usr/local/include/gstreamer-1.0 /usr/include 11 | COPY --from=builder /usr/local/lib/x86_64-linux-gnu/libgstreamer-full-1.0.so /usr/lib/ 12 | #TODO: Determine apt package for libdssim. 13 | COPY --from=builder /usr/local/lib/x86_64-linux-gnu/libdssim-lib.so /usr/lib/libdssim-lib.so.1 14 | 15 | WORKDIR /src/gstreamer-webrtc-echo 16 | COPY ["cJSON.c", "cJSON.h", "CMakeLists.txt", "gstreamer-webrtc-echo.c", "./"] 17 | WORKDIR /src/gstreamer-webrtc-echo/builddir 18 | RUN cmake .. && make && cp gstreamer-webrtc-echo / 19 | WORKDIR / 20 | 21 | EXPOSE 8080 22 | ENTRYPOINT ["/gstreamer-webrtc-echo"] -------------------------------------------------------------------------------- /gstreamer/Dockerfile-Gstreamerbuilder: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | 3 | # Install packages and build system for gstreamer build and app dependencies. 4 | RUN apt update && DEBIAN_FRONTEND="noninteractive" apt install -y \ 5 | bison build-essential cmake flex git libevent-dev libglib2.0-dev libmbedtls-dev \ 6 | libnice-dev libsrtp2-dev libssl-dev libvpx-dev ninja-build python-is-python3 \ 7 | python3-pip 8 | RUN python -m pip install meson 9 | 10 | # Copy libnice plugin patch file. 11 | WORKDIR /src 12 | 13 | # Build gstreamer 14 | RUN git clone https://gitlab.freedesktop.org/gstreamer/gst-build.git --depth 1 gst-build 15 | WORKDIR /src/gst-build 16 | RUN meson -Dgood=enabled -Dgst-plugins-good:vpx=enabled \ 17 | -Dgst-plugins-good:rtpmanager=enabled \ 18 | -Dbad=enabled -Dgst-plugins-bad:dtls=enabled \ 19 | -Dbad=enabled -Dgst-plugins-bad:srtp=enabled \ 20 | -Dbad=enabled -Dgst-plugins-bad:webrtc=enabled \ 21 | -Dintrospection=disabled \ 22 | --default-library=static \ 23 | builddir 24 | RUN ninja -C builddir 25 | RUN meson install -C builddir && ldconfig 26 | -------------------------------------------------------------------------------- /libdatachannel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | project(webrtc-libdatachannel 3 | VERSION 0.1.0 4 | LANGUAGES CXX) 5 | set(PROJECT_DESCRIPTION "WebRTC Data Channels Library") 6 | 7 | # Dependencies 8 | 9 | option(NO_WEBSOCKET "Disable WebSocket support for libdatachannel" ON) 10 | add_subdirectory(deps/libdatachannel EXCLUDE_FROM_ALL) 11 | add_subdirectory(deps/cpp-httplib EXCLUDE_FROM_ALL) 12 | 13 | # Client 14 | 15 | add_executable(webrtc-libdatachannel-client client.cpp) 16 | set_target_properties(webrtc-libdatachannel-client PROPERTIES 17 | VERSION ${PROJECT_VERSION} 18 | CXX_STANDARD 17 19 | OUTPUT_NAME client) 20 | 21 | target_link_libraries(webrtc-libdatachannel-client datachannel-static httplib nlohmann_json) 22 | 23 | # Server 24 | 25 | add_executable(webrtc-libdatachannel-server server.cpp) 26 | set_target_properties(webrtc-libdatachannel-server PROPERTIES 27 | VERSION ${PROJECT_VERSION} 28 | CXX_STANDARD 17 29 | OUTPUT_NAME server) 30 | 31 | target_link_libraries(webrtc-libdatachannel-server datachannel-static httplib nlohmann_json) 32 | 33 | -------------------------------------------------------------------------------- /gstreamer/cJSON-License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2017 Dave Gamble and cJSON contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /gstreamer/gstreamer-webrtc-echo.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31019.35 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gstreamer-webrtc-echo", "gstreamer-webrtc-echo.vcxproj", "{76A3E204-6EBB-4F61-A752-1BF68EC98EA9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {76A3E204-6EBB-4F61-A752-1BF68EC98EA9}.Debug|x64.ActiveCfg = Debug|x64 15 | {76A3E204-6EBB-4F61-A752-1BF68EC98EA9}.Debug|x64.Build.0 = Debug|x64 16 | {76A3E204-6EBB-4F61-A752-1BF68EC98EA9}.Release|x64.ActiveCfg = Release|x64 17 | {76A3E204-6EBB-4F61-A752-1BF68EC98EA9}.Release|x64.Build.0 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {C7EF2E61-678E-4EDB-AE59-802FF05842FA} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /janus/README.md: -------------------------------------------------------------------------------- 1 | ## Building the janus webrtc echo test docker image 2 | 3 | `docker build -t janus-webrtc-echo:0.1 --progress=plain .` 4 | 5 | ## Running docker image 6 | 7 | `docker run -it --init --rm -p 8080:8080 janus-webrtc-echo:0.1` 8 | 9 | ## Base Image 10 | 11 | The canyanio janus gateway docker image is being used. 12 | 13 | Docker image: 14 | 15 | https://hub.docker.com/r/canyan/janus-gateway 16 | 17 | Dockerfile: 18 | 19 | https://github.com/canyanio/janus-gateway-docker/blob/master/Dockerfile 20 | 21 | 22 | ## Building dotnet app 23 | 24 | As with the Kurento docker image For some as yet unknown reason `dotnet` freezes on first run when installed on the `canyan/janus-gateway:latest` image (at this point starting to suspect it's a problem between docker and WSL rather than the images). The alternative is to publish the application and copy across the binaries. 25 | 26 | The publish command to build the app is: 27 | 28 | `dotnet publish -r linux-x64 --self-contained` 29 | 30 | ** This publish command still produces a long list of individual files. Using the publish profile in Visual Studio is able to produce a single file. Work out how to do this from the command line. 31 | -------------------------------------------------------------------------------- /gstreamer/gstreamer-webrtc-echo.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | GST_DEBUG=2 8 | WindowsLocalDebugger 9 | 10 | 11 | GST_DEBUG=2 12 | WindowsLocalDebugger 13 | 14 | 15 | GST_DEBUG=2 16 | WindowsLocalDebugger 17 | 18 | 19 | GST_DEBUG=2 20 | WindowsLocalDebugger 21 | 22 | -------------------------------------------------------------------------------- /janus/JanusWebRTCEcho.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31019.35 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JanusWebRTCEcho", "JanusWebRTCEcho.csproj", "{345922FB-A57B-46EE-B5B4-28243D230497}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {345922FB-A57B-46EE-B5B4-28243D230497}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {345922FB-A57B-46EE-B5B4-28243D230497}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {345922FB-A57B-46EE-B5B4-28243D230497}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {345922FB-A57B-46EE-B5B4-28243D230497}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {91BD28B3-DD3D-4655-B004-2138FA9DAC01} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /sipsorcery/server/webrtc-echo.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30524.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "webrtc-echo", "webrtc-echo.csproj", "{FCB6D8B1-6788-4560-B10F-3238C6A2DF98}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {FCB6D8B1-6788-4560-B10F-3238C6A2DF98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {FCB6D8B1-6788-4560-B10F-3238C6A2DF98}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {FCB6D8B1-6788-4560-B10F-3238C6A2DF98}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {FCB6D8B1-6788-4560-B10F-3238C6A2DF98}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {633A2FEE-1E6C-46B7-877F-F516797D79CA} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /kurento/KurentoWebRTCEcho.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31019.35 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KurentoWebRTCEcho", "KurentoWebRTCEcho.csproj", "{4630C008-225B-4D42-917E-BD2DB7E70B87}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {4630C008-225B-4D42-917E-BD2DB7E70B87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {4630C008-225B-4D42-917E-BD2DB7E70B87}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {4630C008-225B-4D42-917E-BD2DB7E70B87}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {4630C008-225B-4D42-917E-BD2DB7E70B87}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {91BD28B3-DD3D-4655-B004-2138FA9DAC01} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /sipsorcery/client/webrtc-echo-client.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.11.35208.52 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "webrtc-echo-client", "webrtc-echo-client.csproj", "{64DC526E-97AA-493A-B78D-4DCE95DD1799}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {64DC526E-97AA-493A-B78D-4DCE95DD1799}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {64DC526E-97AA-493A-B78D-4DCE95DD1799}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {64DC526E-97AA-493A-B78D-4DCE95DD1799}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {64DC526E-97AA-493A-B78D-4DCE95DD1799}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {D6F33CAD-28F8-41B8-84D3-B0E19564546B} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /libdatachannel/README.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | C++17 WebRTC echo client and server with libdatachannel. 4 | 5 | **Prerequisites** 6 | 7 | You need cmake and the development libraries with header files for either OpenSSL or GnuTLS. On Debian/Ubuntu, you can install them with either `$ apt install libssl-dev` or `$ apt install libgnutls28-dev`. 8 | 9 | Additionally, be sure the submodules are updated with `git submodule update --init --recursive`. 10 | 11 | For building on Windows vcpkg can be used to install the required dependencies: 12 | 13 | `vcpkg install --triplet=x64-windows openssl zlib` 14 | 15 | If cmake fails to find the vcpkg dependencies try passing the vcpkg include file (run `vcpkg integrate install` to print out the path): 16 | 17 | `cmake -DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake -B build` 18 | 19 | **Build** 20 | 21 | `$ cmake -B build` for OpenSSL or `$ cmake -B build -DUSE_GNUTLS=1` for GnuTLS 22 | 23 | `$ (cd build; make -j4)` 24 | 25 | For Windows: 26 | 27 | `msbuild build\webrtc-libdatachannel.sln` 28 | 29 | **Usage** 30 | 31 | Server: `$ build/server [PORT]` 32 | 33 | Client: `$ build/client [URL]` 34 | 35 | The server listens on port 8080 by default and the client uses the URL http://127.0.0.1:8080/offer by default. 36 | 37 | -------------------------------------------------------------------------------- /libwebrtc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM libwebrtc-builder:m132 as builder 2 | FROM ubuntu:latest as appbuilder 3 | 4 | # Install packages with the required runtime libraries. 5 | RUN apt update && DEBIAN_FRONTEND="noninteractive" apt install -y \ 6 | build-essential cmake libglib2.0-dev libx11-dev libevent-dev clang-18 lld-18 7 | 8 | RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-18 100 9 | RUN update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-18 100 10 | RUN update-alternatives --install /usr/bin/llvm-nm llvm-nm /usr/bin/llvm-nm-18 100 11 | RUN update-alternatives --install /usr/bin/ld ld /usr/bin/ld.lld-18 100 12 | 13 | COPY --from=builder /src/webrtc-checkout/src /src/webrtc-checkout/src 14 | 15 | WORKDIR /src/libwebrtc-webrtc-echo 16 | COPY ["CMakeLists.txt", "fake_audio_capture_module.*", "HttpSimpleServer.*", "json.hpp", "libwebrtc-webrtc-echo.cpp", "PcFactory.*", "PcObserver.*", "./"] 17 | WORKDIR /src/libwebrtc-webrtc-echo/build 18 | RUN cmake .. && make VERBOSE=1 && cp libwebrtc-webrtc-echo / 19 | 20 | FROM ubuntu:latest as final 21 | 22 | # Install packages with the required runtime libraries. 23 | RUN apt update && DEBIAN_FRONTEND="noninteractive" apt install -y \ 24 | libevent-dev libx11-dev libglib2.0-dev libatomic1 --no-install-recommends 25 | 26 | COPY --from=appbuilder /libwebrtc-webrtc-echo /libwebrtc-webrtc-echo 27 | 28 | EXPOSE 8080 29 | ENTRYPOINT ["/libwebrtc-webrtc-echo"] -------------------------------------------------------------------------------- /werift/README.md: -------------------------------------------------------------------------------- 1 | # WebRTC Echo Server 2 | 3 | **Description** 4 | 5 | A Node Typescript application that runs a WebRTC Echo Server. 6 | 7 | **Prerequisites** 8 | 9 | - Node.js version 14+ 10 | - yarn (or npm) 11 | - `yarn install` (or `npm install`) 12 | 13 | **Usage** 14 | 15 | By default the in built web server will listen on `http://*:8080/`. 16 | 17 | `yarn server` (or `npm run server`) 18 | 19 | POST an SDP offer to `http://*:8080/offer` or open `http://localhost:8080/` in a browser to use the included `index.html` demo page. 20 | 21 | # WebRTC Echo Client 22 | 23 | **Description** 24 | 25 | A Node Typescript application that acts as a peer for a WebRTC Echo Server. 26 | 27 | **Prerequisites** 28 | 29 | - ffmpeg 30 | - GStreamer 31 | - Node.js version 14+ 32 | - yarn (or npm) 33 | - `yarn install` (or `npm install`) 34 | 35 | **Usage** 36 | 37 | By default the in built client will attempt to POST its SDP offer to an echo server at `http://localhost:8080/`. 38 | 39 | - Make sure the echo test server is running. 40 | - `ffmpeg -re -f lavfi -i testsrc=size=640x480:rate=30 -vcodec libvpx -keyint_min 30 -cpu-used 5 -deadline 1 -g 10 -error-resilient 1 -auto-alt-ref 1 -f rtp rtp://127.0.0.1:5000` 41 | - `gst-launch-1.0 -v udpsrc port=4002 caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)VP8, payload=(int)97" ! rtpvp8depay ! decodebin ! videoconvert ! autovideosink` 42 | - `yarn client` (or `npm run client`) 43 | -------------------------------------------------------------------------------- /libwebrtc/buildtest.cpp: -------------------------------------------------------------------------------- 1 | //#include 2 | //#include 3 | #include 4 | //#include 5 | //#include 6 | 7 | #include 8 | 9 | int main() 10 | { 11 | std::cout << "webrtc native build test" << std::endl; 12 | 13 | //auto factory = webrtc::CreatePeerConnectionFactory( 14 | // nullptr /* network_thread */, 15 | // nullptr /* worker_thread */, 16 | // nullptr /* signaling_thread */, 17 | // nullptr /* default_adm */, 18 | // nullptr, //webrtc::CreateBuiltinAudioEncoderFactory(), 19 | // nullptr, //webrtc::CreateBuiltinAudioDecoderFactory(), 20 | // nullptr, //webrtc::CreateBuiltinVideoEncoderFactory(), 21 | // nullptr, //webrtc::CreateBuiltinVideoDecoderFactory(), 22 | // nullptr /* audio_mixer */, 23 | // nullptr /* audio_processing */, 24 | // nullptr); 25 | 26 | //webrtc::PeerConnectionInterface::RTCConfiguration config; 27 | //auto pc = factory->CreatePeerConnection(config, nullptr, nullptr, nullptr); 28 | 29 | webrtc::SdpParseError sdpError; 30 | auto remoteOffer = webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, "", &sdpError); 31 | 32 | std::cout << "sdp parse error: " << sdpError.description << std::endl; 33 | 34 | std::cout << "Finished." << std::endl; 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /libwebrtc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | set(CMAKE_C_COMPILER clang) 4 | set(CMAKE_CXX_COMPILER clang++) 5 | set(CMAKE_LINKER ld.lld) 6 | set(CMAKE_CXX_STANDARD 20) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -frtti") 9 | 10 | project(libwebrtc-webrtc-echo VERSION 1.0) 11 | 12 | add_executable(libwebrtc-webrtc-echo libwebrtc-webrtc-echo.cpp) 13 | target_sources(libwebrtc-webrtc-echo PRIVATE 14 | fake_audio_capture_module.cc 15 | HttpSimpleServer.cpp 16 | PcFactory.cpp 17 | PcObserver.cpp) 18 | 19 | add_definitions(-D_LIBCPP_ABI_UNSTABLE -D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS -D_LIBCPP_DEBUG=0 -DWEBRTC_LINUX -DWEBRTC_POSIX -DUSE_AURA=1 -D_HAS_EXCEPTIONS=0 -D_SILENCE_ALL_CXX20_DEPRECATION_WARNINGS) 20 | 21 | # SET(CMAKE_CXX_FLAGS "-fstack-protector -funwind-tables -fPIC -O0 -g2 -std=c++20") 22 | set(CMAKE_CXX_FLAGS "-fstack-protector -funwind-tables -fPIC -frtti -std=c++20") 23 | 24 | target_include_directories(libwebrtc-webrtc-echo PRIVATE 25 | /src/webrtc-checkout/src 26 | /src/webrtc-checkout/src/third_party/abseil-cpp) 27 | 28 | SET(CMAKE_EXE_LINKER_FLAGS "-z noexecstack -z relro -z now -pie") 29 | 30 | #link_directories( /src/webrtc-checkout/src/out/Default/obj) 31 | 32 | target_link_libraries(libwebrtc-webrtc-echo 33 | -L/src/webrtc-checkout/src/out/Default/obj 34 | event 35 | webrtc # This will link to libwebrtc.a from the Builder image. 36 | dl 37 | pthread 38 | X11 39 | glib-2.0 40 | stdc++ 41 | atomic) 42 | -------------------------------------------------------------------------------- /kurento/KurentoModel.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Filename: KurentoModel.cs 3 | // 4 | // Description: Object model to allow basic WebRTC test with the Kurento media 5 | // server https://www.kurento.org/. 6 | // 7 | // Author(s): 8 | // Aaron Clauson (aaron@sipsorcery.com) 9 | // 10 | // History: 11 | // 16 Mar 2021 Aaron Clauson Created, Dublin, Ireland. 12 | // 13 | // License: 14 | // BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. 15 | //----------------------------------------------------------------------------- 16 | 17 | namespace KurentoEchoClient 18 | { 19 | public enum KurentoMethodsEnum 20 | { 21 | create, 22 | invoke, 23 | ping, 24 | release, 25 | subscribe, 26 | unsubscribe, 27 | } 28 | 29 | public class KurentoResult 30 | { 31 | public string value { get; set; } 32 | public string sessionId { get; set; } 33 | } 34 | 35 | public class KurentoEvent 36 | { 37 | public KurentoEventData data { get; set; } 38 | public string @object { get; set;} 39 | public string type { get; set; } 40 | } 41 | 42 | public class KurentoEventData 43 | { 44 | public string source { get; set; } 45 | public string[] tags { get; set; } 46 | public string timestamp { get; set; } 47 | public string timestampMillis { get; set; } 48 | public string type { get; set; } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /libwebrtc/Dockerfile-Builder: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | # Install packages for libwebrtc build and app dependencies. 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | RUN echo "tzdata tzdata/Areas select Europe" | debconf-set-selections && \ 6 | echo "tzdata tzdata/Zones/Europe select Dublin" | debconf-set-selections 7 | RUN apt update && apt install -y tzdata 8 | RUN echo "keyboard-configuration keyboard-configuration/layoutcode string us" | debconf-set-selections && \ 9 | echo "keyboard-configuration keyboard-configuration/modelcode string pc105" | debconf-set-selections 10 | RUN apt update && apt install -y keyboard-configuration 11 | RUN apt update && apt install -y curl git lsb-release python3 python-is-python3 sudo wget xz-utils file 12 | 13 | # Install depot_tools 14 | WORKDIR /src 15 | 16 | # https://webrtc.googlesource.com/src/+/refs/heads/master/docs/native-code/development/prerequisite-sw/index.md 17 | RUN git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git --depth 1 depot_tools 18 | ENV PATH="/src/depot_tools:${PATH}" 19 | 20 | # See the README.md file in the same directory as this docker build file for more explanations and links. 21 | WORKDIR /src/webrtc-checkout 22 | RUN fetch --nohooks webrtc 23 | WORKDIR /src/webrtc-checkout/src 24 | RUN git fetch --all 25 | RUN git checkout remotes/branch-heads/6834 -b m132 # 6834 == m132, see https://chromiumdash.appspot.com/branches. 26 | RUN gclient sync 27 | RUN build/install-build-deps.sh 28 | RUN gn gen out/Default --args='is_debug=false rtc_include_tests=false treat_warnings_as_errors=false use_custom_libcxx=false use_rtti=true' 29 | RUN autoninja -C out/Default 30 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "werift:server", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/werift/node_modules/ts-node-dev/lib/bin.js", 9 | "protocol": "inspector", 10 | "args": [ 11 | "--project", 12 | "${workspaceRoot}/werift/tsconfig.json", 13 | "${workspaceRoot}/werift/server.ts" 14 | ], 15 | "env": { "DEBUG": "werift*" }, 16 | "console": "integratedTerminal", 17 | "internalConsoleOptions": "neverOpen" 18 | }, 19 | { 20 | "name": "werift:client", 21 | "type": "node", 22 | "request": "launch", 23 | "program": "${workspaceRoot}/werift/node_modules/ts-node-dev/lib/bin.js", 24 | "protocol": "inspector", 25 | "args": [ 26 | "--project", 27 | "${workspaceRoot}/werift/tsconfig.json", 28 | "${workspaceRoot}/werift/client.ts" 29 | ], 30 | "env": { "DEBUG": "werift*" }, 31 | "console": "integratedTerminal", 32 | "internalConsoleOptions": "neverOpen" 33 | }, 34 | { 35 | "name": "werift:client-datachannel", 36 | "type": "node", 37 | "request": "launch", 38 | "program": "${workspaceRoot}/werift/node_modules/ts-node-dev/lib/bin.js", 39 | "protocol": "inspector", 40 | "args": [ 41 | "--project", 42 | "${workspaceRoot}/werift/tsconfig.json", 43 | "${workspaceRoot}/werift/client.ts", 44 | "-t", 45 | "1" 46 | ], 47 | "env": { "DEBUG": "werift*" }, 48 | "console": "integratedTerminal", 49 | "internalConsoleOptions": "neverOpen" 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /libwebrtc/HttpSimpleServer.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Filename: HttpSimpleServer.h 3 | * 4 | * Description: 5 | * Simple HTTP server that's designed to only process a small number of 6 | * expected requests. No attempt is made to behave as a generic HTTP server. 7 | * 8 | * Author: 9 | * Aaron Clauson (aaron@sipsorcery.com) 10 | * 11 | * History: 12 | * 08 Mar 2021 Aaron Clauson Created, Dublin, Ireland. 13 | * 21 Dec 2024 Aaron Clauson Updated for libwebrtc version m132. 14 | * 15 | * License: Public Domain (no warranty, use at own risk) 16 | /******************************************************************************/ 17 | 18 | #ifndef __HTTP_SIMPLE_SERVER__ 19 | #define __HTTP_SIMPLE_SERVER__ 20 | 21 | #include "PcFactory.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | class HttpSimpleServer 37 | { 38 | public: 39 | HttpSimpleServer(); 40 | ~HttpSimpleServer(); 41 | void Init(const char * httpServerAddress, int httpServerPort, const char * offerPath); 42 | void Run(); 43 | void Stop(); 44 | 45 | static void SetPeerConnectionFactory(PcFactory* pcFactory); 46 | 47 | private: 48 | event_base* _evtBase; 49 | evhttp* _httpSvr; 50 | event* _signalEvent; 51 | bool _isDisposed; 52 | 53 | static PcFactory* _pcFactory; 54 | 55 | static void OnHttpRequest(struct evhttp_request* req, void* arg); 56 | static void OnSignal(evutil_socket_t sig, short events, void* user_data); 57 | }; 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /werift/server.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { RTCPeerConnection } from "werift"; 3 | import * as yargs from "yargs"; 4 | import https from "https"; 5 | import { readFileSync } from "fs"; 6 | 7 | const args = yargs 8 | .option("host", { 9 | description: "Host for HTTP server (default: 0.0.0.0)", 10 | default: "0.0.0.0", 11 | }) 12 | .option("port", { 13 | description: "Port for HTTP server (default: 8080)", 14 | default: 8080, 15 | }) 16 | .option("cert-file", { description: "SSL certificate file (for HTTPS)" }) 17 | .option("key-file", { description: "SSL key file (for HTTPS)" }) 18 | .option("static", {}) 19 | .help().argv; 20 | 21 | console.log(args); 22 | 23 | const app = express(); 24 | app.use(express.json()); 25 | if (args["cert-file"] && args["key-file"]) { 26 | https 27 | .createServer( 28 | { 29 | cert: readFileSync(args["cert-file"] as string), 30 | key: readFileSync(args["key-file"] as string), 31 | }, 32 | app 33 | ) 34 | .listen(args.port, args.host); 35 | } else { 36 | app.listen(args.port, args.host); 37 | } 38 | app.use(express.static((args.static as string) || "../html")); 39 | 40 | app.post("/offer", async (req, res) => { 41 | const offer = req.body; 42 | console.log("offer", offer); 43 | 44 | const pc = new RTCPeerConnection({ 45 | iceServers: [{ urls: "stun:stun.l.google.com:19302" }], 46 | }); 47 | pc.onRemoteTransceiverAdded.subscribe(async (transceiver) => { 48 | const [track] = await transceiver.onTrack.asPromise(); 49 | pc.addTrack(track); 50 | }); 51 | pc.onDataChannel.subscribe((dc) => { 52 | dc.message.subscribe((msg) => { 53 | console.log("datachannel incoming message", msg); 54 | dc.send(msg); 55 | }); 56 | }); 57 | 58 | await pc.setRemoteDescription(offer); 59 | const answer = await pc.setLocalDescription(await pc.createAnswer()); 60 | res.send(answer); 61 | }); 62 | -------------------------------------------------------------------------------- /libwebrtc/libwebrtc-webrtc-echo.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.12.35527.113 d17.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libwebrtc-webrtc-echo", "libwebrtc-webrtc-echo.vcxproj", "{D52A1111-536E-44F2-AA70-B322343FD453}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Win32 = Release|Win32 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {D52A1111-536E-44F2-AA70-B322343FD453}.Debug|Win32.ActiveCfg = Debug|Win32 19 | {D52A1111-536E-44F2-AA70-B322343FD453}.Debug|Win32.Build.0 = Debug|Win32 20 | {D52A1111-536E-44F2-AA70-B322343FD453}.Debug|x64.ActiveCfg = Debug|x64 21 | {D52A1111-536E-44F2-AA70-B322343FD453}.Debug|x64.Build.0 = Debug|x64 22 | {D52A1111-536E-44F2-AA70-B322343FD453}.Debug|x86.ActiveCfg = Debug|Win32 23 | {D52A1111-536E-44F2-AA70-B322343FD453}.Debug|x86.Build.0 = Debug|Win32 24 | {D52A1111-536E-44F2-AA70-B322343FD453}.Release|Win32.ActiveCfg = Release|Win32 25 | {D52A1111-536E-44F2-AA70-B322343FD453}.Release|Win32.Build.0 = Release|Win32 26 | {D52A1111-536E-44F2-AA70-B322343FD453}.Release|x64.ActiveCfg = Release|x64 27 | {D52A1111-536E-44F2-AA70-B322343FD453}.Release|x64.Build.0 = Release|x64 28 | {D52A1111-536E-44F2-AA70-B322343FD453}.Release|x86.ActiveCfg = Release|Win32 29 | {D52A1111-536E-44F2-AA70-B322343FD453}.Release|x86.Build.0 = Release|Win32 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {A0CF31D4-A448-41A5-BA4A-D3D4AE509A88} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /libwebrtc/PcObserver.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Filename: PcObserver.cpp 3 | * 4 | * Description: See header file. 5 | * 6 | * Author : 7 | * Aaron Clauson (aaron@sipsorcery.com) 8 | * 9 | * History : 10 | * 08 Mar 2021 Aaron Clauson Created, Dublin, Ireland. 11 | * 21 Dec 2024 Aaron Clauson Updated for libwebrtc version m132. 12 | * 13 | * License: Public Domain (no warranty, use at own risk) 14 | /******************************************************************************/ 15 | 16 | #include "PcObserver.h" 17 | 18 | #include 19 | 20 | void PcObserver::OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state) 21 | { 22 | std::cout << "OnSignalingChange " << new_state << "." << std::endl; 23 | } 24 | 25 | void PcObserver::OnDataChannel(rtc::scoped_refptr data_channel) 26 | { 27 | std::cout << "OnDataChannel " << data_channel->id() << "." << std::endl; 28 | } 29 | 30 | void PcObserver::OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state) 31 | { 32 | std::cout << "OnIceGatheringChange " << new_state << "." << std::endl; 33 | } 34 | 35 | void PcObserver::OnIceCandidate(const webrtc::IceCandidateInterface* candidate) 36 | { 37 | std::cout << "OnIceCandidate " << candidate->candidate().ToString() << "." << std::endl; 38 | } 39 | 40 | void PcObserver::OnAddTrack( 41 | rtc::scoped_refptr receiver, 42 | const std::vector>& streams) 43 | { 44 | std::cout << "OnAddTrack." << std::endl; 45 | } 46 | 47 | void PcObserver::OnTrack(rtc::scoped_refptr transceiver) 48 | { 49 | std::cout << "OnTrack." << std::endl; 50 | } 51 | 52 | void PcObserver::OnConnectionChange( 53 | webrtc::PeerConnectionInterface::PeerConnectionState new_state) 54 | { 55 | std::cout << "OnConnectionChange to " << (int)new_state << "." << std::endl; 56 | } -------------------------------------------------------------------------------- /libwebrtc/PcFactory.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Filename: PcFactory.h 3 | * 4 | * Description: 5 | * Factory to allow the creation of new WebRTC peer connection instances. 6 | * 7 | * Author: 8 | * Aaron Clauson (aaron@sipsorcery.com) 9 | * 10 | * History: 11 | * 08 Mar 2021 Aaron Clauson Created, Dublin, Ireland. 12 | * 21 Dec 2024 Aaron Clauson Updated for libwebrtc version m132. 13 | * 14 | * License: Public Domain (no warranty, use at own risk) 15 | */ 16 | 17 | #ifndef __PEER_CONNECTION_FACTORY__ 18 | #define __PEER_CONNECTION_FACTORY__ 19 | 20 | #include "PcObserver.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include "absl/base/nullability.h" 26 | #include "api/environment/environment.h" 27 | #include "call/call.h" 28 | #include "call/call_config.h" 29 | #include "pc/media_factory.h" 30 | #include "rtc_base/checks.h" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | class PcFactory { 38 | public: 39 | PcFactory(); 40 | ~PcFactory(); 41 | std::string CreatePeerConnection(const char* buffer, int length); 42 | 43 | /* The thread logic is now tricky. I was not able to get even a basic peer connection 44 | * example working on Windows in debug mode due to the failing thread checks, see 45 | * https://groups.google.com/u/2/g/discuss-webrtc/c/HG9hzDP2djA 46 | // From peer_connection_interface.h line 1632 47 | // If `network_thread` or `worker_thread` are null, the PeerConnectionFactory 48 | // will create the necessary thread internally. If `signaling_thread` is null, 49 | // the PeerConnectionFactory will use the thread on which this method is called 50 | // as the signaling thread, wrapping it in an rtc::Thread object if needed. 51 | */ 52 | std::unique_ptr SignalingThread; 53 | 54 | private: 55 | rtc::scoped_refptr _peerConnectionFactory; 56 | std::vector> _peerConnections; 57 | }; 58 | 59 | #endif -------------------------------------------------------------------------------- /libwebrtc/Dockerfile-AllInOne: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | # Install packages for libwebrtc build and app dependencies. 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | RUN echo "tzdata tzdata/Areas select Europe" | debconf-set-selections && \ 6 | echo "tzdata tzdata/Zones/Europe select Dublin" | debconf-set-selections 7 | RUN apt update && apt install -y tzdata 8 | RUN echo "keyboard-configuration keyboard-configuration/layoutcode string us" | debconf-set-selections && \ 9 | echo "keyboard-configuration keyboard-configuration/modelcode string pc105" | debconf-set-selections 10 | RUN apt update && apt install -y keyboard-configuration 11 | RUN apt update && apt install -y curl git lsb-release python3 python-is-python3 sudo wget xz-utils file 12 | RUN apt update && DEBIAN_FRONTEND="noninteractive" apt install -y \ 13 | build-essential cmake libglib2.0-dev libx11-dev libevent-dev clang-18 lld-18 14 | 15 | # Install depot_tools 16 | WORKDIR /src 17 | 18 | # https://webrtc.googlesource.com/src/+/refs/heads/master/docs/native-code/development/prerequisite-sw/index.md 19 | RUN git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git --depth 1 depot_tools 20 | ENV PATH="/src/depot_tools:${PATH}" 21 | 22 | # See the README.md file in the same directory as this docker build file for more explanations and links. 23 | WORKDIR /src/webrtc-checkout 24 | RUN fetch --nohooks webrtc 25 | WORKDIR /src/webrtc-checkout/src 26 | RUN git fetch --all 27 | RUN git checkout remotes/branch-heads/6834 -b m132 # 6834 == m132, see https://chromiumdash.appspot.com/branches. 28 | ENV DEPOT_TOOLS_WIN_TOOLCHAIN=0 29 | RUN gclient sync 30 | RUN build/install-build-deps.sh 31 | 32 | WORKDIR /src/libwebrtc-webrtc-echo 33 | COPY ["CMakeLists.txt", "fake_audio_capture_module.*", "HttpSimpleServer.*", "json.hpp", "libwebrtc-webrtc-echo.cpp", "PcFactory.*", "PcObserver.*", "./"] 34 | 35 | WORKDIR /src 36 | 37 | RUN gn gen out/Default --args='is_debug=false rtc_include_tests=false treat_warnings_as_errors=false use_custom_libcxx=false use_rtti=true' 38 | RUN autoninja -C out/Default 39 | 40 | WORKDIR /src/libwebrtc-webrtc-echo/build 41 | RUN cmake .. && make VERBOSE=1 && cp libwebrtc-webrtc-echo / -------------------------------------------------------------------------------- /libwebrtc/libwebrtc-webrtc-echo.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Header Files 45 | 46 | 47 | Header Files 48 | 49 | 50 | Header Files 51 | 52 | 53 | Header Files 54 | 55 | 56 | -------------------------------------------------------------------------------- /doc/EchoTestDockerRequirements.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | Once an `Server Peer` or `Client Peer` has succeeded in one or more manual tests with another implementation a Docker image should be created so that it can be included in the automated tests. 4 | 5 | This document outlines the requirements for creating a Docker image. 6 | 7 | ## Example 8 | 9 | The `Dockerfile` for the SIPSorcery library is available [here](../sipsorcery/Dockerfile). 10 | 11 | ```` 12 | FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build 13 | WORKDIR /src 14 | COPY ["./sipsorcery/server", ""] 15 | RUN dotnet publish "webrtc-echo.csproj" -c Release -o /app/publish 16 | WORKDIR /src/client 17 | COPY ["./sipsorcery/client", ""] 18 | RUN dotnet publish "webrtc-echo-client.csproj" -c Release -o /app/publish/client 19 | 20 | FROM mcr.microsoft.com/dotnet/runtime:5.0 AS final 21 | WORKDIR /app 22 | EXPOSE 8080 23 | #EXPOSE 49000-65535 24 | COPY ["html", "../html/"] 25 | COPY ["./sipsorcery/client.sh", "/client.sh"] 26 | RUN chmod +x /client.sh 27 | COPY --from=build /app/publish . 28 | COPY --from=build /app/publish/client . 29 | ENTRYPOINT ["dotnet", "webrtc-echo.dll"] 30 | ```` 31 | 32 | To build use: 33 | 34 | `webrtc-echoes> docker build -t sipsorcery-webrtc-echo:0.4 --progress=plain -f sipsorcery\Dockerfile .` 35 | 36 | 37 | ## Requirements 38 | 39 | The Docker image built from the `Dockerfile` must be capable of being used as either a `Server Peer`, `Client Peer` or, ideally, both. 40 | 41 | The default mode of operation is as a `Server Peer` and the `ENTRYPOINT` in the `Dockerfile` should result in an [Server Peer](PeerConnectionTestSpecification.md) running when a new container is created. An example of how the [GitHub Action Script](../.github/workflows/peerconnection-test.yml) will create the container is: 42 | 43 | `docker run ghcr.io/sipsorcery/sipsorcery-webrtc-echo` 44 | 45 | If the image can also support operating as an `Client Peer` then a file called `client.sh` should exist in the root of the image file system. When a container is being used as in a client role the default `ENTRYPOINT` will be overruled by calling the `/client.sh` script. The script must take a single parameter of the URL of the `Server Peer`. An example of how the [GitHub Action Script](../.github/workflows/interop-peerconnection-echo.yml) will create a container to operate an an Echo Test client is: 46 | 47 | `docker run --entrypoint "/client.sh" ghcr.io/sipsorcery/sipsorcery-webrtc-echo http://echo-server:8080/offer` 48 | -------------------------------------------------------------------------------- /libwebrtc/libwebrtc-webrtc-echo.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Filename: libwebrtc-webrtc-echo.cpp 3 | * 4 | * Description: 5 | * Main program for a test program that creates a echo peer using Google's 6 | * webrtc library, https://webrtc.googlesource.com/src/webrtc/. 7 | * 8 | * Dependencies: 9 | * vcpkg install libevent:x64-windows 10 | * 11 | * Author: 12 | * Aaron Clauson (aaron@sipsorcery.com) 13 | * 14 | * History: 15 | * 08 Mar 2021 Aaron Clauson Created, Dublin, Ireland. 16 | * 21 DEc 2024 Aaron Clauson Updated for libwebrtc version m132. 17 | * 18 | * License: Public Domain (no warranty, use at own risk) 19 | /******************************************************************************/ 20 | 21 | #include "HttpSimpleServer.h" 22 | #include "PcFactory.h" 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #define HTTP_SERVER_ADDRESS "0.0.0.0" 37 | #define HTTP_SERVER_PORT 8080 38 | #define HTTP_OFFER_URL "/offer" 39 | 40 | int main() 41 | { 42 | std::cout << "libwebrtc echo test server" << std::endl; 43 | 44 | #ifdef _WIN32 45 | { 46 | /* If running on Windows need to initialise sockets. */ 47 | WORD wVersionRequested; 48 | WSADATA wsaData; 49 | wVersionRequested = MAKEWORD(2, 2); 50 | WSAStartup(wVersionRequested, &wsaData); 51 | } 52 | #endif 53 | 54 | std::cout << "libevent version " << event_get_version() << "." << std::endl; 55 | 56 | rtc::LogMessage::LogToDebug(rtc::LoggingSeverity::LS_WARNING); 57 | 58 | { 59 | std::cout << "On main thread, thread ID " << std::this_thread::get_id() << std::endl; 60 | 61 | PcFactory pcFactory; 62 | 63 | HttpSimpleServer httpSvr; 64 | httpSvr.Init(HTTP_SERVER_ADDRESS, HTTP_SERVER_PORT, HTTP_OFFER_URL); 65 | HttpSimpleServer::SetPeerConnectionFactory(&pcFactory); 66 | 67 | httpSvr.Run(); 68 | 69 | std::cout << "Stopping HTTP server..." << std::endl; 70 | 71 | httpSvr.Stop(); 72 | } 73 | 74 | #ifdef _WIN32 75 | WSACleanup(); 76 | #endif 77 | 78 | std::cout << "Exiting..." << std::endl; 79 | } -------------------------------------------------------------------------------- /doc/DataChannelEchoTestSpecification.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | The purpose of the `Data Channel Echo Test` is to verify that data channel messages can be sent between two peers: the `Server Peer` and the `Client Peer`. 4 | 5 | This test builds on the [Peer Connection Test](PeerConnectionTestSpecification.md) and uses the same signalling mechanism to exchange the SDP offer and answer. 6 | 7 | ## Server Peer Operation 8 | 9 | The required operation for a `Server Peer` is: 10 | 11 | - Perform the same steps as outlined in the [Peer Connection Test](PeerConnectionTestSpecification.md) to complete the DTLS handshake. An additional requirement is that a data channel will be offered in the `Client Peer` SDP and must be accepted. 12 | - Following the DTLS handshake the SCTP association and data channels must be created. 13 | - The `Server Peer` must then wait for a string message on the `Client Peer's` data channel. Once received it must respond by sending back an identical string on the same data channel the message was received on. 14 | 15 | The `Server Peer` must be capable of being run in the same manner described in the [Echo Test Docker Requirements](EchoTestDockerRequirements.md): 16 | 17 | `docker run ghcr.io/sipsorcery/sipsorcery-webrtc-echo` 18 | 19 | ## Client Peer Operation 20 | 21 | The required operation for a `Client Peer` is: 22 | 23 | - Perform the same steps as outlined in the [Peer Connection Test](dPeerConnectionTestSpecification.md) to complete the DTLS handshake. An additional requirement is that a data channel must be offered in the SDP sent to the `Server Peer`. 24 | - Following the DTLS handshake the SCTP association and data channels should be created. 25 | - Once the data channel is opened a short (4 or 5 characters is sufficient) pseudo-random string should be sent to the `Server Peer`. 26 | - If the `Server Peer` responds with an identical string on the same data channel the client should **Return 0** to indicate a successful test. If the `Server Peer` responds with a different message or the test times out the client should **Return 1**. It's solely up to the `Client Peer` to decide if the test was successful or not. 27 | 28 | The `Client Peer` docker image command line has been expanded from the [Echo Test Docker Requirements](EchoTestDockerRequirements.md). Two additional command line parameters are required: 29 | 30 | `-s`: The URL the `Server Peer` is listening on for the SDP offer. 31 | 32 | `-t`: A number to indicate the test type. The purpose of this parameter is to allow the same client application to be used for multiple tests. For th `Data Channel Echo` test the parameter must be set to `1`. 33 | 34 | `docker run --entrypoint "/client.sh" ghcr.io/sipsorcery/sipsorcery-webrtc-echo "-s -t 1"` 35 | 36 | e.g. 37 | 38 | `docker run --entrypoint "/client.sh" ghcr.io/sipsorcery/sipsorcery-webrtc-echo "-s http://echo-server:8080/offer -t 1"` -------------------------------------------------------------------------------- /test/collate-results.py: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # Filename: collate-results.py 3 | # 4 | # Description: This script collates the results from a set of csv files that 5 | # each contain the result of a single interoperability WebRTC Echo Test. The 6 | # results are combined into a markdown table. 7 | # 8 | # Prerequisites: 9 | # pip install weasyprint 10 | # 11 | # Author(s): 12 | # Aaron Clauson (aaron@sipsorcery.com) 13 | # 14 | # History: 15 | # 19 Feb 2021 Aaron Clauson Created, Dublin, Ireland. 16 | # 21 Aug 2021 Aaron Clauson Investigating breaking change to weasyprint that removed the write to png function, see 17 | # https://www.courtbouillon.org/blog/00004-weasyprint-without-cairo-what-s-different. 18 | # 12 Oct 2023 Aaron Clauson Swtiched from csv file processing to stdin. Removed weasyprint html to png stage. 19 | # 20 | # License: 21 | # BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. 22 | #----------------------------------------------------------------------------- 23 | 24 | import json 25 | import sys 26 | 27 | from collections import defaultdict 28 | from datetime import datetime 29 | 30 | testname = "PeerConnection" 31 | 32 | # Read JSON input from stdin (for GitHub Actions) 33 | input_data = sys.stdin.read() 34 | 35 | # Parse the input JSON 36 | try: 37 | results = json.loads(input_data) 38 | except json.JSONDecodeError as e: 39 | print(f"Failed to parse JSON: {e}") 40 | sys.exit(1) 41 | 42 | # Initialize data structures 43 | serverKeys = [] 44 | clientKeys = [] 45 | 46 | # Prepare the results structure 47 | results_dict = defaultdict(dict) 48 | 49 | # Populate the results_dict with data from the parsed JSON 50 | for key, value in results.items(): 51 | server, client = key.split("_")[1:] # Assuming keys are formatted as "result__" 52 | results_dict[server][client] = value 53 | if server not in serverKeys: 54 | serverKeys.append(server) 55 | if client not in clientKeys: 56 | clientKeys.append(client) 57 | 58 | # Sort the server and client keys 59 | serverKeys.sort() 60 | clientKeys.sort() 61 | 62 | # Print Markdown table header 63 | 64 | print('Test run at %s\n' % datetime.now()) 65 | 66 | # Print Table header row. 67 | print(f'| {"Server": <12} | {" | ".join(clientKeys)} |') 68 | print('|--------|' + '|'.join(['--------'] * len(clientKeys)) + '|') 69 | 70 | # Print Server rows. 71 | for serverKey in serverKeys: 72 | print(f'| {serverKey: <12} ', end='') 73 | for clientKey in clientKeys: 74 | if clientKey in results_dict[serverKey]: 75 | badge_url = "https://img.shields.io/badge/-✔-green" if results_dict[serverKey][clientKey] == '0' else "https://img.shields.io/badge/-✘-red" 76 | resultChar = f'![{badge_url}]({badge_url})' 77 | print(f'| {resultChar} ', end='') 78 | else: 79 | badge_url = "https://img.shields.io/badge/-✘-red" 80 | resultChar = f'![{badge_url}]({badge_url})' 81 | print(f'| {resultChar} ', end='') 82 | print('|') 83 | -------------------------------------------------------------------------------- /libdatachannel/server.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * libdatachannel echo server 3 | * Copyright (c) 2021 Paul-Louis Ageneau 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace http = httplib; 30 | using json = nlohmann::json; 31 | 32 | using namespace std::chrono_literals; 33 | 34 | int main(int argc, char **argv) try { 35 | const std::string host = "0.0.0.0"; 36 | const int port = argc > 1 ? std::atoi(argv[1]) : 8080; 37 | 38 | rtc::InitLogger(rtc::LogLevel::Warning); 39 | 40 | http::Server srv; 41 | srv.Post("/offer", [](const httplib::Request &req, httplib::Response &res) { 42 | auto parsed = json::parse(req.body); 43 | rtc::Description remote(parsed["sdp"].get(), parsed["type"].get()); 44 | 45 | auto pc = std::make_shared(rtc::Configuration{}); 46 | 47 | std::promise promise; 48 | auto future = promise.get_future(); 49 | 50 | pc->onGatheringStateChange([pc, &promise](rtc::PeerConnection::GatheringState state) { 51 | if (state == rtc::PeerConnection::GatheringState::Complete) { 52 | try { 53 | auto local = pc->localDescription().value(); 54 | json msg; 55 | msg["sdp"] = std::string(local); 56 | msg["type"] = local.typeString(); 57 | promise.set_value(msg.dump()); 58 | 59 | } catch (...) { 60 | promise.set_exception(std::current_exception()); 61 | } 62 | } 63 | }); 64 | 65 | pc->onStateChange([pc](rtc::PeerConnection::State state) { 66 | if (state == rtc::PeerConnection::State::Disconnected) { 67 | pc->close(); 68 | } 69 | }); 70 | 71 | pc->onDataChannel([](std::shared_ptr dc) { 72 | dc->onMessage([dc](auto msg) { dc->send(msg); }); 73 | }); 74 | 75 | pc->onTrack([](std::shared_ptr tr) { 76 | tr->onMessage([tr](auto msg) { tr->send(msg); }); 77 | }); 78 | 79 | pc->setRemoteDescription(std::move(remote)); 80 | 81 | auto body = future.get(); 82 | res.set_content(body, "application/json"); 83 | }); 84 | 85 | srv.set_exception_handler([](const http::Request &req, http::Response &res, std::exception_ptr e) { 86 | res.status = 500; 87 | res.set_content("500 Internal Server Error", "text/plain"); 88 | }); 89 | 90 | std::cout << "Listening on " << host << ":" << port << "..." << std::endl; 91 | if (!srv.listen(host.c_str(), port)) 92 | throw std::runtime_error("Failed to listen on port " + std::to_string(port)); 93 | 94 | return 0; 95 | 96 | } catch (const std::exception &e) { 97 | std::cerr << "Fatal error: " << e.what() << std::endl; 98 | return -1; 99 | } 100 | -------------------------------------------------------------------------------- /aiortc/client.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import logging 4 | 5 | import aiohttp 6 | 7 | from aiortc import RTCPeerConnection, RTCSessionDescription 8 | from aiortc.contrib.media import MediaBlackhole, MediaPlayer, MediaRecorder 9 | 10 | 11 | async def send_offer( 12 | url: str, 13 | offer: RTCSessionDescription, 14 | ) -> RTCSessionDescription: 15 | offer_data = {"sdp": offer.sdp, "type": offer.type} 16 | async with aiohttp.ClientSession() as client: 17 | async with client.post(url, json=offer_data) as response: 18 | answer_data = await response.json() 19 | return RTCSessionDescription( 20 | sdp=answer_data["sdp"], type=answer_data["type"] 21 | ) 22 | 23 | 24 | async def run(*, pc, player, recorder, url): 25 | connected = asyncio.Event() 26 | 27 | @pc.on("connectionstatechange") 28 | def on_connectionstatechange(): 29 | print("Connection state %s" % pc.connectionState) 30 | if pc.connectionState == "connected": 31 | connected.set() 32 | 33 | @pc.on("track") 34 | def on_track(track): 35 | print("Receiving %s" % track.kind) 36 | recorder.addTrack(track) 37 | 38 | # add local media 39 | if player and player.audio: 40 | pc.addTrack(player.audio) 41 | else: 42 | pc.addTransceiver("audio") 43 | 44 | if player and player.video: 45 | pc.addTrack(player.video) 46 | 47 | # send offer 48 | await pc.setLocalDescription(await pc.createOffer()) 49 | answer = await send_offer(url, pc.localDescription) 50 | 51 | # apply answer 52 | await pc.setRemoteDescription(answer) 53 | 54 | # wait for connected state 55 | await connected.wait() 56 | 57 | 58 | if __name__ == "__main__": 59 | parser = argparse.ArgumentParser(description="WebRTC Echo Client demo") 60 | parser.add_argument("--play-from", help="Read the media from a file and sent it."), 61 | parser.add_argument("--record-to", help="Write received media to a file."), 62 | parser.add_argument("--verbose", "-v", action="count") 63 | parser.add_argument("url", help="The URL to which the SDP offer is sent."), 64 | args = parser.parse_args() 65 | 66 | if args.verbose: 67 | logging.basicConfig(level=logging.DEBUG) 68 | else: 69 | logging.basicConfig(level=logging.INFO) 70 | 71 | # create peer connection 72 | pc = RTCPeerConnection() 73 | 74 | # create media source 75 | if args.play_from: 76 | player = MediaPlayer(args.play_from) 77 | else: 78 | player = None 79 | 80 | # create media sink 81 | if args.record_to: 82 | recorder = MediaRecorder(args.record_to) 83 | else: 84 | recorder = MediaBlackhole() 85 | 86 | # run event loop 87 | loop = asyncio.get_event_loop() 88 | try: 89 | loop.run_until_complete( 90 | asyncio.wait_for( 91 | run( 92 | pc=pc, 93 | player=player, 94 | recorder=recorder, 95 | url=args.url, 96 | ), 97 | timeout=10, 98 | ) 99 | ) 100 | if args.play_from or args.record_to: 101 | loop.run_forever() 102 | except KeyboardInterrupt: 103 | pass 104 | finally: 105 | # cleanup 106 | loop.run_until_complete(recorder.stop()) 107 | loop.run_until_complete(pc.close()) 108 | -------------------------------------------------------------------------------- /werift/client.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MediaStreamTrack, 3 | RTCPeerConnection, 4 | RtpPacket, 5 | randomPort, 6 | } from "werift"; 7 | import got from "got"; 8 | import * as yargs from "yargs"; 9 | import { setTimeout } from "timers/promises"; 10 | import { exec } from "child_process"; 11 | import { createSocket } from "dgram"; 12 | 13 | const TestType = { PeerConnection: 0, DataChannelEcho: 1 }; 14 | 15 | const args = yargs 16 | .option("s", { 17 | default: 18 | process.argv.length === 3 19 | ? process.argv[2] || "http://localhost:8080/offer" 20 | : "http://localhost:8080/offer", 21 | }) 22 | .option("t", { 23 | default: TestType.PeerConnection, 24 | }) 25 | .help().argv; 26 | const url = args["s"]; 27 | const testType = args["t"]; 28 | console.log(args, { url, testType }); 29 | 30 | new Promise(async (done, failed) => { 31 | setTimeout(60_000).then(failed); 32 | 33 | const port = await randomPort("udp4"); 34 | const udp = createSocket("udp4"); 35 | udp.bind(port); 36 | 37 | const pc = new RTCPeerConnection({ 38 | iceServers: [{ urls: "stun:stun.l.google.com:19302" }], 39 | }); 40 | const senderTrack = new MediaStreamTrack({ kind: "video" }); 41 | const transceiver = pc.addTransceiver(senderTrack); 42 | transceiver.onTrack.once((track) => { 43 | track.onReceiveRtp.subscribe((rtp) => { 44 | console.log("Received echoed back rtp", rtp.header); 45 | if (testType === TestType.PeerConnection) { 46 | done(); 47 | } 48 | }); 49 | }); 50 | 51 | if (testType === TestType.DataChannelEcho) { 52 | const pseudo = Math.random().toString().slice(2, 7); 53 | const dc = pc.createDataChannel("werift-dc"); 54 | dc.onopen = () => { 55 | dc.send(pseudo); 56 | }; 57 | dc.message.subscribe((msg) => { 58 | console.log("data channel onmessage"); 59 | if (msg === pseudo) { 60 | console.log("Data channel echo test success."); 61 | if (testType === TestType.DataChannelEcho) { 62 | done(); 63 | } 64 | } 65 | }); 66 | } 67 | 68 | await pc.setLocalDescription(await pc.createOffer()); 69 | console.log("local offer ", pc.localDescription?.sdp); 70 | const data = await got 71 | .post(url, { 72 | json: pc.localDescription, 73 | retry: { limit: 5, methods: ["POST"] }, 74 | }) 75 | .json() 76 | .catch(failed); 77 | console.log("server answer sdp", data?.sdp); 78 | pc.setRemoteDescription(data); 79 | 80 | await pc.connectionStateChange.watch((state) => state === "connected"); 81 | 82 | if (testType === TestType.PeerConnection) { 83 | if (transceiver.receiver.tracks.length === 0) { 84 | console.log("remoteTrack not found"); 85 | done(); 86 | return; 87 | } 88 | 89 | const payloadType = transceiver.getPayloadType("vp8")!; 90 | udp.on("message", (data) => { 91 | const rtp = RtpPacket.deSerialize(data); 92 | rtp.header.payloadType = payloadType; 93 | senderTrack.writeRtp(rtp); 94 | }); 95 | 96 | exec( 97 | `gst-launch-1.0 videotestsrc ! video/x-raw,width=640,height=480,format=I420 ! vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1 ! rtpvp8pay ! udpsink host=127.0.0.1 port=${port}` 98 | ); 99 | } 100 | }) 101 | .then(() => { 102 | console.log("interop done"); 103 | process.exit(0); 104 | }) 105 | .catch((e) => { 106 | console.log("interop failed", e); 107 | process.exit(1); 108 | }); 109 | -------------------------------------------------------------------------------- /doc/PeerConnectionTestSpecification.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | The purpose of the `Peer Connection Test` is verify that the WebRTC steps up to and including a DTLS handshake can be successfully completed between two peers: the `Server Peer` and the `Client Peer`. 4 | 5 | ## View the Code 6 | 7 | The descriptions in the following sections outline how the `Server Peer` and `Client Peer` work together. Often it can be easier to read the code. A number of implementations in different languages that can be reviewed: 8 | 9 | - Python: [aiortc Test Server](../aiortc/server.py) 10 | - Python: [aiortc Test Client](../aiortc/client.py) 11 | - Go: [Pion Test Server](../pion/main.go) 12 | - Go: [Pion Test Client](../pion/client/main.go) 13 | - C#: [SIPSorcery Test Server](../sipsorcery/server/Program.cs) 14 | - C#: [SIPSorcery Test Client](../sipsorcery/client/Program.cs) 15 | - C++: [libdatachannel Test Server](../libdatachannel/server.cpp) 16 | - C++: [libdatachannel Test Client](../libdatachannel/client.cpp) 17 | - TypeScript: [werift Test Server](../werift/server.ts) 18 | - TypeScript: [werift Test Client](../werift/client.ts) 19 | - Rust: [webrtc-rs Test Server](../webrtc-rs/server/src/main.rs) 20 | - Rust: [webrtc-rs Test Client](../webrtc-rs/client/src/main.rs) 21 | 22 | ## Signaling 23 | 24 | The single step signaling operation involved in the test is a HTTP POST request from the `Client Peer` to the `Server Peer`. The Client must send its SDP offer to the Server and the Server will respond with its SDP answer. 25 | 26 | **Note**: Using this single-shot signaling approach means that at least one of the `Client Peer` or `Server Peer` must operate in non-trickle ICE mode and include their ICE candidates in the SDP. Ideally both peers should be configured to operate in non-trickle mode. This is a deliberate choice in order to reduce the signaling complexity. 27 | 28 | ## Server Peer Operation 29 | 30 | The required operation for a `Server Peer` is: 31 | 32 | - Listen on TCP port `8080` for HTTP POST requests. The URL that the HTTP server must listen on for POST requests is: 33 | - http://*:8080/offer (if a wildcard IP address cannot be used any address that the client can reach is suitable). 34 | - POST requests from the client will have a body that is a JSON encoded [RTCSessionDescriptionInit](https://www.w3.org/TR/webrtc/#dom-rtcsessiondescriptioninit) object. 35 | - When an SDP offer is received create a new `Peer Connection` and set the remote offer. 36 | - Generate an SDP answer and return it as a JSON encoded [RTCSessionDescriptionInit](https://www.w3.org/TR/webrtc/#dom-rtcsessiondescriptioninit) object in the HTTP POST response. 37 | - Perform the `Peer Connection` initialisation. 38 | 39 | ## Client Peer Operation 40 | 41 | The required operation for a `Client Peer` is: 42 | 43 | - Create a new `Peer Connection` and generate an SDP offer. 44 | - Send the SDP offer as a JSON encoded [RTCSessionDescriptionInit](https://www.w3.org/TR/webrtc/#dom-rtcsessiondescriptioninit) object to the `Server Peer` with an HTTP POST request to: 45 | - http://localhost:8080/offer (adjust the address if the server is not on localhost) 46 | - The HTTP POST response will be an SDP answer as a JSON encoded [RTCSessionDescriptionInit](https://www.w3.org/TR/webrtc/#dom-rtcsessiondescriptioninit) object. 47 | - Set the remote offer on the `Peer Connection`. 48 | - Perform the `Peer Connection` initialisation. 49 | - If the DTLS handshake is successfully completed the client should **Return 0**. If the test fails or times out the client should **Return 1**. It's solely up to the `Client Peer` to decide if the test was successful or not. 50 | 51 | In addition to the various implementations listed above a javascript application that functions as an Echo Test `Client Peer` is available [here](../html/index.html). 52 | -------------------------------------------------------------------------------- /aiortc/server.py: -------------------------------------------------------------------------------- 1 | # A WebRTC Echo Test server that reflects back any audio or video frames it receives. 2 | # Originally based on https://github.com/aiortc/aiortc/blob/main/examples/server/server.py. 3 | 4 | import argparse 5 | import asyncio 6 | import json 7 | import logging 8 | import os 9 | import ssl 10 | import uuid 11 | 12 | from aiohttp import web 13 | from av import VideoFrame 14 | 15 | from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription 16 | from aiortc.contrib.media import MediaBlackhole 17 | 18 | ROOT = os.path.dirname(__file__) 19 | 20 | logger = logging.getLogger("pc") 21 | pcs = set() 22 | 23 | class EchoTrack(MediaStreamTrack): 24 | """ 25 | A media stream track that reflects the same media frame it receives. 26 | """ 27 | 28 | def __init__(self, track): 29 | super().__init__() # don't forget this! 30 | self.track = track 31 | self.kind = track.kind 32 | 33 | async def recv(self): 34 | frame = await self.track.recv() 35 | return frame 36 | 37 | async def index(request): 38 | content = open(os.path.join(ROOT, "../html/index.html"), "r").read() 39 | return web.Response(content_type="text/html", text=content) 40 | 41 | async def offer(request): 42 | params = await request.json() 43 | 44 | offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) 45 | 46 | pc = RTCPeerConnection() 47 | pc_id = "PeerConnection(%s)" % uuid.uuid4() 48 | pcs.add(pc) 49 | 50 | def log_info(msg, *args): 51 | logger.info(pc_id + " " + msg, *args) 52 | 53 | log_info("Created for %s", request.remote) 54 | 55 | @pc.on("iceconnectionstatechange") 56 | async def on_iceconnectionstatechange(): 57 | log_info("ICE connection state is %s", pc.iceConnectionState) 58 | if pc.iceConnectionState == "failed": 59 | await pc.close() 60 | pcs.discard(pc) 61 | 62 | @pc.on("track") 63 | def on_track(track): 64 | log_info("Track %s received", track.kind) 65 | 66 | if track.kind == "audio": 67 | local_audio = EchoTrack(track) 68 | pc.addTrack(local_audio) 69 | elif track.kind == "video": 70 | local_video = EchoTrack(track) 71 | pc.addTrack(local_video) 72 | 73 | @track.on("ended") 74 | async def on_ended(): 75 | log_info("Track %s ended", track.kind) 76 | 77 | # handle offer 78 | await pc.setRemoteDescription(offer) 79 | 80 | # send answer 81 | answer = await pc.createAnswer() 82 | await pc.setLocalDescription(answer) 83 | 84 | return web.Response( 85 | content_type="application/json", 86 | text=json.dumps( 87 | {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type} 88 | ), 89 | ) 90 | 91 | 92 | async def on_shutdown(app): 93 | # close peer connections 94 | coros = [pc.close() for pc in pcs] 95 | await asyncio.gather(*coros) 96 | pcs.clear() 97 | 98 | 99 | if __name__ == "__main__": 100 | parser = argparse.ArgumentParser( 101 | description="WebRTC Echo Server demo" 102 | ) 103 | parser.add_argument("--cert-file", help="SSL certificate file (for HTTPS)") 104 | parser.add_argument("--key-file", help="SSL key file (for HTTPS)") 105 | parser.add_argument( 106 | "--host", default="0.0.0.0", help="Host for HTTP server (default: 0.0.0.0)" 107 | ) 108 | parser.add_argument( 109 | "--port", type=int, default=8080, help="Port for HTTP server (default: 8080)" 110 | ) 111 | parser.add_argument("--verbose", "-v", action="count") 112 | args = parser.parse_args() 113 | 114 | if args.verbose: 115 | logging.basicConfig(level=logging.DEBUG) 116 | else: 117 | logging.basicConfig(level=logging.INFO) 118 | 119 | if args.cert_file: 120 | ssl_context = ssl.SSLContext() 121 | ssl_context.load_cert_chain(args.cert_file, args.key_file) 122 | else: 123 | ssl_context = None 124 | 125 | app = web.Application() 126 | app.on_shutdown.append(on_shutdown) 127 | app.router.add_post("/offer", offer) 128 | app.router.add_get("/", index) 129 | web.run_app( 130 | app, access_log=None, host=args.host, port=args.port, ssl_context=ssl_context 131 | ) 132 | -------------------------------------------------------------------------------- /libwebrtc/webrtc_lib_link_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The WebRTC Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #include "api/audio_codecs/audio_decoder_factory_template.h" 12 | #include "api/audio_codecs/audio_encoder_factory_template.h" 13 | #include "api/audio_codecs/opus/audio_decoder_opus.h" 14 | #include "api/audio_codecs/opus/audio_encoder_opus.h" 15 | #include "api/call/call_factory_interface.h" 16 | #include "api/create_peerconnection_factory.h" 17 | #include "api/peer_connection_interface.h" 18 | #include "api/rtc_event_log/rtc_event_log_factory.h" 19 | #include "api/stats/rtcstats_objects.h" 20 | #include "api/task_queue/default_task_queue_factory.h" 21 | #include "api/video_codecs/builtin_video_decoder_factory.h" 22 | #include "api/video_codecs/builtin_video_encoder_factory.h" 23 | #include "media/engine/webrtc_media_engine.h" 24 | #include "modules/audio_device/include/audio_device.h" 25 | #include "modules/audio_processing/include/audio_processing.h" 26 | 27 | namespace webrtc { 28 | 29 | cricket::MediaEngineDependencies CreateSomeMediaDeps( 30 | TaskQueueFactory* task_queue_factory) { 31 | cricket::MediaEngineDependencies media_deps; 32 | media_deps.task_queue_factory = task_queue_factory; 33 | media_deps.adm = AudioDeviceModule::CreateForTest( 34 | AudioDeviceModule::kDummyAudio, task_queue_factory); 35 | media_deps.audio_encoder_factory = 36 | webrtc::CreateAudioEncoderFactory(); 37 | media_deps.audio_decoder_factory = 38 | webrtc::CreateAudioDecoderFactory(); 39 | media_deps.video_encoder_factory = CreateBuiltinVideoEncoderFactory(); 40 | media_deps.video_decoder_factory = webrtc::CreateBuiltinVideoDecoderFactory(); 41 | media_deps.audio_processing = webrtc::AudioProcessingBuilder().Create(); 42 | return media_deps; 43 | } 44 | 45 | webrtc::PeerConnectionFactoryDependencies CreateSomePcfDeps() { 46 | webrtc::PeerConnectionFactoryDependencies pcf_deps; 47 | pcf_deps.task_queue_factory = CreateDefaultTaskQueueFactory(); 48 | pcf_deps.signaling_thread = rtc::Thread::Current(); 49 | pcf_deps.network_thread = rtc::Thread::Current(); 50 | pcf_deps.worker_thread = rtc::Thread::Current(); 51 | pcf_deps.call_factory = webrtc::CreateCallFactory(); 52 | pcf_deps.event_log_factory = std::make_unique( 53 | pcf_deps.task_queue_factory.get()); 54 | auto media_deps = CreateSomeMediaDeps(pcf_deps.task_queue_factory.get()); 55 | pcf_deps.media_engine = cricket::CreateMediaEngine(std::move(media_deps)); 56 | return pcf_deps; 57 | } 58 | 59 | // NOTE: These "test cases" should pull in as much of WebRTC as possible to make 60 | // sure most commonly used symbols are actually in libwebrtc.a. It's entirely 61 | // possible these tests won't work at all times (maybe crash even), but that's 62 | // fine. 63 | void TestCase1ModularFactory() { 64 | auto pcf_deps = CreateSomePcfDeps(); 65 | auto peer_connection_factory = 66 | webrtc::CreateModularPeerConnectionFactory(std::move(pcf_deps)); 67 | webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; 68 | auto peer_connection = peer_connection_factory->CreatePeerConnection( 69 | rtc_config, nullptr, nullptr, nullptr); 70 | printf("peer_connection=%s\n", peer_connection == nullptr ? "nullptr" : "ok"); 71 | } 72 | 73 | void TestCase2RegularFactory() { 74 | auto task_queue_factory = CreateDefaultTaskQueueFactory(); 75 | auto media_deps = CreateSomeMediaDeps(task_queue_factory.get()); 76 | 77 | auto peer_connection_factory = webrtc::CreatePeerConnectionFactory( 78 | rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(), 79 | std::move(media_deps.adm), std::move(media_deps.audio_encoder_factory), 80 | std::move(media_deps.audio_decoder_factory), 81 | std::move(media_deps.video_encoder_factory), 82 | std::move(media_deps.video_decoder_factory), nullptr, nullptr); 83 | webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; 84 | auto peer_connection = peer_connection_factory->CreatePeerConnection( 85 | rtc_config, nullptr, nullptr, nullptr); 86 | printf("peer_connection=%s\n", peer_connection == nullptr ? "nullptr" : "ok"); 87 | } 88 | 89 | } // namespace webrtc 90 | 91 | int mainz(int argc, char** argv) { 92 | webrtc::TestCase1ModularFactory(); 93 | webrtc::TestCase2RegularFactory(); 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /janus/JanusModel.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Filename: JanusModel.cs 3 | // 4 | // Description: Classes etc. to implement a minimal object model to interact 5 | // with the Janus WebRTC Server's REST interface. 6 | // 7 | // Author(s): 8 | // Aaron Clauson (aaron@sipsorcery.com) 9 | // 10 | // History: 11 | // 04 Feb 2021 Aaron Clauson Created, Dublin, Ireland. 12 | // 13 | // License: 14 | // BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. 15 | //----------------------------------------------------------------------------- 16 | 17 | using System; 18 | using Newtonsoft.Json; 19 | using Newtonsoft.Json.Converters; 20 | 21 | namespace JanusEchoClient 22 | { 23 | public enum JanusOperationsEnum 24 | { 25 | unknown, 26 | ack, 27 | attach, 28 | create, 29 | destroy, 30 | error, 31 | @event, 32 | keepalive, 33 | message, 34 | success, 35 | trickle 36 | } 37 | 38 | public static class JanusPlugins 39 | { 40 | public const string ECHO_TEST = "janus.plugin.echotest"; 41 | } 42 | 43 | public struct JanusData 44 | { 45 | public ulong id { get; set; } 46 | } 47 | 48 | /// 49 | /// Request to create a new session. 50 | /// 51 | public class JanusRequest 52 | { 53 | [JsonConverter(typeof(StringEnumConverter))] 54 | public JanusOperationsEnum janus; 55 | public string transaction => DateTime.Now.Ticks.ToString(); 56 | } 57 | 58 | public class AttachPluginRequest : JanusRequest 59 | { 60 | public string plugin { get; set; } 61 | 62 | public AttachPluginRequest(string pluginID) 63 | { 64 | janus = JanusOperationsEnum.attach; 65 | plugin = pluginID; 66 | } 67 | } 68 | 69 | public class Jsep 70 | { 71 | public string type { get; set; } 72 | public string sdp { get; set; } 73 | public bool trickle { get; set; } 74 | } 75 | 76 | /// 77 | /// See https://janus.conf.meetecho.com/docs/echotest.html. 78 | /// 79 | public class EchoTestBody 80 | { 81 | public bool audio { get; set; } = true; 82 | public bool video { get; set; } = true; 83 | } 84 | 85 | public class EchoTestRequest : JanusRequest 86 | { 87 | public Jsep jsep; 88 | public EchoTestBody body; 89 | 90 | public EchoTestRequest(string sdpType, string sdp) 91 | { 92 | janus = JanusOperationsEnum.message; 93 | jsep = new Jsep 94 | { 95 | type = sdpType, 96 | sdp = sdp 97 | }; 98 | body = new EchoTestBody(); 99 | } 100 | } 101 | 102 | public class JanusError 103 | { 104 | public int code { get; set; } 105 | public string reason { get; set; } 106 | } 107 | 108 | public class PluginResponse 109 | { 110 | public string plugin { get; set; } 111 | } 112 | 113 | public class JanusResponse 114 | { 115 | public string janus { get; set; } 116 | public ulong session_id { get; set; } 117 | public string transaction { get; set; } 118 | public ulong sender { get; set; } 119 | public string hint { get; set; } 120 | public JanusData data { get; set; } 121 | public JanusError error { get; set; } 122 | public PluginResponse plugindata { get; set; } 123 | public Jsep jsep { get; set; } 124 | 125 | [JsonIgnore] 126 | public JanusOperationsEnum JanusOp 127 | { 128 | get 129 | { 130 | if (Enum.TryParse(janus, out var op)) 131 | { 132 | return op; 133 | } 134 | else 135 | { 136 | return JanusOperationsEnum.unknown; 137 | } 138 | } 139 | set 140 | { 141 | janus = value.ToString(); 142 | } 143 | } 144 | } 145 | 146 | /// 147 | /// Response for /info. 148 | /// TODO: Numerous additional properties are available from the Janus info response. 149 | /// 150 | public class ServerInfo 151 | { 152 | public string name { get; set; } 153 | public int version { get; set; } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /gstreamer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 114 | 115 | 116 | 117 | 118 | 119 |
120 |
121 | 122 | 123 |
124 | 125 | 126 | -------------------------------------------------------------------------------- /libwebrtc/webrtc_lib_link_test_libevent.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 The WebRTC Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | #include "api/audio_codecs/audio_decoder_factory_template.h" 12 | #include "api/audio_codecs/audio_encoder_factory_template.h" 13 | #include "api/audio_codecs/opus/audio_decoder_opus.h" 14 | #include "api/audio_codecs/opus/audio_encoder_opus.h" 15 | #include "api/call/call_factory_interface.h" 16 | #include "api/create_peerconnection_factory.h" 17 | #include "api/peer_connection_interface.h" 18 | #include "api/rtc_event_log/rtc_event_log_factory.h" 19 | #include "api/stats/rtcstats_objects.h" 20 | #include "api/task_queue/default_task_queue_factory.h" 21 | #include "api/video_codecs/builtin_video_decoder_factory.h" 22 | #include "api/video_codecs/builtin_video_encoder_factory.h" 23 | #include "media/engine/webrtc_media_engine.h" 24 | #include "modules/audio_device/include/audio_device.h" 25 | #include "modules/audio_processing/include/audio_processing.h" 26 | #include 27 | 28 | namespace webrtc { 29 | 30 | cricket::MediaEngineDependencies CreateSomeMediaDeps( 31 | TaskQueueFactory* task_queue_factory) { 32 | cricket::MediaEngineDependencies media_deps; 33 | media_deps.task_queue_factory = task_queue_factory; 34 | media_deps.adm = AudioDeviceModule::CreateForTest( 35 | AudioDeviceModule::kDummyAudio, task_queue_factory); 36 | media_deps.audio_encoder_factory = 37 | webrtc::CreateAudioEncoderFactory(); 38 | media_deps.audio_decoder_factory = 39 | webrtc::CreateAudioDecoderFactory(); 40 | media_deps.video_encoder_factory = CreateBuiltinVideoEncoderFactory(); 41 | media_deps.video_decoder_factory = webrtc::CreateBuiltinVideoDecoderFactory(); 42 | media_deps.audio_processing = webrtc::AudioProcessingBuilder().Create(); 43 | return media_deps; 44 | } 45 | 46 | webrtc::PeerConnectionFactoryDependencies CreateSomePcfDeps() { 47 | webrtc::PeerConnectionFactoryDependencies pcf_deps; 48 | pcf_deps.task_queue_factory = CreateDefaultTaskQueueFactory(); 49 | pcf_deps.signaling_thread = rtc::Thread::Current(); 50 | pcf_deps.network_thread = rtc::Thread::Current(); 51 | pcf_deps.worker_thread = rtc::Thread::Current(); 52 | pcf_deps.call_factory = webrtc::CreateCallFactory(); 53 | pcf_deps.event_log_factory = std::make_unique( 54 | pcf_deps.task_queue_factory.get()); 55 | auto media_deps = CreateSomeMediaDeps(pcf_deps.task_queue_factory.get()); 56 | pcf_deps.media_engine = cricket::CreateMediaEngine(std::move(media_deps)); 57 | return pcf_deps; 58 | } 59 | 60 | // NOTE: These "test cases" should pull in as much of WebRTC as possible to make 61 | // sure most commonly used symbols are actually in libwebrtc.a. It's entirely 62 | // possible these tests won't work at all times (maybe crash even), but that's 63 | // fine. 64 | void TestCase1ModularFactory() { 65 | auto pcf_deps = CreateSomePcfDeps(); 66 | auto peer_connection_factory = 67 | webrtc::CreateModularPeerConnectionFactory(std::move(pcf_deps)); 68 | webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; 69 | auto peer_connection = peer_connection_factory->CreatePeerConnection( 70 | rtc_config, nullptr, nullptr, nullptr); 71 | printf("peer_connection=%s\n", peer_connection == nullptr ? "nullptr" : "ok"); 72 | } 73 | 74 | void TestCase2RegularFactory() { 75 | auto task_queue_factory = CreateDefaultTaskQueueFactory(); 76 | auto media_deps = CreateSomeMediaDeps(task_queue_factory.get()); 77 | 78 | auto peer_connection_factory = webrtc::CreatePeerConnectionFactory( 79 | rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(), 80 | std::move(media_deps.adm), std::move(media_deps.audio_encoder_factory), 81 | std::move(media_deps.audio_decoder_factory), 82 | std::move(media_deps.video_encoder_factory), 83 | std::move(media_deps.video_decoder_factory), nullptr, nullptr); 84 | webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; 85 | auto peer_connection = peer_connection_factory->CreatePeerConnection( 86 | rtc_config, nullptr, nullptr, nullptr); 87 | printf("peer_connection=%s\n", peer_connection == nullptr ? "nullptr" : "ok"); 88 | } 89 | 90 | } // namespace webrtc 91 | 92 | int main(int argc, char** argv) { 93 | webrtc::TestCase1ModularFactory(); 94 | webrtc::TestCase2RegularFactory(); 95 | printf("libevent version %s.\n", event_get_version()); 96 | return 0; 97 | } 98 | -------------------------------------------------------------------------------- /pion/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net/http" 10 | "strings" 11 | "sync" 12 | 13 | "github.com/pion/webrtc/v3" 14 | ) 15 | 16 | var ( 17 | pcs []*webrtc.PeerConnection 18 | pcsLock sync.RWMutex 19 | ) 20 | 21 | func offer(w http.ResponseWriter, r *http.Request) { 22 | var offer webrtc.SessionDescription 23 | if err := json.NewDecoder(r.Body).Decode(&offer); err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | pc, err := webrtc.NewPeerConnection(webrtc.Configuration{}) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | pcsLock.Lock() 33 | pcs = append(pcs, pc) 34 | pcsLock.Unlock() 35 | 36 | // // Set the handler for ICE connection state 37 | // // This will notify you when the peer has connected/disconnected 38 | pc.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { 39 | fmt.Printf("ICE connection state has changed to %s.\n", connectionState.String()) 40 | 41 | if connectionState == webrtc.ICEConnectionStateFailed { 42 | pcsLock.Lock() 43 | defer pcsLock.Unlock() 44 | 45 | for i := range pcs { 46 | if pcs[i] == pc { 47 | if err = pc.Close(); err != nil { 48 | log.Fatal(err) 49 | } 50 | 51 | pcs = append(pcs[:i], pcs[i+1:]...) 52 | } 53 | } 54 | } 55 | }) 56 | 57 | audioTrack := &echoTrack{id: "audio", streamID: "pion", kind: webrtc.RTPCodecTypeAudio} 58 | if _, err = pc.AddTrack(audioTrack); err != nil { 59 | log.Fatal(err) 60 | } 61 | 62 | videoTrack := &echoTrack{id: "video", streamID: "pion", kind: webrtc.RTPCodecTypeVideo} 63 | if _, err = pc.AddTrack(videoTrack); err != nil { 64 | log.Fatal(err) 65 | } 66 | 67 | pc.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { 68 | outTrack := videoTrack 69 | if strings.HasPrefix(track.Codec().MimeType, "audio") { 70 | outTrack = audioTrack 71 | } 72 | 73 | fmt.Printf("Track has started, of type %d: %s \n", track.PayloadType(), track.Codec().MimeType) 74 | for { 75 | rtp, _, err := track.ReadRTP() 76 | if err != nil { 77 | if err == io.EOF { 78 | return 79 | } 80 | 81 | log.Fatal(err) 82 | } 83 | 84 | rtp.SSRC = uint32(outTrack.ctx.SSRC()) 85 | if _, err = outTrack.ctx.WriteStream().WriteRTP(&rtp.Header, rtp.Payload); err != nil { 86 | log.Fatal(err) 87 | } 88 | } 89 | 90 | }) 91 | 92 | pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) { 93 | fmt.Printf("Peer connection state has changed to %s.\n", state.String()) 94 | }) 95 | 96 | pc.OnSignalingStateChange(func(state webrtc.SignalingState) { 97 | fmt.Printf("Signaling state has changed to %s.\n", state.String()) 98 | }) 99 | 100 | if err := pc.SetRemoteDescription(offer); err != nil { 101 | log.Fatal(err) 102 | } 103 | 104 | answer, err := pc.CreateAnswer(nil) 105 | if err != nil { 106 | log.Fatal(err) 107 | } else if err = pc.SetLocalDescription(answer); err != nil { 108 | log.Fatal(err) 109 | } 110 | 111 | response, err := json.Marshal(pc.LocalDescription()) 112 | if err != nil { 113 | log.Fatal(err) 114 | } 115 | 116 | w.Header().Set("Content-Type", "application/json") 117 | if _, err := w.Write(response); err != nil { 118 | log.Fatal(err) 119 | } 120 | 121 | } 122 | 123 | func main() { 124 | certFile := flag.String("cert-file", "", "SSL certificate file (for HTTPS)") 125 | keyFile := flag.String("key-file", "", "SSL key file (for HTTPS)") 126 | host := flag.String("host", "0.0.0.0", "Host for HTTP server (default: 0.0.0.0)") 127 | port := flag.Int("port", 8080, "Port for HTTP server (default: 8080)") 128 | flag.Parse() 129 | 130 | pcs = []*webrtc.PeerConnection{} 131 | 132 | http.Handle("/", http.FileServer(http.Dir("../html"))) 133 | http.HandleFunc("/offer", offer) 134 | 135 | addr := fmt.Sprintf("%s:%d", *host, *port) 136 | 137 | fmt.Printf("Listening on %s...\n", addr) 138 | 139 | if *keyFile != "" && *certFile != "" { 140 | log.Fatal(http.ListenAndServeTLS(addr, *certFile, *keyFile, nil)) 141 | } else { 142 | log.Fatal(http.ListenAndServe(addr, nil)) 143 | } 144 | } 145 | 146 | // echoTrack just passes through RTP packets 147 | // in a real application you will want to confirm that 148 | // the receiver supports the codec and updating PayloadTypes if needed 149 | type echoTrack struct { 150 | id, streamID string 151 | kind webrtc.RTPCodecType 152 | ctx webrtc.TrackLocalContext 153 | } 154 | 155 | func (e *echoTrack) Bind(c webrtc.TrackLocalContext) (webrtc.RTPCodecParameters, error) { 156 | e.ctx = c 157 | return c.CodecParameters()[0], nil 158 | } 159 | 160 | func (e *echoTrack) Unbind(webrtc.TrackLocalContext) error { return nil } 161 | func (e *echoTrack) ID() string { return e.id } 162 | func (e *echoTrack) StreamID() string { return e.streamID } 163 | func (e *echoTrack) Kind() webrtc.RTPCodecType { return e.kind } 164 | -------------------------------------------------------------------------------- /.github/workflows/datachannel_echo-test.yml: -------------------------------------------------------------------------------- 1 | name: WebRTC Data Channel Interoperability Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | repository_dispatch: 11 | types: [datachannel-test-command] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | jobs: 17 | # Job Description: Performs the Data Channel Test between each combination of the client and server for each library. 18 | interoptests: 19 | runs-on: ubuntu-latest 20 | 21 | services: 22 | 23 | echo-test-server: 24 | image: ghcr.io/sipsorcery/${{ matrix.server }}-webrtc-echo 25 | credentials: 26 | username: ${{ github.actor }} 27 | password: ${{ secrets.CR_PAT }} 28 | #ports: 29 | # - 8080:8080 30 | options: "--name echo-server" 31 | 32 | outputs: 33 | result_libdatachannel_libdatachannel: ${{ steps.set-output.outputs.result_libdatachannel_libdatachannel }} 34 | result_libdatachannel_sipsorcery: ${{ steps.set-output.outputs.result_libdatachannel_sipsorcery }} 35 | result_libdatachannel_werift: ${{ steps.set-output.outputs.result_libdatachannel_werift }} 36 | result_sipsorcery_libdatachannel: ${{ steps.set-output.outputs.result_sipsorcery_libdatachannel }} 37 | result_sipsorcery_sipsorcery: ${{ steps.set-output.outputs.result_sipsorcery_sipsorcery }} 38 | result_sipsorcery_werift: ${{ steps.set-output.outputs.result_sipsorcery_werift }} 39 | result_werift_libdatachannel: ${{ steps.set-output.outputs.result_werift_libdatachannel }} 40 | result_werift_sipsorcery: ${{ steps.set-output.outputs.result_werift_sipsorcery }} 41 | result_werift_werift: ${{ steps.set-output.outputs.result_werift_werift }} 42 | 43 | strategy: 44 | matrix: 45 | server: ["libdatachannel", "sipsorcery", "werift"] 46 | client: ["libdatachannel", "sipsorcery", "werift"] 47 | 48 | steps: 49 | 50 | - name: Data Channel test for server ${{ matrix.server }} and ${{ matrix.client }} client 51 | id: check_connection 52 | run: | 53 | docker run --entrypoint "/client.sh" --network ${{ job.container.network }} ghcr.io/sipsorcery/${{ matrix.client }}-webrtc-echo "-s http://echo-server:8080/offer -t 1" 54 | result=$? 55 | echo "Check connection for ${{ matrix.server }} server and ${{ matrix.client }} client result $result." 56 | echo "::set-output name=TEST_RESULT::$result" 57 | continue-on-error: true 58 | 59 | - name: Set output results 60 | id: set-output 61 | run: | 62 | echo "result_${{ matrix.server }}_${{ matrix.client }}=${{ steps.check_connection.outputs.TEST_RESULT }}" >> "$GITHUB_OUTPUT" 63 | 64 | # Job Description: Collates the results of the interop tests into a mark down table. 65 | collate: 66 | runs-on: ubuntu-latest 67 | needs: [interoptests] 68 | steps: 69 | - uses: actions/checkout@v2 70 | - uses: actions/setup-python@v2 71 | with: 72 | python-version: "3.x" 73 | - name: Create results file from interop test outputs 74 | run: | 75 | echo "Collating...." 76 | echo "raw results=${{ toJSON(needs.interoptests.outputs) }}" 77 | 78 | python --version 79 | 80 | echo '${{ toJSON(needs.interoptests.outputs) }}' | python3 test/collate-results.py > DataChannel_Echo_test_results.md 81 | 82 | # Display the results file 83 | cat DataChannel_Echo_test_results.md 84 | 85 | - name: Replace Data Channel Echo Test Results in README 86 | run: | 87 | # Read the new content from DataChannel_Echo_test_results.md 88 | new_content="$( README.tmp && mv -f README.tmp README.md 106 | 107 | cat README.md 108 | 109 | - name: Commit the results to the git repository 110 | if: github.event_name != 'pull_request' 111 | run: | 112 | git config user.name github-actions 113 | git config user.email github-actions@github.com 114 | git commit DataChannel_Echo_test_results.md README.md -m "Automated data channel echo test results." 115 | git pull --rebase 116 | git push 117 | -------------------------------------------------------------------------------- /PeerConnection_test_results.md: -------------------------------------------------------------------------------- 1 | Test run at 2025-07-18 21:01:46.707472 2 | 3 | | Server | aiortc | libdatachannel | pion | sipsorcery | webrtc-rs | werift | 4 | |--------|--------|--------|--------|--------|--------|--------| 5 | | aiortc | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 6 | | gstreamer | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 7 | | janus | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 8 | | kurento | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✘-red](https://img.shields.io/badge/-✘-red) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✘-red](https://img.shields.io/badge/-✘-red) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 9 | | libdatachannel | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 10 | | libwebrtc | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✘-red](https://img.shields.io/badge/-✘-red) | 11 | | pion | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 12 | | sipsorcery | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 13 | | webrtc-rs | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 14 | | werift | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | ![https://img.shields.io/badge/-✔-green](https://img.shields.io/badge/-✔-green) | 15 | -------------------------------------------------------------------------------- /libwebrtc/PcObserver.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Filename: PcObserver.h 3 | * 4 | * Description: 5 | * Observer to receive notifications of peer connection lifetime events. 6 | * 7 | * Author: 8 | * Aaron Clauson (aaron@sipsorcery.com) 9 | * 10 | * History: 11 | * 08 Mar 2021 Aaron Clauson Created, Dublin, Ireland. 12 | * 21 Dec 2024 Aaron Clauson Updated for libwebrtc version m132. 13 | * 14 | * License: Public Domain (no warranty, use at own risk) 15 | /******************************************************************************/ 16 | 17 | #ifndef __PEER_CONNECTION_OBSERVER__ 18 | #define __PEER_CONNECTION_OBSERVER__ 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | class PcObserver : 29 | public webrtc::PeerConnectionObserver 30 | { 31 | public: 32 | void OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state); 33 | void OnDataChannel(rtc::scoped_refptr data_channel); 34 | void OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state); 35 | void OnIceCandidate(const webrtc::IceCandidateInterface* candidate); 36 | void OnAddTrack( 37 | rtc::scoped_refptr receiver, 38 | const std::vector>& streams); 39 | void OnTrack( 40 | rtc::scoped_refptr transceiver); 41 | void OnConnectionChange( 42 | webrtc::PeerConnectionInterface::PeerConnectionState new_state); 43 | }; 44 | 45 | class SetRemoteSdpObserver : 46 | public webrtc::SetRemoteDescriptionObserverInterface 47 | { 48 | public: 49 | static rtc::scoped_refptr Create() { 50 | return rtc::scoped_refptr(new rtc::RefCountedObject()); 51 | } 52 | 53 | void OnSetRemoteDescriptionComplete(webrtc::RTCError error) 54 | { 55 | std::cout << "OnSetRemoteDescriptionComplete ok ? " << std::boolalpha << error.ok() << "." << std::endl; 56 | } 57 | }; 58 | 59 | class CreateSdpObserver : 60 | public webrtc::SetLocalDescriptionObserverInterface 61 | { 62 | public: 63 | 64 | static rtc::scoped_refptr Create(std::mutex& mtx, std::condition_variable& cv, bool& isReady) { 65 | return rtc::scoped_refptr(new rtc::RefCountedObject(mtx, cv, isReady)); 66 | } 67 | 68 | CreateSdpObserver(std::mutex& mtx, std::condition_variable& cv, bool& isReady) 69 | : _mtx(mtx), _cv(cv), _isReady(isReady) { 70 | std::cout << "CreateSdpObserver Constructor." << std::endl; 71 | } 72 | 73 | ~CreateSdpObserver() { 74 | std::cout << "CreateSdpObserver Destructor." << std::endl; 75 | } 76 | 77 | void OnSetLocalDescriptionComplete(webrtc::RTCError error) { 78 | std::cout << "OnSetLocalDescriptionComplete." << std::endl; 79 | 80 | if (!error.ok()) { 81 | std::cerr << "OnSetLocalDescription error. " << error.message() << std::endl; 82 | } 83 | 84 | std::unique_lock lck(_mtx); 85 | _isReady = true; 86 | _cv.notify_all(); 87 | } 88 | 89 | private: 90 | std::mutex& _mtx; 91 | std::condition_variable& _cv; 92 | bool& _isReady; 93 | }; 94 | 95 | class SetRemoteDescriptionObserver : public webrtc::SetSessionDescriptionObserver { 96 | public: 97 | 98 | static rtc::scoped_refptr Create() { 99 | return rtc::scoped_refptr(new rtc::RefCountedObject()); 100 | } 101 | 102 | SetRemoteDescriptionObserver() { 103 | std::cout << "SetRemoteDescriptionObserver Constructor." << std::endl; 104 | } 105 | 106 | void OnSuccess() override { 107 | std::cout << "SetRemoteDescriptionObserver::OnSuccess" << std::endl; 108 | } 109 | 110 | void OnFailure(webrtc::RTCError error) override { 111 | std::cerr << "SetRemoteDescriptionObserver::OnFailure: " << error.message() << std::endl; 112 | } 113 | }; 114 | 115 | class SetLocalDescriptionObserver : public webrtc::SetSessionDescriptionObserver { 116 | public: 117 | 118 | static rtc::scoped_refptr Create() { 119 | return rtc::scoped_refptr(new rtc::RefCountedObject()); 120 | } 121 | 122 | void OnSuccess() override { 123 | std::cout << "SetLocalDescriptionObserver::OnSuccess" << std::endl; 124 | } 125 | 126 | void OnFailure(webrtc::RTCError error) override { 127 | std::cerr << "SetLocalDescriptionObserver::OnFailure: " << error.message() << std::endl; 128 | } 129 | }; 130 | 131 | class CreateAnswerObserver : public webrtc::CreateSessionDescriptionObserver { 132 | public: 133 | 134 | static std::unique_ptr Create() { 135 | return std::unique_ptr(); 136 | } 137 | 138 | void OnSuccess(webrtc::SessionDescriptionInterface* desc) override { 139 | std::cout << "CreateAnswerObserver::OnSuccess" << std::endl; 140 | } 141 | 142 | void OnFailure(webrtc::RTCError error) override { 143 | std::cerr << "CreateAnswerObserver::OnFailure: " << error.message() << std::endl; 144 | } 145 | }; 146 | 147 | #endif -------------------------------------------------------------------------------- /libwebrtc/HttpSimpleServer.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Filename: HttpSimpleServer.cpp 3 | * 4 | * Description: See header file. 5 | * 6 | * Author: 7 | * Aaron Clauson (aaron@sipsorcery.com) 8 | * 9 | * History: 10 | * 08 Mar 2021 Aaron Clauson Created, Dublin, Ireland. 11 | * 21 Dec 2024 Aaron Clauson Updated for libwebrtc version m132. 12 | * 13 | * License: Public Domain (no warranty, use at own risk) 14 | /******************************************************************************/ 15 | 16 | #include "HttpSimpleServer.h" 17 | 18 | #include 19 | 20 | PcFactory* HttpSimpleServer::_pcFactory = nullptr; 21 | 22 | HttpSimpleServer::HttpSimpleServer() : 23 | _isDisposed(false) 24 | { 25 | #ifdef _WIN32 26 | evthread_use_windows_threads(); 27 | #endif 28 | 29 | /* Initialise libevent HTTP server. */ 30 | _evtBase = event_base_new(); 31 | if (!_evtBase) { 32 | throw std::runtime_error("HttpSimpleServer couldn't create an event_base instance."); 33 | } 34 | 35 | _httpSvr = evhttp_new(_evtBase); 36 | if (!_httpSvr) { 37 | throw std::runtime_error("HttpSimpleServer couldn't create an evhttp instance."); 38 | } 39 | 40 | _signalEvent = evsignal_new(_evtBase, SIGINT, HttpSimpleServer::OnSignal, (void*)_evtBase); 41 | if (!_signalEvent) { 42 | throw std::runtime_error("HttpSimpleServer couldn't create an event instance with evsignal_new."); 43 | } 44 | 45 | int addResult = event_add(_signalEvent, NULL); 46 | if (addResult < 0) { 47 | std::cerr << "Failed to add signal event handler." << std::endl; 48 | } 49 | } 50 | 51 | HttpSimpleServer::~HttpSimpleServer() { 52 | if (!_isDisposed) { 53 | _isDisposed = true; 54 | event_base_loopexit(_evtBase, nullptr); 55 | evhttp_free(_httpSvr); 56 | event_free(_signalEvent); 57 | event_base_free(_evtBase); 58 | } 59 | } 60 | 61 | void HttpSimpleServer::Init(const char* httpServerAddress, int httpServerPort, const char* offerPath) { 62 | 63 | int res = evhttp_bind_socket(_httpSvr, httpServerAddress, httpServerPort); 64 | if (res != 0) { 65 | throw std::runtime_error("HttpSimpleServer failed to start HTTP server on " + 66 | std::string(httpServerAddress) + ":" + std::to_string(httpServerPort) + "."); 67 | } 68 | 69 | evhttp_set_allowed_methods(_httpSvr, 70 | EVHTTP_REQ_GET | 71 | EVHTTP_REQ_POST | 72 | EVHTTP_REQ_OPTIONS); 73 | 74 | std::cout << "Waiting for SDP offer on http://" 75 | + std::string(httpServerAddress) + ":" + std::to_string(httpServerPort) + offerPath << std::endl; 76 | 77 | res = evhttp_set_cb(_httpSvr, offerPath, HttpSimpleServer::OnHttpRequest, NULL); 78 | if (res != 0) { 79 | throw std::runtime_error("HttpSimpleServer failed to set request callback."); 80 | } 81 | } 82 | 83 | void HttpSimpleServer::Run() { 84 | event_base_dispatch(_evtBase); 85 | } 86 | 87 | void HttpSimpleServer::Stop() { 88 | this->~HttpSimpleServer(); 89 | } 90 | 91 | void HttpSimpleServer::SetPeerConnectionFactory(PcFactory* pcFactory) { 92 | _pcFactory = pcFactory; 93 | } 94 | 95 | /** 96 | * The handler function for an incoming HTTP request. This is the start of the 97 | * handling for any WebRTC peer that wishes to establish a connection. The incoming 98 | * request MUST have an SDP offer in its body. 99 | * @param[in] req: the HTTP request received from the remote client. 100 | * @param[in] arg: not used. 101 | */ 102 | void HttpSimpleServer::OnHttpRequest(struct evhttp_request* req, void* arg) 103 | { 104 | const char* uri = evhttp_request_get_uri(req); 105 | evbuffer* http_req_body = nullptr; 106 | size_t http_req_body_len{ 0 }; 107 | char* http_req_buffer = nullptr; 108 | struct evbuffer* resp_buffer; 109 | 110 | printf("Received HTTP request for %s.\n", uri); 111 | 112 | if (req->type == EVHTTP_REQ_OPTIONS) { 113 | evhttp_add_header(req->output_headers, "Access-Control-Allow-Origin", "*"); 114 | evhttp_add_header(req->output_headers, "Access-Control-Allow-Methods", "POST"); 115 | evhttp_add_header(req->output_headers, "Access-Control-Allow-Headers", "content-type"); 116 | evhttp_send_reply(req, 200, "OK", NULL); 117 | } 118 | else { 119 | 120 | resp_buffer = evbuffer_new(); 121 | if (!resp_buffer) { 122 | fprintf(stderr, "Failed to create HTTP response buffer.\n"); 123 | } 124 | 125 | evhttp_add_header(req->output_headers, "Access-Control-Allow-Origin", "*"); 126 | 127 | http_req_body = evhttp_request_get_input_buffer(req); 128 | http_req_body_len = evbuffer_get_length(http_req_body); 129 | 130 | if (http_req_body_len > 0) { 131 | http_req_buffer = static_cast(calloc(http_req_body_len, sizeof(char))); 132 | 133 | evbuffer_copyout(http_req_body, http_req_buffer, http_req_body_len); 134 | 135 | printf("HTTP request body length %zu.\n", http_req_body_len); 136 | 137 | std::string offerJson(http_req_buffer, http_req_body_len); 138 | //std::cout << offerJson << std::endl; 139 | 140 | if (_pcFactory != nullptr) { 141 | std::string answer = _pcFactory->CreatePeerConnection(http_req_buffer, http_req_body_len); 142 | std::cout << "Answer: " << answer << std::endl; 143 | evhttp_add_header(req->output_headers, "Content-type", "application/json"); 144 | evbuffer_add_printf(resp_buffer, "%s", answer.c_str()); 145 | evhttp_send_reply(req, 200, "OK", resp_buffer); 146 | } 147 | else { 148 | evbuffer_add_printf(resp_buffer, "No handler"); 149 | evhttp_send_reply(req, 400, "Bad Request", resp_buffer); 150 | } 151 | } 152 | else { 153 | evbuffer_add_printf(resp_buffer, "Request was missing the SDP offer."); 154 | evhttp_send_reply(req, 400, "Bad Request", resp_buffer); 155 | } 156 | } 157 | } 158 | 159 | void HttpSimpleServer::OnSignal(evutil_socket_t sig, short events, void* user_data) 160 | { 161 | event_base* base = static_cast(user_data); 162 | 163 | std::cout << "Caught an interrupt signal; calling loop exit." << std::endl; 164 | 165 | event_base_loopexit(base, nullptr); 166 | } 167 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 154 | 157 | 158 | 159 |
SourceEcho
152 | 153 | 155 | 156 |
160 | 161 |
162 |
163 | 164 | 165 |
166 | 167 | 168 | -------------------------------------------------------------------------------- /libwebrtc/README.md: -------------------------------------------------------------------------------- 1 | ## Building the libwebrtc builder docker image 2 | 3 | This image gets as far as producing `libwebrtc-full.a` so that it can be used for application builds. 4 | 5 | `docker build -t libwebrtc-builder:m132 -f Dockerfile-Builder --progress=plain .` 6 | 7 | If the build fails: 8 | 9 | 1. Comment out the failing steps, generally that will be the ones from the `gn gen` command on. 10 | 11 | 2. `docker run -it --init --rm libwebrtc-builder:m132` 12 | 13 | ## Building echo application docker image 14 | 15 | The application image. It builds the application on an instance of the builder image and then copies the binary to a new ubuntu image and installs the required shared library packages. 16 | 17 | `docker build -t libwebrtc-webrtc-echo:m132 --progress=plain .` 18 | 19 | If the build fails: 20 | 21 | 1. Comment out the failing steps, generally that will be the ones from the `cmake` command on. 22 | 23 | 2. `docker run -it --init --rm libwebrtc-webrtc-echo:m132` 24 | 25 | ## Running echo application docker image 26 | 27 | `docker run -it --init --rm -p 8080:8080 libwebrtc-webrtc-echo:m132` 28 | 29 | ## Generate Ninja (GN) Reference 30 | 31 | The options supplied to the gn command are critical for buiding a working webrtc.lib (and equivalent object files on linux) as well as ensuring all the required symbols are included. 32 | 33 | gn --help # Get all command line args for gn. 34 | gn args out/Default --list > gnargs.txt # See what options are available for controlling the libwebrtc build. 35 | 36 | https://gn.googlesource.com/gn/+/main/docs/reference.md 37 | 38 | ## Building webrtc.lib on Windows 39 | 40 | Follow the standard instructions at https://webrtc.github.io/webrtc-org/native-code/development/ and then use the steps below. Pay particular attention to the `--args` argument supplied to the `gn` command. 41 | 42 | It seems the latest version of the build instructions are available directly in the source tree at https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/development/. 43 | 44 | ```` 45 | src> git checkout branch-heads/4430 -b m90 # See https://chromiumdash.appspot.com/branches for remote branch info. 46 | src> set PATH=C:\Tools\depot_tools;%PATH% 47 | src> glient sync 48 | src> rmdir /q /s out\Default 49 | src> gn gen out/Default --args="is_clang=false use_lld=false" --ide=vs # See https://gn.googlesource.com/gn/+/master/docs/reference.md#IDE-options for more info. 50 | src> gn args out/Default --list # Check use_lld is false. is_clang always shows true but if the false option is not set then linker errors when using webrtc.lib. 51 | src> gn args out/Default --list --short --overrides-only 52 | src> gn clean out/Default # If previous compilation. 53 | src> ninja -C out/Default 54 | ```` 55 | 56 | Update Dec 2024 for version m132. 57 | 58 | NOTE: Only the clang build chain is now supported for the libwebrtc build. To use webrtc.lib with Visual Studio the clang build chain can be installed via the Visual Studio Installer and then selected as the option in the project settings. 59 | 60 | Install the Google depot tools as per https://webrtc.googlesource.com/src/+/main/docs/native-code/development/prerequisite-sw/. 61 | 62 | In the webrtc-checkout directory, e.g. c:\dev\webrtc-checkout: 63 | 64 | ```` 65 | c:\dev\webrtc-checkout\src> set PATH=C:\Tools\depot_tools;%PATH% 66 | c:\dev\webrtc-checkout\src> git checkout branch-heads/6834 -b m132 # See https://chromiumdash.appspot.com/branches for remote branch info. 67 | c:\dev\webrtc-checkout\src> gclient sync -D 68 | c:\dev\webrtc-checkout\src> SET DEPOT_TOOLS_WIN_TOOLCHAIN=0 # Use Visual Studio installed clang toolchain instead of devtools one. 69 | c:\dev\webrtc-checkout\src> rmdir /q /s out\Default 70 | # Setting use_custom_libcxx=false is critical. If it is true, the build uses the libc++ standard library from the third-party source tree, 71 | # making it incompatible when linking with Visual Studio. By setting it to false, the MSVC standard library will be used (e.g msvcp140.dll), 72 | # ensuring that Visual Studio can properly resolve all the required symbols. 73 | c:\dev\webrtc-checkout\src> gn gen out/Default --args="is_debug=false rtc_include_tests=false treat_warnings_as_errors=false use_custom_libcxx=false use_rtti=true" 74 | c:\dev\webrtc-checkout\src> autoninja -C out/Default # DON'T use "ninja all -C out/Default", as listed on the build instructions site, it attempts to build everything in the out/Default directory rather than just libwebrtc. 75 | # The resultant webrtc.lib should be in out/Default/obj. 76 | # Note as an added bonus vcpkg installed binaries that are build the the standard msbuild toolcahin, e.g. cl and ld, are compatible with the clang and lld-ld linker. 77 | ```` 78 | 79 | ## Building libwebrtc-full.a on Ubuntu 80 | 81 | Follow the standard instructions at https://webrtc.github.io/webrtc-org/native-code/development/ and then use the steps below. Pay particular attention to the `--args` argument supplied to the `gn` command. 82 | 83 | It seems the latest version of the build instructions are available directly in the source tree at https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/development/. 84 | 85 | To update to the latest source or build a specific branch (see https://chromiumdash.appspot.com/branches for Chromium branch names): 86 | 87 | ```` 88 | git pull origin master 89 | ```` 90 | or 91 | ```` 92 | git checkout branch-heads/4430 -b m90 93 | ```` 94 | 95 | And then to build: 96 | 97 | First time: 98 | 99 | ```` 100 | src$ gclient sync 101 | src$ build/install-build-deps.sh 102 | ```` 103 | 104 | Subsequently: 105 | 106 | ```` 107 | src$ gclient sync 108 | ```` 109 | 110 | or if there are problems with `gclient sync`: 111 | 112 | ```` 113 | src$ gclient sync --force 114 | ```` 115 | 116 | And then: 117 | 118 | ```` 119 | src$ gn gen out/Default --args="use_custom_libcxx=false" 120 | src$ gn args out/Default --list --short --overrides-only 121 | src$ ninja -C out/Default 122 | src$ out/Default/webrtc_lib_link_test 123 | src$ cd out/Default 124 | src$ ar crs out/Default/libwebrtc-full.a $(find out/Default/obj -name '*\.o') 125 | sudo apt install software-properties-common 126 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test 127 | sudo apt update 128 | sudo apt install libstdc++-9-dev libevent-dev 129 | ```` 130 | 131 | ## Support Group 132 | 133 | The main place to get support for building libwebrtc is the Google Group at https://groups.google.com/u/2/g/discuss-webrtc. 134 | -------------------------------------------------------------------------------- /pion/client/main.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "io/ioutil" 11 | "net/http" 12 | "os" 13 | "strings" 14 | "time" 15 | 16 | "github.com/pion/webrtc/v3" 17 | ) 18 | 19 | var ECHO_TEST_SERVER_URL = "http://localhost:8080/offer" 20 | var CONNECTION_ATTEMPT_TIMEOUT_SECONDS = 10 21 | var SUCCESS_RETURN_VALUE = 0 22 | var ERROR_RETURN_VALUE = 1 23 | 24 | func main() { 25 | 26 | echoTestServerURL := ECHO_TEST_SERVER_URL 27 | if len(os.Args) > 1 { 28 | echoTestServerURL = os.Args[1] 29 | println("Echo test server URL set to:", echoTestServerURL) 30 | } 31 | 32 | connectResult := false 33 | defer func() { 34 | if err := recover(); err != nil { 35 | fmt.Println(err) 36 | } 37 | fmt.Println("Panic returning status:", ERROR_RETURN_VALUE) 38 | os.Exit(ERROR_RETURN_VALUE) 39 | }() 40 | 41 | ctx, cancel := context.WithTimeout(context.Background(), 42 | time.Duration(CONNECTION_ATTEMPT_TIMEOUT_SECONDS)*time.Second) 43 | defer cancel() 44 | 45 | // Everything below is the Pion WebRTC API! Thanks for using it ❤️. 46 | 47 | // Create a MediaEngine object to configure the supported codec 48 | m := webrtc.MediaEngine{} 49 | 50 | // Setup the codecs you want to use. 51 | // We'll use a VP8 and Opus but you can also define your own 52 | if err := m.RegisterCodec(webrtc.RTPCodecParameters{ 53 | RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "video/VP8", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil}, 54 | PayloadType: 96, 55 | }, webrtc.RTPCodecTypeVideo); err != nil { 56 | panic(err) 57 | } 58 | 59 | // Create the API object with the MediaEngine 60 | api := webrtc.NewAPI(webrtc.WithMediaEngine(&m)) 61 | 62 | // Prepare the configuration 63 | config := webrtc.Configuration{ 64 | ICEServers: []webrtc.ICEServer{ 65 | { 66 | URLs: []string{"stun:stun.l.google.com:19302"}, 67 | }, 68 | }, 69 | } 70 | // Create a new RTCPeerConnection 71 | peerConnection, err := api.NewPeerConnection(config) 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | // Create Track that we send video back to browser on 77 | outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion") 78 | if err != nil { 79 | panic(err) 80 | } 81 | 82 | // Add this newly created track to the PeerConnection 83 | rtpSender, err := peerConnection.AddTrack(outputTrack) 84 | if err != nil { 85 | panic(err) 86 | } 87 | 88 | // Read incoming RTCP packets 89 | // Before these packets are retuned they are processed by interceptors. For things 90 | // like NACK this needs to be called. 91 | go func() { 92 | rtcpBuf := make([]byte, 1500) 93 | for { 94 | if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { 95 | return 96 | } 97 | } 98 | }() 99 | 100 | // Add ICE candidates to the local offer (simulates non-trickle). 101 | peerConnection.OnICECandidate(func(c *webrtc.ICECandidate) { 102 | if c == nil { 103 | //fmt.Println(peerConnection.LocalDescription()) 104 | } 105 | }) 106 | 107 | offer, err := peerConnection.CreateOffer(nil) 108 | if err != nil { 109 | panic(err) 110 | } 111 | 112 | // Sets the LocalDescription, and starts our UDP listeners 113 | if err = peerConnection.SetLocalDescription(offer); err != nil { 114 | panic(err) 115 | } 116 | 117 | // Set the handler for ICE connection state 118 | // This will notify you when the peer has connected/disconnected 119 | peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { 120 | fmt.Printf("ICE connection state has changed %s.\n", connectionState.String()) 121 | }) 122 | 123 | peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) { 124 | fmt.Printf("Peer connection state has changed to %s.\n", state.String()) 125 | if state == webrtc.PeerConnectionStateConnected { 126 | connectResult = true 127 | peerConnection.Close() 128 | cancel() 129 | } else if state == webrtc.PeerConnectionStateDisconnected || 130 | state == webrtc.PeerConnectionStateFailed || 131 | state == webrtc.PeerConnectionStateClosed { 132 | connectResult = true 133 | cancel() 134 | } 135 | }) 136 | 137 | peerConnection.OnSignalingStateChange(func(state webrtc.SignalingState) { 138 | fmt.Printf("Signaling state has changed to %s.\n", state.String()) 139 | }) 140 | 141 | // Create an answer 142 | /* answer, err := peerConnection.CreateAnswer(nil) 143 | if err != nil { 144 | panic(err) 145 | } */ 146 | 147 | // Create channel that is blocked until ICE Gathering is complete 148 | gatherComplete := webrtc.GatheringCompletePromise(peerConnection) 149 | 150 | // Block until ICE Gathering is complete, disabling trickle ICE 151 | // we do this because we only can exchange one signaling message 152 | // in a production application you should exchange ICE Candidates via OnICECandidate 153 | <-gatherComplete 154 | 155 | fmt.Printf("Attempting to POST offer to %s.\n", echoTestServerURL) 156 | 157 | // POST the offer to the echo server. 158 | offerWithIce := peerConnection.LocalDescription() 159 | offerBuf := new(bytes.Buffer) 160 | json.NewEncoder(offerBuf).Encode(offerWithIce) 161 | req, err := http.NewRequest("POST", echoTestServerURL, offerBuf) 162 | req.Header.Set("Content-Type", "application/json") 163 | 164 | client := &http.Client{ 165 | Timeout: time.Duration(CONNECTION_ATTEMPT_TIMEOUT_SECONDS) * time.Second} 166 | resp, err := client.Do(req) 167 | if err != nil { 168 | panic(err) 169 | } 170 | defer resp.Body.Close() 171 | 172 | fmt.Println("POST offer response Status:", resp.Status) 173 | //fmt.Println("response Headers:", resp.Header) 174 | body, _ := ioutil.ReadAll(resp.Body) 175 | //fmt.Println("response Body:", string(body)) 176 | 177 | // Set the remote SessionDescription 178 | var answer webrtc.SessionDescription 179 | err = json.NewDecoder(strings.NewReader(string(body))).Decode(&answer) 180 | if err != nil { 181 | panic(err) 182 | } 183 | 184 | err = peerConnection.SetRemoteDescription(answer) 185 | if err != nil { 186 | panic(err) 187 | } 188 | 189 | // Output the answer in base64 so we can paste it in browser 190 | //fmt.Println(signal.Encode(*peerConnection.LocalDescription())) 191 | 192 | select { 193 | case <-ctx.Done(): 194 | fmt.Println("Context done.") 195 | } 196 | 197 | if connectResult { 198 | fmt.Println("Connection attempt successful returning status:", SUCCESS_RETURN_VALUE) 199 | os.Exit(SUCCESS_RETURN_VALUE) 200 | } else { 201 | fmt.Println("Connection attempt failed returning status:", ERROR_RETURN_VALUE) 202 | os.Exit(ERROR_RETURN_VALUE) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /libdatachannel/client.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * libdatachannel echo client 3 | * Copyright (c) 2021 Paul-Louis Ageneau 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | namespace http = httplib; 33 | using json = nlohmann::json; 34 | 35 | using namespace std::chrono_literals; 36 | 37 | std::string randomString(std::size_t length) { 38 | static const std::string chars = "0123456789abcdefghijklmnopqrstuvwxyz"; 39 | std::default_random_engine rng(std::random_device{}()); 40 | std::uniform_int_distribution dist(0, int(chars.size() - 1)); 41 | std::string result(length, '\0'); 42 | std::generate(result.begin(), result.end(), [&]() { return chars[dist(rng)]; }); 43 | return result; 44 | } 45 | 46 | int main(int argc, char **argv) try { 47 | // Default arguments 48 | int test = 0; 49 | std::string url = "http://localhost:8080/offer"; 50 | 51 | // Parse arguments 52 | for (int i = 1; i < argc; ++i) { 53 | std::string arg = argv[i]; 54 | if (!arg.empty() && arg[0] == '-') { 55 | std::string option = arg.substr(1); 56 | if (option == "h") { 57 | std::cout 58 | << "Usage: " << argv[0] << "[URL|]\n" 59 | << "Options:\n" 60 | << "\t-h,\t\tShow this help message\n" 61 | << "\t-t NUMBER\tSpecify the test number (default 0)\n" 62 | << "\t-s URL\t\tSpecify the server URL (default http://localhost:8080/offer)\n" 63 | << std::endl; 64 | return 0; 65 | } else if (option == "t") { 66 | if (i + 1 == argc) 67 | throw std::invalid_argument("Missing argument for option \"t\""); 68 | test = std::atoi(argv[++i]); 69 | } else if (option == "s") { 70 | if (i + 1 == argc) 71 | throw std::invalid_argument("Missing argument for option \"s\""); 72 | url = argv[++i]; 73 | } else { 74 | throw std::invalid_argument("Unknown option \"" + option + "\""); 75 | } 76 | } else { 77 | if (i > 1) 78 | throw std::invalid_argument("Unexpected positional argument \"" + arg + "\""); 79 | url = std::move(arg); 80 | } 81 | } 82 | 83 | if (test != 0 && test != 1) 84 | throw std::invalid_argument("Invalid test number"); 85 | 86 | const size_t separator = url.find_last_of('/'); 87 | if (separator == std::string::npos) 88 | throw std::invalid_argument("Invalid URL"); 89 | 90 | const std::string server = url.substr(0, separator); 91 | const std::string path = url.substr(separator); 92 | 93 | std::promise promise; 94 | auto future = promise.get_future(); 95 | 96 | http::Client cl(server.c_str()); 97 | 98 | rtc::InitLogger(rtc::LogLevel::Warning); 99 | 100 | // Set up Peer Connection 101 | rtc::Configuration config; 102 | config.disableAutoNegotiation = true; 103 | rtc::PeerConnection pc{std::move(config)}; 104 | 105 | pc.onGatheringStateChange([url, path, &pc, &cl, 106 | &promise](rtc::PeerConnection::GatheringState state) { 107 | if (state == rtc::PeerConnection::GatheringState::Complete) { 108 | try { 109 | auto local = pc.localDescription().value(); 110 | json msg; 111 | msg["sdp"] = std::string(local); 112 | msg["type"] = local.typeString(); 113 | 114 | auto dumped = msg.dump(); 115 | auto res = cl.Post(path.c_str(), dumped.c_str(), "application/json"); 116 | if (!res) 117 | throw std::runtime_error("HTTP request to " + url + 118 | " failed; error code: " + http::to_string(res.error())); 119 | 120 | if (res->status != 200) 121 | throw std::runtime_error("HTTP POST failed with status " + 122 | std::to_string(res->status) + "; content: " + dumped); 123 | json parsed; 124 | try { 125 | parsed = json::parse(res->body); 126 | 127 | } catch (const std::exception &e) { 128 | throw std::runtime_error("HTTP response parsing failed: " + 129 | std::string(e.what()) + "; content: " + res->body); 130 | } 131 | 132 | rtc::Description remote(parsed["sdp"].get(), 133 | parsed["type"].get()); 134 | pc.setRemoteDescription(std::move(remote)); 135 | 136 | } catch (...) { 137 | promise.set_exception(std::current_exception()); 138 | } 139 | } 140 | }); 141 | 142 | pc.onStateChange([&pc, &promise](rtc::PeerConnection::State state) { 143 | if (state == rtc::PeerConnection::State::Disconnected) { 144 | pc.close(); 145 | promise.set_exception( 146 | std::make_exception_ptr(std::runtime_error("Peer Connection failed"))); 147 | } 148 | }); 149 | 150 | std::shared_ptr tr; 151 | std::shared_ptr dc; 152 | if (test == 0) { // Peer Connection test 153 | rtc::Description::Video media("echo", rtc::Description::Direction::SendRecv); 154 | media.addVP8Codec(96); 155 | tr = pc.addTrack(std::move(media)); 156 | 157 | // TODO: send media 158 | tr->onOpen([&promise]() { promise.set_value(); }); 159 | 160 | } else { // test == 1 Data Channel test 161 | auto test = randomString(5); 162 | dc = pc.createDataChannel("echo"); 163 | 164 | dc->onOpen([test, &dc]() { dc->send(test); }); 165 | 166 | dc->onMessage([test, &promise](auto message) { 167 | try { 168 | if (!std::holds_alternative(message)) 169 | throw std::runtime_error("Received unexpected binary message"); 170 | 171 | auto str = std::get(message); 172 | if (str != test) 173 | throw std::runtime_error("Received unexpected message \"" + str + "\""); 174 | 175 | promise.set_value(); 176 | 177 | } catch (const std::exception &e) { 178 | promise.set_exception(std::make_exception_ptr(e)); 179 | } 180 | }); 181 | } 182 | 183 | pc.setLocalDescription(rtc::Description::Type::Offer); 184 | 185 | if (future.wait_for(10s) != std::future_status::ready) 186 | throw std::runtime_error("Timeout"); 187 | 188 | future.get(); 189 | return 0; 190 | 191 | } catch (const std::exception &e) { 192 | std::cerr << "Error: " << e.what() << std::endl; 193 | return -1; 194 | } 195 | -------------------------------------------------------------------------------- /gstreamer/gstreamer-webrtc-echo.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 16.0 29 | Win32Proj 30 | {76a3e204-6ebb-4f61-a752-1bf68ec98ea9} 31 | GstWebRTCEchoTestServer 32 | 10.0 33 | gstreamer-webrtc-echo 34 | 35 | C:\gstreamer-1.18.3\1.0\msvc_x86_64 36 | 37 | 38 | 39 | Application 40 | true 41 | v142 42 | Unicode 43 | 44 | 45 | Application 46 | false 47 | v142 48 | true 49 | Unicode 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | true 65 | $(GStreamerBasePath)\lib;$(LibraryPath) 66 | 67 | 68 | false 69 | $(GStreamerBasePath)\lib;$(LibraryPath) 70 | 71 | 72 | x64-windows 73 | 74 | 75 | x64-windows 76 | 77 | 78 | x64-windows 79 | 80 | 81 | x64-windows 82 | 83 | 84 | 85 | Level3 86 | true 87 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 88 | true 89 | $(GStreamerBasePath)\lib\glib-2.0\include;$(GStreamerBasePath)\include\glib-2.0;$(GStreamerBasePath)\include\gstreamer-1.0;%(AdditionalIncludeDirectories) 90 | stdc11 91 | 92 | 93 | Console 94 | true 95 | gstwebrtc-1.0.lib;gstsdp-1.0.lib;ws2_32.lib;gobject-2.0.lib;glib-2.0.lib;gstreamer-1.0.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 96 | 97 | 98 | 99 | 100 | Level3 101 | true 102 | true 103 | true 104 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | true 106 | $(GStreamerBasePath)\lib\glib-2.0\include;$(GStreamerBasePath)\include\glib-2.0;$(GStreamerBasePath)\include\gstreamer-1.0;%(AdditionalIncludeDirectories) 107 | stdc11 108 | 109 | 110 | Console 111 | true 112 | true 113 | true 114 | gstwebrtc-1.0.lib;gstsdp-1.0.lib;ws2_32.lib;gobject-2.0.lib;glib-2.0.lib;gstreamer-1.0.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /sipsorcery/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /janus/Program.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Filename: Program.cs 3 | // 4 | // Description: This program acts as a gateway to a Janus media server 5 | // instance to facilitate a WebRTC echo test. The program accepts an 6 | // HTTP POST request with an SDP offer from a WebRTC peer and then 7 | // performs the necessary initialisation with the Janus server to get it 8 | // to act as the second peer. 9 | // 10 | // Remarks: 11 | // docker run -it --init -p 8088:8088 canyan/janus-gateway:latest 12 | // 13 | // Author(s): 14 | // Aaron Clauson (aaron@sipsorcery.com) 15 | // 16 | // History: 17 | // 25 Mar 2021 Aaron Clauson Created, Dublin, Ireland. 18 | // 19 | // License: 20 | // BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. 21 | //----------------------------------------------------------------------------- 22 | 23 | using System; 24 | using System.Text.Json; 25 | using System.Threading; 26 | using System.Threading.Tasks; 27 | using Microsoft.Extensions.Logging; 28 | using Microsoft.Extensions.Logging.Abstractions; 29 | using EmbedIO; 30 | using Serilog; 31 | using Serilog.Events; 32 | using Serilog.Extensions.Logging; 33 | using SIPSorcery.Net; 34 | 35 | namespace JanusEchoClient 36 | { 37 | class Program 38 | { 39 | private const string DEFAULT_WEBSERVER_LISTEN_URL = "http://*:8080/"; 40 | private const string JANUS_REST_URL = "http://localhost:8088/janus/"; 41 | private const int MAX_TEST_DURATION_SECONDS = 30; 42 | 43 | private static Microsoft.Extensions.Logging.ILogger logger = NullLogger.Instance; 44 | 45 | static async Task Main(string[] args) 46 | { 47 | Console.WriteLine("Kurento Echo Test Server starting..."); 48 | 49 | logger = AddConsoleLogger(LogEventLevel.Verbose); 50 | 51 | // Start the web server. 52 | using (var server = CreateWebServer(DEFAULT_WEBSERVER_LISTEN_URL)) 53 | { 54 | await server.RunAsync(); 55 | 56 | Console.WriteLine("ctrl-c to exit."); 57 | var mre = new ManualResetEvent(false); 58 | Console.CancelKeyPress += (sender, eventArgs) => 59 | { 60 | // cancel the cancellation to allow the program to shutdown cleanly 61 | eventArgs.Cancel = true; 62 | mre.Set(); 63 | }; 64 | 65 | mre.WaitOne(); 66 | } 67 | } 68 | 69 | private static WebServer CreateWebServer(string url) 70 | { 71 | var server = new WebServer(o => o 72 | .WithUrlPrefix(url) 73 | .WithMode(HttpListenerMode.EmbedIO)) 74 | .WithCors("*", "*", "*") 75 | .WithAction("/offer", HttpVerbs.Post, OfferAsync); 76 | //.WithStaticFolder("/", "../../html", false); 77 | server.StateChanged += (s, e) => Console.WriteLine($"WebServer New State - {e.NewState}"); 78 | 79 | return server; 80 | } 81 | 82 | private async static Task OfferAsync(IHttpContext context) 83 | { 84 | var offer = await context.GetRequestDataAsync(); 85 | logger.LogDebug($"SDP Offer={offer.sdp}"); 86 | 87 | var jsonOptions = new JsonSerializerOptions(); 88 | jsonOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); 89 | 90 | CancellationTokenSource cts = new CancellationTokenSource(); 91 | cts.CancelAfter(MAX_TEST_DURATION_SECONDS * 1000); 92 | 93 | var janusClient = new JanusRestClient(JANUS_REST_URL, logger, cts.Token); 94 | 95 | var startSessionResult = await janusClient.StartSession(); 96 | 97 | if (!startSessionResult.Success) 98 | { 99 | throw HttpException.InternalServerError("Failed to create Janus session."); 100 | } 101 | else 102 | { 103 | var waitForAnswer = new TaskCompletionSource(); 104 | 105 | janusClient.OnJanusEvent += (resp) => 106 | { 107 | if (resp.jsep != null) 108 | { 109 | logger.LogDebug($"janus get event jsep={resp.jsep.type}."); 110 | logger.LogDebug($"janus SDP Answer: {resp.jsep.sdp}"); 111 | 112 | waitForAnswer.SetResult(resp.jsep.sdp); 113 | } 114 | }; 115 | 116 | var pluginResult = await janusClient.StartEcho(offer.sdp); 117 | if (!pluginResult.Success) 118 | { 119 | await janusClient.DestroySession(); 120 | waitForAnswer.SetCanceled(); 121 | cts.Cancel(); 122 | throw HttpException.InternalServerError("Failed to create Janus Echo Test Plugin instance."); 123 | } 124 | else 125 | { 126 | using (cts.Token.Register(() => 127 | { 128 | // This callback will be executed if the token is cancelled. 129 | waitForAnswer.TrySetCanceled(); 130 | })) 131 | { 132 | // At this point we're waiting for a response on the Janus long poll thread that 133 | // contains the SDP answer to send back to the client. 134 | try 135 | { 136 | var answer = await waitForAnswer.Task; 137 | logger.LogDebug("SDP answer ready, sending to client."); 138 | 139 | if (answer != null) 140 | { 141 | var answerInit = new RTCSessionDescriptionInit 142 | { 143 | type = RTCSdpType.answer, 144 | sdp = answer 145 | }; 146 | 147 | context.Response.ContentType = "application/json"; 148 | using (var responseStm = context.OpenResponseStream(false, false)) 149 | { 150 | await JsonSerializer.SerializeAsync(responseStm, answerInit, jsonOptions); 151 | } 152 | } 153 | } 154 | catch (TaskCanceledException) 155 | { 156 | await janusClient.DestroySession(); 157 | throw HttpException.InternalServerError("Janus operation timed out waiting for Echo Test plugin SDP answer."); 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | private static Microsoft.Extensions.Logging.ILogger AddConsoleLogger( 165 | LogEventLevel logLevel = LogEventLevel.Debug) 166 | { 167 | var serilogLogger = new LoggerConfiguration() 168 | .Enrich.FromLogContext() 169 | .MinimumLevel.Is(logLevel) 170 | .WriteTo.Console() 171 | .CreateLogger(); 172 | var factory = new SerilogLoggerFactory(serilogLogger); 173 | SIPSorcery.LogFactory.Set(factory); 174 | return factory.CreateLogger(); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /libwebrtc/libwebrtc_linux_build_results.txt: -------------------------------------------------------------------------------- 1 | ## Common set up steps: 2 | 3 | sudo apt update && DEBIAN_FRONTEND="noninteractive" apt install -y curl git \ 4 | lsb-release python sudo wget 5 | 6 | # Get depot_tools 7 | mkdir /src && chown $USER:$USER /src && cd /src 8 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git --depth 1 depot_tools 9 | export PATH="/src/depot_tools:${PATH}" 10 | 11 | # Get libwebrtc source. 12 | mkdir /src/webrtc-checkout && cd /src/webrtc-checkout 13 | fetch --nohooks webrtc 14 | cd /src/webrtc-checkout/src 15 | git pull origin master 16 | gclient sync 17 | build/install-build-deps.sh 18 | 19 | ## Run build tests on Ubuntu Xenial 16.04: 20 | 21 | # xenial.1: Test default build steps 22 | $ gn gen out/Default 23 | $ ninja all -C out/Default 24 | $ out/Default/webrtc_lib_link_test 25 | 26 | result: OK 27 | 28 | # xenial.2: Test default steps followed by re-build webrtc_lib_link_test 29 | $ gn gen out/Default 30 | $ ninja all -C out/Default 31 | $ out/Default/webrtc_lib_link_test 32 | $ ar crs out/Default/libwebrtc-full.a $(find out/Default/obj -name '*\.o') 33 | $ export PATH="/src/webrtc-checkout/src/third_party/llvm-build/Release+Asserts/bin":${PATH} 34 | $ clang++ -D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS -D_LIBCPP_DEBUG=0 -D_DEBUG -DWEBRTC_POSIX -DWEBRTC_LINUX -I/src/webrtc-checkout/src -I/src/webrtc-checkout/src/third_party/abseil-cpp -fstack-protector -funwind-tables -fPIC -O0 -g2 -ggnu-pubnames -std=c++14 -fno-trigraphs -nostdinc++ -isystem/src/webrtc-checkout/src/buildtools/third_party/libc++/trunk/include --sysroot=/src/webrtc-checkout/src/build/linux/debian_sid_amd64-sysroot -c webrtc_lib_link_test.cc -o webrtc_lib_link_test.o 35 | $ clang++ -fuse-ld=lld -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,-z,defs -Wl,--as-needed --sysroot=/src/webrtc-checkout/src/build/linux/debian_sid_amd64-sysroot -L/src/webrtc-checkout/src/build/linux/debian_sid_amd64-sysroot/lib/x86_64-linux-gnu -L/src/webrtc-checkout/src/build/linux/debian_sid_amd64-sysroot/usr/lib/x86_64-linux-gnu -L/src/webrtc-checkout/src/out/Default -pie -o webrtc_lib_link_test webrtc_lib_link_test.o -lwebrtc-full -ldl -lpthread -lX11 -lglib-2.0 36 | $ ./webrtc_lib_link_test 37 | 38 | result: Segmentation fault 39 | $ ./webrtc_lib_link_test 40 | (audio_device_impl.cc:84): CreateForTest 41 | Segmentation fault (core dumped) 42 | 43 | # xenial.3: Test no custom libcxx build 44 | $ gn gen out/Default --args="is_component_build=false use_custom_libcxx=false use_custom_libcxx_for_host=false rtc_enable_protobuf=false" 45 | $ ninja all -C out/Default 46 | 47 | result: build fails, compilation error. 48 | 49 | # xenial.4: Test no custom libcxx build with patch 50 | $ git apply patch-nocustomlibcxx.diff 51 | $ pushd base; git apply ../patch-base-nocustomlibcxx.diff; popd 52 | $ gn gen out/Default --args="is_component_build=false use_custom_libcxx=false use_custom_libcxx_for_host=false rtc_enable_protobuf=false" 53 | $ ninja all -C out/Default 54 | $ out/Default/webrtc_lib_link_test 55 | 56 | result: webrtc_lib_link_test execution fails: out/Default/webrtc_lib_link_test: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.22' not found (required by out/Default/webrtc_lib_link_test) 57 | 58 | # xenial.5: Test no custom libcxx build with patch and libstdc++ update 59 | $ git apply patch-nocustomlibcxx.diff 60 | $ pushd base; git apply ../patch-base-nocustomlibcxx.diff; popd 61 | $ gn gen out/Default --args="is_component_build=false use_custom_libcxx=false use_custom_libcxx_for_host=false rtc_enable_protobuf=false" 62 | $ ninja all -C out/Default 63 | $ sudo apt install software-properties-common 64 | $ sudo add-apt-repository ppa:ubuntu-toolchain-r/test 65 | $ sudo apt update 66 | $ sudo apt install libstdc++-9-dev 67 | $ out/Default/webrtc_lib_link_test 68 | 69 | result: OK 70 | 71 | # xenial.6: Test no custom libcxx build with patch and libstdc++ update followed by re-build webrtc_lib_link_test 72 | $ git apply patch-nocustomlibcxx.diff 73 | $ pushd base; git apply ../patch-base-nocustomlibcxx.diff; popd 74 | $ gn gen out/Default --args="is_component_build=false use_custom_libcxx=false use_custom_libcxx_for_host=false rtc_enable_protobuf=false" 75 | $ ninja all -C out/Default 76 | $ sudo apt install software-properties-common 77 | $ sudo add-apt-repository ppa:ubuntu-toolchain-r/test 78 | $ sudo apt update 79 | $ sudo apt install libstdc++-9-dev 80 | $ out/Default/webrtc_lib_link_test 81 | $ ar crs out/Default/libwebrtc-full.a $(find out/Default/obj -name '*\.o') 82 | $ export PATH="/src/webrtc-checkout/src/third_party/llvm-build/Release+Asserts/bin":${PATH} 83 | $ clang++ -D_LIBCPP_ABI_UNSTABLE -D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS -D_DEBUG -DWEBRTC_POSIX -DWEBRTC_LINUX -I/src/webrtc-checkout/src -I/src/webrtc-checkout/src/third_party/abseil-cpp -fstack-protector -funwind-tables -fPIC -O0 -g2 -std=c++14 -c webrtc_lib_link_test.cc -o webrtc_lib_link_test.o 84 | $ clang++ -fuse-ld=lld -Wl,-z,noexecstack -Wl,-z,relro -L/src/webrtc-checkout/src/out/Default -pie -o webrtc_lib_link_test webrtc_lib_link_test.o -lwebrtc-full -ldl -lpthread -lX11 -lglib-2.0 -lstdc++ -latomic 85 | $ ./webrtc_lib_link_test 86 | 87 | result: OK 88 | 89 | # xenial.7: Test no custom libcxx build with patch and libstdc++ update followed by re-build webrtc_lib_link_test including libevent dependency 90 | $ git apply patch-nocustomlibcxx.diff 91 | $ pushd base; git apply ../patch-base-nocustomlibcxx.diff; popd 92 | $ gn gen out/Default --args="is_component_build=false use_custom_libcxx=false use_custom_libcxx_for_host=false rtc_enable_protobuf=false" 93 | $ ninja all -C out/Default 94 | $ sudo apt install software-properties-common 95 | $ sudo add-apt-repository ppa:ubuntu-toolchain-r/test 96 | $ sudo apt update 97 | $ sudo apt install libstdc++-9-dev 98 | $ out/Default/webrtc_lib_link_test 99 | $ ar crs out/Default/libwebrtc-full.a $(find out/Default/obj -name '*\.o') 100 | $ export PATH="/src/webrtc-checkout/src/third_party/llvm-build/Release+Asserts/bin":${PATH} 101 | $ apt install libevent-dev 102 | $ clang++ -D_LIBCPP_ABI_UNSTABLE -D_LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS -D_DEBUG -DWEBRTC_POSIX -DWEBRTC_LINUX -I/src/webrtc-checkout/src -I/src/webrtc-checkout/src/third_party/abseil-cpp -fstack-protector -funwind-tables -fPIC -O0 -g2 -std=c++14 -c webrtc_lib_link_test_libevent.cc -o webrtc_lib_link_test_libevent.o 103 | $ clang++ -fuse-ld=lld -Wl,-z,noexecstack -Wl,-z,relro -L/src/webrtc-checkout/src/out/Default -pie -o webrtc_lib_link_test_libevent webrtc_lib_link_test_libevent.o -levent -lwebrtc-full -ldl -lpthread -lX11 -lglib-2.0 -lstdc++ -latomic 104 | $ ./webrtc_lib_link_test 105 | 106 | result: OK 107 | (audio_device_impl.cc:319): ~AudioDeviceModuleImpl 108 | (audio_device_buffer.cc:75): AudioDeviceBuffer::~dtor 109 | libevent version 1.4.13-stable. 110 | 111 | # Note this patch adds single include of, #include , to modules/video_coding/decoding_state.h. 112 | cat patch-nocustomlibcxx.diff 113 | diff --git a/modules/video_coding/decoding_state.h b/modules/video_coding/decoding_state.h 114 | index b87fb2d..ec97294 100644 115 | --- a/modules/video_coding/decoding_state.h 116 | +++ b/modules/video_coding/decoding_state.h 117 | @@ -11,6 +11,7 @@ 118 | #ifndef MODULES_VIDEO_CODING_DECODING_STATE_H_ 119 | #define MODULES_VIDEO_CODING_DECODING_STATE_H_ 120 | 121 | +#include 122 | #include 123 | #include 124 | #include 125 | 126 | # Note this patch adds single include of, #include , to base/profiler/stack_copier_signal.cc. 127 | cat patch-base-nocustomlibcxx.diff 128 | diff --git a/profiler/stack_copier_signal.cc b/profiler/stack_copier_signal.cc 129 | index cd3133d..047db3b 100644 130 | --- a/profiler/stack_copier_signal.cc 131 | +++ b/profiler/stack_copier_signal.cc 132 | @@ -10,6 +10,7 @@ 133 | #include 134 | 135 | #include 136 | +#include 137 | 138 | #include "base/notreached.h" 139 | #include "base/profiler/register_context.h" 140 | 141 | Misc Notes: 142 | - Ubuntu Xenial 16.04 is mentioned as the main Chromium develoment platform https://chromium.googlesource.com/chromium/src/+/master/docs/linux/build_instructions.md. 143 | - PR submitted for patch to modules/video_coding/decoding_state.h https://webrtc-review.googlesource.com/c/src/+/211762 144 | -------------------------------------------------------------------------------- /libwebrtc/PcFactory.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Filename: PcFactory.cpp 3 | * 4 | * Description: See header file. 5 | * 6 | * Author: 7 | * Aaron Clauson (aaron@sipsorcery.com) 8 | * 9 | * History: 10 | * 08 Mar 2021 Aaron Clauson Created, Dublin, Ireland. 11 | * 21 Dec 2024 Aaron Clauson Updated for libwebrtc version m132. 12 | * 13 | * License: Public Domain (no warranty, use at own risk) 14 | /******************************************************************************/ 15 | 16 | #include "PcFactory.h" 17 | #include "json.hpp" 18 | 19 | #include "api/audio/audio_processing.h" 20 | #include "api/audio/builtin_audio_processing_builder.h" 21 | #include "api/environment/environment.h" 22 | #include "api/environment/environment_factory.h" 23 | #include "api/audio_codecs/audio_decoder_factory_template.h" 24 | #include "api/audio_codecs/audio_encoder_factory_template.h" 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "api/audio_codecs/g711/audio_decoder_g711.h" 30 | #include "api/audio_codecs/g711/audio_encoder_g711.h" 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "api/enable_media.h" 37 | #include "fake_audio_capture_module.h" 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #include 46 | #include 47 | 48 | // Number of seconds to wait for the remote SDP offer to be set on the peer connection. 49 | #define SET_REMOTE_SDP_TIMEOUT_SECONDS 3 50 | 51 | PcFactory::PcFactory() : 52 | _peerConnections() 53 | { 54 | std::cout << "PcFactory initialise on " << std::this_thread::get_id() << std::endl; 55 | 56 | SignalingThread = rtc::Thread::Create(); 57 | SignalingThread->Start(); 58 | 59 | webrtc::AudioProcessing::Config apmConfig; 60 | apmConfig.gain_controller1.enabled = false; 61 | apmConfig.gain_controller2.enabled = false; 62 | auto apm = webrtc::BuiltinAudioProcessingBuilder(apmConfig).Build(webrtc::CreateEnvironment()); 63 | 64 | webrtc::PeerConnectionFactoryDependencies _pcf_deps; 65 | _pcf_deps.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory(); 66 | _pcf_deps.signaling_thread = SignalingThread.get(); 67 | _pcf_deps.event_log_factory = std::make_unique(_pcf_deps.task_queue_factory.get()); 68 | _pcf_deps.audio_encoder_factory = webrtc::CreateBuiltinAudioEncoderFactory(); 69 | _pcf_deps.audio_decoder_factory = webrtc::CreateBuiltinAudioDecoderFactory(); 70 | _pcf_deps.video_encoder_factory = std::make_unique>(); 71 | _pcf_deps.video_decoder_factory = std::make_unique>(); 72 | _pcf_deps.adm = rtc::scoped_refptr(FakeAudioCaptureModule::Create()); 73 | _pcf_deps.audio_processing = apm; // Gets moved in EnableMedia. 74 | 75 | webrtc::EnableMedia(_pcf_deps); 76 | 77 | _peerConnectionFactory = webrtc::CreateModularPeerConnectionFactory(std::move(_pcf_deps)); 78 | } 79 | 80 | PcFactory::~PcFactory() 81 | { 82 | for (auto pc : _peerConnections) { 83 | pc->Close(); 84 | } 85 | _peerConnections.clear(); 86 | _peerConnectionFactory = nullptr; 87 | } 88 | 89 | std::string PcFactory::CreatePeerConnection(const char* buffer, int length) { 90 | 91 | std::string offerStr(buffer, length); 92 | 93 | std::cout << offerStr << std::endl; 94 | 95 | auto offerJson = nlohmann::json::parse(offerStr); 96 | 97 | //std::cout << "CreatePeerConnection on thread " << std::this_thread::get_id() << " supplied with offer : " << std::endl << offerJson.dump() << std::endl; 98 | std::cout << "CreatePeerConnection on thread " << std::this_thread::get_id() << " supplied with offer : " << std::endl; 99 | 100 | webrtc::PeerConnectionInterface::RTCConfiguration config; 101 | config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan; 102 | //config.media_config.audio = new cricket::MediaConfig::Audio(); 103 | //config.continual_gathering_policy = webrtc::PeerConnectionInterface::ContinualGatheringPolicy::GATHER_ONCE; 104 | 105 | auto dependencies = webrtc::PeerConnectionDependencies(new PcObserver()); 106 | 107 | auto pcOrError = _peerConnectionFactory->CreatePeerConnectionOrError(config, std::move(dependencies)); 108 | 109 | rtc::scoped_refptr pc = nullptr; 110 | 111 | if (!pcOrError.ok()) { 112 | std::cerr << "Failed to get peer connection from factory. " << pcOrError.error().message() << std::endl; 113 | return "error"; 114 | } 115 | else { 116 | pc = pcOrError.MoveValue(); 117 | 118 | // Create a local audio source 119 | rtc::scoped_refptr audio_source = 120 | _peerConnectionFactory->CreateAudioSource(cricket::AudioOptions()); 121 | 122 | // Create a local audio track 123 | rtc::scoped_refptr audio_track = 124 | _peerConnectionFactory->CreateAudioTrack("audio_label", audio_source.get()); 125 | 126 | if (!audio_track) { 127 | std::cerr << "Failed to create AudioTrack." << std::endl; 128 | return "error"; 129 | } 130 | 131 | std::cout << "AudioTrack created successfully." << std::endl; 132 | 133 | auto sender = pc->AddTrack(audio_track, { "stream_id" }); 134 | 135 | if (!sender.ok()) { 136 | std::cerr << "Failed to add AudioTrack to PeerConnection." << std::endl; 137 | return "error"; 138 | } 139 | 140 | std::cout << "AudioTrack added to PeerConnection successfully." << std::endl; 141 | 142 | //webrtc::DataChannelInit config; 143 | //auto dc = pc->CreateDataChannel("data_channel", &config); 144 | 145 | _peerConnections.push_back(pc); 146 | 147 | webrtc::SdpParseError sdpError; 148 | auto remoteOffer = webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, offerJson["sdp"], &sdpError); 149 | 150 | if (remoteOffer == nullptr) { 151 | std::cerr << "Failed to get parse remote SDP. " << sdpError.description << std::endl; 152 | return "error"; 153 | } 154 | else { 155 | 156 | std::mutex mtx; 157 | std::condition_variable cv; 158 | bool isReady = false; 159 | 160 | SignalingThread->PostTask([&]() { 161 | std::cout << "Setting remote description on peer connection, thread ID " << std::this_thread::get_id() << std::endl; 162 | 163 | pc->SetRemoteDescription(remoteOffer->Clone(), SetRemoteSdpObserver::Create()); 164 | 165 | std::cout << "SetLocalDescription on thread, thread ID " << std::this_thread::get_id() << std::endl; 166 | pc->SetLocalDescription(CreateSdpObserver::Create(mtx, cv, isReady)); 167 | }); 168 | 169 | std::unique_lock lck(mtx); 170 | 171 | if (!isReady) { 172 | std::cout << "Waiting for create answer to complete..." << std::endl; 173 | 174 | bool completed = cv.wait_for(lck, std::chrono::seconds(SET_REMOTE_SDP_TIMEOUT_SECONDS), [&]() { 175 | return isReady; 176 | }); 177 | 178 | if (!completed) { 179 | std::cout << "Timed out waiting for isReady." << std::endl; 180 | return std::string(); 181 | } 182 | else { 183 | std::cout << "isReady is now true within 3s." << std::endl; 184 | } 185 | } 186 | 187 | auto localDescription = pc->local_description(); 188 | 189 | if (localDescription == nullptr) { 190 | return "Failed to set local description."; 191 | } 192 | else { 193 | std::cout << "Create answer complete." << std::endl; 194 | 195 | std::string answerSdp; 196 | localDescription->ToString(&answerSdp); 197 | 198 | std::cout << answerSdp << std::endl; 199 | 200 | nlohmann::json answerJson; 201 | answerJson["type"] = "answer"; 202 | answerJson["sdp"] = answerSdp; 203 | 204 | return answerJson.dump(); 205 | } 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /libwebrtc/fake_audio_capture_module.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Filename: fake_Audio_capture_module.h 3 | * 4 | * Description: 5 | * This class does not implement anything useful. Even most of the "fake" audio 6 | * funcitonality has been stripped out. It's purpose is to get the WebRTC lib 7 | * to recognise there is an audio device and answer an SDP audio offer. 8 | * 9 | * Original: 10 | * https://webrtc.googlesource.com/src/+/refs/heads/main/pc/test/fake_audio_capture_module.h 11 | * 12 | * History: 13 | * 21 Dec 2024 Aaron Clauson Copied from original source. 14 | * 15 | * License: 16 | * Original copyright notice is retained below. 17 | /******************************************************************************/ 18 | 19 | /* 20 | * Copyright 2012 The WebRTC project authors. All Rights Reserved. 21 | * 22 | * Use of this source code is governed by a BSD-style license 23 | * that can be found in the LICENSE file in the root of the source 24 | * tree. An additional intellectual property rights grant can be found 25 | * in the file PATENTS. All contributing project authors may 26 | * be found in the AUTHORS file in the root of the source tree. 27 | */ 28 | 29 | #ifndef PC_TEST_FAKE_AUDIO_CAPTURE_MODULE_H_ 30 | #define PC_TEST_FAKE_AUDIO_CAPTURE_MODULE_H_ 31 | 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | #include "api/audio/audio_device.h" 38 | #include "api/audio/audio_device_defines.h" 39 | #include "api/scoped_refptr.h" 40 | 41 | class FakeAudioCaptureModule : public webrtc::AudioDeviceModule { 42 | public: 43 | typedef uint16_t Sample; 44 | 45 | // The value for the following constants have been derived by running VoE 46 | // using a real ADM. The constants correspond to 10ms of mono audio at 44kHz. 47 | static const size_t kNumberSamples = 440; 48 | static const size_t kNumberBytesPerSample = sizeof(Sample); 49 | 50 | // Creates a FakeAudioCaptureModule or returns NULL on failure. 51 | static rtc::scoped_refptr Create(); 52 | 53 | // Returns the number of frames that have been successfully pulled by the 54 | // instance. Note that correctly detecting success can only be done if the 55 | // pulled frame was generated/pushed from a FakeAudioCaptureModule. 56 | int frames_received() const; 57 | 58 | int32_t ActiveAudioLayer(AudioLayer* audio_layer) const override; 59 | 60 | // Note: Calling this method from a callback may result in deadlock. 61 | int32_t RegisterAudioCallback(webrtc::AudioTransport* audio_callback) override; 62 | 63 | int32_t Init() override; 64 | int32_t Terminate() override; 65 | bool Initialized() const override; 66 | 67 | int16_t PlayoutDevices() override; 68 | int16_t RecordingDevices() override; 69 | int32_t PlayoutDeviceName(uint16_t index, 70 | char name[webrtc::kAdmMaxDeviceNameSize], 71 | char guid[webrtc::kAdmMaxGuidSize]) override; 72 | int32_t RecordingDeviceName(uint16_t index, 73 | char name[webrtc::kAdmMaxDeviceNameSize], 74 | char guid[webrtc::kAdmMaxGuidSize]) override; 75 | 76 | int32_t SetPlayoutDevice(uint16_t index) override; 77 | int32_t SetPlayoutDevice(WindowsDeviceType device) override; 78 | int32_t SetRecordingDevice(uint16_t index) override; 79 | int32_t SetRecordingDevice(WindowsDeviceType device) override; 80 | 81 | int32_t PlayoutIsAvailable(bool* available) override; 82 | int32_t InitPlayout() override; 83 | bool PlayoutIsInitialized() const override; 84 | int32_t RecordingIsAvailable(bool* available) override; 85 | int32_t InitRecording() override; 86 | bool RecordingIsInitialized() const override; 87 | 88 | int32_t StartPlayout() override; 89 | int32_t StopPlayout() override; 90 | bool Playing() const override; 91 | int32_t StartRecording() override; 92 | int32_t StopRecording() override; 93 | bool Recording() const override; 94 | 95 | int32_t InitSpeaker() override; 96 | bool SpeakerIsInitialized() const override; 97 | int32_t InitMicrophone() override; 98 | bool MicrophoneIsInitialized() const override; 99 | 100 | int32_t SpeakerVolumeIsAvailable(bool* available) override; 101 | int32_t SetSpeakerVolume(uint32_t volume) override; 102 | int32_t SpeakerVolume(uint32_t* volume) const override; 103 | int32_t MaxSpeakerVolume(uint32_t* max_volume) const override; 104 | int32_t MinSpeakerVolume(uint32_t* min_volume) const override; 105 | 106 | int32_t MicrophoneVolumeIsAvailable(bool* available) override; 107 | int32_t SetMicrophoneVolume(uint32_t volume) override; 108 | int32_t MicrophoneVolume(uint32_t* volume) const override; 109 | int32_t MaxMicrophoneVolume(uint32_t* max_volume) const override; 110 | 111 | int32_t MinMicrophoneVolume(uint32_t* min_volume) const override; 112 | 113 | int32_t SpeakerMuteIsAvailable(bool* available) override; 114 | int32_t SetSpeakerMute(bool enable) override; 115 | int32_t SpeakerMute(bool* enabled) const override; 116 | 117 | int32_t MicrophoneMuteIsAvailable(bool* available) override; 118 | int32_t SetMicrophoneMute(bool enable) override; 119 | int32_t MicrophoneMute(bool* enabled) const override; 120 | 121 | int32_t StereoPlayoutIsAvailable(bool* available) const override; 122 | int32_t SetStereoPlayout(bool enable) override; 123 | int32_t StereoPlayout(bool* enabled) const override; 124 | int32_t StereoRecordingIsAvailable(bool* available) const override; 125 | int32_t SetStereoRecording(bool enable) override; 126 | int32_t StereoRecording(bool* enabled) const override; 127 | 128 | int32_t PlayoutDelay(uint16_t* delay_ms) const override; 129 | 130 | bool BuiltInAECIsAvailable() const override { return false; } 131 | int32_t EnableBuiltInAEC(bool enable) override { return -1; } 132 | bool BuiltInAGCIsAvailable() const override { return false; } 133 | int32_t EnableBuiltInAGC(bool enable) override { return -1; } 134 | bool BuiltInNSIsAvailable() const override { return false; } 135 | int32_t EnableBuiltInNS(bool enable) override { return -1; } 136 | 137 | int32_t GetPlayoutUnderrunCount() const override { return -1; } 138 | 139 | std::optional GetStats() const override { 140 | return webrtc::AudioDeviceModule::Stats(); 141 | } 142 | 143 | // End of functions inherited from webrtc::AudioDeviceModule. 144 | 145 | protected: 146 | // The constructor is protected because the class needs to be created as a 147 | // reference counted object (for memory managment reasons). It could be 148 | // exposed in which case the burden of proper instantiation would be put on 149 | // the creator of a FakeAudioCaptureModule instance. To create an instance of 150 | // this class use the Create(..) API. 151 | FakeAudioCaptureModule(); 152 | // The destructor is protected because it is reference counted and should not 153 | // be deleted directly. 154 | virtual ~FakeAudioCaptureModule(); 155 | 156 | private: 157 | // Initializes the state of the FakeAudioCaptureModule. This API is called on 158 | // creation by the Create() API. 159 | bool Initialize(); 160 | // SetBuffer() sets all samples in send_buffer_ to `value`. 161 | void SetSendBuffer(int value); 162 | // Resets rec_buffer_. I.e., sets all rec_buffer_ samples to 0. 163 | void ResetRecBuffer(); 164 | // Returns true if rec_buffer_ contains one or more sample greater than or 165 | // equal to `value`. 166 | bool CheckRecBuffer(int value); 167 | 168 | // Returns true/false depending on if recording or playback has been 169 | // enabled/started. 170 | bool ShouldStartProcessing(); 171 | 172 | // Starts or stops the pushing and pulling of audio frames. 173 | void UpdateProcessing(bool start); 174 | 175 | // Starts the periodic calling of ProcessFrame() in a thread safe way. 176 | void StartProcessP(); 177 | // Periodcally called function that ensures that frames are pulled and pushed 178 | // periodically if enabled/started. 179 | void ProcessFrameP(); 180 | // Pulls frames from the registered webrtc::AudioTransport. 181 | void ReceiveFrameP(); 182 | // Pushes frames to the registered webrtc::AudioTransport. 183 | void SendFrameP(); 184 | 185 | // Callback for playout and recording. 186 | webrtc::AudioTransport* audio_callback_; 187 | 188 | bool recording_; // True when audio is being pushed from the instance. 189 | bool playing_; // True when audio is being pulled by the instance. 190 | 191 | bool play_is_initialized_; // True when the instance is ready to pull audio. 192 | bool rec_is_initialized_; // True when the instance is ready to push audio. 193 | 194 | // Input to and output from RecordedDataIsAvailable(..) makes it possible to 195 | // modify the current mic level. The implementation does not care about the 196 | // mic level so it just feeds back what it receives. 197 | uint32_t current_mic_level_; 198 | 199 | // next_frame_time_ is updated in a non-drifting manner to indicate the next 200 | // wall clock time the next frame should be generated and received. started_ 201 | // ensures that next_frame_time_ can be initialized properly on first call. 202 | bool started_; 203 | int64_t next_frame_time_; 204 | 205 | // Buffer for storing samples received from the webrtc::AudioTransport. 206 | char rec_buffer_[kNumberSamples * kNumberBytesPerSample]; 207 | // Buffer for samples to send to the webrtc::AudioTransport. 208 | char send_buffer_[kNumberSamples * kNumberBytesPerSample]; 209 | 210 | // Counter of frames received that have samples of high enough amplitude to 211 | // indicate that the frames are not faked somewhere in the audio pipeline 212 | // (e.g. by a jitter buffer). 213 | int frames_received_; 214 | 215 | // Protects variables that are accessed from process_thread_ and 216 | // the main thread. 217 | //mutable webrtc::Mutex mutex_; 218 | //webrtc::SequenceChecker process_thread_checker_{ 219 | // webrtc::SequenceChecker::kDetached }; 220 | }; 221 | 222 | #endif // PC_TEST_FAKE_AUDIO_CAPTURE_MODULE_H_ 223 | --------------------------------------------------------------------------------