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

如何通过GStreamer-Sharp获取摄像头物理采集时间戳(C#实现)

问题

刚接触GStreamer领域,使用C#的GStreamer-Sharp库搭建了RTSP管道:rtspsrc ! decodebin ! jpegenc ! appsink,希望获取摄像头实际采集图像的物理时间戳(而非PTS或DTS)。曾了解到需要用到RTCPBuffer、RTCPPacket,但不知如何在AppSink的NewSample事件处理器中实现该需求,寻求指导。

对应的管道代码如下:

private static Pipeline BuildRtspPipe()
{
    // Build the pipeline
    var rtspSrc = ElementFactory.Make("rtspsrc", "rtspsrc");
    rtspSrc["location"] = "rtsp://someaddr";


    var dec = ElementFactory.Make("decodebin", "decodebin");

    var jpegenc = ElementFactory.Make("jpegenc", "jpegenc");


    var appSink = new AppSink("");
    appSink.NewSample += AppSink_NewSample;
    appSink.EmitSignals = true;
    
    var pipeline = new Pipeline("test");

    pipeline.Add(rtspSrc, dec, jpegenc, appSink);



    if ( !rtspSrc.Link(dec) )
    {
        Console.WriteLine("Could not link pipeline elements");
    }

    rtspSrc.PadAdded += (o, args) =>
    {
        var decpad = dec.GetStaticPad("sink");
        if (decpad.IsLinked)
        {
            return;
        }

        args.NewPad.Link(decpad);
    };

    dec.PadAdded += (o, args) =>
    {
        var jpegencpad = jpegenc.GetStaticPad("sink");
        if (jpegencpad.IsLinked)
        {
            return;
        }
        
        var caps = args.NewPad.QueryCaps();
        if (caps == null)
            return;



        args.NewPad.Link(jpegencpad);
    };

    if (!Element.Link(jpegenc, appSink))
    {
        Console.WriteLine("Could not link pipeline elements");
        throw new Exception();
    }

    return pipeline;
}
解决方案

要获取摄像头采集的物理时间戳,核心是通过RTSP流中的*RTCP Sender Report(SR)*包提取NTP时间,再关联到对应的RTP数据包的时间戳,最终映射到AppSink中的Sample。具体实现步骤如下:

1. 监听rtspsrc的on-rtcp信号

rtspsrc收到RTCP包时会触发on-rtcp信号,我们需要在这里解析Sender Report,建立RTP时间戳到NTP物理时间的映射关系。

在构建管道时,给rtspsrc添加信号处理:

// 缓存RTP时间戳与NTP物理时间的映射,保留最近几条避免内存占用过大
private static Dictionary<uint, DateTime> _rtpToNtpMap = new Dictionary<uint, DateTime>();

// 给rtspsrc绑定RTCP信号处理方法
rtspSrc.Connect("on-rtcp", new SignalHandler(OnRtcpReceived));

2. 解析RTCP Sender Report提取时间映射

实现OnRtcpReceived方法,解析RTCP包中的Sender Report,将NTP时间和对应RTP时间戳存入缓存:

private static void OnRtcpReceived(object o, SignalArgs args)
{
    var buffer = args[0] as Buffer;
    if (buffer == null) return;

    using var rtcpBuffer = new RTCPBuffer(buffer);
    while (rtcpBuffer.HasPacket())
    {
        using var packet = rtcpBuffer.NextPacket();
        if (packet.Type == RTCPType.SenderReport)
        {
            var sr = packet.SenderReport;
            // 解析NTP时间:高32位是秒数,低32位是小数部分
            var ntpSeconds = sr.NtpTimestamp >> 32;
            var ntpFraction = sr.NtpTimestamp & 0xFFFFFFFF;
            
            // NTP纪元(1900-01-01)转Unix纪元(1970-01-01),差值为2208988800秒
            var epochDiff = 2208988800;
            var secondsSinceUnixEpoch = ntpSeconds - epochDiff;
            var fractionalSeconds = (double)ntpFraction / 0x100000000;
            
            // 转换为UTC时间
            var ntpTime = DateTimeOffset.FromUnixTimeSeconds(secondsSinceUnixEpoch)
                .AddSeconds(fractionalSeconds)
                .UtcDateTime;
            
            // 更新缓存
            _rtpToNtpMap[sr.RtpTimestamp] = ntpTime;
            // 只保留最近10条映射
            if (_rtpToNtpMap.Count > 10)
            {
                var oldestKey = _rtpToNtpMap.Keys.OrderBy(k => k).First();
                _rtpToNtpMap.Remove(oldestKey);
            }
        }
    }
}

3. 在AppSink的NewSample事件中计算物理时间戳

在AppSink_NewSample方法中,从Sample的Buffer中提取RTP时间戳,再通过缓存的映射关系计算出摄像头采集的物理时间:

private static FlowReturn AppSink_NewSample(AppSink sender)
{
    using var sample = sender.PullSample();
    using var buffer = sample.Buffer;

    // 提取Buffer中的RTP元数据
    if (buffer.GetMeta<RTPBufferMeta>(out var rtpMeta))
    {
        var rtpTimestamp = rtpMeta.Timestamp;
        // 查找最接近当前RTP时间戳的映射条目
        var closestEntry = _rtpToNtpMap
            .OrderBy(kv => Math.Abs((long)kv.Key - (long)rtpTimestamp))
            .FirstOrDefault();

        if (closestEntry.Key != 0)
        {
            // 大多数视频流的RTP时钟频率为90kHz,需根据实际流调整
            var rtpClockRate = 90000;
            // 计算当前RTP时间戳与映射点的时间差
            var timeDiff = (rtpTimestamp - closestEntry.Key) / (double)rtpClockRate;
            // 得到摄像头采集的物理时间戳
            var captureTime = closestEntry.Value.AddSeconds(timeDiff);
            
            Console.WriteLine($"采集物理时间戳:{captureTime:yyyy-MM-dd HH:mm:ss.fff}");
        }
    }

    return FlowReturn.Ok;
}

关键注意事项

  • RTP时钟频率确认:不同编码的RTP时钟频率不同,比如H.264、MJPEG为90kHz,音频可能是44.1kHz。可在decodebin的PadAdded事件中解析caps获取准确值。
  • 时区转换:代码返回的是UTC时间,可根据需求转换为本地时区。
  • 缓存维护:定期清理过期的映射条目,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 18:20:16