1 | /********** |
2 | This library is free software; you can redistribute it and/or modify it under |
3 | the terms of the GNU Lesser General Public License as published by the |
4 | Free Software Foundation; either version 3 of the License, or (at your |
5 | option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.) |
6 | |
7 | This library is distributed in the hope that it will be useful, but WITHOUT |
8 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
9 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for |
10 | more details. |
11 | |
12 | You should have received a copy of the GNU Lesser General Public License |
13 | along with this library; if not, write to the Free Software Foundation, Inc., |
14 | 51 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 | |
33 | ServerMediaSession* 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 | |
41 | Boolean 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 | |
58 | static char const* const libNameStr = "LIVE555 Streaming Media v" ; |
59 | char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING; |
60 | |
61 | ServerMediaSession::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 | |
85 | ServerMediaSession::~ServerMediaSession() { |
86 | deleteAllSubsessions(); |
87 | delete[] fStreamName; |
88 | delete[] fInfoSDPString; |
89 | delete[] fDescriptionSDPString; |
90 | delete[] fMiscSDPLines; |
91 | } |
92 | |
93 | Boolean |
94 | ServerMediaSession::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 | |
109 | void 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 | |
169 | float 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 | |
197 | void ServerMediaSession::noteLiveness() { |
198 | // default implementation: do nothing |
199 | } |
200 | |
201 | void ServerMediaSession::deleteAllSubsessions() { |
202 | Medium::close(fSubsessionsHead); |
203 | fSubsessionsHead = fSubsessionsTail = NULL; |
204 | fSubsessionCounter = 0; |
205 | } |
206 | |
207 | Boolean ServerMediaSession::isServerMediaSession() const { |
208 | return True; |
209 | } |
210 | |
211 | char* 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 | |
321 | ServerMediaSubsessionIterator |
322 | ::ServerMediaSubsessionIterator(ServerMediaSession& session) |
323 | : fOurSession(session) { |
324 | reset(); |
325 | } |
326 | |
327 | ServerMediaSubsessionIterator::~ServerMediaSubsessionIterator() { |
328 | } |
329 | |
330 | ServerMediaSubsession* ServerMediaSubsessionIterator::next() { |
331 | ServerMediaSubsession* result = fNextPtr; |
332 | |
333 | if (fNextPtr != NULL) fNextPtr = fNextPtr->fNext; |
334 | |
335 | return result; |
336 | } |
337 | |
338 | void ServerMediaSubsessionIterator::reset() { |
339 | fNextPtr = fOurSession.fSubsessionsHead; |
340 | } |
341 | |
342 | |
343 | ////////// ServerMediaSubsession ////////// |
344 | |
345 | ServerMediaSubsession::ServerMediaSubsession(UsageEnvironment& env) |
346 | : Medium(env), |
347 | fParentSession(NULL), fServerAddressForSDP(0), fPortNumForSDP(0), |
348 | fNext(NULL), fTrackNumber(0), fTrackId(NULL) { |
349 | } |
350 | |
351 | ServerMediaSubsession::~ServerMediaSubsession() { |
352 | delete[] (char*)fTrackId; |
353 | Medium::close(fNext); |
354 | } |
355 | |
356 | char 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 | |
367 | void ServerMediaSubsession::pauseStream(unsigned /*clientSessionId*/, |
368 | void* /*streamToken*/) { |
369 | // default implementation: do nothing |
370 | } |
371 | void ServerMediaSubsession::seekStream(unsigned /*clientSessionId*/, |
372 | void* /*streamToken*/, double& /*seekNPT*/, double /*streamDuration*/, u_int64_t& numBytes) { |
373 | // default implementation: do nothing |
374 | numBytes = 0; |
375 | } |
376 | void 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 | } |
382 | void ServerMediaSubsession::nullSeekStream(unsigned /*clientSessionId*/, void* /*streamToken*/, |
383 | double streamEndTime, u_int64_t& numBytes) { |
384 | // default implementation: do nothing |
385 | numBytes = 0; |
386 | } |
387 | void ServerMediaSubsession::setStreamScale(unsigned /*clientSessionId*/, |
388 | void* /*streamToken*/, float /*scale*/) { |
389 | // default implementation: do nothing |
390 | } |
391 | float ServerMediaSubsession::getCurrentNPT(void* /*streamToken*/) { |
392 | // default implementation: return 0.0 |
393 | return 0.0; |
394 | } |
395 | FramedSource* ServerMediaSubsession::getStreamSource(void* /*streamToken*/) { |
396 | // default implementation: return NULL |
397 | return NULL; |
398 | } |
399 | void ServerMediaSubsession::deleteStream(unsigned /*clientSessionId*/, |
400 | void*& /*streamToken*/) { |
401 | // default implementation: do nothing |
402 | } |
403 | |
404 | void ServerMediaSubsession::testScaleFactor(float& scale) { |
405 | // default implementation: Support scale = 1 only |
406 | scale = 1; |
407 | } |
408 | |
409 | float ServerMediaSubsession::duration() const { |
410 | // default implementation: assume an unbounded session: |
411 | return 0.0; |
412 | } |
413 | |
414 | void 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 | |
419 | void ServerMediaSubsession::setServerAddressAndPortForSDP(netAddressBits addressBits, |
420 | portNumBits portBits) { |
421 | fServerAddressForSDP = addressBits; |
422 | fPortNumForSDP = portBits; |
423 | } |
424 | |
425 | char const* |
426 | ServerMediaSubsession::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 | |