/********** 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 data structure that represents a session that consists of // potentially multiple (audio and/or video) sub-sessions // Implementation //#include "liveMedia.hh" #include "Locale.hh" #include "GroupsockHelper.hh" #include ////////// MediaSession ////////// MediaSession* MediaSession::createNew(UsageEnvironment& env, char const* sdpDescription) { MediaSession* newSession = new MediaSession(env); if (newSession != NULL) { if (!newSession->initializeWithSDP(sdpDescription)) { delete newSession; return NULL; } } return newSession; } Boolean MediaSession::lookupByName(UsageEnvironment& env, char const* instanceName, MediaSession*& resultSession) { resultSession = NULL; // unless we succeed Medium* medium; if (!Medium::lookupByName(env, instanceName, medium)) return False; if (!medium->isMediaSession()) { env.setResultMsg(instanceName, " is not a 'MediaSession' object"); return False; } resultSession = (MediaSession*)medium; return True; } MediaSession::MediaSession(UsageEnvironment& env) : Medium(env), fSubsessionsHead(NULL), fSubsessionsTail(NULL), fConnectionEndpointName(NULL), fMaxPlayStartTime(0.0f), fMaxPlayEndTime(0.0f), fAbsStartTime(NULL), fAbsEndTime(NULL), fScale(1.0f), fSpeed(1.0f), fMediaSessionType(NULL), fSessionName(NULL), fSessionDescription(NULL), fControlPath(NULL) { fSourceFilterAddr.s_addr = 0; // Get our host name, and use this for the RTCP CNAME: const unsigned maxCNAMElen = 100; char CNAME[maxCNAMElen+1]; #ifndef CRIS gethostname((char*)CNAME, maxCNAMElen); #else // "gethostname()" isn't defined for this platform sprintf(CNAME, "unknown host %d", (unsigned)(our_random()*0x7FFFFFFF)); #endif CNAME[maxCNAMElen] = '\0'; // just in case fCNAME = strDup(CNAME); } MediaSession::~MediaSession() { delete fSubsessionsHead; delete[] fCNAME; delete[] fConnectionEndpointName; delete[] fAbsStartTime; delete[] fAbsEndTime; delete[] fMediaSessionType; delete[] fSessionName; delete[] fSessionDescription; delete[] fControlPath; } Boolean MediaSession::isMediaSession() const { return True; } MediaSubsession* MediaSession::createNewMediaSubsession() { // default implementation: return new MediaSubsession(*this); } Boolean MediaSession::initializeWithSDP(char const* sdpDescription) { if (sdpDescription == NULL) return False; // Begin by processing all SDP lines until we see the first "m=" char const* sdpLine = sdpDescription; char const* nextSDPLine; while (1) { if (!parseSDPLine(sdpLine, nextSDPLine)) return False; //##### We should really check for the correct SDP version (v=0) if (sdpLine[0] == 'm') break; sdpLine = nextSDPLine; if (sdpLine == NULL) break; // there are no m= lines at all // Check for various special SDP lines that we understand: if (parseSDPLine_s(sdpLine)) continue; if (parseSDPLine_i(sdpLine)) continue; if (parseSDPLine_c(sdpLine)) continue; if (parseSDPAttribute_control(sdpLine)) continue; if (parseSDPAttribute_range(sdpLine)) continue; if (parseSDPAttribute_type(sdpLine)) continue; if (parseSDPAttribute_source_filter(sdpLine)) continue; } while (sdpLine != NULL) { // We have a "m=" line, representing a new sub-session: MediaSubsession* subsession = createNewMediaSubsession(); if (subsession == NULL) { envir().setResultMsg("Unable to create new MediaSubsession"); return False; } // Parse the line as "m= RTP/AVP " // or "m= / RTP/AVP " // (Should we be checking for >1 payload format number here?)##### char* mediumName = strDupSize(sdpLine); // ensures we have enough space char const* protocolName = NULL; unsigned payloadFormat; if ((sscanf(sdpLine, "m=%s %hu RTP/AVP %u", mediumName, &subsession->fClientPortNum, &payloadFormat) == 3 || sscanf(sdpLine, "m=%s %hu/%*u RTP/AVP %u", mediumName, &subsession->fClientPortNum, &payloadFormat) == 3) && payloadFormat <= 127) { protocolName = "RTP"; } else if ((sscanf(sdpLine, "m=%s %hu UDP %u", mediumName, &subsession->fClientPortNum, &payloadFormat) == 3 || sscanf(sdpLine, "m=%s %hu udp %u", mediumName, &subsession->fClientPortNum, &payloadFormat) == 3 || sscanf(sdpLine, "m=%s %hu RAW/RAW/UDP %u", mediumName, &subsession->fClientPortNum, &payloadFormat) == 3) && payloadFormat <= 127) { // This is a RAW UDP source protocolName = "UDP"; } else { // This "m=" line is bad; output an error message saying so: char* sdpLineStr; if (nextSDPLine == NULL) { sdpLineStr = (char*)sdpLine; } else { sdpLineStr = strDup(sdpLine); sdpLineStr[nextSDPLine-sdpLine] = '\0'; } envir() << "Bad SDP \"m=\" line: " << sdpLineStr << "\n"; if (sdpLineStr != (char*)sdpLine) delete[] sdpLineStr; delete[] mediumName; delete subsession; // Skip the following SDP lines, up until the next "m=": while (1) { sdpLine = nextSDPLine; if (sdpLine == NULL) break; // we've reached the end if (!parseSDPLine(sdpLine, nextSDPLine)) return False; if (sdpLine[0] == 'm') break; // we've reached the next subsession } continue; } // Insert this subsession at the end of the list: if (fSubsessionsTail == NULL) { fSubsessionsHead = fSubsessionsTail = subsession; } else { fSubsessionsTail->setNext(subsession); fSubsessionsTail = subsession; } subsession->serverPortNum = subsession->fClientPortNum; // by default char const* mStart = sdpLine; subsession->fSavedSDPLines = strDup(mStart); subsession->fMediumName = strDup(mediumName); delete[] mediumName; subsession->fProtocolName = strDup(protocolName); subsession->fRTPPayloadFormat = payloadFormat; // Process the following SDP lines, up until the next "m=": while (1) { sdpLine = nextSDPLine; if (sdpLine == NULL) break; // we've reached the end if (!parseSDPLine(sdpLine, nextSDPLine)) return False; if (sdpLine[0] == 'm') break; // we've reached the next subsession // Check for various special SDP lines that we understand: if (subsession->parseSDPLine_c(sdpLine)) continue; if (subsession->parseSDPLine_b(sdpLine)) continue; if (subsession->parseSDPAttribute_rtpmap(sdpLine)) continue; if (subsession->parseSDPAttribute_rtcpmux(sdpLine)) continue; if (subsession->parseSDPAttribute_control(sdpLine)) continue; if (subsession->parseSDPAttribute_range(sdpLine)) continue; if (subsession->parseSDPAttribute_fmtp(sdpLine)) continue; if (subsession->parseSDPAttribute_source_filter(sdpLine)) continue; if (subsession->parseSDPAttribute_x_dimensions(sdpLine)) continue; if (subsession->parseSDPAttribute_framerate(sdpLine)) continue; // (Later, check for malformed lines, and other valid SDP lines#####) } if (sdpLine != NULL) subsession->fSavedSDPLines[sdpLine-mStart] = '\0'; // If we don't yet know the codec name, try looking it up from the // list of static payload types: if (subsession->fCodecName == NULL) { subsession->fCodecName = lookupPayloadFormat(subsession->fRTPPayloadFormat, subsession->fRTPTimestampFrequency, subsession->fNumChannels); if (subsession->fCodecName == NULL) { char typeStr[20]; sprintf(typeStr, "%d", subsession->fRTPPayloadFormat); envir().setResultMsg("Unknown codec name for RTP payload type ", typeStr); return False; } } // If we don't yet know this subsession's RTP timestamp frequency // (because it uses a dynamic payload type and the corresponding // SDP "rtpmap" attribute erroneously didn't specify it), // then guess it now: if (subsession->fRTPTimestampFrequency == 0) { subsession->fRTPTimestampFrequency = guessRTPTimestampFrequency(subsession->fMediumName, subsession->fCodecName); } } return True; } Boolean MediaSession::parseSDPLine(char const* inputLine, char const*& nextLine){ // Begin by finding the start of the next line (if any): nextLine = NULL; for (char const* ptr = inputLine; *ptr != '\0'; ++ptr) { if (*ptr == '\r' || *ptr == '\n') { // We found the end of the line ++ptr; while (*ptr == '\r' || *ptr == '\n') ++ptr; nextLine = ptr; if (nextLine[0] == '\0') nextLine = NULL; // special case for end break; } } // Then, check that this line is a SDP line of the form = // (However, we also accept blank lines in the input.) if (inputLine[0] == '\r' || inputLine[0] == '\n') return True; if (strlen(inputLine) < 2 || inputLine[1] != '=' || inputLine[0] < 'a' || inputLine[0] > 'z') { envir().setResultMsg("Invalid SDP line: ", inputLine); return False; } return True; } static char* parseCLine(char const* sdpLine) { char* resultStr = NULL; char* buffer = strDupSize(sdpLine); // ensures we have enough space if (sscanf(sdpLine, "c=IN IP4 %[^/\r\n]", buffer) == 1) { // Later, handle the optional / and / ##### resultStr = strDup(buffer); } delete[] buffer; return resultStr; } Boolean MediaSession::parseSDPLine_s(char const* sdpLine) { // Check for "s=" line char* buffer = strDupSize(sdpLine); Boolean parseSuccess = False; if (sscanf(sdpLine, "s=%[^\r\n]", buffer) == 1) { delete[] fSessionName; fSessionName = strDup(buffer); parseSuccess = True; } delete[] buffer; return parseSuccess; } Boolean MediaSession::parseSDPLine_i(char const* sdpLine) { // Check for "i=" line char* buffer = strDupSize(sdpLine); Boolean parseSuccess = False; if (sscanf(sdpLine, "i=%[^\r\n]", buffer) == 1) { delete[] fSessionDescription; fSessionDescription = strDup(buffer); parseSuccess = True; } delete[] buffer; return parseSuccess; } Boolean MediaSession::parseSDPLine_c(char const* sdpLine) { // Check for "c=IN IP4 " // or "c=IN IP4 /" // (Later, do something with also #####) char* connectionEndpointName = parseCLine(sdpLine); if (connectionEndpointName != NULL) { delete[] fConnectionEndpointName; fConnectionEndpointName = connectionEndpointName; return True; } return False; } Boolean MediaSession::parseSDPAttribute_type(char const* sdpLine) { // Check for a "a=type:broadcast|meeting|moderated|test|H.332|recvonly" line: Boolean parseSuccess = False; char* buffer = strDupSize(sdpLine); if (sscanf(sdpLine, "a=type: %[^ ]", buffer) == 1) { delete[] fMediaSessionType; fMediaSessionType = strDup(buffer); parseSuccess = True; } delete[] buffer; return parseSuccess; } Boolean MediaSession::parseSDPAttribute_control(char const* sdpLine) { // Check for a "a=control:" line: Boolean parseSuccess = False; char* controlPath = strDupSize(sdpLine); // ensures we have enough space if (sscanf(sdpLine, "a=control: %s", controlPath) == 1) { parseSuccess = True; delete[] fControlPath; fControlPath = strDup(controlPath); } delete[] controlPath; return parseSuccess; } static Boolean parseRangeAttribute(char const* sdpLine, double& startTime, double& endTime) { return sscanf(sdpLine, "a=range: npt = %lg - %lg", &startTime, &endTime) == 2; } static Boolean parseRangeAttribute(char const* sdpLine, char*& absStartTime, char*& absEndTime) { size_t len = strlen(sdpLine) + 1; char* as = new char[len]; char* ae = new char[len]; int sscanfResult = sscanf(sdpLine, "a=range: clock = %[^-\r\n]-%[^\r\n]", as, ae); if (sscanfResult == 2) { absStartTime = as; absEndTime = ae; } else if (sscanfResult == 1) { absStartTime = as; delete[] ae; } else { delete[] as; delete[] ae; return False; } return True; } Boolean MediaSession::parseSDPAttribute_range(char const* sdpLine) { // Check for a "a=range:npt=-" line: // (Later handle other kinds of "a=range" attributes also???#####) Boolean parseSuccess = False; double playStartTime; double playEndTime; if (parseRangeAttribute(sdpLine, playStartTime, playEndTime)) { parseSuccess = True; if (playStartTime > fMaxPlayStartTime) { fMaxPlayStartTime = playStartTime; } if (playEndTime > fMaxPlayEndTime) { fMaxPlayEndTime = playEndTime; } } else if (parseRangeAttribute(sdpLine, _absStartTime(), _absEndTime())) { parseSuccess = True; } return parseSuccess; } static Boolean parseSourceFilterAttribute(char const* sdpLine, struct in_addr& sourceAddr) { // Check for a "a=source-filter:incl IN IP4 " line. // Note: At present, we don't check that really matches // one of our multicast addresses. We also don't support more than // one ##### Boolean result = False; // until we succeed char* sourceName = strDupSize(sdpLine); // ensures we have enough space do { if (sscanf(sdpLine, "a=source-filter: incl IN IP4 %*s %s", sourceName) != 1) break; // Now, convert this name to an address, if we can: NetAddressList addresses(sourceName); if (addresses.numAddresses() == 0) break; netAddressBits sourceAddrBits = *(netAddressBits*)(addresses.firstAddress()->data()); if (sourceAddrBits == 0) break; sourceAddr.s_addr = sourceAddrBits; result = True; } while (0); delete[] sourceName; return result; } Boolean MediaSession ::parseSDPAttribute_source_filter(char const* sdpLine) { return parseSourceFilterAttribute(sdpLine, fSourceFilterAddr); } char* MediaSession::lookupPayloadFormat(unsigned char rtpPayloadType, unsigned& freq, unsigned& nCh) { // Look up the codec name and timestamp frequency for known (static) // RTP payload formats. char const* temp = NULL; switch (rtpPayloadType) { case 0: {temp = "PCMU"; freq = 8000; nCh = 1; break;} case 2: {temp = "G726-32"; freq = 8000; nCh = 1; break;} case 3: {temp = "GSM"; freq = 8000; nCh = 1; break;} case 4: {temp = "G723"; freq = 8000; nCh = 1; break;} case 5: {temp = "DVI4"; freq = 8000; nCh = 1; break;} case 6: {temp = "DVI4"; freq = 16000; nCh = 1; break;} case 7: {temp = "LPC"; freq = 8000; nCh = 1; break;} case 8: {temp = "PCMA"; freq = 8000; nCh = 1; break;} case 9: {temp = "G722"; freq = 8000; nCh = 1; break;} case 10: {temp = "L16"; freq = 44100; nCh = 2; break;} case 11: {temp = "L16"; freq = 44100; nCh = 1; break;} case 12: {temp = "QCELP"; freq = 8000; nCh = 1; break;} case 14: {temp = "MPA"; freq = 90000; nCh = 1; break;} // 'number of channels' is actually encoded in the media stream case 15: {temp = "G728"; freq = 8000; nCh = 1; break;} case 16: {temp = "DVI4"; freq = 11025; nCh = 1; break;} case 17: {temp = "DVI4"; freq = 22050; nCh = 1; break;} case 18: {temp = "G729"; freq = 8000; nCh = 1; break;} case 25: {temp = "CELB"; freq = 90000; nCh = 1; break;} case 26: {temp = "JPEG"; freq = 90000; nCh = 1; break;} case 28: {temp = "NV"; freq = 90000; nCh = 1; break;} case 31: {temp = "H261"; freq = 90000; nCh = 1; break;} case 32: {temp = "MPV"; freq = 90000; nCh = 1; break;} case 33: {temp = "MP2T"; freq = 90000; nCh = 1; break;} case 34: {temp = "H263"; freq = 90000; nCh = 1; break;} }; return strDup(temp); } unsigned MediaSession::guessRTPTimestampFrequency(char const* mediumName, char const* codecName) { // By default, we assume that audio sessions use a frequency of 8000, // video sessions use a frequency of 90000, // and text sessions use a frequency of 1000. // Begin by checking for known exceptions to this rule // (where the frequency is known unambiguously (e.g., not like "DVI4")) if (strcmp(codecName, "L16") == 0) return 44100; if (strcmp(codecName, "MPA") == 0 || strcmp(codecName, "MPA-ROBUST") == 0 || strcmp(codecName, "X-MP3-DRAFT-00") == 0) return 90000; // Now, guess default values: if (strcmp(mediumName, "video") == 0) return 90000; else if (strcmp(mediumName, "text") == 0) return 1000; return 8000; // for "audio", and any other medium } char* MediaSession::absStartTime() const { if (fAbsStartTime != NULL) return fAbsStartTime; // If a subsession has an 'absolute' start time, then use that: MediaSubsessionIterator iter(*this); MediaSubsession* subsession; while ((subsession = iter.next()) != NULL) { if (subsession->_absStartTime() != NULL) return subsession->_absStartTime(); } return NULL; } char* MediaSession::absEndTime() const { if (fAbsEndTime != NULL) return fAbsEndTime; // If a subsession has an 'absolute' end time, then use that: MediaSubsessionIterator iter(*this); MediaSubsession* subsession; while ((subsession = iter.next()) != NULL) { if (subsession->_absEndTime() != NULL) return subsession->_absEndTime(); } return NULL; } Boolean MediaSession ::initiateByMediaType(char const* mimeType, MediaSubsession*& resultSubsession, int useSpecialRTPoffset) { // Look through this session's subsessions for media that match "mimeType" resultSubsession = NULL; MediaSubsessionIterator iter(*this); MediaSubsession* subsession; while ((subsession = iter.next()) != NULL) { Boolean wasAlreadyInitiated = subsession->readSource() != NULL; if (!wasAlreadyInitiated) { // Try to create a source for this subsession: if (!subsession->initiate(useSpecialRTPoffset)) return False; } // Make sure the source's MIME type is one that we handle: if (strcmp(subsession->readSource()->MIMEtype(), mimeType) != 0) { if (!wasAlreadyInitiated) subsession->deInitiate(); continue; } resultSubsession = subsession; break; // use this } if (resultSubsession == NULL) { envir().setResultMsg("Session has no usable media subsession"); return False; } return True; } ////////// MediaSubsessionIterator ////////// MediaSubsessionIterator::MediaSubsessionIterator(MediaSession const& session) : fOurSession(session) { reset(); } MediaSubsessionIterator::~MediaSubsessionIterator() { } MediaSubsession* MediaSubsessionIterator::next() { MediaSubsession* result = fNextPtr; if (fNextPtr != NULL) fNextPtr = fNextPtr->fNext; return result; } void MediaSubsessionIterator::reset() { fNextPtr = fOurSession.fSubsessionsHead; } ////////// SDPAttribute definition ////////// class SDPAttribute { public: SDPAttribute(char const* strValue, Boolean valueIsHexadecimal); virtual ~SDPAttribute(); char const* strValue() const { return fStrValue; } char const* strValueToLower() const { return fStrValueToLower; } int intValue() const { return fIntValue; } Boolean valueIsHexadecimal() const { return fValueIsHexadecimal; } private: char* fStrValue; char* fStrValueToLower; int fIntValue; Boolean fValueIsHexadecimal; }; ////////// MediaSubsession ////////// MediaSubsession::MediaSubsession(MediaSession& parent) : serverPortNum(0), sink(NULL), miscPtr(NULL), fParent(parent), fNext(NULL), fConnectionEndpointName(NULL), fClientPortNum(0), fRTPPayloadFormat(0xFF), fSavedSDPLines(NULL), fMediumName(NULL), fCodecName(NULL), fProtocolName(NULL), fRTPTimestampFrequency(0), fMultiplexRTCPWithRTP(False), fControlPath(NULL), fSourceFilterAddr(parent.sourceFilterAddr()), fBandwidth(0), fPlayStartTime(0.0), fPlayEndTime(0.0), fAbsStartTime(NULL), fAbsEndTime(NULL), fVideoWidth(0), fVideoHeight(0), fVideoFPS(0), fNumChannels(1), fScale(1.0f), fNPT_PTS_Offset(0.0f), fAttributeTable(HashTable::create(STRING_HASH_KEYS)), fRTPSocket(NULL), fRTCPSocket(NULL), fRTPSource(NULL), fRTCPInstance(NULL), fReadSource(NULL), fReceiveRawMP3ADUs(False), fReceiveRawJPEGFrames(False), fSessionId(NULL) { rtpInfo.seqNum = 0; rtpInfo.timestamp = 0; rtpInfo.infoIsNew = False; // A few attributes have unusual default values. Set these now: setAttribute("profile-level-id", "0", True/*value is hexadecimal*/); // used with "video/H264" // This won't work for MPEG-4 (unless the value is <10), because for MPEG-4, the value // is assumed to be a decimal string, not a hexadecimal string. NEED TO FIX ##### setAttribute("profile-id", "1"); // used with "video/H265" setAttribute("level-id", "93"); // used with "video/H265" setAttribute("interop-constraints", "B00000000000"); // used with "video/H265" } MediaSubsession::~MediaSubsession() { deInitiate(); delete[] fConnectionEndpointName; delete[] fSavedSDPLines; delete[] fMediumName; delete[] fCodecName; delete[] fProtocolName; delete[] fControlPath; delete[] fAbsStartTime; delete[] fAbsEndTime; delete[] fSessionId; // Empty and delete our 'attributes table': SDPAttribute* attr; while ((attr = (SDPAttribute*)fAttributeTable->RemoveNext()) != NULL) { delete attr; } delete fAttributeTable; delete fNext; } void MediaSubsession::addFilter(FramedFilter* filter){ fReadSource = filter; } double MediaSubsession::playStartTime() const { if (fPlayStartTime > 0) return fPlayStartTime; return fParent.playStartTime(); } double MediaSubsession::playEndTime() const { if (fPlayEndTime > 0) return fPlayEndTime; return fParent.playEndTime(); } char* MediaSubsession::absStartTime() const { if (fAbsStartTime != NULL) return fAbsStartTime; return fParent.absStartTime(); } char* MediaSubsession::absEndTime() const { if (fAbsEndTime != NULL) return fAbsEndTime; return fParent.absEndTime(); } static Boolean const honorSDPPortChoice #ifdef IGNORE_UNICAST_SDP_PORTS = False; #else = True; #endif Boolean MediaSubsession::initiate(int useSpecialRTPoffset) { if (fReadSource != NULL) return True; // has already been initiated do { if (fCodecName == NULL) { env().setResultMsg("Codec is unspecified"); break; } // Create RTP and RTCP 'Groupsocks' on which to receive incoming data. // (Groupsocks will work even for unicast addresses) struct in_addr tempAddr; tempAddr.s_addr = connectionEndpointAddress(); // This could get changed later, as a result of a RTSP "SETUP" if (fClientPortNum != 0 && (honorSDPPortChoice || IsMulticastAddress(tempAddr.s_addr))) { // The sockets' port numbers were specified for us. Use these: Boolean const protocolIsRTP = strcmp(fProtocolName, "RTP") == 0; if (protocolIsRTP && !fMultiplexRTCPWithRTP) { fClientPortNum = fClientPortNum&~1; // use an even-numbered port for RTP, and the next (odd-numbered) port for RTCP } if (isSSM()) { fRTPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, fClientPortNum); } else { fRTPSocket = new Groupsock(env(), tempAddr, fClientPortNum, 255); } if (fRTPSocket == NULL) { env().setResultMsg("Failed to create RTP socket"); break; } if (protocolIsRTP) { if (fMultiplexRTCPWithRTP) { // Use the RTP 'groupsock' object for RTCP as well: fRTCPSocket = fRTPSocket; } else { // Set our RTCP port to be the RTP port + 1: portNumBits const rtcpPortNum = fClientPortNum|1; if (isSSM()) { fRTCPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, rtcpPortNum); } else { fRTCPSocket = new Groupsock(env(), tempAddr, rtcpPortNum, 255); } } } } else { // Port numbers were not specified in advance, so we use ephemeral port numbers. // Create sockets until we get a port-number pair (even: RTP; even+1: RTCP). // (However, if we're multiplexing RTCP with RTP, then we create only one socket, // and the port number can be even or odd.) // We need to make sure that we don't keep trying to use the same bad port numbers over // and over again, so we store bad sockets in a table, and delete them all when we're done. HashTable* socketHashTable = HashTable::create(ONE_WORD_HASH_KEYS); if (socketHashTable == NULL) break; Boolean success = False; NoReuse dummy(env()); // ensures that our new ephemeral port number won't be one that's already in use while (1) { // Create a new socket: if (isSSM()) { fRTPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, 0); } else { fRTPSocket = new Groupsock(env(), tempAddr, 0, 255); } if (fRTPSocket == NULL) { env().setResultMsg("MediaSession::initiate(): unable to create RTP and RTCP sockets"); break; } // Get the client port number: Port clientPort(0); if (!getSourcePort(env(), fRTPSocket->socketNum(), clientPort)) { break; } fClientPortNum = ntohs(clientPort.num()); if (fMultiplexRTCPWithRTP) { // Use this RTP 'groupsock' object for RTCP as well: fRTCPSocket = fRTPSocket; success = True; break; } // To be usable for RTP, the client port number must be even: if ((fClientPortNum&1) != 0) { // it's odd // Record this socket in our table, and keep trying: unsigned key = (unsigned)fClientPortNum; Groupsock* existing = (Groupsock*)socketHashTable->Add((char const*)key, fRTPSocket); delete existing; // in case it wasn't NULL continue; } // Make sure we can use the next (i.e., odd) port number, for RTCP: portNumBits rtcpPortNum = fClientPortNum|1; if (isSSM()) { fRTCPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, rtcpPortNum); } else { fRTCPSocket = new Groupsock(env(), tempAddr, rtcpPortNum, 255); } if (fRTCPSocket != NULL && fRTCPSocket->socketNum() >= 0) { // Success! Use these two sockets. success = True; break; } else { // We couldn't create the RTCP socket (perhaps that port number's already in use elsewhere?). delete fRTCPSocket; fRTCPSocket = NULL; // Record the first socket in our table, and keep trying: unsigned key = (unsigned)fClientPortNum; Groupsock* existing = (Groupsock*)socketHashTable->Add((char const*)key, fRTPSocket); delete existing; // in case it wasn't NULL continue; } } // Clean up the socket hash table (and contents): Groupsock* oldGS; while ((oldGS = (Groupsock*)socketHashTable->RemoveNext()) != NULL) { delete oldGS; } delete socketHashTable; if (!success) break; // a fatal error occurred trying to create the RTP and RTCP sockets; we can't continue } // Try to use a big receive buffer for RTP - at least 0.1 second of // specified bandwidth and at least 50 KB unsigned rtpBufSize = fBandwidth * 25 / 2; // 1 kbps * 0.1 s = 12.5 bytes if (rtpBufSize < 50 * 1024) rtpBufSize = 50 * 1024; increaseReceiveBufferTo(env(), fRTPSocket->socketNum(), rtpBufSize); if (isSSM() && fRTCPSocket != NULL) { // Special case for RTCP SSM: Send RTCP packets back to the source via unicast: fRTCPSocket->changeDestinationParameters(fSourceFilterAddr,0,~0); } // Create "fRTPSource" and "fReadSource": if (!createSourceObjects(useSpecialRTPoffset)) break; if (fReadSource == NULL) { env().setResultMsg("Failed to create read source"); break; } // Finally, create our RTCP instance. (It starts running automatically) if (fRTPSource != NULL && fRTCPSocket != NULL) { // If bandwidth is specified, use it and add 5% for RTCP overhead. // Otherwise make a guess at 500 kbps. unsigned totSessionBandwidth = fBandwidth ? fBandwidth + fBandwidth / 20 : 500; fRTCPInstance = RTCPInstance::createNew(env(), fRTCPSocket, totSessionBandwidth, (unsigned char const*) fParent.CNAME(), NULL /* we're a client */, fRTPSource); if (fRTCPInstance == NULL) { env().setResultMsg("Failed to create RTCP instance"); break; } } return True; } while (0); deInitiate(); fClientPortNum = 0; return False; } void MediaSubsession::deInitiate() { Medium::close(fRTCPInstance); fRTCPInstance = NULL; Medium::close(fReadSource); // this is assumed to also close fRTPSource fReadSource = NULL; fRTPSource = NULL; delete fRTPSocket; if (fRTCPSocket != fRTPSocket) delete fRTCPSocket; fRTPSocket = NULL; fRTCPSocket = NULL; } Boolean MediaSubsession::setClientPortNum(unsigned short portNum) { if (fReadSource != NULL) { env().setResultMsg("A read source has already been created"); return False; } fClientPortNum = portNum; return True; } char const* MediaSubsession::attrVal_str(char const* attrName) const { SDPAttribute* attr = (SDPAttribute*)(fAttributeTable->Lookup(attrName)); if (attr == NULL) return ""; return attr->strValue(); } char const* MediaSubsession::attrVal_strToLower(char const* attrName) const { SDPAttribute* attr = (SDPAttribute*)(fAttributeTable->Lookup(attrName)); if (attr == NULL) return ""; return attr->strValueToLower(); } unsigned MediaSubsession::attrVal_int(char const* attrName) const { SDPAttribute* attr = (SDPAttribute*)(fAttributeTable->Lookup(attrName)); if (attr == NULL) return 0; return attr->intValue(); } char const* MediaSubsession::fmtp_config() const { char const* result = attrVal_str("config"); if (result[0] == '\0') result = attrVal_str("configuration"); return result; } netAddressBits MediaSubsession::connectionEndpointAddress() const { do { // Get the endpoint name from with us, or our parent session: char const* endpointString = connectionEndpointName(); if (endpointString == NULL) { endpointString = parentSession().connectionEndpointName(); } if (endpointString == NULL) break; // Now, convert this name to an address, if we can: NetAddressList addresses(endpointString); if (addresses.numAddresses() == 0) break; return *(netAddressBits*)(addresses.firstAddress()->data()); } while (0); // No address known: return 0; } void MediaSubsession::setDestinations(netAddressBits defaultDestAddress) { // Get the destination address from the connection endpoint name // (This will be 0 if it's not known, in which case we use the default) netAddressBits destAddress = connectionEndpointAddress(); if (destAddress == 0) destAddress = defaultDestAddress; struct in_addr destAddr; destAddr.s_addr = destAddress; // The destination TTL remains unchanged: int destTTL = ~0; // means: don't change if (fRTPSocket != NULL) { Port destPort(serverPortNum); fRTPSocket->changeDestinationParameters(destAddr, destPort, destTTL); } if (fRTCPSocket != NULL && !isSSM() && !fMultiplexRTCPWithRTP) { // Note: For SSM sessions, the dest address for RTCP was already set. Port destPort(serverPortNum+1); fRTCPSocket->changeDestinationParameters(destAddr, destPort, destTTL); } } void MediaSubsession::setSessionId(char const* sessionId) { delete[] fSessionId; fSessionId = strDup(sessionId); } double MediaSubsession::getNormalPlayTime(struct timeval const& presentationTime) { if (rtpSource() == NULL || rtpSource()->timestampFrequency() == 0) return 0.0; // no RTP source, or bad freq! // First, check whether our "RTPSource" object has already been synchronized using RTCP. // If it hasn't, then - as a special case - we need to use the RTP timestamp to compute the NPT. if (!rtpSource()->hasBeenSynchronizedUsingRTCP()) { if (!rtpInfo.infoIsNew) return 0.0; // the "rtpInfo" structure has not been filled in u_int32_t timestampOffset = rtpSource()->curPacketRTPTimestamp() - rtpInfo.timestamp; double nptOffset = (timestampOffset/(double)(rtpSource()->timestampFrequency()))*scale(); double npt = playStartTime() + nptOffset; return npt; } else { // Common case: We have been synchronized using RTCP. This means that the "presentationTime" parameter // will be accurate, and so we should use this to compute the NPT. double ptsDouble = (double)(presentationTime.tv_sec + presentationTime.tv_usec/1000000.0); if (rtpInfo.infoIsNew) { // This is the first time we've been called with a synchronized presentation time since the "rtpInfo" // structure was last filled in. Use this "presentationTime" to compute "fNPT_PTS_Offset": if (seqNumLT(rtpSource()->curPacketRTPSeqNum(), rtpInfo.seqNum)) return -0.1; // sanity check; ignore old packets u_int32_t timestampOffset = rtpSource()->curPacketRTPTimestamp() - rtpInfo.timestamp; double nptOffset = (timestampOffset/(double)(rtpSource()->timestampFrequency()))*scale(); double npt = playStartTime() + nptOffset; fNPT_PTS_Offset = npt - ptsDouble*scale(); rtpInfo.infoIsNew = False; // for next time return npt; } else { // Use the precomputed "fNPT_PTS_Offset" to compute the NPT from the PTS: if (fNPT_PTS_Offset == 0.0) return 0.0; // error: The "rtpInfo" structure was apparently never filled in return (double)(ptsDouble*scale() + fNPT_PTS_Offset); } } } void MediaSubsession ::setAttribute(char const* name, char const* value, Boolean valueIsHexadecimal) { // Replace any existing attribute record with this name (except that the 'valueIsHexadecimal' // property will be inherited from it, if it exists). SDPAttribute* oldAttr = (SDPAttribute*)fAttributeTable->Lookup(name); if (oldAttr != NULL) { valueIsHexadecimal = oldAttr->valueIsHexadecimal(); fAttributeTable->Remove(name); delete oldAttr; } SDPAttribute* newAttr = new SDPAttribute(value, valueIsHexadecimal); (void)fAttributeTable->Add(name, newAttr); } Boolean MediaSubsession::parseSDPLine_c(char const* sdpLine) { // Check for "c=IN IP4 " // or "c=IN IP4 /" // (Later, do something with also #####) char* connectionEndpointName = parseCLine(sdpLine); if (connectionEndpointName != NULL) { delete[] fConnectionEndpointName; fConnectionEndpointName = connectionEndpointName; return True; } return False; } Boolean MediaSubsession::parseSDPLine_b(char const* sdpLine) { // Check for "b=:" line // RTP applications are expected to use bwtype="AS" return sscanf(sdpLine, "b=AS:%u", &fBandwidth) == 1; } Boolean MediaSubsession::parseSDPAttribute_rtpmap(char const* sdpLine) { // Check for a "a=rtpmap: /" line: // (Also check without the "/"; RealNetworks omits this) // Also check for a trailing "/". Boolean parseSuccess = False; unsigned rtpmapPayloadFormat; char* codecName = strDupSize(sdpLine); // ensures we have enough space unsigned rtpTimestampFrequency = 0; unsigned numChannels = 1; if (sscanf(sdpLine, "a=rtpmap: %u %[^/]/%u/%u", &rtpmapPayloadFormat, codecName, &rtpTimestampFrequency, &numChannels) == 4 || sscanf(sdpLine, "a=rtpmap: %u %[^/]/%u", &rtpmapPayloadFormat, codecName, &rtpTimestampFrequency) == 3 || sscanf(sdpLine, "a=rtpmap: %u %s", &rtpmapPayloadFormat, codecName) == 2) { parseSuccess = True; if (rtpmapPayloadFormat == fRTPPayloadFormat) { // This "rtpmap" matches our payload format, so set our // codec name and timestamp frequency: // (First, make sure the codec name is upper case) { Locale l("POSIX"); for (char* p = codecName; *p != '\0'; ++p) *p = toupper(*p); } delete[] fCodecName; fCodecName = strDup(codecName); fRTPTimestampFrequency = rtpTimestampFrequency; fNumChannels = numChannels; } } delete[] codecName; return parseSuccess; } Boolean MediaSubsession::parseSDPAttribute_rtcpmux(char const* sdpLine) { if (strncmp(sdpLine, "a=rtcp-mux", 10) == 0) { fMultiplexRTCPWithRTP = True; return True; } return False; } Boolean MediaSubsession::parseSDPAttribute_control(char const* sdpLine) { // Check for a "a=control:" line: Boolean parseSuccess = False; char* controlPath = strDupSize(sdpLine); // ensures we have enough space if (sscanf(sdpLine, "a=control: %s", controlPath) == 1) { parseSuccess = True; delete[] fControlPath; fControlPath = strDup(controlPath); } delete[] controlPath; return parseSuccess; } Boolean MediaSubsession::parseSDPAttribute_range(char const* sdpLine) { // Check for a "a=range:npt=-" line: // (Later handle other kinds of "a=range" attributes also???#####) Boolean parseSuccess = False; double playStartTime; double playEndTime; if (parseRangeAttribute(sdpLine, playStartTime, playEndTime)) { parseSuccess = True; if (playStartTime > fPlayStartTime) { fPlayStartTime = playStartTime; if (playStartTime > fParent.playStartTime()) { fParent.playStartTime() = playStartTime; } } if (playEndTime > fPlayEndTime) { fPlayEndTime = playEndTime; if (playEndTime > fParent.playEndTime()) { fParent.playEndTime() = playEndTime; } } } else if (parseRangeAttribute(sdpLine, _absStartTime(), _absEndTime())) { parseSuccess = True; } return parseSuccess; } Boolean MediaSubsession::parseSDPAttribute_fmtp(char const* sdpLine) { // Check for a "a=fmtp:" line: // Later: Check that payload format number matches; ##### do { if (strncmp(sdpLine, "a=fmtp:", 7) != 0) break; sdpLine += 7; while (isdigit(*sdpLine)) ++sdpLine; // The remaining "sdpLine" should be a sequence of // =; // or // ; // parameter assignments. Look at each of these. unsigned const sdpLineLen = strlen(sdpLine); char* nameStr = new char[sdpLineLen+1]; char* valueStr = new char[sdpLineLen+1]; while (*sdpLine != '\0' && *sdpLine != '\r' && *sdpLine != '\n') { int sscanfResult = sscanf(sdpLine, " %[^=; \t\r\n] = %[^; \t\r\n]", nameStr, valueStr); if (sscanfResult >= 1) { // or = // Convert to lower-case, to ease comparison: Locale l("POSIX"); for (char* c = nameStr; *c != '\0'; ++c) *c = tolower(*c); if (sscanfResult == 1) { // setAttribute(nameStr); } else { // = setAttribute(nameStr, valueStr); } } // Move to the next parameter assignment string: while (*sdpLine != '\0' && *sdpLine != '\r' && *sdpLine != '\n' && *sdpLine != ';') ++sdpLine; while (*sdpLine == ';') ++sdpLine; } delete[] nameStr; delete[] valueStr; return True; } while (0); return False; } Boolean MediaSubsession ::parseSDPAttribute_source_filter(char const* sdpLine) { return parseSourceFilterAttribute(sdpLine, fSourceFilterAddr); } Boolean MediaSubsession::parseSDPAttribute_x_dimensions(char const* sdpLine) { // Check for a "a=x-dimensions:," line: Boolean parseSuccess = False; int width, height; if (sscanf(sdpLine, "a=x-dimensions:%d,%d", &width, &height) == 2) { parseSuccess = True; fVideoWidth = (unsigned short)width; fVideoHeight = (unsigned short)height; } return parseSuccess; } Boolean MediaSubsession::parseSDPAttribute_framerate(char const* sdpLine) { // Check for a "a=framerate: " or "a=x-framerate: " line: Boolean parseSuccess = False; float frate; int rate; if (sscanf(sdpLine, "a=framerate: %f", &frate) == 1 || sscanf(sdpLine, "a=framerate:%f", &frate) == 1) { parseSuccess = True; fVideoFPS = (unsigned)frate; } else if (sscanf(sdpLine, "a=x-framerate: %d", &rate) == 1) { parseSuccess = True; fVideoFPS = (unsigned)rate; } return parseSuccess; } Boolean MediaSubsession::createSourceObjects(int useSpecialRTPoffset) { // do { // // First, check "fProtocolName" // if (strcmp(fProtocolName, "UDP") == 0) { // // A UDP-packetized stream (*not* a RTP stream) // fReadSource = BasicUDPSource::createNew(env(), fRTPSocket); // fRTPSource = NULL; // Note! // // if (strcmp(fCodecName, "MP2T") == 0) { // MPEG-2 Transport Stream // fReadSource = MPEG2TransportStreamFramer::createNew(env(), fReadSource); // // this sets "durationInMicroseconds" correctly, based on the PCR values // } // } else { // // Check "fCodecName" against the set of codecs that we support, // // and create our RTP source accordingly // // (Later make this code more efficient, as this set grows #####) // // (Also, add more fmts that can be implemented by SimpleRTPSource#####) // Boolean createSimpleRTPSource = False; // by default; can be changed below // Boolean doNormalMBitRule = False; // default behavior if "createSimpleRTPSource" is True // if (strcmp(fCodecName, "QCELP") == 0) { // QCELP audio // fReadSource = // QCELPAudioRTPSource::createNew(env(), fRTPSocket, fRTPSource, // fRTPPayloadFormat, // fRTPTimestampFrequency); // // Note that fReadSource will differ from fRTPSource in this case // } else if (strcmp(fCodecName, "AMR") == 0) { // AMR audio (narrowband) // fReadSource = // AMRAudioRTPSource::createNew(env(), fRTPSocket, fRTPSource, // fRTPPayloadFormat, False /*isWideband*/, // fNumChannels, attrVal_bool("octet-align"), // attrVal_unsigned("interleaving"), // attrVal_bool("robust-sorting"), // attrVal_bool("crc")); // // Note that fReadSource will differ from fRTPSource in this case // } else if (strcmp(fCodecName, "AMR-WB") == 0) { // AMR audio (wideband) // fReadSource = // AMRAudioRTPSource::createNew(env(), fRTPSocket, fRTPSource, // fRTPPayloadFormat, True /*isWideband*/, // fNumChannels, attrVal_bool("octet-align"), // attrVal_unsigned("interleaving"), // attrVal_bool("robust-sorting"), // attrVal_bool("crc")); // // Note that fReadSource will differ from fRTPSource in this case // } else if (strcmp(fCodecName, "MPA") == 0) { // MPEG-1 or 2 audio // fReadSource = fRTPSource // = MPEG1or2AudioRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "MPA-ROBUST") == 0) { // robust MP3 audio // fReadSource = fRTPSource // = MP3ADURTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat, // fRTPTimestampFrequency); // if (fRTPSource == NULL) break; // // if (!fReceiveRawMP3ADUs) { // // Add a filter that deinterleaves the ADUs after depacketizing them: // MP3ADUdeinterleaver* deinterleaver // = MP3ADUdeinterleaver::createNew(env(), fRTPSource); // if (deinterleaver == NULL) break; // // // Add another filter that converts these ADUs to MP3 frames: // fReadSource = MP3FromADUSource::createNew(env(), deinterleaver); // } // } else if (strcmp(fCodecName, "X-MP3-DRAFT-00") == 0) { // // a non-standard variant of "MPA-ROBUST" used by RealNetworks // // (one 'ADU'ized MP3 frame per packet; no headers) // fRTPSource // = SimpleRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat, // fRTPTimestampFrequency, // "audio/MPA-ROBUST" /*hack*/); // if (fRTPSource == NULL) break; // // // Add a filter that converts these ADUs to MP3 frames: // fReadSource = MP3FromADUSource::createNew(env(), fRTPSource, // False /*no ADU header*/); // } else if (strcmp(fCodecName, "MP4A-LATM") == 0) { // MPEG-4 LATM audio // fReadSource = fRTPSource // = MPEG4LATMAudioRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "VORBIS") == 0) { // Vorbis audio // fReadSource = fRTPSource // = VorbisAudioRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "THEORA") == 0) { // Theora video // fReadSource = fRTPSource // = TheoraVideoRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat); // } else if (strcmp(fCodecName, "VP8") == 0) { // VP8 video // fReadSource = fRTPSource // = VP8VideoRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "VP9") == 0) { // VP9 video // fReadSource = fRTPSource // = VP9VideoRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "AC3") == 0 || strcmp(fCodecName, "EAC3") == 0) { // AC3 audio // fReadSource = fRTPSource // = AC3AudioRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "MP4V-ES") == 0) { // MPEG-4 Elementary Stream video // fReadSource = fRTPSource // = MPEG4ESVideoRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "MPEG4-GENERIC") == 0) { // fReadSource = fRTPSource // = MPEG4GenericRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency, // fMediumName, attrVal_strToLower("mode"), // attrVal_unsigned("sizelength"), // attrVal_unsigned("indexlength"), // attrVal_unsigned("indexdeltalength")); // } else if (strcmp(fCodecName, "MPV") == 0) { // MPEG-1 or 2 video // fReadSource = fRTPSource // = MPEG1or2VideoRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "MP2T") == 0) { // MPEG-2 Transport Stream // fRTPSource = SimpleRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat, // fRTPTimestampFrequency, "video/MP2T", // 0, False); // fReadSource = MPEG2TransportStreamFramer::createNew(env(), fRTPSource); // // this sets "durationInMicroseconds" correctly, based on the PCR values // } else if (strcmp(fCodecName, "H261") == 0) { // H.261 // fReadSource = fRTPSource // = H261VideoRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "H263-1998") == 0 || // strcmp(fCodecName, "H263-2000") == 0) { // H.263+ // fReadSource = fRTPSource // = H263plusVideoRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "H264") == 0) { // fReadSource = fRTPSource // = H264VideoRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "H265") == 0) { // Boolean expectDONFields = attrVal_unsigned("sprop-depack-buf-nalus") > 0; // fReadSource = fRTPSource // = H265VideoRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // expectDONFields, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "DV") == 0) { // fReadSource = fRTPSource // = DVVideoRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency); // } else if (strcmp(fCodecName, "JPEG") == 0) { // motion JPEG // if (fReceiveRawJPEGFrames) { // // Special case (used when proxying JPEG/RTP streams): Receive each JPEG/RTP packet, including the special RTP headers: // fReadSource = fRTPSource // = SimpleRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat, // fRTPTimestampFrequency, "video/JPEG", // 0/*special offset*/, False/*doNormalMBitRule => ignore the 'M' bit*/); // } else { // // Normal case: Receive each JPEG frame as a complete, displayable JPEG image: // fReadSource = fRTPSource // = JPEGVideoRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency, // videoWidth(), // videoHeight()); // } // } else if (strcmp(fCodecName, "X-QT") == 0 // || strcmp(fCodecName, "X-QUICKTIME") == 0) { // // Generic QuickTime streams, as defined in // // // char* mimeType // = new char[strlen(mediumName()) + strlen(codecName()) + 2] ; // sprintf(mimeType, "%s/%s", mediumName(), codecName()); // fReadSource = fRTPSource // = QuickTimeGenericRTPSource::createNew(env(), fRTPSocket, // fRTPPayloadFormat, // fRTPTimestampFrequency, // mimeType); // delete[] mimeType; // } else if ( strcmp(fCodecName, "PCMU") == 0 // PCM u-law audio // || strcmp(fCodecName, "GSM") == 0 // GSM audio // || strcmp(fCodecName, "DVI4") == 0 // DVI4 (IMA ADPCM) audio // || strcmp(fCodecName, "PCMA") == 0 // PCM a-law audio // || strcmp(fCodecName, "MP1S") == 0 // MPEG-1 System Stream // || strcmp(fCodecName, "MP2P") == 0 // MPEG-2 Program Stream // || strcmp(fCodecName, "L8") == 0 // 8-bit linear audio // || strcmp(fCodecName, "L16") == 0 // 16-bit linear audio // || strcmp(fCodecName, "L20") == 0 // 20-bit linear audio (RFC 3190) // || strcmp(fCodecName, "L24") == 0 // 24-bit linear audio (RFC 3190) // || strcmp(fCodecName, "G722") == 0 // G.722 audio (RFC 3551) // || strcmp(fCodecName, "G726-16") == 0 // G.726, 16 kbps // || strcmp(fCodecName, "G726-24") == 0 // G.726, 24 kbps // || strcmp(fCodecName, "G726-32") == 0 // G.726, 32 kbps // || strcmp(fCodecName, "G726-40") == 0 // G.726, 40 kbps // || strcmp(fCodecName, "SPEEX") == 0 // SPEEX audio // || strcmp(fCodecName, "ILBC") == 0 // iLBC audio // || strcmp(fCodecName, "OPUS") == 0 // Opus audio // || strcmp(fCodecName, "T140") == 0 // T.140 text (RFC 4103) // || strcmp(fCodecName, "DAT12") == 0 // 12-bit nonlinear audio (RFC 3190) // || strcmp(fCodecName, "VND.ONVIF.METADATA") == 0 // 'ONVIF' 'metadata' (a XML document) // ) { // createSimpleRTPSource = True; // useSpecialRTPoffset = 0; // if (strcmp(fCodecName, "VND.ONVIF.METADATA") == 0) { // // This RTP payload format uses the RTP "M" bit to indicate the end of the content (a XML document): // doNormalMBitRule = True; // } // } else if (useSpecialRTPoffset >= 0) { // // We don't know this RTP payload format, but try to receive // // it using a 'SimpleRTPSource' with the specified header offset: // createSimpleRTPSource = True; // } else { // env().setResultMsg("RTP payload format unknown or not supported"); // break; // } // // if (createSimpleRTPSource) { // char* mimeType // = new char[strlen(mediumName()) + strlen(codecName()) + 2] ; // sprintf(mimeType, "%s/%s", mediumName(), codecName()); // fReadSource = fRTPSource // = SimpleRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat, // fRTPTimestampFrequency, mimeType, // (unsigned)useSpecialRTPoffset, // doNormalMBitRule); // delete[] mimeType; // } // } // // return True; // } while (0); return False; // an error occurred } ////////// SDPAttribute implementation ////////// SDPAttribute::SDPAttribute(char const* strValue, Boolean valueIsHexadecimal) : fStrValue(strDup(strValue)), fStrValueToLower(NULL), fValueIsHexadecimal(valueIsHexadecimal) { if (fStrValue == NULL) { // No value was given for this attribute, so consider it to be a Boolean, with value True: fIntValue = 1; } else { // Create a 'tolower' version of "fStrValue", in case it's needed: Locale l("POSIX"); size_t strSize; fStrValueToLower = strDupSize(fStrValue, strSize); for (unsigned i = 0; i < strSize-1; ++i) fStrValueToLower[i] = tolower(fStrValue[i]); fStrValueToLower[strSize-1] = '\0'; // Try to parse "fStrValueToLower" as an integer. If we can't, assume an integer value of 0: if (sscanf(fStrValueToLower, valueIsHexadecimal ? "%x" : "%d", &fIntValue) != 1) { fIntValue = 0; } } } SDPAttribute::~SDPAttribute() { delete[] fStrValue; delete[] fStrValueToLower; }