From 008e7c851013722a377aa9ff4485d4af2d991883 Mon Sep 17 00:00:00 2001 From: schmirl Date: Fri, 19 Jun 2009 06:32:38 +0000 Subject: [PATCH] - added namespace to remuxers - increased WRITERBUFSIZE - buffer was too small for high bandwidth content - removed cStreamdevStreamer::m_Running - eliminated potential busy waits in remuxers - updated cTSRemux static helpers to code of their VDR 1.6.0 counterparts - re-enabled PES vor VDR 1.7.3+. Streamdev now uses a copy of VDR 1.6.0's cRemux for TS to PES remuxing. - make sure that only complete TS packets are written to ringbuffers - use signaling instead of sleeps when writing to ringbuffers - optimized cStreamdevPatFilter PAT packet initialization - fixed cStreamdevPatFilter not processing PATs with length > TS_SIZE - 5 - use a small ringbuffer for cStreamdevPatFilter instead of writing to cStreamdevStreamers SendBuffer as two threads mustn't write to the same ringbuffer Modified Files: CONTRIBUTORS HISTORY Makefile common.c common.h streamdev-server.c libdvbmpeg/transform.h remux/extern.c remux/extern.h remux/ts2es.c remux/ts2es.h remux/ts2ps.c remux/ts2ps.h remux/tsremux.c remux/tsremux.h server/connectionHTTP.c server/connectionVTP.c server/livestreamer.c server/livestreamer.h server/menuHTTP.c server/streamer.c server/streamer.h Added Files: remux/ts2pes.c remux/ts2pes.h --- CONTRIBUTORS | 5 + HISTORY | 14 + Makefile | 4 +- common.c | 4 +- common.h | 4 +- libdvbmpeg/transform.h | 2 +- remux/extern.c | 5 + remux/extern.h | 4 + remux/ts2es.c | 14 +- remux/ts2es.h | 9 +- remux/ts2pes.c | 2035 +++++++++++++++++++++++++++++++++++++++ remux/ts2pes.h | 58 ++ remux/ts2ps.c | 13 +- remux/ts2ps.h | 9 +- remux/tsremux.c | 51 +- remux/tsremux.h | 28 +- server/connectionHTTP.c | 6 +- server/connectionVTP.c | 4 +- server/livestreamer.c | 140 +-- server/livestreamer.h | 22 +- server/menuHTTP.c | 4 - server/streamer.c | 22 +- server/streamer.h | 43 +- streamdev-server.c | 3 +- 24 files changed, 2339 insertions(+), 164 deletions(-) create mode 100644 remux/ts2pes.c create mode 100644 remux/ts2pes.h diff --git a/CONTRIBUTORS b/CONTRIBUTORS index d7f8dbf..71d40fd 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,6 +1,10 @@ Special thanks go to the following persons (if you think your name is missing here, please send an email to vdrdev@schmirler.de): +Klaus Schmidinger + for VDR as a whole + for permission to use VDR 1.6.0 cRemux code for PES remuxing + Sascha Volkenandt, the original author, for this great plugin @@ -74,6 +78,7 @@ alexw Olli Lammi for fixing a busy wait when client isn't accepting data fast enough + for suggesting signaling instead of sleeping when writing to buffers Joerg Pulz for his FreeBSD compatibility patch diff --git a/HISTORY b/HISTORY index 4f8aef1..96bb984 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,20 @@ VDR Plugin 'streamdev' Revision History --------------------------------------- +- added namespace to remuxers +- increased WRITERBUFSIZE - buffer was too small for high bandwidth content +- removed cStreamdevStreamer::m_Running +- eliminated potential busy waits in remuxers +- updated cTSRemux static helpers to code of their VDR 1.6.0 counterparts +- re-enabled PES vor VDR 1.7.3+. Streamdev now uses a copy of VDR 1.6.0's + cRemux for TS to PES remuxing. +- make sure that only complete TS packets are written to ringbuffers +- use signaling instead of sleeps when writing to ringbuffers +- optimized cStreamdevPatFilter PAT packet initialization +- fixed cStreamdevPatFilter not processing PATs with length > TS_SIZE - 5 +- use a small ringbuffer for cStreamdevPatFilter instead of writing to + cStreamdevStreamers SendBuffer as two threads mustn't write to the same + ringbuffer - added missing call to StopSectionHandler which could cause crashes when shutting down VDR - added IGMP based multicast streaming diff --git a/Makefile b/Makefile index b737563..ee62a42 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # # Makefile for a Video Disk Recorder plugin # -# $Id: Makefile,v 1.17 2009/02/13 10:39:20 schmirl Exp $ +# $Id: Makefile,v 1.18 2009/06/19 06:32:38 schmirl Exp $ # The official name of this plugin. # This name will be used in the '-P...' option of VDR to load the plugin. @@ -62,7 +62,7 @@ SERVEROBJS = $(PLUGIN)-server.o \ server/connectionVTP.o server/connectionHTTP.o server/connectionIGMP.o \ server/streamer.o server/livestreamer.o server/livefilter.o \ server/suspend.o server/setup.o server/menuHTTP.o \ - remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.o + remux/tsremux.o remux/ts2pes.o remux/ts2ps.o remux/ts2es.o remux/extern.o ifdef DEBUG DEFINES += -DDEBUG diff --git a/common.c b/common.c index dada4a3..cd19d75 100644 --- a/common.c +++ b/common.c @@ -1,5 +1,5 @@ /* - * $Id: common.c,v 1.9 2009/01/16 11:35:43 schmirl Exp $ + * $Id: common.c,v 1.10 2009/06/19 06:32:38 schmirl Exp $ */ #include @@ -14,9 +14,7 @@ const char *VERSION = "0.5.0-pre"; const char *StreamTypes[st_Count] = { "TS", -#if APIVERSNUM < 10703 "PES", -#endif "PS", "ES", "Extern", diff --git a/common.h b/common.h index e920c79..a32c988 100644 --- a/common.h +++ b/common.h @@ -1,5 +1,5 @@ /* - * $Id: common.h,v 1.12 2009/01/16 11:35:43 schmirl Exp $ + * $Id: common.h,v 1.13 2009/06/19 06:32:38 schmirl Exp $ */ #ifndef VDR_STREAMDEV_COMMON_H @@ -51,9 +51,7 @@ const cChannel *ChannelFromString(const char *String, int *Apid = NULL); enum eStreamType { stTS, -#if APIVERSNUM < 10703 stPES, -#endif stPS, stES, stExtern, diff --git a/libdvbmpeg/transform.h b/libdvbmpeg/transform.h index ad32706..c65fa0c 100644 --- a/libdvbmpeg/transform.h +++ b/libdvbmpeg/transform.h @@ -106,7 +106,7 @@ #define MAX_PLENGTH 0xFFFF -#define MMAX_PLENGTH (8*MAX_PLENGTH) +#define MMAX_PLENGTH (64*MAX_PLENGTH) #ifdef __cplusplus extern "C" { diff --git a/remux/extern.c b/remux/extern.c index c5f35de..3791d10 100644 --- a/remux/extern.c +++ b/remux/extern.c @@ -7,6 +7,8 @@ #include #include +namespace Streamdev { + class cTSExt: public cThread { private: cRingBufferLinear *m_ResultBuffer; @@ -24,6 +26,9 @@ public: void Put(const uchar *Data, int Count); }; +} // namespace Streamdev +using namespace Streamdev; + cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter): m_ResultBuffer(ResultBuffer), m_Active(false), diff --git a/remux/extern.h b/remux/extern.h index aa6acf7..ff4ddec 100644 --- a/remux/extern.h +++ b/remux/extern.h @@ -5,6 +5,8 @@ #include #include +namespace Streamdev { + class cTSExt; class cExternRemux: public cTSRemux { @@ -21,4 +23,6 @@ public: void Del(int Count) { m_ResultBuffer->Del(Count); } }; +} // namespace Streamdev + #endif // VDR_STREAMDEV_EXTERNREMUX_H diff --git a/remux/ts2es.c b/remux/ts2es.c index 3476e24..6ff4e87 100644 --- a/remux/ts2es.c +++ b/remux/ts2es.c @@ -1,12 +1,13 @@ #include "remux/ts2es.h" #include "server/streamer.h" -#include "libdvbmpeg/transform.h" #include "common.h" #include // from VDR's remux.c #define MAXNONUSEFULDATA (10*1024*1024) +namespace Streamdev { + class cTS2ES: public ipack { friend void PutES(uint8_t *Buffer, int Size, void *Data); @@ -32,6 +33,9 @@ void PutES(uint8_t *Buffer, int Size, void *Data) This->start = 1; } +} // namespace Streamdev +using namespace Streamdev; + cTS2ES::cTS2ES(cRingBufferLinear *ResultBuffer) { m_ResultBuffer = ResultBuffer; @@ -75,10 +79,10 @@ void cTS2ES::PutTSPacket(const uint8_t *Buffer) { cTS2ESRemux::cTS2ESRemux(int Pid): m_Pid(Pid), - m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, IPACKS)), + m_ResultBuffer(new cStreamdevBuffer(WRITERBUFSIZE, IPACKS)), m_Remux(new cTS2ES(m_ResultBuffer)) { - m_ResultBuffer->SetTimeouts(0, 100); + m_ResultBuffer->SetTimeouts(100, 100); } cTS2ESRemux::~cTS2ESRemux() @@ -111,8 +115,10 @@ int cTS2ESRemux::Put(const uchar *Data, int Count) break; if (Data[i] != TS_SYNC_BYTE) break; - if (m_ResultBuffer->Free() < 2 * IPACKS) + if (m_ResultBuffer->Free() < 2 * IPACKS) { + m_ResultBuffer->WaitForPut(); break; // A cTS2ES might write one full packet and also a small rest + } int pid = cTSRemux::GetPid(Data + i + 1); if (Data[i + 3] & 0x10) { // got payload if (m_Pid == pid) diff --git a/remux/ts2es.h b/remux/ts2es.h index 551df1d..95eceb9 100644 --- a/remux/ts2es.h +++ b/remux/ts2es.h @@ -2,15 +2,16 @@ #define VDR_STREAMDEV_TS2ESREMUX_H #include "remux/tsremux.h" -#include +#include "server/streamer.h" + +namespace Streamdev { class cTS2ES; -class cRingBufferLinear; class cTS2ESRemux: public cTSRemux { private: int m_Pid; - cRingBufferLinear *m_ResultBuffer; + cStreamdevBuffer *m_ResultBuffer; cTS2ES *m_Remux; public: @@ -22,4 +23,6 @@ public: void Del(int Count) { m_ResultBuffer->Del(Count); } }; +} // namespace Streamdev + #endif // VDR_STREAMDEV_TS2ESREMUX_H diff --git a/remux/ts2pes.c b/remux/ts2pes.c new file mode 100644 index 0000000..baa57bb --- /dev/null +++ b/remux/ts2pes.c @@ -0,0 +1,2035 @@ +/* + * ts2pes.c: A streaming MPEG2 remultiplexer + * + * This file is based on remux.c from Klaus Schmidinger's VDR, version 1.6.0. + * + * The parts of this code that implement cTS2PES have been taken from + * the Linux DVB driver's 'tuxplayer' example and were rewritten to suit + * VDR's needs. + * + * The cRepacker family's code was originally written by Reinhard Nissl , + * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de. + * + * $Id: ts2pes.c,v 1.1 2009/06/19 06:32:40 schmirl Exp $ + */ + +#include "remux/ts2pes.h" +#include +#include +#include + +namespace Streamdev { + +// --- cRepacker ------------------------------------------------------------- + +#define MIN_LOG_INTERVAL 10 // min. # of seconds between two consecutive log messages of a cRepacker +#define LOG(a...) (LogAllowed() && (esyslog(a), true)) + +class cRepacker { +protected: + bool initiallySyncing; + int maxPacketSize; + uint8_t subStreamId; + time_t lastLog; + int suppressedLogMessages; + bool LogAllowed(void); + void DroppedData(const char *Reason, int Count) { LOG("%s (dropped %d bytes)", Reason, Count); } +public: + static int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded); + cRepacker(void); + virtual ~cRepacker() {} + virtual void Reset(void) { initiallySyncing = true; } + 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; } + }; + +cRepacker::cRepacker(void) +{ + initiallySyncing = true; + maxPacketSize = 6 + 65535; + subStreamId = 0; + suppressedLogMessages = 0;; + lastLog = 0; +} + +bool cRepacker::LogAllowed(void) +{ + bool Allowed = time(NULL) - lastLog >= MIN_LOG_INTERVAL; + lastLog = time(NULL); + if (Allowed) { + if (suppressedLogMessages) { + esyslog("%d cRepacker messages suppressed", suppressedLogMessages); + suppressedLogMessages = 0; + } + } + else + suppressedLogMessages++; + return Allowed; +} + +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]; + int fragmentLen; + uchar pesHeader[6 + 3 + 255 + 3]; + int pesHeaderLen; + uchar pesHeaderBackup[6 + 3 + 255]; + int pesHeaderBackupLen; + uint32_t scanner; + uint32_t localScanner; + int localStart; + bool PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int QuerySnoopSize() { return 4; } + virtual void Reset(void); + }; + +void cCommonRepacker::Reset(void) +{ + cRepacker::Reset(); + skippedBytes = 0; + packetTodo = 0; + fragmentLen = 0; + pesHeaderLen = 0; + pesHeaderBackupLen = 0; + localStart = -1; +} + +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 + // determine PES packet payload + 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) + LOG("cCommonRepacker: invalid PES packet encountered in fragment buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) { + fragmentLen = 0; + 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, 6 + PacketLen); + fragmentLen = 0; + if (n != Bite) + return false; + } + else if (pesHeaderLen > 0) { // ... which is contained in the PES header buffer + 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) + LOG("cCommonRepacker: invalid PES packet encountered in header buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) { + pesHeaderLen = 0; + 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, 6 + PacketLen); + pesHeaderLen = 0; + if (n != Bite) + return false; + } + // append further payload + if (Count > 0) { + // amount of data to put into result buffer + int Bite = Count; + // put data into result buffer + int n = Put(ResultBuffer, Data, Bite, Bite); + if (n != Bite) + return false; + } + // we did it ;-) + return true; +} + +// --- cVideoRepacker -------------------------------------------------------- + +class cVideoRepacker : public cCommonRepacker { +private: + enum eState { + syncing, + findPicture, + scanPicture + }; + int state; + void HandleStartCode(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel); + inline bool ScanDataForStartCodeSlow(const uchar *const Data); + inline bool ScanDataForStartCodeFast(const uchar *&Data, const uchar *Limit); + inline bool ScanDataForStartCode(const uchar *&Data, int &Done, int &Todo); + inline void AdjustCounters(const int Delta, int &Done, int &Todo); + inline bool ScanForEndOfPictureSlow(const uchar *&Data); + inline bool ScanForEndOfPictureFast(const uchar *&Data, const uchar *Limit); + inline bool ScanForEndOfPicture(const uchar *&Data, const uchar *Limit); +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::HandleStartCode(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // which kind of start code have we got? + switch (*Data) { + case 0xB9 ... 0xFF: // system start codes + LOG("cVideoRepacker: found system start code: stream seems to be scrambled or not demultiplexed"); + break; + case 0xB0 ... 0xB1: // reserved start codes + case 0xB6: + LOG("cVideoRepacker: found reserved start code: stream seems to be scrambled"); + break; + case 0xB4: // sequence error code + LOG("cVideoRepacker: found sequence error code: stream seems to be damaged"); + case 0xB2: // user data start code + case 0xB5: // extension start code + break; + case 0xB7: // sequence end code + case 0xB3: // sequence header code + case 0xB8: // group start code + case 0x00: // picture start code + if (state == scanPicture) { + // the above start codes indicate that the current picture is done. So + // push out the packet to start a new packet for the next picuture. If + // the byte count get's negative then the current buffer ends in a + // partitial start code that must be stripped off, as it shall be put + // in the next packet. + PushOutPacket(ResultBuffer, Payload, Data - 3 - Payload); + // go on with syncing to the next picture + state = syncing; + } + if (state == syncing) { + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + LOG("cVideoRepacker: skipped %d bytes to sync on next picture", 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 video, if a PTS is present in a PES packet header + // it shall refer to the access unit containing the first picture start + // code that commences in this PES packet. A picture start code commences + // in PES packet if the first byte of the picture start code 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++] = StreamID; // video 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 start code + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + // the next packet's payload will begin with the fourth byte of + // the start code (= the actual code) + Payload = Data; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo = maxPacketSize - pesHeaderLen; + // go on with finding the picture data + state++; + } + break; + case 0x01 ... 0xAF: // slice start codes + if (state == findPicture) { + // go on with scanning the picture data + state++; + } + break; + } +} + +bool cVideoRepacker::ScanDataForStartCodeSlow(const uchar *const Data) +{ + scanner <<= 8; + bool FoundStartCode = (scanner == 0x00000100); + scanner |= *Data; + return FoundStartCode; +} + +bool cVideoRepacker::ScanDataForStartCodeFast(const uchar *&Data, const uchar *Limit) +{ + Limit--; + + while (Data < Limit && (Data = (const uchar *)memchr(Data, 0x01, Limit - Data))) { + if (Data[-2] || Data[-1]) + Data += 3; + else { + scanner = 0x00000100 | *++Data; + return true; + } + } + + Data = Limit; + uint32_t *Scanner = (uint32_t *)(Data - 3); + scanner = ntohl(*Scanner); + return false; +} + +bool cVideoRepacker::ScanDataForStartCode(const uchar *&Data, int &Done, int &Todo) +{ + const uchar *const DataOrig = Data; + const int MinDataSize = 4; + + if (Todo < MinDataSize || (state != syncing && packetTodo < MinDataSize)) + return ScanDataForStartCodeSlow(Data); + + int Limit = Todo; + if (state != syncing && Limit > packetTodo) + Limit = packetTodo; + + if (ScanDataForStartCodeSlow(Data)) + return true; + + if (ScanDataForStartCodeSlow(++Data)) { + AdjustCounters(1, Done, Todo); + return true; + } + ++Data; + + bool FoundStartCode = ScanDataForStartCodeFast(Data, DataOrig + Limit); + AdjustCounters(Data - DataOrig, Done, Todo); + return FoundStartCode; +} + +void cVideoRepacker::AdjustCounters(const int Delta, int &Done, int &Todo) +{ + Done += Delta; + Todo -= Delta; + + if (state <= syncing) + skippedBytes += Delta; + else + packetTodo -= Delta; +} + +void cVideoRepacker::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("cVideoRepacker: 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++; + // did we reach a start code? + if (ScanDataForStartCode(data, done, todo)) + HandleStartCode(data, ResultBuffer, payload, Data[3], mpegLevel); + // move on + data++; + done++; + todo--; + // do we have to start a new packet as there is no more space left? + if (state != syncing && --packetTodo <= 0) { + // we connot start a new packet here if the current might end in a start + // code and this start code shall possibly be put in the next packet. So + // overfill the current packet until we can safely detect that we won't + // break a start code into pieces: + // + // A) the last four bytes were a start code. + // B) the current byte introduces a start code. + // C) the last three bytes begin a start code. + // + // Todo : Data : Rule : Result + // -----:-------------------------------:------:------- + // : XX 00 00 00 01 YY|YY YY YY YY : : + // 0 : ^^| : A : push + // -----:-------------------------------:------:------- + // : XX XX 00 00 00 01|YY YY YY YY : : + // 0 : ^^| : B : wait + // -1 : |^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX 00 00 00|01 YY YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : B : wait + // -2 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX 00 00|00 01 YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : B : wait + // -3 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX XX 00|00 00 01 YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : : push + // -----:-------------------------------:------:------- + bool A = ((scanner & 0xFFFFFF00) == 0x00000100); + bool B = ((scanner & 0xFFFFFF) == 0x000001); + bool C = ((scanner & 0xFF) == 0x00) && (packetTodo >= -1); + if (A || (!B && !C)) { + // 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 get's 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. + PushOutPacket(ResultBuffer, payload, bite); + // create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // video 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 a start code 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) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cVideoRepacker: skipped %d bytes while syncing on next picture", skippedBytes - SkippedBytesLimit); + skippedBytes = SkippedBytesLimit; + } +} + +bool cVideoRepacker::ScanForEndOfPictureSlow(const uchar *&Data) +{ + localScanner <<= 8; + localScanner |= *Data++; + // check start codes which follow picture data + switch (localScanner) { + case 0x00000100: // picture start code + case 0x000001B8: // group start code + case 0x000001B3: // sequence header code + case 0x000001B7: // sequence end code + return true; + } + return false; +} + +bool cVideoRepacker::ScanForEndOfPictureFast(const uchar *&Data, const uchar *Limit) +{ + Limit--; + + while (Data < Limit && (Data = (const uchar *)memchr(Data, 0x01, Limit - Data))) { + if (Data[-2] || Data[-1]) + Data += 3; + else { + localScanner = 0x00000100 | *++Data; + // check start codes which follow picture data + switch (localScanner) { + case 0x00000100: // picture start code + case 0x000001B8: // group start code + case 0x000001B3: // sequence header code + case 0x000001B7: // sequence end code + Data++; + return true; + default: + Data += 3; + } + } + } + + Data = Limit + 1; + uint32_t *LocalScanner = (uint32_t *)(Data - 4); + localScanner = ntohl(*LocalScanner); + return false; +} + +bool cVideoRepacker::ScanForEndOfPicture(const uchar *&Data, const uchar *Limit) +{ + const uchar *const DataOrig = Data; + const int MinDataSize = 4; + bool FoundEndOfPicture; + + if (Limit - Data <= MinDataSize) { + FoundEndOfPicture = false; + while (Data < Limit) { + if (ScanForEndOfPictureSlow(Data)) { + FoundEndOfPicture = true; + break; + } + } + } + else { + FoundEndOfPicture = true; + if (!ScanForEndOfPictureSlow(Data)) { + if (!ScanForEndOfPictureSlow(Data)) { + if (!ScanForEndOfPictureFast(Data, Limit)) + FoundEndOfPicture = false; + } + } + } + + localStart += (Data - DataOrig); + return FoundEndOfPicture; +} + +int cVideoRepacker::BreakAt(const uchar *Data, int Count) +{ + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + + int PesPayloadOffset = 0; + + if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= phInvalid) + return -1; // not enough data for test + + // just detect end of picture + if (state == scanPicture) { + // 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 + if (ScanForEndOfPicture(data, limit)) + return data - Data; + } + // just fill up packet and append next start code + return PesPayloadOffset + packetTodo + 4; +} + +// --- cAudioRepacker -------------------------------------------------------- + +class cAudioRepacker : public cCommonRepacker { +private: + static int bitRates[2][3][16]; + enum eState { + syncing, + scanFrame + }; + int state; + int frameTodo; + int frameSize; + int cid; + static bool IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize = NULL); +public: + cAudioRepacker(int Cid); + 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[2][3][16] = { // all values are specified as kbits/s + { + { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, // MPEG 1, Layer I + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, // MPEG 1, Layer II + { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 } // MPEG 1, Layer III + }, + { + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, // MPEG 2, Layer I + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // MPEG 2, Layer II/III + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 } // MPEG 2, Layer II/III + } + }; + +cAudioRepacker::cAudioRepacker(int Cid) +{ + cid = Cid; + Reset(); +} + +void cAudioRepacker::Reset(void) +{ + cCommonRepacker::Reset(); + scanner = 0; + state = syncing; + frameTodo = 0; + frameSize = 0; +} + +bool cAudioRepacker::IsValidAudioHeader(uint32_t Header, bool Mpeg2, 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 && !Mpeg2) // reserved in MPEG 1 + 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[2][4] = { // all values are specified in Hz + { 44100, 48000, 32000, -1 }, // MPEG 1 + { 22050, 24000, 16000, -1 } // MPEG 2 + }; + + static int slots_per_frame[2][3] = { + { 12, 144, 144 }, // MPEG 1, Layer I, II, III + { 12, 144, 72 } // MPEG 2, Layer I, II, III + }; + + int mpegIndex = 1 - id; + int layerIndex = 3 - layer; + + // Layer I (i. e., layerIndex == 0) has a larger slot size + int slotSize = (layerIndex == 0) ? 4 : 1; // bytes + + int br = 1000 * bitRates[mpegIndex][layerIndex][bitrate_index]; // bits/s + int sf = samplingFrequencies[mpegIndex][sampling_frequency]; + + int N = slots_per_frame[mpegIndex][layerIndex] * br / sf; // slots + + *FrameSize = (N + padding_bit) * slotSize; // 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++; + // did we reach an audio frame header? + scanner <<= 8; + scanner |= *data; + if ((scanner & 0xFFF00000) == 0xFFF00000) { + if (frameTodo <= 0 && (frameSize == 0 || skippedBytes >= 4) && IsValidAudioHeader(scanner, mpegLevel == phMPEG2, &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. + PushOutPacket(ResultBuffer, payload, data - 3 - payload); + // go on with syncing to the next audio frame + state = syncing; + } + if (state == syncing) { + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + LOG("cAudioRepacker(0x%02X): skipped %d bytes to sync on next audio frame", cid, 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; + // maximum we can hold in one PES packet + packetTodo = maxPacketSize - pesHeaderLen; + // expected remainder of audio frame: so far we have read 3 bytes from the frame header + frameTodo = frameSize - 3; + // go on with collecting the frame's data + state++; + } + } + } + data++; + done++; + todo--; + // do we have to start a new packet as the current is done? + if (frameTodo > 0) { + if (--frameTodo == 0) { + // the current audio frame is is done now. So push out the packet to + // start a new packet for the next audio frame. + PushOutPacket(ResultBuffer, payload, data - payload); + // go on with syncing to the next audio frame + state = syncing; + } + } + // do we have to start a new packet as there is no more space left? + if (state != syncing && --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. + PushOutPacket(ResultBuffer, payload, bite); + // 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) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cAudioRepacker(0x%02X): skipped %d bytes while syncing on next audio frame", cid, skippedBytes - SkippedBytesLimit); + skippedBytes = SkippedBytesLimit; + } +} + +int cAudioRepacker::BreakAt(const uchar *Data, int Count) +{ + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + + int PesPayloadOffset = 0; + + ePesHeader MpegLevel = AnalyzePesHeader(Data, Count, PesPayloadOffset); + if (MpegLevel <= 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, MpegLevel == phMPEG2)) + return data - Data; + } + } + // just fill up packet and append next audio frame header + return packetRemainder; +} + +// --- cDolbyRepacker -------------------------------------------------------- + +class cDolbyRepacker : public cRepacker { +private: + static int frameSizes[]; + uchar fragmentData[6 + 65535]; + int fragmentLen; + int fragmentTodo; + uchar pesHeader[6 + 3 + 255 + 4 + 4]; + int pesHeaderLen; + uchar pesHeaderBackup[6 + 3 + 255]; + int pesHeaderBackupLen; + uchar chk1; + uchar chk2; + int ac3todo; + enum eState { + find_0b, + find_77, + store_chk1, + store_chk2, + get_length, + output_packet + }; + int state; + int skippedBytes; + void ResetPesHeader(bool ContinuationFrame = false); + void AppendSubStreamID(bool ContinuationFrame = false); + bool FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); + bool StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); +public: + cDolbyRepacker(void); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +// frameSizes are in words, i. e. multiply them by 2 to get bytes +int cDolbyRepacker::frameSizes[] = { + // fs = 48 kHz + 64, 64, 80, 80, 96, 96, 112, 112, 128, 128, 160, 160, 192, 192, 224, 224, + 256, 256, 320, 320, 384, 384, 448, 448, 512, 512, 640, 640, 768, 768, 896, 896, + 1024, 1024, 1152, 1152, 1280, 1280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // fs = 44.1 kHz + 69, 70, 87, 88, 104, 105, 121, 122, 139, 140, 174, 175, 208, 209, 243, 244, + 278, 279, 348, 349, 417, 418, 487, 488, 557, 558, 696, 697, 835, 836, 975, 976, + 1114, 1115, 1253, 1254, 1393, 1394, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // fs = 32 kHz + 96, 96, 120, 120, 144, 144, 168, 168, 192, 192, 240, 240, 288, 288, 336, 336, + 384, 384, 480, 480, 576, 576, 672, 672, 768, 768, 960, 960, 1152, 1152, 1344, 1344, + 1536, 1536, 1728, 1728, 1920, 1920, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + +cDolbyRepacker::cDolbyRepacker(void) +{ + pesHeader[0] = 0x00; + pesHeader[1] = 0x00; + pesHeader[2] = 0x01; + pesHeader[3] = 0xBD; + pesHeader[4] = 0x00; + pesHeader[5] = 0x00; + Reset(); +} + +void cDolbyRepacker::AppendSubStreamID(bool ContinuationFrame) +{ + if (subStreamId) { + pesHeader[pesHeaderLen++] = subStreamId; + // number of ac3 frames "starting" in this packet (1 by design). + pesHeader[pesHeaderLen++] = 0x01; + // offset to start of first ac3 frame (0 means "no ac3 frame starting" + // so 1 (by design) addresses the first byte after the next two bytes). + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = (ContinuationFrame ? 0x00 : 0x01); + } +} + +void cDolbyRepacker::ResetPesHeader(bool ContinuationFrame) +{ + pesHeader[6] = 0x80; + pesHeader[7] = 0x00; + pesHeader[8] = 0x00; + pesHeaderLen = 9; + AppendSubStreamID(ContinuationFrame); +} + +void cDolbyRepacker::Reset(void) +{ + cRepacker::Reset(); + ResetPesHeader(); + state = find_0b; + ac3todo = 0; + chk1 = 0; + chk2 = 0; + fragmentLen = 0; + fragmentTodo = 0; + pesHeaderBackupLen = 0; + skippedBytes = 0; +} + +bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) +{ + bool success = true; + // enough data available to put PES packet into buffer? + if (fragmentTodo <= Todo) { + // output a previous fragment first + if (fragmentLen > 0) { + Bite = fragmentLen; + int n = Put(ResultBuffer, fragmentData, Bite, fragmentLen + fragmentTodo); + if (Bite != n) + success = false; + fragmentLen = 0; + } + Bite = fragmentTodo; + if (success) { + int n = Put(ResultBuffer, Data, Bite, Bite); + if (Bite != n) + success = false; + } + fragmentTodo = 0; + // ac3 frame completely processed? + if (Bite >= ac3todo) + state = find_0b; // go on with finding start of next packet + } + else { + // copy the fragment into separate buffer for later processing + Bite = Todo; + memcpy(fragmentData + fragmentLen, Data, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + } + return success; +} + +bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) +{ + bool success = true; + int packetLen = pesHeaderLen + ac3todo; + // limit packet to maximum size + if (packetLen > maxPacketSize) + packetLen = maxPacketSize; + pesHeader[4] = (packetLen - 6) >> 8; + pesHeader[5] = (packetLen - 6) & 0xFF; + Bite = pesHeaderLen; + // enough data available to put PES packet into buffer? + if (packetLen - pesHeaderLen <= Todo) { + int n = Put(ResultBuffer, pesHeader, Bite, packetLen); + if (Bite != n) + success = false; + Bite = packetLen - pesHeaderLen; + if (success) { + n = Put(ResultBuffer, Data, Bite, Bite); + if (Bite != n) + success = false; + } + // ac3 frame completely processed? + if (Bite >= ac3todo) + state = find_0b; // go on with finding start of next packet + } + else { + fragmentTodo = packetLen; + // copy the pesheader into separate buffer for later processing + memcpy(fragmentData + fragmentLen, pesHeader, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + // copy the fragment into separate buffer for later processing + Bite = Todo; + memcpy(fragmentData + fragmentLen, Data, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + } + return success; +} + +void cDolbyRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // check for MPEG 2 + if ((Data[6] & 0xC0) != 0x80) { + DroppedData("cDolbyRepacker: MPEG 2 PES header expected", Count); + return; + } + + // backup PES header + if (Data[6] != 0x80 || Data[7] != 0x00 || Data[8] != 0x00) { + pesHeaderBackupLen = 6 + 3 + Data[8]; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = 6 + 3 + Data[8]; + int todo = Count - done; + const uchar *data = Data + done; + + // look for 0x0B 0x77 + while (todo > 0) { + switch (state) { + case find_0b: + if (*data == 0x0B) { + state++; + // copy header information once for later use + if (pesHeaderBackupLen > 0) { + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + memcpy(pesHeader, pesHeaderBackup, pesHeaderLen); + AppendSubStreamID(); + } + } + data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + continue; + case find_77: + if (*data != 0x77) { + state = find_0b; + continue; + } + data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case store_chk1: + chk1 = *data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case store_chk2: + chk2 = *data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case get_length: + ac3todo = 2 * frameSizes[*data]; + // frameSizeCode was invalid => restart searching + if (ac3todo <= 0) { + // reset PES header instead of using a wrong one + ResetPesHeader(); + if (chk1 == 0x0B) { + if (chk2 == 0x77) { + state = store_chk1; + continue; + } + if (chk2 == 0x0B) { + state = find_77; + continue; + } + state = find_0b; + continue; + } + if (chk2 == 0x0B) { + state = find_77; + continue; + } + state = find_0b; + continue; + } + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + LOG("cDolbyRepacker: skipped %d bytes to sync on next AC3 frame", skippedBytes - SkippedBytesLimit); + skippedBytes = 0; + // append read data to header for common output processing + pesHeader[pesHeaderLen++] = 0x0B; + pesHeader[pesHeaderLen++] = 0x77; + pesHeader[pesHeaderLen++] = chk1; + pesHeader[pesHeaderLen++] = chk2; + ac3todo -= 4; + state++; + // fall through to output + case output_packet: { + int bite = 0; + // finish remainder of ac3 frame? + if (fragmentTodo > 0) + FinishRemainder(ResultBuffer, data, todo, bite); + else { + // start a new packet + StartNewPacket(ResultBuffer, data, todo, bite); + // prepare for next (continuation) packet + ResetPesHeader(state == output_packet); + } + data += bite; + done += bite; + todo -= bite; + ac3todo -= bite; + } + } + } + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cDolbyRepacker: skipped %d bytes while syncing on next AC3 frame", skippedBytes - 4); + skippedBytes = SkippedBytesLimit; + } +} + +int cDolbyRepacker::BreakAt(const uchar *Data, int Count) +{ + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + // enough data for test? + if (Count < 6 + 3) + return -1; + // check for MPEG 2 + if ((Data[6] & 0xC0) != 0x80) + return -1; + int headerLen = Data[8] + 6 + 3; + // break after fragment tail? + if (ac3todo > 0) + return headerLen + ac3todo; + // enough data for test? + if (Count < headerLen + 5) + return -1; + const uchar *data = Data + headerLen; + // break after ac3 frame? + if (data[0] == 0x0B && data[1] == 0x77 && frameSizes[data[4]] > 0) + return headerLen + 2 * frameSizes[data[4]]; + return -1; +} + +// --- cTS2PES --------------------------------------------------------------- + +#include + +//XXX TODO: these should really be available in some driver header file! +#define PROG_STREAM_MAP 0xBC +#ifndef PRIVATE_STREAM1 +#define PRIVATE_STREAM1 0xBD +#endif +#define PADDING_STREAM 0xBE +#ifndef PRIVATE_STREAM2 +#define PRIVATE_STREAM2 0xBF +#endif +#define AUDIO_STREAM_S 0xC0 +#define AUDIO_STREAM_E 0xDF +#define VIDEO_STREAM_S 0xE0 +#define VIDEO_STREAM_E 0xEF +#define ECM_STREAM 0xF0 +#define EMM_STREAM 0xF1 +#define DSM_CC_STREAM 0xF2 +#define ISO13522_STREAM 0xF3 +#define PROG_STREAM_DIR 0xFF + +//pts_dts flags +#define PTS_ONLY 0x80 + +#define TS_SIZE 188 +#define PID_MASK_HI 0x1F +#define CONT_CNT_MASK 0x0F + +// Flags: +#define PAY_LOAD 0x10 +#define ADAPT_FIELD 0x20 +#define PAY_START 0x40 +#define TS_ERROR 0x80 + +#define MAX_PLENGTH 0xFFFF // the maximum PES packet length (theoretically) +#define MMAX_PLENGTH (64*MAX_PLENGTH) // some stations send PES packets that are extremely large, e.g. DVB-T in Finland or HDTV 1920x1080 + +#define IPACKS 2048 + +// Start codes: +#define SC_SEQUENCE 0xB3 // "sequence header code" +#define SC_GROUP 0xB8 // "group start code" +#define SC_PICTURE 0x00 // "picture start code" + +#define MAXNONUSEFULDATA (10*1024*1024) +#define MAXNUMUPTERRORS 10 + +class cTS2PES { +private: + int pid; + int size; + int found; + int count; + uint8_t *buf; + uint8_t cid; + uint8_t rewriteCid; + uint8_t subStreamId; + int plength; + uint8_t plen[2]; + uint8_t flag1; + uint8_t flag2; + uint8_t hlength; + int mpeg; + uint8_t check; + int mpeg1_required; + int mpeg1_stuffing; + bool done; + cRingBufferLinear *resultBuffer; + int tsErrors; + int ccErrors; + int ccCounter; + cRepacker *repacker; + static uint8_t headr[]; + void store(uint8_t *Data, int Count); + void reset_ipack(void); + void send_ipack(void); + void write_ipack(const uint8_t *Data, int Count); + void instant_repack(const uint8_t *Buf, int Count); +public: + cTS2PES(int Pid, cRingBufferLinear *ResultBuffer, int Size, uint8_t RewriteCid = 0x00, uint8_t SubStreamId = 0x00, cRepacker *Repacker = NULL); + ~cTS2PES(); + int Pid(void) { return pid; } + void ts_to_pes(const uint8_t *Buf); // don't need count (=188) + void Clear(void); + }; + +uint8_t cTS2PES::headr[] = { 0x00, 0x00, 0x01 }; + +cTS2PES::cTS2PES(int Pid, cRingBufferLinear *ResultBuffer, int Size, uint8_t RewriteCid, uint8_t SubStreamId, cRepacker *Repacker) +{ + pid = Pid; + resultBuffer = ResultBuffer; + size = Size; + rewriteCid = RewriteCid; + subStreamId = SubStreamId; + repacker = Repacker; + if (repacker) { + repacker->SetMaxPacketSize(size); + repacker->SetSubStreamId(subStreamId); + size += repacker->QuerySnoopSize(); + } + + tsErrors = 0; + ccErrors = 0; + ccCounter = -1; + + if (!(buf = MALLOC(uint8_t, size))) + esyslog("Not enough memory for ts_transform"); + + reset_ipack(); +} + +cTS2PES::~cTS2PES() +{ + if (tsErrors || ccErrors) + dsyslog("cTS2PES got %d TS errors, %d TS continuity errors", tsErrors, ccErrors); + free(buf); + delete repacker; +} + +void cTS2PES::Clear(void) +{ + reset_ipack(); + if (repacker) + repacker->Reset(); +} + +void cTS2PES::store(uint8_t *Data, int Count) +{ + if (repacker) + repacker->Repack(resultBuffer, Data, Count); + else + cRepacker::Put(resultBuffer, Data, Count, Count); +} + +void cTS2PES::reset_ipack(void) +{ + found = 0; + cid = 0; + plength = 0; + flag1 = 0; + flag2 = 0; + hlength = 0; + mpeg = 0; + check = 0; + mpeg1_required = 0; + mpeg1_stuffing = 0; + done = false; + count = 0; +} + +void cTS2PES::send_ipack(void) +{ + if (count <= ((mpeg == 2) ? 9 : 7)) // skip empty packets + return; + buf[3] = rewriteCid ? rewriteCid : cid; + buf[4] = (uint8_t)(((count - 6) & 0xFF00) >> 8); + buf[5] = (uint8_t)((count - 6) & 0x00FF); + store(buf, count); + + switch (mpeg) { + case 2: + buf[6] = 0x80; + buf[7] = 0x00; + buf[8] = 0x00; + count = 9; + if (!repacker && subStreamId) { + buf[9] = subStreamId; + buf[10] = 1; + buf[11] = 0; + buf[12] = 1; + count = 13; + } + break; + case 1: + buf[6] = 0x0F; + count = 7; + break; + } +} + +void cTS2PES::write_ipack(const uint8_t *Data, int Count) +{ + if (count < 6) { + memcpy(buf, headr, 3); + count = 6; + } + + // determine amount of data to process + int bite = Count; + if (count + bite > size) + bite = size - count; + if (repacker) { + int breakAt = repacker->BreakAt(buf, count); + // avoid memcpy of data after break location + if (0 <= breakAt && breakAt < count + bite) { + bite = breakAt - count; + if (bite < 0) // should never happen + bite = 0; + } + } + + memcpy(buf + count, Data, bite); + count += bite; + + if (repacker) { + // determine break location + int breakAt = repacker->BreakAt(buf, count); + if (breakAt > size) // won't fit into packet? + breakAt = -1; + if (breakAt > count) // not enough data? + breakAt = -1; + // push out data before break location + if (breakAt > 0) { + // adjust bite if above memcpy was to large + bite -= count - breakAt; + count = breakAt; + send_ipack(); + // recurse for data after break location + if (Count - bite > 0) + write_ipack(Data + bite, Count - bite); + } + } + + // push out data when buffer is full + if (count >= size) { + send_ipack(); + // recurse for remaining data + if (Count - bite > 0) + write_ipack(Data + bite, Count - bite); + } +} + +void cTS2PES::instant_repack(const uint8_t *Buf, int Count) +{ + int c = 0; + + while (c < Count && (mpeg == 0 || (mpeg == 1 && found < mpeg1_required) || (mpeg == 2 && found < 9)) && (found < 5 || !done)) { + switch (found ) { + case 0: + case 1: + if (Buf[c] == 0x00) + found++; + else + found = 0; + c++; + break; + case 2: + if (Buf[c] == 0x01) + found++; + else if (Buf[c] != 0) + found = 0; + c++; + break; + case 3: + cid = 0; + switch (Buf[c]) { + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + done = true; + case PRIVATE_STREAM1: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + found++; + cid = Buf[c++]; + break; + default: + found = 0; + break; + } + break; + case 4: + if (Count - c > 1) { + unsigned short *pl = (unsigned short *)(Buf + c); + plength = ntohs(*pl); + c += 2; + found += 2; + mpeg1_stuffing = 0; + } + else { + plen[0] = Buf[c]; + found++; + return; + } + break; + case 5: { + plen[1] = Buf[c++]; + unsigned short *pl = (unsigned short *)plen; + plength = ntohs(*pl); + found++; + mpeg1_stuffing = 0; + } + break; + case 6: + if (!done) { + flag1 = Buf[c++]; + found++; + 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 || mpeg1_required > 7)) { + flag2 = Buf[c++]; + found++; + } + break; + case 8: + 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: + break; + } + } + + if (!plength) + plength = MMAX_PLENGTH - 6; + + 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: + case PRIVATE_STREAM1: + + if (mpeg == 2 && found == 9 && count < found) { // make sure to not write the data twice by looking at count + write_ipack(&flag1, 1); + write_ipack(&flag2, 1); + write_ipack(&hlength, 1); + } + + if (mpeg == 1 && found == mpeg1_required && count < found) { // make sure to not write the data twice by looking at count + 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) { + write_ipack(Buf + c, 1); + c++; + found++; + } + if (c == Count) + return; + } + + if (!repacker && subStreamId) { + while (c < Count && found < (hlength + 9) && found < plength + 6) { + write_ipack(Buf + c, 1); + c++; + found++; + } + if (found == (hlength + 9)) { + uchar sbuf[] = { 0x01, 0x00, 0x00 }; + write_ipack(&subStreamId, 1); + write_ipack(sbuf, 3); + } + } + + while (c < Count && found < plength + 6) { + int l = Count - c; + if (l + found > plength + 6) + l = plength + 6 - found; + write_ipack(Buf + c, l); + found += l; + c += l; + } + + break; + } + + if (done) { + if (found + Count - c < plength + 6) { + found += Count - c; + c = Count; + } + else { + c += plength + 6 - found; + found = plength + 6; + } + } + + if (plength && found == plength + 6) { + if (plength == MMAX_PLENGTH - 6) + esyslog("ERROR: PES packet length overflow in remuxer (stream corruption)"); + send_ipack(); + reset_ipack(); + if (c < Count) + instant_repack(Buf + c, Count - c); + } + } + return; +} + +void cTS2PES::ts_to_pes(const uint8_t *Buf) // don't need count (=188) +{ + if (!Buf) + return; + + if (Buf[1] & TS_ERROR) + tsErrors++; + + if (!(Buf[3] & (ADAPT_FIELD | PAY_LOAD))) + return; // discard TS packet with adaption_field_control set to '00'. + + if ((Buf[3] & PAY_LOAD) && ((Buf[3] ^ ccCounter) & CONT_CNT_MASK)) { + // This should check duplicates and packets which do not increase the counter. + // But as the errors usually come in bursts this should be enough to + // show you there is something wrong with signal quality. + if (ccCounter != -1 && ((Buf[3] ^ (ccCounter + 1)) & CONT_CNT_MASK)) { + ccErrors++; + // Enable this if you are having problems with signal quality. + // These are the errors I used to get with Nova-T when antenna + // was not positioned correcly (not transport errors). //tvr + //dsyslog("TS continuity error (%d)", ccCounter); + } + ccCounter = Buf[3] & CONT_CNT_MASK; + } + + if (Buf[1] & PAY_START) { + if (found > 6) { + if (plength != MMAX_PLENGTH - 6 && plength != found - 6) + dsyslog("PES packet shortened to %d bytes (expected: %d bytes)", found, plength + 6); + plength = found - 6; + send_ipack(); + reset_ipack(); + } + found = 0; + } + + uint8_t off = 0; + + if (Buf[3] & ADAPT_FIELD) { // adaptation field? + off = Buf[4] + 1; + if (off + 4 > 187) + return; + } + + if (Buf[3] & PAY_LOAD) + instant_repack(Buf + 4 + off, TS_SIZE - 4 - off); +} + +// --- cRingBufferLinearPes -------------------------------------------------- + +class cRingBufferLinearPes : public cStreamdevBuffer { +protected: + virtual int DataReady(const uchar *Data, int Count); +public: + cRingBufferLinearPes(int Size, int Margin = 0, bool Statistics = false, const char *Description = NULL) + :cStreamdevBuffer(Size, Margin, Statistics, Description) {} + }; + +int cRingBufferLinearPes::DataReady(const uchar *Data, int Count) +{ + int c = cRingBufferLinear::DataReady(Data, Count); + if (!c && Count >= 6) { + if (!Data[0] && !Data[1] && Data[2] == 0x01) { + int Length = 6 + Data[4] * 256 + Data[5]; + if (Length <= Count) + return Length; + } + } + return c; +} + +// --- cTS2PESRemux ---------------------------------------------------------------- + +#define RESULTBUFFERSIZE KILOBYTE(256) + +cTS2PESRemux::cTS2PESRemux(int VPid, const int *APids, const int *DPids, const int *SPids) +{ + noVideo = VPid == 0 || VPid == 1 || VPid == 0x1FFF; + synced = false; + skipped = 0; + numTracks = 0; + resultSkipped = 0; + resultBuffer = new cRingBufferLinearPes(RESULTBUFFERSIZE, IPACKS, false, "Result"); + resultBuffer->SetTimeouts(100, 100); + if (VPid) +#define TEST_cVideoRepacker +#ifdef TEST_cVideoRepacker + ts2pes[numTracks++] = new cTS2PES(VPid, resultBuffer, IPACKS, 0xE0, 0x00, new cVideoRepacker); +#else + ts2pes[numTracks++] = new cTS2PES(VPid, resultBuffer, IPACKS, 0xE0); +#endif + 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(0xC0 + n)); + n++; +#else + ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n++); +#endif + } + } + if (DPids) { + int n = 0; + while (*DPids && numTracks < MAXTRACKS && n < MAXDPIDS) + ts2pes[numTracks++] = new cTS2PES(*DPids++, resultBuffer, IPACKS, 0x00, 0x80 + n++, new cDolbyRepacker); + } + if (SPids) { + int n = 0; + while (*SPids && numTracks < MAXTRACKS && n < MAXSPIDS) + ts2pes[numTracks++] = new cTS2PES(*SPids++, resultBuffer, IPACKS, 0x00, 0x20 + n++); + } +} + +cTS2PESRemux::~cTS2PESRemux() +{ + for (int t = 0; t < numTracks; t++) + delete ts2pes[t]; + delete resultBuffer; +} + +#define TS_SYNC_BYTE 0x47 + +int cTS2PESRemux::Put(const uchar *Data, int Count) +{ + int used = 0; + + // Make sure we are looking at a TS packet: + + while (Count > TS_SIZE) { + if (Data[0] == TS_SYNC_BYTE && Data[TS_SIZE] == TS_SYNC_BYTE) + break; + Data++; + Count--; + used++; + } + if (used) + esyslog("ERROR: skipped %d byte to sync on TS packet", used); + + // Convert incoming TS data into multiplexed PES: + + for (int i = 0; i < Count; i += TS_SIZE) { + if (Count - i < TS_SIZE) + break; + if (Data[i] != TS_SYNC_BYTE) + break; + if (resultBuffer->Free() < 2 * IPACKS) { + resultBuffer->WaitForPut(); + break; // A cTS2PES might write one full packet and also a small rest + } + int pid = cTSRemux::GetPid(Data + i + 1); + if (Data[i + 3] & 0x10) { // got payload + for (int t = 0; t < numTracks; t++) { + if (ts2pes[t]->Pid() == pid) { + ts2pes[t]->ts_to_pes(Data + i); + break; + } + } + } + used += TS_SIZE; + } + + // Check if we're getting anywhere here: + if (!synced && skipped >= 0) { + if (skipped > MAXNONUSEFULDATA) { + esyslog("ERROR: no useful data seen within %d byte of video stream", skipped); + skipped = -1; + } + else + skipped += used; + } + + return used; +} + +uchar *cTS2PESRemux::Get(int &Count, uchar *PictureType) +{ + // Remove any previously skipped data from the result buffer: + + if (resultSkipped > 0) { + resultBuffer->Del(resultSkipped); + resultSkipped = 0; + } + +#if 0 + // Test recording without determining the real frame borders: + if (PictureType) + *PictureType = I_FRAME; + return resultBuffer->Get(Count); +#endif + + // Check for frame borders: + + if (PictureType) + *PictureType = NO_PICTURE; + + Count = 0; + uchar *resultData = NULL; + int resultCount = 0; + uchar *data = resultBuffer->Get(resultCount); + if (data) { + for (int i = 0; i < resultCount - 3; i++) { + if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { + int l = 0; + uchar StreamType = data[i + 3]; + if (VIDEO_STREAM_S <= StreamType && StreamType <= VIDEO_STREAM_E) { + uchar pt = NO_PICTURE; + l = cTSRemux::ScanVideoPacket(data, resultCount, i, pt); + if (l < 0) + return resultData; + if (pt != NO_PICTURE) { + if (pt < I_FRAME || B_FRAME < pt) { + esyslog("ERROR: unknown picture type '%d'", pt); + } + else if (!synced) { + if (pt == I_FRAME) { + if (PictureType) + *PictureType = pt; + resultSkipped = i; // will drop everything before this position + cTSRemux::SetBrokenLink(data + i, l); + synced = true; + } + } + else if (Count) + return resultData; + else if (PictureType) + *PictureType = pt; + } + } + else { //if (AUDIO_STREAM_S <= StreamType && StreamType <= AUDIO_STREAM_E || StreamType == PRIVATE_STREAM1) { + l = cTSRemux::GetPacketLength(data, resultCount, i); + if (l < 0) + return resultData; + if (noVideo) { + 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) + resultData = data + i; + Count += l; + } + else + resultSkipped = i + l; + if (l > 0) + i += l - 1; // the loop increments, too + } + } + } + return resultData; +} + +void cTS2PESRemux::Del(int Count) +{ + resultBuffer->Del(Count); +} + +void cTS2PESRemux::Clear(void) +{ + for (int t = 0; t < numTracks; t++) + ts2pes[t]->Clear(); + resultBuffer->Clear(); + synced = false; + skipped = 0; + resultSkipped = 0; +} + +} // namespace Streamdev diff --git a/remux/ts2pes.h b/remux/ts2pes.h new file mode 100644 index 0000000..f843cf3 --- /dev/null +++ b/remux/ts2pes.h @@ -0,0 +1,58 @@ +/* + * ts2pes.h: A streaming MPEG2 remultiplexer + * + * This file is based on a copy of remux.h from Klaus Schmidinger's + * VDR, version 1.6.0. + * + * $Id: ts2pes.h,v 1.1 2009/06/19 06:32:40 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_TS2PES_H +#define VDR_STREAMDEV_TS2PES_H + +#include "remux/tsremux.h" +#include "server/streamer.h" + +#define MAXTRACKS 64 + +namespace Streamdev { + +class cTS2PES; + +class cTS2PESRemux: public cTSRemux { +private: + bool noVideo; + bool synced; + int skipped; + Streamdev::cTS2PES *ts2pes[MAXTRACKS]; + int numTracks; + cStreamdevBuffer *resultBuffer; + int resultSkipped; +public: + cTS2PESRemux(int VPid, const int *APids, const int *DPids, const int *SPids); + ///< Creates a new remuxer for the given PIDs. VPid is the video PID, while + ///< APids, DPids and SPids are pointers to zero terminated lists of audio, + ///< dolby and subtitle PIDs (the pointers may be NULL if there is no such + ///< PID). + ~cTS2PESRemux(); + int Put(const uchar *Data, int Count); + ///< Puts at most Count bytes of Data into the remuxer. + ///< \return Returns the number of bytes actually consumed from Data. + uchar *Get(int &Count, uchar *PictureType = NULL); + ///< Gets all currently available data from the remuxer. + ///< \return Count contains the number of bytes the result points to, and + ///< PictureType (if not NULL) will contain one of NO_PICTURE, I_FRAME, P_FRAME + ///< or B_FRAME. + void Del(int Count); + ///< Deletes Count bytes from the remuxer. Count must be the number returned + ///< from a previous call to Get(). Several calls to Del() with fractions of + ///< a previously returned Count may be made, but the total sum of all Count + ///< values must be exactly what the previous Get() has returned. + void Clear(void); + ///< Clears the remuxer of all data it might still contain, keeping the PID + ///< settings as they are. + }; + +} // namespace Streamdev + +#endif // VDR_STREAMDEV_TS2PES_H diff --git a/remux/ts2ps.c b/remux/ts2ps.c index d0d08cf..2a97dee 100644 --- a/remux/ts2ps.c +++ b/remux/ts2ps.c @@ -3,6 +3,8 @@ #include #include +namespace Streamdev { + class cTS2PS { friend void PutPES(uint8_t *Buffer, int Size, void *Data); @@ -28,6 +30,9 @@ void PutPES(uint8_t *Buffer, int Size, void *Data) esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Size - n, Size); } +} // namespace Streamdev +using namespace Streamdev; + cTS2PS::cTS2PS(cRingBufferLinear *ResultBuffer, int Pid, uint8_t AudioCid) { m_ResultBuffer = ResultBuffer; @@ -74,13 +79,13 @@ void cTS2PS::PutTSPacket(const uint8_t *Buffer) cTS2PSRemux::cTS2PSRemux(int VPid, const int *APids, const int *DPids, const int *SPids): m_NumTracks(0), - m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, IPACKS)), + m_ResultBuffer(new cStreamdevBuffer(WRITERBUFSIZE, IPACKS)), m_ResultSkipped(0), m_Skipped(0), m_Synced(false), m_IsRadio(VPid == 0 || VPid == 1 || VPid == 0x1FFF) { - m_ResultBuffer->SetTimeouts(0, 100); + m_ResultBuffer->SetTimeouts(100, 100); if (VPid) m_Remux[m_NumTracks++] = new cTS2PS(m_ResultBuffer, VPid); @@ -124,8 +129,10 @@ int cTS2PSRemux::Put(const uchar *Data, int Count) break; if (Data[i] != TS_SYNC_BYTE) break; - if (m_ResultBuffer->Free() < 2 * IPACKS) + if (m_ResultBuffer->Free() < 2 * IPACKS) { + m_ResultBuffer->WaitForPut(); break; // A cTS2PS might write one full packet and also a small rest + } int pid = GetPid(Data + i + 1); if (Data[i + 3] & 0x10) { // got payload for (int t = 0; t < m_NumTracks; t++) { diff --git a/remux/ts2ps.h b/remux/ts2ps.h index f31e025..63ce992 100644 --- a/remux/ts2ps.h +++ b/remux/ts2ps.h @@ -2,20 +2,21 @@ #define VDR_STREAMDEV_TS2PESREMUX_H #include "remux/tsremux.h" -#include -#include +#include "server/streamer.h" #ifndef MAXTRACKS #define MAXTRACKS 64 #endif +namespace Streamdev { + class cTS2PS; class cTS2PSRemux: public cTSRemux { private: int m_NumTracks; cTS2PS *m_Remux[MAXTRACKS]; - cRingBufferLinear *m_ResultBuffer; + cStreamdevBuffer *m_ResultBuffer; int m_ResultSkipped; int m_Skipped; bool m_Synced; @@ -30,4 +31,6 @@ public: void Del(int Count) { m_ResultBuffer->Del(Count); } }; +} // namespace Streamdev + #endif // VDR_STREAMDEV_TS2PESREMUX_H diff --git a/remux/tsremux.c b/remux/tsremux.c index c73c2fe..a503ed0 100644 --- a/remux/tsremux.c +++ b/remux/tsremux.c @@ -2,11 +2,15 @@ #define SC_PICTURE 0x00 // "picture header" #define PID_MASK_HI 0x1F +#define VIDEO_STREAM_S 0xE0 + +using namespace Streamdev; void cTSRemux::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; @@ -40,17 +44,40 @@ int cTSRemux::ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &P // 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++) { - 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; - return Length; + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) { + const uchar *p = Data + Offset + PesPayloadOffset + 2; + const uchar *pLimit = Data + Offset + Length - 3; +#ifdef TEST_cVideoRepacker + // cVideoRepacker ensures that a new PES packet is started for a new sequence, + // group or picture which allows us to easily skip scanning through a huge + // amount of video data. + if (p < pLimit) { + if (p[-2] || p[-1] || p[0] != 0x01) + pLimit = 0; // skip scanning: packet doesn't start with 0x000001 + else { + switch (p[1]) { + case SC_SEQUENCE: + case SC_GROUP: + case SC_PICTURE: + break; + default: // skip scanning: packet doesn't start a new sequence, group or picture + pLimit = 0; + } + } + } +#endif + while (p < pLimit && (p = (const uchar *)memchr(p, 0x01, pLimit - p))) { + if (!p[-2] && !p[-1]) { // found 0x000001 + switch (p[1]) { + case SC_PICTURE: PictureType = (p[3] >> 3) & 0x07; + return Length; + } + p += 4; // continue scanning after 0x01ssxxyy } - } - } + else + p += 3; // continue scanning after 0x01xxyy + } } PictureType = NO_PICTURE; return Length; diff --git a/remux/tsremux.h b/remux/tsremux.h index a7fe481..09a5372 100644 --- a/remux/tsremux.h +++ b/remux/tsremux.h @@ -4,34 +4,22 @@ #include "libdvbmpeg/transform.h" #include -#ifndef NO_PICTURE +// Picture types: #define NO_PICTURE 0 -#endif +#define I_FRAME 1 +#define P_FRAME 2 +#define B_FRAME 3 -#define RESULTBUFFERSIZE KILOBYTE(256) +namespace Streamdev { class cTSRemux { -protected: - /*uchar m_ResultBuffer[RESULTBUFFERSIZE]; - int m_ResultCount; - int m_ResultDelivered; - int m_Synced; - int m_Skipped; - int m_Sync; - - - virtual void PutTSPacket(int Pid, const uint8_t *Data) = 0; - public: - cTSRemux(bool Sync = true); - virtual ~cTSRemux(); - - virtual uchar *Process(const uchar *Data, int &Count, int &Result);*/ - static void SetBrokenLink(uchar *Data, int Length); static int GetPid(const uchar *Data); - static int GetPacketLength(const uchar *Data, int Count, int Offset); + static int GetPacketLength(const uchar *Data, int Count, int Offset); static int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType); }; +} // namespace Streamdev + #endif // VDR_STREAMDEV_TSREMUX_H diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c index fc10bfc..83e568d 100644 --- a/server/connectionHTTP.c +++ b/server/connectionHTTP.c @@ -1,5 +1,5 @@ /* - * $Id: connectionHTTP.c,v 1.16 2009/02/13 07:02:19 schmirl Exp $ + * $Id: connectionHTTP.c,v 1.17 2009/06/19 06:32:45 schmirl Exp $ */ #include @@ -211,10 +211,8 @@ bool cConnectionHTTP::CmdGET(const std::string &Opts) const char* pType = type.c_str(); if (strcasecmp(pType, "PS") == 0) { m_StreamType = stPS; -#if APIVERSNUM < 10703 } else if (strcasecmp(pType, "PES") == 0) { m_StreamType = stPES; -#endif } else if (strcasecmp(pType, "TS") == 0) { m_StreamType = stTS; } else if (strcasecmp(pType, "ES") == 0) { @@ -266,9 +264,7 @@ bool cConnectionHTTP::CmdGET(const std::string &Opts) { case stTS: base += "TS/"; break; case stPS: base += "PS/"; break; -#if APIVERSNUM < 10703 case stPES: base += "PES/"; break; -#endif case stES: base += "ES/"; break; case stExtern: base += "Extern/"; break; default: break; diff --git a/server/connectionVTP.c b/server/connectionVTP.c index e0edb6e..ebe339b 100644 --- a/server/connectionVTP.c +++ b/server/connectionVTP.c @@ -1,5 +1,5 @@ /* - * $Id: connectionVTP.c,v 1.19 2009/01/16 11:35:44 schmirl Exp $ + * $Id: connectionVTP.c,v 1.20 2009/06/19 06:32:45 schmirl Exp $ */ #include "server/connectionVTP.h" @@ -595,12 +595,10 @@ bool cConnectionVTP::CmdCAPS(char *Opts) return Respond(220, "Capability \"%s\" accepted", Opts); } -#if APIVERSNUM < 10703 if (strcasecmp(Opts, "PES") == 0) { m_StreamType = stPES; return Respond(220, "Capability \"%s\" accepted", Opts); } -#endif if (strcasecmp(Opts, "EXTERN") == 0) { m_StreamType = stExtern; diff --git a/server/livestreamer.c b/server/livestreamer.c index 97dffd7..684e701 100644 --- a/server/livestreamer.c +++ b/server/livestreamer.c @@ -4,6 +4,7 @@ #include #include "remux/ts2ps.h" +#include "remux/ts2pes.h" #include "remux/ts2es.h" #include "remux/extern.h" @@ -13,7 +14,7 @@ #include "server/livefilter.h" #include "common.h" -#define TSPATREPACKER +using namespace Streamdev; // --- cStreamdevLiveReceiver ------------------------------------------------- @@ -64,6 +65,8 @@ private: int pmtPid; int pmtSid; int pmtVersion; + uchar tspat_buf[TS_SIZE]; + cStreamdevBuffer siBuffer; const cChannel *m_Channel; cStreamdevLiveStreamer *m_Streamer; @@ -73,9 +76,11 @@ private: int GetPid(SI::PMT::Stream& stream); public: cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel); + uchar* Get(int &Count) { return siBuffer.Get(Count); } + void Del(int Count) { return siBuffer.Del(Count); } }; -cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel) +cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel): siBuffer(10 * TS_SIZE, TS_SIZE) { Dprintf("cStreamdevPatFilter(\"%s\")\n", Channel->Name()); assert(Streamer); @@ -85,6 +90,29 @@ cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const pmtSid = 0; pmtVersion = -1; Set(0x00, 0x00); // PAT + // initialize PAT buffer. Only some values are dynamic (see comments) + memset(tspat_buf, 0xff, TS_SIZE); + tspat_buf[0] = TS_SYNC_BYTE; // Transport packet header sunchronization byte (1000011 = 0x47h) + tspat_buf[1] = 0x40; // Set payload unit start indicator bit + tspat_buf[2] = 0x0; // PID + tspat_buf[3] = 0x10; // Set payload flag, DYNAMIC: Continuity counter + tspat_buf[4] = 0x0; // SI pointer field + tspat_buf[5] = 0x0; // PAT table id + tspat_buf[6] = 0xb0; // Section syntax indicator bit and reserved bits set + tspat_buf[7] = 12 + 1; // Section length (12 bit): PAT_TABLE_LEN + 1 + tspat_buf[8] = 0; // DYNAMIC: Transport stream ID (bits 8-15) + tspat_buf[9] = 0; // DYNAMIC: Transport stream ID (bits 0-7) + tspat_buf[10] = 0xc0; // Reserved, DYNAMIC: Version number, DYNAMIC: Current next indicator + tspat_buf[11] = 0x0; // Section number + tspat_buf[12] = 0x0; // Last section number + tspat_buf[13] = 0; // DYNAMIC: Program number (bits 8-15) + tspat_buf[14] = 0; // DYNAMIC: Program number (bits 0-7) + tspat_buf[15] = 0xe0; // Reserved, DYNAMIC: Network ID (bits 8-12) + tspat_buf[16] = 0; // DYNAMIC: Network ID (bits 0-7) + tspat_buf[17] = 0; // DYNAMIC: Checksum + tspat_buf[18] = 0; // DYNAMIC: Checksum + tspat_buf[19] = 0; // DYNAMIC: Checksum + tspat_buf[20] = 0; // DYNAMIC: Checksum } static const char * const psStreamTypes[] = { @@ -224,54 +252,37 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i if (0 != (pmtPid = assoc.getPid())) { Dprintf("cStreamdevPatFilter: PMT pid for channel %s: %d\n", Channel->Name(), pmtPid); pmtSid = assoc.getServiceId(); - if (Length < TS_SIZE-5) { - // repack PAT to TS frame and send to client -#ifndef TSPATREPACKER - uint8_t pat_ts[TS_SIZE] = {TS_SYNC_BYTE, 0x40 /* pusi=1 */, 0 /* pid=0 */, 0x10 /* adaption=1 */, 0 /* pointer */}; - memcpy(pat_ts + 5, Data, Length); - m_Streamer->Put(pat_ts, TS_SIZE); -#else - int ts_id; - unsigned int crc, i, len; - uint8_t *tmp, tspat_buf[TS_SIZE]; - static uint8_t ccounter = 0; - ccounter = (ccounter + 1) % 16; - memset(tspat_buf, 0xff, TS_SIZE); - ts_id = Channel->Tid(); // Get transport stream id of the channel - tspat_buf[0] = TS_SYNC_BYTE; // Transport packet header sunchronization byte (1000011 = 0x47h) - tspat_buf[1] = 0x40; // Set payload unit start indicator bit - tspat_buf[2] = 0x0; // PID - tspat_buf[3] = 0x10 | ccounter; // Set payload flag, Continuity counter - tspat_buf[4] = 0x0; // SI pointer field - tspat_buf[5] = 0x0; // PAT table id - tspat_buf[6] = 0xb0; // Section syntax indicator bit and reserved bits set - tspat_buf[7] = 12 + 1; // Section length (12 bit): PAT_TABLE_LEN + 1 - tspat_buf[8] = (ts_id >> 8); // Transport stream ID (bits 8-15) - tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7) - tspat_buf[10] = 0xc0 | ((pat.getVersionNumber() << 1) & 0x3e) | - pat.getCurrentNextIndicator();// Version number, Current next indicator - tspat_buf[11] = 0x0; // Section number - tspat_buf[12] = 0x0; // Last section number - tspat_buf[13] = (pmtSid >> 8); // Program number (bits 8-15) - tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7) - tspat_buf[15] = 0xe0 | (pmtPid >> 8); // Network ID (bits 8-12) - tspat_buf[16] = (pmtPid & 0xff); // Network ID (bits 0-7) - crc = 0xffffffff; - len = 12; // PAT_TABLE_LEN - tmp = &tspat_buf[4 + 1]; // TS_HDR_LEN + 1 - while (len--) { - crc ^= *tmp++ << 24; - for (i = 0; i < 8; i++) - crc = (crc << 1) ^ ((crc & 0x80000000) ? 0x04c11db7 : 0); // CRC32POLY - } - tspat_buf[17] = crc >> 24 & 0xff; // Checksum - tspat_buf[18] = crc >> 16 & 0xff; // Checksum - tspat_buf[19] = crc >> 8 & 0xff; // Checksum - tspat_buf[20] = crc & 0xff; // Checksum - m_Streamer->Put(tspat_buf, TS_SIZE); -#endif - } else - isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length); + // repack PAT to TS frame and send to client + int ts_id; + unsigned int crc, i, len; + uint8_t *tmp; + static uint8_t ccounter = 0; + ccounter = (ccounter + 1) % 16; + ts_id = Channel->Tid(); // Get transport stream id of the channel + tspat_buf[3] = 0x10 | ccounter; // Set payload flag, Continuity counter + tspat_buf[8] = (ts_id >> 8); // Transport stream ID (bits 8-15) + tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7) + tspat_buf[10] = 0xc0 | ((pat.getVersionNumber() << 1) & 0x3e) | + pat.getCurrentNextIndicator();// Version number, Current next indicator + tspat_buf[13] = (pmtSid >> 8); // Program number (bits 8-15) + tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7) + tspat_buf[15] = 0xe0 | (pmtPid >> 8); // Network ID (bits 8-12) + tspat_buf[16] = (pmtPid & 0xff); // Network ID (bits 0-7) + crc = 0xffffffff; + len = 12; // PAT_TABLE_LEN + tmp = &tspat_buf[4 + 1]; // TS_HDR_LEN + 1 + while (len--) { + crc ^= *tmp++ << 24; + for (i = 0; i < 8; i++) + crc = (crc << 1) ^ ((crc & 0x80000000) ? 0x04c11db7 : 0); // CRC32POLY + } + tspat_buf[17] = crc >> 24 & 0xff; // Checksum + tspat_buf[18] = crc >> 16 & 0xff; // Checksum + tspat_buf[19] = crc >> 8 & 0xff; // Checksum + tspat_buf[20] = crc & 0xff; // Checksum + int written = siBuffer.PutTS(tspat_buf, TS_SIZE); + if (written != TS_SIZE) + siBuffer.ReportOverflow(TS_SIZE - written); if (pmtPid != prevPmtPid) { m_Streamer->SetPids(pmtPid); Add(pmtPid, 0x02); @@ -292,7 +303,7 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i if (pmtVersion != -1) { if (pmtVersion != pmt.getVersionNumber()) { Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids\n"); - Del(pmtPid, 0x02); + cFilter::Del(pmtPid, 0x02); pmtPid = 0; // this triggers PAT scan } return; @@ -329,9 +340,7 @@ cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Paramet m_Device(NULL), m_Receiver(NULL), m_PatFilter(NULL), -#if APIVERSNUM < 10703 m_PESRemux(NULL), -#endif m_ESRemux(NULL), m_PSRemux(NULL), m_ExtRemux(NULL) @@ -347,9 +356,7 @@ cStreamdevLiveStreamer::~cStreamdevLiveStreamer() DELETENULL(m_PatFilter); } DELETENULL(m_Receiver); -#if APIVERSNUM < 10703 delete m_PESRemux; -#endif delete m_ESRemux; delete m_PSRemux; delete m_ExtRemux; @@ -463,12 +470,10 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str return SetPids(pid); } -#if APIVERSNUM < 10703 case stPES: - m_PESRemux = new cRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), - m_Channel->Spids(), false); + m_PESRemux = new cTS2PESRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), + m_Channel->Spids()); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); -#endif case stPS: m_PSRemux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), @@ -505,13 +510,22 @@ int cStreamdevLiveStreamer::Put(const uchar *Data, int Count) { switch (m_StreamType) { case stTS: + // insert si data + if (m_PatFilter) { + int got; + uchar *si = m_PatFilter->Get(got); + if (si) { + int count = cStreamdevStreamer::Put(si, got); + if (count) + m_PatFilter->Del(count); + } + } + // fall through case stTSPIDS: return cStreamdevStreamer::Put(Data, Count); -#if APIVERSNUM < 10703 case stPES: return m_PESRemux->Put(Data, Count); -#endif case stES: return m_ESRemux->Put(Data, Count); @@ -534,10 +548,8 @@ uchar *cStreamdevLiveStreamer::Get(int &Count) case stTSPIDS: return cStreamdevStreamer::Get(Count); -#if APIVERSNUM < 10703 case stPES: return m_PESRemux->Get(Count); -#endif case stES: return m_ESRemux->Get(Count); @@ -561,11 +573,9 @@ void cStreamdevLiveStreamer::Del(int Count) cStreamdevStreamer::Del(Count); break; -#if APIVERSNUM < 10703 case stPES: m_PESRemux->Del(Count); break; -#endif case stES: m_ESRemux->Del(Count); diff --git a/server/livestreamer.h b/server/livestreamer.h index 5c4ae8f..cf64559 100644 --- a/server/livestreamer.h +++ b/server/livestreamer.h @@ -7,12 +7,12 @@ #include "server/streamer.h" #include "common.h" -class cTS2PSRemux; -class cTS2ESRemux; -class cExternRemux; -#if APIVERSNUM < 10703 -class cRemux; -#endif +namespace Streamdev { + class cTS2PSRemux; + class cTS2ESRemux; + class cExternRemux; + class cTS2PESRemux; +} class cStreamdevPatFilter; class cStreamdevLiveReceiver; @@ -29,12 +29,10 @@ private: cDevice *m_Device; cStreamdevLiveReceiver *m_Receiver; cStreamdevPatFilter *m_PatFilter; -#if APIVERSNUM < 10703 - cRemux *m_PESRemux; -#endif - cTS2ESRemux *m_ESRemux; - cTS2PSRemux *m_PSRemux; - cExternRemux *m_ExtRemux; + Streamdev::cTS2PESRemux *m_PESRemux; + Streamdev::cTS2ESRemux *m_ESRemux; + Streamdev::cTS2PSRemux *m_PSRemux; + Streamdev::cExternRemux *m_ExtRemux; void StartReceiver(void); bool HasPid(int Pid); diff --git a/server/menuHTTP.c b/server/menuHTTP.c index 41b1f10..8d3e404 100644 --- a/server/menuHTTP.c +++ b/server/menuHTTP.c @@ -201,10 +201,8 @@ std::string cHtmlChannelList::StreamTypeMenu() (std::string) "[TS] "); typeMenu += (streamType == stPS ? (std::string) "[PS] " : (std::string) "[PS] "); -#if APIVERSNUM < 10703 typeMenu += (streamType == stPES ? (std::string) "[PES] " : (std::string) "[PES] "); -#endif typeMenu += (streamType == stES ? (std::string) "[ES] " : (std::string) "[ES] "); typeMenu += (streamType == stExtern ? (std::string) "[Extern] " : @@ -343,10 +341,8 @@ std::string cHtmlChannelList::ItemText() switch (streamType) { case stTS: suffix = (std::string) ".ts"; break; case stPS: suffix = (std::string) ".vob"; break; -#if APIVERSNUM < 10703 // for Network Media Tank case stPES: suffix = (std::string) ".vdr"; break; -#endif default: suffix = ""; } line += (std::string) "
  • Number()) + "\">"; diff --git a/server/streamer.c b/server/streamer.c index 9795cc6..42e7efa 100644 --- a/server/streamer.c +++ b/server/streamer.c @@ -1,5 +1,5 @@ /* - * $Id: streamer.c,v 1.18 2009/02/13 10:39:22 schmirl Exp $ + * $Id: streamer.c,v 1.19 2009/06/19 06:32:45 schmirl Exp $ */ #include @@ -14,6 +14,13 @@ #include "tools/select.h" #include "common.h" +// --- cStreamdevBuffer ------------------------------------------------------- + +cStreamdevBuffer::cStreamdevBuffer(int Size, int Margin, bool Statistics, const char *Description): + cRingBufferLinear(Size, Margin, Statistics, Description) +{ +} + // --- cStreamdevWriter ------------------------------------------------------- cStreamdevWriter::cStreamdevWriter(cTBSocket *Socket, @@ -95,14 +102,13 @@ void cStreamdevWriter::Action(void) cStreamdevStreamer::cStreamdevStreamer(const char *Name): cThread(Name), - m_Running(false), m_Writer(NULL), - m_RingBuffer(new cRingBufferLinear(STREAMERBUFSIZE, TS_SIZE * 2, + m_RingBuffer(new cStreamdevBuffer(STREAMERBUFSIZE, TS_SIZE * 2, true, "streamdev-streamer")), - m_SendBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)) + m_SendBuffer(new cStreamdevBuffer(WRITERBUFSIZE, TS_SIZE * 2)) { m_RingBuffer->SetTimeouts(0, 100); - m_SendBuffer->SetTimeouts(0, 100); + m_SendBuffer->SetTimeouts(100, 100); } cStreamdevStreamer::~cStreamdevStreamer() @@ -116,7 +122,6 @@ void cStreamdevStreamer::Start(cTBSocket *Socket) { Dprintf("start streamer\n"); m_Writer = new cStreamdevWriter(Socket, this); - m_Running = true; Attach(); } @@ -135,9 +140,8 @@ void cStreamdevStreamer::Stop(void) Dprintf("stopping streamer\n"); Cancel(3); } - if (m_Running) { + if (m_Writer) { Detach(); - m_Running = false; DELETENULL(m_Writer); } } @@ -152,8 +156,6 @@ void cStreamdevStreamer::Action(void) int count = Put(block, got); if (count) m_RingBuffer->Del(count); - else - cCondWait::SleepMs(100); } } } diff --git a/server/streamer.h b/server/streamer.h index 20323b7..6561bc2 100644 --- a/server/streamer.h +++ b/server/streamer.h @@ -1,5 +1,5 @@ /* - * $Id: streamer.h,v 1.10 2009/02/13 10:39:22 schmirl Exp $ + * $Id: streamer.h,v 1.11 2009/06/19 06:32:45 schmirl Exp $ */ #ifndef VDR_STREAMDEV_STREAMER_H @@ -16,8 +16,34 @@ class cStreamdevStreamer; #define TS_SIZE 188 #endif -#define STREAMERBUFSIZE MEGABYTE(4) -#define WRITERBUFSIZE KILOBYTE(256) +#define STREAMERBUFSIZE (20000 * TS_SIZE) +#define WRITERBUFSIZE (5000 * TS_SIZE) + +// --- cStreamdevBuffer ------------------------------------------------------- + +class cStreamdevBuffer: public cRingBufferLinear { +public: + // make public + void WaitForPut(void) { cRingBuffer::WaitForPut(); } + // Always write complete TS packets + // (assumes Count is a multiple of TS_SIZE) + int PutTS(const uchar *Data, int Count); + cStreamdevBuffer(int Size, int Margin = 0, bool Statistics = false, const char *Description = NULL); +}; + +inline int cStreamdevBuffer::PutTS(const uchar *Data, int Count) +{ + int free = Free(); + if (free < Count) + Count = free; + + Count -= Count % TS_SIZE; + if (Count) + Count = Put(Data, Count); + else + WaitForPut(); + return Count; +} // --- cStreamdevWriter ------------------------------------------------------- @@ -38,15 +64,14 @@ public: class cStreamdevStreamer: public cThread { private: - bool m_Running; cStreamdevWriter *m_Writer; - cRingBufferLinear *m_RingBuffer; - cRingBufferLinear *m_SendBuffer; + cStreamdevBuffer *m_RingBuffer; + cStreamdevBuffer *m_SendBuffer; protected: virtual void Action(void); - bool IsRunning(void) const { return m_Running; } + bool IsRunning(void) const { return m_Writer; } public: cStreamdevStreamer(const char *Name); @@ -57,10 +82,10 @@ public: bool Abort(void); void Activate(bool On); - int Receive(uchar *Data, int Length) { return m_RingBuffer->Put(Data, Length); } + int Receive(uchar *Data, int Length) { return m_RingBuffer->PutTS(Data, Length); } void ReportOverflow(int Bytes) { m_RingBuffer->ReportOverflow(Bytes); } - virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->Put(Data, Count); } + virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->PutTS(Data, Count); } virtual uchar *Get(int &Count) { return m_SendBuffer->Get(Count); } virtual void Del(int Count) { m_SendBuffer->Del(Count); } diff --git a/streamdev-server.c b/streamdev-server.c index 6b4ff6f..3593d9f 100644 --- a/streamdev-server.c +++ b/streamdev-server.c @@ -3,12 +3,11 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: streamdev-server.c,v 1.11 2008/10/14 11:05:47 schmirl Exp $ + * $Id: streamdev-server.c,v 1.12 2009/06/19 06:32:38 schmirl Exp $ */ #include #include -#include "remux/extern.h" #include "streamdev-server.h" #include "server/setup.h" #include "server/server.h"