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 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
19// on demand, from a MPEG-2 Transport Stream file.
20// Implementation
21
22#include "MPEG2TransportFileServerMediaSubsession.hh"
23#include "SimpleRTPSink.hh"
24
25MPEG2TransportFileServerMediaSubsession*
26MPEG2TransportFileServerMediaSubsession::createNew(UsageEnvironment& env,
27 char const* fileName,
28 char const* indexFileName,
29 Boolean reuseFirstSource) {
30 MPEG2TransportStreamIndexFile* indexFile;
31 if (indexFileName != NULL && reuseFirstSource) {
32 // It makes no sense to support trick play if all clients use the same source. Fix this:
33 env << "MPEG2TransportFileServerMediaSubsession::createNew(): ignoring the index file name, because \"reuseFirstSource\" is set\n";
34 indexFile = NULL;
35 } else {
36 indexFile = MPEG2TransportStreamIndexFile::createNew(env, indexFileName);
37 }
38 return new MPEG2TransportFileServerMediaSubsession(env, fileName, indexFile,
39 reuseFirstSource);
40}
41
42MPEG2TransportFileServerMediaSubsession
43::MPEG2TransportFileServerMediaSubsession(UsageEnvironment& env,
44 char const* fileName,
45 MPEG2TransportStreamIndexFile* indexFile,
46 Boolean reuseFirstSource)
47 : FileServerMediaSubsession(env, fileName, reuseFirstSource),
48 fIndexFile(indexFile), fDuration(0.0), fClientSessionHashTable(NULL) {
49 if (fIndexFile != NULL) { // we support 'trick play'
50 fDuration = fIndexFile->getPlayingDuration();
51 fClientSessionHashTable = HashTable::create(ONE_WORD_HASH_KEYS);
52 }
53}
54
55MPEG2TransportFileServerMediaSubsession
56::~MPEG2TransportFileServerMediaSubsession() {
57 if (fIndexFile != NULL) { // we support 'trick play'
58 Medium::close(fIndexFile);
59
60 // Clean out the client session hash table:
61 while (1) {
62 ClientTrickPlayState* client
63 = (ClientTrickPlayState*)(fClientSessionHashTable->RemoveNext());
64 if (client == NULL) break;
65 delete client;
66 }
67 delete fClientSessionHashTable;
68 }
69}
70
71#define TRANSPORT_PACKET_SIZE 188
72#define TRANSPORT_PACKETS_PER_NETWORK_PACKET 7
73// The product of these two numbers must be enough to fit within a network packet
74
75void MPEG2TransportFileServerMediaSubsession
76::startStream(unsigned clientSessionId, void* streamToken, TaskFunc* rtcpRRHandler,
77 void* rtcpRRHandlerClientData, unsigned short& rtpSeqNum,
78 unsigned& rtpTimestamp,
79 ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
80 void* serverRequestAlternativeByteHandlerClientData) {
81 if (fIndexFile != NULL) { // we support 'trick play'
82 ClientTrickPlayState* client = lookupClient(clientSessionId);
83 if (client != NULL && client->areChangingScale()) {
84 // First, handle this like a "PAUSE", except that we back up to the previous VSH
85 client->updateStateOnPlayChange(True);
86 OnDemandServerMediaSubsession::pauseStream(clientSessionId, streamToken);
87
88 // Then, adjust for the change of scale:
89 client->updateStateOnScaleChange();
90 }
91 }
92
93 // Call the original, default version of this routine:
94 OnDemandServerMediaSubsession::startStream(clientSessionId, streamToken,
95 rtcpRRHandler, rtcpRRHandlerClientData,
96 rtpSeqNum, rtpTimestamp,
97 serverRequestAlternativeByteHandler, serverRequestAlternativeByteHandlerClientData);
98}
99
100void MPEG2TransportFileServerMediaSubsession
101::pauseStream(unsigned clientSessionId, void* streamToken) {
102 if (fIndexFile != NULL) { // we support 'trick play'
103 ClientTrickPlayState* client = lookupClient(clientSessionId);
104 if (client != NULL) {
105 client->updateStateOnPlayChange(False);
106 }
107 }
108
109 // Call the original, default version of this routine:
110 OnDemandServerMediaSubsession::pauseStream(clientSessionId, streamToken);
111}
112
113void MPEG2TransportFileServerMediaSubsession
114::seekStream(unsigned clientSessionId, void* streamToken, double& seekNPT, double streamDuration, u_int64_t& numBytes) {
115 // Begin by calling the original, default version of this routine:
116 OnDemandServerMediaSubsession::seekStream(clientSessionId, streamToken, seekNPT, streamDuration, numBytes);
117
118 // Then, special handling specific to indexed Transport Stream files:
119 if (fIndexFile != NULL) { // we support 'trick play'
120 ClientTrickPlayState* client = lookupClient(clientSessionId);
121 if (client != NULL) {
122 unsigned long numTSPacketsToStream = client->updateStateFromNPT(seekNPT, streamDuration);
123 numBytes = numTSPacketsToStream*TRANSPORT_PACKET_SIZE;
124 }
125 }
126}
127
128void MPEG2TransportFileServerMediaSubsession
129::setStreamScale(unsigned clientSessionId, void* streamToken, float scale) {
130 if (fIndexFile != NULL) { // we support 'trick play'
131 ClientTrickPlayState* client = lookupClient(clientSessionId);
132 if (client != NULL) {
133 client->setNextScale(scale); // scale won't take effect until the next "PLAY"
134 }
135 }
136
137 // Call the original, default version of this routine:
138 OnDemandServerMediaSubsession::setStreamScale(clientSessionId, streamToken, scale);
139}
140
141void MPEG2TransportFileServerMediaSubsession
142::deleteStream(unsigned clientSessionId, void*& streamToken) {
143 if (fIndexFile != NULL) { // we support 'trick play'
144 ClientTrickPlayState* client = lookupClient(clientSessionId);
145 if (client != NULL) {
146 client->updateStateOnPlayChange(False);
147 }
148 }
149
150 // Call the original, default version of this routine:
151 OnDemandServerMediaSubsession::deleteStream(clientSessionId, streamToken);
152}
153
154ClientTrickPlayState* MPEG2TransportFileServerMediaSubsession::newClientTrickPlayState() {
155 return new ClientTrickPlayState(fIndexFile);
156}
157
158FramedSource* MPEG2TransportFileServerMediaSubsession
159::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
160 // Create the video source:
161 unsigned const inputDataChunkSize
162 = TRANSPORT_PACKETS_PER_NETWORK_PACKET*TRANSPORT_PACKET_SIZE;
163 ByteStreamFileSource* fileSource
164 = ByteStreamFileSource::createNew(envir(), fFileName, inputDataChunkSize);
165 if (fileSource == NULL) return NULL;
166 fFileSize = fileSource->fileSize();
167
168 // Use the file size and the duration to estimate the stream's bitrate:
169 if (fFileSize > 0 && fDuration > 0.0) {
170 estBitrate = (unsigned)((int64_t)fFileSize/(125*fDuration) + 0.5); // kbps, rounded
171 } else {
172 estBitrate = 5000; // kbps, estimate
173 }
174
175
176 // Create a framer for the Transport Stream:
177 MPEG2TransportStreamFramer* framer
178 = MPEG2TransportStreamFramer::createNew(envir(), fileSource);
179
180 if (fIndexFile != NULL) { // we support 'trick play'
181 // Keep state for this client (if we don't already have it):
182 ClientTrickPlayState* client = lookupClient(clientSessionId);
183 if (client == NULL) {
184 client = newClientTrickPlayState();
185 fClientSessionHashTable->Add((char const*)clientSessionId, client);
186 }
187 client->setSource(framer);
188 }
189
190 return framer;
191}
192
193RTPSink* MPEG2TransportFileServerMediaSubsession
194::createNewRTPSink(Groupsock* rtpGroupsock,
195 unsigned char /*rtpPayloadTypeIfDynamic*/,
196 FramedSource* /*inputSource*/) {
197 return SimpleRTPSink::createNew(envir(), rtpGroupsock,
198 33, 90000, "video", "MP2T",
199 1, True, False /*no 'M' bit*/);
200}
201
202void MPEG2TransportFileServerMediaSubsession::testScaleFactor(float& scale) {
203 if (fIndexFile != NULL && fDuration > 0.0) {
204 // We support any integral scale, other than 0
205 int iScale = scale < 0.0 ? (int)(scale - 0.5f) : (int)(scale + 0.5f); // round
206 if (iScale == 0) iScale = 1;
207 scale = (float)iScale;
208 } else {
209 scale = 1.0f;
210 }
211}
212
213float MPEG2TransportFileServerMediaSubsession::duration() const {
214 return fDuration;
215}
216
217ClientTrickPlayState* MPEG2TransportFileServerMediaSubsession
218::lookupClient(unsigned clientSessionId) {
219 return (ClientTrickPlayState*)(fClientSessionHashTable->Lookup((char const*)clientSessionId));
220}
221
222
223////////// ClientTrickPlayState implementation //////////
224
225ClientTrickPlayState::ClientTrickPlayState(MPEG2TransportStreamIndexFile* indexFile)
226 : fIndexFile(indexFile),
227 fOriginalTransportStreamSource(NULL),
228 fTrickModeFilter(NULL), fTrickPlaySource(NULL),
229 fFramer(NULL),
230 fScale(1.0f), fNextScale(1.0f), fNPT(0.0f),
231 fTSRecordNum(0), fIxRecordNum(0) {
232}
233
234unsigned long ClientTrickPlayState::updateStateFromNPT(double npt, double streamDuration) {
235 fNPT = (float)npt;
236 // Map "fNPT" to the corresponding Transport Stream and Index record numbers:
237 unsigned long tsRecordNum, ixRecordNum;
238 fIndexFile->lookupTSPacketNumFromNPT(fNPT, tsRecordNum, ixRecordNum);
239
240 updateTSRecordNum();
241 if (tsRecordNum != fTSRecordNum) {
242 fTSRecordNum = tsRecordNum;
243 fIxRecordNum = ixRecordNum;
244
245 // Seek the source to the new record number:
246 reseekOriginalTransportStreamSource();
247 // Note: We assume that we're asked to seek only in normal
248 // (i.e., non trick play) mode, so we don't seek within the trick
249 // play source (if any).
250
251 fFramer->clearPIDStatusTable();
252 }
253
254 unsigned long numTSRecordsToStream = 0;
255 float pcrLimit = 0.0;
256 if (streamDuration > 0.0) {
257 // fNPT might have changed when we looked it up in the index file. Adjust "streamDuration" accordingly:
258 streamDuration += npt - (double)fNPT;
259
260 if (streamDuration > 0.0) {
261 // Specify that we want to stream no more data than this.
262
263 if (fNextScale == 1.0f) {
264 // We'll be streaming from the original file.
265 // Use the index file to figure out how many Transport Packets we get to stream:
266 unsigned long toTSRecordNum, toIxRecordNum;
267 float toNPT = (float)(fNPT + streamDuration);
268 fIndexFile->lookupTSPacketNumFromNPT(toNPT, toTSRecordNum, toIxRecordNum);
269 if (toTSRecordNum > tsRecordNum) { // sanity check
270 numTSRecordsToStream = toTSRecordNum - tsRecordNum;
271 }
272 } else {
273 // We'll be streaming from the trick play stream.
274 // It'd be difficult to figure out how many Transport Packets we need to stream, so instead set a PCR
275 // limit in the trick play stream. (We rely upon the fact that PCRs in the trick play stream start at 0.0)
276 int direction = fNextScale < 0.0 ? -1 : 1;
277 pcrLimit = (float)(streamDuration/(fNextScale*direction));
278 }
279 }
280 }
281 fFramer->setNumTSPacketsToStream(numTSRecordsToStream);
282 fFramer->setPCRLimit(pcrLimit);
283
284 return numTSRecordsToStream;
285}
286
287void ClientTrickPlayState::updateStateOnScaleChange() {
288 fScale = fNextScale;
289
290 // Change our source objects to reflect the change in scale:
291 // First, close the existing trick play source (if any):
292 if (fTrickPlaySource != NULL) {
293 fTrickModeFilter->forgetInputSource();
294 // so that the underlying Transport Stream source doesn't get deleted by:
295 Medium::close(fTrickPlaySource);
296 fTrickPlaySource = NULL;
297 fTrickModeFilter = NULL;
298 }
299 if (fNextScale != 1.0f) {
300 // Create a new trick play filter from the original Transport Stream source:
301 UsageEnvironment& env = fIndexFile->envir(); // alias
302 fTrickModeFilter = MPEG2TransportStreamTrickModeFilter
303 ::createNew(env, fOriginalTransportStreamSource, fIndexFile, int(fNextScale));
304 fTrickModeFilter->seekTo(fTSRecordNum, fIxRecordNum);
305
306 // And generate a Transport Stream from this:
307 fTrickPlaySource = MPEG2TransportStreamFromESSource::createNew(env);
308 fTrickPlaySource->addNewVideoSource(fTrickModeFilter, fIndexFile->mpegVersion());
309
310 fFramer->changeInputSource(fTrickPlaySource);
311 } else {
312 // Switch back to the original Transport Stream source:
313 reseekOriginalTransportStreamSource();
314 fFramer->changeInputSource(fOriginalTransportStreamSource);
315 }
316}
317
318void ClientTrickPlayState::updateStateOnPlayChange(Boolean reverseToPreviousVSH) {
319 updateTSRecordNum();
320 if (fTrickPlaySource == NULL) {
321 // We were in regular (1x) play. Use the index file to look up the
322 // index record number and npt from the current transport number:
323 fIndexFile->lookupPCRFromTSPacketNum(fTSRecordNum, reverseToPreviousVSH, fNPT, fIxRecordNum);
324 } else {
325 // We were in trick mode, and so already have the index record number.
326 // Get the transport record number and npt from this:
327 fIxRecordNum = fTrickModeFilter->nextIndexRecordNum();
328 if ((long)fIxRecordNum < 0) fIxRecordNum = 0; // we were at the start of the file
329 unsigned long transportRecordNum;
330 float pcr;
331 u_int8_t offset, size, recordType; // all dummy
332 if (fIndexFile->readIndexRecordValues(fIxRecordNum, transportRecordNum,
333 offset, size, pcr, recordType)) {
334 fTSRecordNum = transportRecordNum;
335 fNPT = pcr;
336 }
337 }
338}
339
340void ClientTrickPlayState::setSource(MPEG2TransportStreamFramer* framer) {
341 fFramer = framer;
342 fOriginalTransportStreamSource = (ByteStreamFileSource*)(framer->inputSource());
343}
344
345void ClientTrickPlayState::updateTSRecordNum(){
346 if (fFramer != NULL) fTSRecordNum += (unsigned long)(fFramer->tsPacketCount());
347}
348
349void ClientTrickPlayState::reseekOriginalTransportStreamSource() {
350 u_int64_t tsRecordNum64 = (u_int64_t)fTSRecordNum;
351 fOriginalTransportStreamSource->seekToByteAbsolute(tsRecordNum64*TRANSPORT_PACKET_SIZE);
352}
353