From 74cdd9ffa1d0e5f74942051e7e22e07542929c03 Mon Sep 17 00:00:00 2001 From: Jochen Dolze Date: Tue, 30 Mar 2010 18:49:58 +0200 Subject: Changed directory structure, added Makefiles --- command/Makefile | 88 +++ command/audio.cpp | 184 +++++ command/audio.h | 59 ++ command/debug.h | 23 + command/decoder.cpp | 478 ++++++++++++ command/decoder.h | 72 ++ command/demux.cpp | 190 +++++ command/demux.h | 45 ++ command/global.h | 139 ++++ command/markad-standalone.cpp | 1632 +++++++++++++++++++++++++++++++++++++++++ command/markad-standalone.h | 214 ++++++ command/marks.cpp | 373 ++++++++++ command/marks.h | 119 +++ command/pes2es.cpp | 100 +++ command/pes2es.h | 78 ++ command/queue.cpp | 451 ++++++++++++ command/queue.h | 159 ++++ command/streaminfo.cpp | 793 ++++++++++++++++++++ command/streaminfo.h | 112 +++ command/ts2pkt.cpp | 194 +++++ command/ts2pkt.h | 127 ++++ command/vdr2pkt.cpp | 30 + command/vdr2pkt.h | 28 + command/video.cpp | 657 +++++++++++++++++ command/video.h | 133 ++++ 25 files changed, 6478 insertions(+) create mode 100644 command/Makefile create mode 100644 command/audio.cpp create mode 100644 command/audio.h create mode 100644 command/debug.h create mode 100644 command/decoder.cpp create mode 100644 command/decoder.h create mode 100644 command/demux.cpp create mode 100644 command/demux.h create mode 100644 command/global.h create mode 100644 command/markad-standalone.cpp create mode 100644 command/markad-standalone.h create mode 100644 command/marks.cpp create mode 100644 command/marks.h create mode 100644 command/pes2es.cpp create mode 100644 command/pes2es.h create mode 100644 command/queue.cpp create mode 100644 command/queue.h create mode 100644 command/streaminfo.cpp create mode 100644 command/streaminfo.h create mode 100644 command/ts2pkt.cpp create mode 100644 command/ts2pkt.h create mode 100644 command/vdr2pkt.cpp create mode 100644 command/vdr2pkt.h create mode 100644 command/video.cpp create mode 100644 command/video.h (limited to 'command') 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='' -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; iAudio.Data.SampleBuf[0+(i*2)]; + right=macontext->Audio.Data.SampleBuf[1+(i*2)]; + + if ((abs(left)+abs(right))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; iAudio.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 // for htonl +#include +#include +#include + +#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 +#include + +#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 + +#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 +#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()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 + +#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)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 (iES_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 \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=