Skip to content

Commit

Permalink
Bug fixes and performance optimizations
Browse files Browse the repository at this point in the history
-H264 parsing speed is increased (avoid constant memory copying when possible)
-Small performance optimizations are made in general
-Multi-threaded bugs are fixed
-Fixed bug in RTSP over TCP when on some cameras were constant receive timeouts
-RTCP reports minimum delay is increased up to 5 seconds to match RFC 3550
-New example project is added (JPEG snapshots maker)
  • Loading branch information
Bogdanov Kirill committed Aug 12, 2018
1 parent f80a36b commit 78ad2c6
Show file tree
Hide file tree
Showing 29 changed files with 495 additions and 235 deletions.
7 changes: 7 additions & 0 deletions Examples/MjpegSnapshotsMaker/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>

<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
</configuration>
71 changes: 71 additions & 0 deletions Examples/MjpegSnapshotsMaker/MjpegSnapshotsMaker.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B8041875-F622-4B41-A727-CC4F2B227182}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>MjpegSnapshotsMaker</RootNamespace>
<AssemblyName>MjpegSnapshotsMaker</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="CommandLine, Version=2.2.1.0, Culture=neutral, PublicKeyToken=de6f01bd326f8c32, processorArchitecture=MSIL">
<HintPath>..\packages\CommandLineParser.2.2.1\lib\net45\CommandLine.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Console, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Console.4.0.0\lib\net46\System.Console.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Reflection.TypeExtensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.TypeExtensions.4.1.0\lib\net46\System.Reflection.TypeExtensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\RtspClientSharp\RtspClientSharp.csproj">
<Project>{dc94331f-847e-4456-bd27-f147551750fb}</Project>
<Name>RtspClientSharp</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
98 changes: 98 additions & 0 deletions Examples/MjpegSnapshotsMaker/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
using RtspClientSharp;
using RtspClientSharp.RawFrames.Video;

namespace MjpegSnapshotsMaker
{
class Program
{
public class Options
{
[Option('u', "uri", Required = true, HelpText = "RTSP URI")]
public Uri Uri { get; set; }

[Option('p', "path", Required = true, HelpText = "Path where snapshots should be saved")]
public string Path { get; set; }

[Option('i', "interval", Required = false, HelpText = "Snapshots saving interval in seconds")]
public int Interval { get; set; } = 5;
}

static void Main(string[] args)
{
Parser.Default.ParseArguments<Options>(args)
.WithParsed(options =>
{
var cancellationTokenSource = new CancellationTokenSource();

Task makeSnapshotsTask = MakeSnapshotsAsync(options, cancellationTokenSource.Token);

Console.ReadKey();

cancellationTokenSource.Cancel();
makeSnapshotsTask.Wait();
})
.WithNotParsed(options =>
{
Console.WriteLine("Usage example: MjpegSnapshotsMaker.exe " +
"-u rtsp://admin:[email protected]:554/ucast/11 " +
"-p S:\\Temp");
});
}

private static async Task MakeSnapshotsAsync(Options options, CancellationToken token)
{
try
{
if (!Directory.Exists(options.Path))
Directory.CreateDirectory(options.Path);

int intervalMs = options.Interval * 1000;
int lastTimeSnapshotSaved = Environment.TickCount - intervalMs;

var connectionParameters = new ConnectionParameters(options.Uri);
using (var rtspClient = new RtspClient(connectionParameters))
{
rtspClient.FrameReceived += (sender, frame) =>
{
if (!(frame is RawJpegFrame))
return;

int ticksNow = Environment.TickCount;

if (Math.Abs(ticksNow - lastTimeSnapshotSaved) < intervalMs)
return;

lastTimeSnapshotSaved = ticksNow;

string snapshotName = frame.Timestamp.ToString("O").Replace(":", "_") + ".jpg";
string path = Path.Combine(options.Path, snapshotName);

ArraySegment<byte> frameSegment = frame.FrameSegment;

using (var stream = File.OpenWrite(path))
stream.Write(frameSegment.Array, frameSegment.Offset, frameSegment.Count);

Console.WriteLine($"[{DateTime.UtcNow}] Snapshot is saved to {snapshotName}");
};

Console.WriteLine("Connecting...");
await rtspClient.ConnectAsync(token);
Console.WriteLine("Receiving...");
await rtspClient.ReceiveAsync(token);
}
}
catch (OperationCanceledException)
{
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}
36 changes: 36 additions & 0 deletions Examples/MjpegSnapshotsMaker/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MjpegSnapshotsMaker")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MjpegSnapshotsMaker")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("b8041875-f622-4b41-a727-cc4f2b227182")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
18 changes: 18 additions & 0 deletions Examples/MjpegSnapshotsMaker/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>

<packages>
<package id="CommandLineParser" version="2.2.1" targetFramework="net461" />
<package id="System.Collections" version="4.0.11" targetFramework="net461" />
<package id="System.Console" version="4.0.0" targetFramework="net461" />
<package id="System.Diagnostics.Debug" version="4.0.11" targetFramework="net461" />
<package id="System.Globalization" version="4.0.11" targetFramework="net461" />
<package id="System.IO" version="4.1.0" targetFramework="net461" />
<package id="System.Linq" version="4.1.0" targetFramework="net461" />
<package id="System.Linq.Expressions" version="4.1.0" targetFramework="net461" />
<package id="System.Reflection" version="4.1.0" targetFramework="net461" />
<package id="System.Reflection.Extensions" version="4.0.1" targetFramework="net461" />
<package id="System.Reflection.TypeExtensions" version="4.1.0" targetFramework="net461" />
<package id="System.Resources.ResourceManager" version="4.0.1" targetFramework="net461" />
<package id="System.Runtime" version="4.1.0" targetFramework="net461" />
<package id="System.Runtime.Extensions" version="4.1.0" targetFramework="net461" />
</packages>
8 changes: 4 additions & 4 deletions Examples/SimpleRtspPlayer/GUI/RealtimeVideoSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class RealtimeVideoSource : IVideoSource, IDisposable
private readonly Dictionary<FFmpegVideoCodecId, FFmpegVideoDecoder> _videoDecodersMap =
new Dictionary<FFmpegVideoCodecId, FFmpegVideoDecoder>();

private ulong _desiredSize;
private long _desiredSize;

public event EventHandler<IDecodedVideoFrame> FrameReceived;

Expand All @@ -41,8 +41,8 @@ public void SetRawFramesSource(IRawFramesSource rawFramesSource)

public void SetVideoSize(int width, int height)
{
ulong desiredSize = (ulong) width << 32 | (uint) height;
Volatile.Write(ref _desiredSize, desiredSize);
long desiredSize = (long) width << 32 | (uint) height;
Interlocked.Exchange(ref _desiredSize, desiredSize);
}

public void Dispose()
Expand All @@ -68,7 +68,7 @@ private void OnFrameReceived(object sender, RawFrame rawFrame)
if (!decoder.TryDecode(rawVideoFrame, out DecodedVideoFrameParameters decodedFrameParameters))
return;

ulong desiredSize = Volatile.Read(ref _desiredSize);
long desiredSize = Interlocked.Read(ref _desiredSize);

int targetWidth;
int targetHeight;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private void OnStartButtonClick()

var credential = new NetworkCredential(Login, Password);
var connectionParameters = new ConnectionParameters(deviceUri, credential);
connectionParameters.RtpTransport = RtpTransportProtocol.UDP;

_mainWindowModel.Start(connectionParameters);
_mainWindowModel.StatusChanged += MainWindowModelOnStatusChanged;

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Something like this:
var serverUri = new Uri("rtsp://192.168.1.77:554/ucast/11");
var credentials = new NetworkCredential("admin", "123456");
var connectionParameters = new ConnectionParameters(serverUri, credentials);
connectionParameters.RtpTransport == RtpTransportProtocol.TCP;
connectionParameters.RtpTransport = RtpTransportProtocol.TCP;
using(var rtspClient = new RtspClient(connectionParameters))
{
rtspClient.FrameReceived += (sender, frame) =>
Expand Down
22 changes: 10 additions & 12 deletions RtspClientSharp.UnitTests/MediaParsers/H264ParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,35 @@ public void Parse_IFrameBytesThenPFrameBytes_GeneratesTwoFrames()

RawH264Frame frame = null;
var parser = new H264Parser {FrameGenerated = rawFrame => frame = (RawH264Frame) rawFrame};
parser.Parse(new ArraySegment<byte>(spsBytes));
parser.Parse(new ArraySegment<byte>(ppsBytes));
parser.Parse(new ArraySegment<byte>(iFrameBytes));
parser.GenerateFrame(DateTime.UtcNow);
parser.Parse(DateTime.UtcNow, new ArraySegment<byte>(spsBytes), false, false);
parser.Parse(DateTime.UtcNow, new ArraySegment<byte>(ppsBytes), false, false);
parser.Parse(DateTime.UtcNow, new ArraySegment<byte>(iFrameBytes), false, true);

Assert.IsInstanceOfType(frame, typeof(RawH264IFrame));
Assert.IsTrue(frame.FrameSegment.SequenceEqual(iFrameBytes));

parser.Parse(new ArraySegment<byte>(pFrameBytes));
parser.GenerateFrame(DateTime.UtcNow);
parser.Parse(DateTime.UtcNow, new ArraySegment<byte>(pFrameBytes), false, true);

Assert.IsInstanceOfType(frame, typeof(RawH264PFrame));
Assert.IsTrue(frame.FrameSegment.SequenceEqual(pFrameBytes));
}

[TestMethod]
public void ResetState_IFrameThenReset_FrameNotGenerated()
public void ResetState_SpsPpsThenIFrameThenReset_FrameGenerated()
{
var spsBytes = Convert.FromBase64String("AAAAAWdNQCmaZgUB7YC1AQEBBenA");
var ppsBytes = Convert.FromBase64String("AAAAAWjuPIA=");
var iFrameBytes = new byte[] {0x0, 0x0, 0x0, 0x1, 0x65, 0x88, 0x80, 0x10, 0x00};

RawH264Frame frame = null;
var parser = new H264Parser {FrameGenerated = rawFrame => frame = (RawH264Frame) rawFrame};
parser.Parse(new ArraySegment<byte>(spsBytes));
parser.Parse(new ArraySegment<byte>(ppsBytes));
parser.Parse(new ArraySegment<byte>(iFrameBytes));
parser.Parse(DateTime.UtcNow, new ArraySegment<byte>(spsBytes), false, false);
parser.Parse(DateTime.UtcNow, new ArraySegment<byte>(ppsBytes), false, false);

parser.ResetState();
parser.GenerateFrame(DateTime.UtcNow);
parser.Parse(DateTime.UtcNow, new ArraySegment<byte>(iFrameBytes), false, true);

Assert.IsNull(frame);
Assert.IsInstanceOfType(frame, typeof(RawH264IFrame));
}
}
}
2 changes: 1 addition & 1 deletion RtspClientSharp.UnitTests/Rtcp/RtcpPacketTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void Parse_SenderReportBytes_ReturnsValidPacket()
Assert.AreEqual(0, senderReportPacket.SourceCount);
Assert.AreEqual(28, senderReportPacket.Length);
Assert.AreEqual(0x2753B30u, senderReportPacket.SyncSourceId);
Assert.AreEqual((ulong) 0xDF000140 << 32 | 0x5313AD5Bu, senderReportPacket.NtpTimestamp);
Assert.AreEqual((long) 0xDF000140 << 32 | 0x5313AD5Bu, senderReportPacket.NtpTimestamp);
}

[TestMethod]
Expand Down
11 changes: 9 additions & 2 deletions RtspClientSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27703.2026
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Perfmaker.Rtsp.Client", "RtspClientSharp\RtspClientSharp.csproj", "{DC94331F-847E-4456-BD27-F147551750FB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RtspClientSharp", "RtspClientSharp\RtspClientSharp.csproj", "{DC94331F-847E-4456-BD27-F147551750FB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{F6A04B7F-1138-4A5F-A306-1A78F443169F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleRtspClient", "Examples\SimpleRtspClient\SimpleRtspClient.csproj", "{145A3C38-93D1-4E35-8FB9-A8E166E0DAC3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Perfmaker.Rtsp.Client.UnitTests", "RtspClientSharp.UnitTests\RtspClientSharp.UnitTests.csproj", "{FD515510-7EBA-45A4-AC0A-2C6993DD0FBC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RtspClientSharp.UnitTests", "RtspClientSharp.UnitTests\RtspClientSharp.UnitTests.csproj", "{FD515510-7EBA-45A4-AC0A-2C6993DD0FBC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleRtspPlayer", "Examples\SimpleRtspPlayer\SimpleRtspPlayer.csproj", "{40493D0C-21B8-4357-AE75-D7637B3FC223}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libffmpeghelper", "Examples\libffmpeghelper\libffmpeghelper.vcxproj", "{3C96BD24-3212-4CD8-86E3-63D1E3E38155}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MjpegSnapshotsMaker", "Examples\MjpegSnapshotsMaker\MjpegSnapshotsMaker.csproj", "{B8041875-F622-4B41-A727-CC4F2B227182}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -39,6 +41,10 @@ Global
{40493D0C-21B8-4357-AE75-D7637B3FC223}.Release|Any CPU.Build.0 = Release|Any CPU
{3C96BD24-3212-4CD8-86E3-63D1E3E38155}.Debug|Any CPU.ActiveCfg = Debug|Win32
{3C96BD24-3212-4CD8-86E3-63D1E3E38155}.Release|Any CPU.ActiveCfg = Release|Win32
{B8041875-F622-4B41-A727-CC4F2B227182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8041875-F622-4B41-A727-CC4F2B227182}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8041875-F622-4B41-A727-CC4F2B227182}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8041875-F622-4B41-A727-CC4F2B227182}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -47,6 +53,7 @@ Global
{145A3C38-93D1-4E35-8FB9-A8E166E0DAC3} = {F6A04B7F-1138-4A5F-A306-1A78F443169F}
{40493D0C-21B8-4357-AE75-D7637B3FC223} = {F6A04B7F-1138-4A5F-A306-1A78F443169F}
{3C96BD24-3212-4CD8-86E3-63D1E3E38155} = {F6A04B7F-1138-4A5F-A306-1A78F443169F}
{B8041875-F622-4B41-A727-CC4F2B227182} = {F6A04B7F-1138-4A5F-A306-1A78F443169F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F5BB521B-37CF-4433-9370-76D4DD3BCEBF}
Expand Down
Loading

0 comments on commit 78ad2c6

Please sign in to comment.