首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >捕获RTP时间戳

捕获RTP时间戳
EN

Stack Overflow用户
提问于 2019-08-21 11:08:01
回答 1查看 4.2K关注 0票数 5

为了从python中Opencv的源代码中获得使用VideoCapture类的RTP数据包的时间戳,我尝试了一个小实验,还必须修改FFmpeg以适应Opencv中的变化。

既然我读到了关于RTP包格式.Wanted的文章,我就可以到处摆弄,看看能不能找到一种方法来获得NTP时间戳。无法在试图获得RTP时间戳方面找到任何可靠的帮助。所以试了一下这个小黑客。

修改后的代码在github上给ryantheseer的学分。

FFmpeg版本: 3.2.3版Opencv: 3.2.0

Opencv中的源代码:

模块/视频include/include/opencv2 2/videoio io.hpp:

为RTP时间戳添加了两个getter:

代码语言:javascript
复制
.....   
    /** @brief Gets the upper bytes of the RTP time stamp in NTP format (seconds).
    */
    CV_WRAP virtual int64 getRTPTimeStampSeconds() const;
    
    /** @brief Gets the lower bytes of the RTP time stamp in NTP format (fraction of seconds).
    */
    CV_WRAP virtual int64 getRTPTimeStampFraction() const;
.....

模块/视频src/src/cap.cpp:

添加了一个导入并添加了时间戳getter的实现:

代码语言:javascript
复制
....
#include <cstdint>
....
....
static inline uint64_t icvGetRTPTimeStamp(const CvCapture* capture)
{
  return capture ? capture->getRTPTimeStamp() : 0;
}
...

在C++类中添加了VideoCapture时间戳getter:

代码语言:javascript
复制
 ....
/**@brief Gets the upper bytes of the RTP time stamp in NTP format (seconds).
*/
int64 VideoCapture::getRTPTimeStampSeconds() const
{
    int64 seconds = 0;
    uint64_t timestamp = 0;
    //Get the time stamp from the capture object
    if (!icap.empty())
        timestamp = icap->getRTPTimeStamp();
    else
        timestamp = icvGetRTPTimeStamp(cap);
    //Take the top 32 bytes of the time stamp
    seconds = (int64)((timestamp & 0xFFFFFFFF00000000) / 0x100000000);
    return seconds;
}

/**@brief Gets the lower bytes of the RTP time stamp in NTP format (seconds).
*/
int64 VideoCapture::getRTPTimeStampFraction() const
{
    int64 fraction = 0;
    uint64_t timestamp = 0;
    //Get the time stamp from the capture object
    if (!icap.empty())
        timestamp = icap->getRTPTimeStamp();
    else
        timestamp = icvGetRTPTimeStamp(cap);
    //Take the bottom 32 bytes of the time stamp
    fraction = (int64)((timestamp & 0xFFFFFFFF));
    return fraction;
}
...

模块/视频src/src/capffmpeg.cpp:

增加了一项进口:

代码语言:javascript
复制
...
#include <cstdint>
...

增加了一个方法参考定义:

代码语言:javascript
复制
...
static CvGetRTPTimeStamp_Plugin icvGetRTPTimeStamp_FFMPEG_p = 0;
...

将该方法添加到模块初始化器方法中:

代码语言:javascript
复制
...
if( icvFFOpenCV )
...
...
  icvGetRTPTimeStamp_FFMPEG_p =
                (CvGetRTPTimeStamp_Plugin)GetProcAddress(icvFFOpenCV, "cvGetRTPTimeStamp_FFMPEG");
...
...
icvWriteFrame_FFMPEG_p != 0 &&
icvGetRTPTimeStamp_FFMPEG_p !=0)
...

icvGetRTPTimeStamp_FFMPEG_p = (CvGetRTPTimeStamp_Plugin)cvGetRTPTimeStamp_FFMPEG;

实现了getter接口:

代码语言:javascript
复制
...
virtual uint64_t getRTPTimeStamp() const
    {
        return ffmpegCapture ? icvGetRTPTimeStamp_FFMPEG_p(ffmpegCapture) : 0;
    } 
...

在FFmpeg的源代码:

libavcodec/avcodec.h:

将NTP时间戳定义添加到AVPacket结构中:

代码语言:javascript
复制
typedef struct AVPacket {
...
...
uint64_t rtp_ntp_time_stamp;
}

libavformat/rtpdec.c:

将ntp时间戳存储在finalize_packet方法中的结构中:

代码语言:javascript
复制
static void finalize_packet(RTPDemuxContext *s, AVPacket *pkt, uint32_t timestamp)
{
    uint64_t offsetTime = 0;
    uint64_t rtp_ntp_time_stamp = timestamp;
...
...
/*RM: Sets the RTP time stamp in the AVPacket */
    if (!s->last_rtcp_ntp_time || !s->last_rtcp_timestamp)
        offsetTime = 0;
    else
        offsetTime = s->last_rtcp_ntp_time - ((uint64_t)(s->last_rtcp_timestamp) * 65536);
    rtp_ntp_time_stamp = ((uint64_t)(timestamp) * 65536) + offsetTime;
    pkt->rtp_ntp_time_stamp = rtp_ntp_time_stamp;

libavformat/utils.c:

将ntp时间戳从数据包复制到read_frame_internal方法中的帧:

代码语言:javascript
复制
static int read_frame_internal(AVFormatContext *s, AVPacket *pkt)
{
    ...
    uint64_t rtp_ntp_time_stamp = 0;
...
    while (!got_packet && !s->internal->parse_queue) {
          ...
          //COPY OVER the RTP time stamp TODO: just create a local copy
          rtp_ntp_time_stamp = cur_pkt.rtp_ntp_time_stamp;


          ...


  #if FF_API_LAVF_AVCTX
    update_stream_avctx(s);
  #endif

  if (s->debug & FF_FDEBUG_TS)
      av_log(s, AV_LOG_DEBUG,
           "read_frame_internal stream=%d, pts=%s, dts=%s, "
           "size=%d, duration=%"PRId64", flags=%d\n",
           pkt->stream_index,
           av_ts2str(pkt->pts),
           av_ts2str(pkt->dts),
           pkt->size, pkt->duration, pkt->flags);
pkt->rtp_ntp_time_stamp = rtp_ntp_time_stamp; #Just added this line in the if statement.
return ret;

我的python代码使用这些更改:

代码语言:javascript
复制
import cv2

uri = 'rtsp://admin:password@192.168.1.67:554'
cap = cv2.VideoCapture(uri)

while True:
    frame_exists, curr_frame = cap.read()
    # if frame_exists:
    k = cap.getRTPTimeStampSeconds()
    l = cap.getRTPTimeStampFraction()
    time_shift = 0x100000000
    #because in the getRTPTimeStampSeconds() 
    #function, seconds was multiplied by 0x10000000 
    seconds = time_shift * k
    m = (time_shift * k) + l
    print("Imagetimestamp: %i" % m)
cap.release()

作为我的输出,我得到了什么:

代码语言:javascript
复制
    Imagetimestamp: 0
    Imagetimestamp: 212041451700224
    Imagetimestamp: 212041687629824
    Imagetimestamp: 212041923559424
    Imagetimestamp: 212042159489024
    Imagetimestamp: 212042395418624
    Imagetimestamp: 212042631348224
    ...

最让我吃惊的是,当我关掉ip摄像头并重新启动它时,时间戳将从0开始,然后迅速增加。我读的NTP时间格式相对于1900年1月1日00:00。即使当我尝试计算偏移量,以及从现在到01-01-1900年间,我仍然得到了一个疯狂的高数字的日期。

不知道我是不是算错了。我有一种感觉,这是非常遥远的,否则我得到的不是时间戳。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-08-22 08:39:18

在我看来,您将收到一个uint64类型的时间戳,其中包含在高位和低位中值uint32的值。在您使用的代码的一部分中,我看到:

代码语言:javascript
复制
seconds = (int64)((timestamp & 0xFFFFFFFF00000000) / 0x100000000);

基本上移除较低的位,并将高位移到较低的位上。然后你把它投给int64。在这里,我只考虑它应该首先是无符号的,因为它在任何情况下都不应该是负的(秒,因为时代总是正的),而且它应该是uint32,因为它保证它不会更大(您只占用32位)。此外,可以通过这样的位移(可能更快)来实现这一点:

代码语言:javascript
复制
auto seconds = static_cast<uint32>(timestamp >> 32);

我发现的另一个错误是在这部分:

代码语言:javascript
复制
time_shift = 0x100000000
seconds = time_shift * k
m = (time_shift * k) + l

这里基本上是重建64位时间戳,而不是创建在其他上下文中可用的时间戳。这意味着,您要在秒内将较低的位转换为较高的位,并将分数部分作为较低的位添加.这将是一个非常大的数字,可能并不总是有用的。您仍然可以使用它进行比较,但是在C++部件中完成的所有转换都是不必要的。我认为一个更正常的时间戳(您可以在python中使用)是这样的:

代码语言:javascript
复制
timestamp = float(str(k) + "." + str(l)) # don't know if there is a better way
date = datetime.fromtimestamp(timestamp)

如果你不关心小数部分,你可以直接使用秒。

另一件需要考虑的是,RTP协议的时间戳取决于摄像机/服务器.他们可以使用时钟时间戳,或者仅仅使用其他一些时钟,比如系统启动流的启动。所以它可能来自于时代,也可能不是时代。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57590264

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档