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 media sink that takes - as input - a MPEG Transport Stream, and outputs a series |
19 | // of MPEG Transport Stream files, each representing a segment of the input stream, |
20 | // suitable for HLS (Apple's "HTTP Live Streaming"). |
21 | // Implementation |
22 | |
23 | #include "HLSSegmenter.hh" |
24 | #include "OutputFile.hh" |
25 | #include "MPEG2TransportStreamMultiplexor.hh" |
26 | |
27 | #define TRANSPORT_PACKET_SIZE 188 |
28 | #define OUTPUT_FILE_BUFFER_SIZE (TRANSPORT_PACKET_SIZE*100) |
29 | |
30 | HLSSegmenter* HLSSegmenter |
31 | ::createNew(UsageEnvironment& env, |
32 | unsigned segmentationDuration, char const* fileNamePrefix, |
33 | onEndOfSegmentFunc* onEndOfSegmentFunc, void* onEndOfSegmentClientData) { |
34 | return new HLSSegmenter(env, segmentationDuration, fileNamePrefix, |
35 | onEndOfSegmentFunc, onEndOfSegmentClientData); |
36 | } |
37 | |
38 | HLSSegmenter::HLSSegmenter(UsageEnvironment& env, |
39 | unsigned segmentationDuration, char const* fileNamePrefix, |
40 | onEndOfSegmentFunc* onEndOfSegmentFunc, void* onEndOfSegmentClientData) |
41 | : MediaSink(env), |
42 | fSegmentationDuration(segmentationDuration), fFileNamePrefix(fileNamePrefix), |
43 | fOnEndOfSegmentFunc(onEndOfSegmentFunc), fOnEndOfSegmentClientData(onEndOfSegmentClientData), |
44 | fHaveConfiguredUpstreamSource(False), fCurrentSegmentCounter(1), fOutFid(NULL) { |
45 | // Allocate enough space for the segment file name: |
46 | fOutputSegmentFileName = new char[strlen(fileNamePrefix) + 20/*more than enough*/]; |
47 | |
48 | // Allocate the output file buffer size: |
49 | fOutputFileBuffer = new unsigned char[OUTPUT_FILE_BUFFER_SIZE]; |
50 | } |
51 | HLSSegmenter::~HLSSegmenter() { |
52 | delete[] fOutputFileBuffer; |
53 | delete[] fOutputSegmentFileName; |
54 | } |
55 | |
56 | void HLSSegmenter::ourEndOfSegmentHandler(void* clientData, double segmentDuration) { |
57 | ((HLSSegmenter*)clientData)->ourEndOfSegmentHandler(segmentDuration); |
58 | } |
59 | |
60 | void HLSSegmenter::ourEndOfSegmentHandler(double segmentDuration) { |
61 | // Note the end of the current segment: |
62 | if (fOnEndOfSegmentFunc != NULL) { |
63 | (*fOnEndOfSegmentFunc)(fOnEndOfSegmentClientData, fOutputSegmentFileName, segmentDuration); |
64 | } |
65 | |
66 | // Begin the next segment: |
67 | ++fCurrentSegmentCounter; |
68 | openNextOutputSegment(); |
69 | } |
70 | |
71 | Boolean HLSSegmenter::openNextOutputSegment() { |
72 | CloseOutputFile(fOutFid); |
73 | |
74 | sprintf(fOutputSegmentFileName, "%s%03u.ts" , fFileNamePrefix, fCurrentSegmentCounter); |
75 | fOutFid = OpenOutputFile(envir(), fOutputSegmentFileName); |
76 | |
77 | return fOutFid != NULL; |
78 | } |
79 | |
80 | void HLSSegmenter::afterGettingFrame(void* clientData, unsigned frameSize, |
81 | unsigned numTruncatedBytes, |
82 | struct timeval /*presentationTime*/, |
83 | unsigned /*durationInMicroseconds*/) { |
84 | ((HLSSegmenter*)clientData)->afterGettingFrame(frameSize, numTruncatedBytes); |
85 | } |
86 | |
87 | void HLSSegmenter::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes) { |
88 | if (numTruncatedBytes > 0) { // Shouldn't happen |
89 | fprintf(stderr, "HLSSegmenter::afterGettingFrame(frameSize %d, numTruncatedBytes %d)\n" , frameSize, numTruncatedBytes); |
90 | } |
91 | |
92 | // Write the data to out output segment file: |
93 | fwrite(fOutputFileBuffer, 1, frameSize, fOutFid); |
94 | |
95 | // Then try getting the next frame: |
96 | continuePlaying(); |
97 | } |
98 | |
99 | void HLSSegmenter::ourOnSourceClosure(void* clientData) { |
100 | ((HLSSegmenter*)clientData)->ourOnSourceClosure(); |
101 | } |
102 | |
103 | void HLSSegmenter::ourOnSourceClosure() { |
104 | // Note the end of the final segment (currently being written): |
105 | if (fOnEndOfSegmentFunc != NULL) { |
106 | // We know that the source is a "MPEG2TransportStreamMultiplexor": |
107 | MPEG2TransportStreamMultiplexor* multiplexorSource = (MPEG2TransportStreamMultiplexor*)fSource; |
108 | double segmentDuration = multiplexorSource->currentSegmentDuration(); |
109 | |
110 | (*fOnEndOfSegmentFunc)(fOnEndOfSegmentClientData, fOutputSegmentFileName, segmentDuration); |
111 | } |
112 | |
113 | // Handle the closure for real: |
114 | onSourceClosure(); |
115 | } |
116 | |
117 | Boolean HLSSegmenter::sourceIsCompatibleWithUs(MediaSource& source) { |
118 | // Our source must be a Transport Stream Multiplexor: |
119 | return source.isMPEG2TransportStreamMultiplexor(); |
120 | } |
121 | |
122 | Boolean HLSSegmenter::continuePlaying() { |
123 | if (fSource == NULL) return False; |
124 | if (!fHaveConfiguredUpstreamSource) { |
125 | // We know that the source is a "MPEG2TransportStreamMultiplexor": |
126 | MPEG2TransportStreamMultiplexor* multiplexorSource = (MPEG2TransportStreamMultiplexor*)fSource; |
127 | |
128 | // Tell our upstream multiplexor to call our 'end of segment handler' at the end of |
129 | // each timed segment: |
130 | multiplexorSource->setTimedSegmentation(fSegmentationDuration, ourEndOfSegmentHandler, this); |
131 | |
132 | fHaveConfiguredUpstreamSource = True; // from now on |
133 | } |
134 | if (fOutFid == NULL && !openNextOutputSegment()) return False; |
135 | |
136 | fSource->getNextFrame(fOutputFileBuffer, OUTPUT_FILE_BUFFER_SIZE, |
137 | afterGettingFrame, this, |
138 | ourOnSourceClosure, this); |
139 | |
140 | return True; |
141 | } |
142 | |