使用 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。