660 lines
28 KiB
C++
660 lines
28 KiB
C++
|
/**********
|
||
|
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 <http://www.gnu.org/copyleft/lesser.html>.)
|
||
|
|
||
|
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 <GroupsockHelper.hh>
|
||
|
|
||
|
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= <media>
|
||
|
fPortNumForSDP, // m= <port>
|
||
|
rtpPayloadType, // m= <fmt list>
|
||
|
ipAddressStr.val(), // c= address
|
||
|
estBitrate, // b=AS:<bandwidth>
|
||
|
rtpmapLine, // a=rtpmap:... (if present)
|
||
|
rtcpmuxLine, // a=rtcp-mux:... (if present)
|
||
|
rangeLine, // a=range:... (if present)
|
||
|
auxSDPLine, // optional extra SDP line
|
||
|
trackId()); // a=control:<track-id>
|
||
|
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;
|
||
|
}
|