diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ff546dd4..9af0b378 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -976,6 +976,7 @@ Reinhard Nissl for making cDvbPlayer::Goto() append a Sequence End Code to get the image shown immediately with softdevices for fixing cDvbSpuBitmap::putPixel() + for implementing cAudioRepacker in remux.c Richard Robson for reporting freezing replay if a timer starts while in Transfer Mode from the diff --git a/HISTORY b/HISTORY index f6227105..7cbc3179 100644 --- a/HISTORY +++ b/HISTORY @@ -3722,3 +3722,6 @@ Video Disk Recorder Revision History - Added missing German OSD texts for 'Audio language'. - The Setup/CICAM menu now only contains the devices that actually have a CI and dynamically detects the number of slots a CI provides. +- Implemented cAudioRepacker for better handling of audio PES packets (thanks to + Reinhard Nissl). + diff --git a/remux.c b/remux.c index c980bd83..66477f12 100644 --- a/remux.c +++ b/remux.c @@ -11,7 +11,7 @@ * The cDolbyRepacker code was originally written by Reinhard Nissl , * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de. * - * $Id: remux.c 1.37 2005/08/21 08:58:58 kls Exp $ + * $Id: remux.c 1.38 2005/08/26 13:34:07 kls Exp $ */ #include "remux.h" @@ -20,49 +20,23 @@ #include "thread.h" #include "tools.h" -// --- cRepacker ------------------------------------------------------------- - -class cRepacker { -protected: - int maxPacketSize; - uint8_t subStreamId; - static void DroppedData(const char *Reason, int Count) { esyslog("%s (dropped %d bytes)", Reason, Count); } - static int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) - { - int n = ResultBuffer->Put(Data, Count); - if (n != Count) - esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count); - return n; - } - static int AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = 0); -public: - cRepacker(void) { maxPacketSize = 6 + 65535; subStreamId = 0; } - virtual ~cRepacker() {} - virtual void Reset(void) {} - virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) = 0; - virtual int BreakAt(const uchar *Data, int Count) = 0; - virtual int QuerySnoopSize(void) { return 0; } - void SetMaxPacketSize(int MaxPacketSize) { maxPacketSize = MaxPacketSize; } - void SetSubStreamId(uint8_t SubStreamId) { subStreamId = SubStreamId; } - }; - -int cRepacker::AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader) +ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader) { if (Count < 7) - return -1; // too short + return phNeedMoreData; // too short if ((Data[6] & 0xC0) == 0x80) { // MPEG 2 if (Count < 9) - return -1; // too short + return phNeedMoreData; // too short PesPayloadOffset = 6 + 3 + Data[8]; if (Count < PesPayloadOffset) - return -1; // too short + return phNeedMoreData; // too short if (ContinuationHeader) *ContinuationHeader = ((Data[6] == 0x80) && !Data[7] && !Data[8]); - return 2; // MPEG 2 + return phMPEG2; // MPEG 2 } // check for MPEG 1 ... @@ -74,7 +48,7 @@ int cRepacker::AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOff break; if (Count <= ++PesPayloadOffset) - return -1; // too short + return phNeedMoreData; // too short } // skip STD_buffer_scale/size @@ -82,7 +56,7 @@ int cRepacker::AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOff PesPayloadOffset += 2; if (Count <= PesPayloadOffset) - return -1; // too short + return phNeedMoreData; // too short } if (ContinuationHeader) @@ -104,18 +78,49 @@ int cRepacker::AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOff *ContinuationHeader = true; } else - return 0; // unknown + return phInvalid; // unknown if (Count < PesPayloadOffset) - return -1; // too short + return phNeedMoreData; // too short - return 1; // MPEG 1 + return phMPEG1; // MPEG 1 } -// --- cVideoRepacker -------------------------------------------------------- +// --- cRepacker ------------------------------------------------------------- -class cVideoRepacker : public cRepacker { -private: +class cRepacker { +protected: + int maxPacketSize; + uint8_t subStreamId; + static void DroppedData(const char *Reason, int Count) { esyslog("%s (dropped %d bytes)", Reason, Count); } +public: + static int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded); + cRepacker(void) { maxPacketSize = 6 + 65535; subStreamId = 0; } + virtual ~cRepacker() {} + virtual void Reset(void) {} + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) = 0; + virtual int BreakAt(const uchar *Data, int Count) = 0; + virtual int QuerySnoopSize(void) { return 0; } + void SetMaxPacketSize(int MaxPacketSize) { maxPacketSize = MaxPacketSize; } + void SetSubStreamId(uint8_t SubStreamId) { subStreamId = SubStreamId; } + }; + +int cRepacker::Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded) +{ + if (CapacityNeeded >= Count && ResultBuffer->Free() < CapacityNeeded) { + esyslog("ERROR: possible result buffer overflow, dropped %d out of %d byte", CapacityNeeded, CapacityNeeded); + return 0; + } + int n = ResultBuffer->Put(Data, Count); + if (n != Count) + esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count); + return n; +} + +// --- cCommonRepacker -------------------------------------------------------- + +class cCommonRepacker : public cRepacker { +protected: int skippedBytes; int packetTodo; uchar fragmentData[6 + 65535 + 3]; @@ -125,40 +130,24 @@ private: uchar pesHeaderBackup[6 + 3 + 255]; int pesHeaderBackupLen; uint32_t scanner; - enum eState { - syncing, - findPicture, - scanPicture - } state; uint32_t localScanner; int localStart; bool PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); -public: - cVideoRepacker(void); - virtual void Reset(void); - virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); - virtual int BreakAt(const uchar *Data, int Count); virtual int QuerySnoopSize() { return 4; } + virtual void Reset(void); }; -cVideoRepacker::cVideoRepacker(void) -{ - Reset(); -} - -void cVideoRepacker::Reset(void) +void cCommonRepacker::Reset(void) { skippedBytes = 0; packetTodo = maxPacketSize - 6 - 3; fragmentLen = 0; pesHeaderLen = 0; pesHeaderBackupLen = 0; - scanner = 0xFFFFFFFF; - state = syncing; localStart = -1; } -bool cVideoRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +bool cCommonRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) { // enter packet length into PES header ... if (fragmentLen > 0) { // ... which is contained in the fragment buffer @@ -166,11 +155,17 @@ bool cVideoRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar int PacketLen = fragmentLen + Count - 6; fragmentData[ 4 ] = PacketLen >> 8; fragmentData[ 5 ] = PacketLen & 0xFF; + // just skip packets with no payload + int PesPayloadOffset = 0; + if (AnalyzePesHeader(fragmentData, fragmentLen, PesPayloadOffset) <= phInvalid) + esyslog("cCommonRepacker: invalid PES packet encountered in fragment buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) + return true; // skip empty packet // amount of data to put into result buffer: a negative Count value means // to strip off any partially contained start code. int Bite = fragmentLen + (Count >= 0 ? 0 : Count); // put data into result buffer - int n = Put(ResultBuffer, fragmentData, Bite); + int n = Put(ResultBuffer, fragmentData, Bite, 6 + PacketLen); fragmentLen = 0; if (n != Bite) return false; @@ -179,11 +174,17 @@ bool cVideoRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar int PacketLen = pesHeaderLen + Count - 6; pesHeader[ 4 ] = PacketLen >> 8; pesHeader[ 5 ] = PacketLen & 0xFF; + // just skip packets with no payload + int PesPayloadOffset = 0; + if (AnalyzePesHeader(pesHeader, pesHeaderLen, PesPayloadOffset) <= phInvalid) + esyslog("cCommonRepacker: invalid PES packet encountered in header buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) + return true; // skip empty packet // amount of data to put into result buffer: a negative Count value means // to strip off any partially contained start code. int Bite = pesHeaderLen + (Count >= 0 ? 0 : Count); // put data into result buffer - int n = Put(ResultBuffer, pesHeader, Bite); + int n = Put(ResultBuffer, pesHeader, Bite, 6 + PacketLen); pesHeaderLen = 0; if (n != Bite) return false; @@ -193,7 +194,7 @@ bool cVideoRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar // amount of data to put into result buffer int Bite = Count; // put data into result buffer - int n = Put(ResultBuffer, Data, Bite); + int n = Put(ResultBuffer, Data, Bite, Bite); if (n != Bite) return false; } @@ -201,6 +202,34 @@ bool cVideoRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar return true; } +// --- cVideoRepacker -------------------------------------------------------- + +class cVideoRepacker : public cCommonRepacker { +private: + enum eState { + syncing, + findPicture, + scanPicture + } state; +public: + cVideoRepacker(void); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +cVideoRepacker::cVideoRepacker(void) +{ + Reset(); +} + +void cVideoRepacker::Reset(void) +{ + cCommonRepacker::Reset(); + scanner = 0xFFFFFFFF; + state = syncing; +} + void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) { // synchronisation is detected some bytes after frame start. @@ -211,8 +240,8 @@ void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int pesPayloadOffset = 0; bool continuationHeader = false; - int mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); - if (mpegLevel <= 0) { + ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); + if (mpegLevel <= phInvalid) { DroppedData("cVideoRepacker: no valid PES packet header found", Count); return; } @@ -298,7 +327,7 @@ void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, pesHeader[pesHeaderLen++] = 0x00; // length still unknown pesHeader[pesHeaderLen++] = 0x00; // length still unknown - if (mpegLevel == 2) { + if (mpegLevel == phMPEG2) { pesHeader[pesHeaderLen++] = 0x80; pesHeader[pesHeaderLen++] = 0x00; pesHeader[pesHeaderLen++] = 0x00; @@ -392,7 +421,7 @@ void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, pesHeader[pesHeaderLen++] = 0x00; // length still unknown pesHeader[pesHeaderLen++] = 0x00; // length still unknown - if (mpegLevel == 2) { + if (mpegLevel == phMPEG2) { pesHeader[pesHeaderLen++] = 0x80; pesHeader[pesHeaderLen++] = 0x00; pesHeader[pesHeaderLen++] = 0x00; @@ -443,7 +472,7 @@ int cVideoRepacker::BreakAt(const uchar *Data, int Count) { int PesPayloadOffset = 0; - if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= 0) + if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= phInvalid) return -1; // not enough data for test // just detect end of picture @@ -475,6 +504,355 @@ int cVideoRepacker::BreakAt(const uchar *Data, int Count) return PesPayloadOffset + packetTodo + 4; } +// --- cAudioRepacker -------------------------------------------------------- + +class cAudioRepacker : public cCommonRepacker { +private: + static int bitRates[]; + enum eState { + syncing, + scanFrame + } state; + int frameTodo; + int frameSize; + bool IsValidAudioHeader(uint32_t Header, int *FrameSize = NULL); +public: + cAudioRepacker(void); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +int cAudioRepacker::bitRates[] = { // all values are specified as kbits/s + // Layer I + 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1, + // Layer II + 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1, + // Layer III + 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 + }; + +cAudioRepacker::cAudioRepacker(void) +{ + Reset(); +} + +void cAudioRepacker::Reset(void) +{ + cCommonRepacker::Reset(); + scanner = 0; + state = syncing; + frameTodo = 0; + frameSize = 0; +} + +bool cAudioRepacker::IsValidAudioHeader(uint32_t Header, int *FrameSize) +{ + int syncword = (Header & 0xFFF00000) >> 20; + int id = (Header & 0x00080000) >> 19; + int layer = (Header & 0x00060000) >> 17; +//int protection_bit = (Header & 0x00010000) >> 16; + int bitrate_index = (Header & 0x0000F000) >> 12; + int sampling_frequency = (Header & 0x00000C00) >> 10; + int padding_bit = (Header & 0x00000200) >> 9; +//int private_bit = (Header & 0x00000100) >> 8; +//int mode = (Header & 0x000000C0) >> 6; +//int mode_extension = (Header & 0x00000030) >> 4; +//int copyright = (Header & 0x00000008) >> 3; +//int orignal_copy = (Header & 0x00000004) >> 2; + int emphasis = (Header & 0x00000003); + + if (syncword != 0xFFF) + return false; + + if (id == 0) // reserved + return false; + + if (layer == 0) // reserved + return false; + + if (bitrate_index == 0xF) // forbidden + return false; + + if (sampling_frequency == 3) // reserved + return false; + + if (emphasis == 2) // reserved + return false; + + if (FrameSize) { + if (bitrate_index == 0) + *FrameSize = 0; + else { + static int samplingFrequencies[] = { 44100, 48000, 32000 }; // Hz + + int br = 1000 * bitRates[ (3 - layer) * 0x10 + bitrate_index ]; // bits/s + int sf = samplingFrequencies[sampling_frequency]; + + bool layerI = (layer == 3); + + int N = (layerI ? 12 : 144) * br / sf; // slots + + *FrameSize = (N + padding_bit) * (layerI ? 4 : 1); // bytes + } + } + + return true; +} + +void cAudioRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // reset local scanner + localStart = -1; + + int pesPayloadOffset = 0; + bool continuationHeader = false; + ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); + if (mpegLevel <= phInvalid) { + DroppedData("cAudioRepacker: no valid PES packet header found", Count); + return; + } + if (!continuationHeader) { + // backup PES header + pesHeaderBackupLen = pesPayloadOffset; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = pesPayloadOffset; + int todo = Count - done; + const uchar *data = Data + done; + // remember start of the data + const uchar *payload = data; + + while (todo > 0) { + // collect number of skipped bytes while syncing + if (state <= syncing) + skippedBytes++; + else if (frameTodo > 0) + frameTodo--; + // did we reach an audio frame header? + scanner <<= 8; + scanner |= *data; + if ((scanner & 0xFFF00000) == 0xFFF00000) { + if (frameTodo <= 0 && IsValidAudioHeader(scanner, &frameSize)) { + if (state == scanFrame) { + // As a new audio frame starts here, the previous one is done. So push + // out the packet to start a new packet for the next audio frame. If + // the byte count gets negative then the current buffer ends in a + // partitial audio frame header that must be stripped off, as it shall + // be put in the next packet. + if (!PushOutPacket(ResultBuffer, payload, data - 3 - payload)) { + DroppedData("cAudioRepacker: result buffer overflow", Count - (done - 3)); + return; + } + // go on with syncing to the next audio frame + state = syncing; + } + if (state == syncing) { + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) + esyslog("cAudioRepacker: skipped %d bytes to sync on next audio frame", skippedBytes - SkippedBytesLimit); + skippedBytes = 0; + // if there is a PES header available, then use it ... + if (pesHeaderBackupLen > 0) { + // ISO 13818-1 says: + // In the case of audio, if a PTS is present in a PES packet header + // it shall refer to the access unit commencing in the PES packet. An + // audio access unit commences in a PES packet if the first byte of + // the audio access unit is present in the PES packet. + memcpy(pesHeader, pesHeaderBackup, pesHeaderBackupLen); + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + } + else { + // ... otherwise create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + } + // append the first three bytes of the audio frame header + pesHeader[pesHeaderLen++] = 0xFF; + pesHeader[pesHeaderLen++] = (scanner >> 16) & 0xFF; + pesHeader[pesHeaderLen++] = (scanner >> 8) & 0xFF; + // the next packet's payload will begin with the fourth byte of + // the audio frame header (= the actual byte) + payload = data; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo = maxPacketSize - pesHeaderLen; + // setup expected audio frame size to omit false audio header detection + frameTodo = frameSize; + // go on with collecting the frame's data + ((int &)state)++; + } + } + } + data++; + done++; + todo--; + // do we have to start a new packet as there is no more space left? + if (--packetTodo <= 0) { + // We connot start a new packet here if the current might end in an audio + // frame header and this header shall possibly be put in the next packet. So + // overfill the current packet until we can safely detect that we won't + // break an audio frame header into pieces: + // + // A) the last four bytes were an audio frame header. + // B) the last three bytes introduce an audio frame header. + // C) the last two bytes introduce an audio frame header. + // D) the last byte introduces an audio frame header. + // + // Todo : Data : Rule : Result + // -----:-------------------------------:------:------- + // : XX XX FF Fz zz zz|YY YY YY YY : : + // 0 : ^^| : A : push + // -----:-------------------------------:------:------- + // : XX XX XX FF Fz zz|zz YY YY YY : : + // 0 : ^^| : B : wait + // -1 : |^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX FF Fz|zz zz YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : B : wait + // -2 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX XX FF|Fz zz zz YY : : + // 0 : ^^| : D : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : B : wait + // -3 : | ^^ : A : push + // -----:-------------------------------:------:------- + bool A = ((scanner & 0xFFF00000) == 0xFFF00000); + bool B = ((scanner & 0xFFF000) == 0xFFF000); + bool C = ((scanner & 0xFFF0) == 0xFFF0); + bool D = ((scanner & 0xFF) == 0xFF); + if (A || (!B && !C && !D)) { + // Actually we cannot push out an overfull packet. So we'll have to + // adjust the byte count and payload start as necessary. If the byte + // count gets negative we'll have to append the excess from fragment's + // tail to the next PES header. + int bite = data + packetTodo - payload; + const uchar *excessData = fragmentData + fragmentLen + bite; + // A negative byte count means to drop some bytes from the current + // fragment's tail, to not exceed the maximum packet size. + if (!PushOutPacket(ResultBuffer, payload, bite)) { + DroppedData("cAudioRepacker: result buffer overflow", Count - done); + return; + } + // create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + + // copy any excess data + while (bite++ < 0) { + // append the excess data here + pesHeader[pesHeaderLen++] = *excessData++; + packetTodo++; + } + // the next packet's payload will begin here + payload = data + packetTodo; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo += maxPacketSize - pesHeaderLen; + } + } + } + // The packet is done. Now store any remaining data into fragment buffer + // if we are no longer syncing. + if (state != syncing) { + // append the PES header ... + int bite = pesHeaderLen; + pesHeaderLen = 0; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, pesHeader, bite); + fragmentLen += bite; + } + // append payload. It may contain part of an audio frame header at it's + // end, which will be removed when the next packet gets processed. + bite = data - payload; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, payload, bite); + fragmentLen += bite; + } + } + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + esyslog("cAudioRepacker: skipped %d bytes while syncing on next audio frame", skippedBytes - SkippedBytesLimit); + skippedBytes = SkippedBytesLimit; + } +} + +int cAudioRepacker::BreakAt(const uchar *Data, int Count) +{ + int PesPayloadOffset = 0; + + if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= phInvalid) + return -1; // not enough data for test + + // determine amount of data to fill up packet and to append next audio frame header + int packetRemainder = PesPayloadOffset + packetTodo + 4; + + // just detect end of an audio frame + if (state == scanFrame) { + // when remaining audio frame size is known, then omit scanning + if (frameTodo > 0) { + // determine amount of data to fill up audio frame and to append next audio frame header + int remaining = PesPayloadOffset + frameTodo + 4; + if (remaining < packetRemainder) + return remaining; + return packetRemainder; + } + // setup local scanner + if (localStart < 0) { + localScanner = scanner; + localStart = 0; + } + // start where we've stopped at the last run + const uchar *data = Data + PesPayloadOffset + localStart; + const uchar *limit = Data + Count; + // scan data + while (data < limit) { + localStart++; + localScanner <<= 8; + localScanner |= *data++; + // check whether the next audio frame follows + if ((localScanner & 0xFFF00000) == 0xFFF00000 && IsValidAudioHeader(localScanner)) + return data - Data; + } + } + // just fill up packet and append next audio frame header + return packetRemainder; +} + // --- cDolbyRepacker -------------------------------------------------------- class cDolbyRepacker : public cRepacker { @@ -587,7 +965,7 @@ bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const ucha // output a previous fragment first if (fragmentLen > 0) { Bite = fragmentLen; - int n = Put(ResultBuffer, fragmentData, Bite); + int n = Put(ResultBuffer, fragmentData, Bite, fragmentLen + fragmentTodo); if (Bite != n) { Reset(); return false; @@ -595,7 +973,7 @@ bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const ucha fragmentLen = 0; } Bite = fragmentTodo; - int n = Put(ResultBuffer, Data, Bite); + int n = Put(ResultBuffer, Data, Bite, Bite); if (Bite != n) { Reset(); Done += n; @@ -631,13 +1009,13 @@ bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar Bite = pesHeaderLen; // enough data available to put PES packet into buffer? if (packetLen - pesHeaderLen <= Todo) { - int n = Put(ResultBuffer, pesHeader, Bite); + int n = Put(ResultBuffer, pesHeader, Bite, packetLen); if (Bite != n) { Reset(); return false; } Bite = packetLen - pesHeaderLen; - n = Put(ResultBuffer, Data, Bite); + n = Put(ResultBuffer, Data, Bite, Bite); if (Bite != n) { Reset(); Done += n; @@ -890,7 +1268,8 @@ private: uint8_t hlength; int mpeg; uint8_t check; - int which; + int mpeg1_required; + int mpeg1_stuffing; bool done; cRingBufferLinear *resultBuffer; int tsErrors; @@ -956,11 +1335,8 @@ void cTS2PES::store(uint8_t *Data, int Count) { if (repacker) repacker->Repack(resultBuffer, Data, Count); - else { - int n = resultBuffer->Put(Data, Count); - if (n != Count) - esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count); - } + else + cRepacker::Put(resultBuffer, Data, Count, Count); } void cTS2PES::reset_ipack(void) @@ -973,7 +1349,8 @@ void cTS2PES::reset_ipack(void) hlength = 0; mpeg = 0; check = 0; - which = 0; + mpeg1_required = 0; + mpeg1_stuffing = 0; done = false; count = 0; } @@ -1057,7 +1434,7 @@ void cTS2PES::instant_repack(const uint8_t *Buf, int Count) { int c = 0; - while (c < Count && (mpeg == 0 || (mpeg == 1 && found < 7) || (mpeg == 2 && found < 9)) && (found < 5 || !done)) { + while (c < Count && (mpeg == 0 || (mpeg == 1 && found < mpeg1_required) || (mpeg == 2 && found < 9)) && (found < 5 || !done)) { switch (found ) { case 0: case 1: @@ -1103,6 +1480,7 @@ void cTS2PES::instant_repack(const uint8_t *Buf, int Count) plength = ntohs(*pl); c += 2; found += 2; + mpeg1_stuffing = 0; } else { plen[0] = Buf[c]; @@ -1115,32 +1493,53 @@ void cTS2PES::instant_repack(const uint8_t *Buf, int Count) unsigned short *pl = (unsigned short *)plen; plength = ntohs(*pl); found++; + mpeg1_stuffing = 0; } break; case 6: if (!done) { flag1 = Buf[c++]; found++; - if ((flag1 & 0xC0) == 0x80 ) - mpeg = 2; - else { - hlength = 0; - which = 0; - mpeg = 1; - flag2 = 0; + if (mpeg1_stuffing == 0) { // first stuffing iteration: determine MPEG level + if ((flag1 & 0xC0) == 0x80) + mpeg = 2; + else + mpeg = 1; + mpeg1_required = 7; + } + if (mpeg == 1) { + if (flag1 == 0xFF) { // MPEG1 stuffing + if (++mpeg1_stuffing > 16) + found = 0; // invalid MPEG1 header + else { // ignore stuffing + found--; + if (plength > 0) + plength--; + } + } + else if ((flag1 & 0xC0) == 0x40) // STD_buffer_scale/size + mpeg1_required += 2; + else if (flag1 != 0x0F && (flag1 & 0xF0) != 0x20 && (flag1 & 0xF0) != 0x30) + found = 0; // invalid MPEG1 header + else { + flag2 = 0; + hlength = 0; + } } } break; case 7: - if (!done && mpeg == 2) { + if (!done && (mpeg == 2 || mpeg1_required > 7)) { flag2 = Buf[c++]; found++; } break; case 8: - if (!done && mpeg == 2) { + if (!done && (mpeg == 2 || mpeg1_required > 7)) { hlength = Buf[c++]; found++; + if (mpeg == 1 && hlength != 0x0F && (hlength & 0xF0) != 0x20 && (hlength & 0xF0) != 0x30) + found = 0; // invalid MPEG1 header } break; default: @@ -1151,7 +1550,7 @@ void cTS2PES::instant_repack(const uint8_t *Buf, int Count) if (!plength) plength = MMAX_PLENGTH - 6; - if (done || ((mpeg == 2 && found >= 9) || (mpeg == 1 && found >= 7))) { + if (done || ((mpeg == 2 && found >= 9) || (mpeg == 1 && found >= mpeg1_required))) { switch (cid) { case AUDIO_STREAM_S ... AUDIO_STREAM_E: case VIDEO_STREAM_S ... VIDEO_STREAM_E: @@ -1163,8 +1562,13 @@ void cTS2PES::instant_repack(const uint8_t *Buf, int Count) write_ipack(&hlength, 1); } - if (mpeg == 1 && found == 7) + if (mpeg == 1 && found == mpeg1_required) { write_ipack(&flag1, 1); + if (mpeg1_required > 7) { + write_ipack(&flag2, 1); + write_ipack(&hlength, 1); + } + } if (mpeg == 2 && (flag2 & PTS_ONLY) && found < 14) { while (c < Count && found < 14) { @@ -1277,7 +1681,12 @@ cRemux::cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, b if (APids) { int n = 0; while (*APids && numTracks < MAXTRACKS && n < MAXAPIDS) +#define TEST_cAudioRepacker +#ifdef TEST_cAudioRepacker + ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n++, 0x00, new cAudioRepacker); +#else ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n++); +#endif } if (DPids) { int n = 0; @@ -1321,10 +1730,9 @@ int cRemux::ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &Pic // If the return value is -1 the packet was not completely in the buffer. int Length = GetPacketLength(Data, Count, Offset); if (Length > 0) { - if (Length >= 8) { - int i = Offset + 8; // the minimum length of the video packet header - i += Data[i] + 1; // possible additional header bytes - for (; i < Offset + Length - 5; i++) { + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) { + for (int i = Offset + PesPayloadOffset; i < Offset + Length - 5; i++) { if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) { switch (Data[i + 3]) { case SC_PICTURE: PictureType = (Data[i + 5] >> 3) & 0x07; @@ -1499,8 +1907,9 @@ void cRemux::Clear(void) void cRemux::SetBrokenLink(uchar *Data, int Length) { - if (Length > 9 && Data[0] == 0 && Data[1] == 0 && Data[2] == 1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) { - for (int i = Data[8] + 9; i < Length - 7; i++) { // +9 to skip video packet header + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data, Length, PesPayloadOffset) >= phMPEG1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) { + for (int i = PesPayloadOffset; i < Length - 7; i++) { if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1 && Data[i + 3] == 0xB8) { if (!(Data[i + 7] & 0x40)) // set flag only if GOP is not closed Data[i + 7] |= 0x20; diff --git a/remux.h b/remux.h index 0cd570e0..ab296553 100644 --- a/remux.h +++ b/remux.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.h 1.14 2005/08/07 10:28:07 kls Exp $ + * $Id: remux.h 1.15 2005/08/26 13:22:19 kls Exp $ */ #ifndef __REMUX_H @@ -15,6 +15,15 @@ #include "ringbuffer.h" #include "tools.h" +enum ePesHeader { + phNeedMoreData = -1, + phInvalid = 0, + phMPEG1 = 1, + phMPEG2 = 2 + }; + +ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = NULL); + // Picture types: #define NO_PICTURE 0 #define I_FRAME 1