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 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
30HLSSegmenter* 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
38HLSSegmenter::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}
51HLSSegmenter::~HLSSegmenter() {
52 delete[] fOutputFileBuffer;
53 delete[] fOutputSegmentFileName;
54}
55
56void HLSSegmenter::ourEndOfSegmentHandler(void* clientData, double segmentDuration) {
57 ((HLSSegmenter*)clientData)->ourEndOfSegmentHandler(segmentDuration);
58}
59
60void 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
71Boolean 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
80void HLSSegmenter::afterGettingFrame(void* clientData, unsigned frameSize,
81 unsigned numTruncatedBytes,
82 struct timeval /*presentationTime*/,
83 unsigned /*durationInMicroseconds*/) {
84 ((HLSSegmenter*)clientData)->afterGettingFrame(frameSize, numTruncatedBytes);
85}
86
87void 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
99void HLSSegmenter::ourOnSourceClosure(void* clientData) {
100 ((HLSSegmenter*)clientData)->ourOnSourceClosure();
101}
102
103void 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
117Boolean HLSSegmenter::sourceIsCompatibleWithUs(MediaSource& source) {
118 // Our source must be a Transport Stream Multiplexor:
119 return source.isMPEG2TransportStreamMultiplexor();
120}
121
122Boolean 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