C# RTSP解码-RtspClientSharp库使用分享

1,755 阅读4分钟

C# RTSP解码-RtspClientSharp库使用分享

一.项目前言

1.项目部分需求

获取输出的rtsp流,对其进行拆分取帧解码,并实时进行重构。

2.使用的库及思路

C# 的RtspClientSharp库,按照github上例子进行rtsp的流抓取,并捕获其帧的格式,进行拆分解码(此处的解码是方法是ffmpeg库,使用优化也是作者用c++进行编写的dll,后面会介绍其用法)

下面贴一个库的链接:BogdanovKirill/RtspClientSharp: Pure C# RTSP client for .NET Standard without external dependencies and with true async nature. I welcome contributions. (github.com)

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);

贴下我这部分的项目组-需要请原库里自取

image-20240401170336420.png

这里有几个注意点提醒以下,首先dll库 libffmpeghelper.dll 可以用源码自行生成 x86版本和x64版本,当然下面这个ffmpeg需要的库也别忘记,作者源码库里都有

image-20240401172349534.png

解码部分我也对库的例子进行了参考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;

  		}
        

三.总结

image-20240401175117133.png 项目部分展示

通篇写下来,个人还是觉得太繁琐,如果有rtsp解码更方便的办法,请留言告诉我谢谢!