142 lines
5.4 KiB
C++
142 lines
5.4 KiB
C++
/**********
|
|
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;
|
|
}
|