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); }
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; }
MPEG2TransportStreamMultiplexor4iOS.hh
Description: Binary data
_______________________________________________ live-devel mailing list [email protected] http://lists.live555.com/mailman/listinfo/live-devel
