C# RTSP解码-RtspClientSharp库使用分享
一.项目前言
1.项目部分需求
获取输出的rtsp流,对其进行拆分取帧解码,并实时进行重构。
2.使用的库及思路
C# 的RtspClientSharp库,按照github上例子进行rtsp的流抓取,并捕获其帧的格式,进行拆分解码(此处的解码是方法是ffmpeg库,使用优化也是作者用c++进行编写的dll,后面会介绍其用法)
3.为何使用此库?
测试的时候使用过VLC的库,发现延迟实在接受不了,不如自己对流进行拆分解码 速度符合项目需求。当时查阅的时候,也看到了opencv。由于已测试过此库,也就没有继续再测试opencv
二.项目使用注意
1.如何安装库
使用内部nuget自行搜索RtspClientSharp库,本人使用的1.3.3版本
2.如何取流?
下面直接贴源码和参考的链接,源码上会标注出需要 自行构建的参数
private CancellationTokenSource cancellationTokenSource;
string RtspLive = "自定义rtsp的流";
//点击开始的时候进行取流
public void startPlay() {
try
{
var serverUri = new Uri(RtspLive);
var connectionParameters = new ConnectionParameters(serverUri);
cancellationTokenSource = new CancellationTokenSource();
connectTask = ConnectAsync(connectionParameters, cancellationTokenSource.Token);
}
catch (Exception)
{
MessageBox.Show("Rtsp取流失败!");
}
}
//开始进行异步连接取流
private async Task ConnectAsync(ConnectionParameters connectionParameters, CancellationToken token) {
try {
TimeSpan delay = TimeSpan.FromSeconds(5);
using (var rtspClient = new RtspClient(connectionParameters)) {
rtspClient.FrameReceived += RtspClient_FrameReceived;
while (true) {
// Console.WriteLine("Connecting...");
try {
await rtspClient.ConnectAsync(token);
}
catch (OperationCanceledException) {
rtspClient.FrameReceived -= RtspClient_FrameReceived;
return;
}
catch (RtspClientException e) {
Console.WriteLine(e.ToString());
await Task.Delay(delay, token);
continue;
}
Console.WriteLine("Connected.");
try {
await rtspClient.ReceiveAsync(token);
}
catch (OperationCanceledException) {
return;
}
catch (RtspClientException e) {
Console.WriteLine(e.ToString());
await Task.Delay(delay, token);
}
}
}
}
catch (OperationCanceledException) {
}
}
//流接受并进行解码
private void RtspClient_FrameReceived(object sender, RtspClientSharp.RawFrames.RawFrame e) {
/* loggerManager.LogDebug($"New frame {e.Timestamp}: {e.GetType().Name}");
e.FrameSegment*/
loggerManager.LogDebug($"帧的格式+{e.GetType().Name}"); //日志打印部分 自行构建
//h264的处理内容
if (e is RawVideoFrame rawVideoFrame) {
var decoder = GetDecoderForFrame(rawVideoFrame); //这里涉及到解码部分 后面会讲
IDecodedVideoFrame decodedFrame = decoder.TryDecode(rawVideoFrame);//这里涉及到解码部分 后面会讲
Invalidate(decodedFrame); //这里涉及展示部分 放到后面会讲
Application.Current.Dispatcher.Invoke(_picRefresh, DispatcherPriority.Send); //这里涉及展示部分 放到后面会讲
}
}
//停止取流
public async void stopPlay() {
cancellationTokenSource.Cancel();
// await connectTask;
}
上面源码部分介绍了 如何使用此库取流的过程,监听流进行拆包,对所需要的h264的包进行解码,再进行展示,并停止取流的函数。 后面会介绍 解码 和展示部分
参考RtspClientSharp/Examples/SimpleRtspClient at master · BogdanovKirill/RtspClientSharp (github.com)
3.如何解码?
主要是使用原作者提供的dll库 自行生成,再把需要的函数利用到自己的源码中,下面贴我使用的解码部分,建设使用的此库的解码功能最好还是把库下下来,对examples部分自己再阅读一边,更加深刻去理解作者的思路;
//注意对上文解码部分的解释
var decoder = GetDecoderForFrame(rawVideoFrame);
private readonly Dictionary<FFmpegVideoCodecId, FFmpegVideoDecoder> _videoDecodersMap =
new Dictionary<FFmpegVideoCodecId, FFmpegVideoDecoder>();
//这里对帧进行FFmpegVideoDecoder的封装
private FFmpegVideoDecoder GetDecoderForFrame(RawVideoFrame videoFrame) {
FFmpegVideoCodecId codecId = DetectCodecId(videoFrame);
if (!_videoDecodersMap.TryGetValue(codecId, out FFmpegVideoDecoder decoder)) {
decoder = FFmpegVideoDecoder.CreateDecoder(codecId); //FFmpegVideoDecoder属于库的内容,需要自行去原作者库里取
_videoDecodersMap.Add(codecId, decoder);
}
return decoder;
}
//区分帧的类型
private FFmpegVideoCodecId DetectCodecId(RawVideoFrame videoFrame) {
if (videoFrame is RawJpegFrame)
return FFmpegVideoCodecId.MJPEG;
if (videoFrame is RawH264Frame)
return FFmpegVideoCodecId.H264;
throw new ArgumentOutOfRangeException(nameof(videoFrame));
}
//这里能使用TryDecode 方法是因为接口IDecodedVideoFrame ,也是原库提供的
IDecodedVideoFrame decodedFrame = decoder.TryDecode(rawVideoFrame);
贴下我这部分的项目组-需要请原库里自取
这里有几个注意点提醒以下,首先dll库 libffmpeghelper.dll 可以用源码自行生成 x86版本和x64版本,当然下面这个ffmpeg需要的库也别忘记,作者源码库里都有
解码部分我也对库的例子进行了参考RtspClientSharp/Examples/SimpleRtspPlayer at master · BogdanovKirill/RtspClientSharp (github.com)
4.如何展示?
Invalidate(decodedFrame); //这里进行指针转换到writeableBitmap
private static WriteableBitmap _writeableBitmap
const int dst_row = 480;
const int dst_col = 640;
private void Invalidate(IDecodedVideoFrame decodedVideoFrame) {
if (_width == 0 || _height == 0)
return;
_writeableBitmap.Lock();
try {
decodedVideoFrame.TransformTo(_writeableBitmap.BackBuffer, _writeableBitmap.BackBufferStride, _transformParameters)
//TransformTo 也是库里提供的接口
_writeableBitmap.AddDirtyRect(_dirtyRect);
}
finally {
_writeableBitmap.Unlock();
}
CurrentBitmap = _writeableBitmap.ToSKBitmap(); //这里因为需要取到的帧图进行二次开发,所以存入到skbitmap里,这里不展开讲。一般无二次开发需求,直接使用writeableBitmap就可展示帧图
}
//分享一个 WriteableBitmap的生成 自用的 640x480 分辨率
private void ReinitializeBitmap(int width, int height) {
_width = width;
_height = height;
_dirtyRect = new Int32Rect(0, 0, width, height);
_transformParameters = new TransformParameters(RectangleF.Empty,
new System.Drawing.Size(_width, _height),
ScalingPolicy.Stretch, SimpleRtspPlayer.RawFramesDecoding.PixelFormat.Bgra32, ScalingQuality.FastBilinear);
_writeableBitmap = new WriteableBitmap(
width,
height,
96,
96,
PixelFormats.Pbgra32,
null);
}
然后进行图片的控件展示,这里采用委托的方式展示
Application.Current.Dispatcher.Invoke(_picRefresh, DispatcherPriority.Send); //解释这部分展示,DispatcherPriority.Send防止抢线程
//图片刷新委托
private readonly Action<bool> _picRefresh;
//构造函数
public DemoModel() {
调用定义委托
_picRefresh = PicRefresh;
}
private void PicRefresh()
{
VideoImage.Source = _writeableBitmap;
}
三.总结
项目部分展示
通篇写下来,个人还是觉得太繁琐,如果有rtsp解码更方便的办法,请留言告诉我谢谢!