summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrank Schmirler <vdr@schmirler.de>2010-12-02 09:44:53 +0100
committerFrank Schmirler <vdr@schmirler.de>2010-12-02 09:44:53 +0100
commit008ea7f151dd84b314062a272ad61448f51df605 (patch)
treef239ede406d0c0bb1bf5e9d2b08a37027c14e635
parent7254a6752808830d5fc133e5362da6c47f3f84ff (diff)
downloadvdr-plugin-streamdev-008ea7f151dd84b314062a272ad61448f51df605.tar.gz
vdr-plugin-streamdev-008ea7f151dd84b314062a272ad61448f51df605.tar.bz2
Snapshot 2009-07-01
-rw-r--r--CONTRIBUTORS9
-rw-r--r--HISTORY17
-rw-r--r--Makefile6
-rw-r--r--client/device.h5
-rw-r--r--common.c6
-rw-r--r--common.h5
-rw-r--r--libdvbmpeg/transform.h2
-rw-r--r--remux/extern.c5
-rw-r--r--remux/extern.h4
-rw-r--r--remux/ts2es.c14
-rw-r--r--remux/ts2es.h9
-rw-r--r--remux/ts2pes.c2017
-rw-r--r--remux/ts2pes.h56
-rw-r--r--remux/ts2ps.c13
-rw-r--r--remux/ts2ps.h9
-rw-r--r--remux/tsremux.c51
-rw-r--r--remux/tsremux.h30
-rw-r--r--server/connectionHTTP.c6
-rw-r--r--server/connectionVTP.c840
-rw-r--r--server/connectionVTP.h21
-rw-r--r--server/livestreamer.c226
-rw-r--r--server/livestreamer.h17
-rw-r--r--server/menuHTTP.c4
-rw-r--r--server/recplayer.c288
-rw-r--r--server/recplayer.h63
-rw-r--r--server/streamer.c22
-rw-r--r--server/streamer.h43
-rw-r--r--streamdev-server.c3
28 files changed, 3462 insertions, 329 deletions
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index d7f8dbf..6b2c092 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
@@ -30,6 +34,7 @@ Rolf Ahrenberg
for replacing private members by cThread::Running()/Active()
for improving externremux script termination
for fixing PAT repacker version field
+ for correcting LIMIKUUTIO patch detection
Rantanen Teemu
for providing vdr-incompletesections.diff
@@ -74,6 +79,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
@@ -111,3 +117,6 @@ Joachim König-Baltes
Artem Makhutov
for suggesting and heavy testing IGMP based multicast streaming
+
+Alwin Esch
+ for adding XBMC support by extending VTP capabilities
diff --git a/HISTORY b/HISTORY
index 4f8aef1..730b1e3 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,6 +1,23 @@
VDR Plugin 'streamdev' Revision History
---------------------------------------
+- added XBMC support by extending VTP capabilities (thanks to Alwin Esch)
+- now there's a common baseclass for all remuxers, make use of it
+- added cDevice::NumProvidedSystems() which was introduced in VDR 1.7.0
+- 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..2b6e959 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.19 2009/07/01 10:46:15 schmirl Exp $
# The official name of this plugin.
# This name will be used in the '-P...' option of VDR to load the plugin.
@@ -61,8 +61,8 @@ SERVEROBJS = $(PLUGIN)-server.o \
server/componentVTP.o server/componentHTTP.o server/componentIGMP.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
+ server/suspend.o server/setup.o server/menuHTTP.o server/recplayer.o \
+ remux/tsremux.o remux/ts2pes.o remux/ts2ps.o remux/ts2es.o remux/extern.o
ifdef DEBUG
DEFINES += -DDEBUG
diff --git a/client/device.h b/client/device.h
index 8263fa5..e96b05f 100644
--- a/client/device.h
+++ b/client/device.h
@@ -1,5 +1,5 @@
/*
- * $Id: device.h,v 1.8 2008/10/02 07:14:47 schmirl Exp $
+ * $Id: device.h,v 1.9 2009/06/23 10:26:54 schmirl Exp $
*/
#ifndef VDR_STREAMDEV_DEVICE_H
@@ -54,6 +54,9 @@ public:
virtual bool ProvidesTransponder(const cChannel *Channel) const;
virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1,
bool *NeedsDetachReceivers = NULL) const;
+#if APIVERSNUM >= 10700
+ virtual int NumProvidedSystems(void) const { return 1; }
+#endif
virtual bool IsTunedToTransponder(const cChannel *Channel);
static bool Init(void);
diff --git a/common.c b/common.c
index c38c689..1d65138 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 <vdr/channels.h>
@@ -10,13 +10,11 @@
using namespace std;
-const char *VERSION = "0.5.0-pre-20090611";
+const char *VERSION = "0.5.0-pre-20090701";
const char *StreamTypes[st_Count] = {
"TS",
-#if APIVERSNUM < 10703
"PES",
-#endif
"PS",
"ES",
"Extern",
diff --git a/common.h b/common.h
index e920c79..f7b894a 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.14 2009/07/01 10:46:16 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,
@@ -74,6 +72,7 @@ enum eSocketId {
siLive,
siReplay,
siLiveFilter,
+ siDataRespond,
si_Count
};
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 <signal.h>
#include <unistd.h>
+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 <vdr/ringbuffer.h>
#include <string>
+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 <vdr/device.h>
// 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 <vdr/ringbuffer.h>
+#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..eeb56d5
--- /dev/null
+++ b/remux/ts2pes.c
@@ -0,0 +1,2017 @@
+/*
+ * 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 <rnissl@gmx.de>,
+ * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de.
+ *
+ * $Id: ts2pes.c,v 1.2 2009/06/30 06:04:33 schmirl Exp $
+ */
+
+#include "remux/ts2pes.h"
+#include <stdlib.h>
+#include <vdr/channels.h>
+#include <vdr/shutdown.h>
+
+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 <chk1> <chk2> <frameSize>
+ 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 <netinet/in.h>
+
+//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)
+{
+ // Remove any previously skipped data from the result buffer:
+
+ if (resultSkipped > 0) {
+ resultBuffer->Del(resultSkipped);
+ resultSkipped = 0;
+ }
+
+ // Check for frame borders:
+
+ 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) {
+ resultSkipped = i; // will drop everything before this position
+ cTSRemux::SetBrokenLink(data + i, l);
+ synced = true;
+ }
+ }
+ else if (Count)
+ return resultData;
+ }
+ }
+ 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) {
+ resultSkipped = i; // will drop everything before this position
+ synced = true;
+ }
+ else if (Count)
+ return resultData;
+ }
+ }
+ 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..61ac857
--- /dev/null
+++ b/remux/ts2pes.h
@@ -0,0 +1,56 @@
+/*
+ * 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.3 2009/06/30 06:04:33 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;
+ 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);
+ ///< Gets all currently available data from the remuxer.
+ ///< \return Count contains the number of bytes the result points to, and
+ 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 <vdr/channels.h>
#include <vdr/device.h>
+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 <vdr/remux.h>
-#include <vdr/ringbuffer.h>
+#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..dbcb9ff 100644
--- a/remux/tsremux.h
+++ b/remux/tsremux.h
@@ -4,34 +4,26 @@
#include "libdvbmpeg/transform.h"
#include <vdr/remux.h>
-#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);*/
+ virtual int Put(const uchar *Data, int Count) = 0;
+ virtual uchar *Get(int &Count) = 0;
+ virtual void Del(int Count) = 0;
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 <ctype.h>
@@ -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..579cff2 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.21 2009/07/01 10:46:16 schmirl Exp $
*/
#include "server/connectionVTP.h"
@@ -8,6 +8,8 @@
#include "setup.h"
#include <vdr/tools.h>
+#include <vdr/videodir.h>
+#include <vdr/menu.h>
#include <tools/select.h>
#include <string.h>
#include <ctype.h>
@@ -28,13 +30,20 @@
563: Recording not available (currently?)
*/
+enum eDumpModeStreamdev { dmsdAll, dmsdPresent, dmsdFollowing, dmsdAtTime, dmsdFromToTime };
+
// --- cLSTEHandler -----------------------------------------------------------
class cLSTEHandler
{
private:
+#ifdef USE_PARENTALRATING
+ enum eStates { Channel, Event, Title, Subtitle, Description, Vps, Content,
+ EndEvent, EndChannel, EndEPG };
+#else
enum eStates { Channel, Event, Title, Subtitle, Description, Vps,
EndEvent, EndChannel, EndEPG };
+#endif /* PARENTALRATING */
cConnectionVTP *m_Client;
cSchedulesLock *m_SchedulesLock;
const cSchedules *m_Schedules;
@@ -44,6 +53,7 @@ private:
char *m_Error;
eStates m_State;
bool m_Traverse;
+ time_t m_ToTime;
public:
cLSTEHandler(cConnectionVTP *Client, const char *Option);
~cLSTEHandler();
@@ -59,10 +69,12 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
m_Errno(0),
m_Error(NULL),
m_State(Channel),
- m_Traverse(false)
+ m_Traverse(false),
+ m_ToTime(0)
{
- eDumpMode dumpmode = dmAll;
+ eDumpModeStreamdev dumpmode = dmsdAll;
time_t attime = 0;
+ time_t fromtime = 0;
if (m_Schedules != NULL && *Option) {
char buf[strlen(Option) + 1];
@@ -70,13 +82,13 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
const char *delim = " \t";
char *strtok_next;
char *p = strtok_r(buf, delim, &strtok_next);
- while (p && dumpmode == dmAll) {
+ while (p && dumpmode == dmsdAll) {
if (strcasecmp(p, "NOW") == 0)
- dumpmode = dmPresent;
+ dumpmode = dmsdPresent;
else if (strcasecmp(p, "NEXT") == 0)
- dumpmode = dmFollowing;
+ dumpmode = dmsdFollowing;
else if (strcasecmp(p, "AT") == 0) {
- dumpmode = dmAtTime;
+ dumpmode = dmsdAtTime;
if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
if (isnumber(p))
attime = strtol(p, NULL, 10);
@@ -90,6 +102,39 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
m_Error = strdup("Missing time");
break;
}
+ }
+ else if (strcasecmp(p, "FROM") == 0) {
+ dumpmode = dmsdFromToTime;
+ if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
+ if (isnumber(p))
+ fromtime = strtol(p, NULL, 10);
+ else {
+ m_Errno = 501;
+ m_Error = strdup("Invalid time");
+ break;
+ }
+ if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
+ if (strcasecmp(p, "TO") == 0) {
+ if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) {
+ if (isnumber(p))
+ m_ToTime = strtol(p, NULL, 10);
+ else {
+ m_Errno = 501;
+ m_Error = strdup("Invalid time");
+ break;
+ }
+ } else {
+ m_Errno = 501;
+ m_Error = strdup("Missing time");
+ break;
+ }
+ }
+ }
+ } else {
+ m_Errno = 501;
+ m_Error = strdup("Missing time");
+ break;
+ }
} else if (!m_Schedule) {
cChannel* Channel = NULL;
if (isnumber(p))
@@ -129,16 +174,29 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option):
if (m_Schedule != NULL && m_Schedule->Events() != NULL) {
switch (dumpmode) {
- case dmAll: m_Event = m_Schedule->Events()->First();
- m_Traverse = true;
- break;
- case dmPresent: m_Event = m_Schedule->GetPresentEvent();
- break;
- case dmFollowing: m_Event = m_Schedule->GetFollowingEvent();
- break;
- case dmAtTime: m_Event = m_Schedule->GetEventAround(attime);
- break;
-
+ case dmsdAll: m_Event = m_Schedule->Events()->First();
+ m_Traverse = true;
+ break;
+ case dmsdPresent: m_Event = m_Schedule->GetPresentEvent();
+ break;
+ case dmsdFollowing: m_Event = m_Schedule->GetFollowingEvent();
+ break;
+ case dmsdAtTime: m_Event = m_Schedule->GetEventAround(attime);
+ break;
+ case dmsdFromToTime:
+ if (m_Schedule->Events()->Count() <= 1) {
+ m_Event = m_Schedule->Events()->First();
+ break;
+ }
+ if (fromtime < m_Schedule->Events()->First()->StartTime()) {
+ fromtime = m_Schedule->Events()->First()->StartTime();
+ }
+ if (m_ToTime > m_Schedule->Events()->Last()->EndTime()) {
+ m_ToTime = m_Schedule->Events()->Last()->EndTime();
+ }
+ m_Event = m_Schedule->GetEventAround(fromtime);
+ m_Traverse = true;
+ break;
}
}
}
@@ -227,7 +285,11 @@ bool cLSTEHandler::Next(bool &Last)
break;
case Vps:
+#ifdef USE_PARENTALRATING
+ m_State = Content;
+#else
m_State = EndEvent;
+#endif /* PARENTALRATING */
if (m_Event->Vps())
#ifdef __FreeBSD__
return m_Client->Respond(-215, "V %d", m_Event->Vps());
@@ -238,9 +300,26 @@ bool cLSTEHandler::Next(bool &Last)
return Next(Last);
break;
+#ifdef USE_PARENTALRATING
+ case Content:
+ m_State = EndEvent;
+ if (!isempty(m_Event->GetContentsString())) {
+ char *copy = strdup(m_Event->GetContentsString());
+ cString cpy(copy, true);
+ strreplace(copy, '\n', '|');
+ return m_Client->Respond(-215, "G %i %i %s", m_Event->Contents() & 0xF0, m_Event->Contents() & 0x0F, copy);
+ } else
+ return Next(Last);
+ break;
+#endif
+
case EndEvent:
- if (m_Traverse)
+ if (m_Traverse) {
m_Event = m_Schedule->Events()->Next(m_Event);
+ if ((m_Event != NULL) && (m_ToTime != 0) && (m_Event->StartTime() > m_ToTime)) {
+ m_Event = NULL;
+ }
+ }
else
m_Event = NULL;
@@ -377,7 +456,7 @@ bool cLSTCHandler::Next(bool &Last)
}
}
- if (i < Channels.MaxNumber())
+ if (i < Channels.MaxNumber() + 1)
Last = false;
}
@@ -468,6 +547,181 @@ bool cLSTTHandler::Next(bool &Last)
return result;
}
+// --- cLSTRHandler -----------------------------------------------------------
+
+class cLSTRHandler
+{
+private:
+ enum eStates { Recording, Event, Title, Subtitle, Description, Components, Vps,
+ EndRecording };
+ cConnectionVTP *m_Client;
+ cRecording *m_Recording;
+ const cEvent *m_Event;
+ int m_Index;
+ int m_Errno;
+ char *m_Error;
+ bool m_Traverse;
+ bool m_Info;
+ eStates m_State;
+ int m_CurrentComponent;
+public:
+ cLSTRHandler(cConnectionVTP *Client, const char *Option);
+ ~cLSTRHandler();
+ bool Next(bool &Last);
+};
+
+cLSTRHandler::cLSTRHandler(cConnectionVTP *Client, const char *Option):
+ m_Client(Client),
+ m_Recording(NULL),
+ m_Event(NULL),
+ m_Index(0),
+ m_Errno(0),
+ m_Error(NULL),
+ m_Traverse(false),
+ m_Info(false),
+ m_State(Recording),
+ m_CurrentComponent(0)
+{
+ if (*Option) {
+ if (isnumber(Option)) {
+ m_Recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
+#if defined(USE_STREAMDEVEXT) || APIVERSNUM >= 10705
+ m_Event = m_Recording->Info()->GetEvent();
+#endif
+ m_Info = true;
+ if (m_Recording == NULL) {
+ m_Errno = 501;
+ asprintf(&m_Error, "Recording \"%s\" not found", Option);
+ }
+ }
+ else {
+ m_Errno = 501;
+ asprintf(&m_Error, "Error in Recording number \"%s\"", Option);
+ }
+ }
+ else if (Recordings.Count()) {
+ m_Traverse = true;
+ m_Index = 0;
+ m_Recording = Recordings.Get(m_Index);
+ if (m_Recording == NULL) {
+ m_Errno = 501;
+ asprintf(&m_Error, "Recording \"%d\" not found", m_Index + 1);
+ }
+ }
+ else {
+ m_Errno = 550;
+ m_Error = strdup("No recordings available");
+ }
+}
+
+cLSTRHandler::~cLSTRHandler()
+{
+ if (m_Error != NULL)
+ free(m_Error);
+}
+
+bool cLSTRHandler::Next(bool &Last)
+{
+ if (m_Error != NULL) {
+ Last = true;
+ cString str(m_Error, true);
+ m_Error = NULL;
+ return m_Client->Respond(m_Errno, *str);
+ }
+
+ if (m_Info) {
+ Last = false;
+ switch (m_State) {
+ case Recording:
+ if (m_Recording != NULL) {
+ m_State = Event;
+ return m_Client->Respond(-215, "C %s%s%s",
+ *m_Recording->Info()->ChannelID().ToString(),
+ m_Recording->Info()->ChannelName() ? " " : "",
+ m_Recording->Info()->ChannelName() ? m_Recording->Info()->ChannelName() : "");
+ }
+ else {
+ m_State = EndRecording;
+ return Next(Last);
+ }
+ break;
+
+ case Event:
+ m_State = Title;
+ if (m_Event != NULL) {
+ return m_Client->Respond(-215, "E %u %ld %d %X %X", (unsigned int) m_Event->EventID(),
+ m_Event->StartTime(), m_Event->Duration(),
+ m_Event->TableID(), m_Event->Version());
+ }
+ return Next(Last);
+
+ case Title:
+ m_State = Subtitle;
+ return m_Client->Respond(-215, "T %s", m_Recording->Info()->Title());
+
+ case Subtitle:
+ m_State = Description;
+ if (!isempty(m_Recording->Info()->ShortText())) {
+ return m_Client->Respond(-215, "S %s", m_Recording->Info()->ShortText());
+ }
+ return Next(Last);
+
+ case Description:
+ m_State = Components;
+ if (!isempty(m_Recording->Info()->Description())) {
+ m_State = Components;
+ char *copy = strdup(m_Recording->Info()->Description());
+ cString cpy(copy, true);
+ strreplace(copy, '\n', '|');
+ return m_Client->Respond(-215, "D %s", copy);
+ }
+ return Next(Last);
+
+ case Components:
+ if (m_Recording->Info()->Components()) {
+ if (m_CurrentComponent < m_Recording->Info()->Components()->NumComponents()) {
+ tComponent *p = m_Recording->Info()->Components()->Component(m_CurrentComponent);
+ m_CurrentComponent++;
+ if (!Setup.UseDolbyDigital && p->stream == 0x02 && p->type == 0x05)
+ return Next(Last);
+
+ return m_Client->Respond(-215, "X %s", *p->ToString());
+ }
+ }
+ m_State = Vps;
+ return Next(Last);
+
+ case Vps:
+ m_State = EndRecording;
+ if (m_Event != NULL) {
+ if (m_Event->Vps()) {
+ return m_Client->Respond(-215, "V %ld", m_Event->Vps());
+ }
+ }
+ return Next(Last);
+
+ case EndRecording:
+ Last = true;
+ return m_Client->Respond(215, "End of recording information");
+ }
+ }
+ else {
+ bool result;
+ Last = !m_Traverse || m_Index >= Recordings.Count() - 1;
+ result = m_Client->Respond(Last ? 250 : -250, "%d %s", m_Recording->Index() + 1, m_Recording->Title(' ', true));
+
+ if (m_Traverse && !Last) {
+ m_Recording = Recordings.Get(++m_Index);
+ if (m_Recording == NULL) {
+ m_Errno = 501;
+ asprintf(&m_Error, "Recording \"%d\" not found", m_Index + 1);
+ }
+ }
+ return result;
+ }
+ return false;
+}
+
// --- cConnectionVTP ---------------------------------------------------------
cConnectionVTP::cConnectionVTP(void):
@@ -476,12 +730,16 @@ cConnectionVTP::cConnectionVTP(void):
m_LiveStreamer(NULL),
m_FilterSocket(NULL),
m_FilterStreamer(NULL),
+ m_RecSocket(NULL),
+ m_DataSocket(NULL),
m_LastCommand(NULL),
m_StreamType(stTSPIDS),
m_FiltersSupport(false),
+ m_RecPlayer(NULL),
m_LSTEHandler(NULL),
m_LSTCHandler(NULL),
- m_LSTTHandler(NULL)
+ m_LSTTHandler(NULL),
+ m_LSTRHandler(NULL)
{
}
@@ -491,11 +749,15 @@ cConnectionVTP::~cConnectionVTP()
free(m_LastCommand);
delete m_LiveStreamer;
delete m_LiveSocket;
+ delete m_RecSocket;
delete m_FilterStreamer;
delete m_FilterSocket;
+ delete m_DataSocket;
delete m_LSTTHandler;
delete m_LSTCHandler;
delete m_LSTEHandler;
+ delete m_LSTRHandler;
+ delete m_RecPlayer;
}
inline bool cConnectionVTP::Abort(void) const
@@ -548,7 +810,7 @@ bool cConnectionVTP::Command(char *Cmd)
}
if (strcasecmp(Cmd, "LSTE") == 0) return CmdLSTE(param);
- //else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param);
+ else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param);
else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(param);
else if (strcasecmp(Cmd, "LSTC") == 0) return CmdLSTC(param);
@@ -561,7 +823,9 @@ bool cConnectionVTP::Command(char *Cmd)
if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param);
else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(param);
else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(param);
+ else if (strcasecmp(Cmd, "READ") == 0) return CmdREAD(param);
else if (strcasecmp(Cmd, "TUNE") == 0) return CmdTUNE(param);
+ else if (strcasecmp(Cmd, "PLAY") == 0) return CmdPLAY(param);
else if (strcasecmp(Cmd, "ADDP") == 0) return CmdADDP(param);
else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(param);
else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(param);
@@ -570,10 +834,17 @@ bool cConnectionVTP::Command(char *Cmd)
else if (strcasecmp(Cmd, "QUIT") == 0) return CmdQUIT();
else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP();
// Commands adopted from SVDRP
- //else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param);
+ else if (strcasecmp(Cmd, "STAT") == 0) return CmdSTAT(param);
else if (strcasecmp(Cmd, "MODT") == 0) return CmdMODT(param);
else if (strcasecmp(Cmd, "NEWT") == 0) return CmdNEWT(param);
else if (strcasecmp(Cmd, "DELT") == 0) return CmdDELT(param);
+ else if (strcasecmp(Cmd, "NEXT") == 0) return CmdNEXT(param);
+ else if (strcasecmp(Cmd, "NEWC") == 0) return CmdNEWC(param);
+ else if (strcasecmp(Cmd, "MODC") == 0) return CmdMODC(param);
+ else if (strcasecmp(Cmd, "MOVC") == 0) return CmdMOVC(param);
+ else if (strcasecmp(Cmd, "DELC") == 0) return CmdDELC(param);
+ else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param);
+ else if (strcasecmp(Cmd, "RENR") == 0) return CmdRENR(param);
else
return Respond(500, "Unknown Command \"%s\"", Cmd);
}
@@ -595,12 +866,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;
@@ -648,7 +917,7 @@ bool cConnectionVTP::CmdPORT(char *Opts)
if (ep == Opts || !isspace(*ep))
return Respond(500, "Use: PORT Id Destination");
- if (id != siLive && id != siLiveFilter)
+ if (id >= si_Count)
return Respond(501, "Wrong connection id %d", id);
Opts = skipspace(ep);
@@ -676,7 +945,8 @@ bool cConnectionVTP::CmdPORT(char *Opts)
isyslog("Streamdev: Setting data connection to %s:%d", dataip, dataport);
- if (id == siLiveFilter) {
+ switch (id) {
+ case siLiveFilter:
m_FiltersSupport = true;
if(m_FilterStreamer)
m_FilterStreamer->Stop();
@@ -696,26 +966,91 @@ bool cConnectionVTP::CmdPORT(char *Opts)
m_FilterStreamer->Activate(true);
return Respond(220, "Port command ok, data connection opened");
- }
+ break;
- if(m_LiveSocket && m_LiveStreamer)
- m_LiveStreamer->Stop();
- delete m_LiveSocket;
+ case siLive:
+ if(m_LiveSocket && m_LiveStreamer)
+ m_LiveStreamer->Stop();
+ delete m_LiveSocket;
- m_LiveSocket = new cTBSocket(SOCK_STREAM);
- if (!m_LiveSocket->Connect(dataip, dataport)) {
- esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
- dataip, dataport, strerror(errno));
- DELETENULL(m_LiveSocket);
- return Respond(551, "Couldn't open data connection");
- }
+ m_LiveSocket = new cTBSocket(SOCK_STREAM);
+ if (!m_LiveSocket->Connect(dataip, dataport)) {
+ esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
+ dataip, dataport, strerror(errno));
+ DELETENULL(m_LiveSocket);
+ return Respond(551, "Couldn't open data connection");
+ }
- if (!m_LiveSocket->SetDSCP())
- LOG_ERROR_STR("unable to set DSCP sockopt");
- if (m_LiveStreamer)
- m_LiveStreamer->Start(m_LiveSocket);
+ if (!m_LiveSocket->SetDSCP())
+ LOG_ERROR_STR("unable to set DSCP sockopt");
+ if (m_LiveStreamer)
+ m_LiveStreamer->Start(m_LiveSocket);
- return Respond(220, "Port command ok, data connection opened");
+ return Respond(220, "Port command ok, data connection opened");
+ break;
+
+ case siReplay:
+ delete m_RecSocket;
+
+ m_RecSocket = new cTBSocket(SOCK_STREAM);
+ if (!m_RecSocket->Connect(dataip, dataport)) {
+ esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
+ dataip, dataport, strerror(errno));
+ DELETENULL(m_RecSocket);
+ return Respond(551, "Couldn't open data connection");
+ }
+
+ if (!m_RecSocket->SetDSCP())
+ LOG_ERROR_STR("unable to set DSCP sockopt");
+
+ return Respond(220, "Port command ok, data connection opened");
+ break;
+
+ case siDataRespond:
+ delete m_DataSocket;
+
+ m_DataSocket = new cTBSocket(SOCK_STREAM);
+ if (!m_DataSocket->Connect(dataip, dataport)) {
+ esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s",
+ dataip, dataport, strerror(errno));
+ DELETENULL(m_DataSocket);
+ return Respond(551, "Couldn't open data connection");
+ }
+ return Respond(220, "Port command ok, data connection opened");
+ break;
+
+ default:
+ return Respond(501, "No handler for id %u", id);
+ }
+}
+
+bool cConnectionVTP::CmdREAD(char *Opts)
+{
+ if (*Opts) {
+ char *tail;
+ uint64_t position = strtoll(Opts, &tail, 10);
+ if (tail && tail != Opts) {
+ tail = skipspace(tail);
+ if (tail && tail != Opts) {
+ int size = strtol(tail, NULL, 10);
+ uint8_t* data = (uint8_t*)malloc(size+4);
+ unsigned long count_readed = m_RecPlayer->getBlock(data, position, size);
+ unsigned long count_written = m_RecSocket->SysWrite(data, count_readed);
+
+ free(data);
+ return Respond(220, "%lu Bytes submitted", count_written);
+ }
+ else {
+ return Respond(501, "Missing position");
+ }
+ }
+ else {
+ return Respond(501, "Missing size");
+ }
+ }
+ else {
+ return Respond(501, "Missing position");
+ }
}
bool cConnectionVTP::CmdTUNE(char *Opts)
@@ -749,6 +1084,32 @@ bool cConnectionVTP::CmdTUNE(char *Opts)
return Respond(220, "Channel tuned");
}
+bool cConnectionVTP::CmdPLAY(char *Opts)
+{
+ Recordings.Update(true);
+ if (*Opts) {
+ if (isnumber(Opts)) {
+ cRecording *recording = Recordings.Get(strtol(Opts, NULL, 10) - 1);
+ if (recording) {
+ if (m_RecPlayer) {
+ delete m_RecPlayer;
+ }
+ m_RecPlayer = new RecPlayer(recording);
+ return Respond(220, "%llu (Bytes), %u (Frames)", (long long unsigned int) m_RecPlayer->getLengthBytes(), (unsigned int) m_RecPlayer->getLengthFrames());
+ }
+ else {
+ return Respond(550, "Recording \"%s\" not found", Opts);
+ }
+ }
+ else {
+ return Respond(500, "Use: PLAY record");
+ }
+ }
+ else {
+ return Respond(500, "Use: PLAY record");
+ }
+}
+
bool cConnectionVTP::CmdADDP(char *Opts)
{
int pid;
@@ -844,6 +1205,13 @@ bool cConnectionVTP::CmdABRT(char *Opts)
DELETENULL(m_FilterStreamer);
DELETENULL(m_FilterSocket);
break;
+ case siReplay:
+ DELETENULL(m_RecPlayer);
+ DELETENULL(m_RecSocket);
+ break;
+ case siDataRespond:
+ DELETENULL(m_DataSocket);
+ break;
default:
return Respond(501, "Wrong connection id %d", id);
break;
@@ -881,7 +1249,8 @@ bool cConnectionVTP::CmdLSTX(cHandler *&Handler, char *Option)
Handler = new cHandler(this, Option);
}
- bool last, result = false;
+ bool last = false;
+ bool result = false;
if (Handler != NULL)
result = Handler->Next(last);
else
@@ -907,11 +1276,66 @@ bool cConnectionVTP::CmdLSTT(char *Option)
return CmdLSTX(m_LSTTHandler, Option);
}
+bool cConnectionVTP::CmdLSTR(char *Option)
+{
+ return CmdLSTX(m_LSTRHandler, Option);
+}
+
// Functions adopted from SVDRP
#define INIT_WRAPPER() bool _res
#define Reply(c,m...) _res = Respond(c,m)
#define EXIT_WRAPPER() return _res
+bool cConnectionVTP::CmdSTAT(const char *Option)
+{
+ INIT_WRAPPER();
+ if (*Option) {
+ if (strcasecmp(Option, "DISK") == 0) {
+ int FreeMB, UsedMB;
+ int Percent = VideoDiskSpace(&FreeMB, &UsedMB);
+ Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
+ }
+ else if (strcasecmp(Option, "NAME") == 0) {
+ Reply(250, "vdr - The Video Disk Recorder with Streamdev-Server");
+ }
+ else if (strcasecmp(Option, "VERSION") == 0) {
+ Reply(250, "VDR: %s | Streamdev: %s", VDRVERSION, VERSION);
+ }
+ else if (strcasecmp(Option, "RECORDS") == 0) {
+ bool recordings = Recordings.Load();
+ Recordings.Sort();
+ if (recordings) {
+ cRecording *recording = Recordings.Last();
+ Reply(250, "%d", recording->Index() + 1);
+ }
+ else {
+ Reply(250, "0");
+ }
+ }
+ else if (strcasecmp(Option, "CHANNELS") == 0) {
+ Reply(250, "%d", Channels.MaxNumber());
+ }
+ else if (strcasecmp(Option, "TIMERS") == 0) {
+ Reply(250, "%d", Timers.Count());
+ }
+ else if (strcasecmp(Option, "CHARSET") == 0) {
+ Reply(250, "%s", cCharSetConv::SystemCharacterTable());
+ }
+ else if (strcasecmp(Option, "TIME") == 0) {
+ time_t timeNow = time(NULL);
+ struct tm* timeStruct = localtime(&timeNow);
+ int timeOffset = timeStruct->tm_gmtoff;
+
+ Reply(250, "%lu %i", (unsigned long) timeNow, timeOffset);
+ }
+ else
+ Reply(501, "Invalid Option \"%s\"", Option);
+ }
+ else
+ Reply(501, "No option given");
+ EXIT_WRAPPER();
+}
+
bool cConnectionVTP::CmdMODT(const char *Option)
{
INIT_WRAPPER();
@@ -992,61 +1416,293 @@ bool cConnectionVTP::CmdDELT(const char *Option)
EXIT_WRAPPER();
}
-/*bool cConnectionVTP::CmdLSTR(char *Option) {
+bool cConnectionVTP::CmdNEXT(const char *Option)
+{
INIT_WRAPPER();
- bool recordings = Recordings.Load();
- Recordings.Sort();
- if (*Option) {
- if (isnumber(Option)) {
- cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
- if (recording) {
- if (recording->Summary()) {
- char *summary = strdup(recording->Summary());
- Reply(250, "%s", strreplace(summary,'\n','|'));
- free(summary);
- }
- else
- Reply(550, "No summary availabe");
- }
- else
- Reply(550, "Recording \"%s\" not found", Option);
- }
- else
- Reply(501, "Error in recording number \"%s\"", Option);
- }
- else if (recordings) {
- cRecording *recording = Recordings.First();
- while (recording) {
- Reply(recording == Recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true));
- recording = Recordings.Next(recording);
- }
- }
- else
- Reply(550, "No recordings available");
+ cTimer *t = Timers.GetNextActiveTimer();
+ if (t) {
+ time_t Start = t->StartTime();
+ int Number = t->Index() + 1;
+ if (!*Option)
+ Reply(250, "%d %s", Number, *TimeToString(Start));
+ else if (strcasecmp(Option, "ABS") == 0)
+ Reply(250, "%d %ld", Number, Start);
+ else if (strcasecmp(Option, "REL") == 0)
+ Reply(250, "%d %ld", Number, Start - time(NULL));
+ else
+ Reply(501, "Unknown option: \"%s\"", Option);
+ }
+ else
+ Reply(550, "No active timers");
EXIT_WRAPPER();
}
-bool cConnectionVTP::CmdDELR(char *Option) {
+bool cConnectionVTP::CmdNEWC(const char *Option)
+{
INIT_WRAPPER();
- if (*Option) {
- if (isnumber(Option)) {
- cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
- if (recording) {
- if (recording->Delete())
- Reply(250, "Recording \"%s\" deleted", Option);
- else
- Reply(554, "Error while deleting recording!");
- }
- else
- Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)");
- }
- else
- Reply(501, "Error in recording number \"%s\"", Option);
- }
- else
- Reply(501, "Missing recording number");
+ if (*Option) {
+ cChannel ch;
+ if (ch.Parse(Option)) {
+ if (Channels.HasUniqueChannelID(&ch)) {
+ cChannel *channel = new cChannel;
+ *channel = ch;
+ Channels.Add(channel);
+ Channels.ReNumber();
+ Channels.SetModified(true);
+ isyslog("new channel %d %s", channel->Number(), *channel->ToText());
+ Reply(250, "%d %s", channel->Number(), *channel->ToText());
+ }
+ else {
+ Reply(501, "Channel settings are not unique");
+ }
+ }
+ else {
+ Reply(501, "Error in channel settings");
+ }
+ }
+ else {
+ Reply(501, "Missing channel settings");
+ }
EXIT_WRAPPER();
-}*/
+}
+
+bool cConnectionVTP::CmdMODC(const char *Option)
+{
+ INIT_WRAPPER();
+ if (*Option) {
+ char *tail;
+ int n = strtol(Option, &tail, 10);
+ if (tail && tail != Option) {
+ tail = skipspace(tail);
+ if (!Channels.BeingEdited()) {
+ cChannel *channel = Channels.GetByNumber(n);
+ if (channel) {
+ cChannel ch;
+ if (ch.Parse(tail)) {
+ if (Channels.HasUniqueChannelID(&ch, channel)) {
+ *channel = ch;
+ Channels.ReNumber();
+ Channels.SetModified(true);
+ isyslog("modifed channel %d %s", channel->Number(), *channel->ToText());
+ Reply(250, "%d %s", channel->Number(), *channel->ToText());
+ }
+ else {
+ Reply(501, "Channel settings are not unique");
+ }
+ }
+ else {
+ Reply(501, "Error in channel settings");
+ }
+ }
+ else {
+ Reply(501, "Channel \"%d\" not defined", n);
+ }
+ }
+ else {
+ Reply(550, "Channels are being edited - try again later");
+ }
+ }
+ else {
+ Reply(501, "Error in channel number");
+ }
+ }
+ else {
+ Reply(501, "Missing channel settings");
+ }
+ EXIT_WRAPPER();
+}
+
+bool cConnectionVTP::CmdMOVC(const char *Option)
+{
+ INIT_WRAPPER();
+ if (*Option) {
+ if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
+ char *tail;
+ int From = strtol(Option, &tail, 10);
+ if (tail && tail != Option) {
+ tail = skipspace(tail);
+ if (tail && tail != Option) {
+ int To = strtol(tail, NULL, 10);
+ int CurrentChannelNr = cDevice::CurrentChannel();
+ cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
+ cChannel *FromChannel = Channels.GetByNumber(From);
+ if (FromChannel) {
+ cChannel *ToChannel = Channels.GetByNumber(To);
+ if (ToChannel) {
+ int FromNumber = FromChannel->Number();
+ int ToNumber = ToChannel->Number();
+ if (FromNumber != ToNumber) {
+ Channels.Move(FromChannel, ToChannel);
+ Channels.ReNumber();
+ Channels.SetModified(true);
+ if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
+ if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) {
+ Channels.SwitchTo(CurrentChannel->Number());
+ }
+ else {
+ cDevice::SetCurrentChannel(CurrentChannel);
+ }
+ }
+ isyslog("channel %d moved to %d", FromNumber, ToNumber);
+ Reply(250,"Channel \"%d\" moved to \"%d\"", From, To);
+ }
+ else {
+ Reply(501, "Can't move channel to same postion");
+ }
+ }
+ else {
+ Reply(501, "Channel \"%d\" not defined", To);
+ }
+ }
+ else {
+ Reply(501, "Channel \"%d\" not defined", From);
+ }
+ }
+ else {
+ Reply(501, "Error in channel number");
+ }
+ }
+ else {
+ Reply(501, "Error in channel number");
+ }
+ }
+ else {
+ Reply(550, "Channels or timers are being edited - try again later");
+ }
+ }
+ else {
+ Reply(501, "Missing channel number");
+ }
+ EXIT_WRAPPER();
+}
+
+bool cConnectionVTP::CmdDELC(const char *Option)
+{
+ INIT_WRAPPER();
+ if (*Option) {
+ if (isnumber(Option)) {
+ if (!Channels.BeingEdited()) {
+ cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10));
+ if (channel) {
+ for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) {
+ if (timer->Channel() == channel) {
+ Reply(550, "Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1);
+ return false;
+ }
+ }
+ int CurrentChannelNr = cDevice::CurrentChannel();
+ cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr);
+ if (CurrentChannel && channel == CurrentChannel) {
+ int n = Channels.GetNextNormal(CurrentChannel->Index());
+ if (n < 0)
+ n = Channels.GetPrevNormal(CurrentChannel->Index());
+ CurrentChannel = Channels.Get(n);
+ CurrentChannelNr = 0; // triggers channel switch below
+ }
+ Channels.Del(channel);
+ Channels.ReNumber();
+ Channels.SetModified(true);
+ isyslog("channel %s deleted", Option);
+ if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) {
+ if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring())
+ Channels.SwitchTo(CurrentChannel->Number());
+ else
+ cDevice::SetCurrentChannel(CurrentChannel);
+ }
+ Reply(250, "Channel \"%s\" deleted", Option);
+ }
+ else
+ Reply(501, "Channel \"%s\" not defined", Option);
+ }
+ else
+ Reply(550, "Channels are being edited - try again later");
+ }
+ else
+ Reply(501, "Error in channel number \"%s\"", Option);
+ }
+ else {
+ Reply(501, "Missing channel number");
+ }
+ EXIT_WRAPPER();
+}
+
+bool cConnectionVTP::CmdDELR(const char *Option)
+{
+ INIT_WRAPPER();
+ if (*Option) {
+ if (isnumber(Option)) {
+ cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);
+ if (recording) {
+ cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName());
+ if (!rc) {
+ if (recording->Delete()) {
+ Reply(250, "Recording \"%s\" deleted", Option);
+ ::Recordings.DelByName(recording->FileName());
+ }
+ else
+ Reply(554, "Error while deleting recording!");
+ }
+ else
+ Reply(550, "Recording \"%s\" is in use by timer %d", Option, rc->Timer()->Index() + 1);
+ }
+ else
+ Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)");
+ }
+ else
+ Reply(501, "Error in recording number \"%s\"", Option);
+ }
+ else
+ Reply(501, "Missing recording number");
+ EXIT_WRAPPER();
+}
+
+bool cConnectionVTP::CmdRENR(const char *Option)
+{
+ INIT_WRAPPER();
+#if defined(LIEMIKUUTIO)
+ bool recordings = Recordings.Update(true);
+ if (recordings) {
+ if (*Option) {
+ char *tail;
+ int n = strtol(Option, &tail, 10);
+ cRecording *recording = Recordings.Get(n - 1);
+ if (recording && tail && tail != Option) {
+#if APIVERSNUM < 10704
+ int priority = recording->priority;
+ int lifetime = recording->lifetime;
+#endif
+ char *oldName = strdup(recording->Name());
+ tail = skipspace(tail);
+#if APIVERSNUM < 10704
+ if (recording->Rename(tail, &priority, &lifetime)) {
+#else
+ if (recording->Rename(tail)) {
+#endif
+ Reply(250, "Renamed \"%s\" to \"%s\"", oldName, recording->Name());
+ Recordings.ChangeState();
+ Recordings.TouchUpdate();
+ }
+ else {
+ Reply(501, "Renaming \"%s\" to \"%s\" failed", oldName, tail);
+ }
+ free(oldName);
+ }
+ else {
+ Reply(501, "Recording not found or wrong syntax");
+ }
+ }
+ else {
+ Reply(501, "Missing Input settings");
+ }
+ }
+ else {
+ Reply(550, "No recordings available");
+ }
+#else
+ Reply(501, "Rename not supported, please use LIEMIEXT");
+#endif /* LIEMIKUUTIO */
+ EXIT_WRAPPER();
+}
bool cConnectionVTP::Respond(int Code, const char *Message, ...)
{
diff --git a/server/connectionVTP.h b/server/connectionVTP.h
index 452f3ae..b938fe6 100644
--- a/server/connectionVTP.h
+++ b/server/connectionVTP.h
@@ -2,6 +2,7 @@
#define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H
#include "server/connection.h"
+#include "server/recplayer.h"
class cTBSocket;
class cStreamdevLiveStreamer;
@@ -9,6 +10,7 @@ class cStreamdevFilterStreamer;
class cLSTEHandler;
class cLSTCHandler;
class cLSTTHandler;
+class cLSTRHandler;
class cConnectionVTP: public cServerConnection {
friend class cLSTEHandler;
@@ -21,16 +23,19 @@ private:
cStreamdevLiveStreamer *m_LiveStreamer;
cTBSocket *m_FilterSocket;
cStreamdevFilterStreamer *m_FilterStreamer;
+ cTBSocket *m_RecSocket;
+ cTBSocket *m_DataSocket;
char *m_LastCommand;
eStreamType m_StreamType;
bool m_FiltersSupport;
+ RecPlayer *m_RecPlayer;
// Members adopted for SVDRP
- cRecordings Recordings;
cLSTEHandler *m_LSTEHandler;
cLSTCHandler *m_LSTCHandler;
cLSTTHandler *m_LSTTHandler;
+ cLSTRHandler *m_LSTRHandler;
protected:
template<class cHandler>
@@ -51,7 +56,9 @@ public:
bool CmdCAPS(char *Opts);
bool CmdPROV(char *Opts);
bool CmdPORT(char *Opts);
+ bool CmdREAD(char *Opts);
bool CmdTUNE(char *Opts);
+ bool CmdPLAY(char *Opts);
bool CmdADDP(char *Opts);
bool CmdDELP(char *Opts);
bool CmdADDF(char *Opts);
@@ -64,14 +71,20 @@ public:
bool CmdLSTE(char *Opts);
bool CmdLSTC(char *Opts);
bool CmdLSTT(char *Opts);
+ bool CmdLSTR(char *Opts);
// Commands adopted from SVDRP
+ bool CmdSTAT(const char *Option);
bool CmdMODT(const char *Option);
bool CmdNEWT(const char *Option);
bool CmdDELT(const char *Option);
-
- //bool CmdLSTR(char *Opts);
- //bool CmdDELR(char *Opts);
+ bool CmdNEXT(const char *Option);
+ bool CmdNEWC(const char *Option);
+ bool CmdMODC(const char *Option);
+ bool CmdMOVC(const char *Option);
+ bool CmdDELC(const char *Option);
+ bool CmdDELR(const char *Option);
+ bool CmdRENR(const char *Option);
bool Respond(int Code, const char *Message, ...)
__attribute__ ((format (printf, 3, 4)));
diff --git a/server/livestreamer.c b/server/livestreamer.c
index 97dffd7..71a3565 100644
--- a/server/livestreamer.c
+++ b/server/livestreamer.c
@@ -4,6 +4,7 @@
#include <libsi/descriptor.h>
#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,12 +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)
+ m_Remux(NULL)
{
}
@@ -347,12 +353,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;
+ delete m_Remux;
}
bool cStreamdevLiveStreamer::HasPid(int Pid)
@@ -459,19 +460,17 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
int pid = ISRADIO(m_Channel) ? m_Channel->Apid(0) : m_Channel->Vpid();
if (Apid != 0)
pid = Apid;
- m_ESRemux = new cTS2ESRemux(pid);
+ m_Remux = new cTS2ESRemux(pid);
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_Remux = 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(),
+ m_Remux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
m_Channel->Spids());
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
@@ -490,7 +489,7 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
return true;
case stExtern:
- m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
+ m_Remux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(),
m_Channel->Spids(), m_Parameter);
return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids());
@@ -503,82 +502,39 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str
int cStreamdevLiveStreamer::Put(const uchar *Data, int Count)
{
- switch (m_StreamType) {
- case stTS:
- 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);
-
- case stPS:
- return m_PSRemux->Put(Data, Count);
-
- case stExtern:
- return m_ExtRemux->Put(Data, Count);
-
- default: // shouldn't happen???
- return 0;
+ // insert si data
+ if (m_PatFilter) {
+ int siCount;
+ uchar *siData = m_PatFilter->Get(siCount);
+ if (siData) {
+ if (m_Remux)
+ siCount = m_Remux->Put(siData, siCount);
+ else
+ siCount = cStreamdevStreamer::Put(siData, siCount);
+ if (siCount)
+ m_PatFilter->Del(siCount);
+ }
}
+ if (m_Remux)
+ return m_Remux->Put(Data, Count);
+ else
+ return cStreamdevStreamer::Put(Data, Count);
}
uchar *cStreamdevLiveStreamer::Get(int &Count)
{
- switch (m_StreamType) {
- case stTS:
- case stTSPIDS:
+ if (m_Remux)
+ return m_Remux->Get(Count);
+ else
return cStreamdevStreamer::Get(Count);
-
-#if APIVERSNUM < 10703
- case stPES:
- return m_PESRemux->Get(Count);
-#endif
-
- case stES:
- return m_ESRemux->Get(Count);
-
- case stPS:
- return m_PSRemux->Get(Count);
-
- case stExtern:
- return m_ExtRemux->Get(Count);
-
- default: // shouldn't happen???
- return 0;
- }
}
void cStreamdevLiveStreamer::Del(int Count)
{
- switch (m_StreamType) {
- case stTS:
- case stTSPIDS:
+ if (m_Remux)
+ m_Remux->Del(Count);
+ else
cStreamdevStreamer::Del(Count);
- break;
-
-#if APIVERSNUM < 10703
- case stPES:
- m_PESRemux->Del(Count);
- break;
-#endif
-
- case stES:
- m_ESRemux->Del(Count);
- break;
-
- case stPS:
- m_PSRemux->Del(Count);
- break;
-
- case stExtern:
- m_ExtRemux->Del(Count);
- break;
- }
}
void cStreamdevLiveStreamer::Attach(void)
diff --git a/server/livestreamer.h b/server/livestreamer.h
index 5c4ae8f..7f442ba 100644
--- a/server/livestreamer.h
+++ b/server/livestreamer.h
@@ -5,14 +5,12 @@
#include <vdr/receiver.h>
#include "server/streamer.h"
+#include "remux/tsremux.h"
#include "common.h"
-class cTS2PSRemux;
-class cTS2ESRemux;
-class cExternRemux;
-#if APIVERSNUM < 10703
-class cRemux;
-#endif
+namespace Streamdev {
+ class cTSRemux;
+}
class cStreamdevPatFilter;
class cStreamdevLiveReceiver;
@@ -29,12 +27,7 @@ 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::cTSRemux *m_Remux;
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) "[<a href=\"/TS/" + self + "\">TS</a>] ");
typeMenu += (streamType == stPS ? (std::string) "[PS] " :
(std::string) "[<a href=\"/PS/" + self + "\">PS</a>] ");
-#if APIVERSNUM < 10703
typeMenu += (streamType == stPES ? (std::string) "[PES] " :
(std::string) "[<a href=\"/PES/" + self + "\">PES</a>] ");
-#endif
typeMenu += (streamType == stES ? (std::string) "[ES] " :
(std::string) "[<a href=\"/ES/" + self + "\">ES</a>] ");
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) "<li value=\"" + (const char*) itoa(current->Number()) + "\">";
diff --git a/server/recplayer.c b/server/recplayer.c
new file mode 100644
index 0000000..f45d8c3
--- /dev/null
+++ b/server/recplayer.c
@@ -0,0 +1,288 @@
+/*
+ Copyright 2004-2005 Chris Tallon
+
+ This file is part of VOMP.
+ and adopted for streamdev to play recordings
+
+ VOMP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ VOMP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with VOMP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include "recplayer.h"
+
+#define _XOPEN_SOURCE 600
+#include <fcntl.h>
+
+RecPlayer::RecPlayer(cRecording* rec)
+{
+ file = NULL;
+ fileOpen = 0;
+ lastPosition = 0;
+ recording = rec;
+ for(int i = 1; i < 1000; i++) segments[i] = NULL;
+
+ // FIXME find out max file path / name lengths
+
+#if VDRVERSNUM >= 10703
+ indexFile = new cIndexFile(recording->FileName(), false, rec->IsPesRecording());
+#else
+ indexFile = new cIndexFile(recording->FileName(), false);
+#endif
+ if (!indexFile) esyslog("ERROR: Streamdev: Failed to create indexfile!");
+
+ scan();
+}
+
+void RecPlayer::scan()
+{
+ if (file) fclose(file);
+ totalLength = 0;
+ fileOpen = 0;
+ totalFrames = 0;
+
+ int i = 1;
+ while(segments[i++]) delete segments[i];
+
+ char fileName[2048];
+ for(i = 1; i < 1000; i++)
+ {
+
+#if APIVERSNUM < 10703
+ snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i);
+ //log->log("RecPlayer", Log::DEBUG, "FILENAME: %s", fileName);
+ file = fopen(fileName, "r");
+#else
+ snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), i);
+ file = fopen(fileName, "r");
+ if (!file) {
+ snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i);
+ file = fopen(fileName, "r");
+ }
+#endif
+ if (!file) break;
+
+ segments[i] = new Segment();
+ segments[i]->start = totalLength;
+ fseek(file, 0, SEEK_END);
+ totalLength += ftell(file);
+ totalFrames = indexFile->Last();
+ //log->log("RecPlayer", Log::DEBUG, "File %i found, totalLength now %llu, numFrames = %lu", i, totalLength, totalFrames);
+ segments[i]->end = totalLength;
+ fclose(file);
+ }
+
+ file = NULL;
+}
+
+RecPlayer::~RecPlayer()
+{
+ //log->log("RecPlayer", Log::DEBUG, "destructor");
+ int i = 1;
+ while(segments[i++]) delete segments[i];
+ if (file) fclose(file);
+}
+
+int RecPlayer::openFile(int index)
+{
+ if (file) fclose(file);
+
+ char fileName[2048];
+
+#if APIVERSNUM >= 10703
+ snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), index);
+ isyslog("openFile called for index %i string:%s", index, fileName);
+
+ file = fopen(fileName, "r");
+ if (file)
+ {
+ fileOpen = index;
+ return 1;
+ }
+#endif
+
+ snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), index);
+ isyslog("openFile called for index %i string:%s", index, fileName);
+ //log->log("RecPlayer", Log::DEBUG, "openFile called for index %i string:%s", index, fileName);
+
+ file = fopen(fileName, "r");
+ if (file)
+ {
+ fileOpen = index;
+ return 1;
+ }
+
+ //log->log("RecPlayer", Log::DEBUG, "file failed to open");
+ fileOpen = 0;
+ return 0;
+}
+
+uint64_t RecPlayer::getLengthBytes()
+{
+ return totalLength;
+}
+
+uint32_t RecPlayer::getLengthFrames()
+{
+ return totalFrames;
+}
+
+unsigned long RecPlayer::getBlock(unsigned char* buffer, uint64_t position, unsigned long amount)
+{
+ if ((amount > totalLength) || (amount > 500000))
+ {
+ //log->log("RecPlayer", Log::DEBUG, "Amount %lu requested and rejected", amount);
+ return 0;
+ }
+
+ if (position >= totalLength)
+ {
+ //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!");
+ return 0;
+ }
+
+ if ((position + amount) > totalLength)
+ {
+ //log->log("RecPlayer", Log::DEBUG, "Client asked for some data past the end of recording, adjusting amount");
+ amount = totalLength - position;
+ }
+
+ // work out what block position is in
+ int segmentNumber;
+ for(segmentNumber = 1; segmentNumber < 1000; segmentNumber++)
+ {
+ if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break;
+ // position is in this block
+ }
+
+ // we could be seeking around
+ if (segmentNumber != fileOpen)
+ {
+ if (!openFile(segmentNumber)) return 0;
+ }
+
+ uint64_t currentPosition = position;
+ uint32_t yetToGet = amount;
+ uint32_t got = 0;
+ uint32_t getFromThisSegment = 0;
+ uint32_t filePosition;
+
+ while(got < amount)
+ {
+ if (got)
+ {
+ // if(got) then we have already got some and we are back around
+ // advance the file pointer to the next file
+ if (!openFile(++segmentNumber)) return 0;
+ }
+
+ // is the request completely in this block?
+ if ((currentPosition + yetToGet) <= segments[segmentNumber]->end)
+ getFromThisSegment = yetToGet;
+ else
+ getFromThisSegment = segments[segmentNumber]->end - currentPosition;
+
+ filePosition = currentPosition - segments[segmentNumber]->start;
+ fseek(file, filePosition, SEEK_SET);
+ if (fread(&buffer[got], getFromThisSegment, 1, file) != 1) return 0; // umm, big problem.
+
+ // Tell linux not to bother keeping the data in the FS cache
+ posix_fadvise(file->_fileno, filePosition, getFromThisSegment, POSIX_FADV_DONTNEED);
+
+ got += getFromThisSegment;
+ currentPosition += getFromThisSegment;
+ yetToGet -= getFromThisSegment;
+ }
+
+ lastPosition = position;
+ return got;
+}
+
+uint64_t RecPlayer::getLastPosition()
+{
+ return lastPosition;
+}
+
+cRecording* RecPlayer::getCurrentRecording()
+{
+ return recording;
+}
+
+uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber)
+{
+ if (!indexFile) return 0;
+
+#if VDRVERSNUM >= 10703
+ uint16_t retFileNumber;
+ off_t retFileOffset;
+#else
+ uchar retFileNumber;
+ int retFileOffset;
+#endif
+
+ if (!indexFile->Get((int)frameNumber, &retFileNumber, &retFileOffset))
+ {
+ return 0;
+ }
+
+// log->log("RecPlayer", Log::DEBUG, "FN: %u FO: %i", retFileNumber, retFileOffset);
+ if (!segments[retFileNumber]) return 0;
+ uint64_t position = segments[retFileNumber]->start + retFileOffset;
+// log->log("RecPlayer", Log::DEBUG, "Pos: %llu", position);
+
+ return position;
+}
+
+uint32_t RecPlayer::frameNumberFromPosition(uint64_t position)
+{
+ if (!indexFile) return 0;
+
+ if (position >= totalLength)
+ {
+ //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!");
+ return 0;
+ }
+
+ uint8_t segmentNumber;
+ for(segmentNumber = 1; segmentNumber < 255; segmentNumber++)
+ {
+ if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break;
+ // position is in this block
+ }
+ uint32_t askposition = position - segments[segmentNumber]->start;
+ return indexFile->Get((int)segmentNumber, askposition);
+
+}
+
+
+bool RecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength)
+{
+ // 0 = backwards
+ // 1 = forwards
+
+ if (!indexFile) return false;
+
+ int iframeLength;
+ int indexReturnFrameNumber;
+
+ indexReturnFrameNumber = (uint32_t)indexFile->GetNextIFrame(frameNumber, (direction==1 ? true : false), NULL, NULL, &iframeLength);
+ //log->log("RecPlayer", Log::DEBUG, "GNIF input framenumber:%lu, direction=%lu, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength);
+
+ if (indexReturnFrameNumber == -1) return false;
+
+ *rfilePosition = positionFromFrameNumber(indexReturnFrameNumber);
+ *rframeNumber = (uint32_t)indexReturnFrameNumber;
+ *rframeLength = (uint32_t)iframeLength;
+
+ return true;
+}
diff --git a/server/recplayer.h b/server/recplayer.h
new file mode 100644
index 0000000..3da6c89
--- /dev/null
+++ b/server/recplayer.h
@@ -0,0 +1,63 @@
+/*
+ Copyright 2004-2005 Chris Tallon
+
+ This file is part of VOMP.
+
+ VOMP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ VOMP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with VOMP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#ifndef RECPLAYER_H
+#define RECPLAYER_H
+
+#include <stdio.h>
+#include <vdr/recording.h>
+
+#include "server/streamer.h"
+
+class Segment
+{
+ public:
+ uint64_t start;
+ uint64_t end;
+};
+
+class RecPlayer
+{
+ public:
+ RecPlayer(cRecording* rec);
+ ~RecPlayer();
+ uint64_t getLengthBytes();
+ uint32_t getLengthFrames();
+ unsigned long getBlock(unsigned char* buffer, uint64_t position, unsigned long amount);
+ int openFile(int index);
+ uint64_t getLastPosition();
+ cRecording* getCurrentRecording();
+ void scan();
+ uint64_t positionFromFrameNumber(uint32_t frameNumber);
+ uint32_t frameNumberFromPosition(uint64_t position);
+ bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength);
+
+ private:
+ cRecording* recording;
+ cIndexFile* indexFile;
+ FILE* file;
+ int fileOpen;
+ Segment* segments[1000];
+ uint64_t totalLength;
+ uint64_t lastPosition;
+ uint32_t totalFrames;
+};
+
+#endif
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 <vdr/ringbuffer.h>
@@ -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 <getopt.h>
#include <vdr/tools.h>
-#include "remux/extern.h"
#include "streamdev-server.h"
#include "server/setup.h"
#include "server/server.h"