From c2966475942fcb9d4b8d41dbf026ff57630a1ad6 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Tue, 6 Jan 2009 20:31:53 +0100 Subject: =?UTF-8?q?Version=201.7.3=20-=20Updated=20the=20Russian=20OSD=20t?= =?UTF-8?q?exts=20(thanks=20to=20Oleg=20Roitburd).=20-=20Fixed=20handling?= =?UTF-8?q?=20the=20'pointer=20field'=20in=20generating=20and=20parsing=20?= =?UTF-8?q?PAT/PMT=20(thanks=20to=20=20=20Frank=20Schmirler).=20-=20Fixed?= =?UTF-8?q?=20handling=20modulation=20types=20for=20DVB-S=20transponders?= =?UTF-8?q?=20when=20processing=20the=20NIT.=20-=20Changed=20cDvbDevice::G?= =?UTF-8?q?rabImage()=20to=20use=20V4L2=20(thanks=20to=20Marco=20Schl=FC?= =?UTF-8?q?=DFler).=20-=20Added=20a=20poll=20to=20cDvbDevice::PlayVideo()?= =?UTF-8?q?=20and=20cDvbDevice::PlayAudio()=20to=20avoid=20=20=20excessive?= =?UTF-8?q?=20CPU=20load=20(this=20is=20just=20a=20makeshift=20solution=20?= =?UTF-8?q?until=20the=20FF=20DVB=20cards=20=20=20can=20play=20TS=20direct?= =?UTF-8?q?ly).=20-=20The=20recording=20format=20is=20now=20Transport=20St?= =?UTF-8?q?ream.=20Existing=20recordings=20in=20PES=20format=20=20=20can?= =?UTF-8?q?=20still=20be=20replayed=20and=20edited,=20but=20new=20recordin?= =?UTF-8?q?gs=20are=20done=20in=20TS.=20=20=20All=20code=20for=20recording?= =?UTF-8?q?=20in=20PES=20has=20been=20removed.=20=20=20The=20following=20c?= =?UTF-8?q?hanges=20were=20made=20to=20switch=20to=20TS=20recording=20form?= =?UTF-8?q?at:=20=20=20+=20The=20index=20file=20format=20has=20been=20chan?= =?UTF-8?q?ged=20to=20support=20file=20sizes=20of=20up=20to=201TB=20=20=20?= =?UTF-8?q?=20=20(previously=202GB),=20and=20up=20to=2065535=20separate=20?= =?UTF-8?q?files=20per=20recording=20(previously=20=20=20=20=20255).=20=20?= =?UTF-8?q?=20+=20The=20recording=20file=20names=20are=20now=20of=20the=20?= =?UTF-8?q?form=2000001.ts=20(previously=20001.vdr).=20=20=20+=20The=20fra?= =?UTF-8?q?me=20rate=20is=20now=20detected=20by=20looking=20at=20two=20sub?= =?UTF-8?q?sequent=20PTS=20values.=20=20=20=20=20The=20"frame=20duration"?= =?UTF-8?q?=20(in=20multiples=20of=201/90000)=20is=20stored=20in=20the=20i?= =?UTF-8?q?nfo.vdr=20=20=20=20=20file=20using=20the=20new=20tag=20F=20(tha?= =?UTF-8?q?nks=20to=20Artur=20Skawina=20for=20helping=20to=20get=20the=20?= =?UTF-8?q?=20=20=20=20IndexToHMSF()=20calculation=20right).=20=20=20+=20S?= =?UTF-8?q?everal=20functions=20now=20have=20an=20additional=20parameter?= =?UTF-8?q?=20FramesPerSecond.=20=20=20+=20Several=20functions=20now=20hav?= =?UTF-8?q?e=20an=20additional=20parameter=20IsPesRecording.=20=20=20+=20T?= =?UTF-8?q?he=20functionality=20of=20cFileWriter=20was=20moved=20into=20cR?= =?UTF-8?q?ecorder,=20and=20cRemux=20is=20=20=20=20=20now=20obsolete.=20Th?= =?UTF-8?q?is=20also=20avoids=20one=20level=20of=20data=20copying=20while?= =?UTF-8?q?=20recording.=20=20=20+=20cRemux,=20cRingBufferLinearPes,=20cTS?= =?UTF-8?q?2PES=20and=20all=20c*Repacker=20classes=20have=20been=20=20=20?= =?UTF-8?q?=20=20removed.=20=20=20+=20A=20PAT/PMT=20is=20inserted=20before?= =?UTF-8?q?=20every=20independent=20frame,=20so=20that=20no=20extra=20=20?= =?UTF-8?q?=20=20=20measures=20need=20to=20be=20taken=20when=20editing=20a?= =?UTF-8?q?=20recording.=20=20=20+=20The=20directory=20name=20for=20a=20re?= =?UTF-8?q?cording=20has=20been=20changed=20from=20=20=20=20=20YYYY-MM-DD-?= =?UTF-8?q?hh[.:]mm.pr.lt.rec=20(pr=3Dpriority,=20lt=3Dlifetime)=20to=20?= =?UTF-8?q?=20=20=20=20YYYY-MM-DD-hh.mm.ch-ri.rec=20(ch=3Dchannel,=20ri=3D?= =?UTF-8?q?resumeId).=20=20=20=20=20Priority=20and=20Lifetime=20are=20now?= =?UTF-8?q?=20stored=20in=20the=20info.vdr=20file=20with=20the=20new=20=20?= =?UTF-8?q?=20=20=20tags=20P=20and=20L=20(if=20no=20such=20file=20exists,?= =?UTF-8?q?=20the=20maximum=20values=20are=20assumed=20by=20=20=20=20=20de?= =?UTF-8?q?fault,=20which=20avoids=20inadvertently=20deleting=20a=20record?= =?UTF-8?q?ing=20if=20disk=20space=20=20=20=20=20is=20low).=20No=20longer?= =?UTF-8?q?=20storing=20Priority=20and=20Lifetime=20in=20the=20directory?= =?UTF-8?q?=20name=20=20=20=20=20avoids=20starting=20a=20new=20recording?= =?UTF-8?q?=20if=20one=20of=20these=20is=20changed=20in=20the=20timer=20?= =?UTF-8?q?=20=20=20=20and=20the=20recording=20is=20re-started=20for=20som?= =?UTF-8?q?e=20reason.=20=20=20=20=20Instead=20of=20Priority=20and=20Lifet?= =?UTF-8?q?ime,=20the=20directory=20name=20now=20contains=20the=20=20=20?= =?UTF-8?q?=20=20channel=20number=20from=20which=20the=20recording=20was?= =?UTF-8?q?=20made,=20and=20the=20"resume=20id"=20of=20=20=20=20=20this=20?= =?UTF-8?q?instance=20of=20VDR.=20This=20avoids=20problems=20if=20several?= =?UTF-8?q?=20VDR=20instances=20record=20=20=20=20=20the=20same=20show=20o?= =?UTF-8?q?n=20different=20channels,=20or=20even=20on=20the=20same=20chann?= =?UTF-8?q?el.=20=20=20=20=20The=20'-'=20between=20channel=20number=20and?= =?UTF-8?q?=20resumeId=20prevents=20older=20versions=20of=20=20=20=20=20VD?= =?UTF-8?q?R=20from=20"seeing"=20these=20recordings,=20which=20makes=20sur?= =?UTF-8?q?e=20they=20won't=20even=20try=20=20=20=20=20to=20replay=20them,?= =?UTF-8?q?=20or=20remove=20them=20in=20case=20the=20disk=20runs=20full.?= =?UTF-8?q?=20=20=20+=20The=20semantics=20of=20PlayTs*()=20have=20been=20c?= =?UTF-8?q?hanged.=20These=20functions=20are=20now=20=20=20=20=20required?= =?UTF-8?q?=20to=20return=20the=20given=20Length=20(which=20is=20TS=5FSIZE?= =?UTF-8?q?)=20if=20they=20have=20=20=20=20=20processed=20the=20TS=20packe?= =?UTF-8?q?t.=20=20=20+=20The=20files=20"index",=20"info",=20"marks"=20and?= =?UTF-8?q?=20"resume"=20within=20a=20TS=20recording=20=20=20=20=20directo?= =?UTF-8?q?ry=20are=20now=20created=20without=20the=20".vdr"=20extension.?= =?UTF-8?q?=20=20=20+=20The=20"resume"=20file=20is=20no=20longer=20a=20bin?= =?UTF-8?q?ary=20file,=20but=20contains=20tagged=20lines=20=20=20=20=20to?= =?UTF-8?q?=20be=20able=20to=20store=20additional=20information,=20like=20?= =?UTF-8?q?the=20selected=20audio=20or=20=20=20=20=20subtitle=20track.=20?= =?UTF-8?q?=20=20+=20cDevice::StillPicture()=20will=20now=20be=20called=20?= =?UTF-8?q?with=20either=20TS=20or=20PES=20data.=20=20=20+=20cDvbPlayer::G?= =?UTF-8?q?oto()=20no=20longer=20appends=20a=20"sequence=20end=20code"=20t?= =?UTF-8?q?o=20the=20data.=20=20=20=20=20If=20the=20output=20device=20need?= =?UTF-8?q?s=20this,=20it=20has=20to=20take=20care=20of=20it=20by=20itself?= =?UTF-8?q?.=20-=20Fixed=20cPatPmtParser::ParsePmt()=20to=20reset=20vpid?= =?UTF-8?q?=20and=20vtype=20when=20switching=20from=20=20=20a=20video=20to?= =?UTF-8?q?=20an=20audio=20channel=20(thanks=20to=20Reinhard=20Nissl).=20-?= =?UTF-8?q?=20cDvbDevice=20now=20uses=20the=20FE=5FCAN=5F2G=5FMODULATION?= =?UTF-8?q?=20flag=20to=20determine=20whether=20a=20device=20=20=20can=20h?= =?UTF-8?q?andle=20DVB-S2.=20The=20#define=20is=20still=20there=20to=20all?= =?UTF-8?q?ow=20people=20with=20older=20drivers=20=20=20who=20don't=20need?= =?UTF-8?q?=20DVB-S2=20to=20use=20this=20version=20without=20pathcing.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- recording.c | 353 +++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 266 insertions(+), 87 deletions(-) (limited to 'recording.c') diff --git a/recording.c b/recording.c index 340e68a..2948343 100644 --- a/recording.c +++ b/recording.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.c 2.3 2008/06/12 21:46:08 kls Exp $ + * $Id: recording.c 2.4 2009/01/06 14:41:11 kls Exp $ */ #include "recording.h" @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -19,7 +20,6 @@ #include "channels.h" #include "i18n.h" #include "interface.h" -#include "remux.h" //XXX+ I_FRAME #include "skins.h" #include "tools.h" #include "videodir.h" @@ -37,15 +37,17 @@ #define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT #define NAMEFORMAT "%s/%s/" DATAFORMAT */ -#define DATAFORMAT "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT -#define NAMEFORMAT "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT +#define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT +#define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT +#define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT +#define NAMEFORMATTS "%s/%s/" DATAFORMATTS -#define RESUMEFILESUFFIX "/resume%s%s.vdr" +#define RESUMEFILESUFFIX "/resume%s%s" #ifdef SUMMARYFALLBACK #define SUMMARYFILESUFFIX "/summary.vdr" #endif -#define INFOFILESUFFIX "/info.vdr" -#define MARKSFILESUFFIX "/marks.vdr" +#define INFOFILESUFFIX "/info" +#define MARKSFILESUFFIX "/marks" #define MINDISKSPACE 1024 // MB @@ -202,12 +204,14 @@ void AssertFreeDiskSpace(int Priority, bool Force) // --- cResumeFile ----------------------------------------------------------- -cResumeFile::cResumeFile(const char *FileName) +cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording) { - fileName = MALLOC(char, strlen(FileName) + strlen(RESUMEFILESUFFIX) + 1); + isPesRecording = IsPesRecording; + const char *Suffix = isPesRecording ? RESUMEFILESUFFIX ".vdr" : RESUMEFILESUFFIX; + fileName = MALLOC(char, strlen(FileName) + strlen(Suffix) + 1); if (fileName) { strcpy(fileName, FileName); - sprintf(fileName + strlen(fileName), RESUMEFILESUFFIX, Setup.ResumeID ? "." : "", Setup.ResumeID ? *itoa(Setup.ResumeID) : ""); + sprintf(fileName + strlen(fileName), Suffix, Setup.ResumeID ? "." : "", Setup.ResumeID ? *itoa(Setup.ResumeID) : ""); } else esyslog("ERROR: can't allocate memory for resume file name"); @@ -227,16 +231,37 @@ int cResumeFile::Read(void) if ((st.st_mode & S_IWUSR) == 0) // no write access, assume no resume return -1; } - int f = open(fileName, O_RDONLY); - if (f >= 0) { - if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) { - resume = -1; + if (isPesRecording) { + int f = open(fileName, O_RDONLY); + if (f >= 0) { + if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) { + resume = -1; + LOG_ERROR_STR(fileName); + } + close(f); + } + else if (errno != ENOENT) LOG_ERROR_STR(fileName); + } + else { + FILE *f = fopen(fileName, "r"); + if (f) { + cReadLine ReadLine; + char *s; + int line = 0; + while ((s = ReadLine.Read(f)) != NULL) { + ++line; + char *t = skipspace(s + 1); + switch (*s) { + case 'I': resume = atoi(t); + break; + } + } + fclose(f); } - close(f); + else if (errno != ENOENT) + LOG_ERROR_STR(fileName); } - else if (errno != ENOENT) - LOG_ERROR_STR(fileName); } return resume; } @@ -244,12 +269,24 @@ int cResumeFile::Read(void) bool cResumeFile::Save(int Index) { if (fileName) { - int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE); - if (f >= 0) { - if (safe_write(f, &Index, sizeof(Index)) < 0) + if (isPesRecording) { + int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE); + if (f >= 0) { + if (safe_write(f, &Index, sizeof(Index)) < 0) + LOG_ERROR_STR(fileName); + close(f); + Recordings.ResetResume(fileName); + return true; + } + } + else { + FILE *f = fopen(fileName, "w"); + if (f) { + fprintf(f, "I %d\n", Index); + fclose(f); + } + else LOG_ERROR_STR(fileName); - close(f); - Recordings.ResetResume(fileName); return true; } } @@ -274,6 +311,10 @@ cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event) ownEvent = Event ? NULL : new cEvent(0); event = ownEvent ? ownEvent : Event; aux = NULL; + framesPerSecond = DEFAULTFRAMESPERSECOND; + priority = MAXPRIORITY; + lifetime = MAXLIFETIME; + fileName = NULL; if (Channel) { // Since the EPG data's component records can carry only a single // language code, let's see whether the channel's PID data has @@ -322,11 +363,25 @@ cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event) } } +cRecordingInfo::cRecordingInfo(const char *FileName) +{ + channelID = tChannelID::InvalidID; + channelName = NULL; + ownEvent = new cEvent(0); + event = ownEvent; + aux = NULL; + framesPerSecond = DEFAULTFRAMESPERSECOND; + priority = MAXPRIORITY; + lifetime = MAXLIFETIME; + fileName = strdup(cString::sprintf("%s%s", FileName, INFOFILESUFFIX)); +} + cRecordingInfo::~cRecordingInfo() { delete ownEvent; free(aux); free(channelName); + free(fileName); } void cRecordingInfo::SetData(const char *Title, const char *ShortText, const char *Description) @@ -345,6 +400,11 @@ void cRecordingInfo::SetAux(const char *Aux) aux = Aux ? strdup(Aux) : NULL; } +void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond) +{ + framesPerSecond = FramesPerSecond; +} + bool cRecordingInfo::Read(FILE *f) { if (ownEvent) { @@ -382,6 +442,12 @@ bool cRecordingInfo::Read(FILE *f) } } break; + case 'F': framesPerSecond = atof(t); + break; + case 'L': lifetime = atoi(t); + break; + case 'P': priority = atoi(t); + break; case '@': free(aux); aux = strdup(t); break; @@ -403,11 +469,48 @@ bool cRecordingInfo::Write(FILE *f, const char *Prefix) const if (channelID.Valid()) fprintf(f, "%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ? " " : "", channelName ? channelName : ""); event->Dump(f, Prefix, true); + fprintf(f, "%sF %.10g\n", Prefix, framesPerSecond); + fprintf(f, "%sP %d\n", Prefix, priority); + fprintf(f, "%sL %d\n", Prefix, lifetime); if (aux) fprintf(f, "%s@ %s\n", Prefix, aux); return true; } +bool cRecordingInfo::Read(void) +{ + bool Result = false; + if (fileName) { + FILE *f = fopen(fileName, "r"); + if (f) { + if (Read(f)) + Result = true; + else + esyslog("ERROR: EPG data problem in file %s", fileName); + fclose(f); + } + else if (errno != ENOENT) + LOG_ERROR_STR(fileName); + } + return Result; +} + +bool cRecordingInfo::Write(void) const +{ + bool Result = false; + if (fileName) { + cSafeFile f(fileName); + if (f.Open()) { + if (Write(f)) + Result = true; + f.Close(); + } + else + LOG_ERROR_STR(fileName); + } + return Result; +} + // --- cRecording ------------------------------------------------------------ #define RESUME_NOT_INITIALIZED (-2) @@ -497,6 +600,10 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event) fileName = NULL; name = NULL; fileSizeMB = -1; // unknown + channel = Timer->Channel()->Number(); + resumeId = Setup.ResumeID; + isPesRecording = false; + framesPerSecond = DEFAULTFRAMESPERSECOND; deleted = 0; // set up the actual name: const char *Title = Event ? Event->Title() : NULL; @@ -542,12 +649,20 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event) // handle info: info = new cRecordingInfo(Timer->Channel(), Event); info->SetAux(Timer->Aux()); + info->priority = priority; + info->lifetime = lifetime; } cRecording::cRecording(const char *FileName) { resume = RESUME_NOT_INITIALIZED; fileSizeMB = -1; // unknown + channel = -1; + resumeId = -1; + priority = MAXPRIORITY; // assume maximum in case there is no info file + lifetime = MAXLIFETIME; + isPesRecording = false; + framesPerSecond = DEFAULTFRAMESPERSECOND; deleted = 0; titleBuffer = NULL; sortBuffer = NULL; @@ -562,7 +677,8 @@ cRecording::cRecording(const char *FileName) struct tm tm_r; struct tm t = *localtime_r(&now, &tm_r); // this initializes the time zone in 't' t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting - if (7 == sscanf(p + 1, DATAFORMAT, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) { + if (7 == sscanf(p + 1, DATAFORMATTS, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &channel, &resumeId) + || 7 == sscanf(p + 1, DATAFORMATPES, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) { t.tm_year -= 1900; t.tm_mon--; t.tm_sec = 0; @@ -571,14 +687,22 @@ cRecording::cRecording(const char *FileName) strncpy(name, FileName, p - FileName); name[p - FileName] = 0; name = ExchangeChars(name, false); + isPesRecording = resumeId < 0; } + else + return; GetResume(); // read an optional info file: - cString InfoFileName = cString::sprintf("%s%s", fileName, INFOFILESUFFIX); + cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX); FILE *f = fopen(InfoFileName, "r"); if (f) { if (!info->Read(f)) esyslog("ERROR: EPG data problem in file %s", *InfoFileName); + else if (!isPesRecording) { + priority = info->priority; + lifetime = info->lifetime; + framesPerSecond = info->framesPerSecond; + } fclose(f); } else if (errno != ENOENT) @@ -683,7 +807,7 @@ char *cRecording::SortName(void) const int cRecording::GetResume(void) const { if (resume == RESUME_NOT_INITIALIZED) { - cResumeFile ResumeFile(FileName()); + cResumeFile ResumeFile(FileName(), isPesRecording); resume = ResumeFile.Read(); } return resume; @@ -700,8 +824,11 @@ const char *cRecording::FileName(void) const if (!fileName) { struct tm tm_r; struct tm *t = localtime_r(&start, &tm_r); + const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS; + int ch = isPesRecording ? priority : channel; + int ri = isPesRecording ? lifetime : resumeId; name = ExchangeChars(name, true); - fileName = strdup(cString::sprintf(NAMEFORMAT, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, priority, lifetime)); + fileName = strdup(cString::sprintf(fmt, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri)); name = ExchangeChars(name, false); } return fileName; @@ -789,7 +916,7 @@ bool cRecording::IsEdited(void) const bool cRecording::WriteInfo(void) { - cString InfoFileName = cString::sprintf("%s%s", fileName, INFOFILESUFFIX); + cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX); FILE *f = fopen(InfoFileName, "w"); if (f) { info->Write(f); @@ -1061,10 +1188,14 @@ void cRecordings::ResetResume(const char *ResumeFileName) // --- cMark ----------------------------------------------------------------- -cMark::cMark(int Position, const char *Comment) +double MarkFramesPerSecond = DEFAULTFRAMESPERSECOND; +cMutex MutexMarkFramesPerSecond; + +cMark::cMark(int Position, const char *Comment, double FramesPerSecond) { position = Position; comment = Comment ? strdup(Comment) : NULL; + framesPerSecond = FramesPerSecond; } cMark::~cMark() @@ -1074,14 +1205,15 @@ cMark::~cMark() cString cMark::ToText(void) { - return cString::sprintf("%s%s%s\n", *IndexToHMSF(position, true), comment ? " " : "", comment ? comment : ""); + return cString::sprintf("%s%s%s\n", *IndexToHMSF(position, true, framesPerSecond), comment ? " " : "", comment ? comment : ""); } bool cMark::Parse(const char *s) { free(comment); comment = NULL; - position = HMSFToIndex(s); + framesPerSecond = MarkFramesPerSecond; + position = HMSFToIndex(s, framesPerSecond); const char *p = strchr(s, ' '); if (p) { p = skipspace(p); @@ -1098,9 +1230,12 @@ bool cMark::Save(FILE *f) // --- cMarks ---------------------------------------------------------------- -bool cMarks::Load(const char *RecordingFileName) +bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording) { - if (cConfig::Load(AddDirectory(RecordingFileName, MARKSFILESUFFIX))) { + cMutexLock MutexLock(&MutexMarkFramesPerSecond); + framesPerSecond = FramesPerSecond; + MarkFramesPerSecond = framesPerSecond; + if (cConfig::Load(AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX))) { Sort(); return true; } @@ -1123,7 +1258,7 @@ cMark *cMarks::Add(int Position) { cMark *m = Get(Position); if (!m) { - cConfig::Add(m = new cMark(Position)); + cConfig::Add(m = new cMark(Position, NULL, framesPerSecond)); Sort(); } return m; @@ -1169,12 +1304,9 @@ void cRecordingUserCommand::InvokeCommand(const char *State, const char *Recordi } } -// --- XXX+ - -//XXX+ somewhere else??? // --- cIndexFile ------------------------------------------------------------ -#define INDEXFILESUFFIX "/index.vdr" +#define INDEXFILESUFFIX "/index" // The number of frames to stay off the end in case of time shift: #define INDEXSAFETYLIMIT 150 // frames @@ -1185,33 +1317,56 @@ void cRecordingUserCommand::InvokeCommand(const char *State, const char *Recordi // The minimum age of an index file for considering it no longer to be written: #define MININDEXAGE 3600 // seconds -cIndexFile::cIndexFile(const char *FileName, bool Record) -:resumeFile(FileName) +struct tIndexPes { + uint32_t offset; + uchar type; + uchar number; + uint16_t reserved; + }; + +struct tIndexTs { + uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!) + int reserved:7; // reserved for future use + int independent:1; // marks frames that can be displayed by themselves (for trick modes) + uint16_t number:16; // up to 64K files per recording + tIndexTs(off_t Offset, bool Independent, uint16_t Number) + { + offset = Offset; + reserved = 0; + independent = Independent; + number = Number; + } + }; + +cIndexFile::cIndexFile(const char *FileName, bool Record, bool IsPesRecording) +:resumeFile(FileName, IsPesRecording) { f = -1; fileName = NULL; size = 0; last = -1; index = NULL; + isPesRecording = IsPesRecording; if (FileName) { - fileName = MALLOC(char, strlen(FileName) + strlen(INDEXFILESUFFIX) + 1); + const char *Suffix = isPesRecording ? INDEXFILESUFFIX ".vdr" : INDEXFILESUFFIX; + fileName = MALLOC(char, strlen(FileName) + strlen(Suffix) + 1); if (fileName) { strcpy(fileName, FileName); char *pFileExt = fileName + strlen(fileName); - strcpy(pFileExt, INDEXFILESUFFIX); + strcpy(pFileExt, Suffix); int delta = 0; if (access(fileName, R_OK) == 0) { struct stat buf; if (stat(fileName, &buf) == 0) { - delta = buf.st_size % sizeof(tIndex); + delta = buf.st_size % sizeof(tIndexTs); if (delta) { - delta = sizeof(tIndex) - delta; - esyslog("ERROR: invalid file size (%ld) in '%s'", buf.st_size, fileName); + delta = sizeof(tIndexTs) - delta; + esyslog("ERROR: invalid file size (%lld) in '%s'", buf.st_size, fileName); } - last = (buf.st_size + delta) / sizeof(tIndex) - 1; + last = (buf.st_size + delta) / sizeof(tIndexTs) - 1; if (!Record && last >= 0) { size = last + 1; - index = MALLOC(tIndex, size); + index = MALLOC(tIndexTs, size); if (index) { f = open(fileName, O_RDONLY); if (f >= 0) { @@ -1223,12 +1378,14 @@ cIndexFile::cIndexFile(const char *FileName, bool Record) f = -1; } // we don't close f here, see CatchUp()! + else if (isPesRecording) + ConvertFromPes(index, size); } else LOG_ERROR_STR(fileName); } else - esyslog("ERROR: can't allocate %zd bytes for index '%s'", size * sizeof(tIndex), fileName); + esyslog("ERROR: can't allocate %zd bytes for index '%s'", size * sizeof(tIndexTs), fileName); } } else @@ -1261,6 +1418,18 @@ cIndexFile::~cIndexFile() free(index); } +void cIndexFile::ConvertFromPes(tIndexTs *IndexTs, int Count) +{ + tIndexPes IndexPes; + while (Count-- > 0) { + memcpy(&IndexPes, IndexTs, sizeof(IndexPes)); + IndexTs->offset = IndexPes.offset; + IndexTs->independent = IndexPes.type == 1; // I_FRAME + IndexTs->number = IndexPes.number; + IndexTs++; + } +} + bool cIndexFile::CatchUp(int Index) { // returns true unless something really goes wrong, so that 'index' becomes NULL @@ -1275,17 +1444,17 @@ bool cIndexFile::CatchUp(int Index) f = -1; break; } - int newLast = buf.st_size / sizeof(tIndex) - 1; + int newLast = buf.st_size / sizeof(tIndexTs) - 1; if (newLast > last) { if (size <= newLast) { size *= 2; if (size <= newLast) size = newLast + 1; } - index = (tIndex *)realloc(index, size * sizeof(tIndex)); + index = (tIndexTs *)realloc(index, size * sizeof(tIndexTs)); if (index) { - int offset = (last + 1) * sizeof(tIndex); - int delta = (newLast - last) * sizeof(tIndex); + int offset = (last + 1) * sizeof(tIndexTs); + int delta = (newLast - last) * sizeof(tIndexTs); if (lseek(f, offset, SEEK_SET) == offset) { if (safe_read(f, &index[last + 1], delta) != delta) { esyslog("ERROR: can't read from index"); @@ -1295,6 +1464,8 @@ bool cIndexFile::CatchUp(int Index) f = -1; break; } + if (isPesRecording) + ConvertFromPes(&index[last + 1], newLast - last); last = newLast; } else @@ -1314,10 +1485,10 @@ bool cIndexFile::CatchUp(int Index) return index != NULL; } -bool cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset) +bool cIndexFile::Write(bool Independent, uint16_t FileNumber, off_t FileOffset) { if (f >= 0) { - tIndex i = { FileOffset, PictureType, FileNumber, 0 }; + tIndexTs i(FileOffset, Independent, FileNumber); if (safe_write(f, &i, sizeof(i)) < 0) { LOG_ERROR_STR(fileName); close(f); @@ -1329,14 +1500,14 @@ bool cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset) return f >= 0; } -bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType, int *Length) +bool cIndexFile::Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent, int *Length) { if (CatchUp(Index)) { if (Index >= 0 && Index < last) { *FileNumber = index[Index].number; *FileOffset = index[Index].offset; - if (PictureType) - *PictureType = index[Index].type; + if (Independent) + *Independent = index[Index].independent; if (Length) { int fn = index[Index + 1].number; int fo = index[Index + 1].offset; @@ -1351,24 +1522,24 @@ bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *Pictu return false; } -int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length, bool StayOffEnd) +int cIndexFile::GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber, off_t *FileOffset, int *Length, bool StayOffEnd) { if (CatchUp()) { int d = Forward ? 1 : -1; for (;;) { Index += d; if (Index >= 0 && Index < last - ((Forward && StayOffEnd) ? INDEXSAFETYLIMIT : 0)) { - if (index[Index].type == I_FRAME) { - if (FileNumber) - *FileNumber = index[Index].number; - else - FileNumber = &index[Index].number; - if (FileOffset) - *FileOffset = index[Index].offset; - else - FileOffset = &index[Index].offset; + if (index[Index].independent) { + uint16_t fn; + if (!FileNumber) + FileNumber = &fn; + off_t fo; + if (!FileOffset) + FileOffset = &fo; + *FileNumber = index[Index].number; + *FileOffset = index[Index].offset; if (Length) { - // all recordings end with a non-I_FRAME, so the following should be safe: + // all recordings end with a non-independent frame, so the following should be safe: int fn = index[Index + 1].number; int fo = index[Index + 1].offset; if (fn == *FileNumber) @@ -1388,13 +1559,13 @@ int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *F return -1; } -int cIndexFile::Get(uchar FileNumber, int FileOffset) +int cIndexFile::Get(uint16_t FileNumber, off_t FileOffset) { if (CatchUp()) { //TODO implement binary search! int i; for (i = 0; i < last; i++) { - if (index[i].number > FileNumber || (index[i].number == FileNumber) && index[i].offset >= FileOffset) + if (index[i].number > FileNumber || (index[i].number == FileNumber) && off_t(index[i].offset) >= FileOffset) break; } return i; @@ -1409,16 +1580,19 @@ bool cIndexFile::IsStillRecording() // --- cFileName ------------------------------------------------------------- -#define MAXFILESPERRECORDING 255 -#define RECORDFILESUFFIX "/%03d.vdr" +#define MAXFILESPERRECORDINGPES 255 +#define RECORDFILESUFFIXPES "/%03d.vdr" +#define MAXFILESPERRECORDINGTS 65535 +#define RECORDFILESUFFIXTS "/%05d.ts" #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety... -cFileName::cFileName(const char *FileName, bool Record, bool Blocking) +cFileName::cFileName(const char *FileName, bool Record, bool Blocking, bool IsPesRecording) { file = NULL; fileNumber = 0; record = Record; blocking = Blocking; + isPesRecording = IsPesRecording; // Prepare the file name: fileName = MALLOC(char, strlen(FileName) + RECORDFILESUFFIXLEN); if (!fileName) { @@ -1442,14 +1616,14 @@ cUnbufferedFile *cFileName::Open(void) int BlockingFlag = blocking ? 0 : O_NONBLOCK; if (record) { dsyslog("recording to '%s'", fileName); - file = OpenVideoFile(fileName, O_RDWR | O_CREAT | BlockingFlag); + file = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag); if (!file) LOG_ERROR_STR(fileName); } else { if (access(fileName, R_OK) == 0) { dsyslog("playing '%s'", fileName); - file = cUnbufferedFile::Create(fileName, O_RDONLY | BlockingFlag); + file = cUnbufferedFile::Create(fileName, O_RDONLY | O_LARGEFILE | BlockingFlag); if (!file) LOG_ERROR_STR(fileName); } @@ -1469,13 +1643,14 @@ void cFileName::Close(void) } } -cUnbufferedFile *cFileName::SetOffset(int Number, int Offset) +cUnbufferedFile *cFileName::SetOffset(int Number, off_t Offset) { if (fileNumber != Number) Close(); - if (0 < Number && Number <= MAXFILESPERRECORDING) { + int MaxFilesPerRecording = isPesRecording ? MAXFILESPERRECORDINGPES : MAXFILESPERRECORDINGTS; + if (0 < Number && Number <= MaxFilesPerRecording) { fileNumber = Number; - sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber); + sprintf(pFileNumber, isPesRecording ? RECORDFILESUFFIXPES : RECORDFILESUFFIXTS, fileNumber); if (record) { if (access(fileName, F_OK) == 0) { // files exists, check if it has non-zero size @@ -1506,7 +1681,7 @@ cUnbufferedFile *cFileName::SetOffset(int Number, int Offset) } return file; } - esyslog("ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING); + esyslog("ERROR: max number of files (%d) exceeded", MaxFilesPerRecording); return NULL; } @@ -1517,11 +1692,12 @@ cUnbufferedFile *cFileName::NextFile(void) // --- Index stuff ----------------------------------------------------------- -cString IndexToHMSF(int Index, bool WithFrame) +cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond) { char buffer[16]; - int f = (Index % FRAMESPERSEC) + 1; - int s = (Index / FRAMESPERSEC); + double Seconds; + int f = modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond + 1; + int s = int(Seconds); int m = s / 60 % 60; int h = s / 3600; s %= 60; @@ -1529,17 +1705,20 @@ cString IndexToHMSF(int Index, bool WithFrame) return buffer; } -int HMSFToIndex(const char *HMSF) +int HMSFToIndex(const char *HMSF, double FramesPerSecond) { - int h, m, s, f = 0; - if (3 <= sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f)) - return (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1; + int h, m, s, f = 1; + int n = sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f); + if (n == 1) + return h - 1; // plain frame number + if (n >= 3) + return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f - 1; return 0; } -int SecondsToFrames(int Seconds) +int SecondsToFrames(int Seconds, double FramesPerSecond) { - return Seconds * FRAMESPERSEC; + return round(Seconds * FramesPerSecond); } // --- ReadFrame ------------------------------------------------------------- -- cgit v1.2.3