Analysis of libsrt of srt for streaming media analysis:

1. In the previous chapter, we analyzed the implementation of srt protocol by calling libsrt library from ffmpeg.

The following is the external interface of libsrt:

// Binding and connection management
int srt_bind(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::bind(u, name, namelen); }
int srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock) { return CUDT::bind(u, udpsock); }
int srt_listen(SRTSOCKET u, int backlog) { return CUDT::listen(u, backlog); }
SRTSOCKET srt_accept(SRTSOCKET u, struct sockaddr * addr, int * addrlen) { return CUDT::accept(u, addr, addrlen); }
SRTSOCKET srt_accept_bond(const SRTSOCKET lsns[], int lsize, int64_t msTimeOut) { return CUDT::accept_bond(lsns, lsize, msTimeOut); }
int srt_connect(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::connect(u, name, namelen, SRT_SEQNO_NONE); }
int srt_connect_debug(SRTSOCKET u, const struct sockaddr * name, int namelen, int forced_isn) { return CUDT::connect(u, name, namelen, forced_isn); }
int srt_getpeername(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getpeername(u, name, namelen); }
int srt_getsockname(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getsockname(u, name, namelen); }
int srt_getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void * optval, int * optlen)
{ return CUDT::getsockopt(u, level, optname, optval, optlen); }
int srt_setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void * optval, int optlen)
{ return CUDT::setsockopt(u, level, optname, optval, optlen); }
int srt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen)
{ return CUDT::getsockopt(u, 0, opt, optval, optlen); }
int srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen)
{ return CUDT::setsockopt(u, 0, opt, optval, optlen); }
int srt_send(SRTSOCKET u, const char * buf, int len) { return CUDT::send(u, buf, len, 0); }
int srt_recv(SRTSOCKET u, char * buf, int len) { return CUDT::recv(u, buf, len, 0); }
int srt_sendmsg(SRTSOCKET u, const char * buf, int len, int ttl, int inorder) { return CUDT::sendmsg(u, buf, len, ttl, 0!=  inorder); }
int srt_recvmsg(SRTSOCKET u, char * buf, int len) { int64_t ign_srctime; return CUDT::recvmsg(u, buf, len, ign_srctime); }

 2. The overall library of libsrt library data flow is as follows:

 2.1 srt_bind analysis

   According to the above figure, updateMux will be called to create a CMultiplexer class object, which is as follows

    CSndQueue*    m_pSndQueue; // The sending queue

    CRcvQueue*    m_pRcvQueue; // The receiving queue

    CChannel*     m_pChannel;  // The UDP channel for sending and receiving

void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& addr, const UDPSOCKET* udpsock /*[[nullable]]*/)
{
    ScopedLock cg(m_GlobControlLock);

    // If udpsock is provided, then this socket will be simply
    // taken for binding as a good deal. It would be nice to make
    // a sanity check to see if this UDP socket isn't already installed
    // in some multiplexer, but we state this UDP socket isn't accessible
    // anyway so this wouldn't be possible.
    if (!udpsock)
    {
        // If not, we need to see if there exist already a multiplexer bound
        // to the same endpoint.
        const int         port      = addr.hport();
        const CSrtConfig& cfgSocket = s->core().m_config;

        bool reuse_attempt = false;
        for (map<int, CMultiplexer>::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i)
        {
            CMultiplexer& m = i->second;

            // First, we need to find a multiplexer with the same port.
            if (m.m_iPort != port)
            {
                HLOGC(smlog.Debug,
                      log << "bind: muxer @" << m.m_iID << " found, but for port " << m.m_iPort
                          << " (requested port: " << port << ")");
                continue;
            }

            // If this is bound to the wildcard address, it can be reused if:
            // - addr is also a wildcard
            // - channel settings match
            // Otherwise it's a conflict.
            sockaddr_any sa;
            m.m_pChannel->getSockAddr((sa));

            HLOGC(smlog.Debug,
                  log << "bind: Found existing muxer @" << m.m_iID << " : " << sa.str() << " - check against "
                      << addr.str());

            if (sa.isany())
            {
                if (!addr.isany())
                {
                    LOGC(smlog.Error,
                         log << "bind: Address: " << addr.str()
                             << " conflicts with existing wildcard binding: " << sa.str());
                    throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
                }

                // Still, for ANY you need either the same family, or open
                // for families.
                if (m.m_mcfg.iIpV6Only != -1 && m.m_mcfg.iIpV6Only != cfgSocket.iIpV6Only)
                {
                    LOGC(smlog.Error,
                         log << "bind: Address: " << addr.str()
                             << " conflicts with existing IPv6 wildcard binding: " << sa.str());
                    throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
                }

                if ((m.m_mcfg.iIpV6Only == 0 || cfgSocket.iIpV6Only == 0) && m.m_iIPversion != addr.family())
                {
                    LOGC(smlog.Error,
                         log << "bind: Address: " << addr.str() << " conflicts with IPv6 wildcard binding: " << sa.str()
                             << " : family " << (m.m_iIPversion == AF_INET ? "IPv4" : "IPv6") << " vs. "
                             << (addr.family() == AF_INET ? "IPv4" : "IPv6"));
                    throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
                }
                reuse_attempt = true;
                HLOGC(smlog.Debug, log << "bind: wildcard address - multiplexer reusable");
            }
            else if (addr.isany() && addr.family() == sa.family())
            {
                LOGC(smlog.Error,
                     log << "bind: Wildcard address: " << addr.str()
                         << " conflicts with existting IP binding: " << sa.str());
                throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
            }
            // If this is bound to a certain address, AND:
            else if (sa.equal_address(addr))
            {
                // - the address is the same as addr
                reuse_attempt = true;
                HLOGC(smlog.Debug, log << "bind: same IP address - multiplexer reusable");
            }
            else
            {
                HLOGC(smlog.Debug, log << "bind: IP addresses differ - ALLOWED to create a new multiplexer");
            }
            // Otherwise:
            // - the address is different than addr
            //   - the address can't be reused, but this can go on with new one.

            // If this is a reusage attempt:
            if (reuse_attempt)
            {
                //   - if the channel settings match, it can be reused
                if (channelSettingsMatch(m.m_mcfg, cfgSocket))
                {
                    HLOGC(smlog.Debug, log << "bind: reusing multiplexer for port " << port);
                    // reuse the existing multiplexer
                    ++i->second.m_iRefCount;
                    installMuxer((s), (i->second));
                    return;
                }
                else
                {
                    //   - if not, it's a conflict
                    LOGC(smlog.Error,
                         log << "bind: Address: " << addr.str() << " conflicts with binding: " << sa.str()
                             << " due to channel settings");
                    throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
                }
            }
            // If not, proceed to the next one, and when there are no reusage
            // candidates, proceed with creating a new multiplexer.

            // Note that a binding to a different IP address is not treated
            // as a candidate for either reuseage or conflict.
        }
    }

    // a new multiplexer is needed
    CMultiplexer m;
    configureMuxer((m), s, addr.family());

    try
    {
        m.m_pChannel = new CChannel();
        m.m_pChannel->setConfig(m.m_mcfg);

        if (udpsock)
        {
            // In this case, addr contains the address
            // that has been extracted already from the
            // given socket
            m.m_pChannel->attach(*udpsock, addr);
        }
        else if (addr.empty())
        {
            // The case of previously used case of a NULL address.
            // This here is used to pass family only, in this case
            // just automatically bind to the "0" address to autoselect
            // everything.
            m.m_pChannel->open(addr.family());
        }
        else
        {
            // If at least the IP address is specified, then bind to that
            // address, but still possibly autoselect the outgoing port, if the
            // port was specified as 0.
            m.m_pChannel->open(addr);
        }

        m.m_pTimer    = new CTimer;
        m.m_pSndQueue = new CSndQueue;
        m.m_pSndQueue->init(m.m_pChannel, m.m_pTimer);
        m.m_pRcvQueue = new CRcvQueue;
        m.m_pRcvQueue->init(128, s->core().maxPayloadSize(), m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer);

        // Rewrite the port here, as it might be only known upon return
        // from CChannel::open.
        m.m_iPort               = installMuxer((s), m);
        m_mMultiplexer[m.m_iID] = m;
    }
    catch (const CUDTException&)
    {
        m.destroy();
        throw;
    }
    catch (...)
    {
        m.destroy();
        throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
    }

    HLOGC(smlog.Debug, log << "bind: creating new multiplexer for port " << m.m_iPort);
}

2.2 Send queue CSndQueue worker function analysis.  

When the CSndQueue object is initialized, a thread will be created: execute the worker function

  woker function: fetch data from the sendbuf cache queue in a loop, and pack the data according to srt to send

void* srt::CSndQueue::worker(void* param)
{
    CSndQueue* self = (CSndQueue*)param;

#if ENABLE_LOGGING
    THREAD_STATE_INIT(("SRT:SndQ:w" + Sprint(m_counter)).c_str());
#else
    THREAD_STATE_INIT("SRT:SndQ:worker");
#endif

#if defined(SRT_DEBUG_SNDQ_HIGHRATE)
    CTimer::rdtsc(self->m_ullDbgTime);
    self->m_ullDbgPeriod = uint64_t(5000000) * CTimer::getCPUFrequency();
    self->m_ullDbgTime += self->m_ullDbgPeriod;
#endif /* SRT_DEBUG_SNDQ_HIGHRATE */

    while (!self->m_bClosing)
    {
        const steady_clock::time_point next_time = self->m_pSndUList->getNextProcTime();

#if defined(SRT_DEBUG_SNDQ_HIGHRATE)
        self->m_WorkerStats.lIteration++;
#endif /* SRT_DEBUG_SNDQ_HIGHRATE */

        if (is_zero(next_time))
        {
#if defined(SRT_DEBUG_SNDQ_HIGHRATE)
            self->m_WorkerStats.lNotReadyTs++;
#endif /* SRT_DEBUG_SNDQ_HIGHRATE */

            // wait here if there is no sockets with data to be sent
            THREAD_PAUSED();
            if (!self->m_bClosing)
            {
                self->m_pSndUList->waitNonEmpty();

#if defined(SRT_DEBUG_SNDQ_HIGHRATE)
                self->m_WorkerStats.lCondWait++;
#endif /* SRT_DEBUG_SNDQ_HIGHRATE */
            }
            THREAD_RESUMED();

            continue;
        }

        // wait until next processing time of the first socket on the list
        const steady_clock::time_point currtime = steady_clock::now();

#if defined(SRT_DEBUG_SNDQ_HIGHRATE)
        if (self->m_ullDbgTime <= currtime)
        {
            fprintf(stdout,
                    "SndQueue %lu slt:%lu nrp:%lu snt:%lu nrt:%lu ctw:%lu\n",
                    self->m_WorkerStats.lIteration,
                    self->m_WorkerStats.lSleepTo,
                    self->m_WorkerStats.lNotReadyPop,
                    self->m_WorkerStats.lSendTo,
                    self->m_WorkerStats.lNotReadyTs,
                    self->m_WorkerStats.lCondWait);
            memset(&self->m_WorkerStats, 0, sizeof(self->m_WorkerStats));
            self->m_ullDbgTime = currtime + self->m_ullDbgPeriod;
        }
#endif /* SRT_DEBUG_SNDQ_HIGHRATE */

        THREAD_PAUSED();
        if (currtime < next_time)
        {
            self->m_pTimer->sleep_until(next_time);

#if defined(HAI_DEBUG_SNDQ_HIGHRATE)
            self->m_WorkerStats.lSleepTo++;
#endif /* SRT_DEBUG_SNDQ_HIGHRATE */
        }
        THREAD_RESUMED();

        // Get a socket with a send request if any.
        CUDT* u = self->m_pSndUList->pop();
        if (u == NULL)
        {
#if defined(SRT_DEBUG_SNDQ_HIGHRATE)
            self->m_WorkerStats.lNotReadyPop++;
#endif /* SRT_DEBUG_SNDQ_HIGHRATE */
            continue;
        }
        
#define UST(field) ((u->m_b##field) ? "+" : "-") << #field << " "
        HLOGC(qslog.Debug,
            log << "CSndQueue: requesting packet from @" << u->socketID() << " STATUS: " << UST(Listening)
                << UST(Connecting) << UST(Connected) << UST(Closing) << UST(Shutdown) << UST(Broken) << UST(PeerHealth)
                << UST(Opened));
#undef UST

        if (!u->m_bConnected || u->m_bBroken)
        {
#if defined(SRT_DEBUG_SNDQ_HIGHRATE)
            self->m_WorkerStats.lNotReadyPop++;
#endif /* SRT_DEBUG_SNDQ_HIGHRATE */
            continue;
        }

        // pack a packet from the socket
        CPacket pkt;
        // 取数据srt 打包
        const std::pair<bool, steady_clock::time_point> res_time = u->packData((pkt));

        // Check if payload size is invalid.
        if (res_time.first == false)
        {
#if defined(SRT_DEBUG_SNDQ_HIGHRATE)
            self->m_WorkerStats.lNotReadyPop++;
#endif /* SRT_DEBUG_SNDQ_HIGHRATE */
            continue;
        }

        const sockaddr_any addr = u->m_PeerAddr;
        const steady_clock::time_point next_send_time = res_time.second;
        if (!is_zero(next_send_time))
            self->m_pSndUList->update(u, CSndUList::DO_RESCHEDULE, next_send_time);

        HLOGC(qslog.Debug, log << self->CONID() << "chn:SENDING: " << pkt.Info());
        // 发送srt 数据
        self->m_pChannel->sendto(addr, pkt);

#if defined(SRT_DEBUG_SNDQ_HIGHRATE)
        self->m_WorkerStats.lSendTo++;
#endif /* SRT_DEBUG_SNDQ_HIGHRATE */
    }

    THREAD_EXIT();
    return NULL;
}

2.3 Receive queue CRcvQueue worker function analysis.

         Circularly read data from the data channel, it will judge the data packet type, if it is a control data packet, call the processCtrl function to process;

If it is a data packet, call the processData function to put the data in the CRcvBuffer cache

void* srt::CRcvQueue::worker(void* param)
{
    CRcvQueue*   self = (CRcvQueue*)param;
    sockaddr_any sa(self->getIPversion());
    int32_t      id = 0;

#if ENABLE_LOGGING
    THREAD_STATE_INIT(("SRT:RcvQ:w" + Sprint(m_counter)).c_str());
#else
    THREAD_STATE_INIT("SRT:RcvQ:worker");
#endif

    CUnit*         unit = 0;
    EConnectStatus cst  = CONN_AGAIN;
    while (!self->m_bClosing)
    {
        bool        have_received = false;
        EReadStatus rst           = self->worker_RetrieveUnit((id), (unit), (sa));
        if (rst == RST_OK)
        {
            if (id < 0)
            {
                // User error on peer. May log something, but generally can only ignore it.
                // XXX Think maybe about sending some "connection rejection response".
                HLOGC(qrlog.Debug,
                      log << self->CONID() << "RECEIVED negative socket id '" << id
                          << "', rejecting (POSSIBLE ATTACK)");
                continue;
            }

            // NOTE: cst state is being changed here.
            // This state should be maintained through any next failed calls to worker_RetrieveUnit.
            // Any error switches this to rejection, just for a case.

            // Note to rendezvous connection. This can accept:
            // - ID == 0 - take the first waiting rendezvous socket
            // - ID > 0  - find the rendezvous socket that has this ID.
            if (id == 0)
            {
                // ID 0 is for connection request, which should be passed to the listening socket or rendezvous sockets
                cst = self->worker_ProcessConnectionRequest(unit, sa);
            }
            else
            {
                // Otherwise ID is expected to be associated with:
                // - an enqueued rendezvous socket
                // - a socket connected to a peer
                cst = self->worker_ProcessAddressedPacket(id, unit, sa);
                // CAN RETURN CONN_REJECT, but m_RejectReason is already set
            }
            HLOGC(qrlog.Debug, log << self->CONID() << "worker: result for the unit: " << ConnectStatusStr(cst));
            if (cst == CONN_AGAIN)
            {
                HLOGC(qrlog.Debug, log << self->CONID() << "worker: packet not dispatched, continuing reading.");
                continue;
            }
            have_received = true;
        }
        else if (rst == RST_ERROR)
        {
            // According to the description by CChannel::recvfrom, this can be either of:
            // - IPE: all errors except EBADF
            // - socket was closed in the meantime by another thread: EBADF
            // If EBADF, then it's expected that the "closing" state is also set.
            // Check that just to report possible errors, but interrupt the loop anyway.
            if (self->m_bClosing)
            {
                HLOGC(qrlog.Debug,
                      log << self->CONID() << "CChannel reported error, but Queue is closing - INTERRUPTING worker.");
            }
            else
            {
                LOGC(qrlog.Fatal,
                     log << self->CONID()
                         << "CChannel reported ERROR DURING TRANSMISSION - IPE. INTERRUPTING worker anyway.");
            }
            cst = CONN_REJECT;
            break;
        }
        // OTHERWISE: this is an "AGAIN" situation. No data was read, but the process should continue.

        // take care of the timing event for all UDT sockets
        const steady_clock::time_point curtime_minus_syn =
            steady_clock::now() - microseconds_from(CUDT::COMM_SYN_INTERVAL_US);

        CRNode* ul = self->m_pRcvUList->m_pUList;
        while ((NULL != ul) && (ul->m_tsTimeStamp < curtime_minus_syn))
        {
            CUDT* u = ul->m_pUDT;

            if (u->m_bConnected && !u->m_bBroken && !u->m_bClosing)
            {
                u->checkTimers();
                self->m_pRcvUList->update(u);
            }
            else
            {
                HLOGC(qrlog.Debug,
                      log << CUDTUnited::CONID(u->m_SocketID) << " SOCKET broken, REMOVING FROM RCV QUEUE/MAP.");
                // the socket must be removed from Hash table first, then RcvUList
                self->m_pHash->remove(u->m_SocketID);
                self->m_pRcvUList->remove(u);
                u->m_pRNode->m_bOnList = false;
            }

            ul = self->m_pRcvUList->m_pUList;
        }

        if (have_received)
        {
            HLOGC(qrlog.Debug,
                  log << "worker: RECEIVED PACKET --> updateConnStatus. cst=" << ConnectStatusStr(cst) << " id=" << id
                      << " pkt-payload-size=" << unit->m_Packet.getLength());
        }

        // Check connection requests status for all sockets in the RendezvousQueue.
        // Pass the connection status from the last call of:
        // worker_ProcessAddressedPacket --->
        // worker_TryAsyncRend_OrStore --->
        // CUDT::processAsyncConnectResponse --->
        // CUDT::processConnectResponse
        self->m_pRendezvousQueue->updateConnStatus(rst, cst, unit);

        // XXX updateConnStatus may have removed the connector from the list,
        // however there's still m_mBuffer in CRcvQueue for that socket to care about.
    }

    HLOGC(qrlog.Debug, log << "worker: EXIT");

    THREAD_EXIT();
    return NULL;
}

Guess you like

Origin blog.csdn.net/u012794472/article/details/126783288