使用 Live555 从记录的 RTSP 会话 (pcap) 接收组播 RTP 流(包含多个子会话)
Receiving multicast RTP stream (containing multiple subsessions) from a recorded RTSP session (pcap) using Live555
我必须实现一个连接到现有 RTSP 会话的 RTSP 客户端,但不能向 RTSP 服务器发送命令(仅接收)。
为了模拟这样的环境,我使用 Wireshark 在 Live555 的 testH264VideoStreamer 和 testRTSPClient 示例之间录制了一个 RTSP/RTP 流,并在尝试使用修改版本的 testRTSPClient 接收流数据时使用 tcpreplay 播放它。
我也将testH264VideoStreamer提供的SDP信息存储为SDP文件。
v=0
o=- 1606317166144671 1 IN IP4 192.168.3.92
s=Session streamed by "testH264VideoStreamer"
i=test.264
t=0 0
a=tool:LIVE555 Streaming Media v2020.10.16
a=type:broadcast
a=control:*
a=source-filter: incl IN IP4 * 192.168.3.92
a=rtcp-unicast: reflection
a=range:npt=0-
a=x-qt-text-nam:Session streamed by "testH264VideoStreamer"
a=x-qt-text-inf:test.264
m=video 18888 RTP/AVP 96
c=IN IP4 232.42.39.62/255
b=AS:500
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=640028;sprop-$
a=control:track1
^@
我修改了 testRTSPClient 示例,使其仅通过使用 SDP 文件中的数据连接到 RTP 流。
这是我用来初始化的两个函数。
void openSDP(UsageEnvironment& env, char const* sdpFile)
{
const char * rtspURL = "rtsp://192.168.3.92:8554/testStream/";
RTSPClient* rtspClient = ourRTSPClient::createNew(env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL);
if(rtspClient == NULL)
{
env << "Failed to create a RTSP client for URL \"" << rtspURL << "\": " << env.getResultMsg();
return;
}
else
{
env << "Connecting to the stream at " << rtspURL;
}
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
std::vector<char> sdpBuffer;
std::ifstream file(sdpFile, std::ios_base::in | std::ios_base::binary);
file.unsetf(std::ios::skipws);
std::streampos fileSize;
file.seekg(0, std::ios::end);
fileSize = file.tellg();
file.seekg(0, std::ios::beg);
sdpBuffer.reserve(fileSize);
sdpBuffer.insert(sdpBuffer.begin(),
std::istream_iterator<unsigned char>(file),
std::istream_iterator<unsigned char>());
char* const sdpDescription = sdpBuffer.data();
// Create a media session object from this SDP description:
scs.session = MediaSession::createNew(env, sdpDescription);
if(scs.session == NULL)
{
env << *rtspClient << "Failed to create a MediaSession object from the SDP description: " << env.getResultMsg() << "\n";
}
else
if(!scs.session->hasSubsessions())
{
env << *rtspClient << "This session has no media subsessions (i.e., no \"m=\" lines)\n";
}
scs.iter = new MediaSubsessionIterator(*scs.session);
setupNextSubsession(rtspClient);
return;
}
void setupNextSubsession(RTSPClient* rtspClient)
{
UsageEnvironment& env = rtspClient->envir(); // alias
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
scs.subsession = scs.iter->next();
if(scs.subsession != NULL)
{
if(!scs.subsession->initiate())
{
env << "Failed to initiate the subsession: " << env.getResultMsg();
setupNextSubsession(rtspClient); // give up on this subsession; go to the next one
}
else
{
env << "Initiated the subsession:";
if(scs.subsession->rtcpIsMuxed())
{
env << "client port " << scs.subsession->clientPortNum();
}
else
{
env << "client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1;
}
scs.subsession->sink = DummySink::createNew(env,
*scs.subsession,
rtspClient->url());
// perhaps use your own custom "MediaSink" subclass instead
if(scs.subsession->sink == NULL)
{
env << "Failed to create a data sink for the subsession: " << env.getResultMsg();
}
env << "Created a data sink for the subsession";
scs.subsession->miscPtr = rtspClient; // a hack to let subsession handler functions get the "RTSPClient" from the subsession
scs.subsession->sink->startPlaying(*(scs.subsession->readSource()),
subsessionAfterPlaying, scs.subsession);
// Also set a handler to be called if a RTCP "BYE" arrives for this subsession:
if(scs.subsession->rtcpInstance() != NULL)
{
scs.subsession->rtcpInstance()->setByeWithReasonHandler(subsessionByeHandler, scs.subsession);
}
// Set up the next subsession, if any:
setupNextSubsession(rtspClient);
}
}
}
一切都初始化没有错误,但 DummySink 没有收到任何数据。有什么想法吗?
我发现虽然 wireshark 向我显示了带有有效校验和的传入数据包,但 udp 端口没有收到任何数据包。
我已经尝试使用以下命令(如 sudo)来避免内核丢弃数据包,但它们对 Debian Buster 根本没有帮助。
sysctl net.ipv4.conf.eth0.rp_filter=0
sysctl net.ipv4.conf.all.rp_filter=0
echo 0 > /proc/sys/net/ipv4/conf/eth0/rp_filter
sysctl -a | grep "\.rp_filter" | awk '{print "=0"}' | xargs sysctl
基本上我已经结束了从另一台计算机流式传输 pcap 文件,现在我能够接收 NALU。
我必须实现一个连接到现有 RTSP 会话的 RTSP 客户端,但不能向 RTSP 服务器发送命令(仅接收)。
为了模拟这样的环境,我使用 Wireshark 在 Live555 的 testH264VideoStreamer 和 testRTSPClient 示例之间录制了一个 RTSP/RTP 流,并在尝试使用修改版本的 testRTSPClient 接收流数据时使用 tcpreplay 播放它。
我也将testH264VideoStreamer提供的SDP信息存储为SDP文件。
v=0
o=- 1606317166144671 1 IN IP4 192.168.3.92
s=Session streamed by "testH264VideoStreamer"
i=test.264
t=0 0
a=tool:LIVE555 Streaming Media v2020.10.16
a=type:broadcast
a=control:*
a=source-filter: incl IN IP4 * 192.168.3.92
a=rtcp-unicast: reflection
a=range:npt=0-
a=x-qt-text-nam:Session streamed by "testH264VideoStreamer"
a=x-qt-text-inf:test.264
m=video 18888 RTP/AVP 96
c=IN IP4 232.42.39.62/255
b=AS:500
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=640028;sprop-$
a=control:track1
^@
我修改了 testRTSPClient 示例,使其仅通过使用 SDP 文件中的数据连接到 RTP 流。
这是我用来初始化的两个函数。
void openSDP(UsageEnvironment& env, char const* sdpFile)
{
const char * rtspURL = "rtsp://192.168.3.92:8554/testStream/";
RTSPClient* rtspClient = ourRTSPClient::createNew(env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL);
if(rtspClient == NULL)
{
env << "Failed to create a RTSP client for URL \"" << rtspURL << "\": " << env.getResultMsg();
return;
}
else
{
env << "Connecting to the stream at " << rtspURL;
}
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
std::vector<char> sdpBuffer;
std::ifstream file(sdpFile, std::ios_base::in | std::ios_base::binary);
file.unsetf(std::ios::skipws);
std::streampos fileSize;
file.seekg(0, std::ios::end);
fileSize = file.tellg();
file.seekg(0, std::ios::beg);
sdpBuffer.reserve(fileSize);
sdpBuffer.insert(sdpBuffer.begin(),
std::istream_iterator<unsigned char>(file),
std::istream_iterator<unsigned char>());
char* const sdpDescription = sdpBuffer.data();
// Create a media session object from this SDP description:
scs.session = MediaSession::createNew(env, sdpDescription);
if(scs.session == NULL)
{
env << *rtspClient << "Failed to create a MediaSession object from the SDP description: " << env.getResultMsg() << "\n";
}
else
if(!scs.session->hasSubsessions())
{
env << *rtspClient << "This session has no media subsessions (i.e., no \"m=\" lines)\n";
}
scs.iter = new MediaSubsessionIterator(*scs.session);
setupNextSubsession(rtspClient);
return;
}
void setupNextSubsession(RTSPClient* rtspClient)
{
UsageEnvironment& env = rtspClient->envir(); // alias
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
scs.subsession = scs.iter->next();
if(scs.subsession != NULL)
{
if(!scs.subsession->initiate())
{
env << "Failed to initiate the subsession: " << env.getResultMsg();
setupNextSubsession(rtspClient); // give up on this subsession; go to the next one
}
else
{
env << "Initiated the subsession:";
if(scs.subsession->rtcpIsMuxed())
{
env << "client port " << scs.subsession->clientPortNum();
}
else
{
env << "client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1;
}
scs.subsession->sink = DummySink::createNew(env,
*scs.subsession,
rtspClient->url());
// perhaps use your own custom "MediaSink" subclass instead
if(scs.subsession->sink == NULL)
{
env << "Failed to create a data sink for the subsession: " << env.getResultMsg();
}
env << "Created a data sink for the subsession";
scs.subsession->miscPtr = rtspClient; // a hack to let subsession handler functions get the "RTSPClient" from the subsession
scs.subsession->sink->startPlaying(*(scs.subsession->readSource()),
subsessionAfterPlaying, scs.subsession);
// Also set a handler to be called if a RTCP "BYE" arrives for this subsession:
if(scs.subsession->rtcpInstance() != NULL)
{
scs.subsession->rtcpInstance()->setByeWithReasonHandler(subsessionByeHandler, scs.subsession);
}
// Set up the next subsession, if any:
setupNextSubsession(rtspClient);
}
}
}
一切都初始化没有错误,但 DummySink 没有收到任何数据。有什么想法吗?
我发现虽然 wireshark 向我显示了带有有效校验和的传入数据包,但 udp 端口没有收到任何数据包。
我已经尝试使用以下命令(如 sudo)来避免内核丢弃数据包,但它们对 Debian Buster 根本没有帮助。
sysctl net.ipv4.conf.eth0.rp_filter=0
sysctl net.ipv4.conf.all.rp_filter=0
echo 0 > /proc/sys/net/ipv4/conf/eth0/rp_filter
sysctl -a | grep "\.rp_filter" | awk '{print "=0"}' | xargs sysctl
基本上我已经结束了从另一台计算机流式传输 pcap 文件,现在我能够接收 NALU。