You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core中实现AXIS摄像机RTSP录制流转发至浏览器播放的技术问题

解决Blazor客户端播放AXIS摄像机RTSP录制流的无延迟方案

首先得明确核心问题:你现在直接返回的是RTP封装的H264原始流,而HTML5的<video>元素根本不认识这种格式——浏览器需要的是带容器的视频流(比如MP4、WebM),而且容器结构必须支持流式播放(允许边下载边解码)。

为什么当前代码无法播放?

  1. 流格式不对:RtspTcpClient.GetStream()返回的是包含RTP协议头的字节流,不是浏览器能解码的H264裸流,更没有容器结构。
  2. 缺少必要的解码信息:H264解码需要SPS(序列参数集)和PPS(图像参数集),这些必须在视频数据前传递给浏览器,否则解码器无法初始化。

无磁盘、低延迟的技术方向

下面是具体的实现思路,分为三个核心步骤:


1. 解析RTSP流,提取纯净的H264 NAL单元

不要直接操作RtspTcpClient的原始流,而是用RtspClientSharp的媒体轨道API来自动处理RTP分片和NALU拼接:

// 替换原有的流获取逻辑,改用MediaStreamTrack
var client = new RtspClient(connectionParameters);
await client.ConnectAsync(cts.Token);
await client.SetupTracksAsync(cts.Token);

// 获取H264轨道
var h264Track = client.Tracks.OfType<H264StreamTrack>().FirstOrDefault();
if (h264Track == null)
{
    throw new InvalidOperationException("No H264 track found in the RTSP stream");
}

// 订阅帧接收事件,获取完整的H264 NALU
h264Track.FrameReceived += (sender, args) =>
{
    // args.Frame.Buffer 就是完整的H264 NALU字节数组
    // 这里需要把这些NALU传递给封装逻辑
};

// 开始播放流
await client.PlayAsync(cts.Token);

2. 将H264 NALU封装成支持流式播放的Fragmented MP4

Fragmented MP4(分片MP4)是浏览器原生支持的流式格式,它把视频分成多个小片段(moof+mdat),并且把关键的moov元数据放在流的最开头,这样浏览器可以边接收边解码,完全不需要写入磁盘。

你可以用第三方库简化封装工作,比如MP4Box.NET或者SharpMP4,也可以手动实现基础的封装逻辑(如果想控制细节)。核心要点:

  • 首先提取H264的SPS和PPS(从第一个关键帧的NALU中获取),用它们构建MP4的moov原子。
  • 为每个H264帧生成对应的moof(片段元数据)和mdat(帧数据)块,持续写入输出流。

3. 在ASP.NET Core中返回流式响应

创建一个双向管道(比如PipeStream),在后台线程中处理RTSP流的解析和MP4封装,然后把管道的读取端返回给客户端:

[HttpGet("RecordingsDemo")]
public async Task<IActionResult> RecordingsDemo(CancellationToken ct)
{
    var deviceIp = "rtsp://192.168.0.125";
    var recordingUri = "rtsp://192.168.0.125/axis-media/media.amp?recordingid=20211109_140710_E1A3_B8A44F2D0300";
    var credentials = new NetworkCredential("user", "password");

    // 创建管道,用于后台封装和前端响应的桥接
    var pipe = new Pipe();
    var writer = pipe.Writer;
    var reader = pipe.Reader;

    // 后台线程处理RTSP流和MP4封装
    _ = Task.Run(async () =>
    {
        try
        {
            var connectionParams = new ConnectionParameters(new Uri(deviceIp), credentials);
            using var client = new RtspClient(connectionParams);
            await client.ConnectAsync(ct);
            await client.SetupTracksAsync(ct);

            var h264Track = client.Tracks.OfType<H264StreamTrack>().First();
            var mp4Writer = new FragmentedMp4Writer(writer, h264Track.Sps, h264Track.Pps);

            // 初始化MP4头部(moov原子)
            await mp4Writer.WriteInitializationAsync(ct);

            // 处理每一个H264帧
            var frameReceivedTask = new TaskCompletionSource<bool>();
            void FrameHandler(object sender, FrameReceivedEventArgs args)
            {
                try
                {
                    mp4Writer.WriteFrameAsync(args.Frame, ct).Wait(ct);
                }
                catch (OperationCanceledException)
                {
                    frameReceivedTask.TrySetResult(true);
                }
                catch (Exception ex)
                {
                    frameReceivedTask.TrySetException(ex);
                }
            }

            h264Track.FrameReceived += FrameHandler;
            await client.PlayAsync(ct);
            await frameReceivedTask.Task;
        }
        catch (Exception ex)
        {
            // 处理异常,比如写入错误信息到管道(可选)
            Console.WriteLine($"Stream processing failed: {ex.Message}");
        }
        finally
        {
            await writer.CompleteAsync();
        }
    }, ct);

    // 设置响应头
    Response.Headers.Add("Cache-Control", "no-cache");
    Response.Headers.Add("Accept-Ranges", "bytes");

    // 返回管道的读取端作为流式响应
    return new FileStreamResult(reader.AsStream(), "video/mp4")
    {
        EnableRangeProcessing = true
    };
}

简化替代方案:用FFmpeg转码(无需手动封装)

如果不想自己处理RTP解析和MP4封装,可以直接调用FFmpeg进程,把RTSP流实时转成浏览器支持的流式MP4,然后把FFmpeg的标准输出返回给客户端。这种方式代码更简洁,延迟也很低:

[HttpGet("RecordingsDemo")]
public async Task<IActionResult> RecordingsDemo(CancellationToken ct)
{
    var rtspUrl = "rtsp://user:password@192.168.0.125/axis-media/media.amp?recordingid=20211109_140710_E1A3_B8A44F2D0300";
    
    // FFmpeg命令:复制H264编码,输出分片MP4,禁用缓存,直接输出到标准输出
    var ffmpegArgs = $"-i \"{rtspUrl}\" -c:v copy -f mp4 -movflags frag_keyframe+empty_moov+default_base_moof -";

    var process = new ProcessStartInfo
    {
        FileName = "ffmpeg",
        Arguments = ffmpegArgs,
        RedirectStandardOutput = true,
        UseShellExecute = false,
        CreateNoWindow = true
    };

    var ffmpegProcess = Process.Start(process);
    if (ffmpegProcess == null)
    {
        return StatusCode(StatusCodes.Status500InternalServerError);
    }

    // 确保进程在请求取消时终止
    ct.Register(() => ffmpegProcess.Kill());

    Response.Headers.Add("Cache-Control", "no-cache");
    return new FileStreamResult(ffmpegProcess.StandardOutput.BaseStream, "video/mp4")
    {
        EnableRangeProcessing = true
    };
}

注意:需要在服务器上安装FFmpeg,并且确保ASP.NET进程有执行权限。

关键注意事项

  • 响应头设置:一定要把Content-Type设为video/mp4,而不是video/x-h264。
  • 范围请求:启用EnableRangeProcessing = true,这样浏览器可以跳过部分内容,提升播放体验。
  • 取消令牌:一定要正确处理CancellationToken,在客户端断开连接时终止RTSP客户端或FFmpeg进程,避免资源泄漏。

内容的提问来源于stack exchange,提问作者Alessandro Martinelli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.01 00:27:48