diff options
Diffstat (limited to 'command')
-rw-r--r-- | command/Makefile | 88 | ||||
-rw-r--r-- | command/audio.cpp | 184 | ||||
-rw-r--r-- | command/audio.h | 59 | ||||
-rw-r--r-- | command/debug.h | 23 | ||||
-rw-r--r-- | command/decoder.cpp | 478 | ||||
-rw-r--r-- | command/decoder.h | 72 | ||||
-rw-r--r-- | command/demux.cpp | 190 | ||||
-rw-r--r-- | command/demux.h | 45 | ||||
-rw-r--r-- | command/global.h | 139 | ||||
-rw-r--r-- | command/markad-standalone.cpp | 1632 | ||||
-rw-r--r-- | command/markad-standalone.h | 214 | ||||
-rw-r--r-- | command/marks.cpp | 373 | ||||
-rw-r--r-- | command/marks.h | 119 | ||||
-rw-r--r-- | command/pes2es.cpp | 100 | ||||
-rw-r--r-- | command/pes2es.h | 78 | ||||
-rw-r--r-- | command/queue.cpp | 451 | ||||
-rw-r--r-- | command/queue.h | 159 | ||||
-rw-r--r-- | command/streaminfo.cpp | 793 | ||||
-rw-r--r-- | command/streaminfo.h | 112 | ||||
-rw-r--r-- | command/ts2pkt.cpp | 194 | ||||
-rw-r--r-- | command/ts2pkt.h | 127 | ||||
-rw-r--r-- | command/vdr2pkt.cpp | 30 | ||||
-rw-r--r-- | command/vdr2pkt.h | 28 | ||||
-rw-r--r-- | command/video.cpp | 657 | ||||
-rw-r--r-- | command/video.h | 133 |
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 |