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

基于ASP.NET Core的视频接收、预处理与客户端推流方案咨询

视频监控服务器实现方案

已实现进展

  • 使用RtspClientSharp获取RTSP视频流,通过基于FFMpeg的FrameDecoderCore将H.264原始帧解码为Bitmap;
  • 基于ML.Net搭建了YoloV4 ONNX目标检测模型。

核心问题与解决方案

问题1:无延迟视频接收下的目标检测及ONNX预测模型构建

无延迟处理方案

当前FrameReceived事件同步处理解码逻辑,会阻塞RTSP流接收。必须解耦流接收与帧处理:

  • 用Channel<T>创建异步有界队列,将解码后的DecodedFrame存入队列,后台独立任务消费队列执行目标检测;
  • 禁止在FrameReceived回调中做任何耗时操作(包括检测、Bitmap转换后的复杂处理),确保RTSP客户端持续接收流,避免丢帧。

ONNX预测模型优化

  • 简化模型结构:用Netron工具打开原始ONNX模型,删除训练相关节点(损失计算、优化器等),导出仅保留推理链路的轻量模型;
  • ML.Net加载时明确输入输出约束:
var mlContext = new MLContext();
var model = mlContext.Model.Load("yolov4-infer-only.onnx", out _);
// 定义与模型匹配的输入输出类型,确保Bitmap预处理(缩放、归一化)符合模型要求
var predictionEngine = mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(model);
  • 将Bitmap预处理(如缩放至416x416、像素归一化)与模型推理放在同一个异步任务中,减少上下文切换开销。

问题2:处理后Bitmap转回视频流及目标触发录像

Bitmap转视频流

使用FFMpeg封装库(如Xabe.FFmpeg或FFmpeg.AutoGen)实现编码:

  • 初始化编码器,配置分辨率、帧率、码率等参数(需与原始流匹配或按需调整);
  • 将Bitmap转换为FFmpeg支持的YUV420P格式,逐帧送入编码器生成H.264原始帧;
  • 编码后的帧可存入缓冲区,用于推流或本地文件写入。

目标触发录像

  • 维护一个固定长度的帧缓存队列(比如缓存最近5秒的帧);
  • 检测到目标时,从缓存中取出触发前的历史帧,拼接后续检测到目标的帧,用FFmpeg编码为MP4文件保存;
  • 目标消失后立即停止录像,避免无效存储占用磁盘空间。

问题3:按需向Angular/React客户端推流

gRPC推流优化(替代Bitmap直推)

直接推送Bitmap效率极低,建议推送压缩后的帧数据:

  • 定义gRPC服务协议:
service VideoStream {
  rpc GetStream (StreamRequest) returns (stream FramePacket);
}

message StreamRequest {
  bool requireProcessed = 1; // 标记是否需要经过检测处理的流
}

message FramePacket {
  bytes data = 1; // H.264原始帧或JPEG压缩后的Bitmap数据
  int64 timestamp = 2;
}
  • 后端根据客户端请求,选择推送原始编码帧或处理后编码帧;
  • 前端接收后:
    • 若为H.264流,用hls.js或mpegts.js解析,直接在<video>标签播放;
    • 若为JPEG序列,用requestAnimationFrame循环更新<canvas>,模拟视频播放。

低延迟替代方案:WebRTC

如果追求极致低延迟,WebRTC比gRPC更适合:

  • 后端用Microsoft.MixedReality.WebRTC搭建WebRTC服务,将处理后的视频流通过WebRTC推送到前端;
  • 前端直接通过WebRTC API接收流,在<video>标签播放,延迟可控制在100ms以内。

优化后的服务代码示例

using System.Threading.Channels;

public class CCTVService : BackgroundService
{
    private readonly RtspClient _rtspClient;
    private readonly ILogger<CCTVService> _logger;
    private readonly Channel<DecodedFrame> _frameQueue;
    private readonly PredictionEngine<ModelInput, ModelOutput> _predictionEngine;

    private const int streamWidth = 480;
    private const int streamHeight = 640;

    private static readonly FrameDecoder FrameDecoder = new FrameDecoder();
    private static readonly FrameTransformer FrameTransformer = new FrameTransformer(streamWidth, streamHeight);

    public CCTVService(ILogger<CCTVService> logger)
    {
        _logger = logger;
        _rtspClient = new RtspClient(new ConnectionParameters(new Uri("rtsp://192.168.0.99:5540/ch0")));
        // 创建有界队列,避免内存溢出,满时丢弃最旧帧
        _frameQueue = Channel.CreateBounded<DecodedFrame>(new BoundedChannelOptions(100)
        {
            FullMode = BoundedChannelFullMode.DropOldest
        });
        // 初始化预测引擎
        var mlContext = new MLContext();
        var model = mlContext.Model.Load("yolov4-infer-only.onnx", out _);
        _predictionEngine = mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(model);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 启动后台帧处理任务
        _ = ProcessFramesAsync(stoppingToken);

        using (_rtspClient)
        {
            try
            {
                await _rtspClient.ConnectAsync(stoppingToken);
                _logger.LogInformation("已连接到RTSP流");
            }
            catch (RtspClientException ex)
            {
                _logger.LogError(ex, "RTSP连接失败");
                return;
            }

            _rtspClient.FrameReceived += async (_, rawFrame) =>
            {
                if (rawFrame is not RawVideoFrame rawVideoFrame) return;
                var decodedFrame = FrameDecoder.TryDecode(rawVideoFrame);
                if (decodedFrame == null) return;

                // 异步写入队列,不阻塞RTSP接收线程
                await _frameQueue.Writer.WriteAsync(decodedFrame, stoppingToken);
            };

            try
            {
                await _rtspClient.ReceiveAsync(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "RTSP流接收异常");
            }
        }
    }

    private async Task ProcessFramesAsync(CancellationToken stoppingToken)
    {
        await foreach (var frame in _frameQueue.Reader.ReadAllAsync(stoppingToken))
        {
            try
            {
                using var bitmap = FrameTransformer.TransformToBitmap(frame);
                // 执行目标检测
                var input = new ModelInput { Image = bitmap };
                var detectionResult = _predictionEngine.Predict(input);

                // 后续处理:编码推流、触发录像等
                await HandleProcessedFrameAsync(bitmap, detectionResult, stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "帧处理失败");
            }
        }
    }

    private async Task HandleProcessedFrameAsync(Bitmap bitmap, ModelOutput result, CancellationToken ct)
    {
        // 这里实现编码、推流、录像逻辑
    }
}

// 模型输入输出定义
public class ModelInput
{
    [ImageType(416, 416)]
    public Bitmap Image { get; set; }
}

public class ModelOutput
{
    [ColumnName("output")]
    public float[] Predictions { get; set; }
}

内容的提问来源于stack exchange,提问作者Ruh Roh Raggy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 18:16:38