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连接\t播放客户端[{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连接\t播放客户端[{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\n");
 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的代码就不展示了,网上很多!

 

posted @ 2020-06-28 15:25  Lexy  阅读(10796)  评论(17编辑  收藏  举报