blob: 5525c440bc7e1c87c222b298eb5f433c1b468380 [file] [log] [blame]
/**********
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 <http://www.gnu.org/copyleft/lesser.html>.)
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
**********/
// "liveMedia"
// Copyright (c) 1996-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 <ctype.h>
////////// 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=<medium_name> <client_portNum> <proto> <fmt>"
// or "m=<medium_name> <client_portNum>/<num_ports> <proto> <fmt>"
// (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 <char>=<etc>
// (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:<prtcl-id> <keymgmt-data>" 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=<session name>" line
return parseStringValue(sdpLine, "s=%[^\r\n]", fSessionName);
}
Boolean MediaSession::parseSDPLine_i(char const* sdpLine) {
// Check for "i=<session description>" line
return parseStringValue(sdpLine, "i=%[^\r\n]", fSessionDescription);
}
Boolean MediaSession::parseSDPLine_c(char const* sdpLine) {
// Check for "c=IN IP4 <connection-endpoint>"
// or "c=IN IP4 <connection-endpoint>/<ttl+numAddresses>"
// (Later, do something with <ttl+numAddresses> 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:<control-path>" 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=<startTime>-<endTime>" 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 <something> <source>" line.
// Note: At present, we don't check that <something> really matches
// one of our multicast addresses. We also don't support more than
// one <source> #####
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 <connection-endpoint>"
// or "c=IN IP4 <connection-endpoint>/<ttl+numAddresses>"
// (Later, do something with <ttl+numAddresses> also #####)
return parseStringValue(sdpLine, "c=IN IP4 %[^/\r\n]", fConnectionEndpointName);
}
Boolean MediaSubsession::parseSDPLine_b(char const* sdpLine) {
// Check for "b=<bwtype>:<bandwidth>" 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:<fmt> <codec>/<freq>" line:
// (Also check without the "/<freq>"; RealNetworks omits this)
// Also check for a trailing "/<numChannels>".
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:<control-path>" line:
return parseStringValue(sdpLine, "a=control: %s", fControlPath);
}
Boolean MediaSubsession::parseSDPAttribute_range(char const* sdpLine) {
// Check for a "a=range:npt=<startTime>-<endTime>" 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
// <name>=<value>;
// or
// <name>;
// 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) {
// <name> or <name>=<value>
// Convert <name> to lower-case, to ease comparison:
Locale l("POSIX");
for (char* c = nameStr; *c != '\0'; ++c) *c = tolower(*c);
if (sscanfResult == 1) {
// <name>
setAttribute(nameStr);
} else {
// <name>=<value>
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:<width>,<height>" 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: <fps>" or "a=x-framerate: <fps>" 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
// <http://developer.apple.com/quicktime/icefloe/dispatch026.html>
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;
}