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 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s |
19 | // on demand. |
20 | // Implementation |
21 | |
22 | #include "OnDemandServerMediaSubsession.hh" |
23 | #include <GroupsockHelper.hh> |
24 | |
25 | OnDemandServerMediaSubsession |
26 | ::OnDemandServerMediaSubsession(UsageEnvironment& env, |
27 | Boolean reuseFirstSource, |
28 | portNumBits initialPortNum, |
29 | Boolean multiplexRTCPWithRTP) |
30 | : ServerMediaSubsession(env), |
31 | fSDPLines(NULL), fReuseFirstSource(reuseFirstSource), |
32 | fMultiplexRTCPWithRTP(multiplexRTCPWithRTP), fLastStreamToken(NULL), |
33 | fAppHandlerTask(NULL), fAppHandlerClientData(NULL) { |
34 | fDestinationsHashTable = HashTable::create(ONE_WORD_HASH_KEYS); |
35 | if (fMultiplexRTCPWithRTP) { |
36 | fInitialPortNum = initialPortNum; |
37 | } else { |
38 | // Make sure RTP ports are even-numbered: |
39 | fInitialPortNum = (initialPortNum+1)&~1; |
40 | } |
41 | gethostname(fCNAME, sizeof fCNAME); |
42 | fCNAME[sizeof fCNAME-1] = '\0'; // just in case |
43 | } |
44 | |
45 | OnDemandServerMediaSubsession::~OnDemandServerMediaSubsession() { |
46 | delete[] fSDPLines; |
47 | |
48 | // Clean out the destinations hash table: |
49 | while (1) { |
50 | Destinations* destinations |
51 | = (Destinations*)(fDestinationsHashTable->RemoveNext()); |
52 | if (destinations == NULL) break; |
53 | delete destinations; |
54 | } |
55 | delete fDestinationsHashTable; |
56 | } |
57 | |
58 | char const* |
59 | OnDemandServerMediaSubsession::sdpLines() { |
60 | if (fSDPLines == NULL) { |
61 | // We need to construct a set of SDP lines that describe this |
62 | // subsession (as a unicast stream). To do so, we first create |
63 | // dummy (unused) source and "RTPSink" objects, |
64 | // whose parameters we use for the SDP lines: |
65 | unsigned estBitrate; |
66 | FramedSource* inputSource = createNewStreamSource(0, estBitrate); |
67 | if (inputSource == NULL) return NULL; // file not found |
68 | |
69 | struct in_addr dummyAddr; |
70 | dummyAddr.s_addr = 0; |
71 | Groupsock* dummyGroupsock = createGroupsock(dummyAddr, 0); |
72 | unsigned char rtpPayloadType = 96 + trackNumber()-1; // if dynamic |
73 | RTPSink* dummyRTPSink = createNewRTPSink(dummyGroupsock, rtpPayloadType, inputSource); |
74 | if (dummyRTPSink != NULL && dummyRTPSink->estimatedBitrate() > 0) estBitrate = dummyRTPSink->estimatedBitrate(); |
75 | |
76 | setSDPLinesFromRTPSink(dummyRTPSink, inputSource, estBitrate); |
77 | Medium::close(dummyRTPSink); |
78 | delete dummyGroupsock; |
79 | closeStreamSource(inputSource); |
80 | } |
81 | |
82 | return fSDPLines; |
83 | } |
84 | |
85 | void OnDemandServerMediaSubsession |
86 | ::getStreamParameters(unsigned clientSessionId, |
87 | netAddressBits clientAddress, |
88 | Port const& clientRTPPort, |
89 | Port const& clientRTCPPort, |
90 | int tcpSocketNum, |
91 | unsigned char rtpChannelId, |
92 | unsigned char rtcpChannelId, |
93 | netAddressBits& destinationAddress, |
94 | u_int8_t& /*destinationTTL*/, |
95 | Boolean& isMulticast, |
96 | Port& serverRTPPort, |
97 | Port& serverRTCPPort, |
98 | void*& streamToken) { |
99 | if (destinationAddress == 0) destinationAddress = clientAddress; |
100 | struct in_addr destinationAddr; destinationAddr.s_addr = destinationAddress; |
101 | isMulticast = False; |
102 | |
103 | if (fLastStreamToken != NULL && fReuseFirstSource) { |
104 | // Special case: Rather than creating a new 'StreamState', |
105 | // we reuse the one that we've already created: |
106 | serverRTPPort = ((StreamState*)fLastStreamToken)->serverRTPPort(); |
107 | serverRTCPPort = ((StreamState*)fLastStreamToken)->serverRTCPPort(); |
108 | ++((StreamState*)fLastStreamToken)->referenceCount(); |
109 | streamToken = fLastStreamToken; |
110 | } else { |
111 | // Normal case: Create a new media source: |
112 | unsigned streamBitrate; |
113 | FramedSource* mediaSource |
114 | = createNewStreamSource(clientSessionId, streamBitrate); |
115 | |
116 | // Create 'groupsock' and 'sink' objects for the destination, |
117 | // using previously unused server port numbers: |
118 | RTPSink* rtpSink = NULL; |
119 | BasicUDPSink* udpSink = NULL; |
120 | Groupsock* rtpGroupsock = NULL; |
121 | Groupsock* rtcpGroupsock = NULL; |
122 | |
123 | if (clientRTPPort.num() != 0 || tcpSocketNum >= 0) { // Normal case: Create destinations |
124 | portNumBits serverPortNum; |
125 | if (clientRTCPPort.num() == 0) { |
126 | // We're streaming raw UDP (not RTP). Create a single groupsock: |
127 | NoReuse dummy(envir()); // ensures that we skip over ports that are already in use |
128 | for (serverPortNum = fInitialPortNum; ; ++serverPortNum) { |
129 | struct in_addr dummyAddr; dummyAddr.s_addr = 0; |
130 | |
131 | serverRTPPort = serverPortNum; |
132 | rtpGroupsock = createGroupsock(dummyAddr, serverRTPPort); |
133 | if (rtpGroupsock->socketNum() >= 0) break; // success |
134 | } |
135 | |
136 | udpSink = BasicUDPSink::createNew(envir(), rtpGroupsock); |
137 | } else { |
138 | // Normal case: We're streaming RTP (over UDP or TCP). Create a pair of |
139 | // groupsocks (RTP and RTCP), with adjacent port numbers (RTP port number even). |
140 | // (If we're multiplexing RTCP and RTP over the same port number, it can be odd or even.) |
141 | NoReuse dummy(envir()); // ensures that we skip over ports that are already in use |
142 | for (portNumBits serverPortNum = fInitialPortNum; ; ++serverPortNum) { |
143 | struct in_addr dummyAddr; dummyAddr.s_addr = 0; |
144 | |
145 | serverRTPPort = serverPortNum; |
146 | rtpGroupsock = createGroupsock(dummyAddr, serverRTPPort); |
147 | if (rtpGroupsock->socketNum() < 0) { |
148 | delete rtpGroupsock; |
149 | continue; // try again |
150 | } |
151 | |
152 | if (fMultiplexRTCPWithRTP) { |
153 | // Use the RTP 'groupsock' object for RTCP as well: |
154 | serverRTCPPort = serverRTPPort; |
155 | rtcpGroupsock = rtpGroupsock; |
156 | } else { |
157 | // Create a separate 'groupsock' object (with the next (odd) port number) for RTCP: |
158 | serverRTCPPort = ++serverPortNum; |
159 | rtcpGroupsock = createGroupsock(dummyAddr, serverRTCPPort); |
160 | if (rtcpGroupsock->socketNum() < 0) { |
161 | delete rtpGroupsock; |
162 | delete rtcpGroupsock; |
163 | continue; // try again |
164 | } |
165 | } |
166 | |
167 | break; // success |
168 | } |
169 | |
170 | unsigned char rtpPayloadType = 96 + trackNumber()-1; // if dynamic |
171 | rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource); |
172 | if (rtpSink != NULL && rtpSink->estimatedBitrate() > 0) streamBitrate = rtpSink->estimatedBitrate(); |
173 | } |
174 | |
175 | // Turn off the destinations for each groupsock. They'll get set later |
176 | // (unless TCP is used instead): |
177 | if (rtpGroupsock != NULL) rtpGroupsock->removeAllDestinations(); |
178 | if (rtcpGroupsock != NULL) rtcpGroupsock->removeAllDestinations(); |
179 | |
180 | if (rtpGroupsock != NULL) { |
181 | // Try to use a big send buffer for RTP - at least 0.1 second of |
182 | // specified bandwidth and at least 50 KB |
183 | unsigned rtpBufSize = streamBitrate * 25 / 2; // 1 kbps * 0.1 s = 12.5 bytes |
184 | if (rtpBufSize < 50 * 1024) rtpBufSize = 50 * 1024; |
185 | increaseSendBufferTo(envir(), rtpGroupsock->socketNum(), rtpBufSize); |
186 | } |
187 | } |
188 | |
189 | // Set up the state of the stream. The stream will get started later: |
190 | streamToken = fLastStreamToken |
191 | = new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink, |
192 | streamBitrate, mediaSource, |
193 | rtpGroupsock, rtcpGroupsock); |
194 | } |
195 | |
196 | // Record these destinations as being for this client session id: |
197 | Destinations* destinations; |
198 | if (tcpSocketNum < 0) { // UDP |
199 | destinations = new Destinations(destinationAddr, clientRTPPort, clientRTCPPort); |
200 | } else { // TCP |
201 | destinations = new Destinations(tcpSocketNum, rtpChannelId, rtcpChannelId); |
202 | } |
203 | fDestinationsHashTable->Add((char const*)clientSessionId, destinations); |
204 | } |
205 | |
206 | void OnDemandServerMediaSubsession::startStream(unsigned clientSessionId, |
207 | void* streamToken, |
208 | TaskFunc* rtcpRRHandler, |
209 | void* rtcpRRHandlerClientData, |
210 | unsigned short& rtpSeqNum, |
211 | unsigned& rtpTimestamp, |
212 | ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler, |
213 | void* serverRequestAlternativeByteHandlerClientData) { |
214 | StreamState* streamState = (StreamState*)streamToken; |
215 | Destinations* destinations |
216 | = (Destinations*)(fDestinationsHashTable->Lookup((char const*)clientSessionId)); |
217 | if (streamState != NULL) { |
218 | streamState->startPlaying(destinations, clientSessionId, |
219 | rtcpRRHandler, rtcpRRHandlerClientData, |
220 | serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData); |
221 | RTPSink* rtpSink = streamState->rtpSink(); // alias |
222 | if (rtpSink != NULL) { |
223 | rtpSeqNum = rtpSink->currentSeqNo(); |
224 | rtpTimestamp = rtpSink->presetNextTimestamp(); |
225 | } |
226 | } |
227 | } |
228 | |
229 | void OnDemandServerMediaSubsession::pauseStream(unsigned /*clientSessionId*/, |
230 | void* streamToken) { |
231 | // Pausing isn't allowed if multiple clients are receiving data from |
232 | // the same source: |
233 | if (fReuseFirstSource) return; |
234 | |
235 | StreamState* streamState = (StreamState*)streamToken; |
236 | if (streamState != NULL) streamState->pause(); |
237 | } |
238 | |
239 | void OnDemandServerMediaSubsession::seekStream(unsigned /*clientSessionId*/, |
240 | void* streamToken, double& seekNPT, double streamDuration, u_int64_t& numBytes) { |
241 | numBytes = 0; // by default: unknown |
242 | |
243 | // Seeking isn't allowed if multiple clients are receiving data from the same source: |
244 | if (fReuseFirstSource) return; |
245 | |
246 | StreamState* streamState = (StreamState*)streamToken; |
247 | if (streamState != NULL && streamState->mediaSource() != NULL) { |
248 | seekStreamSource(streamState->mediaSource(), seekNPT, streamDuration, numBytes); |
249 | |
250 | streamState->startNPT() = (float)seekNPT; |
251 | RTPSink* rtpSink = streamState->rtpSink(); // alias |
252 | if (rtpSink != NULL) rtpSink->resetPresentationTimes(); |
253 | } |
254 | } |
255 | |
256 | void OnDemandServerMediaSubsession::seekStream(unsigned /*clientSessionId*/, |
257 | void* streamToken, char*& absStart, char*& absEnd) { |
258 | // Seeking isn't allowed if multiple clients are receiving data from the same source: |
259 | if (fReuseFirstSource) return; |
260 | |
261 | StreamState* streamState = (StreamState*)streamToken; |
262 | if (streamState != NULL && streamState->mediaSource() != NULL) { |
263 | seekStreamSource(streamState->mediaSource(), absStart, absEnd); |
264 | } |
265 | } |
266 | |
267 | void OnDemandServerMediaSubsession::nullSeekStream(unsigned /*clientSessionId*/, void* streamToken, |
268 | double streamEndTime, u_int64_t& numBytes) { |
269 | numBytes = 0; // by default: unknown |
270 | |
271 | StreamState* streamState = (StreamState*)streamToken; |
272 | if (streamState != NULL && streamState->mediaSource() != NULL) { |
273 | // Because we're not seeking here, get the current NPT, and remember it as the new 'start' NPT: |
274 | streamState->startNPT() = getCurrentNPT(streamToken); |
275 | |
276 | double duration = streamEndTime - streamState->startNPT(); |
277 | if (duration < 0.0) duration = 0.0; |
278 | setStreamSourceDuration(streamState->mediaSource(), duration, numBytes); |
279 | |
280 | RTPSink* rtpSink = streamState->rtpSink(); // alias |
281 | if (rtpSink != NULL) rtpSink->resetPresentationTimes(); |
282 | } |
283 | } |
284 | |
285 | void OnDemandServerMediaSubsession::setStreamScale(unsigned /*clientSessionId*/, |
286 | void* streamToken, float scale) { |
287 | // Changing the scale factor isn't allowed if multiple clients are receiving data |
288 | // from the same source: |
289 | if (fReuseFirstSource) return; |
290 | |
291 | StreamState* streamState = (StreamState*)streamToken; |
292 | if (streamState != NULL && streamState->mediaSource() != NULL) { |
293 | setStreamSourceScale(streamState->mediaSource(), scale); |
294 | } |
295 | } |
296 | |
297 | float OnDemandServerMediaSubsession::getCurrentNPT(void* streamToken) { |
298 | do { |
299 | if (streamToken == NULL) break; |
300 | |
301 | StreamState* streamState = (StreamState*)streamToken; |
302 | RTPSink* rtpSink = streamState->rtpSink(); |
303 | if (rtpSink == NULL) break; |
304 | |
305 | return streamState->startNPT() |
306 | + (rtpSink->mostRecentPresentationTime().tv_sec - rtpSink->initialPresentationTime().tv_sec) |
307 | + (rtpSink->mostRecentPresentationTime().tv_usec - rtpSink->initialPresentationTime().tv_usec)/1000000.0f; |
308 | } while (0); |
309 | |
310 | return 0.0; |
311 | } |
312 | |
313 | FramedSource* OnDemandServerMediaSubsession::getStreamSource(void* streamToken) { |
314 | if (streamToken == NULL) return NULL; |
315 | |
316 | StreamState* streamState = (StreamState*)streamToken; |
317 | return streamState->mediaSource(); |
318 | } |
319 | |
320 | void OnDemandServerMediaSubsession |
321 | ::getRTPSinkandRTCP(void* streamToken, |
322 | RTPSink const*& rtpSink, RTCPInstance const*& rtcp) { |
323 | if (streamToken == NULL) { |
324 | rtpSink = NULL; |
325 | rtcp = NULL; |
326 | return; |
327 | } |
328 | |
329 | StreamState* streamState = (StreamState*)streamToken; |
330 | rtpSink = streamState->rtpSink(); |
331 | rtcp = streamState->rtcpInstance(); |
332 | } |
333 | |
334 | void OnDemandServerMediaSubsession::deleteStream(unsigned clientSessionId, |
335 | void*& streamToken) { |
336 | StreamState* streamState = (StreamState*)streamToken; |
337 | |
338 | // Look up (and remove) the destinations for this client session: |
339 | Destinations* destinations |
340 | = (Destinations*)(fDestinationsHashTable->Lookup((char const*)clientSessionId)); |
341 | if (destinations != NULL) { |
342 | fDestinationsHashTable->Remove((char const*)clientSessionId); |
343 | |
344 | // Stop streaming to these destinations: |
345 | if (streamState != NULL) streamState->endPlaying(destinations, clientSessionId); |
346 | } |
347 | |
348 | // Delete the "StreamState" structure if it's no longer being used: |
349 | if (streamState != NULL) { |
350 | if (streamState->referenceCount() > 0) --streamState->referenceCount(); |
351 | if (streamState->referenceCount() == 0) { |
352 | delete streamState; |
353 | streamToken = NULL; |
354 | } |
355 | } |
356 | |
357 | // Finally, delete the destinations themselves: |
358 | delete destinations; |
359 | } |
360 | |
361 | char const* OnDemandServerMediaSubsession |
362 | ::getAuxSDPLine(RTPSink* rtpSink, FramedSource* /*inputSource*/) { |
363 | // Default implementation: |
364 | return rtpSink == NULL ? NULL : rtpSink->auxSDPLine(); |
365 | } |
366 | |
367 | void OnDemandServerMediaSubsession::seekStreamSource(FramedSource* /*inputSource*/, |
368 | double& /*seekNPT*/, double /*streamDuration*/, u_int64_t& numBytes) { |
369 | // Default implementation: Do nothing |
370 | numBytes = 0; |
371 | } |
372 | |
373 | void OnDemandServerMediaSubsession::seekStreamSource(FramedSource* /*inputSource*/, |
374 | char*& absStart, char*& absEnd) { |
375 | // Default implementation: do nothing (but delete[] and assign "absStart" and "absEnd" to NULL, to show that we don't handle this) |
376 | delete[] absStart; absStart = NULL; |
377 | delete[] absEnd; absEnd = NULL; |
378 | } |
379 | |
380 | void OnDemandServerMediaSubsession |
381 | ::setStreamSourceScale(FramedSource* /*inputSource*/, float /*scale*/) { |
382 | // Default implementation: Do nothing |
383 | } |
384 | |
385 | void OnDemandServerMediaSubsession |
386 | ::setStreamSourceDuration(FramedSource* /*inputSource*/, double /*streamDuration*/, u_int64_t& numBytes) { |
387 | // Default implementation: Do nothing |
388 | numBytes = 0; |
389 | } |
390 | |
391 | void OnDemandServerMediaSubsession::closeStreamSource(FramedSource *inputSource) { |
392 | Medium::close(inputSource); |
393 | } |
394 | |
395 | Groupsock* OnDemandServerMediaSubsession |
396 | ::createGroupsock(struct in_addr const& addr, Port port) { |
397 | // Default implementation; may be redefined by subclasses: |
398 | return new Groupsock(envir(), addr, port, 255); |
399 | } |
400 | |
401 | RTCPInstance* OnDemandServerMediaSubsession |
402 | ::createRTCP(Groupsock* RTCPgs, unsigned totSessionBW, /* in kbps */ |
403 | unsigned char const* cname, RTPSink* sink) { |
404 | // Default implementation; may be redefined by subclasses: |
405 | return RTCPInstance::createNew(envir(), RTCPgs, totSessionBW, cname, sink, NULL/*we're a server*/); |
406 | } |
407 | |
408 | void OnDemandServerMediaSubsession |
409 | ::setRTCPAppPacketHandler(RTCPAppHandlerFunc* handler, void* clientData) { |
410 | fAppHandlerTask = handler; |
411 | fAppHandlerClientData = clientData; |
412 | } |
413 | |
414 | void OnDemandServerMediaSubsession |
415 | ::sendRTCPAppPacket(u_int8_t subtype, char const* name, |
416 | u_int8_t* appDependentData, unsigned appDependentDataSize) { |
417 | StreamState* streamState = (StreamState*)fLastStreamToken; |
418 | if (streamState != NULL) { |
419 | streamState->sendRTCPAppPacket(subtype, name, appDependentData, appDependentDataSize); |
420 | } |
421 | } |
422 | |
423 | void OnDemandServerMediaSubsession |
424 | ::setSDPLinesFromRTPSink(RTPSink* rtpSink, FramedSource* inputSource, unsigned estBitrate) { |
425 | if (rtpSink == NULL) return; |
426 | |
427 | char const* mediaType = rtpSink->sdpMediaType(); |
428 | unsigned char rtpPayloadType = rtpSink->rtpPayloadType(); |
429 | AddressString ipAddressStr(fServerAddressForSDP); |
430 | char* rtpmapLine = rtpSink->rtpmapLine(); |
431 | char const* rtcpmuxLine = fMultiplexRTCPWithRTP ? "a=rtcp-mux\r\n" : "" ; |
432 | char const* rangeLine = rangeSDPLine(); |
433 | char const* auxSDPLine = getAuxSDPLine(rtpSink, inputSource); |
434 | if (auxSDPLine == NULL) auxSDPLine = "" ; |
435 | |
436 | char const* const sdpFmt = |
437 | "m=%s %u RTP/AVP %d\r\n" |
438 | "c=IN IP4 %s\r\n" |
439 | "b=AS:%u\r\n" |
440 | "%s" |
441 | "%s" |
442 | "%s" |
443 | "%s" |
444 | "a=control:%s\r\n" ; |
445 | unsigned sdpFmtSize = strlen(sdpFmt) |
446 | + strlen(mediaType) + 5 /* max short len */ + 3 /* max char len */ |
447 | + strlen(ipAddressStr.val()) |
448 | + 20 /* max int len */ |
449 | + strlen(rtpmapLine) |
450 | + strlen(rtcpmuxLine) |
451 | + strlen(rangeLine) |
452 | + strlen(auxSDPLine) |
453 | + strlen(trackId()); |
454 | char* sdpLines = new char[sdpFmtSize]; |
455 | sprintf(sdpLines, sdpFmt, |
456 | mediaType, // m= <media> |
457 | fPortNumForSDP, // m= <port> |
458 | rtpPayloadType, // m= <fmt list> |
459 | ipAddressStr.val(), // c= address |
460 | estBitrate, // b=AS:<bandwidth> |
461 | rtpmapLine, // a=rtpmap:... (if present) |
462 | rtcpmuxLine, // a=rtcp-mux:... (if present) |
463 | rangeLine, // a=range:... (if present) |
464 | auxSDPLine, // optional extra SDP line |
465 | trackId()); // a=control:<track-id> |
466 | delete[] (char*)rangeLine; delete[] rtpmapLine; |
467 | |
468 | delete[] fSDPLines; fSDPLines = strDup(sdpLines); |
469 | delete[] sdpLines; |
470 | } |
471 | |
472 | |
473 | ////////// StreamState implementation ////////// |
474 | |
475 | static void afterPlayingStreamState(void* clientData) { |
476 | StreamState* streamState = (StreamState*)clientData; |
477 | if (streamState->streamDuration() == 0.0) { |
478 | // When the input stream ends, tear it down. This will cause a RTCP "BYE" |
479 | // to be sent to each client, teling it that the stream has ended. |
480 | // (Because the stream didn't have a known duration, there was no other |
481 | // way for clients to know when the stream ended.) |
482 | streamState->reclaim(); |
483 | } |
484 | // Otherwise, keep the stream alive, in case a client wants to |
485 | // subsequently re-play the stream starting from somewhere other than the end. |
486 | // (This can be done only on streams that have a known duration.) |
487 | } |
488 | |
489 | StreamState::StreamState(OnDemandServerMediaSubsession& master, |
490 | Port const& serverRTPPort, Port const& serverRTCPPort, |
491 | RTPSink* rtpSink, BasicUDPSink* udpSink, |
492 | unsigned totalBW, FramedSource* mediaSource, |
493 | Groupsock* rtpGS, Groupsock* rtcpGS) |
494 | : fMaster(master), fAreCurrentlyPlaying(False), fReferenceCount(1), |
495 | fServerRTPPort(serverRTPPort), fServerRTCPPort(serverRTCPPort), |
496 | fRTPSink(rtpSink), fUDPSink(udpSink), fStreamDuration(master.duration()), |
497 | fTotalBW(totalBW), fRTCPInstance(NULL) /* created later */, |
498 | fMediaSource(mediaSource), fStartNPT(0.0), fRTPgs(rtpGS), fRTCPgs(rtcpGS) { |
499 | } |
500 | |
501 | StreamState::~StreamState() { |
502 | reclaim(); |
503 | } |
504 | |
505 | void StreamState |
506 | ::startPlaying(Destinations* dests, unsigned clientSessionId, |
507 | TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData, |
508 | ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler, |
509 | void* serverRequestAlternativeByteHandlerClientData) { |
510 | if (dests == NULL) return; |
511 | |
512 | if (fRTCPInstance == NULL && fRTPSink != NULL) { |
513 | // Create (and start) a 'RTCP instance' for this RTP sink: |
514 | fRTCPInstance = fMaster.createRTCP(fRTCPgs, fTotalBW, (unsigned char*)fMaster.fCNAME, fRTPSink); |
515 | // Note: This starts RTCP running automatically |
516 | fRTCPInstance->setAppHandler(fMaster.fAppHandlerTask, fMaster.fAppHandlerClientData); |
517 | } |
518 | |
519 | if (dests->isTCP) { |
520 | // Change RTP and RTCP to use the TCP socket instead of UDP: |
521 | if (fRTPSink != NULL) { |
522 | fRTPSink->addStreamSocket(dests->tcpSocketNum, dests->rtpChannelId); |
523 | RTPInterface |
524 | ::setServerRequestAlternativeByteHandler(fRTPSink->envir(), dests->tcpSocketNum, |
525 | serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData); |
526 | // So that we continue to handle RTSP commands from the client |
527 | } |
528 | if (fRTCPInstance != NULL) { |
529 | fRTCPInstance->addStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId); |
530 | fRTCPInstance->setSpecificRRHandler(dests->tcpSocketNum, dests->rtcpChannelId, |
531 | rtcpRRHandler, rtcpRRHandlerClientData); |
532 | } |
533 | } else { |
534 | // Tell the RTP and RTCP 'groupsocks' about this destination |
535 | // (in case they don't already have it): |
536 | if (fRTPgs != NULL) fRTPgs->addDestination(dests->addr, dests->rtpPort, clientSessionId); |
537 | if (fRTCPgs != NULL && !(fRTCPgs == fRTPgs && dests->rtcpPort.num() == dests->rtpPort.num())) { |
538 | fRTCPgs->addDestination(dests->addr, dests->rtcpPort, clientSessionId); |
539 | } |
540 | if (fRTCPInstance != NULL) { |
541 | fRTCPInstance->setSpecificRRHandler(dests->addr.s_addr, dests->rtcpPort, |
542 | rtcpRRHandler, rtcpRRHandlerClientData); |
543 | } |
544 | } |
545 | |
546 | if (fRTCPInstance != NULL) { |
547 | // Hack: Send an initial RTCP "SR" packet, before the initial RTP packet, so that receivers will (likely) be able to |
548 | // get RTCP-synchronized presentation times immediately: |
549 | fRTCPInstance->sendReport(); |
550 | } |
551 | |
552 | if (!fAreCurrentlyPlaying && fMediaSource != NULL) { |
553 | if (fRTPSink != NULL) { |
554 | fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this); |
555 | fAreCurrentlyPlaying = True; |
556 | } else if (fUDPSink != NULL) { |
557 | fUDPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this); |
558 | fAreCurrentlyPlaying = True; |
559 | } |
560 | } |
561 | } |
562 | |
563 | void StreamState::pause() { |
564 | if (fRTPSink != NULL) fRTPSink->stopPlaying(); |
565 | if (fUDPSink != NULL) fUDPSink->stopPlaying(); |
566 | fAreCurrentlyPlaying = False; |
567 | } |
568 | |
569 | void StreamState::endPlaying(Destinations* dests, unsigned clientSessionId) { |
570 | #if 0 |
571 | // The following code is temporarily disabled, because it erroneously sends RTCP "BYE"s to all clients if multiple |
572 | // clients are streaming from the same data source (i.e., if "reuseFirstSource" is True), and we don't want that to happen |
573 | // if we're being called as a result of a single one of these clients having sent a "TEARDOWN" (rather than the whole stream |
574 | // having been closed, for all clients). |
575 | // This will be fixed for real later. |
576 | if (fRTCPInstance != NULL) { |
577 | // Hack: Explicitly send a RTCP "BYE" packet now, because the code below will prevent that from happening later, |
578 | // when "fRTCPInstance" gets deleted: |
579 | fRTCPInstance->sendBYE(); |
580 | } |
581 | #endif |
582 | |
583 | if (dests->isTCP) { |
584 | if (fRTPSink != NULL) { |
585 | RTPInterface::clearServerRequestAlternativeByteHandler(fRTPSink->envir(), dests->tcpSocketNum); |
586 | fRTPSink->removeStreamSocket(dests->tcpSocketNum, dests->rtpChannelId); |
587 | } |
588 | if (fRTCPInstance != NULL) { |
589 | fRTCPInstance->removeStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId); |
590 | fRTCPInstance->unsetSpecificRRHandler(dests->tcpSocketNum, dests->rtcpChannelId); |
591 | } |
592 | } else { |
593 | // Tell the RTP and RTCP 'groupsocks' to stop using these destinations: |
594 | if (fRTPgs != NULL) fRTPgs->removeDestination(clientSessionId); |
595 | if (fRTCPgs != NULL && fRTCPgs != fRTPgs) fRTCPgs->removeDestination(clientSessionId); |
596 | if (fRTCPInstance != NULL) { |
597 | fRTCPInstance->unsetSpecificRRHandler(dests->addr.s_addr, dests->rtcpPort); |
598 | } |
599 | } |
600 | } |
601 | |
602 | void StreamState::sendRTCPAppPacket(u_int8_t subtype, char const* name, |
603 | u_int8_t* appDependentData, unsigned appDependentDataSize) { |
604 | if (fRTCPInstance != NULL) { |
605 | fRTCPInstance->sendAppPacket(subtype, name, appDependentData, appDependentDataSize); |
606 | } |
607 | } |
608 | |
609 | void StreamState::reclaim() { |
610 | // Delete allocated media objects |
611 | Medium::close(fRTCPInstance) /* will send a RTCP BYE */; fRTCPInstance = NULL; |
612 | Medium::close(fRTPSink); fRTPSink = NULL; |
613 | Medium::close(fUDPSink); fUDPSink = NULL; |
614 | |
615 | fMaster.closeStreamSource(fMediaSource); fMediaSource = NULL; |
616 | if (fMaster.fLastStreamToken == this) fMaster.fLastStreamToken = NULL; |
617 | |
618 | delete fRTPgs; |
619 | if (fRTCPgs != fRTPgs) delete fRTCPgs; |
620 | fRTPgs = NULL; fRTCPgs = NULL; |
621 | } |
622 | |