/********** 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 3 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-2020 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 #include #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 = "LIVE555 Streaming Media 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()); } 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=type:broadcast\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= 1, // o= // (needs to change if params are modified) ipAddressStr.val(), // o=
fDescriptionSDPString, // s= fInfoSDPString, // i= 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); } }