• 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的代码就不展示了,网上很多!

  • 相关阅读:
    ZOJ2402 Lenny's Lucky Lotto List 简单DP
    HDU1024 最大M子段和问题 (单调队列优化)
    HDU2048 HDU2049 组合数系列 错排
    HDU1081 最大字段和 压缩数组(单调队列优化)
    HDU1166 数状数组
    HDU1085 多重背包
    HDU3062
    递归 递推 规律
    【机器学习PAI实战】—— 玩转人工智能之美食推荐
    阿里开源自用 OpenJDK 版本,Java 社区迎来中国力量
  • 原文地址:https://www.cnblogs.com/Lexy/p/13203005.html
Copyright © 2020-2023  润新知