Skip to content

Commit

Permalink
Now it's possible to get snapshots from H.264 video stream by using t…
Browse files Browse the repository at this point in the history
…ranscoding

Code here is not very efficient but could be good starting point.
  • Loading branch information
Bogdanov Kirill committed Oct 22, 2018
1 parent 6232276 commit bdfaea3
Show file tree
Hide file tree
Showing 17 changed files with 73 additions and 162 deletions.
14 changes: 1 addition & 13 deletions RtspCapture.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,28 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28010.2046
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RtspCapture", "RtspCapture\RtspCapture.csproj", "{5DC5AECB-3072-4B82-93EF-CE0990B21004}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RtspCapture", "RtspCapture\RtspCapture.csproj", "{5DC5AECB-3072-4B82-93EF-CE0990B21004}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RtspClientSharp", "RtspClientSharp\RtspClientSharp.csproj", "{1E3DCB5C-FA9D-4A6B-AADC-0FF971989A74}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libffmpeghelper", "libffmpeghelper\libffmpeghelper.vcxproj", "{3C96BD24-3212-4CD8-86E3-63D1E3E38155}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5DC5AECB-3072-4B82-93EF-CE0990B21004}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5DC5AECB-3072-4B82-93EF-CE0990B21004}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5DC5AECB-3072-4B82-93EF-CE0990B21004}.Debug|x86.ActiveCfg = Debug|Any CPU
{5DC5AECB-3072-4B82-93EF-CE0990B21004}.Debug|x86.Build.0 = Debug|Any CPU
{5DC5AECB-3072-4B82-93EF-CE0990B21004}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5DC5AECB-3072-4B82-93EF-CE0990B21004}.Release|Any CPU.Build.0 = Release|Any CPU
{5DC5AECB-3072-4B82-93EF-CE0990B21004}.Release|x86.ActiveCfg = Release|Any CPU
{5DC5AECB-3072-4B82-93EF-CE0990B21004}.Release|x86.Build.0 = Release|Any CPU
{1E3DCB5C-FA9D-4A6B-AADC-0FF971989A74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E3DCB5C-FA9D-4A6B-AADC-0FF971989A74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E3DCB5C-FA9D-4A6B-AADC-0FF971989A74}.Debug|x86.ActiveCfg = Debug|Any CPU
{1E3DCB5C-FA9D-4A6B-AADC-0FF971989A74}.Debug|x86.Build.0 = Debug|Any CPU
{1E3DCB5C-FA9D-4A6B-AADC-0FF971989A74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E3DCB5C-FA9D-4A6B-AADC-0FF971989A74}.Release|Any CPU.Build.0 = Release|Any CPU
{1E3DCB5C-FA9D-4A6B-AADC-0FF971989A74}.Release|x86.ActiveCfg = Release|Any CPU
{1E3DCB5C-FA9D-4A6B-AADC-0FF971989A74}.Release|x86.Build.0 = Release|Any CPU
{3C96BD24-3212-4CD8-86E3-63D1E3E38155}.Debug|Any CPU.ActiveCfg = Debug|Win32
{3C96BD24-3212-4CD8-86E3-63D1E3E38155}.Debug|x86.ActiveCfg = Debug|Win32
{3C96BD24-3212-4CD8-86E3-63D1E3E38155}.Debug|x86.Build.0 = Debug|Win32
{3C96BD24-3212-4CD8-86E3-63D1E3E38155}.Release|Any CPU.ActiveCfg = Release|Win32
{3C96BD24-3212-4CD8-86E3-63D1E3E38155}.Release|x86.ActiveCfg = Release|Win32
{3C96BD24-3212-4CD8-86E3-63D1E3E38155}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
Expand Down
99 changes: 19 additions & 80 deletions RtspCapture/Program.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using CommandLine;
using RtspClientSharp;
using RtspClientSharp.RawFrames.Video;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Imaging;
using RtspCapture.processor;
using RtspCapture.RawFramesReceiving;

Expand All @@ -25,28 +23,11 @@ public class Options
[Option('i', "interval", Required = false, HelpText = "Snapshots saving interval in seconds")]
public int Interval { get; set; } = 5;
}
private static readonly RTSPProcessor rTSPProcessor = new RTSPProcessor();
//private RawFramesSource _rawFramesSource;

public event EventHandler<string> StatusChanged;

//public IVideoSource VideoSource => _realtimeVideoSource;
static void Main(string[] args)
{


Parser.Default.ParseArguments<Options>(args)
.WithParsed(options =>
{
var cancellationTokenSource = new CancellationTokenSource();

//Task makeSnapshotsTask = MakeSnapshotsAsync(options, cancellationTokenSource.Token);
StartCapture(options);
Console.ReadKey();

cancellationTokenSource.Cancel();
//makeSnapshotsTask.Wait();
})
.WithParsed(StartCapture)
.WithNotParsed(options =>
{
Console.WriteLine("Usage example: MjpegSnapshotsMaker.exe " +
Expand All @@ -55,14 +36,11 @@ static void Main(string[] args)
});

Console.WriteLine("Press any key to cancel");
Console.ReadLine();

Console.ReadKey(false);
}

private static void StartCapture(Options options)
{
//if (_rawFramesSource != null)
// return;
if (!Directory.Exists(options.Path))
Directory.CreateDirectory(options.Path);

Expand All @@ -71,70 +49,31 @@ private static void StartCapture(Options options)

var connectionParameters = new ConnectionParameters(options.Uri);

RawFramesSource _rawFramesSource = new RawFramesSource(connectionParameters);
_rawFramesSource.ConnectionStatusChanged += ConnectionStatusChanged;

rTSPProcessor.SetRawFramesSource(_rawFramesSource);

_rawFramesSource.Start();
}

private static void ConnectionStatusChanged(object sender, string s)
{
//StatusChanged?.Invoke(this, s);
}

private static async Task MakeSnapshotsAsync(Options options, CancellationToken token)
{
try
var rawFramesSource = new RawFramesSource(connectionParameters);
rawFramesSource.ConnectionStatusChanged += (sender, status) => Console.WriteLine(status);
var decodedFrameSource = new DecodedFrameSource();
decodedFrameSource.FrameReceived += (sender, frame) =>
{
if (!Directory.Exists(options.Path))
Directory.CreateDirectory(options.Path);

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

var connectionParameters = new ConnectionParameters(options.Uri);

//using (var rtspClient = new RtspClient(connectionParameters))
//{
// rtspClient.FrameReceived += (sender, frame) =>
// {
if (Math.Abs(ticksNow - lastTimeSnapshotSaved) < intervalMs)
return;

// int ticksNow = Environment.TickCount;
lastTimeSnapshotSaved = ticksNow;

// if (Math.Abs(ticksNow - lastTimeSnapshotSaved) < intervalMs)
// return;
Bitmap bitmap = frame.GetBitmap();

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

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

// ArraySegment<byte> frameSegment = frame.FrameSegment;
Console.WriteLine($"[{DateTime.UtcNow}] Snapshot is saved to {snapshotName}");
};

// //var stream = new MemoryStream(frameSegment.Array, 0, frameSegment.Count);
decodedFrameSource.SetRawFramesSource(rawFramesSource);

// //Image image = Image.FromStream(stream);

// //image.Save($"[{DateTime.UtcNow}] Snapshot is saved to {snapshotName}");

// 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());
}
rawFramesSource.Start();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Threading;
using RtspCapture.RawFramesDecoding;
using RtspCapture.RawFramesDecoding.DecodedFrames;
using RtspCapture.RawFramesDecoding.FFmpeg;
Expand All @@ -12,17 +10,17 @@

namespace RtspCapture.processor
{
class RTSPProcessor : IRTSPProcessor, IDisposable
class DecodedFrameSource : IDisposable
{

private IRawFramesSource _rawFramesSource;
private byte[] _decodedFrameBuffer = new byte[0];

private PostVideoDecodingParameters _postVideoDecodingParameters = new PostVideoDecodingParameters(RectangleF.Empty,
new Size(0, 0), ScalingPolicy.Stretch, PixelFormat.Bgr24, ScalingQuality.Nearest);

private readonly Dictionary<FFmpegVideoCodecId, FFmpegVideoDecoder> _videoDecodersMap =
new Dictionary<FFmpegVideoCodecId, FFmpegVideoDecoder>();

private long _desiredSize;

public event EventHandler<IDecodedVideoFrame> FrameReceived;


Expand All @@ -42,12 +40,6 @@ public void SetRawFramesSource(IRawFramesSource rawFramesSource)
rawFramesSource.FrameReceived += OnFrameReceived;
}

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

public void Dispose()
{
DropAllVideoDecoders();
Expand All @@ -71,43 +63,26 @@ private void OnFrameReceived(object sender, RawFrame rawFrame)
if (!decoder.TryDecode(rawVideoFrame, out DecodedVideoFrameParameters decodedFrameParameters))
return;

//long desiredSize = (long)rawFrame. << 32 | (uint)height;

//long desiredSize = Interlocked.Read(ref _desiredSize);
int targetWidth = decodedFrameParameters.Width;
int targetHeight = decodedFrameParameters.Height;

int targetWidth;
int targetHeight;

int bufferSize;

//if (desiredSize == 0)
//{
targetWidth = decodedFrameParameters.Width;
targetHeight = decodedFrameParameters.Height;

bufferSize = decodedFrameParameters.Height *
int bufferSize = decodedFrameParameters.Height *
ImageUtils.GetStride(decodedFrameParameters.Width, PixelFormat.Bgr24);
//}
//else
//{
// targetWidth = (int)(desiredSize >> 32);
// targetHeight = (int)desiredSize;

// bufferSize = targetHeight *
// ImageUtils.GetStride(targetWidth, PixelFormat.Bgr24);
//}


if (_decodedFrameBuffer.Length != bufferSize)
_decodedFrameBuffer = new byte[bufferSize];

var bufferSegment = new ArraySegment<byte>(_decodedFrameBuffer);

var postVideoDecodingParameters = new PostVideoDecodingParameters(RectangleF.Empty,
new Size(targetWidth, targetHeight),
ScalingPolicy.Stretch, PixelFormat.Bgr24, ScalingQuality.Bicubic);
if (_postVideoDecodingParameters.TargetFrameSize.Width != targetWidth ||
_postVideoDecodingParameters.TargetFrameSize.Height != targetHeight)
{
_postVideoDecodingParameters = new PostVideoDecodingParameters(RectangleF.Empty,
new Size(targetWidth, targetHeight),
ScalingPolicy.Stretch, PixelFormat.Bgr24, ScalingQuality.Nearest);
}

IDecodedVideoFrame decodedFrame = decoder.GetDecodedFrame(bufferSegment, postVideoDecodingParameters);
IDecodedVideoFrame decodedFrame = decoder.GetDecodedFrame(bufferSegment, _postVideoDecodingParameters);

FrameReceived?.Invoke(this, decodedFrame);
}
Expand Down
34 changes: 34 additions & 0 deletions RtspCapture/RawFramesDecoding/DecodedFrames/DecodedVideoFrame.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace RtspCapture.RawFramesDecoding.DecodedFrames
{
Expand All @@ -25,5 +28,36 @@ public DecodedVideoFrame(DateTime timestamp, ArraySegment<byte> decodedBytes, in
Format = format;
Stride = stride;
}

public Bitmap GetBitmap()
{
System.Drawing.Imaging.PixelFormat format;

switch (Format)
{
case PixelFormat.Bgr24:
format = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
break;
case PixelFormat.Abgr32:
format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
break;
default:
throw new InvalidOperationException("Unsupported format");
}

var bitmap = new Bitmap(Width, Height, format);

var boundsRect = new Rectangle(0, 0, Width, Height);

BitmapData bmpData = bitmap.LockBits(boundsRect,
ImageLockMode.WriteOnly,
bitmap.PixelFormat);

IntPtr ptr = bmpData.Scan0;
Marshal.Copy(DecodedBytes.Array, DecodedBytes.Offset, ptr, DecodedBytes.Count);
bitmap.UnlockBits(bmpData);

return bitmap;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Drawing;

namespace RtspCapture.RawFramesDecoding.DecodedFrames
{
Expand All @@ -12,5 +13,7 @@ public interface IDecodedVideoFrame
int Height { get; }
PixelFormat Format { get; }
int Stride { get; }

Bitmap GetBitmap();
}
}
2 changes: 1 addition & 1 deletion RtspCapture/RtspCapture.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@
<ProjectReference Include="..\RtspClientSharp\RtspClientSharp.csproj" />
</ItemGroup>
<PropertyGroup>
<PostBuildEvent>xcopy $(ProjectDir)\x86\*.dll $(TargetDir) /Y /E /C /F</PostBuildEvent>
<PostBuildEvent>xcopy $(MSBuildProjectDirectory)\x86\*.dll $(TargetDir) /Y /E /C /F</PostBuildEvent>
</PropertyGroup>
</Project>
18 changes: 0 additions & 18 deletions RtspCapture/processor/IRTSPProcessor.cs

This file was deleted.

10 changes: 0 additions & 10 deletions RtspCapture/rtspFrameParser.cs

This file was deleted.

Binary file added RtspCapture/x86/avcodec-58.dll
Binary file not shown.
Binary file added RtspCapture/x86/avdevice-58.dll
Binary file not shown.
Binary file added RtspCapture/x86/avfilter-7.dll
Binary file not shown.
Binary file added RtspCapture/x86/avformat-58.dll
Binary file not shown.
Binary file added RtspCapture/x86/avutil-56.dll
Binary file not shown.
Binary file added RtspCapture/x86/libffmpeghelper.dll
Binary file not shown.
Binary file added RtspCapture/x86/postproc-55.dll
Binary file not shown.
Binary file added RtspCapture/x86/swresample-3.dll
Binary file not shown.
Binary file added RtspCapture/x86/swscale-5.dll
Binary file not shown.

0 comments on commit bdfaea3

Please sign in to comment.