| /********** |
| 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; |
| } |