捕获 RTP 时间戳
Capturing RTP Timestamps
我正在尝试一个小实验,以便使用VideoCapture类从Opencv的python源代码中获取RTP数据包的时间戳,还必须修改FFmpeg以适应Opencv中的更改。
因为我读到了 RTP 数据包格式。想摆弄一下,看看我是否可以设法找到获取 NTP 时间戳的方法。在尝试获取 RTP 时间戳时找不到任何可靠的帮助。所以尝试了这个小技巧。
感谢 github 上的 ryantheer 修改后的代码。
FFmpeg版本:3.2.3 Opencv 版本:3.2.0
在 Opencv 源代码中:
modules/videoio/include/opencv2/videoio.hpp:
为 RTP 时间戳添加了两个获取器:
.....
/** @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;
.....
Modules/videoio/src/cap.cpp:
添加了一个导入并添加了时间戳获取器的实现:
....
#include <cstdint>
....
....
static inline uint64_t icvGetRTPTimeStamp(const CvCapture* capture)
{
return capture ? capture->getRTPTimeStamp() : 0;
}
...
在 VideoCapture 类中添加了C++时间戳获取器:
....
/**@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;
}
...
modules/videoio/src/cap_ffmpeg.cpp:
添加了导入:
...
#include <cstdint>
...
添加了方法引用定义:
...
static CvGetRTPTimeStamp_Plugin icvGetRTPTimeStamp_FFMPEG_p = 0;
...
将该方法添加到模块初始值设定项方法:
...
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 接口:
...
virtual uint64_t getRTPTimeStamp() const
{
return ffmpegCapture ? icvGetRTPTimeStamp_FFMPEG_p(ffmpegCapture) : 0;
}
...
在 FFmpeg 的源代码中:
libavcodec/avcodec.h:
将 NTP 时间戳定义添加到 AVPacket 结构中:
typedef struct AVPacket {
...
...
uint64_t rtp_ntp_time_stamp;
}
libavformat/rtpdec.c:
将 ntp 时间戳存储在 finalize_packet 方法的结构中:
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 方法中的帧:
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=%dn",
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 代码来利用这些更改:
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()
我得到的输出:
Imagetimestamp: 0
Imagetimestamp: 212041451700224
Imagetimestamp: 212041687629824
Imagetimestamp: 212041923559424
Imagetimestamp: 212042159489024
Imagetimestamp: 212042395418624
Imagetimestamp: 212042631348224
...
最让我震惊的是,当我关闭网络摄像机电源并重新打开电源时,时间戳将从 0 开始,然后迅速递增。我读到NTP时间格式是相对于1900年1月1日00:00的。即使我尝试计算偏移量,并在现在和 1900 年 1 月 1 日之间进行会计处理,我最终仍然得到了一个疯狂的高数字。
不知道是不是算错了。我有一种感觉,它非常不对劲,或者我得到的不是时间戳。
在我看来,您会收到一个 uint64 类型的时间戳,其中包含高位和低位的 uint32 值。我在您使用的代码的一部分中看到了这一点:
seconds = (int64)((timestamp & 0xFFFFFFFF00000000) / 0x100000000);
这基本上删除了较低的位并将高位移动到较低的位中。然后你把它转换为 int64。在这里,我只考虑它首先应该是无符号的,因为它在任何情况下都不应该是负数(自 epoch 始终为正数以来的秒数(,它应该是 uint32,因为它可以保证它不会更大(你只需要 32 位(。此外,这可以通过这样的位移来实现(可能更快(:
auto seconds = static_cast<uint32>(timestamp >> 32);
我发现的另一个错误是在这一部分:
time_shift = 0x100000000
seconds = time_shift * k
m = (time_shift * k) + l
在这里,您基本上是在重建 64 位时间戳,而不是创建可用于其他上下文的时间戳。这意味着,您将在几秒钟内将较低的位移动到较高的位,并将分数部分添加为较低的位...这将以一个非常大的数字结束,这可能并不总是有用的。您仍然可以使用它进行比较,但是不需要在C++部分中完成的所有转换。我认为一个更正常的时间戳,你可以与python日期时间一起使用
,如下所示:timestamp = float(str(k) + "." + str(l)) # don't know if there is a better way
date = datetime.fromtimestamp(timestamp)
如果你不关心小数部分,你可以直接使用秒。
另一件需要考虑的事情是,RTP 协议的时间戳取决于相机/服务器......他们可能会使用时钟时间戳或只是其他一些时钟,例如系统启动的流式传输的开始。所以它可能来自也可能不是来自时代。
- C++:floor unix时间戳到UTC月份
- 如何在c++中录制具有精确帧时间戳的视频
- 在两台机器之间进行时间戳的最佳c++chrono函数是什么
- Google protobuf 时间戳未声明标识符,在 Windows 上具有C++
- 在多个时间戳处执行函数
- 以天C++为单位的两个时间戳之间的差异
- 获取 QInputEvent 在 Qt 4.8 中被放入 QEventLoop 队列时的时间戳
- 在 C++ 中为文件名添加时间戳
- 如何从远程 SFTP 服务器获取 HH-MM-SS 时间戳格式的文件列表
- 如何将消息时间戳写入日志文件?
- 将时间戳打印到流的最简单方法
- 读取悖论时间戳字段
- C++程序中多个位置的时间 (0) 时间戳
- 在 c++ 中获取 X 毫秒前的时间戳
- 将 unix 时间戳转换为人类可读的日期
- Microsoft NTLM (v2) 中的时间戳使用
- C++中上周一的时间戳
- 捕获 RTP 时间戳
- 正在RTP/RTP中检索时间戳
- RTP AAC时间戳问题