458 lines
15 KiB
C++
Executable File
458 lines
15 KiB
C++
Executable File
/**********
|
|
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 data structure that represents a session that consists of
|
|
// potentially multiple (audio and/or video) sub-sessions
|
|
// (This data structure is used for media *streamers* - i.e., servers.
|
|
// For media receivers, use "MediaSession" instead.)
|
|
// Implementation
|
|
|
|
#include "ServerMediaSession.hh"
|
|
#include <GroupsockHelper.hh>
|
|
#include <math.h>
|
|
#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
|
|
#define snprintf _snprintf
|
|
#endif
|
|
|
|
////////// ServerMediaSession //////////
|
|
|
|
ServerMediaSession* ServerMediaSession
|
|
::createNew(UsageEnvironment& env,
|
|
char const* streamName, char const* info,
|
|
char const* description, Boolean isSSM, char const* miscSDPLines) {
|
|
return new ServerMediaSession(env, streamName, info, description,
|
|
isSSM, miscSDPLines);
|
|
}
|
|
|
|
Boolean ServerMediaSession
|
|
::lookupByName(UsageEnvironment& env, char const* mediumName,
|
|
ServerMediaSession*& resultSession) {
|
|
resultSession = NULL; // unless we succeed
|
|
|
|
Medium* medium;
|
|
if (!Medium::lookupByName(env, mediumName, medium)) return False;
|
|
|
|
if (!medium->isServerMediaSession()) {
|
|
env.setResultMsg(mediumName, " is not a 'ServerMediaSession' object");
|
|
return False;
|
|
}
|
|
|
|
resultSession = (ServerMediaSession*)medium;
|
|
return True;
|
|
}
|
|
|
|
static char const* const libNameStr = "AW RTSP Streaming v";
|
|
char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING;
|
|
|
|
ServerMediaSession::ServerMediaSession(UsageEnvironment& env,
|
|
char const* streamName,
|
|
char const* info,
|
|
char const* description,
|
|
Boolean isSSM, char const* miscSDPLines)
|
|
: Medium(env), fIsSSM(isSSM), fSubsessionsHead(NULL),
|
|
fSubsessionsTail(NULL), fSubsessionCounter(0),
|
|
fReferenceCount(0), fDeleteWhenUnreferenced(False) {
|
|
fStreamName = strDup(streamName == NULL ? "" : streamName);
|
|
|
|
char* libNamePlusVersionStr = NULL; // by default
|
|
if (info == NULL || description == NULL) {
|
|
libNamePlusVersionStr = new char[strlen(libNameStr) + strlen(libVersionStr) + 1];
|
|
sprintf(libNamePlusVersionStr, "%s%s", libNameStr, libVersionStr);
|
|
}
|
|
fInfoSDPString = strDup(info == NULL ? libNamePlusVersionStr : info);
|
|
fDescriptionSDPString = strDup(description == NULL ? libNamePlusVersionStr : description);
|
|
delete[] libNamePlusVersionStr;
|
|
|
|
fMiscSDPLines = strDup(miscSDPLines == NULL ? "" : miscSDPLines);
|
|
|
|
gettimeofday(&fCreationTime, NULL);
|
|
}
|
|
|
|
ServerMediaSession::~ServerMediaSession() {
|
|
deleteAllSubsessions();
|
|
delete[] fStreamName;
|
|
delete[] fInfoSDPString;
|
|
delete[] fDescriptionSDPString;
|
|
delete[] fMiscSDPLines;
|
|
}
|
|
|
|
Boolean
|
|
ServerMediaSession::addSubsession(ServerMediaSubsession* subsession) {
|
|
if (subsession->fParentSession != NULL) return False; // it's already used
|
|
|
|
if (fSubsessionsTail == NULL) {
|
|
fSubsessionsHead = subsession;
|
|
} else {
|
|
fSubsessionsTail->fNext = subsession;
|
|
}
|
|
fSubsessionsTail = subsession;
|
|
|
|
subsession->fParentSession = this;
|
|
subsession->fTrackNumber = ++fSubsessionCounter;
|
|
return True;
|
|
}
|
|
|
|
void ServerMediaSession::testScaleFactor(float& scale) {
|
|
// First, try setting all subsessions to the desired scale.
|
|
// If the subsessions' actual scales differ from each other, choose the
|
|
// value that's closest to 1, and then try re-setting all subsessions to that
|
|
// value. If the subsessions' actual scales still differ, re-set them all to 1.
|
|
float minSSScale = 1.0;
|
|
float maxSSScale = 1.0;
|
|
float bestSSScale = 1.0;
|
|
float bestDistanceTo1 = 0.0;
|
|
ServerMediaSubsession* subsession;
|
|
for (subsession = fSubsessionsHead; subsession != NULL;
|
|
subsession = subsession->fNext) {
|
|
float ssscale = scale;
|
|
subsession->testScaleFactor(ssscale);
|
|
if (subsession == fSubsessionsHead) { // this is the first subsession
|
|
minSSScale = maxSSScale = bestSSScale = ssscale;
|
|
bestDistanceTo1 = (float)fabs(ssscale - 1.0f);
|
|
} else {
|
|
if (ssscale < minSSScale) {
|
|
minSSScale = ssscale;
|
|
} else if (ssscale > maxSSScale) {
|
|
maxSSScale = ssscale;
|
|
}
|
|
|
|
float distanceTo1 = (float)fabs(ssscale - 1.0f);
|
|
if (distanceTo1 < bestDistanceTo1) {
|
|
bestSSScale = ssscale;
|
|
bestDistanceTo1 = distanceTo1;
|
|
}
|
|
}
|
|
}
|
|
if (minSSScale == maxSSScale) {
|
|
// All subsessions are at the same scale: minSSScale == bestSSScale == maxSSScale
|
|
scale = minSSScale;
|
|
return;
|
|
}
|
|
|
|
// The scales for each subsession differ. Try to set each one to the value
|
|
// that's closest to 1:
|
|
for (subsession = fSubsessionsHead; subsession != NULL;
|
|
subsession = subsession->fNext) {
|
|
float ssscale = bestSSScale;
|
|
subsession->testScaleFactor(ssscale);
|
|
if (ssscale != bestSSScale) break; // no luck
|
|
}
|
|
if (subsession == NULL) {
|
|
// All subsessions are at the same scale: bestSSScale
|
|
scale = bestSSScale;
|
|
return;
|
|
}
|
|
|
|
// Still no luck. Set each subsession's scale to 1:
|
|
for (subsession = fSubsessionsHead; subsession != NULL;
|
|
subsession = subsession->fNext) {
|
|
float ssscale = 1;
|
|
subsession->testScaleFactor(ssscale);
|
|
}
|
|
scale = 1;
|
|
}
|
|
|
|
float ServerMediaSession::duration() const {
|
|
float minSubsessionDuration = 0.0;
|
|
float maxSubsessionDuration = 0.0;
|
|
for (ServerMediaSubsession* subsession = fSubsessionsHead; subsession != NULL;
|
|
subsession = subsession->fNext) {
|
|
// Hack: If any subsession supports seeking by 'absolute' time, then return a negative value, to indicate that only subsessions
|
|
// will have a "a=range:" attribute:
|
|
char* absStartTime = NULL; char* absEndTime = NULL;
|
|
subsession->getAbsoluteTimeRange(absStartTime, absEndTime);
|
|
if (absStartTime != NULL) return -1.0f;
|
|
|
|
float ssduration = subsession->duration();
|
|
if (subsession == fSubsessionsHead) { // this is the first subsession
|
|
minSubsessionDuration = maxSubsessionDuration = ssduration;
|
|
} else if (ssduration < minSubsessionDuration) {
|
|
minSubsessionDuration = ssduration;
|
|
} else if (ssduration > maxSubsessionDuration) {
|
|
maxSubsessionDuration = ssduration;
|
|
}
|
|
}
|
|
|
|
if (maxSubsessionDuration != minSubsessionDuration) {
|
|
return -maxSubsessionDuration; // because subsession durations differ
|
|
} else {
|
|
return maxSubsessionDuration; // all subsession durations are the same
|
|
}
|
|
}
|
|
|
|
void ServerMediaSession::noteLiveness() {
|
|
// default implementation: do nothing
|
|
}
|
|
|
|
void ServerMediaSession::deleteAllSubsessions() {
|
|
Medium::close(fSubsessionsHead);
|
|
fSubsessionsHead = fSubsessionsTail = NULL;
|
|
fSubsessionCounter = 0;
|
|
}
|
|
|
|
Boolean ServerMediaSession::isServerMediaSession() const {
|
|
return True;
|
|
}
|
|
|
|
char* ServerMediaSession::generateSDPDescription() {
|
|
AddressString ipAddressStr(ourIPAddress(envir()));
|
|
unsigned ipAddressStrSize = strlen(ipAddressStr.val());
|
|
|
|
// For a SSM sessions, we need a "a=source-filter: incl ..." line also:
|
|
char* sourceFilterLine;
|
|
if (fIsSSM) {
|
|
// char const* const sourceFilterFmt =
|
|
// "a=source-filter: incl IN IP4 * %s\r\n"
|
|
// "a=rtcp-unicast: reflection\r\n";
|
|
// unsigned const sourceFilterFmtSize = strlen(sourceFilterFmt) + ipAddressStrSize + 1;
|
|
//
|
|
// sourceFilterLine = new char[sourceFilterFmtSize];
|
|
// sprintf(sourceFilterLine, sourceFilterFmt, ipAddressStr.val());
|
|
sourceFilterLine = strDup("");
|
|
} else {
|
|
sourceFilterLine = strDup("");
|
|
}
|
|
|
|
char* rangeLine = NULL; // for now
|
|
char* sdp = NULL; // for now
|
|
|
|
do {
|
|
// Count the lengths of each subsession's media-level SDP lines.
|
|
// (We do this first, because the call to "subsession->sdpLines()"
|
|
// causes correct subsession 'duration()'s to be calculated later.)
|
|
unsigned sdpLength = 0;
|
|
ServerMediaSubsession* subsession;
|
|
for (subsession = fSubsessionsHead; subsession != NULL;
|
|
subsession = subsession->fNext) {
|
|
char const* sdpLines = subsession->sdpLines();
|
|
if (sdpLines == NULL) continue; // the media's not available
|
|
sdpLength += strlen(sdpLines);
|
|
}
|
|
if (sdpLength == 0) break; // the session has no usable subsessions
|
|
|
|
// Unless subsessions have differing durations, we also have a "a=range:" line:
|
|
float dur = duration();
|
|
if (dur == 0.0) {
|
|
rangeLine = strDup("a=range:npt=0-\r\n");
|
|
} else if (dur > 0.0) {
|
|
char buf[100];
|
|
sprintf(buf, "a=range:npt=0-%.3f\r\n", dur);
|
|
rangeLine = strDup(buf);
|
|
} else { // subsessions have differing durations, so "a=range:" lines go there
|
|
rangeLine = strDup("");
|
|
}
|
|
|
|
char const* const sdpPrefixFmt =
|
|
"v=0\r\n"
|
|
"o=- %ld%06ld %d IN IP4 %s\r\n"
|
|
"s=%s\r\n"
|
|
"i=%s\r\n"
|
|
"t=0 0\r\n"
|
|
"a=tool:%s%s\r\n"
|
|
"a=recvonly\r\n"
|
|
"a=control:*\r\n"
|
|
"%s"
|
|
"%s"
|
|
"a=x-qt-text-nam:%s\r\n"
|
|
"a=x-qt-text-inf:%s\r\n"
|
|
"%s";
|
|
sdpLength += strlen(sdpPrefixFmt)
|
|
+ 20 + 6 + 20 + ipAddressStrSize
|
|
+ strlen(fDescriptionSDPString)
|
|
+ strlen(fInfoSDPString)
|
|
+ strlen(libNameStr) + strlen(libVersionStr)
|
|
+ strlen(sourceFilterLine)
|
|
+ strlen(rangeLine)
|
|
+ strlen(fDescriptionSDPString)
|
|
+ strlen(fInfoSDPString)
|
|
+ strlen(fMiscSDPLines);
|
|
sdpLength += 1000; // in case the length of the "subsession->sdpLines()" calls below change
|
|
sdp = new char[sdpLength];
|
|
if (sdp == NULL) break;
|
|
|
|
// Generate the SDP prefix (session-level lines):
|
|
snprintf(sdp, sdpLength, sdpPrefixFmt,
|
|
fCreationTime.tv_sec, fCreationTime.tv_usec, // o= <session id>
|
|
1, // o= <version> // (needs to change if params are modified)
|
|
ipAddressStr.val(), // o= <address>
|
|
fDescriptionSDPString, // s= <description>
|
|
fInfoSDPString, // i= <info>
|
|
libNameStr, libVersionStr, // a=tool:
|
|
sourceFilterLine, // a=source-filter: incl (if a SSM session)
|
|
rangeLine, // a=range: line
|
|
fDescriptionSDPString, // a=x-qt-text-nam: line
|
|
fInfoSDPString, // a=x-qt-text-inf: line
|
|
fMiscSDPLines); // miscellaneous session SDP lines (if any)
|
|
|
|
// Then, add the (media-level) lines for each subsession:
|
|
char* mediaSDP = sdp;
|
|
for (subsession = fSubsessionsHead; subsession != NULL;
|
|
subsession = subsession->fNext) {
|
|
unsigned mediaSDPLength = strlen(mediaSDP);
|
|
mediaSDP += mediaSDPLength;
|
|
sdpLength -= mediaSDPLength;
|
|
if (sdpLength <= 1) break; // the SDP has somehow become too long
|
|
|
|
char const* sdpLines = subsession->sdpLines();
|
|
if (sdpLines != NULL) snprintf(mediaSDP, sdpLength, "%s", sdpLines);
|
|
}
|
|
} while (0);
|
|
|
|
delete[] rangeLine; delete[] sourceFilterLine;
|
|
return sdp;
|
|
}
|
|
|
|
|
|
////////// ServerMediaSubsessionIterator //////////
|
|
|
|
ServerMediaSubsessionIterator
|
|
::ServerMediaSubsessionIterator(ServerMediaSession& session)
|
|
: fOurSession(session) {
|
|
reset();
|
|
}
|
|
|
|
ServerMediaSubsessionIterator::~ServerMediaSubsessionIterator() {
|
|
}
|
|
|
|
ServerMediaSubsession* ServerMediaSubsessionIterator::next() {
|
|
ServerMediaSubsession* result = fNextPtr;
|
|
|
|
if (fNextPtr != NULL) fNextPtr = fNextPtr->fNext;
|
|
|
|
return result;
|
|
}
|
|
|
|
void ServerMediaSubsessionIterator::reset() {
|
|
fNextPtr = fOurSession.fSubsessionsHead;
|
|
}
|
|
|
|
|
|
////////// ServerMediaSubsession //////////
|
|
|
|
ServerMediaSubsession::ServerMediaSubsession(UsageEnvironment& env)
|
|
: Medium(env),
|
|
fParentSession(NULL), fServerAddressForSDP(0), fPortNumForSDP(0),
|
|
fNext(NULL), fTrackNumber(0), fTrackId(NULL) {
|
|
}
|
|
|
|
ServerMediaSubsession::~ServerMediaSubsession() {
|
|
delete[] (char*)fTrackId;
|
|
Medium::close(fNext);
|
|
}
|
|
|
|
char const* ServerMediaSubsession::trackId() {
|
|
if (fTrackNumber == 0) return NULL; // not yet in a ServerMediaSession
|
|
|
|
if (fTrackId == NULL) {
|
|
char buf[100];
|
|
sprintf(buf, "track%d", fTrackNumber);
|
|
fTrackId = strDup(buf);
|
|
}
|
|
return fTrackId;
|
|
}
|
|
|
|
void ServerMediaSubsession::pauseStream(unsigned /*clientSessionId*/,
|
|
void* /*streamToken*/) {
|
|
// default implementation: do nothing
|
|
}
|
|
void ServerMediaSubsession::seekStream(unsigned /*clientSessionId*/,
|
|
void* /*streamToken*/, double& /*seekNPT*/, double /*streamDuration*/, u_int64_t& numBytes) {
|
|
// default implementation: do nothing
|
|
numBytes = 0;
|
|
}
|
|
void ServerMediaSubsession::seekStream(unsigned /*clientSessionId*/,
|
|
void* /*streamToken*/, 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 ServerMediaSubsession::nullSeekStream(unsigned /*clientSessionId*/, void* /*streamToken*/,
|
|
double streamEndTime, u_int64_t& numBytes) {
|
|
// default implementation: do nothing
|
|
numBytes = 0;
|
|
}
|
|
void ServerMediaSubsession::setStreamScale(unsigned /*clientSessionId*/,
|
|
void* /*streamToken*/, float /*scale*/) {
|
|
// default implementation: do nothing
|
|
}
|
|
float ServerMediaSubsession::getCurrentNPT(void* /*streamToken*/) {
|
|
// default implementation: return 0.0
|
|
return 0.0;
|
|
}
|
|
FramedSource* ServerMediaSubsession::getStreamSource(void* /*streamToken*/) {
|
|
// default implementation: return NULL
|
|
return NULL;
|
|
}
|
|
void ServerMediaSubsession::deleteStream(unsigned /*clientSessionId*/,
|
|
void*& /*streamToken*/) {
|
|
// default implementation: do nothing
|
|
}
|
|
|
|
void ServerMediaSubsession::testScaleFactor(float& scale) {
|
|
// default implementation: Support scale = 1 only
|
|
scale = 1;
|
|
}
|
|
|
|
float ServerMediaSubsession::duration() const {
|
|
// default implementation: assume an unbounded session:
|
|
return 0.0;
|
|
}
|
|
|
|
void ServerMediaSubsession::getAbsoluteTimeRange(char*& absStartTime, char*& absEndTime) const {
|
|
// default implementation: We don't support seeking by 'absolute' time, so indicate this by setting both parameters to NULL:
|
|
absStartTime = absEndTime = NULL;
|
|
}
|
|
|
|
void ServerMediaSubsession::setServerAddressAndPortForSDP(netAddressBits addressBits,
|
|
portNumBits portBits) {
|
|
fServerAddressForSDP = addressBits;
|
|
fPortNumForSDP = portBits;
|
|
}
|
|
|
|
char const*
|
|
ServerMediaSubsession::rangeSDPLine() const {
|
|
// First, check for the special case where we support seeking by 'absolute' time:
|
|
char* absStart = NULL; char* absEnd = NULL;
|
|
getAbsoluteTimeRange(absStart, absEnd);
|
|
if (absStart != NULL) {
|
|
char buf[100];
|
|
|
|
if (absEnd != NULL) {
|
|
sprintf(buf, "a=range:clock=%s-%s\r\n", absStart, absEnd);
|
|
} else {
|
|
sprintf(buf, "a=range:clock=%s-\r\n", absStart);
|
|
}
|
|
return strDup(buf);
|
|
}
|
|
|
|
if (fParentSession == NULL) return NULL;
|
|
|
|
// If all of our parent's subsessions have the same duration
|
|
// (as indicated by "fParentSession->duration() >= 0"), there's no "a=range:" line:
|
|
if (fParentSession->duration() >= 0.0) return strDup("");
|
|
|
|
// Use our own duration for a "a=range:" line:
|
|
float ourDuration = duration();
|
|
if (ourDuration == 0.0) {
|
|
return strDup("a=range:npt=0-\r\n");
|
|
} else {
|
|
char buf[100];
|
|
sprintf(buf, "a=range:npt=0-%.3f\r\n", ourDuration);
|
|
return strDup(buf);
|
|
}
|
|
}
|