blob: b5107ae95c117bba53051a12d4379a46da540830 [file] [log] [blame]
/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
**********/
// "liveMedia"
// Copyright (c) 1996-2020 Live Networks, Inc. All rights reserved.
// A media sink that takes - as input - a MPEG Transport Stream, and outputs a series
// of MPEG Transport Stream files, each representing a segment of the input stream,
// suitable for HLS (Apple's "HTTP Live Streaming").
// Implementation
#include "HLSSegmenter.hh"
#include "OutputFile.hh"
#include "MPEG2TransportStreamMultiplexor.hh"
#define TRANSPORT_PACKET_SIZE 188
#define OUTPUT_FILE_BUFFER_SIZE (TRANSPORT_PACKET_SIZE*100)
HLSSegmenter* HLSSegmenter
::createNew(UsageEnvironment& env,
unsigned segmentationDuration, char const* fileNamePrefix,
onEndOfSegmentFunc* onEndOfSegmentFunc, void* onEndOfSegmentClientData) {
return new HLSSegmenter(env, segmentationDuration, fileNamePrefix,
onEndOfSegmentFunc, onEndOfSegmentClientData);
}
HLSSegmenter::HLSSegmenter(UsageEnvironment& env,
unsigned segmentationDuration, char const* fileNamePrefix,
onEndOfSegmentFunc* onEndOfSegmentFunc, void* onEndOfSegmentClientData)
: MediaSink(env),
fSegmentationDuration(segmentationDuration), fFileNamePrefix(fileNamePrefix),
fOnEndOfSegmentFunc(onEndOfSegmentFunc), fOnEndOfSegmentClientData(onEndOfSegmentClientData),
fHaveConfiguredUpstreamSource(False), fCurrentSegmentCounter(1), fOutFid(NULL) {
// Allocate enough space for the segment file name:
fOutputSegmentFileName = new char[strlen(fileNamePrefix) + 20/*more than enough*/];
// Allocate the output file buffer size:
fOutputFileBuffer = new unsigned char[OUTPUT_FILE_BUFFER_SIZE];
}
HLSSegmenter::~HLSSegmenter() {
delete[] fOutputFileBuffer;
delete[] fOutputSegmentFileName;
}
void HLSSegmenter::ourEndOfSegmentHandler(void* clientData, double segmentDuration) {
((HLSSegmenter*)clientData)->ourEndOfSegmentHandler(segmentDuration);
}
void HLSSegmenter::ourEndOfSegmentHandler(double segmentDuration) {
// Note the end of the current segment:
if (fOnEndOfSegmentFunc != NULL) {
(*fOnEndOfSegmentFunc)(fOnEndOfSegmentClientData, fOutputSegmentFileName, segmentDuration);
}
// Begin the next segment:
++fCurrentSegmentCounter;
openNextOutputSegment();
}
Boolean HLSSegmenter::openNextOutputSegment() {
CloseOutputFile(fOutFid);
sprintf(fOutputSegmentFileName, "%s%03u.ts", fFileNamePrefix, fCurrentSegmentCounter);
fOutFid = OpenOutputFile(envir(), fOutputSegmentFileName);
return fOutFid != NULL;
}
void HLSSegmenter::afterGettingFrame(void* clientData, unsigned frameSize,
unsigned numTruncatedBytes,
struct timeval /*presentationTime*/,
unsigned /*durationInMicroseconds*/) {
((HLSSegmenter*)clientData)->afterGettingFrame(frameSize, numTruncatedBytes);
}
void HLSSegmenter::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes) {
if (numTruncatedBytes > 0) { // Shouldn't happen
fprintf(stderr, "HLSSegmenter::afterGettingFrame(frameSize %d, numTruncatedBytes %d)\n", frameSize, numTruncatedBytes);
}
// Write the data to our output segment file:
fwrite(fOutputFileBuffer, 1, frameSize, fOutFid);
// Then try getting the next frame:
continuePlaying();
}
void HLSSegmenter::ourOnSourceClosure(void* clientData) {
((HLSSegmenter*)clientData)->ourOnSourceClosure();
}
void HLSSegmenter::ourOnSourceClosure() {
// Note the end of the final segment (currently being written):
if (fOnEndOfSegmentFunc != NULL) {
// We know that the source is a "MPEG2TransportStreamMultiplexor":
MPEG2TransportStreamMultiplexor* multiplexorSource = (MPEG2TransportStreamMultiplexor*)fSource;
double segmentDuration = multiplexorSource->currentSegmentDuration();
(*fOnEndOfSegmentFunc)(fOnEndOfSegmentClientData, fOutputSegmentFileName, segmentDuration);
}
// Handle the closure for real:
onSourceClosure();
}
Boolean HLSSegmenter::sourceIsCompatibleWithUs(MediaSource& source) {
// Our source must be a Transport Stream Multiplexor:
return source.isMPEG2TransportStreamMultiplexor();
}
Boolean HLSSegmenter::continuePlaying() {
if (fSource == NULL) return False;
if (!fHaveConfiguredUpstreamSource) {
// We know that the source is a "MPEG2TransportStreamMultiplexor":
MPEG2TransportStreamMultiplexor* multiplexorSource = (MPEG2TransportStreamMultiplexor*)fSource;
// Tell our upstream multiplexor to call our 'end of segment handler' at the end of
// each timed segment:
multiplexorSource->setTimedSegmentation(fSegmentationDuration, ourEndOfSegmentHandler, this);
fHaveConfiguredUpstreamSource = True; // from now on
}
if (fOutFid == NULL && !openNextOutputSegment()) return False;
fSource->getNextFrame(fOutputFileBuffer, OUTPUT_FILE_BUFFER_SIZE,
afterGettingFrame, this,
ourOnSourceClosure, this);
return True;
}