从 RTP 数据包重建图像
Posted
技术标签:
【中文标题】从 RTP 数据包重建图像【英文标题】:Reconstruct image from RTP packets 【发布时间】:2014-05-26 06:38:10 【问题描述】:我正在尝试通过网络将用户的网络摄像头流式传输到基于 C 的服务器。我用过Janus gateway。
我创建了一个很大程度上基于 echotest 演示示例的小插件:我的浏览器通过 WebRTC 技术连接到我的 janus 服务器,并让它流式传输用户的网络摄像头。
在服务器端,我有 janus_incomming_rtp 函数,它给了我一个 char * buffer 和 int length。经过检查,传入的数据缓冲区大约是 MTU 的长度:我的视频的每一帧都是通过几个 RTP 数据包发送的。
我已经按照this wikipedia page 对标头进行了一些检查,但我不知道如何从该 UDP RTP 数据包流中重建图像。理想情况下,我想将流传递给 openCV 以进行实时图像处理。
我听说过 gstreamer,但我不明白它是什么,也不知道它对我有什么帮助;除了我不知道openCV是否有任何内置函数来“重建”图像?而且我不知道视频帧是以哪种格式编码的:PT(有效负载类型)似乎是 116,它被定义为“动态”,但我不知道它是什么意思。
有什么帮助吗?
【问题讨论】:
从我目前阅读的内容来看,gstreamer 似乎是关键:任何人都可以向我指出如何将 gstreamer 配置为 WebRTC 客户端的文档/示例? 您应该可以通过 SDP 交换中提到的内容来获取媒体格式类型。 SDP 交换将有效负载与格式类型匹配。唯一的问题是,您必须确保您的 RTP 流没有被多路复用(音频和视频一起),chrome 有这样做的习惯,它可能会导致其他技术出现问题。 (感谢您的回答!)。好吧,据我了解,网络摄像头流是用 VP8 插件编码的。我尝试将传入的 RTP 数据包转发到我让 openCV 打开捕获流的 udp 套接字。但显然,openCV 无法解码 VP8,所以知道如何实时转换流并将其传递给 openCV 吗? 【参考方案1】:以下是处理 SRTP 数据包以对其进行解码的一些指导步骤。
-
确保rtp和RTCP没有复用,可以remove that option from the SDP
将 SRTP 数据包解密为原始 RTP,您将需要访问密钥交换(不确定您是否已经这样做,但所有媒体均已加密,并且使用 DTLS 交换密钥,并且必须在处理前解密)
获取您的媒体负载类型并将其与来自 SDP 的媒体进行匹配(您可以从 SDP 中的 RTPMAP 看到什么媒体是什么负载)
从数据包中删除 RTP 有效负载(Gstreamer 为大多数常见有效负载提供 RtpDepay 插件,包括 VP8)并解码流。 Quick example 使用 vp8 的命令行管道
现在您有了一个可以显示的原始视频/音频数据包。
SDP:
如果 RTCP 和 RTP 被多路复用,您将看到该行a=rtcp-mux
,您会看到a=rtcp:50111 IN IP4
<address>
中的端口与候选媒体端口相同。
如果媒体本身正在被多路复用,您将看到a=group:BUNDLE
audio video
SRTP:
Janus 已经处理了 DTLS 交换,并且它似乎在发送 rtp 之前已经对其进行了解密,但它看起来不像是多路复用的 rtp/rtcp 和媒体。 Here 是一个快速而肮脏的 SRTP 解密器,当您将在 DTLS 中交换的 MasterKey 传递给它时,它就可以工作。GStreamer:
您可能需要查看 GstAppSrc 它允许您将数组字符化到 gstreamer 管道中 解码,您可以将其推送到另一个 udp 端口以获取它 OpenCV。 这里有一些example code 从我写的一个 websocket 服务器获取原始媒体并推送它 到管道。这个例子不是你想要做的(它 不抓取 RTP,而是从网页中抓取原始媒体帧) 但它会告诉你如何使用 AppSrc。【讨论】:
(感谢您的解释)。我一直在 janus 的代码附近潜伏以尝试按照您的建议进行操作,但找不到太多。 1.如何知道RTP和RTCP是否复用?我尝试分析 SDP 消息,但它们并不容易理解。 2. 我如何将 SRTP 数据包解密为原始 RTP 以及如何访问密钥交换?从 janus 的代码中,我可以找到 .key 和 .pem 证书文件->您在说什么是私钥? 3. 从 SDP 消息中我得到 a=rtpmap:120 VP8/90000 怎么办?谢谢! 我一直在阅读您在 github 上提供的代码示例,我看到它可以与 WebSockets(即 TCP)一起使用,在延迟/实时方面我可以期待什么样的性能?跨度> 感谢您的编辑。我花了一些时间阅读文档(并尝试替代解决方案)。有些事情我仍然无法弄清楚:我是否需要 SRTP 解密器(因为您显然是 Janus 处理 DTLS 解密)?我找不到任何适用于 vp8 的 gstreamer rtp depayloader,你能指点我吗?我将继续阅读您的源代码以获得一些见解,但我不知道(尚)我如何使用这个概念。最后一个问题: GstAppSrc 和 gstreamer 之间有什么关系?再次,非常感谢您的帮助! 经过调查,我看到了 a=rtcp-mux 行(但不是 a=rtcp:PORT In ....)所以我想我是复用 rtp 和 rtcp。我关注了您的链接,但找不到任何解释来删除它,只有一句:“如果您的系统不支持 rtcp-mux,请不要将其包含在 INVITE 或 200 OK 中。”。但我们这样做吗?提前致谢! 感谢您的所有建议!我得到了一些工作。作为未来用户的信息:Janus 处理 RTP / RTCP 解复用和 DTLS 握手。我们在“incoming_rtp_packet”中获得的数据包包含 RTP 标头和 vp8 有效负载(其中包含一个 vp8 标头)。我使用 gstreamer 的 rtp 解包和 vp8 解码器来获取数据包,然后 autovideosink 显示流。伟大的 ! (感谢 Janus 的创造者 Lorenzo 的宝贵帮助)。现在唯一剩下的就是从该流中获取一个 cv::Mat (也许是 appsink ?)。无论如何,谢谢!【参考方案2】:我遵循了包括@nschoe(OP)和@Benjamin Trent 在内的其他人的建议,最终使用Janus 和GStreamer(1.9)完成了这项工作。我想我会包含我的代码,以便让下一个出现的人生活更轻松,因为我涉及到如此多的反复试验:
首先构建/安装 GStreamer 及其所需的所有插件(对于我的设置,我需要确保两个插件目录位于 GST_PLUGIN_SYSTEM_PATH 环境变量中)。现在在 Janus 插件初始化时初始化 GStreamer(init()
回调):
gst_init(NULL, NULL);
对于每个 WebRTC 会话,您需要保留一些 GStreamer 句柄,因此将以下内容添加到您的 Janus 插件会话结构中:
GstElement *pipeline, *appsrc, *multifilesink;
创建 Janus 插件会话(create_session()
回调)时,为该会话设置 GStreamer 管道(在我的情况下,我需要降低帧速率,因此需要降低视频速率/上限;您可能不需要这些):
GstElement *conv, *vp8depay, *vp8dec, *videorate, *capsrate, *pngenc;
session->pipeline = gst_pipeline_new("pipeline");
session->appsrc = gst_element_factory_make("appsrc", "source");
vp8depay = gst_element_factory_make("rtpvp8depay", NULL);
vp8dec = gst_element_factory_make("vp8dec", NULL);
videorate = gst_element_factory_make("videorate", NULL);
capsrate = gst_element_factory_make("capsfilter", NULL);
conv = gst_element_factory_make("videoconvert", "conv");
pngenc = gst_element_factory_make("pngenc", NULL);
session->multifilesink = gst_element_factory_make("multifilesink", NULL);
GstCaps* capsRate = gst_caps_new_simple("video/x-raw", "framerate", GST_TYPE_FRACTION, 15, 1, NULL);
g_object_set(capsrate, "caps", capsRate, NULL);
gst_caps_unref(capsRate);
GstCaps* caps = gst_caps_new_simple ("application/x-rtp",
"media", G_TYPE_STRING, "video",
"encoding-name", G_TYPE_STRING, "VP8-DRAFT-IETF-01",
"payload", G_TYPE_INT, 96,
"clock-rate", G_TYPE_INT, 90000,
NULL);
g_object_set(G_OBJECT (session->appsrc), "caps", caps, NULL);
gst_caps_unref(caps);
gst_bin_add_many(GST_BIN(session->pipeline), session->appsrc, vp8depay, vp8dec, conv, videorate, capsrate, pngenc, session->multifilesink, NULL);
gst_element_link_many(session->appsrc, vp8depay, vp8dec, conv, videorate, capsrate, pngenc, session->multifilesink, NULL);
// Setup appsrc
g_object_set(G_OBJECT (session->appsrc), "stream-type", 0, NULL);
g_object_set(G_OBJECT (session->appsrc), "format", GST_FORMAT_TIME, NULL);
g_object_set(G_OBJECT (session->appsrc), "is-live", TRUE, NULL);
g_object_set(G_OBJECT (session->appsrc), "do-timestamp", TRUE, NULL);
g_object_set(session->multifilesink, "location", "/blah/some/dir/output-%d.png", NULL);
gst_element_set_state(session->pipeline, GST_STATE_PLAYING);
当传入的 RTP 数据包被 Janus 解复用并准备好读取时,(incoming_rtp()
回调),将其输入 GStreamer 管道:
if(video && session->video_active)
// Send to GStreamer
guchar* temp = NULL;
temp = (guchar*)malloc(len);
memcpy(temp, buf, len);
GstBuffer* buffer = gst_buffer_new_wrapped_full(0, temp, len, 0, len, temp, g_free);
gst_app_src_push_buffer(GST_APP_SRC(session->appsrc), buffer);
最后,当 Janus 插件会话结束时(destroy_session()
回调),一定要释放 GStreamer 资源:
if(session->pipeline)
gst_element_set_state(session->pipeline, GST_STATE_NULL);
gst_object_unref(session->pipeline);
session->pipeline = NULL;
【讨论】:
我们可以在某处看到您完成的项目吗?我有完全相同的用例,想看看你的代码【参考方案3】:我们对 WebRTC 的流式传输也有同样的担忧。我所做的是将视频帧发送到 WebSocket 服务器,从那里我使用 imdecode() 解码图像缓冲区。
我在这里有一个现场演示 twistedcv 并在 github 上托管源代码 twistedcv。 但流媒体不是实时的。
【讨论】:
链接似乎失效了。顺便说一句,依靠WebSocket发送帧意味着TCP连接,对吗?使用 TCP 可以做到 30fps 吗? 我检查了链接,似乎 websocket 服务器已关闭。我重新启动服务器,它现在应该可以工作了。是的,它是一个 TCP。这不是它不实时流式传输的原因。 嗯,我绝对需要实时,所以我不能使用该解决方案。无论如何谢谢!以上是关于从 RTP 数据包重建图像的主要内容,如果未能解决你的问题,请参考以下文章
Linux 上的 C++:设置套接字和数据包以最小化 RTP 流延迟