Safari or apple in general is very picky about the segment length and what
is in them. I did get this to work but had to modify one of the live 555
classes. It was a few years ago but I did send it to this list as a
suggestion, so it is in the history somewhere.  While the standard allows
you to not start a segment on a keyframe if already going, the previous
segment MUST have decoded and played. If the browser drops the ball it gets
lost. So the solution I chose was to make every segment start on a keyframe.

HTTP live streaming requires the stream to be broken into segments of equal
segment lengths. 10 second is the default, I have used 5 and 2. The index
file claims the segment length and the segments cannot very much at all.
In order for this to work reliably with H264 video you need to start each
segment on a keyframe.  Currently the part of the code for transport stream
inserts the PES** frames at a timed interval that is not tied to the
keyframe interval.  I changed the attached classes and had them working
before we abandoned this for an app using live555 and rtsp on the device
themselfs. HTTP live streaming imparts a delay on live video that was
unacceptable for security camera video in our use case.

I was doing my own segmenting and index creation on the fly because I was
trying to use it for live video to the ipad,iphone, android. Safari was my
desktop test.  It became trivial once I knew I had a gop size of 10 @ 10fps
to start a segment on a keyframe and to start a new one on the segment
length * gop size +1 PTS/DTS/keyframe packet was encountered. Essentially
payed it forward. Stick in the PTS at index friendly intervals then the
downstream indexer just worked.

** sorry this is off the top of my head, PTS? I can dig deeper if necessary
after work. I see the files in an old directory and am attaching them.
Please note this was years ago, Ross may have already changed this code. I
have not followed as I have dropped interest in HTTP live streaming.  There
is a new tech, extremely similar to HTTP live streaming without the index
file and latency issues that mirrors my own solution already out there VP8
+ HTML5, so I am a bit surprized to see a question about HLS.

On Wed, Dec 10, 2014 at 3:42 AM, Ross Finlayson <[email protected]>
wrote:

> I have use the test application "testH264VideoToTransportStream" to change
> the H264 stream to transport stream, after this step I get a out.ts file,
> then I use "MPEG2TransportStreamIndexer" to create an index file out.txs
> for the out.ts file, after those two steps, I use Safari browser to request
> the transport stream but failed.
> what is wrong with those?
> I have also downloaded the bipbop-gear1-all.ts, and use
> "MPEG2TransportStreamIndexer" to create the index file, this process is
> succeed, do this means the "MPEG2TransportStreamIndexer" is correct, but
> there is something wrong with the H264VideoToTransportStream process?
>
>
> Probably not.  It’s unlikely that there is anything ‘wrong’ with the
> “H264VideoToTransportStream” mechanism, although it’s possible that (for
> some unknown reason) the type of Transport Stream that it produces does not
> satisfy Safari.  It is also possible that the problem is that the H.264
> ‘profile’ that your stream uses does not match the profile that Safari
> requires.  See
>
> https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/StreamingMediaGuide/UsingHTTPLiveStreaming/UsingHTTPLiveStreaming.html
> Unfortunately this is not something that we can help you with.  You will
> need to figure out (with Apple, using some appropriate Apple-specific
> mailing list; not this one) exactly what is wrong with your Transport
> Stream.
>
>
> I really need your help, and please response this email as soon as
> possible!
>
>
> Sorry, but requests posted to this free mailing list are never considered
> ‘urgent’.  Only paying consulting clients get priority support.  (Live
> Networks, Inc. is not a charity.)
>
> Also (as clearly stated in the FAQ that everyone is asked to read before
> posting to this mailing list), messages sent using unprofessional email
> addresses (like “@163.c <http://i63.net>om”) are considered very low
> priority, and might not get answered at all.
>
>
> Ross Finlayson
> Live Networks, Inc.
> http://www.live555.com/
>
>
>
> _______________________________________________
> live-devel mailing list
> [email protected]
> http://lists.live555.com/mailman/listinfo/live-devel
>
>
/**********
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 2.1 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-2011 Live Networks, Inc.  All rights reserved.
// A filter for converting one or more MPEG Elementary Streams
// to a MPEG-2 Transport Stream
// Implementation

#include <Media/MPEG2TransportStreamFromESSource4iOS.hh>

#define MAX_INPUT_ES_FRAME_SIZE 50000
//#define SIMPLE_PES_HEADER_SIZE 14
#define SIMPLE_PES_HEADER_SIZE 19 //JFS ipad tweaks
#define LOW_WATER_MARK 128 // <= MAX_INPUT_ES_FRAME_SIZE
#define INPUT_BUFFER_SIZE (SIMPLE_PES_HEADER_SIZE + 2*MAX_INPUT_ES_FRAME_SIZE)

////////// InputESSourceRecord4iOS definition //////////

class InputESSourceRecord4iOS {
public:
  InputESSourceRecord4iOS(MPEG2TransportStreamFromESSource4iOS& parent,
              FramedSource* inputSource,
              u_int8_t streamId, int mpegVersion,
              InputESSourceRecord4iOS* next);
  virtual ~InputESSourceRecord4iOS();

  InputESSourceRecord4iOS* next() const { return fNext; }
  FramedSource* inputSource() const { return fInputSource; }

  void askForNewData();
  Boolean deliverBufferToClient();

  unsigned char* buffer() const { return fInputBuffer; }
  void reset() {
    // Reset the buffer for future use:
    fInputBufferBytesAvailable = 0;
    fInputBufferInUse = False;
  }

private:
  static void afterGettingFrame(void* clientData, unsigned frameSize,
                                unsigned numTruncatedBytes,
                                struct timeval presentationTime,
                                unsigned durationInMicroseconds);
  void afterGettingFrame1(unsigned frameSize,
                          unsigned numTruncatedBytes,
                          struct timeval presentationTime);

private:
  InputESSourceRecord4iOS* fNext;
  MPEG2TransportStreamFromESSource4iOS& fParent;
  FramedSource* fInputSource;
  u_int8_t fStreamId;
  int fMPEGVersion;
  unsigned char* fInputBuffer;
  unsigned fInputBufferBytesAvailable;
  Boolean fInputBufferInUse;
  MPEG1or2Demux::SCR fSCR;
};


////////// MPEG2TransportStreamFromESSource4iOS implementation //////////

MPEG2TransportStreamFromESSource4iOS* MPEG2TransportStreamFromESSource4iOS
::createNew(UsageEnvironment& env , unsigned SegmentMarkerFrequency) {
  return new MPEG2TransportStreamFromESSource4iOS(env, SegmentMarkerFrequency);
}

void MPEG2TransportStreamFromESSource4iOS
::addNewVideoSource(FramedSource* inputSource, int mpegVersion) {
  u_int8_t streamId = 0xE0 | (fVideoSourceCounter++&0x0F);
  addNewInputSource(inputSource, streamId, mpegVersion);
  fHaveVideoStreams = True;
}

void MPEG2TransportStreamFromESSource4iOS
::addNewAudioSource(FramedSource* inputSource, int mpegVersion) {
  u_int8_t streamId = 0xC0 | (fAudioSourceCounter++&0x0F);
  addNewInputSource(inputSource, streamId, mpegVersion);
}

MPEG2TransportStreamFromESSource4iOS
::MPEG2TransportStreamFromESSource4iOS(UsageEnvironment& env, unsigned smf)
  : MPEG2TransportStreamMultiplexor4iOS(env,smf),
    fInputSources(NULL), fVideoSourceCounter(0), fAudioSourceCounter(0) {
  fHaveVideoStreams = False; // unless we add a video source
}

MPEG2TransportStreamFromESSource4iOS::~MPEG2TransportStreamFromESSource4iOS() {
  delete fInputSources;
}

void MPEG2TransportStreamFromESSource4iOS::doStopGettingFrames() {
  // Stop each input source:
  for (InputESSourceRecord4iOS* sourceRec = fInputSources; sourceRec != NULL;
       sourceRec = sourceRec->next()) {
    sourceRec->inputSource()->stopGettingFrames();
  }
}

void MPEG2TransportStreamFromESSource4iOS
::awaitNewBuffer(unsigned char* oldBuffer) {
  InputESSourceRecord4iOS* sourceRec;
  // Begin by resetting the old buffer:
  if (oldBuffer != NULL) {
    for (sourceRec = fInputSources; sourceRec != NULL;
     sourceRec = sourceRec->next()) {
      if (sourceRec->buffer() == oldBuffer) {
    sourceRec->reset();
    break;
      }
    }
  }

  if (isCurrentlyAwaitingData()) {
    // Try to deliver one filled-in buffer to the client:
    for (sourceRec = fInputSources; sourceRec != NULL;
     sourceRec = sourceRec->next()) {
      if (sourceRec->deliverBufferToClient()) break;
    }
  }

  // No filled-in buffers are available. Ask each of our inputs for data:
  for (sourceRec = fInputSources; sourceRec != NULL;
       sourceRec = sourceRec->next()) {
    sourceRec->askForNewData();
  }

}

void MPEG2TransportStreamFromESSource4iOS
::addNewInputSource(FramedSource* inputSource,
            u_int8_t streamId, int mpegVersion) {
  if (inputSource == NULL) return;
  fInputSources = new InputESSourceRecord4iOS(*this, inputSource, streamId,
                      mpegVersion, fInputSources);
}


////////// InputESSourceRecord4iOS implementation //////////

InputESSourceRecord4iOS
::InputESSourceRecord4iOS(MPEG2TransportStreamFromESSource4iOS& parent,
              FramedSource* inputSource,
              u_int8_t streamId, int mpegVersion,
              InputESSourceRecord4iOS* next)
  : fNext(next), fParent(parent), fInputSource(inputSource),
    fStreamId(streamId), fMPEGVersion(mpegVersion) {
  fInputBuffer = new unsigned char[INPUT_BUFFER_SIZE];
  reset();
}

InputESSourceRecord4iOS::~InputESSourceRecord4iOS() {
  Medium::close(fInputSource);
  delete[] fInputBuffer;
  delete fNext;
}

void InputESSourceRecord4iOS::askForNewData() {
  if (fInputBufferInUse) return;

  if (fInputBufferBytesAvailable == 0) {
    // Reset our buffer, by adding a simple PES header at the start:
    fInputBuffer[0] = 0; fInputBuffer[1] = 0; fInputBuffer[2] = 1;
    fInputBuffer[3] = fStreamId;
    fInputBuffer[4] = 0; fInputBuffer[5] = 0; // fill in later with the length

    //original
    //fInputBuffer[6] = 0x80;
    //fInputBuffer[7] = 0x80; // include a PTS
    //fInputBuffer[8] = 10; // PES_header_data_length (enough for a PTS)
    //// fInputBuffer[9..13] will be the PTS; fill this in later

    fInputBuffer[6] = 0x84; //JFS enable data_alignment bit
    fInputBuffer[7] = 0xC0; // include a PTS and DTS
    fInputBuffer[8] = 10; // PES_header_data_length (enough for a PTS)
    // fInputBuffer[9..13] will be the PTS; fill this in later
    // fInputBuffer[14..18] will be the PTS; fill this in later
    fInputBufferBytesAvailable = SIMPLE_PES_HEADER_SIZE;
  }
 // if (fInputBufferBytesAvailable < LOW_WATER_MARK &&
  //    !fInputSource->isCurrentlyAwaitingData()) {

    if (!fInputSource->isCurrentlyAwaitingData()) {
    // We don't yet have enough data in our buffer.  Arrange to read more:
    fInputSource->getNextFrame(&fInputBuffer[fInputBufferBytesAvailable],
                               INPUT_BUFFER_SIZE-fInputBufferBytesAvailable,
                               afterGettingFrame, this,
                               FramedSource::handleClosure, &fParent);
  }
}

Boolean InputESSourceRecord4iOS::deliverBufferToClient() {
  if (fInputBufferInUse || fInputBufferBytesAvailable < LOW_WATER_MARK) return False;
   // if (fInputBufferInUse) return False;

  // Fill in the PES_packet_length field that we left unset before:
  unsigned PES_packet_length = fInputBufferBytesAvailable - 6;
  if (PES_packet_length > 0xFFFF) {
    // Set the PES_packet_length field to 0.  This indicates an unbounded length (see ISO 13818-1, 2.4.3.7)
    PES_packet_length = 0;
  }
  fInputBuffer[4] = PES_packet_length>>8;
  fInputBuffer[5] = PES_packet_length;

  // it is allowed to be zero, lets try that in case error in PES size breaks ipad
  //fInputBuffer[4] = 0;
  //fInputBuffer[5] = 0;

  // Fill in the PES PTS (from our SCR):
  fInputBuffer[9] = 0x30|(fSCR.highBit<<3)|(fSCR.remainingBits>>29)|0x01;
  fInputBuffer[10] = fSCR.remainingBits>>22;
  fInputBuffer[11] = (fSCR.remainingBits>>14)|0x01;
  fInputBuffer[12] = fSCR.remainingBits>>7;
  fInputBuffer[13] = (fSCR.remainingBits<<1)|0x01;

   // Fill in the PES DTS (from our SCR):
  fInputBuffer[14] = 0x10|(fSCR.highBit<<3)|(fSCR.remainingBits>>29)|0x01;
  fInputBuffer[15] = fSCR.remainingBits>>22;
  fInputBuffer[16] = (fSCR.remainingBits>>14)|0x01;
  fInputBuffer[17] = fSCR.remainingBits>>7;
  fInputBuffer[18] = (fSCR.remainingBits<<1)|0x01;

  fInputBufferInUse = True;

  // Do the delivery:
  fParent.handleNewBuffer(fInputBuffer, fInputBufferBytesAvailable,
             fMPEGVersion, fSCR);

  return True;
}

void InputESSourceRecord4iOS
::afterGettingFrame(void* clientData, unsigned frameSize,
            unsigned numTruncatedBytes,
            struct timeval presentationTime,
            unsigned /*durationInMicroseconds*/) {
  InputESSourceRecord4iOS* source = (InputESSourceRecord4iOS*)clientData;
  source->afterGettingFrame1(frameSize, numTruncatedBytes, presentationTime);
}
void InputESSourceRecord4iOS
::afterGettingFrame1(unsigned frameSize, unsigned numTruncatedBytes,
             struct timeval presentationTime) {
  if (numTruncatedBytes > 0) {
    fParent.envir() << "MPEG2TransportStreamFromESSource4iOS: input buffer too small; increase \"MAX_INPUT_ES_FRAME_SIZE\" in \"MPEG2TransportStreamFromESSource4iOS\" by at least "
            << numTruncatedBytes << " bytes!\n";
  }

  if (fInputBufferBytesAvailable == SIMPLE_PES_HEADER_SIZE) {
    // Use this presentationTime for our SCR:
    fSCR.highBit
      = ((presentationTime.tv_sec*45000 + (presentationTime.tv_usec*9)/200)&
     0x80000000) != 0;
    fSCR.remainingBits
      = presentationTime.tv_sec*90000 + (presentationTime.tv_usec*9)/100;
    fSCR.extension = (presentationTime.tv_usec*9)%100;
#ifdef DEBUG_SCR
    fprintf(stderr, "PES header: stream_id 0x%02x, pts: %u.%06u => SCR 0x%x%08x:%03x\n", fStreamId, (unsigned)presentationTime.tv_sec, (unsigned)presentationTime.tv_usec, fSCR.highBit, fSCR.remainingBits, fSCR.extension);
#endif
  }

  fInputBufferBytesAvailable += frameSize;

  fParent.fPresentationTime = presentationTime;

  // Now that we have new input data, check if we can deliver to the client:
  fParent.awaitNewBuffer(NULL);
}

Attachment: MPEG2TransportStreamFromESSource4iOS.hh
Description: Binary data

/**********
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 2.1 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-2011 Live Networks, Inc.  All rights reserved.
// A class for generating MPEG-2 Transport Stream from one or more input
// Elementary Stream data sources
// Implementation


#include <Media/MPEG2TransportStreamMultiplexor4iOS.hh>
#include <stdint.h>


#define TRANSPORT_PACKET_SIZE 188

//#define PAT_FREQUENCY 100 // # of packets between Program Association Tables
//#define PMT_FREQUENCY 500 // # of packets between Program Map Tables

//#define PID_TABLE_SIZE 8192


MPEG2TransportStreamMultiplexor4iOS
::MPEG2TransportStreamMultiplexor4iOS(UsageEnvironment& env,unsigned SegmentMarkerFrequency)
  : FramedSource(env),
    mpeg4Version_(4),
    fSegmentMarkerFrequency(SegmentMarkerFrequency),
    fHaveVideoStreams(True/*by default*/),
    fOutgoingPacketCounter(0), fProgramMapVersion(0),
    fPreviousInputProgramMapVersion(0xFF), fCurrentInputProgramMapVersion(0xFF),
    fPCR_PID(0), fCurrentPID(0),
    fInputBuffer(NULL), fInputBufferSize(0), fInputBufferBytesUsed(0),
    fIsFirstAdaptationField(True) {
  for (unsigned i = 0; i < PID_TABLE_SIZE; ++i) {
    fPIDState[i].counter = 0;
    fPIDState[i].keyframe = 0;
    fPIDState[i].segmentState_ = MPEG2TransportStreamMultiplexor4iOS::NEED_PAT;
    fPIDState[i].streamType = 0;
  }
}

MPEG2TransportStreamMultiplexor4iOS::~MPEG2TransportStreamMultiplexor4iOS() {
}

void MPEG2TransportStreamMultiplexor4iOS::doGetNextFrame() 
{
    if (fInputBufferBytesUsed >= fInputBufferSize) 
    {
        // No more bytes are available from the current buffer.
        // Arrange to read a new one.
        awaitNewBuffer(fInputBuffer);
        
        return;
    }

    bool SEGMENT_TRACE = true;
    do {

        if (fPIDState[fCurrentPID].segmentState_ == MPEG2TransportStreamMultiplexor4iOS::NEED_PMT)
        {
            deliverPMTPacket(false);
            fPIDState[fCurrentPID].segmentState_ = MPEG2TransportStreamMultiplexor4iOS::NEED_KEYFRAME; 
            break;
        }

        if (fInputBufferBytesUsed == 0)
        {
            //if keyframe bump seconds
            //if seconds>segment size insert PAT and PMT before the keyframe.
            int offset = 3;
            int streamID = fInputBuffer[offset];
            offset += 3;
            bool DataAlignmentIndicator = (fInputBuffer[offset] & 0x04) == 4;
            if (DataAlignmentIndicator && (streamID ==  0xE0))
            {
                //if (SEGMENT_TRACE) envir() << "Beggining of video payload \n";
                offset += 2;
                int PES_HeaderLength = fInputBuffer[offset];

                offset += PES_HeaderLength + 4;
                //skip over access_unit_delimeter
                offset += 6;
                //uint8_t naltypeByte = fInputBuffer[offset];
                // If key frame, start new second. 
                // If newsecond is greater than duration, start a new segment

                //if ((naltypeByte & 0x1f) == 7)
                uint8_t * frameStart = &fInputBuffer[offset];
                if (isKeyFrame(frameStart,mpeg4Version_))
                {
                    if (fPIDState[fCurrentPID].keyframe >= fSegmentMarkerFrequency)
                    { 
                       fPIDState[fCurrentPID].keyframe = 0;
                       if (fPIDState[fCurrentPID].segmentState_ == MPEG2TransportStreamMultiplexor4iOS::NEED_PAT)
                        {
                            deliverPATPacket();
                            fPIDState[fCurrentPID].segmentState_ = MPEG2TransportStreamMultiplexor4iOS::NEED_PMT;
                            break;
                        }else //NEED_KEYFRAME
                        {
                            fPIDState[fCurrentPID].segmentState_ = MPEG2TransportStreamMultiplexor4iOS::NEED_PAT;
                            fPIDState[fCurrentPID].keyframe++;
                            break;
                        }
                    }else
                        fPIDState[fCurrentPID].keyframe++;
                }
                else
                {
                    fPIDState[fCurrentPID].segmentState_ = MPEG2TransportStreamMultiplexor4iOS::NEED_PAT;
                }

            }

            // Periodically (or when we see a new PID) return a Program Map Table instead:
            Boolean programMapHasChanged = fCurrentInputProgramMapVersion != fPreviousInputProgramMapVersion;
            if (programMapHasChanged) 
            { // reset values for next time:
                fPIDState[fCurrentPID].counter = 1;
                fPreviousInputProgramMapVersion = fCurrentInputProgramMapVersion;
                deliverPMTPacket(true);
                break;
            }
        }

        // Normal case: Deliver (or continue delivering) the recently-read data:
        deliverDataToClient(fCurrentPID, fInputBuffer, fInputBufferSize, fInputBufferBytesUsed);

    } while (0);

  // NEED TO SET fPresentationTime, durationInMicroseconds #####
  // Complete the delivery to the client:
    envir().taskScheduler().scheduleDelayedTask(0,(TaskFunc*)afterGetting,this);
  //afterGetting(this);
}

void MPEG2TransportStreamMultiplexor4iOS
::handleNewBuffer(unsigned char* buffer, unsigned bufferSize,
          int mpegVersion, MPEG1or2Demux::SCR scr) {
  mpeg4Version_ = mpegVersion;
  if (bufferSize < 4) return;
  fInputBuffer = buffer;
  fInputBufferSize = bufferSize;
  fInputBufferBytesUsed = 0;

  u_int8_t stream_id = fInputBuffer[3];
  // Use "stream_id" directly as our PID.
  // Also, figure out the Program Map 'stream type' from this.
  if (stream_id == 0xBE) { // padding_stream; ignore
    fInputBufferSize = 0;
  } else if (stream_id == 0xBC) { // program_stream_map
    setProgramStreamMap(fInputBufferSize);
    fInputBufferSize = 0; // then, ignore the buffer
  } else {
    fCurrentPID = stream_id;

    // Set the stream's type:
    u_int8_t& streamType = fPIDState[fCurrentPID].streamType; // alias

    if (streamType == 0) {
      // Instead, set the stream's type to default values, based on whether
      // the stream is audio or video, and whether it's MPEG-1 or MPEG-2:
      if ((stream_id&0xF0) == 0xE0) { // video
    streamType = mpegVersion == 1 ? 1 : mpegVersion == 2 ? 2 : mpegVersion == 4 ? 0x10 : 0x1B;
      } else if ((stream_id&0xE0) == 0xC0) { // audio
    streamType = mpegVersion == 1 ? 3 : mpegVersion == 2 ? 4 : 0xF;
      } else if (stream_id == 0xBD) { // private_stream1 (usually AC-3)
    streamType = 0x06; // for DVB; for ATSC, use 0x81
      } else { // something else, e.g., AC-3 uses private_stream1 (0xBD)
    streamType = 0x81; // private
      }
    }

    if (fPCR_PID == 0) { // set it to this stream, if it's appropriate:
      if ((!fHaveVideoStreams && (streamType == 3 || streamType == 4 || streamType == 0xF))/* audio stream */ ||
      (streamType == 1 || streamType == 2 || streamType == 0x10 || streamType == 0x1B)/* video stream */) {
    fPCR_PID = fCurrentPID; // use this stream's SCR for PCR
      }
    }
    if (fCurrentPID == fPCR_PID) {
      // Record the input's current SCR timestamp, for use as our PCR:
      fPCR = scr;
    }
  }

  // Now that we have new input data, retry the last delivery to the client:
  doGetNextFrame();
}

void MPEG2TransportStreamMultiplexor4iOS
::deliverDataToClient(u_int16_t pid, unsigned char* buffer, unsigned bufferSize,
              unsigned& startPositionInBuffer) {
  // Construct a new Transport packet, and deliver it to the client:
  if (fMaxSize < TRANSPORT_PACKET_SIZE) {
    fFrameSize = 0; // the client hasn't given us enough space; deliver nothing
    fNumTruncatedBytes = TRANSPORT_PACKET_SIZE;
  } else {
    fFrameSize = TRANSPORT_PACKET_SIZE;
    Boolean willAddPCR = pid == fPCR_PID && startPositionInBuffer == 0
      && !(fPCR.highBit == 0 && fPCR.remainingBits == 0 && fPCR.extension == 0);
    unsigned const numBytesAvailable = bufferSize - startPositionInBuffer;
    unsigned numHeaderBytes = 4; // by default
    unsigned numPCRBytes = 0; // by default
    unsigned numPaddingBytes = 0; // by default
    unsigned numDataBytes;
    u_int8_t adaptation_field_control;
    if (willAddPCR) {
      adaptation_field_control = 0x30;
      numHeaderBytes += 2; // for the "adaptation_field_length" and flags
      numPCRBytes = 6;
      if (numBytesAvailable >= TRANSPORT_PACKET_SIZE - numHeaderBytes - numPCRBytes) {
    numDataBytes = TRANSPORT_PACKET_SIZE - numHeaderBytes - numPCRBytes;
      } else {
    numDataBytes = numBytesAvailable;
    numPaddingBytes
      = TRANSPORT_PACKET_SIZE - numHeaderBytes - numPCRBytes - numDataBytes;
      }
    } else if (numBytesAvailable >= TRANSPORT_PACKET_SIZE - numHeaderBytes) {
      // This is the common case
      adaptation_field_control = 0x10;
      numDataBytes = TRANSPORT_PACKET_SIZE - numHeaderBytes;
    } else {
      adaptation_field_control = 0x30;
      ++numHeaderBytes; // for the "adaptation_field_length"
      // ASSERT: numBytesAvailable <= TRANSPORT_PACKET_SIZE - numHeaderBytes
      numDataBytes = numBytesAvailable;
      if (numDataBytes < TRANSPORT_PACKET_SIZE - numHeaderBytes) {
    ++numHeaderBytes; // for the adaptation field flags
    numPaddingBytes = TRANSPORT_PACKET_SIZE - numHeaderBytes - numDataBytes;
      }
    }
    // ASSERT: numHeaderBytes+numPCRBytes+numPaddingBytes+numDataBytes
    //         == TRANSPORT_PACKET_SIZE

    // Fill in the header of the Transport Stream packet:
    unsigned char* header = fTo;
    *header++ = 0x47; // sync_byte
    *header  = (startPositionInBuffer == 0) ? 0x40 : 0x00;// transport_error_indicator, payload_unit_start_indicator, transport_priority,
    *header++ |= ((pid & 0x01fff) >> 8);   // first 5 bits of PID
    *header++ = (pid & 0x00ff);           // last 8 bits of PID
    unsigned& continuity_counter = fPIDState[pid].counter; // alias
    *header++ = adaptation_field_control|(continuity_counter&0x0F);
      // transport_scrambling_control, adaptation_field_control, continuity_counter
    ++continuity_counter;
    if (adaptation_field_control == 0x30) {
      // Add an adaptation field:
      u_int8_t adaptation_field_length
    = (numHeaderBytes == 5) ? 0 : 1 + numPCRBytes + numPaddingBytes;
      *header++ = adaptation_field_length;
      if (numHeaderBytes > 5) {
    u_int8_t flags = willAddPCR ? 0x10 : 0x00;
    if (fIsFirstAdaptationField) {
      flags |= 0x80; // discontinuity_indicator
      fIsFirstAdaptationField = False;
    }
    *header++ = flags;
    if (willAddPCR) {
      u_int32_t pcrHigh32Bits = (fPCR.highBit<<31) | (fPCR.remainingBits>>1);
      u_int8_t pcrLowBit = fPCR.remainingBits&1;
      u_int8_t extHighBit = (fPCR.extension&0x100)>>8;
      *header++ = pcrHigh32Bits>>24;
      *header++ = pcrHigh32Bits>>16;
      *header++ = pcrHigh32Bits>>8;
      *header++ = pcrHigh32Bits;
      *header++ = (pcrLowBit<<7)|0x7E|extHighBit;
      *header++ = (u_int8_t)fPCR.extension; // low 8 bits of extension
    }
      }
    }

    // Add any padding bytes:
    for (unsigned i = 0; i < numPaddingBytes; ++i) *header++ = 0xFF;

    // Finally, add the data bytes:
    memmove(header, &buffer[startPositionInBuffer], numDataBytes);
    startPositionInBuffer += numDataBytes;
  }
}

static u_int32_t calculateCRC(u_int8_t* data, unsigned dataLength); // forward

#define PAT_PID 0
#define OUR_PROGRAM_NUMBER 1
#define OUR_PROGRAM_MAP_PID 0x123

void MPEG2TransportStreamMultiplexor4iOS::deliverPATPacket() {
  // First, create a new buffer for the PAT packet:
  unsigned const patSize = TRANSPORT_PACKET_SIZE - 4; // allow for the 4-byte header
  unsigned char* patBuffer = new unsigned char[patSize];

  // and fill it in:
  unsigned char* pat = patBuffer;
  *pat++ = 0; // pointer_field
  *pat++ = 0; // table_id
  *pat++ = 0xB0; // section_syntax_indicator; 0; reserved, section_length (high)
  *pat++ = 13; // section_length (low)
  *pat++ = 0; *pat++ = 1; // transport_stream_id
  *pat++ = 0xC1; // reserved; version_number; current_next_indicator
  *pat++ = 0; // section_number
  *pat++ = 0; // last_section_number
  *pat++ = OUR_PROGRAM_NUMBER>>8; *pat++ = OUR_PROGRAM_NUMBER; // program_number
  *pat++ = 0xE0|(OUR_PROGRAM_MAP_PID>>8); // reserved; program_map_PID (high)
  *pat++ = OUR_PROGRAM_MAP_PID & 0xff; // program_map_PID (low)

  // Compute the CRC from the bytes we currently have (not including "pointer_field"):
  u_int32_t crc = calculateCRC(patBuffer+1, pat - (patBuffer+1));
  *pat++ = crc>>24; *pat++ = crc>>16; *pat++ = crc>>8; *pat++ = crc;

  // Fill in the rest of the packet with padding bytes:
  while (pat < &patBuffer[patSize]) *pat++ = 0xFF;

  // Deliver the packet:
  unsigned startPosition = 0;
  deliverDataToClient(PAT_PID, patBuffer, patSize, startPosition);

  // Finally, remove the new buffer:
  delete[] patBuffer;
}

void MPEG2TransportStreamMultiplexor4iOS::deliverPMTPacket(Boolean hasChanged) {
  if (hasChanged) ++fProgramMapVersion;

  // First, create a new buffer for the PMT packet:
  unsigned const pmtSize = TRANSPORT_PACKET_SIZE - 4; // allow for the 4-byte header
  unsigned char* pmtBuffer = new unsigned char[pmtSize];

  // and fill it in:
  unsigned char* pmt = pmtBuffer;
  *pmt++ = 0; // pointer_field
  *pmt++ = 2; // table_id
  *pmt++ = 0xB0; // section_syntax_indicator; 0; reserved, section_length (high)
  unsigned char* section_lengthPtr = pmt; // save for later
  *pmt++ = 0; // section_length (low) (fill in later)
  *pmt++ = OUR_PROGRAM_NUMBER>>8; *pmt++ = OUR_PROGRAM_NUMBER; // program_number
  *pmt++ = 0xC1|((fProgramMapVersion&0x1F)<<1); // reserved; version_number; current_next_indicator
  *pmt++ = 0; // section_number
  *pmt++ = 0; // last_section_number
  *pmt++ = 0xE0; // reserved; PCR_PID (high)
  *pmt++ = fPCR_PID; // PCR_PID (low)
  *pmt++ = 0xF0; // reserved; program_info_length (high)
  *pmt++ = 0; // program_info_length (low)
  for (int pid = 0; pid < PID_TABLE_SIZE; ++pid) {
    if (fPIDState[pid].streamType != 0) {
      // This PID gets recorded in the table
      *pmt++ = fPIDState[pid].streamType;
      *pmt++ = 0xE0; // reserved; elementary_pid (high)
      *pmt++ = pid; // elementary_pid (low)
      *pmt++ = 0xF0; // reserved; ES_info_length (high)
      *pmt++ = 0; // ES_info_length (low)
    }
  }
  unsigned section_length = pmt - (section_lengthPtr+1) + 4 /*for CRC*/;
  *section_lengthPtr = section_length;

  // Compute the CRC from the bytes we currently have (not including "pointer_field"):
  u_int32_t crc = calculateCRC(pmtBuffer+1, pmt - (pmtBuffer+1));
  *pmt++ = crc>>24; *pmt++ = crc>>16; *pmt++ = crc>>8; *pmt++ = crc;

  // Fill in the rest of the packet with padding bytes:
  while (pmt < &pmtBuffer[pmtSize]) *pmt++ = 0xFF;

  // Deliver the packet:
  unsigned startPosition = 0;
  deliverDataToClient(OUR_PROGRAM_MAP_PID, pmtBuffer, pmtSize, startPosition);

  // Finally, remove the new buffer:
  delete[] pmtBuffer;
}

void MPEG2TransportStreamMultiplexor4iOS::setProgramStreamMap(unsigned frameSize) {
  if (frameSize <= 16) return; // program_stream_map is too small to be useful
  if (frameSize > 0xFF) return; // program_stream_map is too large

  u_int16_t program_stream_map_length = (fInputBuffer[4]<<8) | fInputBuffer[5];
  if ((u_int16_t)frameSize > 6+program_stream_map_length) {
    frameSize = 6+program_stream_map_length;
  }

  u_int8_t versionByte = fInputBuffer[6];
  if ((versionByte&0x80) == 0) return; // "current_next_indicator" is not set
  fCurrentInputProgramMapVersion = versionByte&0x1F;

  u_int16_t program_stream_info_length = (fInputBuffer[8]<<8) | fInputBuffer[9];
  unsigned offset = 10 + program_stream_info_length; // skip over 'descriptors'

  u_int16_t elementary_stream_map_length
    = (fInputBuffer[offset]<<8) | fInputBuffer[offset+1];
  offset += 2;
  frameSize -= 4; // sizeof CRC_32
  if (frameSize > offset + elementary_stream_map_length) {
    frameSize = offset + elementary_stream_map_length;
  }

  while (offset + 4 <= frameSize) {
    u_int8_t stream_type = fInputBuffer[offset];
    u_int8_t elementary_stream_id = fInputBuffer[offset+1];

    fPIDState[elementary_stream_id].streamType = stream_type;

    u_int16_t elementary_stream_info_length
      = (fInputBuffer[offset+2]<<8) | fInputBuffer[offset+3];
    offset += 4 + elementary_stream_info_length;
  }
}

bool MPEG2TransportStreamMultiplexor4iOS::isKeyFrame(uint8_t * mem, int mpegVersion)
{
    if (mpegVersion == 4)
    {
        uint8_t payloadtype = IFilter::payloadType((char*) mem);
        if (payloadtype == IFilter::MP4_VOP_I || payloadtype == IFilter::MP4_VIOSS) return true;
        return false;
    }
    else if (mpegVersion == 5)
    {
        uint8_t naltype = IFilter::nalType((char*) mem);
        if (naltype == 7) return true;
        return false;
    }
    return false;
}



static u_int32_t CRC32[256] = {
  0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
  0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
  0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
  0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
  0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
  0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
  0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
  0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
  0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
  0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
  0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
  0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
  0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
  0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
  0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
  0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
  0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
  0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
  0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
  0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
  0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
  0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
  0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
  0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
  0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
  0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
  0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
  0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
  0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
  0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
  0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
  0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
  0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
  0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
  0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
  0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
  0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
  0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
  0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
  0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
  0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
  0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
  0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
  0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
  0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
  0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
  0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
  0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
  0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
  0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
  0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
  0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
  0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
  0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
  0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
  0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
  0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
  0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
  0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
  0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
  0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
  0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
  0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
  0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};

static u_int32_t calculateCRC(u_int8_t* data, unsigned dataLength) {
  u_int32_t crc = 0xFFFFFFFF;

  while (dataLength-- > 0) {
    crc = (crc<<8) ^ CRC32[(crc>>24) ^ (u_int32_t)(*data++)];
  }

  return crc;
}

Attachment: MPEG2TransportStreamMultiplexor4iOS.hh
Description: Binary data

_______________________________________________
live-devel mailing list
[email protected]
http://lists.live555.com/mailman/listinfo/live-devel

Reply via email to