mirror of
https://github.com/VDR4Arch/vdr.git
synced 2023-10-10 13:36:52 +02:00
Redesigned the ring buffer; prepared for remultiplexing
This commit is contained in:
parent
191fb910bf
commit
5c68fc100c
7
HISTORY
7
HISTORY
@ -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.
|
||||
|
41
Makefile
41
Makefile
@ -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:
|
||||
|
||||
|
4
config.h
4
config.h
@ -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
314
dvbapi.c
@ -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
173
remux.c
Normal 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
51
remux.h
Normal 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
170
ringbuffer.c
Normal 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
55
ringbuffer.h
Normal 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
|
Loading…
Reference in New Issue
Block a user