/********** This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. (See .) This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA **********/ // "liveMedia" // Copyright (c) 1996-2016 Live Networks, Inc. All rights reserved. // A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s // on demand. // Implementation #include "OnDemandServerMediaSubsession.hh" #include OnDemandServerMediaSubsession::OnDemandServerMediaSubsession(UsageEnvironment& env, Boolean reuseFirstSource, portNumBits initialPortNum, Boolean multiplexRTCPWithRTP) : ServerMediaSubsession(env), fSDPLines(NULL), fReuseFirstSource(reuseFirstSource), fMultiplexRTCPWithRTP(multiplexRTCPWithRTP), fLastStreamToken(NULL), fAppHandlerTask(NULL), fAppHandlerClientData(NULL), _onPlay(NULL), _playContext(NULL), _onStop(NULL), _stopContext(NULL) { fDestinationsHashTable = HashTable::create(ONE_WORD_HASH_KEYS); if (fMultiplexRTCPWithRTP) { fInitialPortNum = initialPortNum; } else { // Make sure RTP ports are even-numbered: fInitialPortNum = (initialPortNum + 1) & ~1; } gethostname(fCNAME, sizeof fCNAME); fCNAME[sizeof fCNAME - 1] = '\0'; // just in case } OnDemandServerMediaSubsession::~OnDemandServerMediaSubsession() { delete[] fSDPLines; // Clean out the destinations hash table: while (1) { Destinations* destinations = (Destinations*)(fDestinationsHashTable->RemoveNext()); if (destinations == NULL) break; delete destinations; } delete fDestinationsHashTable; } void OnDemandServerMediaSubsession::registerPlayCallback(PlayCallbackFunc *cb, void *context) { _onPlay = cb; _playContext = context; } void OnDemandServerMediaSubsession::registerStopCallback(StopCallbackFunc *cb, void *context) { _onStop = cb; _stopContext = context; } char const* OnDemandServerMediaSubsession::sdpLines() { if (fSDPLines == NULL) { // We need to construct a set of SDP lines that describe this // subsession (as a unicast stream). To do so, we first create // dummy (unused) source and "RTPSink" objects, // whose parameters we use for the SDP lines: unsigned estBitrate; FramedSource* inputSource = createNewStreamSource(0, estBitrate); if (inputSource == NULL) return NULL; // file not found struct in_addr dummyAddr; dummyAddr.s_addr = 0; Groupsock* dummyGroupsock = createGroupsock(dummyAddr, 0); unsigned char rtpPayloadType = 96 + trackNumber() - 1; // if dynamic RTPSink* dummyRTPSink = createNewRTPSink(dummyGroupsock, rtpPayloadType, inputSource); if (dummyRTPSink != NULL && dummyRTPSink->estimatedBitrate() > 0) estBitrate = dummyRTPSink->estimatedBitrate(); setSDPLinesFromRTPSink(dummyRTPSink, inputSource, estBitrate); Medium::close(dummyRTPSink); delete dummyGroupsock; closeStreamSource(inputSource); } return fSDPLines; } void OnDemandServerMediaSubsession::getStreamParameters(unsigned clientSessionId, netAddressBits clientAddress, Port const& clientRTPPort, Port const& clientRTCPPort, int tcpSocketNum, unsigned char rtpChannelId, unsigned char rtcpChannelId, netAddressBits& destinationAddress, u_int8_t& /*destinationTTL*/, Boolean& isMulticast, Port& serverRTPPort, Port& serverRTCPPort, void*& streamToken) { if (destinationAddress == 0) destinationAddress = clientAddress; struct in_addr destinationAddr; destinationAddr.s_addr = destinationAddress; isMulticast = False; if (fLastStreamToken != NULL && fReuseFirstSource) { // Special case: Rather than creating a new 'StreamState', // we reuse the one that we've already created: serverRTPPort = ((StreamState*)fLastStreamToken)->serverRTPPort(); serverRTCPPort = ((StreamState*)fLastStreamToken)->serverRTCPPort(); ++((StreamState*)fLastStreamToken)->referenceCount(); streamToken = fLastStreamToken; } else { // Normal case: Create a new media source: unsigned streamBitrate; FramedSource* mediaSource = createNewStreamSource(clientSessionId, streamBitrate); // Create 'groupsock' and 'sink' objects for the destination, // using previously unused server port numbers: RTPSink* rtpSink = NULL; BasicUDPSink* udpSink = NULL; Groupsock* rtpGroupsock = NULL; Groupsock* rtcpGroupsock = NULL; if (clientRTPPort.num() != 0 || tcpSocketNum >= 0) { // Normal case: Create destinations portNumBits serverPortNum; if (clientRTCPPort.num() == 0) { // We're streaming raw UDP (not RTP). Create a single groupsock: NoReuse dummy(envir()); // ensures that we skip over ports that are already in use for (serverPortNum = fInitialPortNum;; ++serverPortNum) { struct in_addr dummyAddr; dummyAddr.s_addr = 0; serverRTPPort = serverPortNum; rtpGroupsock = createGroupsock(dummyAddr, serverRTPPort); if (rtpGroupsock->socketNum() >= 0) break; // success } udpSink = BasicUDPSink::createNew(envir(), rtpGroupsock); } else { // Normal case: We're streaming RTP (over UDP or TCP). Create a pair of // groupsocks (RTP and RTCP), with adjacent port numbers (RTP port number even). // (If we're multiplexing RTCP and RTP over the same port number, it can be odd or even.) NoReuse dummy(envir()); // ensures that we skip over ports that are already in use for (portNumBits serverPortNum = fInitialPortNum;; ++serverPortNum) { struct in_addr dummyAddr; dummyAddr.s_addr = 0; serverRTPPort = serverPortNum; rtpGroupsock = createGroupsock(dummyAddr, serverRTPPort); if (rtpGroupsock->socketNum() < 0) { delete rtpGroupsock; continue; // try again } if (fMultiplexRTCPWithRTP) { // Use the RTP 'groupsock' object for RTCP as well: serverRTCPPort = serverRTPPort; rtcpGroupsock = rtpGroupsock; } else { // Create a separate 'groupsock' object (with the next (odd) port number) for RTCP: serverRTCPPort = ++serverPortNum; rtcpGroupsock = createGroupsock(dummyAddr, serverRTCPPort); if (rtcpGroupsock->socketNum() < 0) { delete rtpGroupsock; delete rtcpGroupsock; continue; // try again } } break; // success } unsigned char rtpPayloadType = 96 + trackNumber() - 1; // if dynamic rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource); if (rtpSink != NULL && rtpSink->estimatedBitrate() > 0) streamBitrate = rtpSink->estimatedBitrate(); } // Turn off the destinations for each groupsock. They'll get set later // (unless TCP is used instead): if (rtpGroupsock != NULL) rtpGroupsock->removeAllDestinations(); if (rtcpGroupsock != NULL) rtcpGroupsock->removeAllDestinations(); if (rtpGroupsock != NULL) { // Try to use a big send buffer for RTP - at least 0.1 second of // specified bandwidth and at least 50 KB unsigned rtpBufSize = streamBitrate * 25 / 2; // 1 kbps * 0.1 s = 12.5 bytes if (rtpBufSize < 50 * 1024) rtpBufSize = 50 * 1024; increaseSendBufferTo(envir(), rtpGroupsock->socketNum(), rtpBufSize); } } // Set up the state of the stream. The stream will get started later: streamToken = fLastStreamToken = new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink, streamBitrate, mediaSource, rtpGroupsock, rtcpGroupsock); } // Record these destinations as being for this client session id: Destinations* destinations; if (tcpSocketNum < 0) { // UDP destinations = new Destinations(destinationAddr, clientRTPPort, clientRTCPPort); } else { // TCP destinations = new Destinations(tcpSocketNum, rtpChannelId, rtcpChannelId); } fDestinationsHashTable->Add((char const*)clientSessionId, destinations); } void OnDemandServerMediaSubsession::startStream( unsigned clientSessionId, void* streamToken, TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData, unsigned short& rtpSeqNum, unsigned& rtpTimestamp, ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler, void* serverRequestAlternativeByteHandlerClientData) { if (_onPlay) _onPlay(_playContext); StreamState* streamState = (StreamState*)streamToken; Destinations* destinations = (Destinations*)(fDestinationsHashTable->Lookup((char const*)clientSessionId)); if (streamState != NULL) { streamState->startPlaying(destinations, clientSessionId, rtcpRRHandler, rtcpRRHandlerClientData, serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData); RTPSink* rtpSink = streamState->rtpSink(); // alias if (rtpSink != NULL) { rtpSeqNum = rtpSink->currentSeqNo(); rtpTimestamp = rtpSink->presetNextTimestamp(); } // envir().taskScheduler().scheduleDelayedTask(0, StreamState::sendSRReport, streamState); } } void OnDemandServerMediaSubsession::pauseStream(unsigned /*clientSessionId*/, void* streamToken) { // Pausing isn't allowed if multiple clients are receiving data from // the same source: if (fReuseFirstSource) return; StreamState* streamState = (StreamState*)streamToken; if (streamState != NULL) streamState->pause(); if(_onStop) _onStop(_stopContext); } void OnDemandServerMediaSubsession::seekStream(unsigned /*clientSessionId*/, void* streamToken, double& seekNPT, double streamDuration, u_int64_t& numBytes) { numBytes = 0; // by default: unknown // Seeking isn't allowed if multiple clients are receiving data from the same source: if (fReuseFirstSource) return; StreamState* streamState = (StreamState*)streamToken; if (streamState != NULL && streamState->mediaSource() != NULL) { seekStreamSource(streamState->mediaSource(), seekNPT, streamDuration, numBytes); streamState->startNPT() = (float)seekNPT; RTPSink* rtpSink = streamState->rtpSink(); // alias if (rtpSink != NULL) rtpSink->resetPresentationTimes(); } } void OnDemandServerMediaSubsession::seekStream(unsigned /*clientSessionId*/, void* streamToken, char*& absStart, char*& absEnd) { // Seeking isn't allowed if multiple clients are receiving data from the same source: if (fReuseFirstSource) return; StreamState* streamState = (StreamState*)streamToken; if (streamState != NULL && streamState->mediaSource() != NULL) { seekStreamSource(streamState->mediaSource(), absStart, absEnd); } } void OnDemandServerMediaSubsession::nullSeekStream(unsigned /*clientSessionId*/, void* streamToken, double streamEndTime, u_int64_t& numBytes) { numBytes = 0; // by default: unknown StreamState* streamState = (StreamState*)streamToken; if (streamState != NULL && streamState->mediaSource() != NULL) { // Because we're not seeking here, get the current NPT, and remember it as the new 'start' NPT: streamState->startNPT() = getCurrentNPT(streamToken); double duration = streamEndTime - streamState->startNPT(); if (duration < 0.0) duration = 0.0; setStreamSourceDuration(streamState->mediaSource(), duration, numBytes); RTPSink* rtpSink = streamState->rtpSink(); // alias if (rtpSink != NULL) rtpSink->resetPresentationTimes(); } } void OnDemandServerMediaSubsession::setStreamScale(unsigned /*clientSessionId*/, void* streamToken, float scale) { // Changing the scale factor isn't allowed if multiple clients are receiving data // from the same source: if (fReuseFirstSource) return; StreamState* streamState = (StreamState*)streamToken; if (streamState != NULL && streamState->mediaSource() != NULL) { setStreamSourceScale(streamState->mediaSource(), scale); } } float OnDemandServerMediaSubsession::getCurrentNPT(void* streamToken) { do { if (streamToken == NULL) break; StreamState* streamState = (StreamState*)streamToken; RTPSink* rtpSink = streamState->rtpSink(); if (rtpSink == NULL) break; return streamState->startNPT() + (rtpSink->mostRecentPresentationTime().tv_sec - rtpSink->initialPresentationTime().tv_sec) + (rtpSink->mostRecentPresentationTime().tv_usec - rtpSink->initialPresentationTime().tv_usec) / 1000000.0f; } while (0); return 0.0; } FramedSource* OnDemandServerMediaSubsession::getStreamSource(void* streamToken) { if (streamToken == NULL) return NULL; StreamState* streamState = (StreamState*)streamToken; return streamState->mediaSource(); } void OnDemandServerMediaSubsession::getRTPSinkandRTCP(void* streamToken, RTPSink const*& rtpSink, RTCPInstance const*& rtcp) { if (streamToken == NULL) { rtpSink = NULL; rtcp = NULL; return; } StreamState* streamState = (StreamState*)streamToken; rtpSink = streamState->rtpSink(); rtcp = streamState->rtcpInstance(); } void OnDemandServerMediaSubsession::deleteStream(unsigned clientSessionId, void*& streamToken) { StreamState* streamState = (StreamState*)streamToken; // Look up (and remove) the destinations for this client session: Destinations* destinations = (Destinations*)(fDestinationsHashTable->Lookup((char const*)clientSessionId)); if (destinations != NULL) { fDestinationsHashTable->Remove((char const*)clientSessionId); // Stop streaming to these destinations: if (streamState != NULL) streamState->endPlaying(destinations, clientSessionId); } // Delete the "StreamState" structure if it's no longer being used: if (streamState != NULL) { if (streamState->referenceCount() > 0) --streamState->referenceCount(); if (streamState->referenceCount() == 0) { delete streamState; streamToken = NULL; } } // Finally, delete the destinations themselves: delete destinations; } char const* OnDemandServerMediaSubsession::getAuxSDPLine(RTPSink* rtpSink, FramedSource* /*inputSource*/) { // Default implementation: return rtpSink == NULL ? NULL : rtpSink->auxSDPLine(); } void OnDemandServerMediaSubsession::seekStreamSource(FramedSource* /*inputSource*/, double& /*seekNPT*/, double /*streamDuration*/, u_int64_t& numBytes) { // Default implementation: Do nothing numBytes = 0; } void OnDemandServerMediaSubsession::seekStreamSource(FramedSource* /*inputSource*/, char*& absStart, char*& absEnd) { // Default implementation: do nothing (but delete[] and assign "absStart" and "absEnd" to NULL, to show that we don't handle this) delete[] absStart; absStart = NULL; delete[] absEnd; absEnd = NULL; } void OnDemandServerMediaSubsession::setStreamSourceScale(FramedSource* /*inputSource*/, float /*scale*/) { // Default implementation: Do nothing } void OnDemandServerMediaSubsession::setStreamSourceDuration(FramedSource* /*inputSource*/, double /*streamDuration*/, u_int64_t& numBytes) { // Default implementation: Do nothing numBytes = 0; } void OnDemandServerMediaSubsession::closeStreamSource(FramedSource* inputSource) { Medium::close(inputSource); } Groupsock* OnDemandServerMediaSubsession::createGroupsock(struct in_addr const& addr, Port port) { // Default implementation; may be redefined by subclasses: return new Groupsock(envir(), addr, port, 255); } RTCPInstance* OnDemandServerMediaSubsession::createRTCP(Groupsock* RTCPgs, unsigned totSessionBW, /* in kbps */ unsigned char const* cname, RTPSink* sink) { // Default implementation; may be redefined by subclasses: return RTCPInstance::createNew(envir(), RTCPgs, totSessionBW, cname, sink, NULL /*we're a server*/); } void OnDemandServerMediaSubsession::setRTCPAppPacketHandler(RTCPAppHandlerFunc* handler, void* clientData) { fAppHandlerTask = handler; fAppHandlerClientData = clientData; } void OnDemandServerMediaSubsession::sendRTCPAppPacket(u_int8_t subtype, char const* name, u_int8_t* appDependentData, unsigned appDependentDataSize) { StreamState* streamState = (StreamState*)fLastStreamToken; if (streamState != NULL) { streamState->sendRTCPAppPacket(subtype, name, appDependentData, appDependentDataSize); } } void OnDemandServerMediaSubsession::setSDPLinesFromRTPSink(RTPSink* rtpSink, FramedSource* inputSource, unsigned estBitrate) { if (rtpSink == NULL) return; char const* mediaType = rtpSink->sdpMediaType(); unsigned char rtpPayloadType = rtpSink->rtpPayloadType(); AddressString ipAddressStr(fServerAddressForSDP); char* rtpmapLine = rtpSink->rtpmapLine(); char const* rtcpmuxLine = fMultiplexRTCPWithRTP ? "a=rtcp-mux\r\n" : ""; char const* rangeLine = rangeSDPLine(); char const* auxSDPLine = getAuxSDPLine(rtpSink, inputSource); if (auxSDPLine == NULL) auxSDPLine = ""; char const* const sdpFmt = "m=%s %u RTP/AVP %d\r\n" "c=IN IP4 %s\r\n" "b=AS:%u\r\n" "%s" "%s" "%s" "%s" "a=control:%s\r\n"; unsigned sdpFmtSize = strlen(sdpFmt) + strlen(mediaType) + 5 /* max short len */ + 3 /* max char len */ + strlen(ipAddressStr.val()) + 20 /* max int len */ + strlen(rtpmapLine) + strlen(rtcpmuxLine) + strlen(rangeLine) + strlen(auxSDPLine) + strlen(trackId()); char* sdpLines = new char[sdpFmtSize]; sprintf(sdpLines, sdpFmt, mediaType, // m= fPortNumForSDP, // m= rtpPayloadType, // m= ipAddressStr.val(), // c= address estBitrate, // b=AS: rtpmapLine, // a=rtpmap:... (if present) rtcpmuxLine, // a=rtcp-mux:... (if present) rangeLine, // a=range:... (if present) auxSDPLine, // optional extra SDP line trackId()); // a=control: delete[](char*) rangeLine; delete[] rtpmapLine; fSDPLines = strDup(sdpLines); delete[] sdpLines; } ////////// StreamState implementation ////////// static void afterPlayingStreamState(void* clientData) { StreamState* streamState = (StreamState*)clientData; if (streamState->streamDuration() == 0.0) { // When the input stream ends, tear it down. This will cause a RTCP "BYE" // to be sent to each client, teling it that the stream has ended. // (Because the stream didn't have a known duration, there was no other // way for clients to know when the stream ended.) streamState->reclaim(); } // Otherwise, keep the stream alive, in case a client wants to // subsequently re-play the stream starting from somewhere other than the end. // (This can be done only on streams that have a known duration.) } StreamState::StreamState(OnDemandServerMediaSubsession& master, Port const& serverRTPPort, Port const& serverRTCPPort, RTPSink* rtpSink, BasicUDPSink* udpSink, unsigned totalBW, FramedSource* mediaSource, Groupsock* rtpGS, Groupsock* rtcpGS) : fMaster(master), fAreCurrentlyPlaying(False), fReferenceCount(1), fServerRTPPort(serverRTPPort), fServerRTCPPort(serverRTCPPort), fRTPSink(rtpSink), fUDPSink(udpSink), fStreamDuration(master.duration()), fTotalBW(totalBW), fRTCPInstance(NULL) /* created later */, fMediaSource(mediaSource), fStartNPT(0.0), fRTPgs(rtpGS), fRTCPgs(rtcpGS) { } StreamState::~StreamState() { reclaim(); } void StreamState::startPlaying(Destinations* dests, unsigned clientSessionId, TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData, ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler, void* serverRequestAlternativeByteHandlerClientData) { if (dests == NULL) return; if (fRTCPInstance == NULL && fRTPSink != NULL) { // Create (and start) a 'RTCP instance' for this RTP sink: fRTCPInstance = fMaster.createRTCP(fRTCPgs, fTotalBW, (unsigned char*)fMaster.fCNAME, fRTPSink); // Note: This starts RTCP running automatically fRTCPInstance->setAppHandler(fMaster.fAppHandlerTask, fMaster.fAppHandlerClientData); } if (dests->isTCP) { // Change RTP and RTCP to use the TCP socket instead of UDP: if (fRTPSink != NULL) { fRTPSink->addStreamSocket(dests->tcpSocketNum, dests->rtpChannelId); RTPInterface::setServerRequestAlternativeByteHandler(fRTPSink->envir(), dests->tcpSocketNum, serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData); // So that we continue to handle RTSP commands from the client } if (fRTCPInstance != NULL) { fRTCPInstance->addStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId); fRTCPInstance->setSpecificRRHandler(dests->tcpSocketNum, dests->rtcpChannelId, rtcpRRHandler, rtcpRRHandlerClientData); } } else { // Tell the RTP and RTCP 'groupsocks' about this destination // (in case they don't already have it): if (fRTPgs != NULL) fRTPgs->addDestination(dests->addr, dests->rtpPort, clientSessionId); if (fRTCPgs != NULL && !(fRTCPgs == fRTPgs && dests->rtcpPort.num() == dests->rtpPort.num())) { fRTCPgs->addDestination(dests->addr, dests->rtcpPort, clientSessionId); } if (fRTCPInstance != NULL) { fRTCPInstance->setSpecificRRHandler(dests->addr.s_addr, dests->rtcpPort, rtcpRRHandler, rtcpRRHandlerClientData); } } // Hack: delay send SR report, change to VSplayer // if (fRTCPInstance != NULL) { // // Hack: Send an initial RTCP "SR" packet, before the initial RTP packet, so that receivers will (likely) be able to // // get RTCP-synchronized presentation times immediately: // fRTCPInstance->sendReport(); // } if (!fAreCurrentlyPlaying && fMediaSource != NULL) { if (fRTPSink != NULL) { fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this); fAreCurrentlyPlaying = True; } else if (fUDPSink != NULL) { fUDPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this); fAreCurrentlyPlaying = True; } } } void StreamState::sendSRReport(void *streamState) { StreamState *s = (StreamState*)streamState; if (s->fRTCPInstance != NULL) { // Hack: Send an initial RTCP "SR" packet, before the initial RTP packet, so that receivers will (likely) be able to // get RTCP-synchronized presentation times immediately: s->fRTCPInstance->sendReport(); } } void StreamState::pause() { if (fRTPSink != NULL) fRTPSink->stopPlaying(); if (fUDPSink != NULL) fUDPSink->stopPlaying(); fAreCurrentlyPlaying = False; } void StreamState::endPlaying(Destinations* dests, unsigned clientSessionId) { #if 0 // The following code is temporarily disabled, because it erroneously sends RTCP "BYE"s to all clients if multiple // clients are streaming from the same data source (i.e., if "reuseFirstSource" is True), and we don't want that to happen // if we're being called as a result of a single one of these clients having sent a "TEARDOWN" (rather than the whole stream // having been closed, for all clients). // This will be fixed for real later. if (fRTCPInstance != NULL) { // Hack: Explicitly send a RTCP "BYE" packet now, because the code below will prevent that from happening later, // when "fRTCPInstance" gets deleted: fRTCPInstance->sendBYE(); } #endif if (dests->isTCP) { if (fRTPSink != NULL) { fRTPSink->removeStreamSocket(dests->tcpSocketNum, dests->rtpChannelId); } if (fRTCPInstance != NULL) { fRTCPInstance->removeStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId); fRTCPInstance->unsetSpecificRRHandler(dests->tcpSocketNum, dests->rtcpChannelId); } } else { // Tell the RTP and RTCP 'groupsocks' to stop using these destinations: if (fRTPgs != NULL) fRTPgs->removeDestination(clientSessionId); if (fRTCPgs != NULL && fRTCPgs != fRTPgs) fRTCPgs->removeDestination(clientSessionId); if (fRTCPInstance != NULL) { fRTCPInstance->unsetSpecificRRHandler(dests->addr.s_addr, dests->rtcpPort); } } } void StreamState::sendRTCPAppPacket(u_int8_t subtype, char const* name, u_int8_t* appDependentData, unsigned appDependentDataSize) { if (fRTCPInstance != NULL) { fRTCPInstance->sendAppPacket(subtype, name, appDependentData, appDependentDataSize); } } void StreamState::reclaim() { // Delete allocated media objects Medium::close(fRTCPInstance) /* will send a RTCP BYE */; fRTCPInstance = NULL; Medium::close(fRTPSink); fRTPSink = NULL; Medium::close(fUDPSink); fUDPSink = NULL; fMaster.closeStreamSource(fMediaSource); fMediaSource = NULL; if (fMaster.fLastStreamToken == this) fMaster.fLastStreamToken = NULL; delete fRTPgs; if (fRTCPgs != fRTPgs) delete fRTCPgs; fRTPgs = NULL; fRTCPgs = NULL; }