/********** 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 // Implementation #include "liveMedia.hh" #include "Locale.hh" #include "Base64.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), fMIKEYState(NULL), fCrypto(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; delete fCrypto; delete fMIKEYState; } 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; if (parseSDPAttribute_key_mgmt(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= " // or "m= / " // (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 RTP/SAVP %u", mediumName, &subsession->fClientPortNum, &payloadFormat) == 3 || sscanf(sdpLine, "m=%s %hu/%*u RTP/SAVP %u", mediumName, &subsession->fClientPortNum, &payloadFormat) == 3) && payloadFormat <= 127) { protocolName = "SRTP"; } 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; if (subsession->parseSDPAttribute_key_mgmt(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; } // Common code used to parse many string values within SDP lines: static Boolean parseStringValue(char const* sdpLine, char const* searchFormat, char*& result) { Boolean parseSuccess = False; char* buffer = strDupSize(sdpLine); if (sscanf(sdpLine, searchFormat, buffer) == 1) { delete[] result; result = strDup(buffer); parseSuccess = True; } delete[] buffer; return parseSuccess; } static Boolean parseTwoStringValues(char const* sdpLine, char const* searchFormat, char*& result1, char*& result2) { Boolean parseSuccess = False; size_t sdpLineSize = strlen(sdpLine) + 1; char* buffer1 = new char[sdpLineSize]; char* buffer2 = new char[sdpLineSize]; if (sscanf(sdpLine, searchFormat, buffer1, buffer2) == 2) { delete[] result1; result1 = strDup(buffer1); delete[] result2; result2 = strDup(buffer2); parseSuccess = True; } delete[] buffer1; delete[] buffer2; return parseSuccess; } static MIKEYState* parseSDPAttribute_key_mgmtToMIKEY(char const* sdpLine) { char* keyMgmtPrtclId = NULL; char* keyMgmtData = NULL; MIKEYState* resultMIKEYState = NULL; do { // Check for a "a=key-mgmt: " line: if (!parseTwoStringValues(sdpLine, "a=key-mgmt:%s %s", keyMgmtPrtclId, keyMgmtData)) break; // We understand only the 'protocol id' "mikey": if (strcmp(keyMgmtPrtclId, "mikey") != 0) break; // Base64-decode the "keyMgmtData" string: unsigned keyMgmtData_decodedSize; u_int8_t* keyMgmtData_decoded = base64Decode(keyMgmtData, keyMgmtData_decodedSize); if (keyMgmtData_decoded == NULL) break; resultMIKEYState = MIKEYState::createNew(keyMgmtData_decoded, keyMgmtData_decodedSize); } while (0); delete[] keyMgmtPrtclId; delete[] keyMgmtData; return resultMIKEYState; } Boolean MediaSession::parseSDPLine_s(char const* sdpLine) { // Check for "s=" line return parseStringValue(sdpLine, "s=%[^\r\n]", fSessionName); } Boolean MediaSession::parseSDPLine_i(char const* sdpLine) { // Check for "i=" line return parseStringValue(sdpLine, "i=%[^\r\n]", fSessionDescription); } Boolean MediaSession::parseSDPLine_c(char const* sdpLine) { // Check for "c=IN IP4 " // or "c=IN IP4 /" // (Later, do something with also #####) return parseStringValue(sdpLine, "c=IN IP4 %[^/\r\n]", fConnectionEndpointName); } Boolean MediaSession::parseSDPAttribute_type(char const* sdpLine) { // Check for a "a=type:broadcast|meeting|moderated|test|H.332|recvonly" line: return parseStringValue(sdpLine, "a=type: %[^ ]", fMediaSessionType); } Boolean MediaSession::parseSDPAttribute_control(char const* sdpLine) { // Check for a "a=control:" line: return parseStringValue(sdpLine, "a=control: %s", fControlPath); } 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 = NULL; do { if (!parseStringValue(sdpLine, "a=source-filter: incl IN IP4 %*s %s", sourceName)) 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); } Boolean MediaSession::parseSDPAttribute_key_mgmt(char const* sdpLine) { MIKEYState* newMIKEYState = parseSDPAttribute_key_mgmtToMIKEY(sdpLine); if (newMIKEYState == NULL) return False; delete fCrypto; delete fMIKEYState; fMIKEYState = newMIKEYState; fCrypto = new SRTPCryptographicContext(*fMIKEYState); return True; } 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), fMIKEYState(NULL), fCrypto(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" setAttribute("sampling", "RGB"); // used with "video/JPEG2000" } MediaSubsession::~MediaSubsession() { deInitiate(); delete[] fConnectionEndpointName; delete[] fSavedSDPLines; delete[] fMediumName; delete[] fCodecName; delete[] fProtocolName; delete[] fControlPath; delete fCrypto; delete fMIKEYState; 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" Boolean const useSRTP = strcmp(fProtocolName, "SRTP") == 0; Boolean const protocolIsRTP = useSRTP || strcmp(fProtocolName, "RTP") == 0; if (fClientPortNum != 0 && (honorSDPPortChoice || IsMulticastAddress(tempAddr.s_addr))) { // The sockets' port numbers were specified for us. Use these: 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; } SRTPCryptographicContext* ourCrypto = NULL; if (useSRTP) { // For SRTP, we need key management. If MIKEY (key management) state wasn't given // to us in the SDP description, then create it now: ourCrypto = getCrypto(); if (ourCrypto == NULL) { // then fMIKEYState is also NULL; create both fMIKEYState = new MIKEYState(); ourCrypto = fCrypto = new SRTPCryptographicContext(*fMIKEYState); } if (fRTPSource != NULL) fRTPSource->setCrypto(ourCrypto); } // 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, False /* we're not a data transmitter */, ourCrypto); 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 #####) return parseStringValue(sdpLine, "c=IN IP4 %[^/\r\n]", fConnectionEndpointName); } 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: return parseStringValue(sdpLine, "a=control: %s", fControlPath); } 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::parseSDPAttribute_key_mgmt(char const* sdpLine) { MIKEYState* newMIKEYState = parseSDPAttribute_key_mgmtToMIKEY(sdpLine); if (newMIKEYState == NULL) return False; delete fCrypto; delete fMIKEYState; fMIKEYState = newMIKEYState; fCrypto = new SRTPCryptographicContext(*fMIKEYState); return True; } 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, "RAW") == 0) { // Uncompressed raw video (RFC 4175) fReadSource = fRTPSource = RawVideoRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat, fRTPTimestampFrequency); } 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, "JPEG2000") == 0) { // JPEG 2000 video fReadSource = fRTPSource = JPEG2000VideoRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat, fRTPTimestampFrequency, attrVal_str("sampling")); } 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; }