summaryrefslogtreecommitdiff
path: root/command
diff options
context:
space:
mode:
Diffstat (limited to 'command')
-rw-r--r--command/Makefile88
-rw-r--r--command/audio.cpp184
-rw-r--r--command/audio.h59
-rw-r--r--command/debug.h23
-rw-r--r--command/decoder.cpp478
-rw-r--r--command/decoder.h72
-rw-r--r--command/demux.cpp190
-rw-r--r--command/demux.h45
-rw-r--r--command/global.h139
-rw-r--r--command/markad-standalone.cpp1632
-rw-r--r--command/markad-standalone.h214
-rw-r--r--command/marks.cpp373
-rw-r--r--command/marks.h119
-rw-r--r--command/pes2es.cpp100
-rw-r--r--command/pes2es.h78
-rw-r--r--command/queue.cpp451
-rw-r--r--command/queue.h159
-rw-r--r--command/streaminfo.cpp793
-rw-r--r--command/streaminfo.h112
-rw-r--r--command/ts2pkt.cpp194
-rw-r--r--command/ts2pkt.h127
-rw-r--r--command/vdr2pkt.cpp30
-rw-r--r--command/vdr2pkt.h28
-rw-r--r--command/video.cpp657
-rw-r--r--command/video.h133
25 files changed, 6478 insertions, 0 deletions
diff --git a/command/Makefile b/command/Makefile
new file mode 100644
index 0000000..6091c55
--- /dev/null
+++ b/command/Makefile
@@ -0,0 +1,88 @@
+#
+# Makefile for a Video Disk Recorder addon
+#
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'static const char \*VERSION *=' ../version.h | awk '{ print $$6 }' | sed -e 's/[";]//g')
+
+### The C++ compiler and options:
+
+CXX ?= g++
+CXXFLAGS ?= -g -O2 -Wall -Wextra -Woverloaded-virtual -Wno-parentheses
+PKG-CONFIG ?= pkg-config
+
+### Includes and Defines (add further entries here):
+
+PKG-LIBS += libavcodec
+PKG-INCLUDES += libavcodec
+
+DEFINES += -D_GNU_SOURCE
+DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
+
+INCLUDES += $(shell $(PKG-CONFIG) --cflags $(PKG-INCLUDES))
+LIBS += $(shell $(PKG-CONFIG) --libs $(PKG-LIBS))
+
+INCLUDES += -I..
+
+### The object files (add further files here):
+
+OBJS = markad-standalone.o decoder.o marks.o streaminfo.o video.o audio.o demux.o queue.o vdr2pkt.o ts2pkt.o pes2es.o
+
+### The main target:
+
+all: markad i18n
+
+### Implicit rules:
+
+%.o: %.cpp
+ $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $<
+
+### Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+ @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR = po
+LOCALEDIR = $(DESTDIR)/usr/share/locale
+I18Npo = $(wildcard $(PODIR)/*.po)
+I18Nmsgs = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/markad.mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Npot = $(PODIR)/markad.pot
+
+%.mo: %.po
+ msgfmt -c -o $@ $<
+
+$(I18Npot): $(wildcard *.cpp *.h)
+ xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='<see README>' -o $@ $^
+
+%.po: $(I18Npot)
+ msgmerge -U --no-wrap --no-location --backup=none -q $@ $<
+ @touch $@
+
+$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+ @mkdir -p $(dir $@)
+ cp $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmsgs) $(I18Npot)
+
+### Targets:
+
+markad: $(OBJS)
+ $(CXX) $(CXXFLAGS) $(OBJS) $(LIBS) -o $@
+
+install: markad
+ @cp --remove-destination markad $(DESTDIR)/usr/bin/markad
+ @strip $(DESTDIR)/usr/bin/markad
+ @mkdir -p $(DESTDIR)/var/lib/markad
+ @cp logos/* $(DESTDIR)/var/lib/markad
+ @echo markad installed
+
+clean:
+ @-rm -f $(OBJS) $(DEPFILE) markad *.so *.so.* *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot
diff --git a/command/audio.cpp b/command/audio.cpp
new file mode 100644
index 0000000..bd35ddc
--- /dev/null
+++ b/command/audio.cpp
@@ -0,0 +1,184 @@
+/*
+ * audio.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include "audio.h"
+
+cMarkAdAudio::cMarkAdAudio(MarkAdContext *maContext)
+{
+ macontext=maContext;
+ mark.Comment=NULL;
+ mark.Position=0;
+ mark.Type=0;
+ channels=0;
+#if 0
+ lastiframe_gain=-ANALYZEFRAMES;
+#endif
+ lastiframe_silence=-1;
+}
+
+cMarkAdAudio::~cMarkAdAudio()
+{
+ ResetMark();
+}
+
+void cMarkAdAudio::ResetMark()
+{
+ if (mark.Comment) free(mark.Comment);
+ mark.Comment=NULL;
+ mark.Position=0;
+ mark.Type=0;
+}
+
+bool cMarkAdAudio::AddMark(int Type, int Position, const char *Comment)
+{
+ if (!Comment) return false;
+ if (mark.Comment)
+ {
+ int oldlen=strlen(mark.Comment);
+ mark.Comment=(char *) realloc(mark.Comment,oldlen+10+strlen(Comment));
+ if (!mark.Comment)
+ {
+ mark.Position=0;
+ return false;
+ }
+ strcat(mark.Comment," [");
+ strcat(mark.Comment,Comment);
+ strcat(mark.Comment,"]");
+ }
+ else
+ {
+ mark.Comment=strdup(Comment);
+ }
+ mark.Position=Position;
+ mark.Type=Type;
+ return true;
+}
+
+bool cMarkAdAudio::SilenceDetection()
+{
+ if (!macontext->Audio.Data.Valid) return false;
+ if (lastiframe_silence==lastiframe) return false; // we already detected silence for this frame
+
+ int samples=macontext->Audio.Data.SampleBufLen/
+ sizeof(*macontext->Audio.Data.SampleBuf)/
+ macontext->Audio.Info.Channels;
+
+ short left,right;
+ int lowvalcount=0;
+ for (int i=0; i<samples; i++)
+ {
+ left=macontext->Audio.Data.SampleBuf[0+(i*2)];
+ right=macontext->Audio.Data.SampleBuf[1+(i*2)];
+
+ if ((abs(left)+abs(right))<CUT_VAL)
+ {
+ lowvalcount++;
+ if (lowvalcount>MIN_LOWVALS)
+ {
+ lastiframe_silence=lastiframe;
+ return true;
+ }
+ }
+ else
+ {
+ lowvalcount=0;
+ }
+ }
+ return false;
+}
+
+#if 0
+bool cMarkAdAudio::AnalyzeGain()
+{
+ if (!macontext->Audio.Data.Valid) return false;
+
+ int samples=macontext->Audio.Data.SampleBufLen/
+ sizeof(*macontext->Audio.Data.SampleBuf)/
+ macontext->Audio.Info.Channels;
+
+ double left[samples];
+ double right[samples];
+
+ for (int i=0; i<samples; i++)
+ {
+ left[i]=macontext->Audio.Data.SampleBuf[0+(i*2)];
+ right[i]=macontext->Audio.Data.SampleBuf[1+(i*2)];
+ }
+
+ if ((lastiframe-lastiframe_gain)>ANALYZEFRAMES)
+ {
+ if (lastiframe_gain>0)
+ {
+ double dgain,gain = audiogain.GetGain();
+ dgain=gain-lastgain;
+ printf("%05i %+.2f db %+.2f db\n",lastiframe_gain,gain,lastgain);
+ lastgain=gain;
+ }
+ audiogain.Init(macontext->Audio.Info.SampleRate);
+ lastiframe_gain=lastiframe;
+ }
+ if (audiogain.AnalyzeSamples(left,right,samples,2)!=GAIN_ANALYSIS_OK)
+ {
+ lastiframe_gain=-ANALYZEFRAMES;
+ }
+
+ return true;
+}
+#endif
+
+bool cMarkAdAudio::ChannelChange(int a, int b)
+{
+ if ((a==0) || (b==0)) return false;
+ if (a!=b) return true;
+ return false;
+}
+
+MarkAdMark *cMarkAdAudio::Process(int LastIFrame)
+{
+ ResetMark();
+ if (!LastIFrame) return NULL;
+ lastiframe=LastIFrame;
+
+#if 0
+ AnalyzeGain();
+#endif
+ if (macontext->Audio.Options.AudioSilenceDetection)
+ {
+ if (SilenceDetection())
+ {
+ char *buf=NULL;
+ if (asprintf(&buf,"audio channel silence detecion (%i)",lastiframe)!=-1)
+ {
+ AddMark(MT_SILENCECHANGE,lastiframe,buf);
+ free(buf);
+ }
+ }
+ }
+
+ if (ChannelChange(macontext->Audio.Info.Channels,channels))
+ {
+ char *buf=NULL;
+ if (asprintf(&buf,"audio channel change from %i to %i (%i)", channels,
+ macontext->Audio.Info.Channels,lastiframe)!=-1)
+ {
+ if (macontext->Audio.Info.Channels>2)
+ {
+ AddMark(MT_CHANNELSTART,lastiframe,buf);
+ }
+ else
+ {
+ AddMark(MT_CHANNELSTOP,lastiframe,buf);
+ }
+ free(buf);
+ }
+ }
+
+ channels=macontext->Audio.Info.Channels;
+ return &mark;
+}
+
diff --git a/command/audio.h b/command/audio.h
new file mode 100644
index 0000000..8995732
--- /dev/null
+++ b/command/audio.h
@@ -0,0 +1,59 @@
+/*
+ * audio.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __audio_h_
+#define __audio_h_
+
+#include <netinet/in.h> // for htonl
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "global.h"
+
+extern "C"
+{
+#include "debug.h"
+}
+
+#if 0
+#include "audio_gain_analysis.h"
+#endif
+
+class cMarkAdAudio
+{
+private:
+ int lastiframe;
+ MarkAdContext *macontext;
+
+ MarkAdMark mark;
+ void ResetMark();
+ bool AddMark(int Type, int Position, const char *Comment);
+
+#define CUT_VAL 10
+#define MIN_LOWVALS 3
+ bool SilenceDetection();
+ int lastiframe_silence;
+
+#if 0
+#define ANALYZEFRAMES 1
+ int lastiframe_gain;
+ double lastgain;
+ cMarkAdAudioGainAnalysis audiogain;
+ bool AnalyzeGain();
+#endif
+
+ int channels;
+ bool ChannelChange(int a, int b);
+public:
+ cMarkAdAudio(MarkAdContext *maContext);
+ ~cMarkAdAudio();
+ MarkAdMark *Process(int LastIFrame);
+};
+
+
+#endif
diff --git a/command/debug.h b/command/debug.h
new file mode 100644
index 0000000..a12a972
--- /dev/null
+++ b/command/debug.h
@@ -0,0 +1,23 @@
+/*
+ * debug.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __debug_h_
+#define __debug_h_
+
+#ifndef LOG_ERR
+#define LOG_ERR 3
+#endif
+
+extern int SysLogLevel;
+extern void syslog_with_tid(int priority, const char *format, ...) __attribute__ ((format (printf, 2, 3)));
+
+#define esyslog(a...) void( (SysLogLevel > 0) ? syslog_with_tid(LOG_ERR, a) : void() )
+#define isyslog(a...) void( (SysLogLevel > 1) ? syslog_with_tid(LOG_ERR, a) : void() )
+#define dsyslog(a...) void( (SysLogLevel > 2) ? syslog_with_tid(LOG_ERR, a) : void() )
+#define tsyslog(a...) void( (SysLogLevel > 3) ? syslog_with_tid(LOG_ERR, a) : void() )
+
+#endif
diff --git a/command/decoder.cpp b/command/decoder.cpp
new file mode 100644
index 0000000..6b01404
--- /dev/null
+++ b/command/decoder.cpp
@@ -0,0 +1,478 @@
+/*
+ * decoder.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "decoder.h"
+
+cMarkAdDecoder::cMarkAdDecoder(bool useH264, bool useMP2, bool hasAC3)
+{
+ avcodec_init();
+ avcodec_register_all();
+
+ last_qscale_table=NULL;
+
+ cpu_set_t cpumask;
+ uint len = sizeof(cpumask);
+ int cpucount;
+ if (sched_getaffinity(0,len,&cpumask)<0)
+ {
+ cpucount=1;
+ }
+ else
+ {
+ cpucount=CPU_COUNT(&cpumask);
+ }
+
+ int ver = avcodec_version();
+ char libver[256];
+ snprintf(libver,sizeof(libver),"%i.%i.%i",ver >> 16 & 0xFF,ver >> 8 & 0xFF,ver & 0xFF);
+ isyslog("using libavcodec.so.%s with %i threads",libver,cpucount);
+
+ if (ver!=LIBAVCODEC_VERSION_INT)
+ {
+ esyslog("libavcodec header version %s",AV_STRINGIFY(LIBAVCODEC_VERSION));
+ esyslog("header and library mismatch, decoding disabled");
+ video_context=NULL;
+ ac3_context=NULL;
+ mp2_context=NULL;
+ audiobuf=NULL;
+ return;
+ }
+
+ if (((ver >> 16)<52) && (useH264))
+ {
+ esyslog("dont report bugs about H264, use libavcodec >= 52 instead!");
+ }
+
+ if (useMP2)
+ {
+ CodecID mp2_codecid=CODEC_ID_MP2;
+ AVCodec *mp2_codec= avcodec_find_decoder(mp2_codecid);
+ if (mp2_codec)
+ {
+ mp2_context = avcodec_alloc_context();
+ if (mp2_context)
+ {
+ mp2_context->thread_count=cpucount;
+ mp2_context->codec_id = mp2_codecid;
+ mp2_context->codec_type = CODEC_TYPE_AUDIO;
+ if (avcodec_open(mp2_context, mp2_codec) < 0)
+ {
+ esyslog("could not open codec for MP2");
+ av_free(mp2_context);
+ mp2_context=NULL;
+ }
+ }
+ else
+ {
+ esyslog("could not allocate mp2 context");
+ }
+ }
+ else
+ {
+ esyslog("codec for MP2 not found");
+ mp2_context=NULL;
+ }
+ }
+ else
+ {
+ mp2_context=NULL;
+ }
+
+ if (hasAC3)
+ {
+ CodecID ac3_codecid=CODEC_ID_AC3;
+ AVCodec *ac3_codec= avcodec_find_decoder(ac3_codecid);
+ if (ac3_codec)
+ {
+ ac3_context = avcodec_alloc_context();
+ if (ac3_context)
+ {
+ ac3_context->thread_count=cpucount;
+ ac3_context->codec_id = ac3_codecid;
+ ac3_context->codec_type = CODEC_TYPE_AUDIO;
+ if (avcodec_open(ac3_context, ac3_codec) < 0)
+ {
+ esyslog("could not open codec for AC3");
+ av_free(ac3_context);
+ ac3_context=NULL;
+ }
+ }
+ else
+ {
+ esyslog("could not allocate ac3 context");
+ }
+ }
+ else
+ {
+ esyslog("codec for AC3 not found");
+ ac3_context=NULL;
+ }
+ }
+ else
+ {
+ ac3_context=NULL;
+ }
+
+ AVCodec *video_codec=NULL;
+ CodecID video_codecid;
+
+ if (useH264)
+ {
+ video_codecid=CODEC_ID_H264;
+ }
+ else
+ {
+ video_codecid=CODEC_ID_MPEG2VIDEO_XVMC;
+ }
+
+ video_codec = avcodec_find_decoder(video_codecid);
+ if ((!video_codec) && (video_codecid==CODEC_ID_MPEG2VIDEO_XVMC))
+ {
+ // fallback to MPEG2VIDEO
+ video_codecid=CODEC_ID_MPEG2VIDEO;
+ video_codec=avcodec_find_decoder(video_codecid);
+ }
+
+ if (video_codec)
+ {
+ video_context = avcodec_alloc_context();
+ if (video_context)
+ {
+ video_context->thread_count=cpucount;
+ if (video_codec->capabilities & CODEC_CAP_TRUNCATED)
+ video_context->flags|=CODEC_FLAG_TRUNCATED; // we do not send complete frames
+
+ video_context->flags|=CODEC_FLAG_GRAY; // only decode grayscale
+ video_context->flags2|=CODEC_FLAG2_FAST; // really?
+
+ video_context->skip_idct=AVDISCARD_ALL;
+
+ if (video_codecid==CODEC_ID_H264)
+ {
+ video_context->flags2|=CODEC_FLAG2_CHUNKS; // needed for H264!
+ video_context->skip_loop_filter=AVDISCARD_ALL; // skip deblocking
+ av_log_set_level(AV_LOG_FATAL); // H264 decoder is very chatty
+ }
+ else
+ {
+ video_context->skip_frame=AVDISCARD_NONKEY; // just I-frames
+ }
+
+ video_context->codec_id = video_codecid;
+ video_context->codec_type = CODEC_TYPE_VIDEO;
+ int ret=avcodec_open(video_context, video_codec);
+ if ((ret < 0) && (video_codecid==CODEC_ID_MPEG2VIDEO_XVMC))
+ {
+ // fallback to MPEG2VIDEO
+ video_codecid=CODEC_ID_MPEG2VIDEO;
+ video_codec=avcodec_find_decoder(video_codecid);
+ if (video_codec)
+ {
+ video_context->codec_type=CODEC_TYPE_UNKNOWN;
+ video_context->codec_id=CODEC_ID_NONE;
+ video_context->codec_tag=0;
+ memset(video_context->codec_name,0,sizeof(video_context->codec_name));
+ ret=avcodec_open(video_context, video_codec);
+ }
+ else
+ {
+ ret=-1;
+ }
+ }
+ if (ret < 0)
+ {
+ switch (video_codecid)
+ {
+ case CODEC_ID_H264:
+ esyslog("could not open codec for H264");
+ break;
+ case CODEC_ID_MPEG2VIDEO_XVMC:
+ esyslog("could not open codec MPEG2 (XVMC)");
+ break;
+ case CODEC_ID_MPEG2VIDEO:
+ esyslog("could not open codec MPEG2");
+ break;
+ default:
+ esyslog("could not open video codec");
+ break;
+ }
+ av_free(video_context);
+ video_context=NULL;
+ }
+ else
+ {
+ isyslog("using codec %s",video_codec->long_name);
+
+#if LIBAVCODEC_VERSION_INT >= ((52<<16)+(22<<8)+2)
+ if (video_context->hwaccel)
+ {
+ isyslog("using hwaccel %s",video_context->hwaccel->name);
+ }
+#endif
+
+ video_frame = avcodec_alloc_frame();
+ if (!video_frame)
+ {
+ esyslog("could not allocate frame");
+ avcodec_close(video_context);
+ av_free(video_context);
+ video_context=NULL;
+ }
+ }
+ }
+ else
+ {
+ esyslog("could not allocate video context");
+ }
+ }
+ else
+ {
+ switch (video_codecid)
+ {
+ case CODEC_ID_H264:
+ esyslog("codec for H264 not found");
+ break;
+ case CODEC_ID_MPEG2VIDEO_XVMC:
+ esyslog("codec for MPEG2 (XVMC) not found");
+ break;
+ case CODEC_ID_MPEG2VIDEO:
+ esyslog("codec for MPEG2 not found");
+ break;
+ default:
+ esyslog("video codec not found");
+ break;
+ }
+ video_context=NULL;
+ }
+
+ if ((ac3_context) || (mp2_context))
+ {
+ audiobuf=(int16_t*) malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE);
+ }
+ else
+ {
+ audiobuf=NULL;
+ }
+
+}
+
+cMarkAdDecoder::~cMarkAdDecoder()
+{
+ if (video_context)
+ {
+ avcodec_close(video_context);
+ av_free(video_context);
+ av_free(video_frame);
+ }
+
+ if (ac3_context)
+ {
+ avcodec_close(ac3_context);
+ av_free(ac3_context);
+ }
+
+ if (mp2_context)
+ {
+ avcodec_close(mp2_context);
+ av_free(mp2_context);
+ }
+ if (audiobuf) free(audiobuf);
+}
+
+bool cMarkAdDecoder::DecodeMP2(MarkAdContext *maContext, uchar *espkt, int eslen)
+{
+ if (!mp2_context) return false;
+ maContext->Audio.Data.Valid=false;
+ AVPacket avpkt;
+#if LIBAVCODEC_VERSION_INT >= ((52<<16)+(25<<8)+0)
+ av_init_packet(&avpkt);
+#else
+ memset(&avpkt,0,sizeof(avpkt));
+ avpkt.pts = avpkt.dts = AV_NOPTS_VALUE;
+ avpkt.pos = -1;
+#endif
+ avpkt.data=espkt;
+ avpkt.size=eslen;
+
+ audiobufsize=AVCODEC_MAX_AUDIO_FRAME_SIZE;
+ int ret=false;
+ int16_t Taudiobuf[AVCODEC_MAX_AUDIO_FRAME_SIZE];
+ while (avpkt.size>0)
+ {
+#if LIBAVCODEC_VERSION_INT < ((52<<16)+(25<<8)+0)
+ int len=avcodec_decode_audio2(mp2_context,Taudiobuf,&audiobufsize,
+ avpkt.data,avpkt.size);
+#else
+ int len=avcodec_decode_audio3(mp2_context,Taudiobuf,&audiobufsize,&avpkt);
+#endif
+ if (len<0)
+ {
+ esyslog("error decoding mp2");
+ break;
+ }
+ if (audiobufsize>0)
+ {
+ memcpy(audiobuf,Taudiobuf,audiobufsize);
+ SetAudioInfos(maContext,mp2_context);
+ ret=true;
+ avpkt.size-=len;
+ avpkt.data+=len;
+ }
+ }
+ return ret;
+}
+
+bool cMarkAdDecoder::SetAudioInfos(MarkAdContext *maContext, AVCodecContext *Audio_Context)
+{
+ if ((!maContext) || (!Audio_Context)) return false;
+
+ maContext->Audio.Info.SampleRate = Audio_Context->sample_rate;
+ maContext->Audio.Info.Channels = Audio_Context->channels;
+ maContext->Audio.Data.SampleBuf=audiobuf;
+ maContext->Audio.Data.SampleBufLen=audiobufsize;
+ maContext->Audio.Data.Valid=true;
+ return true;
+}
+
+bool cMarkAdDecoder::DecodeAC3(MarkAdContext *maContext, uchar *espkt, int eslen)
+{
+ if (!ac3_context) return false;
+ maContext->Audio.Data.Valid=false;
+ AVPacket avpkt;
+#if LIBAVCODEC_VERSION_INT >= ((52<<16)+(25<<8)+0)
+ av_init_packet(&avpkt);
+#else
+ memset(&avpkt,0,sizeof(avpkt));
+ avpkt.pts = avpkt.dts = AV_NOPTS_VALUE;
+ avpkt.pos = -1;
+#endif
+ avpkt.data=espkt;
+ avpkt.size=eslen;
+
+ int ret=false;
+ int16_t Taudiobuf[AVCODEC_MAX_AUDIO_FRAME_SIZE];
+ while (avpkt.size>0)
+ {
+ audiobufsize=AVCODEC_MAX_AUDIO_FRAME_SIZE;
+#if LIBAVCODEC_VERSION_INT < ((52<<16)+(25<<8)+0)
+ int len=avcodec_decode_audio2(ac3_context,Taudiobuf,&audiobufsize,
+ avpkt.data,avpkt.size);
+#else
+ int len=avcodec_decode_audio3(ac3_context,Taudiobuf,&audiobufsize,&avpkt);
+#endif
+ if (len<0)
+ {
+ esyslog("error decoding ac3");
+ break;
+ }
+ if (audiobufsize>0)
+ {
+ memcpy(audiobuf,Taudiobuf,audiobufsize);
+ SetAudioInfos(maContext,ac3_context);
+ ret=true;
+ avpkt.size-=len;
+ avpkt.data+=len;
+ }
+ }
+ return ret;
+}
+
+void cMarkAdDecoder::PAR2DAR(AVRational a, AVRational *erg)
+{
+ av_reduce(&erg->num,&erg->den,video_context->width*a.num,
+ video_context->height*a.den,1024*1024);
+}
+
+bool cMarkAdDecoder::SetVideoInfos(MarkAdContext *maContext,AVCodecContext *Video_Context, AVFrame *Video_Frame)
+{
+ if ((!maContext) || (!Video_Context) || (!Video_Frame)) return false;
+ for (int i=0; i<4; i++)
+ {
+ if (Video_Frame->data[i])
+ {
+ maContext->Video.Data.Plane[i]=Video_Frame->data[i];
+ maContext->Video.Data.PlaneLinesize[i]=Video_Frame->linesize[i];
+ maContext->Video.Data.Valid=true;
+ }
+ }
+ maContext->Video.Info.Height=Video_Context->height;
+ maContext->Video.Info.Width=Video_Context->width;
+
+ AVRational dar;
+ PAR2DAR(Video_Context->sample_aspect_ratio,&dar);
+
+ maContext->Video.Info.AspectRatio.Num=dar.num;
+ maContext->Video.Info.AspectRatio.Den=dar.den;
+
+ return true;
+}
+
+bool cMarkAdDecoder::DecodeVideo(MarkAdContext *maContext,uchar *pkt, int plen)
+{
+ if (!video_context) return false;
+ maContext->Video.Data.Valid=false;
+
+ if ((video_context->codec_id==CODEC_ID_H264) && (!video_context->skip_frame))
+ {
+ if (maContext->Video.Info.Pict_Type)
+ {
+ if (maContext->Video.Info.Interlaced)
+ {
+ video_context->skip_frame=AVDISCARD_BIDIR; // just P/I-frames
+ }
+ else
+ {
+ video_context->skip_frame=AVDISCARD_NONKEY; // just I-frames
+ }
+ }
+ }
+
+ AVPacket avpkt;
+#if LIBAVCODEC_VERSION_INT >= ((52<<16)+(25<<8)+0)
+ av_init_packet(&avpkt);
+#else
+ memset(&avpkt,0,sizeof(avpkt));
+ avpkt.pts = avpkt.dts = AV_NOPTS_VALUE;
+ avpkt.pos = -1;
+#endif
+ avpkt.data=pkt;
+ avpkt.size=plen;
+
+ // decode video
+ int video_frame_ready=0;
+ int len,ret=false;
+
+ while (avpkt.size>0)
+ {
+#if LIBAVCODEC_VERSION_INT < ((52<<16)+(25<<8)+0)
+ len=avcodec_decode_video(video_context,video_frame,&video_frame_ready,
+ avpkt.data,avpkt.size);
+#else
+ len=avcodec_decode_video2(video_context,video_frame,&video_frame_ready,
+ &avpkt);
+#endif
+ if (len<0)
+ {
+ esyslog("error decoding video");
+ break;
+ }
+ else
+ {
+ avpkt.size-=len;
+ avpkt.data+=len;
+ }
+ if (video_frame_ready)
+ {
+ if (last_qscale_table!=video_frame->qscale_table)
+ {
+ if (SetVideoInfos(maContext,video_context,video_frame)) ret=true;
+ last_qscale_table=video_frame->qscale_table;
+ }
+ }
+ }
+ return ret;
+}
diff --git a/command/decoder.h b/command/decoder.h
new file mode 100644
index 0000000..46e9f74
--- /dev/null
+++ b/command/decoder.h
@@ -0,0 +1,72 @@
+/*
+ * decoder.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __decoder_h_
+#define __decoder_h_
+
+#define __STDC_CONSTANT_MACROS
+
+#include <stdint.h>
+#include <sched.h>
+
+#ifndef DECLARE_ALIGNED
+#define DECLARE_ALIGNED(n,t,v) t v __attribute__ ((aligned (n)))
+#endif
+
+#ifndef CPU_COUNT
+#define CPU_COUNT(i) 1 // very crude ;)
+#endif
+
+#ifndef uchar
+typedef unsigned char uchar;
+#endif
+
+extern "C"
+{
+#include <libavcodec/avcodec.h>
+
+#if LIBAVCODEC_VERSION_INT < ((52<<16)+(0<<8)+0)
+#warning H264 parsing may be broken, better use libavcodec52
+#endif
+
+#if LIBAVCODEC_VERSION_INT < ((52<<16)+(23<<8)+0)
+#include <libavformat/avformat.h>
+#endif
+#include "debug.h"
+}
+
+#include "global.h"
+
+class cMarkAdDecoder
+{
+private:
+ int16_t *audiobuf;
+ int audiobufsize;
+
+ AVCodecContext *ac3_context;
+ AVCodecContext *mp2_context;
+ AVCodecContext *video_context;
+ AVFrame *video_frame;
+
+ int8_t *last_qscale_table;
+
+ bool SetAudioInfos(MarkAdContext *maContext, AVCodecContext *Audio_Context);
+
+ void PAR2DAR(AVRational a, AVRational *erg);
+ bool SetVideoInfos(MarkAdContext *maContext,AVCodecContext *Video_Context,
+ AVFrame *Video_Frame);
+public:
+ bool DecodeVideo(MarkAdContext *maContext, uchar *pkt, int plen);
+ bool DecodeMP2(MarkAdContext *maContext, uchar *espkt, int eslen);
+ bool DecodeAC3(MarkAdContext *maContext, uchar *espkt, int eslen);
+ cMarkAdDecoder(bool useH264, bool useMP2, bool hasAC3);
+ ~cMarkAdDecoder();
+};
+
+
+
+#endif
diff --git a/command/demux.cpp b/command/demux.cpp
new file mode 100644
index 0000000..55113a8
--- /dev/null
+++ b/command/demux.cpp
@@ -0,0 +1,190 @@
+/*
+ * demux.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "demux.h"
+
+cMarkAdDemux::cMarkAdDemux()
+{
+ ts2pkt=NULL;
+ vdr2pkt=NULL;
+ pes2audioes=NULL;
+ pes2videoes=NULL;
+ pause=false;
+ pause_retval=0;
+ queue = new cMarkAdPaketQueue(NULL,376);
+}
+
+cMarkAdDemux::~cMarkAdDemux()
+{
+ if (ts2pkt) delete ts2pkt;
+ if (vdr2pkt) delete vdr2pkt;
+ if (pes2audioes) delete pes2audioes;
+ if (pes2videoes) delete pes2videoes;
+ if (queue) delete queue;
+}
+
+void cMarkAdDemux::ProcessVDR(MarkAdPid Pid, uchar *Data, int Count, uchar **Pkt, int *PktLen)
+{
+ if ((!Pkt) || (!PktLen)) return;
+ *Pkt=NULL;
+ *PktLen=0;
+
+ uchar *pkt;
+ int pktlen;
+
+ if (!vdr2pkt)
+ {
+ if ((Pid.Type==MARKAD_PIDTYPE_AUDIO_AC3) || (Pid.Type==MARKAD_PIDTYPE_AUDIO_MP2))
+ {
+ vdr2pkt= new cMarkAdVDR2Pkt("VDR2PKT audio");
+ }
+ else
+ {
+ vdr2pkt= new cMarkAdVDR2Pkt("VDR2PKT video");
+ }
+ }
+ if (!vdr2pkt) return;
+
+ vdr2pkt->Process(Pid,Data,Count,&pkt,&pktlen);
+
+ if ((Pid.Type==MARKAD_PIDTYPE_AUDIO_AC3) || (Pid.Type==MARKAD_PIDTYPE_AUDIO_MP2))
+ {
+ if (!pes2audioes) pes2audioes=new cMarkAdPES2ES("PES2ES audio");
+ if (!pes2audioes) return;
+ pes2audioes->Process(Pid,pkt,pktlen,Pkt,PktLen);
+ }
+
+ if ((Pid.Type==MARKAD_PIDTYPE_VIDEO_H262) || (Pid.Type==MARKAD_PIDTYPE_VIDEO_H264))
+ {
+ if (!pes2videoes)
+ {
+ if (Pid.Type==MARKAD_PIDTYPE_VIDEO_H264)
+ {
+ pes2videoes=new cMarkAdPES2ES("PES2H264ES video",393216);
+ }
+ else
+ {
+ pes2videoes=new cMarkAdPES2ES("PES2ES video",65536);
+ }
+ }
+ if (!pes2videoes) return;
+ pes2videoes->Process(Pid,pkt,pktlen,Pkt,PktLen);
+ }
+
+ return;
+}
+
+void cMarkAdDemux::ProcessTS(MarkAdPid Pid, uchar *Data, int Count, uchar **Pkt, int *PktLen)
+{
+ if ((!Pkt) || (!PktLen)) return;
+ *Pkt=NULL;
+ *PktLen=0;
+
+ uchar *pkt;
+ int pktlen;
+
+ if (!ts2pkt)
+ {
+ if (Pid.Type==MARKAD_PIDTYPE_VIDEO_H264)
+ {
+ ts2pkt=new cMarkAdTS2Pkt("TS2H264",819200);
+ }
+ else
+ {
+ ts2pkt=new cMarkAdTS2Pkt("TS2PKT",262144);
+ }
+ }
+ if (!ts2pkt) return;
+
+ ts2pkt->Process(Pid,Data,Count,&pkt,&pktlen);
+
+ if ((Pid.Type==MARKAD_PIDTYPE_AUDIO_AC3) || (Pid.Type==MARKAD_PIDTYPE_AUDIO_MP2))
+ {
+ if (!pes2audioes) pes2audioes=new cMarkAdPES2ES("PES2ES audio");
+ if (!pes2audioes) return;
+ pes2audioes->Process(Pid,pkt,pktlen,Pkt,PktLen);
+ }
+
+ if ((Pid.Type==MARKAD_PIDTYPE_VIDEO_H262) || (Pid.Type==MARKAD_PIDTYPE_VIDEO_H264))
+ {
+ if ((pkt) && ((pkt[3] & 0xF0)==0xE0) && (pkt[4]!=0) && (pkt[5]!=0))
+ {
+ ts2pkt->InjectVideoPES(pkt,pktlen);
+ pkt=NULL;
+ pktlen=0;
+ }
+ }
+
+ if ((pkt) && (!*Pkt))
+ {
+ *Pkt=pkt;
+ *PktLen=pktlen;
+ }
+
+ return;
+}
+
+int cMarkAdDemux::Process(MarkAdPid Pid, uchar *Data, int Count, uchar **Pkt, int *PktLen)
+{
+ if ((!Data) && (!Count) && (!Pkt) || (!PktLen)) return -1;
+
+ *Pkt=NULL;
+ *PktLen=0;
+
+ uchar *in=NULL;
+ int inlen=0;
+ int retval=0;
+
+ if (!pause)
+ {
+ int min_needed=TS_SIZE;
+
+ int needed=min_needed-queue->Length();
+ if (Count>needed)
+ {
+ queue->Put(Data,needed);
+ retval=needed;
+ }
+ else
+ {
+ queue->Put(Data,Count);
+ retval=Count;
+ }
+ if (queue->Length()<min_needed) return Count;
+
+ inlen=TS_SIZE;
+ in=queue->Get(&inlen);
+ }
+
+ if (Pid.Num>=0)
+ {
+ ProcessTS(Pid, in, inlen, Pkt, PktLen);
+ }
+ else
+ {
+ ProcessVDR(Pid, in, inlen, Pkt, PktLen);
+ }
+
+ if (*Pkt)
+ {
+ if (!pause_retval) pause_retval=retval;
+ pause=true;
+ return 0;
+ }
+
+ if (pause)
+ {
+ if (pause_retval)
+ {
+ retval=pause_retval;
+ pause_retval=0;
+ }
+ pause=false;
+ }
+
+ return retval;
+}
diff --git a/command/demux.h b/command/demux.h
new file mode 100644
index 0000000..3cb85cc
--- /dev/null
+++ b/command/demux.h
@@ -0,0 +1,45 @@
+/*
+ * demux.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __demux_h_
+#define __demux_h_
+
+#ifndef TS_SIZE
+#define TS_SIZE 188
+#endif
+
+#ifndef VDR_SIZE
+#define VDR_SIZE 2048
+#endif
+
+#include "global.h"
+#include "queue.h"
+#include "vdr2pkt.h"
+#include "ts2pkt.h"
+#include "pes2es.h"
+
+class cMarkAdDemux
+{
+private:
+ cMarkAdVDR2Pkt *vdr2pkt;
+ cMarkAdTS2Pkt *ts2pkt;
+ cMarkAdPES2ES *pes2audioes;
+ cMarkAdPES2ES *pes2videoes;
+ cMarkAdPaketQueue *queue;
+
+ bool pause;
+ int pause_retval;
+
+ void ProcessTS(MarkAdPid Pid, uchar *Data, int Count, uchar **Pkt, int *PktLen);
+ void ProcessVDR(MarkAdPid Pid, uchar *Data, int Count, uchar **Pkt, int *PktLen);
+public:
+ cMarkAdDemux();
+ ~cMarkAdDemux();
+ int Process(MarkAdPid Pid, uchar *Data, int Count, uchar **Pkt, int *PktLen);
+};
+
+#endif
diff --git a/command/global.h b/command/global.h
new file mode 100644
index 0000000..1034ded
--- /dev/null
+++ b/command/global.h
@@ -0,0 +1,139 @@
+/*
+ * global.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __global_h_
+#define __global_h_
+
+#include <time.h>
+
+#ifndef uchar
+typedef unsigned char uchar;
+#endif
+
+#define MA_I_TYPE 1
+#define MA_P_TYPE 2
+#define MA_B_TYPE 3
+#define MA_D_TYPE 4
+#define MA_SI_TYPE 5
+#define MA_SP_TYPE 6
+#define MA_BI_TYPE 7
+
+#define MT_COMMON 0x10
+
+#define MT_ASPECTCHANGE 0x20
+
+#define MT_CHANNELCHANGE 0x30
+#define MT_CHANNELSTART 0x30
+#define MT_CHANNELSTOP 0x31
+
+#define MT_LOGOCHANGE 0x40
+#define MT_LOGOSTART 0x40
+#define MT_LOGOSTOP 0x41
+
+#define MT_BORDERCHANGE 0x50
+#define MT_BORDERSTART 0x50
+#define MT_BORDERSTOP 0x51
+
+#define MT_SILENCECHANGE 0x60
+
+#define MT_MOVED 0xE0
+#define MT_ALL 0xFF
+
+typedef struct MarkAdMark
+{
+ int Type;
+ int Position;
+ char *Comment;
+} MarkAdMark;
+
+typedef struct MarkAdAspectRatio
+{
+ int Num;
+ int Den;
+} MarkAdAspectRatio;
+
+#define MARKAD_PIDTYPE_VIDEO_H262 0x10
+#define MARKAD_PIDTYPE_VIDEO_H264 0x11
+#define MARKAD_PIDTYPE_AUDIO_AC3 0x20
+#define MARKAD_PIDTYPE_AUDIO_MP2 0x21
+
+typedef struct MarkAdPid
+{
+ int Num;
+ int Type;
+} MarkAdPid;
+
+typedef struct MarkAdContext
+{
+ char *LogoDir; // Logo Directory, default /var/lib/markad
+
+ struct Options
+ {
+ int LogoExtraction;
+ int LogoWidth;
+ int LogoHeight;
+ bool ASD;
+ } Options;
+
+ struct Info
+ {
+ int Length; // in Minutes
+ char *ChannelID;
+ MarkAdPid VPid;
+ MarkAdPid APid;
+ MarkAdPid DPid;
+ } Info;
+
+ struct Video
+ {
+ struct Options
+ {
+ bool IgnoreAspectRatio;
+ bool IgnoreLogoDetection;
+ } Options;
+
+ struct Info
+ {
+ int Width; // width of pic
+ int Height; // height of pic
+ int Pict_Type; // picture type (I,P,B,S,SI,SP,BI)
+ MarkAdAspectRatio AspectRatio;
+ double FramesPerSecond;
+ bool Interlaced;
+ } Info;
+
+ struct Data
+ {
+ bool Valid; // flag, if true data is valid
+ uchar *Plane[4]; // picture planes (YUV420)
+ int PlaneLinesize[4]; // size int bytes of each picture plane line
+ } Data;
+ } Video;
+
+ struct Audio
+ {
+ struct Options
+ {
+ bool AudioSilenceDetection;
+ } Options;
+
+ struct Info
+ {
+ int Channels; // number of audio channels
+ int SampleRate;
+ } Info;
+ struct Data
+ {
+ bool Valid;
+ short *SampleBuf;
+ int SampleBufLen;
+ } Data;
+ } Audio;
+
+} MarkAdContext;
+
+#endif
diff --git a/command/markad-standalone.cpp b/command/markad-standalone.cpp
new file mode 100644
index 0000000..9d4cb82
--- /dev/null
+++ b/command/markad-standalone.cpp
@@ -0,0 +1,1632 @@
+/*
+ * markad-standalone.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "markad-standalone.h"
+
+bool SYSLOG=false;
+cMarkAdStandalone *cmasta=NULL;
+int SysLogLevel=2;
+
+void syslog_with_tid(int priority, const char *format, ...)
+{
+ va_list ap;
+ if (SYSLOG)
+ {
+ char fmt[255];
+ snprintf(fmt, sizeof(fmt), "[%d] %s", getpid(), format);
+ va_start(ap, format);
+ vsyslog(priority, fmt, ap);
+ va_end(ap);
+ }
+ else
+ {
+ char fmt[255];
+ snprintf(fmt, sizeof(fmt), "markad: [%d] %s", getpid(), format);
+ va_start(ap, format);
+ vprintf(fmt,ap);
+ va_end(ap);
+ printf("\n");
+ fflush(stdout);
+ }
+}
+
+void cMarkAdStandalone::AddStartMark()
+{
+ char *buf;
+ if (asprintf(&buf,"start of recording (0)")!=-1)
+ {
+ marks.Add(MT_COMMON,0,buf);
+ isyslog(buf);
+ free(buf);
+ }
+}
+
+bool cMarkAdStandalone::CheckFirstMark()
+{
+ if (marksAligned) return true;
+
+ // Check the second mark
+ clMark *second=marks.GetNext(0);
+ if (!second) return false;
+
+ if ((second->type==MT_LOGOSTART) || (second->type==MT_BORDERSTART) ||
+ (second->type==MT_CHANNELSTART))
+ {
+ clMark *first=marks.Get(0);
+ if (first) marks.Del(first);
+ marksAligned=true;
+ }
+
+ if ((second->type==MT_LOGOSTOP) || (second->type==MT_BORDERSTOP) ||
+ (second->type==MT_CHANNELSTOP))
+ {
+ marksAligned=true;
+ }
+
+ // If we have an aspectchange, check the next aspectchange mark
+ // and the difference between
+ if ((second->type==MT_ASPECTCHANGE) && (macontext.Video.Info.FramesPerSecond>0))
+ {
+ clMark *next=marks.GetNext(second->position,MT_ASPECTCHANGE);
+ if (next)
+ {
+ int MAXPOSDIFF=(int) (macontext.Video.Info.FramesPerSecond*60*13);
+ if ((next->position-second->position)>MAXPOSDIFF)
+ {
+ clMark *first=marks.Get(0);
+ if (first) marks.Del(first);
+ marksAligned=true;
+ }
+ }
+ }
+ return marksAligned;
+}
+
+void cMarkAdStandalone::AddMark(MarkAdMark *Mark)
+{
+ if (!Mark) return;
+ if (!Mark->Type) return;
+
+ if (Mark->Type==MT_LOGOSTART)
+ {
+ // check if last mark is an aspectratio change
+ clMark *prev=marks.GetLast();
+ if ((prev) && (prev->type==MT_ASPECTCHANGE))
+ {
+ if ((Mark->Position-prev->position)<20)
+ {
+ if (Mark->Comment) isyslog(Mark->Comment);
+ isyslog("aspectratio change in short distance, moving mark (%i->%i)",Mark->Position,prev->position);
+ marks.Add(MT_MOVED,prev->position,Mark->Comment);
+ return;
+ }
+ }
+ }
+
+ if (((Mark->Type & 0xF0)==MT_LOGOCHANGE) && (macontext.Video.Info.FramesPerSecond>0))
+ {
+ // check if the distance to the last mark is large enough!
+ clMark *prev=marks.GetLast();
+ if ((prev) && ((prev->type & 0xF0)==MT_LOGOCHANGE))
+ {
+ int MINMARKDIFF=(int) (macontext.Video.Info.FramesPerSecond*60*2);
+ if ((Mark->Position-prev->position)<MINMARKDIFF)
+ {
+ if (Mark->Comment) isyslog(Mark->Comment);
+ isyslog("logo distance too short, deleting (%i,%i)",prev->position,Mark->Position);
+ marks.Del(prev);
+ return;
+ }
+ }
+ }
+
+ if (((Mark->Type & 0xF0)==MT_BORDERCHANGE) && (Mark->Position>25000) &&
+ (!macontext.Video.Options.IgnoreLogoDetection))
+ {
+ isyslog("border change detected. logo detection disabled");
+ macontext.Video.Options.IgnoreLogoDetection=true;
+ marks.Del(MT_LOGOSTART);
+ marks.Del(MT_LOGOSTOP);
+ }
+
+ if ((((Mark->Type & 0xF0)==MT_CHANNELCHANGE) || (Mark->Type==MT_ASPECTCHANGE)) &&
+ (Mark->Position>25000) && (bDecodeVideo))
+ {
+ bool TurnOff=true;
+ if (Mark->Type==MT_ASPECTCHANGE)
+ {
+ if (marks.Count(MT_ASPECTCHANGE)<3)
+ {
+ TurnOff=false;
+ }
+ }
+
+ if (Mark->Type==MT_CHANNELCHANGE)
+ {
+ if ((marks.Count(MT_CHANNELSTART)+marks.Count(MT_CHANNELSTOP))<3)
+ {
+ TurnOff=false;
+ }
+ }
+
+ if (TurnOff)
+ {
+ isyslog("%s change detected. logo/border detection disabled",
+ Mark->Type==MT_ASPECTCHANGE ? "aspectratio" : "audio channel");
+
+ bDecodeVideo=false;
+ macontext.Video.Data.Valid=false;
+ marks.Del(MT_LOGOSTART);
+ marks.Del(MT_LOGOSTOP);
+ marks.Del(MT_BORDERSTART);
+ marks.Del(MT_BORDERSTOP);
+ }
+ }
+ CheckFirstMark();
+ clMark *old=marks.Get(Mark->Position);
+ if (old)
+ {
+ // Aspect- / Channelchange wins over Logo/Border
+ if (((old->type & 0xF0)!=MT_ASPECTCHANGE) && ((old->type & 0xF0)!=MT_CHANNELCHANGE))
+ {
+ if (Mark->Comment) isyslog(Mark->Comment);
+ marks.Add(Mark->Type,Mark->Position,Mark->Comment);
+ }
+ }
+ else
+ {
+ if (Mark->Comment) isyslog(Mark->Comment);
+ marks.Add(Mark->Type,Mark->Position,Mark->Comment);
+ }
+}
+
+void cMarkAdStandalone::RateMarks()
+{
+ if (macontext.Info.Length)
+ {
+ if ((marks.Count()>3) && (macontext.Info.Length>30))
+ {
+ int logomarks=marks.Count(MT_LOGOSTART) + marks.Count(MT_LOGOSTOP);
+ int audiomarks=marks.Count(MT_CHANNELSTART) + marks.Count(MT_CHANNELSTOP);
+
+ // If we have logomarks or audiomarks get rid of the aspect changes,
+ // cause if we have a recording with (>=3) aspect changes the
+ // logomarks were already deleted in AddMark
+ if ((logomarks) || (audiomarks))
+ {
+ marks.Del(MT_ASPECTCHANGE);
+ }
+ }
+ }
+
+ // Check the first mark again
+ CheckFirstMark();
+
+ // TODO: more checks
+}
+
+void cMarkAdStandalone::SaveFrame(int frame)
+{
+ if (!macontext.Video.Info.Width) return;
+ if (!macontext.Video.Data.Valid) return;
+
+ FILE *pFile;
+ char szFilename[256];
+
+ // Open file
+ sprintf(szFilename, "/tmp/frame%06d.pgm", frame);
+ pFile=fopen(szFilename, "wb");
+ if (pFile==NULL)
+ return;
+
+ // Write header
+ fprintf(pFile, "P5\n%d %d\n255\n", macontext.Video.Data.PlaneLinesize[0],
+ macontext.Video.Info.Height);
+
+ // Write pixel data
+ fwrite(macontext.Video.Data.Plane[0],1,
+ macontext.Video.Data.PlaneLinesize[0]*macontext.Video.Info.Height,pFile);
+ // Close file
+ fclose(pFile);
+}
+
+void cMarkAdStandalone::CheckIndex(const char *Directory)
+{
+ // Here we check if the index is more
+ // advanced than our framecounter.
+ // If not we wait. If we wait too much,
+ // we discard this check...
+
+#define WAITTIME 10
+
+ if (!indexFile) return;
+ if (sleepcnt>=2) return; // we already slept too much
+
+ bool notenough=true;
+ do
+ {
+ struct stat statbuf;
+ if (stat(indexFile,&statbuf)==-1) return;
+
+ int maxframes=statbuf.st_size/8;
+ if (maxframes<(framecnt+200))
+ {
+ if ((difftime(time(NULL),statbuf.st_mtime))>=10) return; // "old" file
+ marks.Save(Directory,macontext.Video.Info.FramesPerSecond,isTS);
+ sleep(WAITTIME); // now we sleep and hopefully the index will grow
+ waittime+=WAITTIME;
+ if (errno==EINTR) return;
+ sleepcnt++;
+ if (sleepcnt>=2)
+ {
+ esyslog("no new data after %i seconds, skipping wait!",
+ sleepcnt*WAITTIME);
+ notenough=false; // something went wrong?
+ }
+ }
+ else
+ {
+ sleepcnt=0;
+ notenough=false;
+ }
+ }
+ while (notenough);
+}
+
+bool cMarkAdStandalone::ProcessFile(const char *Directory, int Number)
+{
+ if (!Directory) return false;
+ if (!Number) return false;
+
+ CheckIndex(Directory);
+ if (abort) return false;
+
+ const int datalen=385024;
+ uchar data[datalen];
+
+ char *fbuf;
+ if (isTS)
+ {
+ if (asprintf(&fbuf,"%s/%05i.ts",Directory,Number)==-1) return false;
+ }
+ else
+ {
+ if (asprintf(&fbuf,"%s/%03i.vdr",Directory,Number)==-1) return false;
+ }
+
+ int f=open(fbuf,O_RDONLY);
+ free(fbuf);
+ if (f==-1) return false;
+
+ int dataread;
+ dsyslog("processing file %05i",Number);
+
+ while ((dataread=read(f,data,datalen))>0)
+ {
+ if (abort) break;
+ MarkAdMark *mark=NULL;
+
+ if ((video_demux) && (video) && (streaminfo))
+ {
+ uchar *pkt;
+ int pktlen;
+
+ uchar *tspkt = data;
+ int tslen = dataread;
+
+ while (tslen>0)
+ {
+ int len=video_demux->Process(macontext.Info.VPid,tspkt,tslen,&pkt,&pktlen);
+ if (len<0)
+ {
+ break;
+ }
+ else
+ {
+ if (pkt)
+ {
+ if (streaminfo->FindVideoInfos(&macontext,pkt,pktlen))
+ {
+ if (!framecnt)
+ {
+ isyslog("%s %i%c",(macontext.Video.Info.Height>576) ? "HDTV" : "SDTV",
+ macontext.Video.Info.Height,
+ macontext.Video.Info.Interlaced ? 'i' : 'p');
+ AddStartMark();
+ }
+ //printf("%05i( %c )\n",framecnt,frametypes[macontext.Video.Info.Pict_Type]);
+ framecnt++;
+ if (macontext.Video.Info.Pict_Type==MA_I_TYPE)
+ {
+ lastiframe=iframe;
+ iframe=framecnt-1;
+ }
+ }
+
+ bool dRes=true;
+ if ((decoder) && (bDecodeVideo)) dRes=decoder->DecodeVideo(&macontext,pkt,pktlen);
+ if (dRes)
+ {
+ if ((framecnt-iframe)<=3)
+ {
+ mark=video->Process(lastiframe);
+ AddMark(mark);
+ //SaveFrame(lastiframe); // TODO: JUST FOR DEBUGGING!
+ }
+ }
+ }
+ tspkt+=len;
+ tslen-=len;
+ }
+ }
+ }
+
+ if ((ac3_demux) && (streaminfo) && (audio))
+ {
+ uchar *pkt;
+ int pktlen;
+
+ uchar *tspkt = data;
+ int tslen = dataread;
+
+ while (tslen>0)
+ {
+ int len=ac3_demux->Process(macontext.Info.DPid,tspkt,tslen,&pkt,&pktlen);
+ if (len<0)
+ {
+ break;
+ }
+ else
+ {
+ if (pkt)
+ {
+ if (streaminfo->FindAC3AudioInfos(&macontext,pkt,pktlen))
+ {
+ if ((!isTS) && (!noticeVDR_AC3))
+ {
+ dsyslog("found AC3");
+ if (mp2_demux)
+ {
+ delete mp2_demux;
+ mp2_demux=NULL;
+ }
+ noticeVDR_AC3=true;
+ }
+ mark=audio->Process(lastiframe);
+ AddMark(mark);
+ }
+ }
+ tspkt+=len;
+ tslen-=len;
+ }
+ }
+ }
+
+ if ((mp2_demux) && (decoder) && (audio) && (bDecodeAudio))
+ {
+ uchar *pkt;
+ int pktlen;
+
+ uchar *tspkt = data;
+ int tslen = dataread;
+
+ while (tslen>0)
+ {
+ int len=mp2_demux->Process(macontext.Info.APid,tspkt,tslen,&pkt,&pktlen);
+ if (len<0)
+ {
+ break;
+ }
+ else
+ {
+ if (pkt)
+ {
+ if (decoder->DecodeMP2(&macontext,pkt,pktlen))
+ {
+ if ((!isTS) && (!noticeVDR_MP2))
+ {
+ dsyslog("found MP2");
+ noticeVDR_MP2=true;
+ }
+ mark=audio->Process(lastiframe);
+ AddMark(mark);
+ }
+ }
+ tspkt+=len;
+ tslen-=len;
+ }
+ }
+ }
+
+ CheckIndex(Directory);
+ if (abort)
+ {
+ if (f!=-1) close(f);
+ return false;
+ }
+ }
+ close(f);
+ return true;
+}
+
+void cMarkAdStandalone::Process(const char *Directory)
+{
+ if (abort) return;
+
+ struct timeval tv1,tv2;
+ struct timezone tz;
+
+ gettimeofday(&tv1,&tz);
+
+ if (bBackupMarks) marks.Backup(Directory,isTS);
+
+ for (int i=1; i<=MaxFiles; i++)
+ {
+ if (abort) break;
+ if (!ProcessFile(Directory,i))
+ {
+ break;
+ }
+ }
+ if (abort)
+ {
+ isyslog("aborted");
+ }
+ else
+ {
+ if (lastiframe)
+ {
+ MarkAdMark tempmark;
+ tempmark.Type=MT_COMMON;
+ tempmark.Position=lastiframe;
+ char *buf;
+
+ if (asprintf(&buf,"stop of recording (%i)",lastiframe)!=-1)
+ {
+ tempmark.Comment=buf;
+ AddMark(&tempmark);
+ free(buf);
+ }
+ }
+
+ gettimeofday(&tv2,&tz);
+ time_t sec;
+ suseconds_t usec;
+ sec=tv2.tv_sec-tv1.tv_sec;
+ usec=tv2.tv_usec-tv1.tv_usec;
+ if (usec<0)
+ {
+ usec+=1000000;
+ sec--;
+ }
+ RateMarks();
+ if (marks.Save(Directory,macontext.Video.Info.FramesPerSecond,isTS))
+ {
+ bool bIndexError=false;
+ if (marks.CheckIndex(Directory,isTS,&bIndexError))
+ {
+ if (bIndexError)
+ {
+ if ((macontext.Info.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264) && (!isTS))
+ {
+ esyslog("index doesn't match marks, sorry you're lost");
+ }
+ else
+ {
+ esyslog("index doesn't match marks%s",
+ isTS ? ", please recreate it" : ", please run genindex");
+ }
+ }
+ }
+ }
+
+ double etime,ftime=0,ptime=0;
+ etime=sec+((double) usec/1000000)-waittime;
+ if (etime>0) ftime=framecnt/etime;
+ if (macontext.Video.Info.FramesPerSecond>0)
+ ptime=ftime/macontext.Video.Info.FramesPerSecond;
+ isyslog("processed time %.2fs, %i frames, %.1f fps, %.1f pps",
+ etime,framecnt,ftime,ptime);
+
+ }
+}
+
+bool cMarkAdStandalone::LoadInfo(const char *Directory)
+{
+ char *buf;
+ if (isTS)
+ {
+ if (asprintf(&buf,"%s/info",Directory)==-1) return false;
+ }
+ else
+ {
+ if (asprintf(&buf,"%s/info.vdr",Directory)==-1) return false;
+ }
+
+ FILE *f;
+ f=fopen(buf,"r");
+ if (!f)
+ {
+ free(buf);
+ return false;
+ }
+
+ char *line=NULL;
+ size_t length;
+ while (getline(&line,&length,f)!=-1)
+ {
+ if (line[0]=='C')
+ {
+ int ntok=0;
+ char *str=line,*tok;
+ while ((tok=strtok(str," ")))
+ {
+ if (ntok==1) macontext.Info.ChannelID=strdup(tok);
+ ntok++;
+ str=NULL;
+ }
+ if (macontext.Info.ChannelID)
+ {
+ for (int i=0; i<(int) strlen(macontext.Info.ChannelID); i++)
+ {
+ if (macontext.Info.ChannelID[i]=='.') macontext.Info.ChannelID[i]='_';
+ }
+ }
+ }
+ if (line[0]=='E')
+ {
+ int result=sscanf(line,"%*c %*i %*i %i %*i %*x",&macontext.Info.Length);
+ if (result==1)
+ {
+ macontext.Info.Length/=60;
+ }
+ else
+ {
+ macontext.Info.Length=0;
+ }
+ if ((bIgnoreAudioInfo) && (bIgnoreVideoInfo)) break;
+ }
+ if (line[0]=='X')
+ {
+ int stream=0,type=0;
+ char descr[256]="";
+ int result=sscanf(line,"%*c %i %i %250c",&stream,&type,(char *) &descr);
+ if ((result!=0) && (result!=EOF))
+ {
+ if ((stream==1) && (!bIgnoreVideoInfo))
+ {
+ if ((type!=1) && (type!=5))
+ {
+ // we dont have 4:3, so ignore AspectRatio-Changes
+ macontext.Video.Options.IgnoreAspectRatio=true;
+ isyslog("broadcasts aspectratio is not 4:3, disabling aspect ratio");
+ }
+ }
+
+ if ((stream==2) && (!bIgnoreAudioInfo))
+ {
+ if (type==5)
+ {
+ // if we have DolbyDigital 2.0 disable AC3
+ if (strchr(descr,'2'))
+ {
+ macontext.Info.DPid.Num=0;
+ isyslog("broadcast with DolbyDigital2.0, disabling AC3 decoding");
+ }
+ // if we have DolbyDigital 5.1 disable video decoding
+ if (strchr(descr,'5'))
+ {
+ bDecodeVideo=false;
+ isyslog("broadcast with DolbyDigital5.1, disabling video decoding");
+ }
+
+ }
+ }
+ }
+ }
+ }
+ if (line) free(line);
+
+ fclose(f);
+ free(buf);
+ if (!macontext.Info.ChannelID)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+bool cMarkAdStandalone::CheckTS(const char *Directory)
+{
+ MaxFiles=0;
+ isTS=false;
+ if (!Directory) return false;
+ char *buf;
+ if (asprintf(&buf,"%s/00001.ts",Directory)==-1) return false;
+ struct stat statbuf;
+ if (stat(buf,&statbuf)==-1)
+ {
+ if (errno!=ENOENT)
+ {
+ free(buf);
+ return false;
+ }
+ free(buf);
+ if (asprintf(&buf,"%s/001.vdr",Directory)==-1) return false;
+ if (stat(buf,&statbuf)==-1)
+ {
+ free(buf);
+ return false;
+ }
+ free(buf);
+ // .VDR detected
+ isTS=false;
+ MaxFiles=999;
+ return true;
+ }
+ free(buf);
+ // .TS detected
+ isTS=true;
+ MaxFiles=65535;
+ return true;
+}
+
+bool cMarkAdStandalone::CheckVDRHD(const char *Directory)
+{
+ char *buf;
+ if (asprintf(&buf,"%s/001.vdr",Directory)==-1) return false;
+
+ int fd=open(buf,O_RDONLY);
+ free(buf);
+ if (fd==-1) return false;
+
+ uchar pes_buf[32];
+ if (read(fd,pes_buf,sizeof(pes_buf))!=sizeof(pes_buf))
+ {
+ close(fd);
+ return false;
+ }
+ close(fd);
+
+ if ((pes_buf[0]==0) && (pes_buf[1]==0) && (pes_buf[2]==1) && ((pes_buf[3] & 0xF0)==0xE0))
+ {
+ int payloadstart=9+pes_buf[8];
+ if (payloadstart>23) return false;
+ uchar *start=&pes_buf[payloadstart];
+ if ((start[0]==0) && (start[1]==0) && (start[2]==1) && (start[5]==0) && (start[6]==0)
+ && (start[7]==0) && (start[8]==1))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool cMarkAdStandalone::CheckPATPMT(const char *Directory)
+{
+ char *buf;
+ if (asprintf(&buf,"%s/00001.ts",Directory)==-1) return false;
+
+ int fd=open(buf,O_RDONLY);
+ free(buf);
+ if (fd==-1) return false;
+
+ uchar patpmt_buf[564];
+ uchar *patpmt;
+
+ if (read(fd,patpmt_buf,sizeof(patpmt_buf))!=sizeof(patpmt_buf))
+ {
+ close(fd);
+ return false;
+ }
+ close(fd);
+ patpmt=patpmt_buf;
+
+ if ((patpmt[0]==0x47) && ((patpmt[1] & 0x5F)==0x40) && (patpmt[2]==0x11) &&
+ ((patpmt[3] & 0x10)==0x10)) patpmt+=188; // skip SDT
+
+ // some checks
+ if ((patpmt[0]!=0x47) || (patpmt[188]!=0x47)) return false; // no TS-Sync
+ if (((patpmt[1] & 0x5F)!=0x40) && (patpmt[2]!=0)) return false; // no PAT
+ if ((patpmt[3] & 0x10)!=0x10) return false; // PAT not without AFC
+ if ((patpmt[191] & 0x10)!=0x10) return false; // PMT not without AFC
+ struct PAT *pat = (struct PAT *) &patpmt[5];
+
+ // more checks
+ if (pat->reserved1!=3) return false; // is always 11
+ if (pat->reserved3!=7) return false; // is always 111
+
+ int pid=pat->pid_L+(pat->pid_H<<8);
+ int pmtpid=((patpmt[189] & 0x1f)<<8)+patpmt[190];
+ if (pid!=pmtpid) return false; // pid in PAT differs from pid in PMT
+
+ struct PMT *pmt = (struct PMT *) &patpmt[193];
+
+ // still more checks
+ if (pmt->reserved1!=3) return false; // is always 11
+ if (pmt->reserved2!=3) return false; // is always 11
+ if (pmt->reserved3!=7) return false; // is always 111
+ if (pmt->reserved4!=15) return false; // is always 1111
+
+ if ((pmt->program_number_H!=pat->program_number_H) ||
+ (pmt->program_number_L!=pat->program_number_L)) return false;
+
+ int desc_len=(pmt->program_info_length_H<<8)+pmt->program_info_length_L;
+ if (desc_len>166) return false; // beyond patpmt buffer
+
+ int section_end = 196+(pmt->section_length_H<<8)+pmt->section_length_L;
+ section_end-=4; // we don't care about the CRC32
+ if (section_end>376) return false; //beyond patpmt buffer
+
+ int i=205+desc_len;
+
+ while (i<section_end)
+ {
+ struct ES_DESCRIPTOR *es=NULL;
+ struct STREAMINFO *si = (struct STREAMINFO *) &patpmt[i];
+ int esinfo_len=(si->ES_info_length_H<<8)+si->ES_info_length_L;
+ if (esinfo_len)
+ {
+ es = (struct ES_DESCRIPTOR *) &patpmt[i+sizeof(struct STREAMINFO)];
+ }
+
+ // oh no -> more checks!
+ if (si->reserved1!=7) return false;
+ if (si->reserved2!=15) return false;
+
+ int pid=(si->PID_H<<8)+si->PID_L;
+
+ switch (si->stream_type)
+ {
+ case 0x1:
+ case 0x2:
+ macontext.Info.VPid.Type=MARKAD_PIDTYPE_VIDEO_H262;
+ // just use the first pid
+ if (!macontext.Info.VPid.Num) macontext.Info.VPid.Num=pid;
+ break;
+
+ case 0x3:
+ case 0x4:
+ // just use the first pid
+ if (!macontext.Info.APid.Num) macontext.Info.APid.Num=pid;
+ break;
+
+ case 0x6:
+ if (es)
+ {
+ if (es->Descriptor_Tag==0x6A) macontext.Info.DPid.Num=pid;
+ }
+ break;
+
+ case 0x1b:
+ macontext.Info.VPid.Type=MARKAD_PIDTYPE_VIDEO_H264;
+ // just use the first pid
+ if (!macontext.Info.VPid.Num) macontext.Info.VPid.Num=pid;
+ break;
+ }
+
+ i+=(sizeof(struct STREAMINFO)+esinfo_len);
+ }
+
+ return true;
+}
+
+bool cMarkAdStandalone::CreatePidfile(const char *Directory)
+{
+ char *buf;
+ if (asprintf(&buf,"%s/markad.pid",Directory)==-1) return false;
+
+ // check for other running markad process
+ FILE *oldpid=fopen(buf,"r");
+ if (oldpid)
+ {
+ // found old pidfile, check if it's still running
+ int pid;
+ if (fscanf(oldpid,"%i\n",&pid)==1)
+ {
+ char procname[256]="";
+ snprintf(procname,sizeof(procname),"/proc/%i",pid);
+ struct stat statbuf;
+ if (stat(procname,&statbuf)!=-1)
+ {
+ // found another, running markad
+ isyslog("another instance is running on this recording");
+ abort=duplicate=true;
+ }
+ }
+ fclose(oldpid);
+ }
+ if (duplicate) return false;
+
+ FILE *pidfile=fopen(buf,"w+");
+
+ if (getuid()==0 || geteuid()!=0)
+ {
+ // if we are root, set fileowner to owner of 001.vdr/00001.ts file
+ char *spath=NULL;
+ if (asprintf(&spath,"%s/%s",Directory,isTS ? "00001.ts" : "001.vdr")!=-1)
+ {
+ struct stat statbuf;
+ if (!stat(spath,&statbuf))
+ {
+ chown(buf,statbuf.st_uid, statbuf.st_gid);
+ }
+ free(spath);
+ }
+ }
+
+ free(buf);
+ if (!pidfile) return false;
+ fprintf(pidfile,"%i\n",(int) getpid());
+ fflush(pidfile);
+ fclose(pidfile);
+ return true;
+}
+
+void cMarkAdStandalone::RemovePidfile(const char *Directory)
+{
+ char *buf;
+ if (asprintf(&buf,"%s/markad.pid",Directory)!=-1)
+ {
+ unlink(buf);
+ free(buf);
+ }
+}
+
+const char cMarkAdStandalone::frametypes[8]={'?','I','P','B','D','S','s','b'};
+
+cMarkAdStandalone::cMarkAdStandalone(const char *Directory, bool BackupMarks, int LogoExtraction,
+ int LogoWidth, int LogoHeight, bool DecodeVideo,
+ bool DecodeAudio, bool IgnoreVideoInfo, bool IgnoreAudioInfo,
+ const char *LogoDir, const char *MarkFileName, bool ASD,
+ bool noPid)
+{
+ directory=Directory;
+ abort=false;
+
+ noticeVDR_MP2=false;
+ noticeVDR_AC3=false;
+
+ sleepcnt=0;
+ waittime=0;
+ duplicate=false;
+ marksAligned=false;
+
+ memset(&macontext,0,sizeof(macontext));
+ macontext.LogoDir=(char *) LogoDir;
+ macontext.Options.LogoExtraction=LogoExtraction;
+ macontext.Options.LogoWidth=LogoWidth;
+ macontext.Options.LogoHeight=LogoHeight;
+ macontext.Audio.Options.AudioSilenceDetection=ASD;
+
+ bDecodeVideo=DecodeVideo;
+ bDecodeAudio=DecodeAudio;
+ bIgnoreAudioInfo=IgnoreAudioInfo;
+ bIgnoreVideoInfo=IgnoreVideoInfo;
+
+ bBackupMarks=BackupMarks;
+
+ macontext.Info.DPid.Type=MARKAD_PIDTYPE_AUDIO_AC3;
+ macontext.Info.APid.Type=MARKAD_PIDTYPE_AUDIO_MP2;
+
+ isyslog("starting v%s",VERSION);
+ isyslog("on %s",Directory);
+
+ if (!bDecodeAudio)
+ {
+ isyslog("audio decoding disabled by user");
+ }
+ if (!bDecodeVideo)
+ {
+ isyslog("video decoding disabled by user");
+ }
+ if (bIgnoreAudioInfo)
+ {
+ isyslog("audio info usage disabled by user");
+ }
+ if (bIgnoreVideoInfo)
+ {
+ isyslog("video info usage disabled by user");
+ }
+
+ if (LogoExtraction!=-1)
+ {
+ // just to be sure extraction works
+ bDecodeVideo=true;
+ bIgnoreAudioInfo=true;
+ bIgnoreVideoInfo=true;
+ }
+
+ if (!CheckTS(Directory))
+ {
+ video_demux=NULL;
+ ac3_demux=NULL;
+ mp2_demux=NULL;
+ decoder=NULL;
+ video=NULL;
+ audio=NULL;
+ return;
+ }
+
+ if (!noPid)
+ {
+ CreatePidfile(Directory);
+ if (abort)
+ {
+ video_demux=NULL;
+ ac3_demux=NULL;
+ mp2_demux=NULL;
+ decoder=NULL;
+ video=NULL;
+ audio=NULL;
+ return;
+ }
+ }
+
+ if (isTS)
+ {
+ if (!CheckPATPMT(Directory))
+ {
+ esyslog("no PAT/PMT found -> nothing to process");
+ abort=true;
+ }
+ if (!macontext.Audio.Options.AudioSilenceDetection)
+ {
+ macontext.Info.APid.Num=0;
+ }
+ if (asprintf(&indexFile,"%s/index",Directory)==-1) indexFile=NULL;
+ }
+ else
+ {
+ if (macontext.Audio.Options.AudioSilenceDetection)
+ {
+ macontext.Info.APid.Num=-1;
+ }
+ macontext.Info.DPid.Num=-1;
+ macontext.Info.VPid.Num=-1;
+
+ if (CheckVDRHD(Directory))
+ {
+ macontext.Info.VPid.Type=MARKAD_PIDTYPE_VIDEO_H264;
+ }
+ else
+ {
+ macontext.Info.VPid.Type=MARKAD_PIDTYPE_VIDEO_H262;
+ }
+ if (asprintf(&indexFile,"%s/index.vdr",Directory)==-1) indexFile=NULL;
+ }
+
+ if (!LoadInfo(Directory))
+ {
+ if (bDecodeVideo)
+ {
+ esyslog("failed loading info - logo %s disabled",
+ (LogoExtraction!=-1) ? "extraction" : "detection");
+ }
+ }
+
+ if (MarkFileName[0]) marks.SetFileName(MarkFileName);
+
+ if (macontext.Info.VPid.Num)
+ {
+ if (isTS)
+ {
+ dsyslog("using %s-video (0x%04x)",
+ macontext.Info.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264 ? "H264": "H262",
+ macontext.Info.VPid.Num);
+ }
+ else
+ {
+ dsyslog("using %s-video",
+ macontext.Info.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264 ? "H264": "H262");
+ }
+ video_demux = new cMarkAdDemux();
+ }
+ else
+ {
+ video_demux=NULL;
+ }
+
+ if (macontext.Info.APid.Num)
+ {
+ if (macontext.Info.APid.Num!=-1)
+ dsyslog("using MP2 (0x%04x)",macontext.Info.APid.Num);
+ mp2_demux = new cMarkAdDemux();
+ }
+ else
+ {
+ mp2_demux=NULL;
+ }
+
+ if (macontext.Info.DPid.Num)
+ {
+ if (macontext.Info.DPid.Num!=-1)
+ dsyslog("using AC3 (0x%04x)",macontext.Info.DPid.Num);
+ ac3_demux = new cMarkAdDemux();
+ }
+ else
+ {
+ ac3_demux=NULL;
+ }
+
+ if (!abort)
+ {
+ decoder = new cMarkAdDecoder(macontext.Info.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264,
+ macontext.Info.APid.Num!=0,macontext.Info.DPid.Num!=0);
+ video = new cMarkAdVideo(&macontext);
+ audio = new cMarkAdAudio(&macontext);
+ streaminfo = new cMarkAdStreamInfo;
+ if (macontext.Info.ChannelID)
+ dsyslog("channel %s",macontext.Info.ChannelID);
+ }
+ else
+ {
+ decoder=NULL;
+ video=NULL;
+ audio=NULL;
+ streaminfo=NULL;
+ }
+
+ framecnt=0;
+ lastiframe=0;
+ iframe=0;
+}
+
+cMarkAdStandalone::~cMarkAdStandalone()
+{
+ if (macontext.Info.ChannelID) free(macontext.Info.ChannelID);
+ if (indexFile) free(indexFile);
+
+ if (video_demux) delete video_demux;
+ if (ac3_demux) delete ac3_demux;
+ if (mp2_demux) delete mp2_demux;
+ if (decoder) delete decoder;
+ if (video) delete video;
+ if (audio) delete audio;
+ if (streaminfo) delete streaminfo;
+
+ if ((directory) && (!duplicate)) RemovePidfile(directory);
+}
+
+bool isnumber(const char *s)
+{
+ while (*s)
+ {
+ if (!isdigit(*s))
+ return false;
+ s++;
+ }
+ return true;
+}
+
+int usage()
+{
+ // nothing done, give the user some help
+ printf("Usage: markad [options] cmd <record>\n"
+ "options:\n"
+ "-b --background\n"
+ " markad runs as a background-process\n"
+ " this will be automatically set if called with \"after\"\n"
+ "-d --disable=<option>\n"
+ " <option> 1 = disable video 2 = disable audio\n"
+ " 3 = disable video and audio\n"
+ "-i --ignoreinfo=<info>\n"
+ " ignores hints from info(.vdr) file\n"
+ " <info> 1 = ignore audio info 2 = ignore video info\n"
+ " 3 = ignore video and audio info\n"
+ "-l --logocachedir\n"
+ " directory where logos stored, default /var/lib/markad\n"
+ "-p, --priority level=<priority>\n"
+ " priority-level of markad when running in background\n"
+ " <-20...19> default 19\n"
+ "-v, --verbose\n"
+ " increments loglevel by one, can be given multiple times\n"
+ "-B --backupmarks\n"
+ " make a backup of existing marks\n"
+ "-L --extractlogo=<direction>[,width[,height]]\n"
+ " extracts logo to /tmp as pgm files (must be renamed)\n"
+ " <direction> 0 = top left, 1 = top right\n"
+ " 2 = bottom left, 3 = bottom right\n"
+ " [width] range from 50 to %3i, default %3i (SD)\n"
+ " default %3i (HD)\n"
+ " [height] range from 20 to %3i, default %3i\n"
+ "-O, --OSD\n"
+ " markad sends an OSD-Message for start and end\n"
+ "-V --version\n"
+ " print version-info and exit\n"
+ " --asd\n"
+ " enable audio silence detecion\n"
+ " --markfile=<markfilename>\n"
+ " set a different markfile-name\n"
+ " --online[=1|2] (default is 1)\n"
+ " start markad immediately when called with \"before\" as cmd\n"
+ " if online is 1, markad starts online for live-recordings\n"
+ " only, online=2 starts markad online for every recording\n"
+ " live-recordings are identified by having a '@' in the\n"
+ " filename so the entry 'Mark instant recording' in the menu\n"
+ " 'Setup - Recording' of the vdr should be set to 'yes'\n"
+ "\ncmd: one of\n"
+ "- dummy-parameter if called directly\n"
+ "after markad starts to analyze the recording\n"
+ "before markad exits immediately if called with \"before\"\n"
+ "edited markad exits immediately if called with \"edited\"\n"
+ "nice runs markad with nice(19)\n"
+ "\n<record> is the name of the directory where the recording\n"
+ " is stored\n\n",
+ LOGO_MAXWIDTH,LOGO_DEFWIDTH,LOGO_DEFHDWIDTH,
+ LOGO_MAXHEIGHT,LOGO_DEFHEIGHT
+ );
+ return -1;
+}
+
+void signal_handler(int sig)
+{
+ if (sig==SIGUSR1)
+ {
+ // TODO: what we are supposed to do?
+ }
+ else
+ {
+ if (cmasta)
+ {
+ cmasta->SetAbort();
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+ bool bAfter=false,bBefore=false,bEdited=false;
+ bool bFork=false,bNice=false,bImmediateCall=false;
+ bool bASD=false;
+ int niceLevel = 19;
+ char *recDir=NULL;
+ char *tok,*str;
+ int ntok;
+ int logoExtraction=-1;
+ int logoWidth=-1;
+ int logoHeight=-1;
+ bool bBackupMarks=false,bNoPid=false;
+ char markFileName[1024]="";
+ char logoDirectory[1024]="";
+ bool bDecodeVideo=true;
+ bool bDecodeAudio=true;
+ bool bIgnoreAudioInfo=false;
+ bool bIgnoreVideoInfo=false;
+ int online=1;
+
+ strcpy(logoDirectory,"/var/lib/markad");
+
+ while (1)
+ {
+ int option_index = 0;
+ static struct option long_options[] =
+ {
+ {"ac3",0,0,'a'
+ },
+ {"background", 0, 0, 'b'},
+ {"comments", 0, 0, 'c'},
+ {"disable", 1, 0, 'd'},
+ {"ignoreinfo", 1, 0, 'i' },
+ {"jumplogo",0,0,'j'},
+ {"logocachedir", 1, 0, 'l'},
+ {"nelonen",0,0,'n'},
+ {"overlap",0,0,'o' },
+ {"priority",1,0,'p'},
+ {"statisticfile",1,0,'s'},
+ {"verbose", 0, 0, 'v'},
+
+ {"asd",0,0,6},
+ {"loglevel",1,0,2},
+ {"markfile",1,0,1},
+ {"nopid",0,0,5},
+ {"online",2,0,4},
+ {"pass3only",0,0,7},
+ {"testmode",0,0,3},
+
+ {"backupmarks", 0, 0, 'B'},
+ {"scenechangedetection", 0, 0, 'C'},
+ {"extractlogo", 1, 0, 'L'},
+ {"OSD",0,0,'O' },
+ {"savelogo", 0, 0, 'S'},
+ {"version", 0, 0, 'V'},
+
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long (argc, argv, "abcd:i:jl:nop:s:vBCL:O:SV",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+
+ case 'a':
+ // --ac3
+ break;
+
+ case 'b':
+ // --background
+ bFork = SYSLOG = true;
+ break;
+
+ case 'c':
+ // --comments
+ break;
+
+ case 'd':
+ // --disable
+ switch (atoi(optarg))
+ {
+ case 1:
+ bDecodeVideo=false;
+ break;
+ case 2:
+ bDecodeAudio=false;
+ break;
+ case 3:
+ bDecodeVideo=false;
+ bDecodeAudio=false;
+ break;
+ default:
+ fprintf(stderr, "markad: invalid disable option: %s\n", optarg);
+ return 2;
+ break;
+ }
+ break;
+
+ case 'i':
+ // --ignoreinfo
+ switch (atoi(optarg))
+ {
+ case 1:
+ bIgnoreAudioInfo=true;
+ break;
+ case 2:
+ bIgnoreVideoInfo=true;
+ break;
+ case 3:
+ bIgnoreVideoInfo=true;
+ bIgnoreAudioInfo=true;
+ break;
+ default:
+ fprintf(stderr, "markad: invalid ignoreinfo option: %s\n", optarg);
+ return 2;
+ break;
+ }
+ break;
+
+ case 'j':
+ // --jumplogo
+ break;
+
+ case 'l':
+ strncpy(logoDirectory,optarg,1024);
+ logoDirectory[1023]=0;
+ break;
+
+ case 'n':
+ // --nelonen
+ break;
+
+ case 'o':
+ // --overlap
+ break;
+
+ case 'p':
+ // --priority
+ if (isnumber(optarg) || *optarg=='-')
+ niceLevel = atoi(optarg);
+ else
+ {
+ fprintf(stderr, "markad: invalid priority level: %s\n", optarg);
+ return 2;
+ }
+ bNice = true;
+ break;
+
+ case 's':
+ // --statisticfile
+ break;
+
+ case 'v':
+ // --verbose
+ SysLogLevel++;
+ if (SysLogLevel>10) SysLogLevel=10;
+ break;
+
+ case 'B':
+ // --backupmarks
+ bBackupMarks=true;
+ break;
+
+ case 'C':
+ // --scenechangedetection
+ break;
+
+ case 'L':
+ // --extractlogo
+ str=optarg;
+ ntok=0;
+ while (tok=strtok(str,","))
+ {
+ switch (ntok)
+ {
+ case 0:
+ logoExtraction=atoi(tok);
+ if ((logoExtraction<0) || (logoExtraction>3))
+ {
+ fprintf(stderr, "markad: invalid extractlogo value: %s\n", tok);
+ return 2;
+ }
+ break;
+
+ case 1:
+ logoWidth=atoi(tok);
+ if ((logoWidth<50) || (logoWidth>LOGO_MAXWIDTH))
+ {
+ fprintf(stderr, "markad: invalid width value: %s\n", tok);
+ return 2;
+ }
+ break;
+
+ case 2:
+ logoHeight=atoi(tok);
+ if ((logoHeight<20) || (logoHeight>LOGO_MAXHEIGHT))
+ {
+ fprintf(stderr, "markad: invalid height value: %s\n", tok);
+ return 2;
+ }
+ break;
+
+ default:
+ break;
+ }
+ str=NULL;
+ ntok++;
+ }
+ break;
+
+ case 'O':
+ // --OSD
+ break;
+
+ case 'S':
+ // --savelogo
+ break;
+
+ case 'V':
+ printf("markad %s - marks advertisements in VDR recordings\n",VERSION);
+ return 0;
+
+ case '?':
+ printf("unknow option ?\n");
+ break;
+
+ case 0:
+ printf ("option %s", long_options[option_index].name);
+ if (optarg)
+ printf (" with arg %s", optarg);
+ printf ("\n");
+ break;
+
+ case 1: // --markfile
+ strncpy(markFileName,optarg,1024);
+ markFileName[1023]=0;
+ break;
+
+ case 2: // --loglevel
+ SysLogLevel=atoi(optarg);
+ if (SysLogLevel>10) SysLogLevel=10;
+ if (SysLogLevel<0) SysLogLevel=2;
+ break;
+
+ case 3: // --testmode
+ break;
+
+ case 4: // --online
+ online=atoi(optarg);
+ if ((online!=1) && (online!=2))
+ {
+ fprintf(stderr, "markad: invalid online value: %s\n", optarg);
+ return 2;
+ }
+ break;
+
+ case 5: // --nopid
+ bNoPid=true;
+ break;
+
+ case 6: // --asd
+ bASD=true;
+ break;
+
+ case 7: // --pass3only
+ break;
+
+ default:
+ printf ("? getopt returned character code 0%o ? (option_index %d)\n", c,option_index);
+ }
+ }
+
+ if (optind < argc)
+ {
+ while (optind < argc)
+ {
+ if (strcmp(argv[optind], "after" ) == 0 )
+ {
+ bAfter = bFork = bNice = SYSLOG = true;
+ }
+ else if (strcmp(argv[optind], "before" ) == 0 )
+ {
+ bBefore = bFork = bNice = SYSLOG = true;
+ }
+ else if (strcmp(argv[optind], "edited" ) == 0 )
+ {
+ bEdited = true;
+ }
+ else if (strcmp(argv[optind], "nice" ) == 0 )
+ {
+ bNice = true;
+ }
+ else if (strcmp(argv[optind], "-" ) == 0 )
+ {
+ bImmediateCall = true;
+ }
+ else
+ {
+ if ( strstr(argv[optind],".rec") != NULL )
+ recDir = argv[optind];
+ }
+ optind++;
+ }
+ }
+
+ // do nothing if called from vdr before/after the video is cutted
+ if ((bAfter) && (online)) return 0;
+ if (bBefore)
+ {
+ if (!online) return 0;
+ if ((online==1) && (!strchr(recDir,'@'))) return 0;
+ }
+ if (bEdited) return 0;
+
+ // we can run, if one of bImmediateCall, bAfter, bBefore or bNice is true
+ // and recDir is given
+ if ( (bImmediateCall || bBefore || bAfter || bNice) && recDir )
+ {
+ // if bFork is given go in background
+ if ( bFork )
+ {
+ (void)umask((mode_t)0011);
+ //close_files();
+ pid_t pid = fork();
+ if (pid < 0)
+ {
+ char *err=strerror(errno);
+ fprintf(stderr, "%s\n",err);
+ esyslog("fork ERROR: %s",err);
+ return 2;
+ }
+ if (pid != 0)
+ {
+ isyslog("forked to pid %d",pid);
+ return 0; // initial program immediately returns
+ }
+ }
+ if ( bFork )
+ {
+ isyslog("(forked) pid %d", getpid());
+ if (chdir("/")==-1)
+ {
+ perror("chdir");
+ exit(EXIT_FAILURE);
+ }
+ if (setsid() == (pid_t)(-1))
+ {
+ perror("setsid");
+ exit(EXIT_FAILURE);
+ }
+ if (signal(SIGHUP, SIG_IGN) == SIG_ERR)
+ {
+ perror("signal(SIGHUP, SIG_IGN)");
+ errno = 0;
+ }
+ int f;
+
+ f = open("/dev/null", O_RDONLY);
+ if (f == -1)
+ {
+ perror("/dev/null");
+ errno = 0;
+ }
+ else
+ {
+ if (dup2(f, fileno(stdin)) == -1)
+ {
+ perror("dup2");
+ errno = 0;
+ }
+ (void)close(f);
+ }
+
+ f = open("/dev/null", O_WRONLY);
+ if (f == -1)
+ {
+ perror("/dev/null");
+ errno = 0;
+ }
+ else
+ {
+ if (dup2(f, fileno(stdout)) == -1)
+ {
+ perror("dup2");
+ errno = 0;
+ }
+ if (dup2(f, fileno(stderr)) == -1)
+ {
+ perror("dup2");
+ errno = 0;
+ }
+ (void)close(f);
+ }
+ }
+
+ int MaxPossibleFileDescriptors = getdtablesize();
+ for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
+ close(i); //close all dup'ed filedescriptors
+
+ // should we renice ?
+ if ( bNice )
+ {
+ if (setpriority(PRIO_PROCESS,0,niceLevel)==-1)
+ {
+ esyslog("failed to set nice to %d",niceLevel);
+ }
+ }
+
+ if (bBefore) sleep(10);
+
+ // now do the work...
+ struct stat statbuf;
+ if (stat(recDir,&statbuf)==-1)
+ {
+ fprintf(stderr,"%s not found\n",recDir);
+ return -1;
+ }
+
+ if (!S_ISDIR(statbuf.st_mode))
+ {
+ fprintf(stderr,"%s is not a directory\n",recDir);
+ return -1;
+ }
+
+ cmasta = new cMarkAdStandalone(recDir,bBackupMarks, logoExtraction, logoWidth, logoHeight,
+ bDecodeVideo,bDecodeAudio,bIgnoreVideoInfo,bIgnoreAudioInfo,
+ logoDirectory,markFileName,bASD,bNoPid);
+ if (!cmasta) return -1;
+
+ // ignore some signals
+ signal(SIGHUP, SIG_IGN);
+
+ // catch some signals
+ signal(SIGINT, signal_handler);
+ signal(SIGTERM, signal_handler);
+ signal(SIGUSR1, signal_handler);
+
+ cmasta->Process(recDir);
+ delete cmasta;
+ return 0;
+ }
+
+ return usage();
+}
diff --git a/command/markad-standalone.h b/command/markad-standalone.h
new file mode 100644
index 0000000..01f381a
--- /dev/null
+++ b/command/markad-standalone.h
@@ -0,0 +1,214 @@
+/*
+ * markad-standalone.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __markad_standalone_h_
+#define __markad_standalone_h_
+
+#include <syslog.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include "demux.h"
+#include "global.h"
+#include "decoder.h"
+#include "video.h"
+#include "audio.h"
+#include "streaminfo.h"
+#include "version.h"
+#include "marks.h"
+
+class cMarkAdStandalone
+{
+private:
+
+ struct PAT
+ {
+unsigned table_id:
+ 8;
+unsigned section_length_H:
+ 4;
+unsigned reserved1:
+ 2;
+unsigned zero:
+ 1;
+unsigned section_syntax_indicator:
+ 1;
+unsigned section_length_L:
+ 8;
+unsigned transport_stream_id_H:
+ 8;
+unsigned transport_stream_id_L:
+ 8;
+unsigned current_next_indicator:
+ 1;
+unsigned version_number:
+ 5;
+unsigned reserved2:
+ 2;
+unsigned section_number:
+ 8;
+unsigned last_section_number:
+ 8;
+unsigned program_number_H:
+ 8;
+unsigned program_number_L:
+ 8;
+unsigned pid_H:
+ 5;
+unsigned reserved3:
+ 3;
+unsigned pid_L:
+ 8;
+ };
+
+ struct PMT
+ {
+unsigned table_id:
+ 8;
+unsigned section_length_H:
+ 4;
+unsigned reserved1:
+ 2;
+unsigned zero:
+ 1;
+unsigned section_syntax_indicator:
+ 1;
+unsigned section_length_L:
+ 8;
+unsigned program_number_H:
+ 8;
+unsigned program_number_L:
+ 8;
+unsigned current_next_indicator:
+ 1;
+unsigned version_number:
+ 5;
+unsigned reserved2:
+ 2;
+unsigned section_number:
+ 8;
+unsigned last_section_number:
+ 8;
+unsigned PCR_PID_H:
+ 5;
+unsigned reserved3:
+ 3;
+unsigned PCR_PID_L:
+ 8;
+unsigned program_info_length_H:
+ 4;
+unsigned reserved4:
+ 4;
+unsigned program_info_length_L:
+ 8;
+ };
+
+#pragma pack(1)
+ struct STREAMINFO
+ {
+unsigned stream_type:
+ 8;
+unsigned PID_H:
+ 5;
+unsigned reserved1:
+ 3;
+unsigned PID_L:
+ 8;
+unsigned ES_info_length_H:
+ 4;
+unsigned reserved2:
+ 4;
+unsigned ES_info_length_L:
+ 8;
+ };
+#pragma pack()
+
+ struct ES_DESCRIPTOR
+ {
+unsigned Descriptor_Tag:
+ 8;
+unsigned Descriptor_Length:
+ 8;
+ };
+
+ static const char frametypes[8];
+ const char *directory;
+
+ cMarkAdDemux *video_demux;
+ cMarkAdDemux *ac3_demux;
+ cMarkAdDemux *mp2_demux;
+ cMarkAdDecoder *decoder;
+ cMarkAdVideo *video;
+ cMarkAdAudio *audio;
+ cMarkAdStreamInfo *streaminfo;
+
+ MarkAdContext macontext;
+
+ bool CreatePidfile(const char *Directory);
+ void RemovePidfile(const char *Directory);
+ bool duplicate; // are we a dup?
+
+ bool isTS;
+ int MaxFiles;
+ int lastiframe;
+ int iframe;
+ int framecnt;
+ bool abort;
+ int waittime;
+
+ bool noticeVDR_MP2;
+ bool noticeVDR_AC3;
+
+ bool bDecodeVideo;
+ bool bDecodeAudio;
+ bool bIgnoreAudioInfo;
+ bool bIgnoreVideoInfo;
+
+ void CheckIndex(const char *Directory);
+ char *indexFile;
+ int sleepcnt;
+
+ void SaveFrame(int Frame);
+
+ bool marksAligned;
+ bool bBackupMarks;
+ clMarks marks;
+ char *IndexToHMSF(int Index);
+ bool CheckFirstMark();
+ void AddStartMark();
+ void AddMark(MarkAdMark *Mark);
+ void RateMarks();
+
+ bool CheckVDRHD(const char *Directory);
+ bool CheckPATPMT(const char *Directory);
+ bool CheckTS(const char *Directory);
+ bool LoadInfo(const char *Directory);
+ bool ProcessFile(const char *Directory, int Number);
+
+public:
+ void SetAbort()
+ {
+ abort=true;
+ }
+ void Process(const char *Directory);
+ cMarkAdStandalone(const char *Directory, bool BackupMarks, int LogoExtraction,
+ int LogoWidth, int LogoHeight, bool DecodeVideo,
+ bool DecodeAudio, bool IgnoreVideoInfo, bool IgnoreAudioInfo,
+ const char *LogoDir, const char *MarkFileName, bool ASD,
+ bool noPid);
+
+ ~cMarkAdStandalone();
+};
+
+#endif
diff --git a/command/marks.cpp b/command/marks.cpp
new file mode 100644
index 0000000..543ed0d
--- /dev/null
+++ b/command/marks.cpp
@@ -0,0 +1,373 @@
+/*
+ * marks.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "marks.h"
+
+clMark::clMark(int Type, int Position, const char *Comment)
+{
+ type=Type;
+ position=Position;
+ if (Comment)
+ {
+ comment=strdup(Comment);
+ }
+ else
+ {
+ comment=NULL;
+ }
+ prev=NULL;
+ next=NULL;
+}
+
+clMark::~clMark()
+{
+ if (comment) free(comment);
+}
+
+// --------------------------------------------------------------------------
+
+clMarks::~clMarks()
+{
+ clMark *next,*mark=first;
+ while (mark)
+ {
+ next=mark->Next();
+ Del(mark);
+ mark=next;
+ }
+
+}
+
+int clMarks::Count(int Type)
+{
+ if (Type==0xFF) return count;
+
+ if (!first) return 0;
+
+ int ret=0;
+ clMark *mark=first;
+ while (mark)
+ {
+ if (mark->type==Type) ret++;
+ mark=mark->Next();
+ }
+ return ret;
+}
+
+void clMarks::Del(int Type)
+{
+ if (!first) return; // no elements yet
+
+ clMark *next,*mark=first;
+ while (mark)
+ {
+ next=mark->Next();
+ if (mark->type==Type) Del(mark);
+ mark=next;
+ }
+}
+
+void clMarks::Del(clMark *Mark)
+{
+ if (!Mark) return;
+
+ if (first==Mark)
+ {
+ // we are the first mark
+ first=Mark->Next();
+ }
+ else
+ {
+ if (Mark->Next() && (Mark->Prev()))
+ {
+ // there is a next and prev object
+ Mark->Prev()->SetNext(Mark->Next());
+ Mark->Next()->SetPrev(Mark->Prev());
+ }
+ else
+ {
+ // we are the last
+ Mark->Prev()->SetNext(NULL);
+ }
+ }
+ delete Mark;
+ count--;
+}
+
+clMark *clMarks::Get(int Position)
+{
+ if (!first) return NULL; // no elements yet
+
+ clMark *mark=first;
+ while (mark)
+ {
+ if (Position==mark->position) break;
+ mark=mark->Next();
+ }
+ return mark;
+}
+
+clMark *clMarks::GetPrev(int Position, int Type)
+{
+ if (!first) return NULL; // no elements yet
+
+ clMark *mark=first;
+ while (mark)
+ {
+ if (Type==0xFF)
+ {
+ if (mark->position>=Position) break;
+ }
+ else
+ {
+ if ((mark->position>=Position) && (mark->type==Type)) break;
+ }
+ mark=mark->Next();
+ }
+ if (mark) return mark->Prev();
+ return last;
+}
+
+clMark *clMarks::GetNext(int Position, int Type)
+{
+ if (!first) return NULL; // no elements yet
+ clMark *mark=first;
+ while (mark)
+ {
+ if (Type==0xFF)
+ {
+ if (mark->position>=Position) break;
+ }
+ else
+ {
+ if ((mark->position>=Position) && (mark->type==Type)) break;
+ }
+ mark=mark->Next();
+ }
+ return mark->Next();
+}
+
+clMark *clMarks::Add(int Type, int Position,const char *Comment)
+{
+ clMark *newmark;
+ if ((newmark=Get(Position)))
+ {
+ if ((newmark->comment) && (Comment))
+ {
+ free(newmark->comment);
+ newmark->comment=strdup(Comment);
+ }
+ newmark->type=Type;
+ return newmark;
+ }
+
+ newmark=new clMark(Type, Position,Comment);
+ if (!newmark) return NULL;
+
+ if (!first)
+ {
+ //first element
+ first=last=newmark;
+ count++;
+ return newmark;
+ }
+ else
+ {
+ clMark *mark=first;
+ while (mark)
+ {
+ if (!mark->Next())
+ {
+ if (Position>mark->position)
+ {
+ // add as last element
+ newmark->Set(mark,NULL);
+ mark->SetNext(newmark);
+ last=newmark;
+ break;
+ }
+ else
+ {
+ // add before
+ if (!mark->Prev())
+ {
+ // add as first element
+ newmark->Set(NULL,mark);
+ mark->SetPrev(newmark);
+ first=newmark;
+ break;
+ }
+ else
+ {
+ newmark->Set(mark->Prev(),mark);
+ mark->SetPrev(newmark);
+ break;
+ }
+ }
+ }
+ else
+ {
+ if ((Position>mark->position) && (Position<mark->Next()->position))
+ {
+ // add between two marks
+ newmark->Set(mark,mark->Next());
+ mark->SetNext(newmark);
+ break;
+ }
+ }
+ mark=mark->Next();
+ }
+ if (!mark)return NULL;
+ count++;
+ return newmark;
+ }
+ return NULL;
+}
+
+char *clMarks::IndexToHMSF(int Index, double FramesPerSecond)
+{
+ if (FramesPerSecond==0.0) return NULL;
+ char *buf=NULL;
+ double Seconds;
+ int f = int(modf((Index+0.5)/FramesPerSecond,&Seconds)*FramesPerSecond+1);
+ int s = int(Seconds);
+ int m = s / 60 % 60;
+ int h = s / 3600;
+ s %= 60;
+ if (asprintf(&buf,"%d:%02d:%02d.%02d",h,m,s,f)==-1) return NULL;
+ return buf;
+}
+
+bool clMarks::CheckIndex(const char *Directory, bool isTS, bool *IndexError)
+{
+ if (!IndexError) return false;
+ *IndexError=false;
+
+ if (!first) return true;
+
+ char *ipath=NULL;
+ if (asprintf(&ipath,"%s/index%s",Directory,isTS ? "" : ".vdr")==-1) return false;
+
+ int fd=open(ipath,O_RDONLY);
+ free(ipath);
+ if (fd==-1) return false;
+
+ clMark *mark=first;
+ while (mark)
+ {
+ if (isTS)
+ {
+ off_t offset = mark->position * sizeof(struct tIndexTS);
+ if (lseek(fd,offset,SEEK_SET)!=offset)
+ {
+ *IndexError=true;
+ break;
+ }
+ struct tIndexTS IndexTS;
+ if (read(fd,&IndexTS,sizeof(IndexTS))!=sizeof(IndexTS))
+ {
+ *IndexError=true;
+ break;
+ }
+ if (!IndexTS.independent)
+ {
+ *IndexError=true;
+ break;
+ }
+ }
+ else
+ {
+ off_t offset = mark->position * sizeof(struct tIndexVDR);
+ if (lseek(fd,offset,SEEK_SET)!=offset)
+ {
+ *IndexError=true;
+ break;
+ }
+ struct tIndexVDR IndexVDR;
+ if (read(fd,&IndexVDR,sizeof(IndexVDR))!=sizeof(IndexVDR))
+ {
+ *IndexError=true;
+ break;
+ }
+ if (IndexVDR.type!=1)
+ {
+ *IndexError=true;
+ break;
+ }
+ }
+ mark=mark->Next();
+ }
+
+ return true;
+}
+
+bool clMarks::Backup(const char *Directory, bool isTS)
+{
+ char *fpath=NULL;
+ if (asprintf(&fpath,"%s/%s%s",Directory,filename,isTS ? "" : ".vdr")==-1) return false;
+
+ // make backup of old marks, filename convention taken from noad
+ char *bpath=NULL;
+ if (asprintf(&bpath,"%s/%s0%s",Directory,filename,isTS ? "" : ".vdr")==-1)
+ {
+ free(fpath);
+ return false;
+ }
+
+ int ret=rename(fpath,bpath);
+ free(bpath);
+ free(fpath);
+ return (ret==0);
+}
+
+bool clMarks::Save(const char *Directory, double FrameRate, bool isTS)
+{
+ if (!first) return false;
+ if (savedcount==count) return false;
+
+ char *fpath=NULL;
+ if (asprintf(&fpath,"%s/%s%s",Directory,filename,isTS ? "" : ".vdr")==-1) return false;
+
+ FILE *mf;
+ mf=fopen(fpath,"w+");
+
+ if (!mf)
+ {
+ free(fpath);
+ return false;
+ }
+
+ clMark *mark=first;
+ while (mark)
+ {
+ char *buf=IndexToHMSF(mark->position,FrameRate);
+ if (buf)
+ {
+ fprintf(mf,"%s %s\n",buf,mark->comment ? mark->comment : "");
+ free(buf);
+ }
+ mark=mark->Next();
+ }
+ fclose(mf);
+
+ if (getuid()==0 || geteuid()!=0)
+ {
+ // if we are root, set fileowner to owner of 001.vdr/00001.ts file
+ char *spath=NULL;
+ if (asprintf(&spath,"%s/%s",Directory,isTS ? "00001.ts" : "001.vdr")!=-1)
+ {
+ struct stat statbuf;
+ if (!stat(spath,&statbuf))
+ {
+ chown(fpath,statbuf.st_uid, statbuf.st_gid);
+ }
+ free(spath);
+ }
+ }
+ free(fpath);
+ return true;
+}
diff --git a/command/marks.h b/command/marks.h
new file mode 100644
index 0000000..611f6b7
--- /dev/null
+++ b/command/marks.h
@@ -0,0 +1,119 @@
+/*
+ * marks.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __marks_h_
+#define __marks_h_
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <fcntl.h>
+
+class clMark
+{
+private:
+ clMark *next;
+ clMark *prev;
+public:
+ int type;
+ int position;
+ char *comment;
+ clMark(int Type=0, int Position = 0, const char *Comment = NULL);
+ ~clMark();
+ clMark *Next()
+ {
+ return next;
+ };
+ clMark *Prev()
+ {
+ return prev;
+ };
+ void Set(clMark *Prev, clMark *Next)
+ {
+ prev=Prev;
+ next=Next;
+ }
+ void SetNext(clMark *Next)
+ {
+ next=Next;
+ }
+ void SetPrev(clMark *Prev)
+ {
+ prev=Prev;
+ }
+};
+
+class clMarks
+{
+private:
+ struct tIndexVDR
+ {
+ int offset;
+ unsigned char type;
+ unsigned char number;
+ short reserved;
+ };
+
+ struct tIndexTS
+ {
+uint64_t offset:
+ 40;
+int reserved:
+ 7;
+int independent:
+ 1;
+uint16_t number:
+ 16;
+ };
+
+ char filename[1024];
+ clMark *first,*last;
+ char *IndexToHMSF(int Index, double FramesPerSecond);
+ int count;
+ int savedcount;
+public:
+ clMarks()
+ {
+ strcpy(filename,"marks");
+ first=last=NULL;
+ savedcount=0;
+ count=0;
+ }
+ ~clMarks();
+ int Count(int Type=0xFF);
+ void SetFileName(const char *FileName)
+ {
+ if (FileName)
+ {
+ strncpy(filename,FileName,sizeof(filename)-1);
+ filename[sizeof(filename)-1]=0;
+ }
+ }
+ clMark *Add(int Type, int Position, const char *Comment = NULL);
+ void Del(clMark *Mark);
+ void Del(int Type);
+ clMark *Get(int Position);
+ clMark *GetPrev(int Position,int Type=0xFF);
+ clMark *GetNext(int Position,int Type=0xFF);
+ clMark *GetFirst()
+ {
+ return first;
+ }
+ clMark *GetLast()
+ {
+ return last;
+ }
+ bool Backup(const char *Directory, bool isTS);
+ bool Save(const char *Directory, double FrameRate, bool isTS);
+ bool CheckIndex(const char *Directory, bool isTS, bool *IndexError);
+};
+
+#endif
diff --git a/command/pes2es.cpp b/command/pes2es.cpp
new file mode 100644
index 0000000..64565a7
--- /dev/null
+++ b/command/pes2es.cpp
@@ -0,0 +1,100 @@
+/*
+ * pes2es.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "pes2es.h"
+
+cMarkAdPES2ES::cMarkAdPES2ES(const char *QueueName, int QueueSize)
+{
+ queue = new cMarkAdPaketQueue(QueueName,QueueSize);
+ type=0;
+}
+
+cMarkAdPES2ES::~cMarkAdPES2ES()
+{
+ if (queue) delete queue;
+}
+
+void cMarkAdPES2ES::Reset()
+{
+ queue->Clear();
+}
+
+void cMarkAdPES2ES::Process(MarkAdPid Pid, uchar *PESData, int PESSize, uchar **ESData, int *ESSize)
+{
+ if ((!ESData) || (!ESSize) || (!queue)) return;
+ *ESData=NULL;
+ *ESSize=0;
+
+ if (PESData)
+ {
+ struct PESHDR *peshdr=(struct PESHDR *) PESData;
+
+ // first check some simple things
+ if ((peshdr->Sync1!=0) && (peshdr->Sync2!=0) && (peshdr->Sync3!=1))
+ {
+ Reset();
+ return;
+ }
+
+ if (peshdr->StreamID<=0xBC) return;
+
+ int Length=(peshdr->LenH<<8)+peshdr->LenL;
+ if (Length) Length+=sizeof(PESHDR);
+ if (Length!=PESSize)
+ {
+ if ((peshdr->StreamID & 0xF0)==0xE0) return;
+ Reset();
+ return;
+ }
+
+ switch (Pid.Type)
+ {
+ case MARKAD_PIDTYPE_VIDEO_H262:
+ if ((peshdr->StreamID & 0xF0)!=0xE0) return;
+ type=MA_PACKET_PKT;
+ break;
+ case MARKAD_PIDTYPE_VIDEO_H264:
+ if ((peshdr->StreamID & 0xF0)!=0xE0) return;
+ type=MA_PACKET_H264;
+ break;
+ case MARKAD_PIDTYPE_AUDIO_AC3:
+ if (peshdr->StreamID!=0xBD) return;
+ type=MA_PACKET_AC3;
+ break;
+ case MARKAD_PIDTYPE_AUDIO_MP2:
+ if ((peshdr->StreamID<0xC0) || (peshdr->StreamID>0xDF)) return;
+ type=MA_PACKET_MP2;
+ break;
+ default:
+ Reset();
+ return;
+ }
+
+ struct PESHDROPT *peshdropt=(struct PESHDROPT *) &PESData[sizeof(struct PESHDR)];
+
+ uchar *buf;
+ int buflen;
+
+ if (peshdropt->MarkerBits==0x2)
+ {
+ // we have an optional PES header
+ int bpos=sizeof(struct PESHDR)+sizeof(struct PESHDROPT)+
+ peshdropt->Length;
+ buf=&PESData[bpos];
+ buflen=PESSize-bpos;
+ }
+ else
+ {
+ int bpos=sizeof(struct PESHDR);
+ buf=&PESData[bpos];
+ buflen=PESSize-bpos;
+ }
+ queue->Put(buf,buflen);
+ }
+ if (type) *ESData=queue->GetPacket(ESSize,type);
+ return;
+}
diff --git a/command/pes2es.h b/command/pes2es.h
new file mode 100644
index 0000000..6b7c6bc
--- /dev/null
+++ b/command/pes2es.h
@@ -0,0 +1,78 @@
+/*
+ * pes2es.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __pes2es_h_
+#define __pes2es_h_
+
+#ifndef uchar
+typedef unsigned char uchar;
+#endif
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "global.h"
+#include "queue.h"
+
+class cMarkAdPES2ES
+{
+private:
+ struct PESHDR
+ {
+ uchar Sync1;
+ uchar Sync2;
+ uchar Sync3;
+ uchar StreamID;
+ uchar LenH;
+ uchar LenL;
+ };
+
+#pragma pack(1)
+ struct PESHDROPT
+ {
+unsigned OOC:
+ 1;
+unsigned CY:
+ 1;
+unsigned DAI:
+ 1;
+unsigned PESP:
+ 1;
+unsigned PESSC:
+ 2;
+unsigned MarkerBits:
+ 2;
+unsigned EXT:
+ 1;
+unsigned CRC:
+ 1;
+unsigned ACI:
+ 1;
+unsigned TM:
+ 1;
+unsigned RATE:
+ 1;
+unsigned ESCR:
+ 1;
+unsigned TSF:
+ 2;
+unsigned Length:
+ 8;
+ };
+#pragma pack()
+
+ cMarkAdPaketQueue *queue;
+ int type;
+ void Reset();
+public:
+ cMarkAdPES2ES(const char *QueueName="PES2ES", int QueueSize=32768);
+ ~cMarkAdPES2ES();
+ void Process(MarkAdPid Pid, uchar *PESData, int PESSize, uchar **ESData, int *ESSize);
+};
+
+#endif
diff --git a/command/queue.cpp b/command/queue.cpp
new file mode 100644
index 0000000..a50e925
--- /dev/null
+++ b/command/queue.cpp
@@ -0,0 +1,451 @@
+/*
+ * queue.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "queue.h"
+
+cMarkAdPaketQueue::cMarkAdPaketQueue(const char *Name, int Size)
+{
+ inptr=0;
+ outptr=0;
+ memset(&pktinfo,0,sizeof(pktinfo));
+ pktinfo.pkthdr=-1;
+ maxqueue=Size;
+ if (Name)
+ {
+ name=strdup(Name);
+ }
+ else
+ {
+ name=NULL;
+ }
+ buffer=(uchar *) malloc(Size+1);
+ if (!buffer) maxqueue=0;
+ scanner=0xFFFFFFFF;
+ scannerstart=-1;
+ percent=-1;
+ mpercent=0;
+}
+
+cMarkAdPaketQueue::~cMarkAdPaketQueue()
+{
+ if (name)
+ {
+ tsyslog("buffer usage: %-15s %3i%%",name,mpercent);
+ free(name);
+ }
+ if (buffer) free(buffer);
+}
+
+bool cMarkAdPaketQueue::Inject(uchar *Data, int Size)
+{
+ if (!buffer) return false;
+ isyslog("inject was called, please report this");
+
+ if (outptr>Size)
+ {
+ uchar *temp=(uchar *) alloca(Size+1);
+ if (!temp) return false;
+ memcpy(temp,Data,Size);
+ outptr-=Size;
+ memcpy(&buffer[outptr],temp,Size);
+ pktinfo.pkthdr=-1;
+ }
+ else
+ {
+ int oldSize=Length();
+ uchar *tempold=(uchar *) alloca(oldSize+1);
+ if (!tempold) return false;
+ uchar *temp=(uchar *) alloca(Size+1);
+ if (!temp) return false;
+
+ memcpy(tempold,&buffer[outptr],oldSize);
+ memcpy(temp,Data,Size);
+ memcpy(buffer,temp,Size);
+ memcpy(buffer+Size,tempold,oldSize);
+
+ inptr=Size+oldSize;
+ outptr=0;
+ pktinfo.pkthdr=-1;
+ }
+ return true;
+}
+
+bool cMarkAdPaketQueue::Put(uchar *Data, int Size)
+{
+ if (!buffer) return false;
+ if ((inptr) && (inptr==outptr)) inptr=outptr=0;
+
+ if ((inptr+Size)>maxqueue)
+ {
+ if (name)
+ {
+ esyslog("buffer %s full",name);
+ }
+ else
+ {
+ esyslog("buffer full");
+ }
+ Clear();
+ return false;
+ }
+
+ memcpy(&buffer[inptr],Data,Size);
+ inptr+=Size;
+
+ int npercent=(int) ((inptr*100)/maxqueue);
+ if (npercent>mpercent) mpercent=npercent;
+
+ if ((npercent>90) && (name) && (npercent!=percent))
+ {
+ dsyslog("buffer %s usage: %3i%%",
+ name,npercent);
+ percent=npercent;
+ }
+
+ return true;
+}
+
+uchar *cMarkAdPaketQueue::Get(int *Size)
+{
+ if (!buffer) return NULL;
+ if (!Size) return NULL;
+ if (Length()<*Size)
+ {
+ *Size=0;
+ return NULL;
+ }
+ uchar *ret=&buffer[outptr];
+ outptr+=*Size;
+ return ret;
+}
+
+int cMarkAdPaketQueue::FindPktHeader(int Start, int *StreamSize,int *HeaderSize, bool LongStartCode)
+{
+ if ((!StreamSize) || (!HeaderSize)) return -1;
+ if (!Start) Start=outptr;
+ if (Start>=inptr) return -1;
+ *StreamSize=0;
+ if (LongStartCode)
+ {
+ *HeaderSize=4; // 0x0 0x0 0x0 0x1
+ }
+ else
+ {
+ *HeaderSize=3; // 0x0 0x0 0x1
+ }
+ int i;
+
+ if (scanner!=0xFFFFFFFF)
+ {
+ scanner<<=8;
+ scanner|=buffer[Start++];
+ }
+
+ for (i=Start; i<inptr; i++)
+ {
+ if (LongStartCode)
+ {
+ if (scanner==1L) break;
+ if ((scanner & 0xFFFFFFF0)==0x1E0L) break;
+ }
+ else
+ {
+ if ((scanner & 0x00FFFFFF)==1L) break;
+ }
+ scanner<<=8;
+ scanner|=buffer[i];
+ }
+
+ if (i==inptr) return -1;
+ if (LongStartCode) i--;
+ if (buffer[i]>=0xBC)// do we have a PES packet?
+ {
+#define PESHDRSIZE 6
+ if ((i+PESHDRSIZE)>inptr)
+ {
+ return -1; // we need more data (for streamsize and headersize)
+ }
+
+ *StreamSize=(buffer[i+1]<<8)+buffer[i+2];
+ if (*StreamSize) (*StreamSize)+=PESHDRSIZE; // 6 Byte PES-Header
+ if (LongStartCode)
+ {
+ struct PESHDROPT *peshdropt=(struct PESHDROPT *) &buffer[i+3];
+ if (peshdropt->MarkerBits==0x2)
+ {
+ *HeaderSize=PESHDRSIZE+sizeof(struct PESHDROPT)+
+ peshdropt->Length;
+ }
+ else
+ {
+ *HeaderSize=PESHDRSIZE;
+ }
+ }
+ }
+
+ return i-3;
+}
+
+int cMarkAdPaketQueue::FindAudioHeader(int Start, int *FrameSize, int *HeaderSize, bool AC3)
+{
+ if ((!FrameSize) || (!HeaderSize)) return -1;
+ if (!Start) Start=outptr;
+ if (Start>=inptr) return -1;
+ (*FrameSize)=0;
+ if (AC3)
+ {
+ (*HeaderSize)=2;
+ }
+ else
+ {
+ (*HeaderSize)=3;
+ }
+ int i;
+
+ if (scanner!=0xFFFFFFFF)
+ {
+ scanner<<=8;
+ scanner|=buffer[Start++];
+ }
+
+ for (i=Start; i<inptr; i++)
+ {
+
+ if (AC3)
+ {
+ if ((scanner & 0x0000FFFF)==0xB77L) break;
+ }
+ else
+ {
+ if ((scanner & 0x00000FFE)==0xFFEL) break;
+ }
+
+ scanner<<=8;
+ scanner|=buffer[i];
+ }
+ if (i==inptr) return -1;
+ if (AC3) i-=2;
+
+ if (AC3)
+ {
+ struct AC3HDR *ac3hdr = (struct AC3HDR *) &buffer[i];
+
+ if (ac3hdr->SampleRateIndex==3) return -1; // reserved
+ if (ac3hdr->FrameSizeIndex>=38) return -1; // reserved
+
+ if (FrameSize)
+ {
+ int bitRatesAC3[3][38] = // all values are specified as kbits/s
+ {
+ { 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 }, // 48kHz
+
+ { 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 }, // 44.1kHz
+
+ { 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 } // 32kHz
+ };
+
+ *FrameSize=2*bitRatesAC3[ac3hdr->SampleRateIndex][ac3hdr->FrameSizeIndex];
+ }
+ return i;
+ }
+ else
+ {
+ struct MP2HDR *mp2hdr = (struct MP2HDR *) &buffer[i];
+ if (mp2hdr->MpegID==1) return -1; // reserved
+ if (mp2hdr->Layer==0) return -1; // reserved
+ if (mp2hdr->BitRateIndex==0xF) return -1; // forbidden
+ if (mp2hdr->SampleRateIndex==3) return -1; //reserved
+ if (mp2hdr->Emphasis==2) return -1; // reserved
+
+ if (FrameSize)
+ {
+ int bitRates[3][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 }, // M1, L1
+ { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, // M1, L2
+ { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 } // M1, L3
+ },
+ {
+ { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, // M2, L1
+ { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // M2, L2
+ { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 } // M2, L3
+ },
+ {
+ { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, // M2.5, L1
+ { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // M2.5, L2
+ { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 } // M2.5, L3
+ }
+ };
+
+ int samplingFrequencies[3][4] = // all values are specified in Hz
+ {
+ { 44100, 48000, 32000, -1 }, // MPEG 1
+ { 22050, 24000, 16000, -1 }, // MPEG 2
+ { 32000, 16000, 8000, -1 } // MPEG 2.5
+ };
+
+
+ int slots_per_frame[3][3] =
+ {
+ { 12, 144, 144 }, // MPEG 1, Layer I, II, III
+ { 12, 144, 72 }, // MPEG 2, Layer I, II, III
+ { 12, 144, 72 } // MPEG 2.5, Layer I, II, III
+ };
+
+ int mpegIndex;
+ switch (mp2hdr->MpegID)
+ {
+ case 0:
+ mpegIndex=2;
+ break;
+ case 2:
+ mpegIndex=1;
+ break;
+ case 3:
+ mpegIndex=0;
+ break;
+ default:
+ mpegIndex=0; // just to get rid of compiler warnings ;)
+ }
+ int layerIndex = 3 - mp2hdr->Layer;
+
+ // Layer I (i. e., layerIndex == 0) has a larger slot size
+ int slotSize = (layerIndex == 0) ? 4 : 1; // bytes
+ int sf = samplingFrequencies[mpegIndex][mp2hdr->SampleRateIndex];
+
+ if (mp2hdr->BitRateIndex == 0)
+ *FrameSize = 0; // "free" Bitrate -> we don't support this!
+ else
+ {
+ int br = 1000 * bitRates[mpegIndex][layerIndex][mp2hdr->BitRateIndex]; // bits/s
+ int N = slots_per_frame[mpegIndex][layerIndex] * br / sf; // slots
+
+ *FrameSize = (N + mp2hdr->Padding) * slotSize; // bytes
+ }
+ }
+ return i;
+ }
+}
+
+uchar *cMarkAdPaketQueue::GetPacket(int *Size, int Type)
+{
+ if (!Size) return NULL;
+ *Size=0;
+ if (Length()<4) return NULL;
+
+ if ((Type==MA_PACKET_H264) && (pktinfo.pktsyncsize>5) && (pktinfo.pkthdr!=-1))
+ {
+ // ignore PES paket
+ pktinfo.pkthdr=-1;
+ outptr+=pktinfo.pktsyncsize;
+ }
+
+ if (pktinfo.pkthdr==-1)
+ {
+ scanner=0xFFFFFFFF;
+ switch (Type)
+ {
+ case MA_PACKET_AC3:
+ pktinfo.pkthdr=FindAudioHeader(0,&pktinfo.streamsize,&pktinfo.pktsyncsize, true);
+ break;
+ case MA_PACKET_MP2:
+ pktinfo.pkthdr=FindAudioHeader(0,&pktinfo.streamsize,&pktinfo.pktsyncsize, false);
+ break;
+ case MA_PACKET_H264:
+ pktinfo.pkthdr=FindPktHeader(0,&pktinfo.streamsize,&pktinfo.pktsyncsize,true);
+ if (pktinfo.pktsyncsize>5)
+ {
+ // ignore PES paket
+ pktinfo.pkthdr=-1;
+ outptr+=pktinfo.pktsyncsize;
+ }
+ break;
+ default:
+ pktinfo.pkthdr=FindPktHeader(0,&pktinfo.streamsize,&pktinfo.pktsyncsize,false);
+ break;
+ }
+
+ if (pktinfo.pkthdr==-1)
+ {
+ return NULL;
+ }
+ scannerstart=pktinfo.pkthdr+pktinfo.pktsyncsize;
+ }
+
+ int streamsize,pktsyncsize,pkthdr=-1;
+
+ if (pktinfo.streamsize)
+ {
+ if ((pktinfo.pkthdr+pktinfo.streamsize)>inptr)
+ {
+ return NULL; // need more data
+ }
+ else
+ {
+ scannerstart=pktinfo.pkthdr+pktinfo.streamsize;
+ scanner=0xFFFFFFFF;
+ }
+ }
+
+ switch (Type)
+ {
+ case MA_PACKET_AC3:
+ pkthdr=FindAudioHeader(scannerstart,&streamsize,&pktsyncsize, true);
+ break;
+ case MA_PACKET_MP2:
+ pkthdr=FindAudioHeader(scannerstart,&streamsize,&pktsyncsize, false);
+ break;
+ case MA_PACKET_H264:
+ pkthdr=FindPktHeader(scannerstart,&streamsize,&pktsyncsize, true);
+ break;
+ default:
+ pkthdr=FindPktHeader(scannerstart,&streamsize,&pktsyncsize, false);
+ break;
+ }
+
+ if (pkthdr==-1)
+ {
+ scannerstart=inptr;
+ return NULL;
+ }
+ scannerstart=pkthdr+pktsyncsize;
+
+ uchar *ptr=&buffer[pktinfo.pkthdr];
+
+ if (pktinfo.streamsize)
+ {
+ *Size=pktinfo.streamsize;
+ }
+ else
+ {
+ *Size=pkthdr-pktinfo.pkthdr;
+ }
+ outptr=pkthdr;
+
+ int bytesleft=inptr-outptr;
+ if (pktinfo.pkthdr>(4096+bytesleft))
+ {
+ memcpy(buffer,&buffer[pkthdr],bytesleft);
+ scannerstart-=outptr;
+ inptr=bytesleft;
+ outptr=0;
+ pkthdr=0;
+ }
+
+ pktinfo.pkthdr=pkthdr;
+ pktinfo.streamsize=streamsize;
+ pktinfo.pktsyncsize=pktsyncsize;
+
+ return ptr;
+}
diff --git a/command/queue.h b/command/queue.h
new file mode 100644
index 0000000..20cac32
--- /dev/null
+++ b/command/queue.h
@@ -0,0 +1,159 @@
+/*
+ * queue.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __queue_h_
+#define __queue_h_
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#ifndef uchar
+typedef unsigned char uchar;
+#endif
+
+extern "C"
+{
+#include "debug.h"
+}
+
+class cMarkAdPaketQueue
+{
+ struct MP2HDR
+ {
+unsigned Sync1:
+ 8;
+unsigned Protection:
+ 1;
+unsigned Layer:
+ 2;
+unsigned MpegID:
+ 2;
+unsigned Sync2:
+ 3;
+unsigned Private:
+ 1;
+unsigned Padding:
+ 1;
+unsigned SampleRateIndex:
+ 2;
+unsigned BitRateIndex:
+ 4;
+unsigned Emphasis:
+ 2;
+unsigned Original:
+ 1;
+unsigned Copyright:
+ 1;
+unsigned ModeExt:
+ 2;
+unsigned Mode:
+ 2;
+ };
+
+#pragma pack(1)
+ struct AC3HDR
+ {
+unsigned Sync1:
+ 8;
+unsigned Sync2:
+ 8;
+unsigned CRC1:
+ 8;
+unsigned CRC2:
+ 8;
+unsigned FrameSizeIndex:
+ 6;
+unsigned SampleRateIndex:
+ 2;
+ };
+#pragma pack()
+
+#pragma pack(1)
+ struct PESHDROPT
+ {
+unsigned OOC:
+ 1;
+unsigned CY:
+ 1;
+unsigned DAI:
+ 1;
+unsigned PESP:
+ 1;
+unsigned PESSC:
+ 2;
+unsigned MarkerBits:
+ 2;
+unsigned EXT:
+ 1;
+unsigned CRC:
+ 1;
+unsigned ACI:
+ 1;
+unsigned TM:
+ 1;
+unsigned RATE:
+ 1;
+unsigned ESCR:
+ 1;
+unsigned TSF:
+ 2;
+unsigned Length:
+ 8;
+ };
+#pragma pack()
+
+private:
+ char *name;
+ struct pktinfo
+ {
+ int pkthdr;
+ int pktsyncsize;
+ int streamsize;
+ bool ispes;
+ } pktinfo;
+
+ int percent;
+ int mpercent;
+
+ uchar *buffer;
+ int maxqueue;
+ int inptr;
+ int outptr;
+
+ uint32_t scanner;
+ int scannerstart;
+
+ int FindPktHeader(int Start, int *StreamSize,int *SyncSize, bool LongStartCode);
+ int FindAudioHeader(int Start, int *FrameSize, int *SyncSize, bool AC3);
+public:
+ cMarkAdPaketQueue(const char *Name, int Size=32768);
+ ~cMarkAdPaketQueue();
+ int Length()
+ {
+ return inptr-outptr;
+ }
+ void Clear()
+ {
+ inptr=outptr=0;
+ pktinfo.pkthdr=-1;
+ scanner=0xFFFFFFFF;
+ scannerstart=-1;
+ }
+ bool Inject(uchar *Data, int Size);
+ bool Put(uchar *Data, int Size);
+ uchar *Get(int *Size);
+
+#define MA_PACKET_PKT 0x10 // 0x00 0x00 0x01 (PES / H262)
+#define MA_PACKET_H264 0x11 // 0x00 0x00 0x00 0x01 (H264)
+#define MA_PACKET_AC3 0x20
+#define MA_PACKET_MP2 0x30
+
+ uchar *GetPacket(int *Size, int Type);
+};
+
+#endif
diff --git a/command/streaminfo.cpp b/command/streaminfo.cpp
new file mode 100644
index 0000000..fb2bc9c
--- /dev/null
+++ b/command/streaminfo.cpp
@@ -0,0 +1,793 @@
+/*
+ * streaminfo.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "streaminfo.h"
+
+cMarkAdStreamInfo::cMarkAdStreamInfo()
+{
+ memset(&H264,0,sizeof(H264));
+ H264.frame_num=-1;
+}
+
+bool cMarkAdStreamInfo::FindAC3AudioInfos(MarkAdContext *maContext, uchar *espkt, int eslen)
+{
+#pragma pack(1)
+ struct AC3HDR
+ {
+unsigned Sync1:
+ 8;
+unsigned Sync2:
+ 8;
+unsigned CrcH:
+ 8;
+unsigned CrcL:
+ 8;
+unsigned FrameSizeIndex:
+ 6;
+unsigned SampleRateIndex:
+ 2;
+unsigned BsMod:
+ 3;
+unsigned BsID:
+ 5;
+unsigned LFE_Mix_VarField:
+ 5;
+unsigned AcMod:
+ 3;
+ };
+#pragma pack()
+ if ((!maContext) || (!espkt)) return false;
+ if (eslen<(int) sizeof(struct AC3HDR)) return false;
+
+ struct AC3HDR *ac3hdr = (struct AC3HDR *) espkt;
+
+ if ((ac3hdr->Sync1==0x0b) && (ac3hdr->Sync2==0x77))
+ {
+ // some extra checks
+ if (ac3hdr->SampleRateIndex==3) return false; // reserved
+ if (ac3hdr->FrameSizeIndex>=38) return false; // reserved
+
+ maContext->Audio.Info.Channels=0;
+ unsigned int lfe_bitmask = 0x0;
+
+ switch (ac3hdr->AcMod)
+ {
+ case 0:
+ maContext->Audio.Info.Channels=2;
+ lfe_bitmask=0x10;
+ break;
+ case 1:
+ maContext->Audio.Info.Channels=1;
+ lfe_bitmask=0x10;
+ break;
+ case 2:
+ maContext->Audio.Info.Channels=2;
+ lfe_bitmask=0x4;
+ break;
+ case 3:
+ maContext->Audio.Info.Channels=3;
+ lfe_bitmask=0x4;
+ break;
+ case 4:
+ maContext->Audio.Info.Channels=3;
+ lfe_bitmask=0x4;
+ break;
+ case 5:
+ maContext->Audio.Info.Channels=4;
+ lfe_bitmask=0x1;
+ break;
+ case 6:
+ maContext->Audio.Info.Channels=4;
+ lfe_bitmask=0x4;
+ break;
+ case 7:
+ maContext->Audio.Info.Channels=5;
+ lfe_bitmask=0x1;
+ break;
+ }
+
+ if ((ac3hdr->LFE_Mix_VarField & lfe_bitmask)==lfe_bitmask)
+ maContext->Audio.Info.Channels++;
+
+ return true;
+ }
+ return false;
+}
+
+bool cMarkAdStreamInfo::FindVideoInfos(MarkAdContext *maContext, uchar *pkt, int len)
+{
+ if ((!maContext) || (!pkt) || (!len)) return false;
+ if (!maContext->Info.VPid.Type) return false;
+
+ switch (maContext->Info.VPid.Type)
+ {
+ case MARKAD_PIDTYPE_VIDEO_H264:
+ return FindH264VideoInfos(maContext, pkt, len);
+ break;
+ case MARKAD_PIDTYPE_VIDEO_H262:
+ return FindH262VideoInfos(maContext, pkt, len);
+ break;
+ }
+ return false;
+}
+
+bool cMarkAdStreamInfo::FindH264VideoInfos(MarkAdContext *maContext, uchar *pkt, int len)
+{
+ if ((!maContext) || (!pkt) || (!len)) return false;
+
+ int nalu=pkt[4] & 0x1F;
+
+ if (nalu==NAL_AUD)
+ {
+ if (pkt[5]==0x10)
+ {
+ H264.primary_pic_typeI=true;
+ }
+ }
+
+ if (nalu==NAL_SPS)
+ {
+ uint8_t *nal_data=(uint8_t*) alloca(len);
+ if (!nal_data) return false;
+ int nal_len = nalUnescape(nal_data, pkt + 5, len - 5);
+ cBitStream bs(nal_data, nal_len);
+
+ int profile_idc, pic_order_cnt_type, i, j;
+
+ uint32_t width=0;
+ uint32_t height=0;
+ uint32_t aspect_ratio_idc=0;
+ double frame_rate=0;
+
+ profile_idc = bs.getU8(); // profile_idc
+ bs.skipBits(8); // constraint_setN_flags and reserved_zero_Nbits
+ bs.skipBits(8); // level_idc
+ bs.skipUeGolomb(); // seq_parameter_set_id
+
+ if ((profile_idc == 100) || (profile_idc == 110) || (profile_idc == 122) || (profile_idc == 244) ||
+ (profile_idc==44) || (profile_idc==83) || (profile_idc==86))
+ {
+ if (bs.getUeGolomb() == 3) // chroma_format_idc
+ bs.skipBit(); // separate_colour_plane_flag
+ bs.skipUeGolomb(); // bit_depth_luma_minus8
+ bs.skipUeGolomb(); // bit_depth_chroma_minus8
+ bs.skipBit(); // qpprime_y_zero_transform_bypass_flag
+ if (bs.getBit()) // seq_scaling_matrix_present_flag
+ {
+ for (i = 0; i < 8; ++i)
+ {
+ if (bs.getBit()) // seq_scaling_list_present_flag[i]
+ {
+ int last = 8, next = 8, size = (i < 6) ? 16 : 64;
+ for (j = 0; j < size; ++j)
+ {
+ if (next)
+ next = (last + bs.getSeGolomb()) & 0xff;
+ last = next ? next : last;
+ }
+ }
+ }
+ }
+ }
+ H264.log2_max_frame_num=bs.getUeGolomb()+4; // log2_max_frame_num_minus4
+ pic_order_cnt_type = bs.getUeGolomb(); // pic_order_cnt_type
+ if (pic_order_cnt_type == 0)
+ bs.skipUeGolomb(); // log2_max_pic_order_cnt_lsb_minus4
+ else if (pic_order_cnt_type == 1)
+ {
+ bs.skipBit(); // delta_pic_order_always_zero
+ bs.skipSeGolomb(); // offset_for_non_ref_pic
+ bs.skipSeGolomb(); // offset_for_top_to_bottom_field
+ j = bs.getUeGolomb(); // num_ref_frames_in_pic_order_cnt_cycle
+ for (i = 0; i < j; ++i)
+ bs.skipSeGolomb(); // offset_for_ref_frame[i]
+ }
+ bs.skipUeGolomb(); // max num_ref_frames
+ bs.skipBit(); // gaps_in_frame_num_value_allowed_flag
+ width = bs.getUeGolomb() + 1; // pic_width_in_mbs_minus1
+ height = bs.getUeGolomb() + 1; // pic_height_in_mbs_minus1
+ bool frame_mbs_only_flag = bs.getBit(); // frame_mbs_only_flag
+ width *= 16;
+ height *= 16 * (frame_mbs_only_flag ? 1 : 2);
+ if (!frame_mbs_only_flag)
+ bs.skipBit(); // mb_adaptive_frame_field_flag
+ bs.skipBit(); // direct_8x8_inference_flag
+ if (bs.getBit()) // frame_cropping_flag
+ {
+ uint32_t crop_left, crop_right, crop_top, crop_bottom;
+ crop_left = bs.getUeGolomb(); // frame_crop_left_offset
+ crop_right = bs.getUeGolomb(); // frame_crop_rigth_offset
+ crop_top = bs.getUeGolomb(); // frame_crop_top_offset
+ crop_bottom = bs.getUeGolomb(); // frame_crop_bottom_offset
+ width -= 2 * (crop_left + crop_right);
+ if (frame_mbs_only_flag)
+ height -= 2 * (crop_top + crop_bottom);
+ else
+ height -= 4 * (crop_top + crop_bottom);
+ }
+ // VUI parameters
+ if (bs.getBit()) // vui_parameters_present_flag
+ {
+ if (bs.getBit()) // aspect_ratio_info_present
+ {
+ aspect_ratio_idc = bs.getU8(); // aspect_ratio_idc
+ if (aspect_ratio_idc == 255) // extended sar
+ {
+ bs.skipBits(16); // sar_width
+ bs.skipBits(16); // sar_height
+ }
+ }
+ if (bs.getBit()) // overscan_info_present_flag
+ bs.skipBit(); // overscan_approriate_flag
+ if (bs.getBit()) // video_signal_type_present_flag
+ {
+ bs.skipBits(3); // video_format
+ bs.skipBit(); // video_full_range_flag
+ if (bs.getBit()) // colour_description_present_flag
+ {
+ bs.skipBits(8); // colour_primaries
+ bs.skipBits(8); // transfer_characteristics
+ bs.skipBits(8); // matrix_coefficients
+ }
+ }
+ if (bs.getBit()) // chroma_loc_info_present_flag
+ {
+ bs.skipUeGolomb(); // chroma_sample_loc_type_top_field
+ bs.skipUeGolomb(); // chroma_sample_loc_type_bottom_field
+ }
+ if (bs.getBit()) // timing_info_present_flag
+ {
+ uint32_t num_units_in_tick, time_scale;
+ num_units_in_tick = bs.getU32(); // num_units_in_tick
+ time_scale = bs.getU32(); // time_scale
+ if (num_units_in_tick > 0)
+ {
+ frame_rate = time_scale / (2*num_units_in_tick);
+ if (frame_mbs_only_flag) frame_rate/=2;
+ }
+ //bs.skipBit(); // fixed_frame_rate_flag
+ }
+ /*
+ int nal_hrd_parameters_present_flag = bs.getBit(); // nal_hrd_parameters_present_flag
+ if (nal_hrd_parameters_present_flag)
+ {
+ int cpb_cnt_minus1;
+ cpb_cnt_minus1 = bs.getUeGolomb(); // cpb_cnt_minus1
+ bs.skipBits(4); // bit_rate_scale
+ bs.skipBits(4); // cpb_size_scale
+ for (int i = 0; i <= cpb_cnt_minus1; i++)
+ {
+ bs.skipUeGolomb(); // bit_rate_value_minus1[i]
+ bs.skipUeGolomb(); // cpb_size_value_minus1[i]
+ bs.skipBit(); // cbr_flag[i]
+ }
+ bs.skipBits(5); // initial_cpb_removal_delay_length_minus1
+ bs.skipBits(5); // cpb_removal_delay_length_minus1
+ bs.skipBits(5); // dpb_output_delay_length_minus1
+ bs.skipBits(5); // time_offset_length
+ }
+ int vlc_hrd_parameters_present_flag = bs.getBit(); // vlc_hrd_parameters_present_flag
+ if (vlc_hrd_parameters_present_flag)
+ {
+ int cpb_cnt_minus1;
+ cpb_cnt_minus1 = bs.getUeGolomb(); // cpb_cnt_minus1
+ bs.skipBits(4); // bit_rate_scale
+ bs.skipBits(4); // cpb_size_scale
+ for (int i = 0; i <= cpb_cnt_minus1; i++)
+ {
+ bs.skipUeGolomb(); // bit_rate_value_minus1[i]
+ bs.skipUeGolomb(); // cpb_size_value_minus1[i]
+ bs.skipBit(); // cbr_flag[i]
+ }
+ bs.skipBits(5); // initial_cpb_removal_delay_length_minus1
+ bs.skipBits(5); // cpb_removal_delay_length_minus1
+ bs.skipBits(5); // dpb_output_delay_length_minus1
+ bs.skipBits(5); // time_offset_length
+ }
+ cpb_dpb_delays_present_flag = (nal_hrd_parameters_present_flag | vlc_hrd_parameters_present_flag);
+ if (cpb_dpb_delays_present_flag)
+ bs.skipBit(); // low_delay_hrd_flag
+ bs.skipBit(); // pic_struct_present_flag
+ if (bs.getBit()) // bitstream_restriction_flag
+ {
+ bs.skipBit(); // motion_vectors_over_pic_boundaries_flag
+ bs.skipUeGolomb(); // max_bytes_per_pic_denom
+ bs.skipUeGolomb(); // max_bits_per_mb_denom
+ bs.skipUeGolomb(); // log2_max_mv_length_horizontal
+ bs.skipUeGolomb(); // log2_max_mv_length_vertical
+ bs.skipUeGolomb(); // num_reorder_frames
+ bs.skipUeGolomb(); // max_dec_frame_buffering
+ }
+ */
+ }
+
+ if ((bs.getIndex() / 8)>0)
+ {
+ // set values
+ maContext->Video.Info.Interlaced=!frame_mbs_only_flag;
+ maContext->Video.Info.FramesPerSecond=frame_rate;
+ maContext->Video.Info.Width=width;
+ maContext->Video.Info.Height=height;
+
+ switch (aspect_ratio_idc)
+ {
+ case 1:
+ maContext->Video.Info.AspectRatio.Num=1;
+ maContext->Video.Info.AspectRatio.Den=1;
+
+ if (height==1080)
+ {
+ if (width==1920)
+ {
+ maContext->Video.Info.AspectRatio.Num=16;
+ maContext->Video.Info.AspectRatio.Den=9;
+ }
+ }
+
+ if (height==720)
+ {
+ if (width==960)
+ {
+ maContext->Video.Info.AspectRatio.Num=4;
+ maContext->Video.Info.AspectRatio.Den=3;
+ }
+
+ if (width==1280)
+ {
+ maContext->Video.Info.AspectRatio.Num=16;
+ maContext->Video.Info.AspectRatio.Den=9;
+ }
+ }
+ break;
+ case 2:
+ maContext->Video.Info.AspectRatio.Num=12;
+ maContext->Video.Info.AspectRatio.Den=31;
+ break;
+ case 3:
+ maContext->Video.Info.AspectRatio.Num=10;
+ maContext->Video.Info.AspectRatio.Den=11;
+ break;
+ case 4:
+ maContext->Video.Info.AspectRatio.Num=16;
+ maContext->Video.Info.AspectRatio.Den=11;
+ break;
+ case 5:
+ maContext->Video.Info.AspectRatio.Num=40;
+ maContext->Video.Info.AspectRatio.Den=33;
+ break;
+ case 6:
+ maContext->Video.Info.AspectRatio.Num=24;
+ maContext->Video.Info.AspectRatio.Den=11;
+ break;
+ case 7:
+ maContext->Video.Info.AspectRatio.Num=20;
+ maContext->Video.Info.AspectRatio.Den=11;
+ break;
+ case 8:
+ maContext->Video.Info.AspectRatio.Num=32;
+ maContext->Video.Info.AspectRatio.Den=11;
+ break;
+ case 9:
+ maContext->Video.Info.AspectRatio.Num=80;
+ maContext->Video.Info.AspectRatio.Den=33;
+ break;
+ case 10:
+ maContext->Video.Info.AspectRatio.Num=18;
+ maContext->Video.Info.AspectRatio.Den=11;
+ break;
+ case 11:
+ maContext->Video.Info.AspectRatio.Num=15;
+ maContext->Video.Info.AspectRatio.Den=11;
+ break;
+ case 12:
+ maContext->Video.Info.AspectRatio.Num=64;
+ maContext->Video.Info.AspectRatio.Den=33;
+ break;
+ case 13:
+ maContext->Video.Info.AspectRatio.Num=160;
+ maContext->Video.Info.AspectRatio.Den=99;
+ break;
+ case 14:
+ maContext->Video.Info.AspectRatio.Num=4;
+ maContext->Video.Info.AspectRatio.Den=3;
+ break;
+ case 15:
+ maContext->Video.Info.AspectRatio.Num=3;
+ maContext->Video.Info.AspectRatio.Den=2;
+ break;
+ case 16:
+ maContext->Video.Info.AspectRatio.Num=2;
+ maContext->Video.Info.AspectRatio.Den=1;
+ break;
+ }
+ }
+ }
+
+ if ((nalu==NAL_SLICE) || (nalu==NAL_IDR_SLICE))
+ {
+ uint8_t *nal_data=(uint8_t*) alloca(len);
+ if (!nal_data) return false;
+ int nal_len = nalUnescape(nal_data, pkt + 5, len - 5);
+ cBitStream bs(nal_data, nal_len);
+
+ bs.skipUeGolomb(); // first_mb_in_slice
+ int slice_type=bs.getUeGolomb();
+ bs.skipUeGolomb(); // pic_parameter_set_id
+ if (H264.separate_colour_plane_flag)
+ {
+ bs.skipBits(2); // colour_plane_id
+ }
+ int frame_num=bs.getBits(H264.log2_max_frame_num); // frame_num
+ if (H264.frame_num==-1) H264.frame_num=frame_num;
+
+ /*
+ if (maContext->Video.Info.Interlaced)
+ {
+ bool field_pic_flag=bs.getBit();
+ if (field_pic_flag)
+ {
+ bool bottom_field_flag=bs.getBit();
+ }
+ }
+ */
+ switch (slice_type)
+ {
+ case 0:
+ case 5:
+ slice_type=MA_P_TYPE;
+ break;
+
+ case 1:
+ case 6:
+ slice_type=MA_B_TYPE;
+ break;
+
+ case 2:
+ case 7:
+ slice_type=MA_I_TYPE;
+ break;
+
+ case 3:
+ case 8:
+ slice_type=MA_SP_TYPE;
+ break;
+
+ case 4:
+ case 9:
+ slice_type=MA_SI_TYPE;
+ break;
+
+ default:
+ break;
+ }
+
+ maContext->Video.Info.Pict_Type=slice_type;
+
+ if (!maContext->Video.Info.Interlaced)
+ {
+ if (frame_num!=H264.frame_num)
+ {
+ if (H264.primary_pic_typeI)
+ {
+ maContext->Video.Info.Pict_Type=MA_I_TYPE;
+ H264.primary_pic_typeI=false;
+ }
+ H264.frame_num=frame_num;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+const uint8_t *cMarkAdStreamInfo::nextStartCode(const uint8_t *start, const uint8_t *end)
+{
+ for (end -= 4; start < end; ++start)
+ {
+ if ((start[0] == 0x00) && (start[1] == 0x00) && (start[2] == 0x00) && (start[3] == 0x01))
+ return start;
+ }
+ return (end + 4);
+}
+
+bool cMarkAdStreamInfo::FindH262VideoInfos(MarkAdContext *maContext, uchar *pkt, int len)
+{
+ if ((!maContext) || (!pkt) || (!len)) return false;
+
+ struct H262_SequenceHdr
+ {
+unsigned Sync1:
+ 8;
+unsigned Sync2:
+ 8;
+unsigned Sync3:
+ 8;
+unsigned Sync4:
+ 8;
+unsigned WidthH:
+ 8;
+unsigned HeightH:
+ 4;
+unsigned WidthL:
+ 4;
+unsigned HeightL:
+ 8;
+unsigned FrameRateIndex:
+ 4;
+unsigned AspectRatioIndex:
+ 4;
+ };
+
+ struct H262_PictureHdr
+ {
+unsigned Sync1:
+ 8;
+unsigned Sync2:
+ 8;
+unsigned Sync3:
+ 8;
+unsigned Sync4:
+ 8;
+unsigned TemporalReferenceH:
+ 8;
+unsigned VBVDelay:
+ 3;
+unsigned CodingType:
+ 3;
+unsigned TemporalReferenceL:
+ 8;
+ };
+
+ struct H262_SequenceExt
+ {
+unsigned Sync1:
+ 8;
+unsigned Sync2:
+ 8;
+unsigned Sync3:
+ 8;
+unsigned Sync4:
+ 8;
+unsigned Profile:
+ 4;
+unsigned StartCode:
+ 4;
+unsigned WidthExtH:
+ 1;
+unsigned Chroma:
+ 2;
+unsigned Progressive:
+ 1;
+unsigned Level:
+ 4;
+unsigned BitRateExtH:
+ 5;
+unsigned HightExt:
+ 2;
+unsigned WidthExtL:
+ 1;
+unsigned Marker:
+ 1;
+unsigned BitRateExtL:
+ 7;
+ };
+
+ struct H262_SequenceExt *seqext = (struct H262_SequenceExt *) pkt;
+ struct H262_SequenceHdr *seqhdr = (struct H262_SequenceHdr *) pkt;
+ struct H262_PictureHdr *pichdr = (struct H262_PictureHdr *) pkt;
+
+ if (pichdr->Sync1==0 && pichdr->Sync2==0 && pichdr->Sync3==1 && pichdr->Sync4==0)
+ {
+ if (maContext->Video.Info.Height==0) return false;
+
+ switch (pichdr->CodingType)
+ {
+ case 1:
+ maContext->Video.Info.Pict_Type=MA_I_TYPE;
+ break;
+ case 2:
+ maContext->Video.Info.Pict_Type=MA_P_TYPE;
+ break;
+ case 3:
+ maContext->Video.Info.Pict_Type=MA_B_TYPE;
+ break;
+ case 4:
+ maContext->Video.Info.Pict_Type=MA_D_TYPE;
+ break;
+ default:
+ return false;
+ break;
+ }
+ return true;
+ }
+
+ if (seqext->Sync1==0 && seqext->Sync2==0 && seqext->Sync3==1 && seqext->Sync4==0xb5)
+ {
+ maContext->Video.Info.Interlaced=!seqext->Progressive;
+ }
+
+ if (seqhdr->Sync1==0 && seqhdr->Sync2==0 && seqhdr->Sync3==1 && seqhdr->Sync4==0xb3)
+ {
+
+ maContext->Video.Info.Height=(seqhdr->HeightH<<8)+seqhdr->HeightL;
+ maContext->Video.Info.Width=(seqhdr->WidthH<<4)+seqhdr->WidthL;
+
+ switch (seqhdr->AspectRatioIndex)
+ {
+ case 1:
+ maContext->Video.Info.AspectRatio.Num=1;
+ maContext->Video.Info.AspectRatio.Den=1;
+ break;
+ case 2:
+ maContext->Video.Info.AspectRatio.Num=4;
+ maContext->Video.Info.AspectRatio.Den=3;
+ break;
+ case 3:
+ maContext->Video.Info.AspectRatio.Num=16;
+ maContext->Video.Info.AspectRatio.Den=9;
+ break;
+ case 4:
+ maContext->Video.Info.AspectRatio.Num=11; // actually 2.21:1
+ maContext->Video.Info.AspectRatio.Den=5;
+ break;
+ default:
+ break;
+ }
+
+ switch (seqhdr->FrameRateIndex)
+ {
+ case 1:
+ maContext->Video.Info.FramesPerSecond=24000/1001; // 23.976 fps NTSC encapsulated
+ break;
+ case 2:
+ maContext->Video.Info.FramesPerSecond=24.0; // Standard international cinema film rate
+ break;
+ case 3:
+ maContext->Video.Info.FramesPerSecond=25.0; // PAL (625/50) video frame rate
+ break;
+
+ case 4:
+ maContext->Video.Info.FramesPerSecond=30000/1001; // 29.97 NTSC video frame rate
+ break;
+
+ case 5:
+ maContext->Video.Info.FramesPerSecond=30.0; // NTSC drop frame (525/60) video frame rate
+ break;
+
+ case 6:
+ maContext->Video.Info.FramesPerSecond=50.0; // double frame rate/progressive PAL
+ break;
+
+ case 7:
+ maContext->Video.Info.FramesPerSecond=60000/1001; // double frame rate NTSC
+ break;
+
+ case 8:
+ maContext->Video.Info.FramesPerSecond=60.0; // double frame rate drop-frame NTSC
+ break;
+
+ default:
+ break;
+ }
+
+ }
+ return false;
+}
+
+// taken from femon
+int cMarkAdStreamInfo::nalUnescape(uint8_t *dst, const uint8_t *src, int len)
+{
+ int s = 0, d = 0;
+
+ while (s < len)
+ {
+ if (!src[s] && !src[s + 1])
+ {
+ // hit 00 00 xx
+ dst[d] = dst[d + 1] = 0;
+ s += 2;
+ d += 2;
+ if (src[s] == 3)
+ {
+ s++; // 00 00 03 xx --> 00 00 xx
+ if (s >= len)
+ return d;
+ }
+ }
+ dst[d++] = src[s++];
+ }
+
+ return d;
+}
+
+
+cBitStream::cBitStream(const uint8_t *buf, const int len)
+ : data(buf),
+ count(len*8),
+ index(0)
+{
+}
+
+cBitStream::~cBitStream()
+{
+}
+
+int cBitStream::getBit()
+{
+ if (index >= count)
+ return (1); // -> no infinite colomb's ...
+
+ int r = (data[index >> 3] >> (7 - (index & 7))) & 1;
+ ++index;
+
+ return (r);
+}
+
+uint32_t cBitStream::getBits(uint32_t n)
+{
+ uint32_t r = 0;
+
+ while (n--)
+ r = (r | (getBit() << n));
+
+ return (r);
+}
+
+void cBitStream::skipBits(uint32_t n)
+{
+ index += n;
+}
+
+uint32_t cBitStream::getUeGolomb()
+{
+ int n = 0;
+
+ while (!getBit() && (n < 32))
+ n++;
+
+ return (n ? ((1 << n) - 1) + getBits(n) : 0);
+}
+
+int32_t cBitStream::getSeGolomb()
+{
+ uint32_t r = getUeGolomb() + 1;
+
+ return ((r & 1) ? -(r >> 1) : (r >> 1));
+}
+
+void cBitStream::skipGolomb()
+{
+ int n = 0;
+
+ while (!getBit() && (n < 32))
+ n++;
+
+ skipBits(n);
+}
+
+void cBitStream::skipUeGolomb()
+{
+ skipGolomb();
+}
+
+void cBitStream::skipSeGolomb()
+{
+ skipGolomb();
+}
+
+void cBitStream::byteAlign()
+{
+ int n = index % 8;
+
+ if (n > 0)
+ skipBits(8 - n);
+}
diff --git a/command/streaminfo.h b/command/streaminfo.h
new file mode 100644
index 0000000..e08ac7f
--- /dev/null
+++ b/command/streaminfo.h
@@ -0,0 +1,112 @@
+/*
+ * streaminfo.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __streaminfo_h_
+#define __streaminfo_h_
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "global.h"
+
+class cMarkAdStreamInfo
+{
+private:
+ // taken from ffmpeg
+ enum
+ {
+ NAL_SLICE = 0x01, // Slice
+ NAL_IDR_SLICE = 0x05, // IDR-Slice
+ NAL_SEI = 0x06, // Supplemental Enhancement Information
+ NAL_SPS = 0x07, // Sequence Parameter Set
+ NAL_PPS = 0x08, // Picture Parameter Set
+ NAL_AUD = 0x09, // Access Unit Delimiter
+ NAL_END_SEQ = 0x0A, // End of Sequence
+ NAL_SPS_EXT = 0x0D, // Sequence Parameter Set Extension
+ NAL_AUX_SLICE = 0x19 // Auxilary Slice
+ };
+
+ struct H264
+ {
+ bool primary_pic_typeI;
+ bool separate_colour_plane_flag;
+ int log2_max_frame_num;
+ int frame_num;
+ } H264;
+
+ int nalUnescape(uint8_t *dst, const uint8_t *src, int len);
+ const uint8_t *nextStartCode(const uint8_t *start, const uint8_t *end);
+ bool FindH264VideoInfos(MarkAdContext *maContext, uchar *pkt, int len);
+ bool FindH262VideoInfos(MarkAdContext *maContext, uchar *pkt, int len);
+public:
+ cMarkAdStreamInfo();
+ bool FindVideoInfos(MarkAdContext *maContext, uchar *pkt, int len);
+ bool FindAC3AudioInfos(MarkAdContext *maContext, uchar *espkt, int eslen);
+};
+
+// taken from femon
+class cBitStream
+{
+private:
+ const uint8_t *data;
+ int count; // in bits
+ int index; // in bits
+
+public:
+ cBitStream(const uint8_t *buf, const int len);
+ ~cBitStream();
+
+ int getBit();
+ uint32_t getBits(uint32_t n);
+ void skipBits(uint32_t n);
+ uint32_t getUeGolomb();
+ int32_t getSeGolomb();
+ void skipGolomb();
+ void skipUeGolomb();
+ void skipSeGolomb();
+ void byteAlign();
+
+ void skipBit()
+ {
+ skipBits(1);
+ }
+ uint32_t getU8()
+ {
+ return getBits(8);
+ }
+ uint32_t getU16()
+ {
+ return ((getBits(8) << 8) | getBits(8));
+ }
+ uint32_t getU24()
+ {
+ return ((getBits(8) << 16) | (getBits(8) << 8) | getBits(8));
+ }
+ uint32_t getU32()
+ {
+ return ((getBits(8) << 24) | (getBits(8) << 16) | (getBits(8) << 8) | getBits(8));
+ }
+ bool isEOF()
+ {
+ return (index >= count);
+ }
+ void reset()
+ {
+ index = 0;
+ }
+ int getIndex()
+ {
+ return (isEOF() ? count : index);
+ }
+ const uint8_t *getData()
+ {
+ return (isEOF() ? NULL : data + (index / 8));
+ }
+};
+
+#endif
diff --git a/command/ts2pkt.cpp b/command/ts2pkt.cpp
new file mode 100644
index 0000000..0d13df7
--- /dev/null
+++ b/command/ts2pkt.cpp
@@ -0,0 +1,194 @@
+/*
+ * ts2pkt.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "ts2pkt.h"
+
+cMarkAdTS2Pkt::cMarkAdTS2Pkt(const char *QueueName, int QueueSize)
+{
+ queue=new cMarkAdPaketQueue(QueueName,QueueSize);
+ Reset();
+}
+
+cMarkAdTS2Pkt::~cMarkAdTS2Pkt()
+{
+ if (queue) delete queue;
+}
+
+void cMarkAdTS2Pkt::Reset(int ErrIndex)
+{
+ sync=false;
+ switch (ErrIndex)
+ {
+ case MA_ERR_TSSIZE:
+ dsyslog("inbuf not 188 bytes");
+ break;
+ case MA_ERR_NOSYNC:
+ dsyslog("found no sync");
+ break;
+ case MA_ERR_SEQ:
+ dsyslog("sequence error");
+ break;
+ case MA_ERR_AFC:
+ dsyslog("wrong AFC value");
+ break;
+ case MA_ERR_TOBIG:
+ dsyslog("buflen > 188 bytes");
+ break;
+ case MA_ERR_NEG:
+ dsyslog("buflen negative");
+ break;
+ }
+ counter=-1;
+ if (queue) queue->Clear();
+}
+
+bool cMarkAdTS2Pkt::InjectVideoPES(uchar *PESData, int PESSize)
+{
+ if ((!PESData) || (!PESSize)) return false;
+
+ struct PESHDR *peshdr=(struct PESHDR *) PESData;
+
+ // first check some simple things
+ if ((peshdr->Sync1!=0) && (peshdr->Sync2!=0) && (peshdr->Sync3!=1)) return false;
+ if ((peshdr->StreamID & 0xF0)!=0xE0) return false;
+
+ int Length=(peshdr->LenH<<8)+peshdr->LenL;
+ if (Length) Length+=sizeof(PESHDR);
+ if (Length!=PESSize) return false;
+
+ struct PESHDROPT *peshdropt=(struct PESHDROPT *) &PESData[sizeof(struct PESHDR)];
+
+ uchar *buf;
+ int buflen;
+
+ if (peshdropt->MarkerBits==0x2)
+ {
+ // we have an optional PES header
+ int bpos=sizeof(struct PESHDR)+sizeof(struct PESHDROPT)+
+ peshdropt->Length;
+ buf=&PESData[bpos];
+ buflen=PESSize-bpos;
+ }
+ else
+ {
+ int bpos=sizeof(struct PESHDR);
+ buf=&PESData[bpos];
+ buflen=PESSize-bpos;
+ }
+ queue->Inject(buf,buflen);
+ return true;
+}
+
+void cMarkAdTS2Pkt::Process(MarkAdPid Pid, uchar *TSData, int TSSize, uchar **PktData, int *PktSize)
+{
+ if ((!PktData) || (!PktSize) || (!queue)) return;
+ *PktData=NULL;
+ *PktSize=0;
+
+ if (TSData)
+ {
+ if (TSSize!=TS_SIZE)
+ {
+ Reset(MA_ERR_TSSIZE);
+ return; // we need a full packet
+ }
+
+ // check TS packet sync
+ if (TSData[0]!=0x47)
+ {
+ Reset(MA_ERR_NOSYNC);
+ return;
+ }
+
+ struct TSHDR *tshdr = (struct TSHDR *) TSData;
+
+ int pid = (tshdr->PidH << 8) | tshdr->PidL;
+ if (Pid.Num!=pid)
+ {
+ return; // not for us
+ }
+
+ if (tshdr->PayloadStart) sync=true;
+ if (!sync)
+ {
+ return; // not synced
+ }
+
+ if ((counter!=-1) && (((counter+1) & 0xF)!=tshdr->Counter))
+ {
+ if (counter==(int) tshdr->Counter)
+ {
+ // duplicate paket -> just ignore
+ return;
+ }
+ // sequence error
+ Reset(MA_ERR_SEQ);
+ return;
+ }
+ counter=tshdr->Counter;
+
+ if ((tshdr->AFC<=0) || (tshdr->AFC>3))
+ {
+ Reset(MA_ERR_AFC);
+ return;
+ }
+
+ // we just ignore the infos in the adaption field (e.g. OPCR/PCR)
+ if ((tshdr->AFC!=1) && (tshdr->AFC!=3))
+ {
+ return;
+ }
+
+ int buflen=TS_SIZE+1;
+ uchar *buf=NULL;
+
+ if (tshdr->AFC==1)
+ {
+ // payload only
+ buflen=TS_SIZE-sizeof(struct TSHDR);
+ buf=&TSData[sizeof(struct TSHDR)];
+ }
+
+ if (tshdr->AFC==3)
+ {
+ // adaption field + payload
+ struct TSADAPT *tsadapt = (struct TSADAPT *) &TSData[4];
+ int alen=tsadapt->Len+1;
+ buflen=TS_SIZE-(sizeof(struct TSHDR)+alen);
+ buf=&TSData[sizeof(struct TSHDR)+alen];
+ }
+
+ if (buflen>TS_SIZE)
+ {
+ // size to large
+ Reset(MA_ERR_TOBIG);
+ return;
+ }
+ if (buflen<0)
+ {
+ // error in size
+ Reset(MA_ERR_NEG);
+ return;
+ }
+ if (buflen==0)
+ {
+ // no data?
+ return;
+ }
+
+ queue->Put(buf,buflen);
+ }
+ if (Pid.Type==MARKAD_PIDTYPE_VIDEO_H264)
+ {
+ *PktData=queue->GetPacket(PktSize,MA_PACKET_H264);
+ }
+ else
+ {
+ *PktData=queue->GetPacket(PktSize,MA_PACKET_PKT);
+ }
+ return;
+}
diff --git a/command/ts2pkt.h b/command/ts2pkt.h
new file mode 100644
index 0000000..a485d5b
--- /dev/null
+++ b/command/ts2pkt.h
@@ -0,0 +1,127 @@
+/*
+ * ts2pkt.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __ts2pkt_h_
+#define __ts2pkt_h_
+
+extern "C"
+{
+#include "debug.h"
+}
+
+#ifndef TS_SIZE
+#define TS_SIZE 188
+#endif
+
+#ifndef uchar
+typedef unsigned char uchar;
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "global.h"
+#include "queue.h"
+
+class cMarkAdTS2Pkt
+{
+private:
+ struct TSHDR
+ {
+unsigned Sync:
+ 8;
+unsigned PidH:
+ 5;
+unsigned Priority:
+ 1;
+unsigned PayloadStart:
+ 1;
+unsigned TError:
+ 1;
+unsigned PidL:
+ 8;
+unsigned Counter:
+ 4;
+unsigned AFC:
+ 2;
+unsigned TSC:
+ 2;
+ };
+
+ struct TSADAPT
+ {
+unsigned Len:
+ 8;
+unsigned Flags:
+ 8;
+ };
+
+ struct PESHDR
+ {
+ uchar Sync1;
+ uchar Sync2;
+ uchar Sync3;
+ uchar StreamID;
+ uchar LenH;
+ uchar LenL;
+ };
+
+#pragma pack(1)
+ struct PESHDROPT
+ {
+unsigned OOC:
+ 1;
+unsigned CY:
+ 1;
+unsigned DAI:
+ 1;
+unsigned PESP:
+ 1;
+unsigned PESSC:
+ 2;
+unsigned MarkerBits:
+ 2;
+unsigned EXT:
+ 1;
+unsigned CRC:
+ 1;
+unsigned ACI:
+ 1;
+unsigned TM:
+ 1;
+unsigned RATE:
+ 1;
+unsigned ESCR:
+ 1;
+unsigned TSF:
+ 2;
+unsigned Length:
+ 8;
+ };
+#pragma pack()
+
+ int counter;
+ bool sync;
+
+ cMarkAdPaketQueue *queue;
+
+#define MA_ERR_STARTUP 0
+#define MA_ERR_TSSIZE 1
+#define MA_ERR_NOSYNC 2
+#define MA_ERR_SEQ 3
+#define MA_ERR_AFC 4
+#define MA_ERR_TOBIG 5
+#define MA_ERR_NEG 6
+ void Reset(int ErrIndex=MA_ERR_STARTUP);
+public:
+ cMarkAdTS2Pkt(const char *QueueName="TS2Pkt", int QueueSize=32768);
+ ~cMarkAdTS2Pkt();
+ void Process(MarkAdPid Pid,uchar *TSData, int TSSize, uchar **PktData, int *PktSize);
+ bool InjectVideoPES(uchar *PESData, int PESSize);
+};
+
+#endif
diff --git a/command/vdr2pkt.cpp b/command/vdr2pkt.cpp
new file mode 100644
index 0000000..8d5a18f
--- /dev/null
+++ b/command/vdr2pkt.cpp
@@ -0,0 +1,30 @@
+/*
+ * vdr2pkt.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "vdr2pkt.h"
+
+cMarkAdVDR2Pkt::cMarkAdVDR2Pkt(const char *QueueName, int QueueSize)
+{
+ queue = new cMarkAdPaketQueue(QueueName,QueueSize);
+}
+
+cMarkAdVDR2Pkt::~cMarkAdVDR2Pkt()
+{
+ if (queue) delete queue;
+}
+
+void cMarkAdVDR2Pkt::Process(MarkAdPid Pid, uchar *VDRData, int VDRSize, uchar **PktData, int *PktSize)
+{
+ if ((!PktData) || (!PktSize) || (!queue)) return;
+ *PktData=NULL;
+ *PktSize=0;
+ if (!Pid.Type) return;
+
+ if (VDRData) queue->Put(VDRData,VDRSize);
+ *PktData=queue->GetPacket(PktSize,MA_PACKET_PKT);
+ return;
+}
diff --git a/command/vdr2pkt.h b/command/vdr2pkt.h
new file mode 100644
index 0000000..e83013e
--- /dev/null
+++ b/command/vdr2pkt.h
@@ -0,0 +1,28 @@
+/*
+ * vdr2pkt.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __vdr2pkt_h_
+#define __vdr2pkt_h_
+
+#ifndef uchar
+typedef unsigned char uchar;
+#endif
+
+#include "global.h"
+#include "queue.h"
+
+class cMarkAdVDR2Pkt
+{
+private:
+ cMarkAdPaketQueue *queue;
+public:
+ cMarkAdVDR2Pkt(const char *QueueName="VDR2PKT", int QueueSize=32768);
+ ~cMarkAdVDR2Pkt();
+ void Process(MarkAdPid Pid,uchar *VDRData, int VDRSize, uchar **PktData, int *PktSize);
+};
+
+#endif
diff --git a/command/video.cpp b/command/video.cpp
new file mode 100644
index 0000000..f32faa9
--- /dev/null
+++ b/command/video.cpp
@@ -0,0 +1,657 @@
+/*
+ * video.cpp: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "video.h"
+
+cMarkAdLogo::cMarkAdLogo(MarkAdContext *maContext)
+{
+ macontext=maContext;
+
+ // 3x3 GX Sobel mask
+
+ GX[0][0] = -1;
+ GX[0][1] = 0;
+ GX[0][2] = 1;
+ GX[1][0] = -2;
+ GX[1][1] = 0;
+ GX[1][2] = 2;
+ GX[2][0] = -1;
+ GX[2][1] = 0;
+ GX[2][2] = 1;
+
+ // 3x3 GY Sobel mask
+ GY[0][0] = 1;
+ GY[0][1] = 2;
+ GY[0][2] = 1;
+ GY[1][0] = 0;
+ GY[1][1] = 0;
+ GY[1][2] = 0;
+ GY[2][0] = -1;
+ GY[2][1] = -2;
+ GY[2][2] = -1;
+
+ memset(&area,0,sizeof(area));
+
+ LOGOHEIGHT=LOGO_DEFHEIGHT;
+ if (maContext->Info.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264)
+ {
+ LOGOWIDTH=LOGO_DEFHDWIDTH;
+ }
+ else
+ {
+ LOGOWIDTH=LOGO_DEFWIDTH;
+ }
+
+ area.status=UNINITIALIZED;
+}
+
+cMarkAdLogo::~cMarkAdLogo()
+{
+}
+
+int cMarkAdLogo::Load(char *file)
+{
+ // Load mask
+ FILE *pFile;
+ area.valid=false;
+ area.corner=-1;
+ pFile=fopen(file, "rb");
+ if (!pFile) return -1;
+
+ fscanf(pFile, "P5\n#C%i %i\n%d %d\n255\n#", &area.corner,&area.mpixel,&LOGOWIDTH,&LOGOHEIGHT);
+
+ if (LOGOHEIGHT==255)
+ {
+ LOGOHEIGHT=LOGOWIDTH;
+ LOGOWIDTH=area.mpixel;
+ area.mpixel=0;
+ }
+
+ if ((LOGOWIDTH<=0) || (LOGOHEIGHT<=0) || (LOGOWIDTH>LOGO_MAXWIDTH) || (LOGOHEIGHT>LOGO_MAXHEIGHT) ||
+ (area.corner<TOP_LEFT) || (area.corner>BOTTOM_RIGHT))
+ {
+ fclose(pFile);
+ return -2;
+ }
+
+ fread(&area.mask,1,LOGOWIDTH*LOGOHEIGHT,pFile);
+
+ if (!area.mpixel)
+ {
+ for (int i=0; i<LOGOWIDTH*LOGOHEIGHT; i++)
+ {
+ if (!area.mask[i]) area.mpixel++;
+ }
+ }
+
+ fclose(pFile);
+ area.valid=true;
+ return 0;
+}
+
+
+
+void cMarkAdLogo::Save(int lastiframe, uchar *picture)
+{
+ if (!macontext) return;
+
+
+ char *buf=NULL;
+ if (asprintf(&buf,"%s/%06d-%s-A%i_%i.pgm","/tmp/",lastiframe,
+ macontext->Info.ChannelID,
+ area.aspectratio.Num,area.aspectratio.Den)!=-1)
+ {
+ // Open file
+ FILE *pFile=fopen(buf, "wb");
+ if (pFile==NULL)
+ {
+ free(buf);
+ return;
+ }
+
+ // Write header
+ fprintf(pFile, "P5\n#C%i\n%d %d\n255\n", area.corner, LOGOWIDTH,LOGOHEIGHT);
+
+ // Write pixel data
+ fwrite(picture,1,LOGOWIDTH*LOGOHEIGHT,pFile);
+ // Close file
+ fclose(pFile);
+ free(buf);
+ }
+}
+
+int cMarkAdLogo::Detect(int lastiframe, int *logoiframe)
+{
+ // Detection is made with Sobel-Operator
+
+ if (!macontext) return 0;
+ if (!macontext->Video.Info.Width) return 0;
+ if (!macontext->Video.Info.Height) return 0;
+ if (!macontext->Video.Data.Valid) return 0;
+
+ if (area.corner>BOTTOM_RIGHT) return 0;
+ if (area.corner<TOP_LEFT) return 0;
+
+ int xstart,xend,ystart,yend;
+
+ switch (area.corner)
+ {
+ case TOP_LEFT:
+ xstart=0;
+ xend=LOGOWIDTH;
+ ystart=0;
+ yend=LOGOHEIGHT;
+ break;
+ case TOP_RIGHT:
+ xstart=macontext->Video.Info.Width-LOGOWIDTH;
+ xend=macontext->Video.Info.Width;
+ ystart=0;
+ yend=LOGOHEIGHT;
+ break;
+ case BOTTOM_LEFT:
+ xstart=0;
+ xend=LOGOWIDTH;
+ ystart=macontext->Video.Info.Height-LOGOHEIGHT;
+ yend=macontext->Video.Info.Height;
+ break;
+ case BOTTOM_RIGHT:
+ xstart=macontext->Video.Info.Width-LOGOWIDTH;
+ xend=macontext->Video.Info.Width;
+ ystart=macontext->Video.Info.Height-LOGOHEIGHT;
+ yend=macontext->Video.Info.Height;
+ break;
+ default:
+ return 0;
+ }
+
+ int SUMA=0;
+ for (int Y=ystart; Y<=yend-1; Y++)
+ {
+ for (int X=xstart; X<=xend-1; X++)
+ {
+ int val=macontext->Video.Data.Plane[0][X+(Y*macontext->Video.Data.PlaneLinesize[0])];
+ area.source[(X-xstart)+(Y-ystart)*LOGOWIDTH]=val;
+ SUMA+=val;
+ }
+ }
+
+ SUMA/=(LOGOWIDTH*LOGOHEIGHT);
+#if 0
+ if (SUMA>=100)
+ {
+ int maxval=(int) SUMA;
+ SUMA=0;
+ for (int Y=ystart; Y<=yend-1; Y++)
+ {
+ for (int X=xstart; X<=xend-1; X++)
+ {
+ int val=macontext->Video.Data.Plane[0][X+(Y*macontext->Video.Data.PlaneLinesize[0])];
+ val=(int) (((double) val- (double) maxval/1.4)*1.4);
+ if (val>maxval) val=maxval;
+ if (val<0) val=0;
+ area.source[(X-xstart)+(Y-ystart)*LOGOWIDTH]=val;
+ SUMA+=val;
+ }
+ }
+ SUMA/=(LOGOWIDTH*LOGOHEIGHT);
+ }
+#endif
+ int ret=NOCHANGE;
+
+ if ((SUMA<100) || ((area.status==LOGO) && (SUMA<180)))
+ {
+
+ int SUM;
+ int sumX,sumY;
+ area.rpixel=0;
+ for (int Y=ystart; Y<=yend-1; Y++)
+ {
+ for (int X=xstart; X<=xend-1; X++)
+ {
+ sumX=0;
+ sumY=0;
+
+ // image boundaries
+ if (Y<(ystart+15) || Y>(yend-15))
+ SUM=0;
+ else if (X<(xstart+15) || X>(xend-15))
+ SUM=0;
+ // convolution starts here
+ else
+ {
+ // X Gradient approximation
+ for (int I=-1; I<=1; I++)
+ {
+ for (int J=-1; J<=1; J++)
+ {
+ sumX=sumX+ (int) ((*(macontext->Video.Data.Plane[0]+X+I+
+ (Y+J)*macontext->Video.Data.PlaneLinesize[0]))
+ *GX[I+1][J+1]);
+ }
+ }
+
+ // Y Gradient approximation
+ for (int I=-1; I<=1; I++)
+ {
+ for (int J=-1; J<=1; J++)
+ {
+ sumY=sumY+ (int) ((*(macontext->Video.Data.Plane[0]+X+I+
+ (Y+J)*macontext->Video.Data.PlaneLinesize[0]))*
+ GY[I+1][J+1]);
+ }
+ }
+
+ // Gradient Magnitude approximation
+ SUM = abs(sumX) + abs(sumY);
+ }
+
+ if (SUM>=127) SUM=255;
+ if (SUM<127) SUM=0;
+
+ int val = 255-(uchar) SUM;
+
+ area.sobel[(X-xstart)+(Y-ystart)*LOGOWIDTH]=val;
+
+ area.result[(X-xstart)+(Y-ystart)*LOGOWIDTH]=
+ (area.mask[(X-xstart)+(Y-ystart)*LOGOWIDTH] + val) & 255;
+
+ if (!area.result[(X-xstart)+(Y-ystart)*LOGOWIDTH]) area.rpixel++;
+
+ }
+ }
+ if (macontext->Options.LogoExtraction==-1)
+ {
+ if (area.status==UNINITIALIZED)
+ {
+ // Initialize
+ if (area.rpixel>(area.mpixel*LOGO_VMARK))
+ {
+ area.status=LOGO;
+ }
+ else
+ {
+ area.status=NOLOGO;
+ }
+ }
+
+ if (area.rpixel>=(area.mpixel*LOGO_VMARK))
+ {
+ if (area.status==NOLOGO)
+ {
+ if (area.counter>=LOGO_VMAXCOUNT)
+ {
+ area.status=ret=LOGO;
+ *logoiframe=area.lastiframe;
+ area.counter=0;
+ }
+ else
+ {
+ if (!area.counter) area.lastiframe=lastiframe;
+ area.counter++;
+ }
+ }
+ else
+ {
+ area.lastiframe=lastiframe;
+ area.counter=0;
+ }
+ }
+
+ if (area.rpixel<(area.mpixel*LOGO_IMARK))
+ {
+ if (area.status==LOGO)
+ {
+ if (area.counter>=LOGO_IMAXCOUNT)
+ {
+ area.status=ret=NOLOGO;
+ *logoiframe=area.lastiframe;
+ area.counter=0;
+ }
+ else
+ {
+ area.counter++;
+ }
+ }
+ else
+ {
+ area.counter=0;
+ }
+ }
+
+ if ((area.rpixel<(area.mpixel*LOGO_VMARK)) && (area.rpixel>(area.mpixel*LOGO_IMARK)))
+ {
+ area.counter=0;
+ }
+
+#if 0
+ printf("%5i %3i %4i %4i %i %i %i\n",lastiframe,SUMA,area.rpixel,area.mpixel,
+ (area.rpixel>=(area.mpixel*LOGO_VMARK)),(area.rpixel<(area.mpixel*LOGO_IMARK)),
+ area.counter );
+ Save(lastiframe,area.sobel); // TODO: JUST FOR DEBUGGING!
+#endif
+ }
+ else
+ {
+ Save(lastiframe,area.sobel);
+ }
+ }
+ return ret;
+}
+
+int cMarkAdLogo::Process(int LastIFrame, int *LogoIFrame)
+{
+ if (!macontext) return ERROR;
+ if (!macontext->Video.Data.Valid) return ERROR;
+ if (!macontext->Video.Info.Width) return ERROR;
+ if (!macontext->LogoDir) return ERROR;
+ if (!macontext->Info.ChannelID) return ERROR;
+
+ if (macontext->Options.LogoExtraction==-1)
+ {
+
+ if ((area.aspectratio.Num!=macontext->Video.Info.AspectRatio.Num) ||
+ (area.aspectratio.Den!=macontext->Video.Info.AspectRatio.Den))
+ {
+ area.valid=false; // just to be sure!
+ char *buf=NULL;
+ if (asprintf(&buf,"%s/%s-A%i_%i.pgm",macontext->LogoDir,macontext->Info.ChannelID,
+ macontext->Video.Info.AspectRatio.Num,macontext->Video.Info.AspectRatio.Den)!=-1)
+ {
+ int ret=Load(buf);
+ switch (ret)
+ {
+ case -1:
+ esyslog("failed to open %s",buf);
+
+ break;
+
+ case -2:
+ esyslog("format error in %s",buf);
+ break;
+ }
+ free(buf);
+ }
+ area.aspectratio.Num=macontext->Video.Info.AspectRatio.Num;
+ area.aspectratio.Den=macontext->Video.Info.AspectRatio.Den;
+ }
+ }
+ else
+ {
+ area.aspectratio.Num=macontext->Video.Info.AspectRatio.Num;
+ area.aspectratio.Den=macontext->Video.Info.AspectRatio.Den;
+ area.corner=macontext->Options.LogoExtraction;
+ if (macontext->Options.LogoWidth!=-1)
+ {
+ LOGOWIDTH=macontext->Options.LogoWidth;
+ }
+ if (macontext->Options.LogoHeight!=-1)
+ {
+ LOGOHEIGHT=macontext->Options.LogoHeight;
+ }
+ area.valid=true;
+ }
+
+ if (!area.valid) return ERROR;
+
+ return Detect(LastIFrame,LogoIFrame);
+}
+
+cMarkAdBlackBordersHoriz::cMarkAdBlackBordersHoriz(MarkAdContext *maContext)
+{
+ macontext=maContext;
+
+ borderstatus=UNINITIALIZED;
+ borderiframe=-1;
+}
+
+int cMarkAdBlackBordersHoriz::Process(int LastIFrame, int *BorderIFrame)
+{
+#define CHECKHEIGHT 20
+#define BRIGHTNESS 20
+#define OFFSET 5
+ if (!macontext) return 0;
+ if (!macontext->Video.Data.Valid) return 0;
+ if (macontext->Video.Info.FramesPerSecond==0) return 0;
+ *BorderIFrame=0;
+
+ int height=macontext->Video.Info.Height-OFFSET;
+
+ int start=(height-CHECKHEIGHT)*macontext->Video.Data.PlaneLinesize[0];
+ int end=height*macontext->Video.Data.PlaneLinesize[0];
+ bool ftop=true,fbottom=true;
+ int val=0,cnt=0,xz=0;
+
+ for (int x=start; x<end; x++)
+ {
+ if (xz<macontext->Video.Info.Width)
+ {
+ val+=macontext->Video.Data.Plane[0][x];
+ cnt++;
+ }
+ xz++;
+ if (xz>=macontext->Video.Data.PlaneLinesize[0]) xz=0;
+ }
+ val/=cnt;
+ if (val>BRIGHTNESS) fbottom=false;
+
+ if (fbottom)
+ {
+ start=OFFSET*macontext->Video.Data.PlaneLinesize[0];
+ end=macontext->Video.Data.PlaneLinesize[0]*(CHECKHEIGHT+OFFSET);
+ val=0;
+ cnt=0;
+ xz=0;
+ for (int x=start; x<end; x++)
+ {
+ if (xz<macontext->Video.Info.Width)
+ {
+ val+=macontext->Video.Data.Plane[0][x];
+ cnt++;
+ }
+ xz++;
+ if (xz>=macontext->Video.Data.PlaneLinesize[0]) xz=0;
+ }
+ val/=cnt;
+ if (val>BRIGHTNESS) ftop=false;
+ }
+
+ if ((fbottom) && (ftop))
+ {
+ if (borderiframe==-1)
+ {
+ borderiframe=LastIFrame;
+ }
+ else
+ {
+#define MINSECS 60
+ switch (borderstatus)
+ {
+ case UNINITIALIZED:
+ if (LastIFrame>(borderiframe+macontext->Video.Info.FramesPerSecond*MINSECS))
+ {
+ borderstatus=BORDER;
+ }
+ break;
+
+ case NOBORDER:
+ if (LastIFrame>(borderiframe+macontext->Video.Info.FramesPerSecond*MINSECS))
+ {
+ *BorderIFrame=borderiframe;
+ borderstatus=BORDER;
+ return 1; // detected start of black border
+ }
+ break;
+
+ case BORDER:
+ borderiframe=LastIFrame;
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (borderiframe!=-1)
+ {
+ if (borderstatus==BORDER)
+ {
+ *BorderIFrame=borderiframe;
+ borderstatus=NOBORDER;
+ borderiframe=-1;
+ return -1; // detected stop of black border
+ }
+ else
+ {
+ borderiframe=-1;
+ }
+ }
+ else
+ {
+ borderiframe=-1;
+ }
+ }
+ return 0;
+}
+
+
+cMarkAdVideo::cMarkAdVideo(MarkAdContext *maContext)
+{
+ macontext=maContext;
+
+ aspectratio.Num=0;
+ aspectratio.Den=0;
+ mark.Comment=NULL;
+ mark.Position=0;
+ mark.Type=0;
+
+ hborder=new cMarkAdBlackBordersHoriz(maContext);
+ logo = new cMarkAdLogo(maContext);
+}
+
+cMarkAdVideo::~cMarkAdVideo()
+{
+ ResetMark();
+ if (hborder) delete hborder;
+ if (logo) delete logo;
+}
+
+void cMarkAdVideo::ResetMark()
+{
+ if (mark.Comment) free(mark.Comment);
+ mark.Comment=NULL;
+ mark.Position=0;
+ mark.Type=0;
+}
+
+bool cMarkAdVideo::AddMark(int Type, int Position, const char *Comment)
+{
+ if (!Comment) return false;
+ if (mark.Comment)
+ {
+ int oldlen=strlen(mark.Comment);
+ mark.Comment=(char *) realloc(mark.Comment,oldlen+10+strlen(Comment));
+ if (!mark.Comment)
+ {
+ mark.Position=0;
+ return false;
+ }
+ strcat(mark.Comment," [");
+ strcat(mark.Comment,Comment);
+ strcat(mark.Comment,"]");
+ }
+ else
+ {
+ mark.Comment=strdup(Comment);
+ }
+ mark.Position=Position;
+ mark.Type=Type;
+ return true;
+}
+
+bool cMarkAdVideo::AspectRatioChange(MarkAdAspectRatio *a, MarkAdAspectRatio *b)
+{
+ if ((!a) || (!b)) return false;
+
+ if (a->Num==0 || a->Den==0 || b->Num==0 || b->Den==0) return false;
+ if ((a->Num!=b->Num) && (a->Den!=b->Den)) return true;
+ return false;
+
+}
+
+MarkAdMark *cMarkAdVideo::Process(int LastIFrame)
+{
+ ResetMark();
+ if (!LastIFrame) return NULL;
+
+ if (!macontext->Video.Options.IgnoreLogoDetection)
+ {
+ int logoiframe;
+ int lret=logo->Process(LastIFrame,&logoiframe);
+ if ((lret>=-1) && (lret!=0))
+ {
+ char *buf=NULL;
+ if (lret>0)
+ {
+ if (asprintf(&buf,"detected logo start (%i)",logoiframe)!=-1)
+ {
+ AddMark(MT_LOGOSTART,logoiframe,buf);
+ free(buf);
+ }
+ }
+ else
+ {
+ if (asprintf(&buf,"detected logo stop (%i)",logoiframe)!=-1)
+ {
+ AddMark(MT_LOGOSTOP,logoiframe,buf);
+ free(buf);
+ }
+ }
+ }
+ }
+
+ int borderiframe;
+ int hret=hborder->Process(LastIFrame,&borderiframe);
+
+ if ((hret>0) && (borderiframe))
+ {
+ char *buf=NULL;
+ if (asprintf(&buf,"detected start of horiz. borders (%i)",borderiframe)!=-1)
+ {
+ AddMark(MT_BORDERSTART,borderiframe,buf);
+ free(buf);
+ }
+ }
+
+ if ((hret<0) && (borderiframe))
+ {
+ char *buf=NULL;
+ if (asprintf(&buf,"detected stop of horiz. borders (%i)",borderiframe)!=-1)
+ {
+ AddMark(MT_BORDERSTOP,borderiframe,buf);
+ free(buf);
+ }
+ }
+
+ if (!macontext->Video.Options.IgnoreAspectRatio)
+ {
+ if (AspectRatioChange(&macontext->Video.Info.AspectRatio,&aspectratio))
+ {
+ char *buf=NULL;
+ if (asprintf(&buf,"aspect ratio change from %i:%i to %i:%i (%i)",
+ aspectratio.Num,aspectratio.Den,
+ macontext->Video.Info.AspectRatio.Num,
+ macontext->Video.Info.AspectRatio.Den,LastIFrame)!=-1)
+ {
+ AddMark(MT_ASPECTCHANGE,LastIFrame,buf);
+ free(buf);
+ }
+ }
+
+ aspectratio.Num=macontext->Video.Info.AspectRatio.Num;
+ aspectratio.Den=macontext->Video.Info.AspectRatio.Den;
+ }
+ return &mark;
+}
diff --git a/command/video.h b/command/video.h
new file mode 100644
index 0000000..e5e35d4
--- /dev/null
+++ b/command/video.h
@@ -0,0 +1,133 @@
+/*
+ * video.h: A program for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __video_h_
+#define __video_h_
+
+#include <time.h>
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include "global.h"
+
+extern "C"
+{
+#include "debug.h"
+}
+
+#define LOGO_MAXHEIGHT 170
+#define LOGO_MAXWIDTH 480
+
+#define LOGO_DEFHEIGHT 100
+#define LOGO_DEFWIDTH 192
+#define LOGO_DEFHDWIDTH 288
+
+#define LOGO_VMAXCOUNT 3 // count of IFrames for detection of "logo invisible"
+#define LOGO_IMAXCOUNT 5 // count of IFrames for detection of "logo invisible"
+#define LOGO_VMARK 0.5 // percantage of pixels for visible
+#define LOGO_IMARK 0.15 // percentage of pixels for invisible
+
+class cMarkAdLogo
+{
+private:
+
+ enum
+ {
+ TOP_LEFT,
+ TOP_RIGHT,
+ BOTTOM_LEFT,
+ BOTTOM_RIGHT
+ };
+
+ enum
+ {
+ ERROR=-3,
+ UNINITIALIZED=-2,
+ NOLOGO=-1,
+ NOCHANGE=0,
+ LOGO=1
+ };
+
+ int LOGOHEIGHT; // max. 140
+ int LOGOWIDTH; // 192-288
+
+#define MAXPIXEL LOGO_MAXWIDTH*LOGO_MAXHEIGHT
+
+ struct areaT
+ {
+ uchar source[MAXPIXEL]; // original grayscale picture
+ uchar sobel[MAXPIXEL]; // monochrome picture with edges (after sobel)
+ uchar mask[MAXPIXEL]; // monochrome mask of logo
+ uchar result[MAXPIXEL]; // result of sobel + mask
+ int rpixel; // black pixel in result
+ int mpixel; // black pixel in mask
+ int status; // status = LOGO on, off, uninitialized
+ int lastiframe; // start/stop frame
+ int counter; // how many logo on, offs detected?
+ int corner; // which corner
+ MarkAdAspectRatio aspectratio; // aspectratio
+ bool valid; // logo mask valid?
+ } area;
+
+ int G[5][5];
+ int GX[3][3];
+ int GY[3][3];
+
+ MarkAdContext *macontext;
+ int Detect(int lastiframe, int *logoiframe); // ret 1 = logo, 0 = unknown, -1 = no logo
+ int Load(char *file);
+ void Save(int lastiframe, uchar *picture);
+public:
+ cMarkAdLogo(MarkAdContext *maContext);
+ ~cMarkAdLogo();
+ int Process(int LastIFrame, int *LogoIFrame);
+};
+
+class cMarkAdBlackBordersHoriz
+{
+private:
+ enum
+ {
+ ERROR=-3,
+ UNINITIALIZED=-2,
+ NOBORDER=-1,
+ NOCHANGE=0,
+ BORDER=1
+ };
+
+ int borderstatus;
+ int borderiframe;
+ MarkAdContext *macontext;
+public:
+ cMarkAdBlackBordersHoriz(MarkAdContext *maContext);
+ int Process(int LastIFrame,int *BorderIFrame);
+};
+
+class cMarkAdVideo
+{
+private:
+ MarkAdContext *macontext;
+ MarkAdMark mark;
+
+ MarkAdAspectRatio aspectratio;
+ cMarkAdBlackBordersHoriz *hborder;
+ cMarkAdLogo *logo;
+
+ void ResetMark();
+ bool AddMark(int Type, int Position, const char *Comment);
+ bool AspectRatioChange(MarkAdAspectRatio *a, MarkAdAspectRatio *b);
+ void SetTimerMarks(int LastIFrame);
+
+public:
+ cMarkAdVideo(MarkAdContext *maContext);
+ ~cMarkAdVideo();
+ MarkAdMark *Process(int LastIFrame);
+};
+
+#endif