浏览器播放RTSP流,支持H264、H265等格式,支持IE、Chrome等浏览器

说明

背景

项目中需要在浏览器中播放RTSP流,实在是不想折腾ActiveX控件

1、麻烦(开发麻烦、使用时设置也麻烦)

2、浏览器兼容更麻烦

解决方案

使用OpenCvSharp+Nancy写一个解码服务,提供http接口,返回解码后Mat对象的Base64字符串,前端页面循环调用并展示。

效果

项目

11279f47e562236dce03ec7ea82f587f.png

代码

前端代码调用

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>rtsp播放测试</title>
    <link rel="stylesheet" href="bootstrap.min.css" />
    <script src="jquery-1.8.0.js" type="text/javascript"></script>
</head>
<body>
    <div class="container">
        <br />
        <div class="row">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <span class="label label-primary">rtsp播放测试</span>
                    <br />
                </div>
                <div class="panel-body">
                    <input type="text" value="" id="txtRTSPURL" style="width: 500px;" /><br />
                    <br />
                    <input type="button" value="打开" id="btnOpen" />
                    <input type="button" value="播放" id="btnPlay" />
                    <input type="button" value="停止" id="btnStop" />
                    <input type="button" value="测试获取一帧" id="btnTest" />
                    </br> </br>
                    <img id="imgId" src="" style="width: 1024px" />
                </div>
            </div>
        </div>
    </div>
</body>
<script type="text/jscript">
 
 
    $(function () {
 
        $("#btnOpen").click(function () {
            var rtsp_url = $("#txtRTSPURL").val();
            $.ajax({
                type: "post",
                url: base_url + "/open",
                dataType: 'json',
                data: { "rtsp_url": rtsp_url },
                success: function (d) {
                    if (d.code == 1) {
                        alert("打开成功")
                    } else {
                        alert("打开失败:" + d.message)
                    }
                }
            })
 
        })
 
 
        $("#btnTest").click(function () {
 
            $.ajax({
                type: "post",
                url: base_url + "/getframe",
                dataType: 'json',
                data: "",
                success: function (d) {
                    if (d.code == 1) {
                        console.log(d.data);
                        $("#imgId").attr("src", "data:image/jpg;base64," + d.data);
 
                    } else {
                        alert("播放失败:" + d.message)
                    }
                }
            })
 
        })
 
        $("#btnPlay").click(function () {
 
            try {
                flag = true;
                showImage();
            } catch (e) {
            }
 
        })
 
        $("#btnStop").click(function () {
            flag = false;
            $.ajax({
                type: "post",
                url: base_url + "/close",
                dataType: 'json',
                data: "",
                success: function (d) {
                    if (d.code == 1) {
 
                    } else {
                        alert("关闭失败:" + d.message)
                    }
                }
            })
        })
 
    })
 
    var base_url = "http://127.0.0.1:8082";
    var flag = false;
 
    function showImage() {
        $.ajax({
            type: "post",
            url: base_url + "/getframe",
            dataType: 'json',
            data: "",
            success: function (d) {
                if (d.code == 1) {
                    $("#imgId").attr("src", "data:image/jpg;base64," + d.data);
                    if (flag) {
                        showImage();
                    }
                } else {
                    alert("播放失败:" + d.message)
                }
            }
        })
 
    }
 
</script>
</html>

后端服务代码

using Nancy;
using Newtonsoft.Json;
using NLog;
using OpenCvSharp;
using OpenVINO.OCRService.Common;
using System;
using System.Threading;
using System.Threading.Tasks;
 
namespace CaptureService
{
    public class CaptureModule : NancyModule
    {
 
        private Logger _log = NLog.LogManager.GetCurrentClassLogger();
 
        public static readonly object _locker = new object();
 
        public CaptureModule()
        {
            //跨域处理
            After.AddItemToEndOfPipeline((ctx) => ctx.Response
            .WithHeader("Access-Control-Allow-Origin", "*")
            .WithHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS")
            .WithHeader("Access-Control-Allow-Headers", "Accept, Origin, Content-type"));
 
            Get("/", p =>
            {
                return "Hello MediaCaptureService";
            });
 
            Post("/open", p =>
            {
                AjaxReturn ar = new AjaxReturn();
 
                if (Program.open)
                {
                    ar.code = 0;
                    ar.message = "已开启,如需重新开启,请先关闭!";
                    _log.Info(JsonConvert.SerializeObject(ar));
                    return Response.AsJson<AjaxReturn>(ar);
                }
 
                string rtsp_url = Request.Form["rtsp_url"];
                if (string.IsNullOrEmpty(rtsp_url))
                {
                    ar.code = 0;
                    ar.message = "参数[rtsp_url]不能为空";
                    _log.Info(JsonConvert.SerializeObject(ar));
                    return Response.AsJson<AjaxReturn>(ar);
                }
 
                Program.rtsp_url = rtsp_url;
                Program.ctsCapture = new CancellationTokenSource();
                Program.open = true;
 
                try
                {
                    Task.Factory.StartNew(() =>
                    {
                        Program.capture = new VideoCapture(Program.rtsp_url);
                        if (Program.capture.IsOpened())
                        {
                            int index = 0;
                            Mat frame = new Mat();
                            while (true)
                            {
                                if (Program.ctsCapture.IsCancellationRequested) break;
                                Program.capture.Read(frame);
                                if (Program.matQueue.Count >= 5)
                                {
                                    continue;
                                }
                                Program.matQueue.Enqueue(frame);
                                //_log.Info(Program.matQueue.Count);
                                //Cv2.ImWrite(index + ".jpg", frame);
                                //index++;
                            }
                            if (Program.capture != null)
                            {
                                Program.capture.Release();
                            }
                        }
                    });
 
                    ar.code = 1;
                    ar.message = "success";
                }
                catch (Exception ex)
                {
                    ar.code = 0;
                    ar.message = ex.Message;
                    _log.Error(ex, "开启异常");
                }
                return Response.AsJson<AjaxReturn>(ar);
            });
 
            Post("/close", p =>
            {
                AjaxReturn ar = new AjaxReturn();
                try
                {
                    Program.open = false;
                    if (Program.ctsCapture != null)
                    {
                        Program.ctsCapture.Cancel();
                    }
                    ar.code = 1;
                    ar.message = "success";
                }
                catch (Exception ex)
                {
                    ar.code = 0;
                    ar.message = ex.Message;
                    _log.Error(ex, "关闭异常");
                }
                return Response.AsJson<AjaxReturn>(ar);
            });
 
            Post("/getframe", p =>
            {
                AjaxReturn ar = new AjaxReturn();
 
                if (!Program.open)
                {
                    ar.code = 0;
                    ar.message = "网络流未打开,请先打开!";
                    _log.Info(JsonConvert.SerializeObject(ar));
                    return Response.AsJson<AjaxReturn>(ar);
                }
 
                if (Program.matQueue.Count == 0)
                {
                    ar.code = 1;
                    ar.message = "图像队列为空";
                    _log.Info(JsonConvert.SerializeObject(ar));
                    return Response.AsJson<AjaxReturn>(ar);
                }
 
                try
                {
 
                    Mat frame = new Mat();
                    if (!Program.matQueue.TryDequeue(out frame))
                    {
                        ar.code = 0;
                        ar.message = "获取图像失败";
                        _log.Info(JsonConvert.SerializeObject(ar));
                        return Response.AsJson<AjaxReturn>(ar);
                    }
 
                    ar.code = 1;
                    ar.message = "success";
 
                    var bytes = frame.ToBytes();
                    ar.data = Convert.ToBase64String(bytes);
 
                }
                catch (Exception ex)
                {
                    ar.code = 0;
                    ar.message = ex.Message;
                    _log.Error(ex, "获取图像异常");
                }
                return Response.AsJson<AjaxReturn>(ar);
            });
 
        }
 
    }
 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值