/* * 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 { va_start(ap, format); vprintf(format,ap); va_end(ap); printf("\n"); } } void cMarkAdStandalone::AddStartMark() { if (!marks.Count()) { char *buf; if (asprintf(&buf,"start of recording (0)")!=-1) { marks.Add(0,buf); isyslog("markad [%i]: %s",recvnumber,buf); free(buf); } } else { marksAligned=true; } } void cMarkAdStandalone::AddMark(MarkAdMark *Mark) { if (!Mark) return; if (Mark->Position<1) return; marks.Add(Mark->Position,Mark->Comment); if (!marksAligned) { clMark *prevmark=marks.GetPrev(Mark->Position); if (!prevmark) return; if (prevmark->position==0) return; int MAXPOSDIFF = (int) (macontext.Video.Info.FramesPerSecond*60*13); // = 13 min if (abs(Mark->Position-prevmark->position)>MAXPOSDIFF) { clMark *firstmark=marks.Get(0); if (firstmark) { marks.Del(firstmark); marksAligned=true; } } else { marksAligned=true; } } } void cMarkAdStandalone::RateMarks() { } 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() { // Here we check the indexfile // if we have an index 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 (lastmaxframes==maxframes) return; // no new frames still last call! if (maxframes<(framecnt+200)) { // now we sleep and hopefully the index will grow dsyslog("markad [%i]: we are too fast, waiting %i secs",recvnumber,WAITTIME); sleep(WAITTIME); if (errno==EINTR) return; sleepcnt++; if (sleepcnt>=2) { esyslog("markad [%i]: no new data after %i seconds, skipping wait!", recvnumber,sleepcnt*WAITTIME); notenough=false; // something went wrong? } } else { sleepcnt=0; notenough=false; } lastmaxframes=maxframes; } while (notenough); } bool cMarkAdStandalone::ProcessFile(const char *Directory, int Number) { if (!Directory) return false; if (!Number) return false; 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; CheckIndex(); if (abort) return false; int dataread; dsyslog("markad [%i]: processing file %05i",recvnumber,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.General.VPid,tspkt,tslen,&pkt,&pktlen); if (len<0) { break; } else { if (pkt) { if (streaminfo->FindVideoInfos(&macontext,pkt,pktlen)) { if (!framecnt) { if (macontext.General.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264) { isyslog("markad [%i]: HDTV %i%c",recvnumber, macontext.Video.Info.Height,macontext.Video.Info.Interlaced ? 'i' : 'p'); } if (!marks.Load(Directory,macontext.Video.Info.FramesPerSecond,isTS)) { AddStartMark(); } else { marksAligned=true; } } //printf("%05i( %c )\n",framecnt,frametypes[macontext.Video.Info.Pict_Type]); framecnt++; if (macontext.Video.Info.Pict_Type==MA_I_TYPE) { lastiframe=framecnt-1; } } bool dRes=true; if ((decoder) && (bDecodeVideo)) dRes=decoder->DecodeVideo(&macontext,pkt,pktlen); if (dRes) { if ((framecnt-lastiframe)<=3) { //SaveFrame(lastiframe); // TODO: JUST FOR DEBUGGING! mark=video->Process(lastiframe); AddMark(mark); } } } 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.General.DPid,tspkt,tslen,&pkt,&pktlen); if (len<0) { break; } else { if (pkt) { if (streaminfo->FindAC3AudioInfos(&macontext,pkt,pktlen)) { if ((!isTS) && (!noticeVDR_AC3)) { dsyslog("markad [%i]: found AC3",recvnumber); 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.General.APid,tspkt,tslen,&pkt,&pktlen); if (len<0) { break; } else { if (pkt) { if (decoder->DecodeMP2(&macontext,pkt,pktlen)) { if ((!isTS) && (!noticeVDR_MP2)) { dsyslog("markad [%i]: found MP2",recvnumber); noticeVDR_MP2=true; } mark=audio->Process(lastiframe); AddMark(mark); } } tspkt+=len; tslen-=len; } } } CheckIndex(); 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); for (int i=1; i<=MaxFiles; i++) { if (abort) break; if (!ProcessFile(Directory,i)) { break; } } if (abort) { isyslog("markad [%i]: aborted",recvnumber); } else { if (lastiframe) { MarkAdMark tempmark; tempmark.Position=lastiframe; char *buf; if (asprintf(&buf,"stop of recording (%i)",lastiframe)!=-1) { tempmark.Comment=buf; AddMark(&tempmark); isyslog("markad [%i]: %s",recvnumber,buf); free(buf); } } gettimeofday(&tv2,&tz); long sec,usec; sec=tv2.tv_sec-tv1.tv_sec; usec=tv2.tv_usec-tv1.tv_usec; if (usec<0) { usec+=1000000; sec--; } bool bIndexError; if (marks.Save(Directory,macontext.Video.Info.FramesPerSecond,isTS,bBackupMarks,&bIndexError)) { if (bIndexError) { esyslog("markad [%i]: index doesn't match marks%s",recvnumber, isTS ? ", please report this" : ", please run genindex"); } } double etime,ftime=0,ptime=0; etime=sec+((double) usec/1000000); if (etime>0) ftime=framecnt/etime; if (macontext.Video.Info.FramesPerSecond>0) ptime=ftime/macontext.Video.Info.FramesPerSecond; isyslog("markad [%i]: elapsed time %.2fs, %i frames, %.1f fps, %.1f pps",recvnumber, 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 result=sscanf(line,"%*c %as %*s",&macontext.General.ChannelID); if (result==0 || result==EOF) macontext.General.ChannelID=NULL; if (macontext.General.ChannelID) { for (int i=0; i<(int) strlen(macontext.General.ChannelID); i++) { if (macontext.General.ChannelID[i]=='.') macontext.General.ChannelID[i]='_'; } } 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("markad [%i]: broadcasts aspectratio is not 4:3, disabling aspect ratio",recvnumber); } } if ((stream==2) && (!bIgnoreAudioInfo)) { if (type==5) { // if we have DolbyDigital 2.0 disable AC3 if (strchr(descr,'2')) { macontext.General.DPid.Num=0; isyslog("markad [%i]: broadcast with DolbyDigital2.0, disabling AC3 decoding",recvnumber); } // if we have DolbyDigital 5.1 disable video decoding if (strchr(descr,'5')) { bDecodeVideo=false; isyslog("markad [%i]: broadcast with DolbyDigital5.1, disabling video decoding",recvnumber); } } } } } } if (line) free(line); fclose(f); free(buf); if (!macontext.General.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.General.VPid.Type=MARKAD_PIDTYPE_VIDEO_H262; // just use the first pid if (!macontext.General.VPid.Num) macontext.General.VPid.Num=pid; break; case 0x3: case 0x4: // just use the first pid if (!macontext.General.APid.Num) macontext.General.APid.Num=pid; break; case 0x6: if (es) { if (es->Descriptor_Tag==0x6A) macontext.General.DPid.Num=pid; } break; case 0x1b: macontext.General.VPid.Type=MARKAD_PIDTYPE_VIDEO_H264; // just use the first pid if (!macontext.General.VPid.Num) macontext.General.VPid.Num=pid; break; } i+=(sizeof(struct STREAMINFO)+esinfo_len); } return true; } 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) { recvnumber=255; abort=false; noticeVDR_MP2=false; noticeVDR_AC3=false; sleepcnt=0; lastmaxframes=0; memset(&macontext,0,sizeof(macontext)); macontext.LogoDir=(char *) LogoDir; macontext.StandAlone.LogoExtraction=LogoExtraction; macontext.StandAlone.LogoWidth=LogoWidth; macontext.StandAlone.LogoHeight=LogoHeight; bDecodeVideo=DecodeVideo; bDecodeAudio=DecodeAudio; bIgnoreAudioInfo=IgnoreAudioInfo; bIgnoreVideoInfo=IgnoreVideoInfo; bBackupMarks=BackupMarks; if (LogoExtraction!=-1) { // just to be sure extraction works bDecodeVideo=true; bIgnoreAudioInfo=true; bIgnoreVideoInfo=true; } macontext.General.DPid.Type=MARKAD_PIDTYPE_AUDIO_AC3; macontext.General.APid.Type=MARKAD_PIDTYPE_AUDIO_MP2; isyslog("markad [%i]: starting v%s",recvnumber,VERSION); if (!bDecodeAudio) { isyslog("markad [%i]: audio decoding disabled by user",recvnumber); } if (!bDecodeVideo) { isyslog("markad [%i]: video decoding disabled by user",recvnumber); } if (bIgnoreAudioInfo) { isyslog("markad [%i]: audio info usage disabled by user",recvnumber); } if (bIgnoreVideoInfo) { isyslog("markad [%i]: video info usage disabled by user",recvnumber); } if (!CheckTS(Directory)) { video_demux=NULL; ac3_demux=NULL; mp2_demux=NULL; decoder=NULL; video=NULL; audio=NULL; return; } if (isTS) { if (!CheckPATPMT(Directory)) { esyslog("markad [%i]: no PAT/PMT found -> nothing to process",recvnumber); abort=true; } macontext.General.APid.Num=0; if (asprintf(&indexFile,"%s/index",Directory)==-1) indexFile=NULL; } else { macontext.General.APid.Num=-1; macontext.General.DPid.Num=-1; macontext.General.VPid.Num=-1; if (CheckVDRHD(Directory)) { macontext.General.VPid.Type=MARKAD_PIDTYPE_VIDEO_H264; } else { macontext.General.VPid.Type=MARKAD_PIDTYPE_VIDEO_H262; } if (asprintf(&indexFile,"%s/index.vdr",Directory)==-1) indexFile=NULL; } if (!LoadInfo(Directory)) { if (bDecodeVideo) esyslog("markad [%i]: failed loading info - logo detection disabled",recvnumber); } if (MarkFileName[0]) marks.SetFileName(MarkFileName); if (macontext.General.VPid.Num) { if (isTS) { dsyslog("markad [%i]: using %s-video (0x%04x)",recvnumber, macontext.General.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264 ? "H264": "H262", macontext.General.VPid.Num); } else { dsyslog("markad [%i]: using %s-video", recvnumber,macontext.General.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264 ? "H264": "H262"); } video_demux = new cMarkAdDemux(recvnumber); } else { video_demux=NULL; } if (macontext.General.APid.Num) { if (macontext.General.APid.Num!=-1) dsyslog("markad [%i]: using MP2 (0x%04x)",recvnumber,macontext.General.APid.Num); mp2_demux = new cMarkAdDemux(recvnumber); } else { mp2_demux=NULL; } if (macontext.General.DPid.Num) { if (macontext.General.DPid.Num!=-1) dsyslog("markad [%i]: using AC3 (0x%04x)",recvnumber,macontext.General.DPid.Num); ac3_demux = new cMarkAdDemux(recvnumber); } else { ac3_demux=NULL; } if (!abort) { decoder = new cMarkAdDecoder(recvnumber,macontext.General.VPid.Type==MARKAD_PIDTYPE_VIDEO_H264, macontext.General.APid.Num!=0,macontext.General.DPid.Num!=0); video = new cMarkAdVideo(recvnumber,&macontext); audio = new cMarkAdAudio(recvnumber,&macontext); streaminfo = new cMarkAdStreamInfo; } else { decoder=NULL; video=NULL; audio=NULL; streaminfo=NULL; } marksAligned=false; framecnt=0; lastiframe=0; } cMarkAdStandalone::~cMarkAdStandalone() { if (macontext.General.ChannelID) free(macontext.General.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; } 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=