VDR-1.3.30: testing new cAudioRepacker

Message ID 430A52E0.1020807@gmx.de
State New
Headers

Commit Message

Reinhard Nissl Aug. 22, 2005, 10:34 p.m. UTC
  Hi,

I'd like you to test the new cAudioRepacker which is contained in the 
attached patch.

The patch also fixes some locations where VDR didn't handle MPEG1 
streams properly.

An additional patch for vdr-xine-0.7.5 will follow the next day(s), 
which fixes some MPEG1 issues in vdr-xine. Planned to be attached to 
this email, I've just trashed my corrections while preparing the diff :-((

I'd better gone to bed earlier ;-)

Bye.
  

Patch

--- ../vdr-1.3.30-orig/remux.h	2005-08-07 12:28:07.000000000 +0200
+++ remux.h	2005-08-21 17:22:16.000000000 +0200
@@ -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 = 0);
+
 // Picture types:
 #define NO_PICTURE 0
 #define I_FRAME    1
--- ../vdr-1.3.30-orig/remux.c	2005-08-21 10:58:58.000000000 +0200
+++ remux.c	2005-08-23 00:04:46.000000000 +0200
@@ -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 uc
          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 uc
      PesPayloadOffset += 2;
 
      if (Count <= PesPayloadOffset)
-        return -1; // too short
+        return phNeedMoreData; // too short
      }
 
   if (ContinuationHeader)
@@ -104,18 +78,49 @@  int cRepacker::AnalyzePesHeader(const uc
         *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(cRing
      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(cRing
      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(cRing
      // 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(cRing
   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(cRingBufferL
 
   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(cRingBufferL
                         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(cRingBufferL
               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 
 {
   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 
   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 = 0);
+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(cRi
      // 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(cRi
         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(cRin
   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 C
 {
   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
 {
   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
                      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
                     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
   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
                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 *APid
   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 
   // 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;
@@ -1409,19 +1817,6 @@  uchar *cRemux::Get(int &Count, uchar *Pi
   return resultBuffer->Get(Count);
 #endif
 
-  // Special VPID case to enable recording radio channels:
-
-  if (isRadio) {
-     // XXX actually '0' should be enough, but '1' must be used with encrypted channels (driver bug?)
-     // XXX also allowing 0x1FFF to not break Michael Paar's original patch,
-     // XXX but it would probably be best to only use '0'
-     // Force syncing of radio channels to avoid "no useful data" error
-     synced = true;
-     if (PictureType)
-        *PictureType = I_FRAME;
-     return resultBuffer->Get(Count);
-     }
-
   // Check for frame borders:
 
   if (PictureType)
@@ -1466,6 +1861,18 @@  uchar *cRemux::Get(int &Count, uchar *Pi
                l = GetPacketLength(data, resultCount, i);
                if (l < 0)
                   return resultData;
+               if (isRadio) {
+                  if (!synced) {
+                     if (PictureType)
+                        *PictureType = I_FRAME;
+                     resultSkipped = i; // will drop everything before this position
+                     synced = true;
+                     }
+                  else if (Count)
+                     return resultData;
+                  else if (PictureType)
+                     *PictureType = I_FRAME;
+                  }
                }
             if (synced) {
                if (!Count)
@@ -1499,8 +1906,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;