在 ASP.NET Core 中将 RTSP 流从 IP 摄像机转发到浏览器

2023-11-29

我有一个 Blazor 托管应用程序,我需要根据客户请求从 AXIS 摄像机获取 h264 录制内容(通过 RTSP PLAY 命令)并以浏览器可以重现视频的方式返回它。如果向 AXIS 摄像机查询录音列表,答案包括这个,即我尝试在浏览器上播放的那个

    <recording diskid="SD_DISK" recordingid="20211109_122753_1AB3_B8A44F2D0300" starttime="2021-11-09T11:27:53.060281Z" starttimelocal="2021-11-09T12:27:53.060281+01:00" stoptime="2021-11-09T11:43:01.125987Z" stoptimelocal="2021-11-09T12:43:01.125987+01:00" recordingtype="continuous" eventid="continuous" eventtrigger="continuous" recordingstatus="completed" source="1" locked="No">
        <video mimetype="video/x-h264" width="800" height="600" framerate="15:1" resolution="800x600"/>
    </recording>

我可以通过“开放网络流...”并输入来成功地使用 VLC 重现录音

rtsp://192.168.0.125/axis-media/media.amp?recordingid=20211109_140710_E1A3_B8A44F2D0300

然后提供用户名和密码,所以我确信命令是正确的。通过在 url 中嵌入用户名和密码,可以播放录音this项目也是如此,其中使用了我在下面使用的更简单的语法,因此我的示例可能有点过于复杂。

服务器端我可以成功检索流,这要归功于RtspClientSharp,但我无法以正确的方式返回它。到目前为止我有这个:

[HttpGet("RecordingsDemo")]
    public async Task<IActionResult> RecordingsDemo() {
        string deviceIp = "rtsp://192.168.0.125";
        string recordingUri = "rtsp://192.168.0.125/axis-media/media.amp?recordingid=20211109_140710_E1A3_B8A44F2D0300";
        Uri playRequestUri = new Uri(recordingUri);

        CancellationTokenSource cts = new CancellationTokenSource();
        NetworkCredential networkCredential = new NetworkCredential("user", "password");
        ConnectionParameters connectionParameters = new ConnectionParameters(new Uri(deviceIp), networkCredential);
        RtspTcpTransportClient RtspTcpClient = new RtspTcpTransportClient(connectionParameters);
        await RtspTcpClient.ConnectAsync(cts.Token);

        RtspRequestMessage message = new RtspRequestMessage(RtspMethod.SETUP, playRequestUri);
        message.AddHeader("Transport", "RTP/AVP/TCP;unicast");
        RtspResponseMessage response = await RtspTcpClient.EnsureExecuteRequest(message, cts.Token);
        System.Collections.Specialized.NameValueCollection headers = response.Headers;
        string sessionId = headers["SESSION"];
        if (sessionId == null) { throw new Exception("RTSP initialization failed: no session id returned from SETUP command"); }
        message = new RtspRequestMessage(RtspMethod.PLAY, playRequestUri, sessionId);
        response = await RtspTcpClient.EnsureExecuteRequest(message, cts.Token);
        Stream stream = RtspTcpClient.GetStream();
        if (stream != null) {
            Response.Headers.Add("Cache-Control", "no-cache");
            FileStreamResult result = new FileStreamResult(stream, "video/x-h264") {
                EnableRangeProcessing = true
            };
            return result;
        } else {
            return new StatusCodeResult((int)HttpStatusCode.ServiceUnavailable);
        }
        return new StatusCodeResult((int)HttpStatusCode.OK);
    }

请注意,在上面的代码中,我向 RtspRequestMessage 添加了一个构造函数,以便更快地构建它。特别是我添加了以下代码:

    public uint _lastCSeqUsed { get; private set; }

    /// <param name="method">SETUP, PLAY etc</param>
    /// <param name="connectionUri">rtsp://<servername>/axis-media/media.amp?recordingid=...</param>
    /// <param name="cSeq">Method that generate the sequence number. The receiver will reply with the same sequence number</param>
    /// <param name="protocolVersion">Default to 1.0 if omitted or null</param>
    /// <param name="userAgent">Doesn't matter really</param>
    /// <param name="session">This parameter has to be initialized with the value returned by the SETUP method</param>
    public RtspRequestMessage(RtspMethod method, Uri connectionUri, string session = "", Func<uint> cSeqProvider = null, 
        Version protocolVersion = null, string userAgent = "client")
        : base((protocolVersion != null) ? protocolVersion : new Version("1.0"))
    {
        Method = method;
        ConnectionUri = connectionUri;
        UserAgent = userAgent;

        _cSeqProvider = (cSeqProvider != null) ? cSeqProvider : myfun;
        CSeq = (cSeqProvider != null) ? _cSeqProvider() : 0;

        if (!string.IsNullOrEmpty(session))
            Headers.Add("Session", session);
    }

    public void AddHeader(string name, string value)
    {
        Headers.Add(name, value);
    }

    private uint myfun()
    {
        return ++CSeq;
    }

当客户端通过 GET 方法调用此方法时,我非常确定通过查看 bandwitdh 和 Wireshark 可以正确检索记录。您可以在下图中看到 Wireshark 输出,其中 192.168.0.125 是摄像头,192.168.0.120 是服务器。

stream from camera to server

但是服务器返回的文件似乎无法播放。即使使用 VLC,我也无法播放返回的文件或流。 客户端与服务器的通信如下图所示,其中192.168.0.16是客户端,192.168.0.51是服务器。

strema from server to client

我需要能够返回 html5 视频元素可以播放的流。

你能指出我正确的方向吗?谢谢

EDIT.:正如你所看到的,我找到了一种方法,发布在下面。不过,我希望有一个更好的解决方案,不需要在磁盘上写入,也不会因生成 .ts 文件而增加延迟。因此,我对是否有人愿意做出贡献的问题保持开放。


最终我能够达到目标,即使不是以我想要的方式。这些是我必须完成的步骤,我在下面详细说明了它们。

从 RTSP 流生成 HTTP 流

  • 从 RTSP 流生成 .m3u8 和 .ts 文件。
  • 从控制器返回 .m3u8 文件。
  • 播放返回的 .m3u8 文件。这需要一个 javascript 库。

从 RTSP 流生成 .m3u8 和 .ts 文件

我使用以下 FFmpeg 命令从 RTSP 流中提取视频,以便我可以通过命令返回它

ffmpeg.exe -i rtsp://username:[email protected]/axis-media/media.amp?recordingid=20211109_122753_1AB3_B8A44F2D0300 -fflags flush_packets -max_delay 5 -flags -global_header -hls_time 3 -hls_list_size 0 -vcodec copy -y .\example.m3u8

我必须使用 -hls_list_size 0 因为在我的情况下我必须转换录音,并且由于用户需要能够在录音中前后查找,所以我必须设置“删除没有下载的 .ts 段”,请看FFmpeg 文档。我可以利用这个.m3u8 播放器演示检查问题是否出在我生成的视频或其他内容上。这个视频如何通过 NodeJS、FFMPEG 和 ReactJS 将 IP 摄像机 RTSP 流作为 HLS 传输到浏览器中也帮助了我。

从控制器返回 .m3u8 文件

这里我遇到了两个问题:由于缺少跨域标头,请求被阻止。此外,一旦浏览器检索到 .m3u8 文件,它期望能够向控制器请求 .ts 文件。所以我必须像这样构造代码:

[ApiController]
[Route("[controller]")]
public class CameraSystemController : ControllerBase {
    [HttpGet("Example")]
    public async Task<IActionResult> Example() {
        Response.Headers.Add("Access-Control-Allow-Origin", "*");
        return File(System.IO.File.OpenRead("Output/Video/example.m3u8"), "application/octet-stream", enableRangeProcessing: true);
    }

    [HttpGet("{tsFileName}")]
    public async Task<IActionResult> Example_GetTS(string tsFileName) {
        Response.Headers.Add("Access-Control-Allow-Origin", "*");
        return File(System.IO.File.OpenRead("Output/Video/" + tsFileName), "application/octet-stream", enableRangeProcessing: true);
    }
}

我要感谢这篇文章CORS和这篇文章实施动态控制器动作.

在浏览器中播放.m3u8文件

最后,为了在浏览器中播放 .m3u8 文件,我必须使用这个HLS JavaScript 项目,由于这个我发现了post.

我制作的工作 html 页面的示例如下:

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Example</title>
  <link href="https://unpkg.com/video.js/dist/video-js.css" rel="stylesheet">
  <script src="https://unpkg.com/video.js/dist/video.js"></script>
  <script src="https://unpkg.com/videojs-contrib-hls/dist/videojs-contrib-hls.js"></script>
   
</head>
<body>
  <video id="my_video_1" class="video-js vjs-fluid vjs-default-skin" controls preload="auto"
  data-setup='{}'>
            <source src="http://localhost:5000/CameraSystem/Example" type="application/x-mpegURL">
  </video>
<script>
var player = videojs('my_video_1');
player.play();
</script>
</body>
</html>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 ASP.NET Core 中将 RTSP 流从 IP 摄像机转发到浏览器 的相关文章

随机推荐

  • 如何找出哪个变量具有最大值

    if A gt B and C and D print A wins if B gt A and C and D print B wins 如何检查并查看哪个变量包含组中最大的整数 决定谁胜谁负 您可以测试每一项 if A gt B and
  • 如何离线安装 DoctrineFixturesBundle

    首先 由于我的 ISP 我无法使用 Composer 所以 我需要一种方法来安装DoctrineFixturesBundle手动 所以我开始从github 还有data fixtures捆 我在我的项目中创建这个文件夹结构 vendor d
  • SSE 的 EventSource 和 XMLHttpRequest 之间的区别

    我正在实现服务器发送事件应用程序逻辑 服务器端已经完成 现在正在处理客户端部分 在我看来 JS 正在使用 EventSource 对象 这看起来非常合乎逻辑 因为它就是为此而设计的 但它也有很多限制 只有 GET 请求 没有标头 没有数据
  • Datagramsocket:receive(...) 如何处理数据包的碎片

    我从我的教授那里了解到 使用 UDP 套接字发送的数据报包在较低层中会被分段 并且may作为多个数据包到达接收端 例如 如果我在数据报包中发送 1000 字节的数据 则在接收端might到达时 例如 2 字节 500 字节 12 字节等 因
  • 如何将图像从 Java Applet 发送到 JavaScript?

    我有一个正在生成图像的 Java Applet 最终 我想将图像数据插入数据库 因此我想将图像数据临时存储在包含小程序的页面上的表单字段中 我希望做到这一点而不在客户端计算机上存储图像文件 这一切都来自签名板 以下是一些应该从 sigObj
  • 如何使用 toJSON 更改序列化实体的属性名称?

    我想序列化一个属性 其名称与实体中的名称不同 Entity export class MyEntity This should be serialized with name column in JSON Column name strin
  • 如何更改列的堆叠顺序?

    使用 Bootstrap 3 我有一个非常简单的布局 例如 div class container div class row div class col sm 4 Header Content Left div div class col
  • 如何在CSS中使用边框的clip-path属性

    I have clip part达到 偷工减料 的效果 我想将背景更改为白色并使用绿色边框 问题是 当我将背景更改为白色时 角落是空的 如何在悬停时制作绿色边框角 test background red width 100px height
  • PHP 上次登录脚本[关闭]

    很难说出这里问的是什么 这个问题模棱两可 含糊不清 不完整 过于宽泛或言辞激烈 无法以目前的形式合理回答 如需帮助澄清此问题以便重新打开 访问帮助中心 我想知道一种好的 有效的方法来判断我的用户上次登录是多久前的事 在用户个人资料中 我希望
  • php通过socket编程传递c结构体数据

    如何制作一个需要对具有以下结构的linux C套接字服务器执行socket sendto的php套接字客户端 typedef struct UI2 todo char rz LNG RZ 1 char saId LNG SAT ID 1 c
  • 需要复杂数据排序 SQL Server 的帮助

    我需要对数据库中的复杂数据进行排序的帮助 假设我的数据存储在这样的表中 Description JCB Excavator ECU P N 728 35700 Geo Prism 1995 GEO ABS 16213899 GEO pump
  • toLocaleDateString 的相反方法

    为了创建一个尊重浏览器文化的字符串 我们可以这样做 var myDate new Date var myDateString myDate toLocaleDateString myDate returns a string 这很好 因为如
  • JQuery AJAX 从表单发送文件数组

    我在某个地方找到了这段代码 可以在不刷新浏览器的情况下上传图片 然而 我不喜欢上传器文件 所以我决定使用我以前使用过的旧文件 问题在于它正在发送一个数组而不是来自 javascript 文件的字符串 它的目的是发送多个文件而不是仅发送一个文
  • Firefox、SVG、文本装饰

    考虑以下代码片段
  • java中变音符号的问题[重复]

    这个问题在这里已经有答案了 我正在尝试通过控制台读取一些数据并将其写入文件 当控制台中的数据包含元音变音字符时 我遇到问题 它打印出 而不是元音变音字符 请在下面找到我的代码 有人可以帮帮我吗 String cmd cmd C si vie
  • namshi/jose 5.0.2 在 macosx 上需要 lib-openssl

    我查看了其他问题 但找不到解决方案 尝试安装时JWT包我收到标题中提到的错误 我在 mac os x Sierra 上使用 xampp 版本 7 0 8 和 PHP 5 6 我得到的错误如下 Problem 1 namshi jose 5
  • 声明方法时,各种参数前缀的含义是什么?

    声明方法时 参数的各种前缀是什么意思 sh cmd block 什么是 before cmd mean 什么是 before block mean 星号 意味着将所有剩余的参数组合到一个由该参数命名的列表中 与号 意味着如果给方法调用一个块
  • 从 NT 服务向远程桌面会话发送消息

    如何从 nt 服务向在远程桌面会话中启动的应用程序发送 sendmessage 和 postmessage 目前我通过应用程序之间的udp通信暂时解决了这个问题 您不能跨会话边界发送消息 因此 您需要一个 IPC 解决方案 例如 TCP I
  • 从另一个数组中过滤对象数组

    我有两个数组 我想过滤一个包含另一个数组中的对象的数组 let array1 date 1 count 4 date 3 count 6 let array2 1 2 3 4 过滤这两个数组后 我需要过滤后的数组 如下所示 let arra
  • 在 ASP.NET Core 中将 RTSP 流从 IP 摄像机转发到浏览器

    我有一个 Blazor 托管应用程序 我需要根据客户请求从 AXIS 摄像机获取 h264 录制内容 通过 RTSP PLAY 命令 并以浏览器可以重现视频的方式返回它 如果向 AXIS 摄像机查询录音列表 答案包括这个 即我尝试在浏览器上