1/**********
2This library is free software; you can redistribute it and/or modify it under
3the terms of the GNU Lesser General Public License as published by the
4Free Software Foundation; either version 3 of the License, or (at your
5option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
6
7This library is distributed in the hope that it will be useful, but WITHOUT
8ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
10more details.
11
12You should have received a copy of the GNU Lesser General Public License
13along with this library; if not, write to the Free Software Foundation, Inc.,
1451 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15**********/
16// "liveMedia"
17// Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved.
18// A data structure that represents a session that consists of
19// potentially multiple (audio and/or video) sub-sessions
20// (This data structure is used for media *streamers* - i.e., servers.
21// For media receivers, use "MediaSession" instead.)
22// Implementation
23
24#include "ServerMediaSession.hh"
25#include <GroupsockHelper.hh>
26#include <math.h>
27#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
28#define snprintf _snprintf
29#endif
30
31////////// ServerMediaSession //////////
32
33ServerMediaSession* ServerMediaSession
34::createNew(UsageEnvironment& env,
35 char const* streamName, char const* info,
36 char const* description, Boolean isSSM, char const* miscSDPLines) {
37 return new ServerMediaSession(env, streamName, info, description,
38 isSSM, miscSDPLines);
39}
40
41Boolean ServerMediaSession
42::lookupByName(UsageEnvironment& env, char const* mediumName,
43 ServerMediaSession*& resultSession) {
44 resultSession = NULL; // unless we succeed
45
46 Medium* medium;
47 if (!Medium::lookupByName(env, mediumName, medium)) return False;
48
49 if (!medium->isServerMediaSession()) {
50 env.setResultMsg(mediumName, " is not a 'ServerMediaSession' object");
51 return False;
52 }
53
54 resultSession = (ServerMediaSession*)medium;
55 return True;
56}
57
58static char const* const libNameStr = "LIVE555 Streaming Media v";
59char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING;
60
61ServerMediaSession::ServerMediaSession(UsageEnvironment& env,
62 char const* streamName,
63 char const* info,
64 char const* description,
65 Boolean isSSM, char const* miscSDPLines)
66 : Medium(env), fIsSSM(isSSM), fSubsessionsHead(NULL),
67 fSubsessionsTail(NULL), fSubsessionCounter(0),
68 fReferenceCount(0), fDeleteWhenUnreferenced(False) {
69 fStreamName = strDup(streamName == NULL ? "" : streamName);
70
71 char* libNamePlusVersionStr = NULL; // by default
72 if (info == NULL || description == NULL) {
73 libNamePlusVersionStr = new char[strlen(libNameStr) + strlen(libVersionStr) + 1];
74 sprintf(libNamePlusVersionStr, "%s%s", libNameStr, libVersionStr);
75 }
76 fInfoSDPString = strDup(info == NULL ? libNamePlusVersionStr : info);
77 fDescriptionSDPString = strDup(description == NULL ? libNamePlusVersionStr : description);
78 delete[] libNamePlusVersionStr;
79
80 fMiscSDPLines = strDup(miscSDPLines == NULL ? "" : miscSDPLines);
81
82 gettimeofday(&fCreationTime, NULL);
83}
84
85ServerMediaSession::~ServerMediaSession() {
86 deleteAllSubsessions();
87 delete[] fStreamName;
88 delete[] fInfoSDPString;
89 delete[] fDescriptionSDPString;
90 delete[] fMiscSDPLines;
91}
92
93Boolean
94ServerMediaSession::addSubsession(ServerMediaSubsession* subsession) {
95 if (subsession->fParentSession != NULL) return False; // it's already used
96
97 if (fSubsessionsTail == NULL) {
98 fSubsessionsHead = subsession;
99 } else {
100 fSubsessionsTail->fNext = subsession;
101 }
102 fSubsessionsTail = subsession;
103
104 subsession->fParentSession = this;
105 subsession->fTrackNumber = ++fSubsessionCounter;
106 return True;
107}
108
109void ServerMediaSession::testScaleFactor(float& scale) {
110 // First, try setting all subsessions to the desired scale.
111 // If the subsessions' actual scales differ from each other, choose the
112 // value that's closest to 1, and then try re-setting all subsessions to that
113 // value. If the subsessions' actual scales still differ, re-set them all to 1.
114 float minSSScale = 1.0;
115 float maxSSScale = 1.0;
116 float bestSSScale = 1.0;
117 float bestDistanceTo1 = 0.0;
118 ServerMediaSubsession* subsession;
119 for (subsession = fSubsessionsHead; subsession != NULL;
120 subsession = subsession->fNext) {
121 float ssscale = scale;
122 subsession->testScaleFactor(ssscale);
123 if (subsession == fSubsessionsHead) { // this is the first subsession
124 minSSScale = maxSSScale = bestSSScale = ssscale;
125 bestDistanceTo1 = (float)fabs(ssscale - 1.0f);
126 } else {
127 if (ssscale < minSSScale) {
128 minSSScale = ssscale;
129 } else if (ssscale > maxSSScale) {
130 maxSSScale = ssscale;
131 }
132
133 float distanceTo1 = (float)fabs(ssscale - 1.0f);
134 if (distanceTo1 < bestDistanceTo1) {
135 bestSSScale = ssscale;
136 bestDistanceTo1 = distanceTo1;
137 }
138 }
139 }
140 if (minSSScale == maxSSScale) {
141 // All subsessions are at the same scale: minSSScale == bestSSScale == maxSSScale
142 scale = minSSScale;
143 return;
144 }
145
146 // The scales for each subsession differ. Try to set each one to the value
147 // that's closest to 1:
148 for (subsession = fSubsessionsHead; subsession != NULL;
149 subsession = subsession->fNext) {
150 float ssscale = bestSSScale;
151 subsession->testScaleFactor(ssscale);
152 if (ssscale != bestSSScale) break; // no luck
153 }
154 if (subsession == NULL) {
155 // All subsessions are at the same scale: bestSSScale
156 scale = bestSSScale;
157 return;
158 }
159
160 // Still no luck. Set each subsession's scale to 1:
161 for (subsession = fSubsessionsHead; subsession != NULL;
162 subsession = subsession->fNext) {
163 float ssscale = 1;
164 subsession->testScaleFactor(ssscale);
165 }
166 scale = 1;
167}
168
169float ServerMediaSession::duration() const {
170 float minSubsessionDuration = 0.0;
171 float maxSubsessionDuration = 0.0;
172 for (ServerMediaSubsession* subsession = fSubsessionsHead; subsession != NULL;
173 subsession = subsession->fNext) {
174 // Hack: If any subsession supports seeking by 'absolute' time, then return a negative value, to indicate that only subsessions
175 // will have a "a=range:" attribute:
176 char* absStartTime = NULL; char* absEndTime = NULL;
177 subsession->getAbsoluteTimeRange(absStartTime, absEndTime);
178 if (absStartTime != NULL) return -1.0f;
179
180 float ssduration = subsession->duration();
181 if (subsession == fSubsessionsHead) { // this is the first subsession
182 minSubsessionDuration = maxSubsessionDuration = ssduration;
183 } else if (ssduration < minSubsessionDuration) {
184 minSubsessionDuration = ssduration;
185 } else if (ssduration > maxSubsessionDuration) {
186 maxSubsessionDuration = ssduration;
187 }
188 }
189
190 if (maxSubsessionDuration != minSubsessionDuration) {
191 return -maxSubsessionDuration; // because subsession durations differ
192 } else {
193 return maxSubsessionDuration; // all subsession durations are the same
194 }
195}
196
197void ServerMediaSession::noteLiveness() {
198 // default implementation: do nothing
199}
200
201void ServerMediaSession::deleteAllSubsessions() {
202 Medium::close(fSubsessionsHead);
203 fSubsessionsHead = fSubsessionsTail = NULL;
204 fSubsessionCounter = 0;
205}
206
207Boolean ServerMediaSession::isServerMediaSession() const {
208 return True;
209}
210
211char* ServerMediaSession::generateSDPDescription() {
212 AddressString ipAddressStr(ourIPAddress(envir()));
213 unsigned ipAddressStrSize = strlen(ipAddressStr.val());
214
215 // For a SSM sessions, we need a "a=source-filter: incl ..." line also:
216 char* sourceFilterLine;
217 if (fIsSSM) {
218 char const* const sourceFilterFmt =
219 "a=source-filter: incl IN IP4 * %s\r\n"
220 "a=rtcp-unicast: reflection\r\n";
221 unsigned const sourceFilterFmtSize = strlen(sourceFilterFmt) + ipAddressStrSize + 1;
222
223 sourceFilterLine = new char[sourceFilterFmtSize];
224 sprintf(sourceFilterLine, sourceFilterFmt, ipAddressStr.val());
225 } else {
226 sourceFilterLine = strDup("");
227 }
228
229 char* rangeLine = NULL; // for now
230 char* sdp = NULL; // for now
231
232 do {
233 // Count the lengths of each subsession's media-level SDP lines.
234 // (We do this first, because the call to "subsession->sdpLines()"
235 // causes correct subsession 'duration()'s to be calculated later.)
236 unsigned sdpLength = 0;
237 ServerMediaSubsession* subsession;
238 for (subsession = fSubsessionsHead; subsession != NULL;
239 subsession = subsession->fNext) {
240 char const* sdpLines = subsession->sdpLines();
241 if (sdpLines == NULL) continue; // the media's not available
242 sdpLength += strlen(sdpLines);
243 }
244 if (sdpLength == 0) break; // the session has no usable subsessions
245
246 // Unless subsessions have differing durations, we also have a "a=range:" line:
247 float dur = duration();
248 if (dur == 0.0) {
249 rangeLine = strDup("a=range:npt=0-\r\n");
250 } else if (dur > 0.0) {
251 char buf[100];
252 sprintf(buf, "a=range:npt=0-%.3f\r\n", dur);
253 rangeLine = strDup(buf);
254 } else { // subsessions have differing durations, so "a=range:" lines go there
255 rangeLine = strDup("");
256 }
257
258 char const* const sdpPrefixFmt =
259 "v=0\r\n"
260 "o=- %ld%06ld %d IN IP4 %s\r\n"
261 "s=%s\r\n"
262 "i=%s\r\n"
263 "t=0 0\r\n"
264 "a=tool:%s%s\r\n"
265 "a=type:broadcast\r\n"
266 "a=control:*\r\n"
267 "%s"
268 "%s"
269 "a=x-qt-text-nam:%s\r\n"
270 "a=x-qt-text-inf:%s\r\n"
271 "%s";
272 sdpLength += strlen(sdpPrefixFmt)
273 + 20 + 6 + 20 + ipAddressStrSize
274 + strlen(fDescriptionSDPString)
275 + strlen(fInfoSDPString)
276 + strlen(libNameStr) + strlen(libVersionStr)
277 + strlen(sourceFilterLine)
278 + strlen(rangeLine)
279 + strlen(fDescriptionSDPString)
280 + strlen(fInfoSDPString)
281 + strlen(fMiscSDPLines);
282 sdpLength += 1000; // in case the length of the "subsession->sdpLines()" calls below change
283 sdp = new char[sdpLength];
284 if (sdp == NULL) break;
285
286 // Generate the SDP prefix (session-level lines):
287 snprintf(sdp, sdpLength, sdpPrefixFmt,
288 fCreationTime.tv_sec, fCreationTime.tv_usec, // o= <session id>
289 1, // o= <version> // (needs to change if params are modified)
290 ipAddressStr.val(), // o= <address>
291 fDescriptionSDPString, // s= <description>
292 fInfoSDPString, // i= <info>
293 libNameStr, libVersionStr, // a=tool:
294 sourceFilterLine, // a=source-filter: incl (if a SSM session)
295 rangeLine, // a=range: line
296 fDescriptionSDPString, // a=x-qt-text-nam: line
297 fInfoSDPString, // a=x-qt-text-inf: line
298 fMiscSDPLines); // miscellaneous session SDP lines (if any)
299
300 // Then, add the (media-level) lines for each subsession:
301 char* mediaSDP = sdp;
302 for (subsession = fSubsessionsHead; subsession != NULL;
303 subsession = subsession->fNext) {
304 unsigned mediaSDPLength = strlen(mediaSDP);
305 mediaSDP += mediaSDPLength;
306 sdpLength -= mediaSDPLength;
307 if (sdpLength <= 1) break; // the SDP has somehow become too long
308
309 char const* sdpLines = subsession->sdpLines();
310 if (sdpLines != NULL) snprintf(mediaSDP, sdpLength, "%s", sdpLines);
311 }
312 } while (0);
313
314 delete[] rangeLine; delete[] sourceFilterLine;
315 return sdp;
316}
317
318
319////////// ServerMediaSubsessionIterator //////////
320
321ServerMediaSubsessionIterator
322::ServerMediaSubsessionIterator(ServerMediaSession& session)
323 : fOurSession(session) {
324 reset();
325}
326
327ServerMediaSubsessionIterator::~ServerMediaSubsessionIterator() {
328}
329
330ServerMediaSubsession* ServerMediaSubsessionIterator::next() {
331 ServerMediaSubsession* result = fNextPtr;
332
333 if (fNextPtr != NULL) fNextPtr = fNextPtr->fNext;
334
335 return result;
336}
337
338void ServerMediaSubsessionIterator::reset() {
339 fNextPtr = fOurSession.fSubsessionsHead;
340}
341
342
343////////// ServerMediaSubsession //////////
344
345ServerMediaSubsession::ServerMediaSubsession(UsageEnvironment& env)
346 : Medium(env),
347 fParentSession(NULL), fServerAddressForSDP(0), fPortNumForSDP(0),
348 fNext(NULL), fTrackNumber(0), fTrackId(NULL) {
349}
350
351ServerMediaSubsession::~ServerMediaSubsession() {
352 delete[] (char*)fTrackId;
353 Medium::close(fNext);
354}
355
356char const* ServerMediaSubsession::trackId() {
357 if (fTrackNumber == 0) return NULL; // not yet in a ServerMediaSession
358
359 if (fTrackId == NULL) {
360 char buf[100];
361 sprintf(buf, "track%d", fTrackNumber);
362 fTrackId = strDup(buf);
363 }
364 return fTrackId;
365}
366
367void ServerMediaSubsession::pauseStream(unsigned /*clientSessionId*/,
368 void* /*streamToken*/) {
369 // default implementation: do nothing
370}
371void ServerMediaSubsession::seekStream(unsigned /*clientSessionId*/,
372 void* /*streamToken*/, double& /*seekNPT*/, double /*streamDuration*/, u_int64_t& numBytes) {
373 // default implementation: do nothing
374 numBytes = 0;
375}
376void ServerMediaSubsession::seekStream(unsigned /*clientSessionId*/,
377 void* /*streamToken*/, char*& absStart, char*& absEnd) {
378 // default implementation: do nothing (but delete[] and assign "absStart" and "absEnd" to NULL, to show that we don't handle this)
379 delete[] absStart; absStart = NULL;
380 delete[] absEnd; absEnd = NULL;
381}
382void ServerMediaSubsession::nullSeekStream(unsigned /*clientSessionId*/, void* /*streamToken*/,
383 double streamEndTime, u_int64_t& numBytes) {
384 // default implementation: do nothing
385 numBytes = 0;
386}
387void ServerMediaSubsession::setStreamScale(unsigned /*clientSessionId*/,
388 void* /*streamToken*/, float /*scale*/) {
389 // default implementation: do nothing
390}
391float ServerMediaSubsession::getCurrentNPT(void* /*streamToken*/) {
392 // default implementation: return 0.0
393 return 0.0;
394}
395FramedSource* ServerMediaSubsession::getStreamSource(void* /*streamToken*/) {
396 // default implementation: return NULL
397 return NULL;
398}
399void ServerMediaSubsession::deleteStream(unsigned /*clientSessionId*/,
400 void*& /*streamToken*/) {
401 // default implementation: do nothing
402}
403
404void ServerMediaSubsession::testScaleFactor(float& scale) {
405 // default implementation: Support scale = 1 only
406 scale = 1;
407}
408
409float ServerMediaSubsession::duration() const {
410 // default implementation: assume an unbounded session:
411 return 0.0;
412}
413
414void ServerMediaSubsession::getAbsoluteTimeRange(char*& absStartTime, char*& absEndTime) const {
415 // default implementation: We don't support seeking by 'absolute' time, so indicate this by setting both parameters to NULL:
416 absStartTime = absEndTime = NULL;
417}
418
419void ServerMediaSubsession::setServerAddressAndPortForSDP(netAddressBits addressBits,
420 portNumBits portBits) {
421 fServerAddressForSDP = addressBits;
422 fPortNumForSDP = portBits;
423}
424
425char const*
426ServerMediaSubsession::rangeSDPLine() const {
427 // First, check for the special case where we support seeking by 'absolute' time:
428 char* absStart = NULL; char* absEnd = NULL;
429 getAbsoluteTimeRange(absStart, absEnd);
430 if (absStart != NULL) {
431 char buf[100];
432
433 if (absEnd != NULL) {
434 sprintf(buf, "a=range:clock=%s-%s\r\n", absStart, absEnd);
435 } else {
436 sprintf(buf, "a=range:clock=%s-\r\n", absStart);
437 }
438 return strDup(buf);
439 }
440
441 if (fParentSession == NULL) return NULL;
442
443 // If all of our parent's subsessions have the same duration
444 // (as indicated by "fParentSession->duration() >= 0"), there's no "a=range:" line:
445 if (fParentSession->duration() >= 0.0) return strDup("");
446
447 // Use our own duration for a "a=range:" line:
448 float ourDuration = duration();
449 if (ourDuration == 0.0) {
450 return strDup("a=range:npt=0-\r\n");
451 } else {
452 char buf[100];
453 sprintf(buf, "a=range:npt=0-%.3f\r\n", ourDuration);
454 return strDup(buf);
455 }
456}
457