Redesigned the ring buffer; prepared for remultiplexing

This commit is contained in:
Klaus Schmidinger 2001-03-31 08:42:27 +02:00
parent 191fb910bf
commit 5c68fc100c
8 changed files with 589 additions and 226 deletions

View File

@ -414,7 +414,7 @@ Video Disk Recorder Revision History
given number of seconds. This is mainly useful in combination with the new
'runvdr' script that restarts VDR in case is has exited.
2001-03-02: Version 0.72
2001-04-01: Version 0.72
- Fixed SVDRP commands LSTC and LSTT to make them return an error message if
no channels or timers are defined.
@ -427,3 +427,8 @@ Video Disk Recorder Revision History
- Fixed internationalization of some Main menu texts.
- Updated 'channels.conf' after the recent changes of Premiere World (thanks
to Axel Gruber).
- Redesigned the ring buffer to make it work with two separate threads for
input and output (also prepared for using a remultiplexer).
- Fixed setting system time from transponders.
- Fixed a segfault in the Schedule menu in case there is no EPG information.
- The 'runvdr' script now kills any leftover vdr threads before restarting it.

View File

@ -4,13 +4,14 @@
# See the main source file 'vdr.c' for copyright information and
# how to reach the author.
#
# $Id: Makefile 1.20 2001/02/24 15:52:58 kls Exp $
# $Id: Makefile 1.21 2001/03/18 16:47:00 kls Exp $
DVBDIR = ../DVB
INCLUDES = -I$(DVBDIR)/driver
OBJS = config.o dvbapi.o dvbosd.o eit.o font.o i18n.o interface.o menu.o osd.o\
recording.o remote.o svdrp.o thread.o tools.o vdr.o videodir.o
recording.o remote.o remux.o ringbuffer.o svdrp.o thread.o tools.o vdr.o\
videodir.o
OSDFONT = -adobe-helvetica-medium-r-normal--23-*-100-100-p-*-iso8859-1
FIXFONT = -adobe-courier-bold-r-normal--25-*-100-100-m-*-iso8859-1
@ -37,26 +38,28 @@ font: genfontfile fontfix.c fontosd.c
# Implicit rules:
%.o: %.c
g++ -g -O2 -Wall -m486 -c $(DEFINES) $(INCLUDES) $<
g++ -g -O2 -Wall -Woverloaded-virtual -m486 -c $(DEFINES) $(INCLUDES) $<
# Dependencies:
config.o : config.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h remote.h svdrp.h thread.h tools.h
dvbapi.o : dvbapi.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h recording.h remote.h svdrp.h thread.h tools.h videodir.h
dvbosd.o : dvbosd.c dvbosd.h font.h tools.h
eit.o : eit.c config.h dvbapi.h dvbosd.h eit.h font.h thread.h tools.h videodir.h
font.o : font.c font.h fontfix.c fontosd.c tools.h
i18n.o : i18n.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h thread.h tools.h
interface.o: interface.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h remote.h svdrp.h thread.h tools.h
menu.o : menu.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h menu.h osd.h recording.h remote.h svdrp.h thread.h tools.h
osd.o : osd.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h osd.h remote.h svdrp.h thread.h tools.h
recording.o: recording.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h recording.h remote.h svdrp.h thread.h tools.h videodir.h
remote.o : remote.c config.h dvbapi.h dvbosd.h eit.h font.h remote.h thread.h tools.h
svdrp.o : svdrp.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h remote.h svdrp.h thread.h tools.h
thread.o : thread.c thread.h tools.h
tools.o : tools.c tools.h
vdr.o : vdr.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h menu.h osd.h recording.h remote.h svdrp.h thread.h tools.h videodir.h
videodir.o : videodir.c tools.h videodir.h
config.o : config.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h remote.h svdrp.h thread.h tools.h
dvbapi.o : dvbapi.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h recording.h remote.h remux.h ringbuffer.h svdrp.h thread.h tools.h videodir.h
dvbosd.o : dvbosd.c dvbosd.h font.h tools.h
eit.o : eit.c config.h dvbapi.h dvbosd.h eit.h font.h thread.h tools.h videodir.h
font.o : font.c font.h fontfix.c fontosd.c tools.h
i18n.o : i18n.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h thread.h tools.h
interface.o : interface.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h remote.h svdrp.h thread.h tools.h
menu.o : menu.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h menu.h osd.h recording.h remote.h svdrp.h thread.h tools.h
osd.o : osd.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h osd.h remote.h svdrp.h thread.h tools.h
recording.o : recording.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h recording.h remote.h svdrp.h thread.h tools.h videodir.h
remote.o : remote.c config.h dvbapi.h dvbosd.h eit.h font.h remote.h thread.h tools.h
remux.o : remux.c remux.h tools.h
ringbuffer.o: ringbuffer.c ringbuffer.h thread.h tools.h
svdrp.o : svdrp.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h remote.h svdrp.h thread.h tools.h
thread.o : thread.c thread.h tools.h
tools.o : tools.c tools.h
vdr.o : vdr.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h menu.h osd.h recording.h remote.h svdrp.h thread.h tools.h videodir.h
videodir.o : videodir.c tools.h videodir.h
# The main program:

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: config.h 1.42 2001/02/24 13:19:39 kls Exp $
* $Id: config.h 1.43 2001/03/18 16:47:00 kls Exp $
*/
#ifndef __CONFIG_H
@ -19,7 +19,7 @@
#include "eit.h"
#include "tools.h"
#define VDRVERSION "0.71"
#define VDRVERSION "0.72"
#define MaxBuffer 10000

314
dvbapi.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: dvbapi.c 1.63 2001/03/14 18:39:53 kls Exp $
* $Id: dvbapi.c 1.64 2001/03/18 16:47:16 kls Exp $
*/
#include "dvbapi.h"
@ -22,6 +22,8 @@ extern "C" {
#include "config.h"
#include "interface.h"
#include "recording.h"
#include "remux.h"
#include "ringbuffer.h"
#include "tools.h"
#include "videodir.h"
@ -29,29 +31,9 @@ extern "C" {
#define VBIDEVICE "/dev/vbi"
// The size of the array used to buffer video data:
// (must be larger than MINVIDEODATA - see remux.h)
#define VIDEOBUFSIZE (1024*1024)
// The minimum amount of video data necessary to identify frames
// (must be smaller than VIDEOBUFSIZE!):
#define MINVIDEODATA (256*1024) // just a safe guess (max. size of any frame block, plus some safety)
// The maximum time the buffer is allowed to write data to disk when recording:
#define MAXRECORDWRITETIME 50 // ms
// Picture types:
#define NO_PICTURE 0
#define I_FRAME 1
#define P_FRAME 2
#define B_FRAME 3
// Start codes:
#define SC_PICTURE 0x00 // "picture header"
#define SC_SEQU 0xB3 // "sequence header"
#define SC_PHEAD 0xBA // "pack header"
#define SC_SHEAD 0xBB // "system header"
#define SC_AUDIO 0xC0
#define SC_VIDEO 0xE0
#define FRAMESPERSEC 25
// The maximum file size is limited by the range that can be covered
@ -333,7 +315,7 @@ int cIndexFile::Get(uchar FileNumber, int FileOffset)
return -1;
}
// --- cRingBuffer -----------------------------------------------------------
// --- cRingBuffer_ -----------------------------------------------------------
/* cRingBuffer reads data from an input file, stores it in a buffer and writes
it to an output file upon request. The Read() and Write() functions should
@ -344,7 +326,7 @@ int cIndexFile::Get(uchar FileNumber, int FileOffset)
will be made.
*/
class cRingBuffer {
class cRingBuffer_ {
private:
uchar *buffer;
int size, head, tail, freeLimit, availLimit;
@ -367,8 +349,8 @@ protected:
int FindStartCode(uchar Code, int Offset = 0);
int GetPacketLength(int Offset = 0);
public:
cRingBuffer(int *InFile, int *OutFile, int Size, int FreeLimit = 0, int AvailLimit = 0);
virtual ~cRingBuffer();
cRingBuffer_(int *InFile, int *OutFile, int Size, int FreeLimit = 0, int AvailLimit = 0);
virtual ~cRingBuffer_();
virtual int Read(int Max = -1);
virtual int Write(int Max = -1);
bool EndOfFile(void) { return eof; }
@ -377,7 +359,7 @@ public:
void Skip(int n);
};
cRingBuffer::cRingBuffer(int *InFile, int *OutFile, int Size, int FreeLimit, int AvailLimit)
cRingBuffer_::cRingBuffer_(int *InFile, int *OutFile, int Size, int FreeLimit, int AvailLimit)
{
inFile = InFile;
outFile = OutFile;
@ -393,13 +375,13 @@ cRingBuffer::cRingBuffer(int *InFile, int *OutFile, int Size, int FreeLimit, int
esyslog(LOG_ERR, "ERROR: can't allocate ring buffer (size=%d)", size);
}
cRingBuffer::~cRingBuffer()
cRingBuffer_::~cRingBuffer_()
{
dsyslog(LOG_INFO, "buffer stats: %d free, %d overflows, limit exceeded %d times", minFree, countOverflow, countLimit);
delete buffer;
}
int cRingBuffer::Byte(int Offset)
int cRingBuffer_::Byte(int Offset)
{
if (buffer && Offset < Available()) {
Offset += head;
@ -410,7 +392,7 @@ int cRingBuffer::Byte(int Offset)
return -1;
}
bool cRingBuffer::Set(int Offset, int Length, int Value)
bool cRingBuffer_::Set(int Offset, int Length, int Value)
{
if (buffer && Offset + Length <= Available() ) {
Offset += head;
@ -425,7 +407,7 @@ bool cRingBuffer::Set(int Offset, int Length, int Value)
return false;
}
void cRingBuffer::Skip(int n)
void cRingBuffer_::Skip(int n)
{
if (n > 0) {
if (head < tail) {
@ -443,7 +425,7 @@ void cRingBuffer::Skip(int n)
}
}
int cRingBuffer::Read(int Max)
int cRingBuffer_::Read(int Max)
{
if (buffer) {
eof = false;
@ -501,7 +483,7 @@ int cRingBuffer::Read(int Max)
return -1;
}
int cRingBuffer::Write(int Max)
int cRingBuffer_::Write(int Max)
{
if (buffer) {
int avail = Available();
@ -540,7 +522,7 @@ int cRingBuffer::Write(int Max)
return -1;
}
int cRingBuffer::FindStartCode(uchar Code, int Offset)
int cRingBuffer_::FindStartCode(uchar Code, int Offset)
{
// Searches for a start code (beginning at Offset) and returns the number
// of bytes from Offset to the start code.
@ -557,7 +539,7 @@ int cRingBuffer::FindStartCode(uchar Code, int Offset)
return -1;
}
int cRingBuffer::GetPacketLength(int Offset)
int cRingBuffer_::GetPacketLength(int Offset)
{
// Returns the entire length of the packet starting at offset.
return (Byte(Offset + 4) << 8) + Byte(Offset + 5) + 6;
@ -671,31 +653,29 @@ int cFileName::NextFile(void)
// --- cRecordBuffer ---------------------------------------------------------
class cRecordBuffer : public cRingBuffer, public cThread {
class cRecordBuffer : public cRingBuffer {
private:
cFileName fileName;
cIndexFile *index;
cRemux remux;
uchar pictureType;
int fileSize;
int videoDev;
int recordFile;
bool ok, synced, stop;
bool recording;
time_t lastDiskSpaceCheck;
bool RunningLowOnDiskSpace(void);
int ScanVideoPacket(int *PictureType, int Offset);
int Synchronize(void);
bool NextFile(void);
virtual int Write(int Max = -1);
bool WriteWithTimeout(void);
protected:
virtual void Action(void);
virtual void Input(void);
virtual void Output(void);
public:
cRecordBuffer(int *InFile, const char *FileName);
virtual ~cRecordBuffer();
};
cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName)
:cRingBuffer(InFile, &recordFile, VIDEOBUFSIZE, VIDEOBUFSIZE / 10, 0)
:cRingBuffer(VIDEOBUFSIZE)
,fileName(FileName, true)
{
index = NULL;
@ -703,7 +683,7 @@ cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName)
fileSize = 0;
videoDev = *InFile;
recordFile = fileName.Open();
ok = synced = stop = false;
recording = false;
lastDiskSpaceCheck = time(NULL);
if (!fileName.Name())
return;
@ -712,44 +692,15 @@ cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName)
if (!index)
esyslog(LOG_ERR, "ERROR: can't allocate index");
// let's continue without index, so we'll at least have the recording
ok = true;
Start();
}
cRecordBuffer::~cRecordBuffer()
{
stop = true;
Cancel(3);
Stop();
delete index;
}
void cRecordBuffer::Action(void)
{
dsyslog(LOG_INFO, "recording thread started (pid=%d)", getpid());
time_t t = time(NULL);
for (;;) {
usleep(1); // this keeps the CPU load low
LOCK_THREAD;
int r = Read();
if (r >= 0) {
if (r > 0)
t = time(NULL);
if (!WriteWithTimeout())
break;
}
if (r < 0 || (r == 0 && time(NULL) - t > 5)) {
esyslog(LOG_ERR, "ERROR: video data stream broken");
t = time(NULL);
}
}
SetPlayMode(videoDev, VID_PLAY_RESET);
dsyslog(LOG_INFO, "end recording thread");
}
bool cRecordBuffer::RunningLowOnDiskSpace(void)
{
if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
@ -763,88 +714,6 @@ bool cRecordBuffer::RunningLowOnDiskSpace(void)
return false;
}
int cRecordBuffer::ScanVideoPacket(int *PictureType, int Offset)
{
// Scans the video packet starting at Offset and returns its length.
// If the return value is -1 the packet was not completely in the buffer.
int Length = GetPacketLength(Offset);
if (Length <= Available()) {
int i = Offset + 8; // the minimum length of the video packet header
i += Byte(i) + 1; // possible additional header bytes
for (; i < Offset + Length; i++) {
if (Byte(i) == 0 && Byte(i + 1) == 0 && Byte(i + 2) == 1) {
switch (Byte(i + 3)) {
case SC_PICTURE: *PictureType = GetPictureType(i);
return Length;
}
}
}
*PictureType = NO_PICTURE;
return Length;
}
return -1;
}
int cRecordBuffer::Synchronize(void)
{
// Positions to the start of a data block (skipping everything up to
// an I-frame if not synced) and returns the block length.
pictureType = NO_PICTURE;
//XXX remove this once the buffer is handled with two separate threads:
if (!synced && Free() < 100000) {
dsyslog(LOG_INFO, "unable to synchronize, dropped %d bytes", Available());
Clear();
return 0;
}
for (int i = 0; Available() > MINVIDEODATA && i < MINVIDEODATA; i++) {
if (Byte(i) == 0 && Byte(i + 1) == 0 && Byte(i + 2) == 1) {
switch (Byte(i + 3)) {
case SC_VIDEO: {
int pt = NO_PICTURE;
int l = ScanVideoPacket(&pt, i);
if (l < 0)
return 0; // no useful data found, wait for more
if (pt != NO_PICTURE) {
if (pt < I_FRAME || B_FRAME < pt) {
esyslog(LOG_ERR, "ERROR: unknown picture type '%d'", pt);
}
else if (pictureType == NO_PICTURE) {
if (!synced) {
if (pt == I_FRAME) {
Skip(i);
synced = true;
}
else {
Skip(i + l);
i = 0;
break;
}
}
if (synced)
pictureType = pt;
}
else
return i;
}
else if (!synced) {
Skip(i + l);
i = 0;
break;
}
i += l - 1; // -1 to compensate for i++ in the loop!
}
break;
case SC_AUDIO: i += GetPacketLength(i) - 1; // -1 to compensate for i++ in the loop!
break;
}
}
}
return 0; // no useful data found, wait for more
}
bool cRecordBuffer::NextFile(void)
{
if (recordFile >= 0 && pictureType == I_FRAME) { // every file shall start with an I_FRAME
@ -856,56 +725,93 @@ bool cRecordBuffer::NextFile(void)
return recordFile >= 0;
}
int cRecordBuffer::Write(int Max)
void cRecordBuffer::Input(void)
{
// This function ignores the incoming 'Max'!
// It tries to write out exactly *one* frame block.
if (!ok)
return -1;
int n = Synchronize();
if (n) {
if (stop && pictureType == I_FRAME) {
ok = false;
return -1; // finish the recording before the next 'I' frame
}
if (NextFile()) {
if (index && pictureType != NO_PICTURE)
index->Write(pictureType, fileName.Number(), fileSize);
int written = 0;
for (;;) {
int w = cRingBuffer::Write(n);
if (w >= 0) {
fileSize += w;
written += w;
n -= w;
if (n == 0)
return written;
dsyslog(LOG_INFO, "input thread started (pid=%d)", getpid());
uchar b[MINVIDEODATA];
time_t t = time(NULL);
recording = true;
for (;;) {
int r = read(videoDev, b, sizeof(b));
if (r > 0) {
uchar *p = b;
while (r > 0) {
int w = Put(p, r);
p += w;
r -= w;
}
else
return w;
t = time(NULL);
}
else if (r < 0) {
if (errno != EAGAIN) {
LOG_ERROR;
break;
}
}
return -1;
}
return 0;
}
else if (time(NULL) - t > 5) {
esyslog(LOG_ERR, "ERROR: video data stream broken");
t = time(NULL);
}
cFile::FileReady(videoDev, 100);
if (!recording)
break;
}
SetPlayMode(videoDev, VID_PLAY_RESET);
dsyslog(LOG_INFO, "input thread ended (pid=%d)", getpid());
}
bool cRecordBuffer::WriteWithTimeout(void)
void cRecordBuffer::Output(void)
{
int t0 = time_ms();
do {
int w = Write();
if (w < 0)
return false;
if (w == 0)
break;
} while (time_ms() - t0 < MAXRECORDWRITETIME);
return true;
dsyslog(LOG_INFO, "output thread started (pid=%d)", getpid());
uchar b[MINVIDEODATA * 2];
int r = 0;
for (;;) {
usleep(1); // this keeps the CPU load low
r += Get(b + r, sizeof(b) - r);
if (r > 0) {
//XXX buffer full???
int Count = r, Result;
const uchar *p = remux.Process(b, Count, Result, pictureType);
if (p) {
if (!Busy() && pictureType == I_FRAME) // finish the recording before the next 'I' frame
break;
if (NextFile()) {
if (index && pictureType != NO_PICTURE)
index->Write(pictureType, fileName.Number(), fileSize);
while (Result > 0) {
int w = write(recordFile, p, Result);
if (w < 0) {
LOG_ERROR_STR(fileName.Name());
recording = false;
return;
}
p += w;
Result -= w;
fileSize += w;
}
}
else
break;
}
if (Count > 0) {
r -= Count;
memmove(b, b + Count, r);
}
if (!recording)
break;
}
}
recording = false;
dsyslog(LOG_INFO, "output thread ended (pid=%d)", getpid());
}
// --- cReplayBuffer ---------------------------------------------------------
class cReplayBuffer : public cRingBuffer, public cThread {
class cReplayBuffer : public cRingBuffer_, public cThread {
private:
enum eReplayCmd { rcNone, rcStill, rcPause, rcPlay, rcForward, rcBackward };
enum eReplayMode { rmStill, rmPlay, rmFastForward, rmFastRewind, rmSlowRewind };
@ -944,7 +850,7 @@ public:
};
cReplayBuffer::cReplayBuffer(int *OutFile, const char *FileName)
:cRingBuffer(&replayFile, OutFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10)
:cRingBuffer_(&replayFile, OutFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10)
,fileName(FileName, false)
{
index = NULL;
@ -1271,7 +1177,7 @@ int cReplayBuffer::Read(int Max = -1)
int readin = 0;
do {
// If Max is > 0 here we need to make sure we read in the entire block!
int r = cRingBuffer::Read(Max);
int r = cRingBuffer_::Read(Max);
if (r >= 0)
readin += r;
else
@ -1300,7 +1206,7 @@ int cReplayBuffer::Write(int Max)
if (Max) {
int w;
do {
w = cRingBuffer::Write(Max);
w = cRingBuffer_::Write(Max);
if (w >= 0) {
fileOffset += w;
Written += w;
@ -1348,7 +1254,7 @@ void cTransferBuffer::Action(void)
{
dsyslog(LOG_INFO, "data transfer thread started (pid=%d)", getpid());
cRingBuffer Buffer(&fromDevice, &toDevice, VIDEOBUFSIZE, 0, 0);
cRingBuffer_ Buffer(&fromDevice, &toDevice, VIDEOBUFSIZE, 0, 0);
active = true;
while (active && Buffer.Available() < 100000) { // need to give the read buffer a head start
Buffer.Read(); // initializes fromDevice for reading
@ -1364,7 +1270,7 @@ void cTransferBuffer::Action(void)
// --- cCuttingBuffer --------------------------------------------------------
class cCuttingBuffer : public cRingBuffer, public cThread {
class cCuttingBuffer : public cRingBuffer_, public cThread {
private:
bool active;
int fromFile, toFile;
@ -1379,7 +1285,7 @@ public:
};
cCuttingBuffer::cCuttingBuffer(const char *FromFileName, const char *ToFileName)
:cRingBuffer(&fromFile, &toFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10)
:cRingBuffer_(&fromFile, &toFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10)
{
active = false;
fromFile = toFile = -1;
@ -1438,7 +1344,7 @@ void cCuttingBuffer::Action(void)
CurrentFileNumber = FileNumber;
}
if (fromFile >= 0)
Length = cRingBuffer::Read(Length);
Length = cRingBuffer_::Read(Length);
else
break;
}
@ -1456,7 +1362,7 @@ void cCuttingBuffer::Action(void)
}
LastIFrame = 0;
}
cRingBuffer::Write(Length);
cRingBuffer_::Write(Length);
toIndex->Write(PictureType, toFileName->Number(), FileSize);
FileSize += Length;
if (!LastIFrame)

173
remux.c Normal file
View File

@ -0,0 +1,173 @@
/*
* remux.c: A streaming MPEG2 remultiplexer
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: remux.c 1.1 2001/03/31 08:42:17 kls Exp $
*/
/* The calling interface of the 'cRemux::Process()' function is defined
as follows:
'Data' points to a chunk of data that consists of 'Count' bytes.
The 'Process' function shall try to remultiplex as much of the
data as possible and return a pointer to the resulting buffer.
That buffer typically is different from the incoming 'Data',
but in the simplest case (when 'Process' does nothing) might
as well point to the original 'Data'. When returning, 'Count'
shall be set to the number of bytes that have been processed
(i.e. have been taken from 'Data'), while 'Result' indicates
how many bytes the returned buffer contains. 'PictureType' shall
be set to NO_PICTURE if the returned data does not start a new
picture, or one of I_FRAME, P_FRAME or B_FRAME if a new picture
starting point has been found. This also means that the returned
data buffer may contain at most one entire video frame, because
the next frame must be returned with its own value for 'PictureType'.
'Process' shall do it's best to keep the latency time as short
as possible in order to allow a quick start of VDR's "Transfer
mode" (displaying the signal of one DVB card on another card).
In order to do that, this function may decide to first pass
through the incoming data (almost) unprocessed, and make
actual processing kick in after a few seconds (if that is at
all possible for the algorithm). This may result in a non-
optimal stream at the beginning, which won't matter for normal
recordings but may make switching through encrypted channels
in "Transfer mode" faster.
In the resulting data stream, a new packet shall always be started
when a frame border is encountered. VDR needs this in order to
be able to detect and store the frame indexes, and to easily
display single frames in fast forward/back mode. The very first
data block returned shall be the starting point of an I_FRAME.
Everything before that shall be silently dropped.
If the incoming data is not enough to do remultiplexing, a value
of NULL shall be returned ('Result' has no meaning then). This
will tell the caller to wait for more data to be presented in
the next call. If NULL is returned and 'Count' is not 0, the
caller shall remove 'Count' bytes from the beginning of 'Data'
before the next call. This is the way 'Process' indicates that
it must skip that data.
Any data that is not used during this call will appear at the
beginning of the incoming 'Data' buffer at the next call, plus
any new data that has become available.
It is guaranteed that the caller will completely process any
returned data before the next call to 'Process'. That way, 'Process'
can dynamically allocate its return buffer and be sure the caller
doesn't keep any pointers into that buffer.
*/
#include "remux.h"
#include "tools.h"
#if defined(REMUX_NONE)
cRemux::cRemux(void)
{
synced = false;
}
cRemux::~cRemux()
{
}
int cRemux::GetPacketLength(const uchar *Data, int Count, int Offset)
{
// Returns the entire length of the packet starting at offset, or -1 in case of error.
return (Offset + 5 < Count) ? (Data[Offset + 4] << 8) + Data[Offset + 5] + 6 : -1;
}
int cRemux::ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType)
{
// Scans the video packet starting at Offset and returns its length.
// If the return value is -1 the packet was not completely in the buffer.
int Length = GetPacketLength(Data, Count, Offset);
if (Length > 0 && Offset + Length <= Count) {
int i = Offset + 8; // the minimum length of the video packet header
i += Data[i] + 1; // possible additional header bytes
for (; i < Offset + Length; 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;
}
}
}
PictureType = NO_PICTURE;
return Length;
}
return -1;
}
const uchar *cRemux::Process(const uchar *Data, int &Count, int &Result, uchar &PictureType)
{
int Skip = 0;
PictureType = NO_PICTURE;
if (Count >= MINVIDEODATA) {
for (int i = 0; i < Count; i++) {
if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) {
switch (Data[i + 3]) {
case SC_VIDEO:
{
uchar pt = NO_PICTURE;
int l = ScanVideoPacket(Data, Count, i, pt);
if (l < 0) {
if (Skip < Count)
Count = Skip;
return NULL; // no useful data found, wait for more
}
if (pt != NO_PICTURE) {
if (pt < I_FRAME || B_FRAME < pt) {
esyslog(LOG_ERR, "ERROR: unknown picture type '%d'", pt);
}
else if (PictureType == NO_PICTURE) {
if (!synced) {
if (pt == I_FRAME) {
Skip = i;
synced = true;
}
else {
i += l;
Skip = i;
break;
}
}
if (synced)
PictureType = pt;
}
else {
Count = i;
Result = i - Skip;
return Data + Skip;
}
}
else if (!synced) {
i += l;
Skip = i;
break;
}
i += l - 1; // -1 to compensate for i++ in the loop!
}
break;
case SC_AUDIO:
i += GetPacketLength(Data, Count, i) - 1; // -1 to compensate for i++ in the loop!
break;
}
}
}
}
if (Skip < Count)
Count = Skip;
return NULL; // no useful data found, wait for more
}
#elif defined(REMUX_TEST)
#endif

51
remux.h Normal file
View File

@ -0,0 +1,51 @@
/*
* remux.h: A streaming MPEG2 remultiplexer
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: remux.h 1.1 2001/03/31 08:42:27 kls Exp $
*/
#ifndef __REMUX_H
#define __REMUX_H
// There are various experiments with different types of remultiplexers
// going on at the moment. Select the remultiplexer here:
#define REMUX_NONE 1
//#define REMUX_TEST 1
// Picture types:
#define NO_PICTURE 0
#define I_FRAME 1
#define P_FRAME 2
#define B_FRAME 3
// Start codes:
#define SC_PICTURE 0x00 // "picture header"
#define SC_SEQU 0xB3 // "sequence header"
#define SC_PHEAD 0xBA // "pack header"
#define SC_SHEAD 0xBB // "system header"
#define SC_AUDIO 0xC0
#define SC_VIDEO 0xE0
// The minimum amount of video data necessary to identify frames:
#define MINVIDEODATA (256*1024) // just a safe guess (max. size of any frame block, plus some safety)
typedef unsigned char uchar;
class cRemux {
private:
#if defined(REMUX_NONE)
bool synced;
int GetPacketLength(const uchar *Data, int Count, int Offset);
int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType);
#elif defined(REMUX_TEST)
#endif
public:
cRemux(void);
~cRemux();
const uchar *Process(const uchar *Data, int &Count, int &Result, uchar &PictureType);
};
#endif // __REMUX_H

170
ringbuffer.c Normal file
View File

@ -0,0 +1,170 @@
/*
* ringbuffer.c: A threaded ring buffer
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* Parts of this file were inspired by the 'ringbuffy.c' from the
* LinuxDVB driver (see linuxtv.org).
*
* $Id: ringbuffer.c 1.1 2001/03/18 16:47:00 kls Exp $
*/
#include "ringbuffer.h"
#include "tools.h"
// --- cRingBufferInputThread -------------------------------------------------
class cRingBufferInputThread : public cThread {
private:
cRingBuffer *ringBuffer;
protected:
virtual void Action(void) { ringBuffer->Input(); }
public:
cRingBufferInputThread(cRingBuffer *RingBuffer) { ringBuffer = RingBuffer; }
};
// --- cRingBufferOutputThread ------------------------------------------------
class cRingBufferOutputThread : public cThread {
private:
cRingBuffer *ringBuffer;
protected:
virtual void Action(void) { ringBuffer->Output(); }
public:
cRingBufferOutputThread(cRingBuffer *RingBuffer) { ringBuffer = RingBuffer; }
};
// --- cRingBuffer ------------------------------------------------------------
cRingBuffer::cRingBuffer(int Size)
{
size = Size;
buffer = NULL;
inputThread = NULL;
outputThread = NULL;
maxFill = 0;
busy = false;
if (size > 1) { // 'size - 1' must not be 0!
buffer = new uchar[size];
if (!buffer)
esyslog(LOG_ERR, "ERROR: can't allocate ring buffer (size=%d)", size);
Clear();
}
else
esyslog(LOG_ERR, "ERROR: illegal size for ring buffer (%d)", size);
}
cRingBuffer::~cRingBuffer()
{
delete inputThread;
delete outputThread;
delete buffer;
dsyslog(LOG_INFO, "buffer stats: %d (%d%%) used", maxFill, maxFill * 100 / (size - 1));
}
void cRingBuffer::Clear(void)
{
mutex.Lock();
head = tail = 0;
mutex.Unlock();
}
int cRingBuffer::Put(const uchar *Data, int Count)
{
if (Count > 0) {
mutex.Lock();
int rest = size - head;
int diff = tail - head;
mutex.Unlock();
int free = (diff > 0) ? diff - 1 : size + diff - 1;
// Statistics:
int fill = size - free - 1 + Count;
if (fill >= size)
fill = size - 1;
if (fill > maxFill) {
maxFill = fill;
int percent = maxFill * 100 / (size - 1);
if (percent > 75)
dsyslog(LOG_INFO, "buffer usage: %d%%", percent);
}
//
if (free <= 0)
return 0;
if (free < Count)
Count = free;
if (Count > maxFill)
maxFill = Count;
if (Count >= rest) {
memcpy(buffer + head, Data, rest);
if (Count - rest)
memcpy(buffer, Data + rest, Count - rest);
head = Count - rest;
}
else {
memcpy(buffer + head, Data, Count);
head += Count;
}
}
return Count;
}
int cRingBuffer::Get(uchar *Data, int Count)
{
if (Count > 0) {
mutex.Lock();
int rest = size - tail;
int diff = head - tail;
mutex.Unlock();
int cont = (diff >= 0) ? diff : size + diff;
if (rest <= 0)
return 0;
if (cont < Count)
Count = cont;
if (Count >= rest) {
memcpy(Data, buffer + tail, rest);
if (Count - rest)
memcpy(Data + rest, buffer, Count - rest);
tail = Count - rest;
}
else {
memcpy(Data, buffer + tail, Count);
tail += Count;
}
}
return Count;
}
bool cRingBuffer::Start(void)
{
if (!busy) {
busy = true;
outputThread = new cRingBufferOutputThread(this);
if (!outputThread->Start())
DELETENULL(outputThread);
inputThread = new cRingBufferInputThread(this);
if (!inputThread->Start()) {
DELETENULL(inputThread);
DELETENULL(outputThread);
}
busy = outputThread && inputThread;
}
return busy;
}
bool cRingBuffer::Active(void)
{
return outputThread && outputThread->Active() && inputThread && inputThread->Active();
}
void cRingBuffer::Stop(void)
{
busy = false;
for (time_t t0 = time(NULL) + 3; time(NULL) < t0; ) {
if (!((outputThread && outputThread->Active()) || (inputThread && inputThread->Active())))
break;
}
DELETENULL(inputThread);
DELETENULL(outputThread);
}

55
ringbuffer.h Normal file
View File

@ -0,0 +1,55 @@
/*
* ringbuffer.h: A threaded ring buffer
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: ringbuffer.h 1.1 2001/03/18 16:47:00 kls Exp $
*/
#ifndef __RINGBUFFER_H
#define __RINGBUFFER_H
#include "thread.h"
typedef unsigned char uchar;
class cRingBufferInputThread;
class cRingBufferOutputThread;
class cRingBuffer {
friend class cRingBufferInputThread;
friend class cRingBufferOutputThread;
private:
cRingBufferInputThread *inputThread;
cRingBufferOutputThread *outputThread;
cMutex mutex;
int size, head, tail;
uchar *buffer;
int maxFill;
bool busy;
protected:
bool Busy(void) { return busy; }
void Clear(void);
// Immediately clears the ring buffer.
int Put(const uchar *Data, int Count);
// Puts at most Count bytes of Data into the ring buffer.
// Returns the number of bytes actually stored.
int Get(uchar *Data, int Count);
// Gets at most Count bytes of Data from the ring buffer.
// Returns the number of bytes actually retrieved.
virtual void Input(void) = 0;
// Runs as a separate thread and shall continuously read data from
// a source and call Put() to store the data in the ring buffer.
virtual void Output(void) = 0;
// Runs as a separate thread and shall continuously call Get() to
// retrieve data from the ring buffer and write it to a destination.
public:
cRingBuffer(int Size);
virtual ~cRingBuffer();
bool Start(void);
bool Active(void);
void Stop(void);
};
#endif // __RINGBUFFER_H