zoukankan      html  css  js  c++  java
  • RTSP转WebSocket实现H5实时播放

    服务端使用C++/CLR包装EasyRTSPClient实现RTSP播放。播放不产生画面,而是转换成JPEG图像通过WEBSOCKET发送到浏览器端。

    服务端(C++ & C#):

    浏览器端(实时性不错!):

    内存使用情况:播放一路视频时占用39M内存。空闲时(无客户端)时自动关闭RTSP播放后内存占用为18MB.

    关于WebSocket通讯,服务端向浏览器发送二进制帧,每一帧为一张JPEG图像。

     可以看出Websocket播放的帧率为12帧。源码流也是12帧(使用海康威视相机H264子码流).每帧71KB.改变JPEG压缩质量可以更小,但更模糊。当前使用的压缩质量为0.6

    服务端WebSocket代码(使用WebSocketSharp),C#:

     1     public class WebSocketPlayBehavior : WebSocketBehavior, IClient {
     2 
     3         static List<WebSocketPlayBehavior> client = new List<WebSocketPlayBehavior>();
     4         public static int[] InPlayChannel() {
     5             return client.Select(x => x.ChannelId)
     6                 .Distinct()
     7                 .ToArray();
     8         }
     9 
    10         public int ChannelId {
    11             get;
    12             private set;
    13         }
    14         private string clientEP = null;
    15         public PlayServiceWork Service = null;
    16         public WebSocketPlayBehavior() {
    17             this.OriginValidator = (p) => {
    18                 System.Diagnostics.Debug.Print("OriginValidator:{0}", p);
    19                 return true;
    20             };
    21         }
    22         protected override void OnOpen() {
    23             base.OnOpen();
    24             #region 获取参数(this.ChannelId)
    25             {
    26                 var PARAM = this.Context.RequestUri.PathAndQuery.Split('?')
    27                      .LastOrDefault()
    28                      .Split('&')
    29                      .Select(x => x.Split('='))
    30                      .Where(x => x.Length > 1)
    31                      .ToDictionary(x => x[0].ToLower(), x => System.Net.WebUtility.UrlDecode(x[1])); //WebSocketSharp.Ext.UrlDecode
    32 
    33                 int tempid = 1;
    34                 if (PARAM.TryGetValue("channel", out var value)) {
    35                     if (Regex.IsMatch(value, "^\d+$"))
    36                         tempid = Convert.ToInt32(value);
    37                     else {
    38                         var index = RtpConfig.Get().Items.FindIndex(x => string.Equals(x.Name, value));
    39                         if (index > -1) tempid = index + 1;
    40                     }
    41                 }
    42                 this.ChannelId = Math.Max(1, tempid);
    43             }
    44             #endregion
    45             Service.PlayOneIfNotExist(this.ChannelId - 1);
    46 
    47             clientEP = this.Context.UserEndPoint.ToString();
    48             LEI.NLog.Logger.i("建立WS连接	播放客户端[{0}] <--> [通道:{1}]", clientEP, ChannelId);
    49             client.Add(this);
    50             RPlayer.AddClient(this);
    51         }
    52         protected override void OnClose(CloseEventArgs e) {
    53             LEI.NLog.Logger.i("关闭WS连接	播放客户端[{0}] <--> [通道:{1}]", clientEP, ChannelId);
    54             base.OnClose(e);
    55             RPlayer.DelClient(this);
    56             client.Remove(this);
    57         }
    58 
    59 
    60         public int GetChannelId() => ChannelId;
    61         private bool insend = false;
    62         private object lck = new object();
    63         public void SendImage(byte[] bufferImage) {
    64             lock (lck) {
    65                 if (insend)
    66                     return;
    67                 insend = true;
    68             }
    69 
    70             try {
    71                 this.SendAsync(bufferImage, (completed) => {
    72                     insend = false;
    73                 });
    74             } catch (Exception e) {
    75                 insend = false;
    76                 LEI.NLog.Logger.e("发送图片失败:{0}", e.Message);
    77                 //if (this.State == WebSocketState.Closed)
    78                 //    this.OnClose(null);
    79             }
    80         }
    81 
    82 
    83     }

    服务端YUV帧图像转JPEG图像代码(JPEG为转换后的图像),C++:

      1 // 抓图函数实现
      2 int take_snapshot(int w, int h, uint8_t *buffer, AVPixelFormat Format,PBYTE JPEG)
      3 {
      4      
      5     //MessageLog("take_snapshot");
      6     char              *fileext = NULL;
      7     enum AVCodecID     codecid = AV_CODEC_ID_NONE;
      8     struct SwsContext *sws_ctx = NULL;
      9     AVPixelFormat      swsofmt = AV_PIX_FMT_NONE;
     10     AVFrame            picture = {};
     11     int                ret     = -1;
     12 
     13     AVFormatContext   *fmt_ctxt   = NULL;
     14     AVOutputFormat    *out_fmt    = NULL;
     15     AVStream          *stream     = NULL;
     16     AVCodecContext    *codec_ctxt = NULL;
     17     AVCodec           *codec      = NULL;
     18     AVPacket           packet     = {};
     19     int                retry      = 8;
     20     int                got        = 0;
     21 
     22     // init ffmpeg
     23     av_register_all();
     24 
     25     //fileext = file + strlen(file) - 3;
     26     //if (_stricmp(fileext, "png") == 0) {
     27     //    codecid = AV_CODEC_ID_APNG;
     28     //    swsofmt = AV_PIX_FMT_RGB24;
     29     //}
     30     //else 
     31     {
     32         codecid = AV_CODEC_ID_MJPEG;
     33         swsofmt = AV_PIX_FMT_YUVJ420P;
     34     }
     35 
     36     AVFrame video;
     37     int numBytesIn;
     38     numBytesIn = av_image_get_buffer_size(Format, w, h, 1);
     39     av_image_fill_arrays(video.data, video.linesize, buffer, Format, w, h, 1);
     40     video.width = w;
     41     video.height = h;
     42     video.format = Format;
     43 
     44     // alloc picture
     45     picture.format = swsofmt;
     46     picture.width  = w > 0 ? w : video.width;
     47     picture.height = h > 0 ? h : video.height;
     48 
     49     int numBytes = av_image_get_buffer_size(swsofmt, picture.width, picture.height , 1);
     50 
     51     buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
     52 
     53     av_image_fill_arrays(picture.data, picture.linesize, buffer, swsofmt, picture.width, picture.height, 1);
     54 
     55     // scale picture
     56     sws_ctx = sws_getContext(video.width, video.height, (AVPixelFormat)Format/*video->format*/,
     57         picture.width, picture.height, swsofmt, SWS_FAST_BILINEAR, NULL, NULL, NULL);
     58     if (!sws_ctx) {
     59         //MessageLog("could not initialize the conversion context jpg");
     60         ////av_log(NULL, AV_LOG_ERROR, "could not initialize the conversion context jpg
    ");
     61         goto done;
     62     }
     63     sws_scale(sws_ctx, video.data, video.linesize, 0, video.height, picture.data, picture.linesize);
     64 
     65     // do encoding
     66     fmt_ctxt = avformat_alloc_context();
     67     out_fmt  = av_guess_format("mjpeg", NULL, NULL);
     68     fmt_ctxt->oformat = out_fmt;
     69     if (!out_fmt) { 
     70         goto done;
     71     }
     72 
     73     
     74     if (
     75         avio_open_dyn_buf(&fmt_ctxt->pb)
     76         //avio_open(&fmt_ctxt->pb, file, AVIO_FLAG_READ_WRITE)
     77         < 0) { 
     78         goto done;//MessageLog("failed to open output fi");
     79     }
     80 
     81     stream = avformat_new_stream(fmt_ctxt, 0);
     82     if (!stream) { 
     83         goto done;    //MessageLog("failed to create a new stream ");
     84     }
     85 
     86     codec_ctxt                = stream->codec;
     87     codec_ctxt->codec_id      = out_fmt->video_codec;
     88     codec_ctxt->codec_type    = AVMEDIA_TYPE_VIDEO;
     89     codec_ctxt->pix_fmt       = swsofmt;
     90     codec_ctxt->width         = picture.width;
     91     codec_ctxt->height        = picture.height;
     92     codec_ctxt->time_base.num = 1;
     93     codec_ctxt->time_base.den = 25;
     94     codec_ctxt->qcompress = CChannelManager::JPEG_LEVEL;//压缩质量
     95     codec_ctxt->qmin = 2;
     96     codec_ctxt->qmax = 31;
     97     codec_ctxt->max_qdiff = 15;
     98 
     99     codec = avcodec_find_encoder(codec_ctxt->codec_id);
    100     if (!codec)  
    101         goto done;    //MessageLog("failed to find encoder"); 
    102 
    103     if (avcodec_open2(codec_ctxt, codec, NULL) < 0)  
    104         goto done;//MessageLog("failed to open encoder"); 
    105 
    106 
    107     while (retry-- && !got) {
    108         if (avcodec_encode_video2(codec_ctxt, &packet, &picture, &got) < 0) { 
    109             goto done;//MessageLog("failed to do picture encoding");
    110         }
    111 
    112         if (got) {
    113             ret = avformat_write_header(fmt_ctxt, NULL);
    114             if (ret < 0) { 
    115                 goto done;    //MessageLog("error occurred when opening output file");
    116             }
    117             av_write_frame(fmt_ctxt, &packet);
    118             av_write_trailer(fmt_ctxt);
    119         }
    120     }
    121 
    122     // ok
    123     ret = 0;
    124 
    125 done: 
    126     avcodec_close(codec_ctxt);
    127     if (fmt_ctxt) {
    128         //avio_close(fmt_ctxt->pb);
    129         PUCHAR output; 
    130         ret = avio_close_dyn_buf(fmt_ctxt->pb, &output); 
    131         if (ret > 0) {
    132             memcpy_s(JPEG, ret, output, ret);
    133             av_freep(&output);
    134         }
    135         avformat_free_context(fmt_ctxt);
    136     }
    137     av_packet_unref(&packet);
    138     sws_freeContext(sws_ctx);
    139     av_free(buffer);
    140 
    141     return ret;
    142 }

    EasyRTSPClient的代码就不展示了,网上很多!

  • 相关阅读:
    c#自动更新+安装程序的制作
    VS2013项目受源代码管理向源代码管理注册此项目时出错
    WinDbg配置和使用基础
    InstallShield Limited Edition for Visual Studio 2013 图文教程(教你如何打包.NET程序)
    PowerDesigner 如何生成数据库更新脚本
    用户故事(User Story)
    Troubleshooting Record and Playback issues in Coded UI Test
    Coded UI
    compare two oracle database schemas
    How to: Use Schema Compare to Compare Different Database Definitions
  • 原文地址:https://www.cnblogs.com/Lexy/p/13203005.html
Copyright © 2011-2022 走看看