Skip to content

Commit 1f38560

Browse files
authored
Added initial version of webrtc ffmpeg getstarted demo. (#1374)
* Added initial version of webrtc ffmpeg getstarted demo. * Updated readme.
1 parent 2d3e185 commit 1f38560

File tree

7 files changed

+399
-0
lines changed

7 files changed

+399
-0
lines changed

examples/WebRTCExamples/WebRTCExamples.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SIPSorcery", "..\..\src\SIP
4949
EndProject
5050
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebRTCOpenGL", "WebRTCOpenGL\WebRTCOpenGL.csproj", "{581BE6CD-8699-40C0-A975-F2C1993B62B8}"
5151
EndProject
52+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebRTCFFmpegGetStarted", "WebRTCFFmpegGetStarted\WebRTCFFmpegGetStarted.csproj", "{616B1ECF-B8DF-8E04-7AAA-678F947496E1}"
53+
EndProject
5254
Global
5355
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5456
Debug|Any CPU = Debug|Any CPU
@@ -335,6 +337,18 @@ Global
335337
{581BE6CD-8699-40C0-A975-F2C1993B62B8}.Release|x64.Build.0 = Release|Any CPU
336338
{581BE6CD-8699-40C0-A975-F2C1993B62B8}.Release|x86.ActiveCfg = Release|Any CPU
337339
{581BE6CD-8699-40C0-A975-F2C1993B62B8}.Release|x86.Build.0 = Release|Any CPU
340+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
341+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
342+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Debug|x64.ActiveCfg = Debug|Any CPU
343+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Debug|x64.Build.0 = Debug|Any CPU
344+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Debug|x86.ActiveCfg = Debug|Any CPU
345+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Debug|x86.Build.0 = Debug|Any CPU
346+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
347+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Release|Any CPU.Build.0 = Release|Any CPU
348+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Release|x64.ActiveCfg = Release|Any CPU
349+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Release|x64.Build.0 = Release|Any CPU
350+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Release|x86.ActiveCfg = Release|Any CPU
351+
{616B1ECF-B8DF-8E04-7AAA-678F947496E1}.Release|x86.Build.0 = Release|Any CPU
338352
EndGlobalSection
339353
GlobalSection(SolutionProperties) = preSolution
340354
HideSolutionNode = FALSE
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Stage 1: Build FFmpeg Image
2+
FROM sipsorcery/ffmpegbuild:7.0 AS ffmpeg
3+
4+
# Stage 2: Base Image - Install FFmpeg dependencies (This will be cached)
5+
FROM ubuntu:24.04 AS base
6+
7+
ENV DEBIAN_FRONTEND=noninteractive
8+
9+
# Install the required libraries for FFmpeg in the final image (as root)
10+
RUN apt-get update && apt-get install -y \
11+
libdrm2 \
12+
libsdl2-2.0-0 \
13+
libsndio7.0 \
14+
libxvidcore4 \
15+
libxv1 \
16+
libass9 \
17+
libvpx-dev \
18+
libsdl2-dev \
19+
libx264-dev \
20+
libx265-dev \
21+
libopus-dev \
22+
libfreetype6-dev \
23+
libvorbis-dev \
24+
libxvidcore-dev \
25+
libavutil-dev \
26+
libssl-dev \
27+
libavdevice-dev \
28+
libfdk-aac-dev \
29+
aspnetcore-runtime-8.0 \
30+
&& rm -rf /var/lib/apt/lists/*
31+
32+
WORKDIR /app
33+
EXPOSE 8081
34+
35+
# Stage 3: Build .NET Application (Only rebuilds if source code changes)
36+
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
37+
38+
ARG BUILD_CONFIGURATION=Release
39+
WORKDIR /src
40+
41+
COPY [".", "."]
42+
43+
# Publish the application
44+
FROM build AS publish
45+
RUN dotnet publish "./WebRTCFFmpegGetStarted.csproj" -c $BUILD_CONFIGURATION -o /app/publish
46+
47+
# Stage 4: Final Image to Run the App
48+
FROM base AS final
49+
50+
WORKDIR /app
51+
52+
# Copy the published app from the build image
53+
COPY --from=publish /app/publish .
54+
55+
# Copy FFmpeg binaries and libraries from the FFmpeg build image
56+
COPY --from=ffmpeg /usr/local/bin/ffmpeg /usr/local/bin/
57+
COPY --from=ffmpeg /usr/local/bin/ffprobe /usr/local/bin/
58+
COPY --from=ffmpeg /usr/local/lib/libavcodec.so.61.3.100 /usr/local/lib/
59+
COPY --from=ffmpeg /usr/local/lib/libavdevice.so.61.1.100 /usr/local/lib/
60+
COPY --from=ffmpeg /usr/local/lib/libavfilter.so.10.1.100 /usr/local/lib/
61+
COPY --from=ffmpeg /usr/local/lib/libavformat.so.61.1.100 /usr/local/lib/
62+
COPY --from=ffmpeg /usr/local/lib/libavutil.so.59.8.100 /usr/local/lib/
63+
COPY --from=ffmpeg /usr/local/lib/libpostproc.so.58.1.100 /usr/local/lib/
64+
COPY --from=ffmpeg /usr/local/lib/libswresample.so.5.1.100 /usr/local/lib/
65+
COPY --from=ffmpeg /usr/local/lib/libswscale.so.8.1.100 /usr/local/lib/
66+
67+
# Update library links
68+
RUN ldconfig
69+
70+
# Ensure FFmpeg is available in the PATH for your app
71+
ENV PATH="/usr/local/bin:${PATH}"
72+
73+
# Set entrypoint to run the .NET application
74+
ENTRYPOINT ["dotnet", "WebRTCFFmpegGetStarted.dll"]
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
//-----------------------------------------------------------------------------
2+
// Filename: Program.cs
3+
//
4+
// Description: An example WebRTC server application that attempts to send and
5+
// receive audio and video. This example attempts to use the ffmpeg libraries for
6+
// the video encoding. A web socket is used for signalling.
7+
//
8+
// Author(s):
9+
// Aaron Clauson (aaron@sipsorcery.com)
10+
//
11+
// History:
12+
// 06 Apr 2025 Aaron Clauson Created, Dublin, Ireland.
13+
//
14+
// License:
15+
// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
16+
//-----------------------------------------------------------------------------
17+
18+
using System;
19+
using System.Diagnostics.Eventing.Reader;
20+
using System.Linq;
21+
using System.Net;
22+
using System.Threading.Tasks;
23+
using Microsoft.AspNetCore.Builder;
24+
using Microsoft.AspNetCore.Hosting;
25+
using Microsoft.Extensions.Logging;
26+
using Microsoft.Extensions.Logging.Abstractions;
27+
using Serilog;
28+
using Serilog.Extensions.Logging;
29+
using SIPSorcery.Media;
30+
using SIPSorcery.Net;
31+
using SIPSorceryMedia.FFmpeg;
32+
using WebSocketSharp.Server;
33+
34+
namespace demo;
35+
36+
class Program
37+
{
38+
private const int ASPNET_PORT = 8080;
39+
private const int WEBSOCKET_PORT = 8081;
40+
//private const string STUN_URL = "stun:stun.cloudflare.com";
41+
private const string LINUX_FFMPEG_LIB_PATH = "/usr/local/lib/";
42+
43+
private static Microsoft.Extensions.Logging.ILogger logger = NullLogger.Instance;
44+
45+
static void Main()
46+
{
47+
Console.WriteLine("WebRTC FFmpeg Get Started");
48+
49+
if (Environment.OSVersion.Platform == PlatformID.Unix)
50+
{
51+
SIPSorceryMedia.FFmpeg.FFmpegInit.Initialise(SIPSorceryMedia.FFmpeg.FfmpegLogLevelEnum.AV_LOG_VERBOSE, LINUX_FFMPEG_LIB_PATH, logger);
52+
}
53+
else
54+
{
55+
SIPSorceryMedia.FFmpeg.FFmpegInit.Initialise(SIPSorceryMedia.FFmpeg.FfmpegLogLevelEnum.AV_LOG_VERBOSE, null, logger);
56+
}
57+
58+
logger = AddConsoleLogger();
59+
60+
// Start web socket.
61+
Console.WriteLine("Starting web socket server...");
62+
var webSocketServer = new WebSocketServer(IPAddress.Any, WEBSOCKET_PORT);
63+
webSocketServer.AddWebSocketService<WebRTCWebSocketPeer>("/", (peer) => peer.CreatePeerConnection = CreatePeerConnection);
64+
webSocketServer.Start();
65+
66+
Console.WriteLine($"Waiting for web socket connections on {webSocketServer.Address}:{webSocketServer.Port}...");
67+
Console.WriteLine("Press ctrl-c to exit.");
68+
69+
var builder = WebApplication.CreateBuilder();
70+
71+
builder.WebHost.ConfigureKestrel(options =>
72+
{
73+
options.Listen(IPAddress.Any, ASPNET_PORT);
74+
});
75+
76+
var app = builder.Build();
77+
78+
// Map the root URL (/) to return "Hello World"
79+
///app.MapGet("/", () => "Hello World");
80+
81+
app.UseDefaultFiles();
82+
app.UseStaticFiles();
83+
84+
app.Run();
85+
}
86+
87+
private static Task<RTCPeerConnection> CreatePeerConnection()
88+
{
89+
RTCConfiguration config = new RTCConfiguration
90+
{
91+
//iceServers = new List<RTCIceServer> { new RTCIceServer { urls = STUN_URL } },
92+
X_BindAddress = IPAddress.Any // Docker images typically don't support IPv6 so force bind to IPv4.
93+
};
94+
var pc = new RTCPeerConnection(config);
95+
96+
var testPatternSource = new VideoTestPatternSource(new FFmpegVideoEncoder());
97+
var audioSource = new AudioExtrasSource(new AudioEncoder(), new AudioSourceOptions { AudioSource = AudioSourcesEnum.Music });
98+
99+
MediaStreamTrack videoTrack = new MediaStreamTrack(testPatternSource.GetVideoSourceFormats(), MediaStreamStatusEnum.SendRecv);
100+
pc.addTrack(videoTrack);
101+
MediaStreamTrack audioTrack = new MediaStreamTrack(audioSource.GetAudioSourceFormats(), MediaStreamStatusEnum.SendRecv);
102+
pc.addTrack(audioTrack);
103+
104+
testPatternSource.OnVideoSourceEncodedSample += pc.SendVideo;
105+
audioSource.OnAudioSourceEncodedSample += pc.SendAudio;
106+
107+
pc.OnVideoFormatsNegotiated += (formats) => testPatternSource.SetVideoSourceFormat(formats.First());
108+
pc.OnAudioFormatsNegotiated += (formats) => audioSource.SetAudioSourceFormat(formats.First());
109+
pc.onsignalingstatechange += () =>
110+
{
111+
logger.LogDebug($"Signalling state change to {pc.signalingState}.");
112+
113+
if (pc.signalingState == RTCSignalingState.have_local_offer)
114+
{
115+
logger.LogDebug($"Local SDP offer:\n{pc.localDescription.sdp}");
116+
}
117+
else if (pc.signalingState == RTCSignalingState.stable)
118+
{
119+
logger.LogDebug($"Remote SDP offer:\n{pc.remoteDescription.sdp}");
120+
}
121+
};
122+
123+
pc.onconnectionstatechange += async (state) =>
124+
{
125+
logger.LogDebug($"Peer connection state change to {state}.");
126+
127+
if (state == RTCPeerConnectionState.connected)
128+
{
129+
await audioSource.StartAudio();
130+
await testPatternSource.StartVideo();
131+
}
132+
else if (state == RTCPeerConnectionState.failed)
133+
{
134+
pc.Close("ice disconnection");
135+
}
136+
else if (state == RTCPeerConnectionState.closed)
137+
{
138+
await testPatternSource.CloseVideo();
139+
await audioSource.CloseAudio();
140+
}
141+
};
142+
143+
// Diagnostics.
144+
pc.OnReceiveReport += (re, media, rr) => logger.LogDebug($"RTCP Receive for {media} from {re}\n{rr.GetDebugSummary()}");
145+
pc.OnSendReport += (media, sr) => logger.LogDebug($"RTCP Send for {media}\n{sr.GetDebugSummary()}");
146+
pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isRelay) => logger.LogDebug($"STUN {msg.Header.MessageType} received from {ep}.");
147+
pc.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}.");
148+
149+
// To test closing.
150+
//_ = Task.Run(async () =>
151+
//{
152+
// await Task.Delay(5000);
153+
154+
// audioSource.OnAudioSourceEncodedSample -= pc.SendAudio;
155+
// videoEncoderEndPoint.OnVideoSourceEncodedSample -= pc.SendVideo;
156+
157+
// logger.LogDebug("Closing peer connection.");
158+
// pc.Close("normal");
159+
//});
160+
161+
return Task.FromResult(pc);
162+
}
163+
164+
/// <summary>
165+
/// Adds a console logger. Can be omitted if internal SIPSorcery debug and warning messages are not required.
166+
/// </summary>
167+
private static Microsoft.Extensions.Logging.ILogger AddConsoleLogger()
168+
{
169+
var seriLogger = new LoggerConfiguration()
170+
.Enrich.FromLogContext()
171+
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Debug)
172+
.WriteTo.Console()
173+
.CreateLogger();
174+
var factory = new SerilogLoggerFactory(seriLogger);
175+
SIPSorcery.LogFactory.Set(factory);
176+
return factory.CreateLogger<Program>();
177+
}
178+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"profiles": {
3+
"WebRTCFFmpegGetStarted": {
4+
"commandName": "Project",
5+
"launchBrowser": true,
6+
"environmentVariables": {
7+
"ASPNETCORE_ENVIRONMENT": "Development"
8+
},
9+
"applicationUrl": "https://localhost:52561;http://localhost:52562"
10+
}
11+
}
12+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Description
2+
3+
This demo serves a dual purpose:
4+
5+
- A demo of how to get started with the sipsorcery library when using FFmpeg for video encoding.
6+
- A demo of how to host a sipsorcery webrtc application in a docker container.
7+
8+
The demo can be run locally on Windows in the same manner as the other demoes. The correct version of FFmpeg must be installed as per the instructions [here](https://github.com/sipsorcery-org/SIPSorceryMedia.FFmpeg).
9+
10+
The demo "should" also work in a docker container and was verified to do so. The hardest thing about getting the contianer to work and the most likely thing to break iy
11+
is a mismtach in the verion of the FFmpeg binaries supported by the [FFmpeg.Autogen](https://github.com/Ruslan-B/FFmpeg.AutoGen) library used by sipsorcery.
12+
13+
As the time of writing the correct FFmpeg version was `n7.0` and a docker build image was created [here](https://github.com/sipsorcery-org/SIPSorceryMedia.FFmpeg/tree/master/ffmpeg-build).
14+
15+
# Docker Build & Run
16+
17+
`c:\dev\sipsorcery\examples\WebRTCExamples\WebRTCFFmpegGetStarted> docker build -t webrtcgetstarted --progress=plain -f Dockerfile .`
18+
`c:\dev\sipsorcery\examples\WebRTCExamples\WebRTCFFmpegGetStarted> docker run --rm -it -p 8080:8080 -p 8081:8081 webrtcgetstarted`
19+
20+
# From DockerHub
21+
22+
`docker run --rm -it -p 8080:8080 -p 8081:8081 sipsorcery/webrtcgetstarted`
23+
24+
# Troubleshooting
25+
26+
`c:\dev\sipsorcery\examples\WebRTCExamples\WebRTCFFmpegGetStarted> docker run --rm -it -p 8080:8080 -p 8081:8081 --entrypoint "/bin/bash" webrtcgetstarted`
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
7+
<Prefer32Bit>false</Prefer32Bit>
8+
<LangVersion>12.0</LangVersion>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
13+
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
14+
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
15+
<PackageReference Include="SIPSorcery" Version="8.0.11" />
16+
<PackageReference Include="SIPSorcery.WebSocketSharp" Version="0.0.1" />
17+
<PackageReference Include="SIPSorceryMedia.FFmpeg" Version="8.0.10" />
18+
</ItemGroup>
19+
20+
</Project>

0 commit comments

Comments
 (0)