diff options
author | etobi <git@e-tobi.net> | 2011-10-02 23:10:29 +0200 |
---|---|---|
committer | etobi <git@e-tobi.net> | 2011-10-02 23:10:29 +0200 |
commit | 0e7880576c0dcf84eaa53cd9c73bc2b5bf1e8d01 (patch) | |
tree | 1f4e839ce18099b540143bfacfe1cedc537b48d6 | |
download | vdr-checkts-0e7880576c0dcf84eaa53cd9c73bc2b5bf1e8d01.tar.gz vdr-checkts-0e7880576c0dcf84eaa53cd9c73bc2b5bf1e8d01.tar.bz2 |
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | COPYRIGHT | 29 | ||||
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | README | 37 | ||||
-rw-r--r-- | patch/0001-Record-TS-continuity-errors.patch | 190 | ||||
-rw-r--r-- | tsstreamerrorcounter.cc | 44 | ||||
-rw-r--r-- | tsstreamerrorcounter.h | 31 | ||||
-rw-r--r-- | vdr-checkts.cc | 91 |
8 files changed, 440 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b546d6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +vdr-checkts diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..9b9da23 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,29 @@ +Copyright 2011 Tobias Grimm <vdr@e-tobi.net> All rights reserved. + +Software License Agreement (BSD License) + +Redistribution and use of this software in source and binary forms, with or +without modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author/copyright holder nor the names of contributors may + be used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ee08a42 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +SOURCES=vdr-checkts.cc tsstreamerrorcounter.cc +OBJECTS=$(SOURCES:.cc=.o) +EXECUTABLE=vdr-checkts +CC=g++ +CFLAGS=-c -Wall + +all: $(SOURCES) $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + $(CC) $(LDFLAGS) $(OBJECTS) -o $@ + +.cpp.o: + $(CC) $(CFLAGS) $< -o $@ + +clean: + rm -rf *.o $(EXECUTABLE) @@ -0,0 +1,37 @@ +vdr-checkts +=========== + +Copyright 2011 Tobias Grimm <vdr@e-tobi.net> + +vdr-checkts is released under the terms of the BSD License - see COPYRIGHT + + +What's this about? +------------------ + +vdr-checkts parses VDR recordings (only TS-recordings / vdr 1.7.x) and +records any discontinuities in the transport stream by checking the +continuity counter. + +Ideally there will be no discontinuities and vdr-checkts returns with +exit code 0. If continuity errors were found it returns with exit code 1 +and prints the number of errors. + + +How to use? +----------- + +Usage: vdr-checkts [OPTIONS] RECORDING_DIR + + -m errors, --max-errors=ERROR Stop scanning if error count reaches ERROR + -h, --help print this help and exit + + +What else? +---------- + +I've also include a patch for VDR 1.7.21 which will make VDR check for +continuity errors while recording. + +The number of continuity errors is written to the info file in the field +tagged with 'R'. diff --git a/patch/0001-Record-TS-continuity-errors.patch b/patch/0001-Record-TS-continuity-errors.patch new file mode 100644 index 0000000..c49cf5c --- /dev/null +++ b/patch/0001-Record-TS-continuity-errors.patch @@ -0,0 +1,190 @@ +From 2e70ffdb5e8a7e06d48a5426c798fdae6df5abcf Mon Sep 17 00:00:00 2001 +From: etobi <git@e-tobi.net> +Date: Sun, 2 Oct 2011 20:27:37 +0200 +Subject: [PATCH] Record TS continuity errors + +--- + recorder.c | 11 +++++++++++ + recording.c | 9 +++++++++ + recording.h | 3 +++ + remux.c | 41 +++++++++++++++++++++++++++++++++++++++++ + remux.h | 25 +++++++++++++++++++++++++ + 5 files changed, 89 insertions(+), 0 deletions(-) + +diff --git a/recorder.c b/recorder.c +index a6cab47..182fb09 100644 +--- a/recorder.c ++++ b/recorder.c +@@ -119,6 +119,7 @@ void cRecorder::Action(void) + time_t t = time(NULL); + bool InfoWritten = false; + bool FirstIframeSeen = false; ++ cTsStreamErrorCounter TsStreamErrorCounter; + while (Running()) { + int r; + uchar *b = ringBuffer->Get(r); +@@ -154,6 +155,16 @@ void cRecorder::Action(void) + fileSize += TS_SIZE; + } + } ++ TsStreamErrorCounter.CheckTsPackets(b, Count); ++ if (InfoWritten && TsStreamErrorCounter.ErrorCount() > 0) { ++ cRecordingInfo RecordingInfo(recordingName); ++ if (RecordingInfo.Read()) { ++ RecordingInfo.SetErrorCount(RecordingInfo.ErrorCount() + TsStreamErrorCounter.ErrorCount()); ++ RecordingInfo.Write(); ++ Recordings.UpdateByName(recordingName); ++ TsStreamErrorCounter.ResetErrorCount(); ++ } ++ } + if (recordFile->Write(b, Count) < 0) { + LOG_ERROR_STR(fileName->Name()); + break; +diff --git a/recording.c b/recording.c +index bea7eb6..47fd2c2 100644 +--- a/recording.c ++++ b/recording.c +@@ -321,6 +321,7 @@ cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event) + event = ownEvent ? ownEvent : Event; + aux = NULL; + framesPerSecond = DEFAULTFRAMESPERSECOND; ++ errorCount = 0; + priority = MAXPRIORITY; + lifetime = MAXLIFETIME; + fileName = NULL; +@@ -414,6 +415,11 @@ void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond) + framesPerSecond = FramesPerSecond; + } + ++void cRecordingInfo::SetErrorCount(int ErrorCount) ++{ ++ errorCount = ErrorCount; ++} ++ + bool cRecordingInfo::Read(FILE *f) + { + if (ownEvent) { +@@ -453,6 +459,8 @@ bool cRecordingInfo::Read(FILE *f) + break; + case 'F': framesPerSecond = atof(t); + break; ++ case 'R': errorCount = atoi(t); ++ break; + case 'L': lifetime = atoi(t); + break; + case 'P': priority = atoi(t); +@@ -479,6 +487,7 @@ bool cRecordingInfo::Write(FILE *f, const char *Prefix) const + 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, "%sR %d\n", Prefix, errorCount); + fprintf(f, "%sP %d\n", Prefix, priority); + fprintf(f, "%sL %d\n", Prefix, lifetime); + if (aux) +diff --git a/recording.h b/recording.h +index 37979ec..be03ecb 100644 +--- a/recording.h ++++ b/recording.h +@@ -55,6 +55,7 @@ private: + cEvent *ownEvent; + char *aux; + double framesPerSecond; ++ int errorCount; + int priority; + int lifetime; + char *fileName; +@@ -75,6 +76,8 @@ public: + const char *Aux(void) const { return aux; } + double FramesPerSecond(void) const { return framesPerSecond; } + void SetFramesPerSecond(double FramesPerSecond); ++ int ErrorCount(void) const { return errorCount; } ++ void SetErrorCount(int ErrorCount); + bool Write(FILE *f, const char *Prefix = "") const; + bool Read(void); + bool Write(void) const; +diff --git a/remux.c b/remux.c +index 78ab294..eb6b0d9 100644 +--- a/remux.c ++++ b/remux.c +@@ -1029,3 +1029,44 @@ int cFrameDetector::Analyze(const uchar *Data, int Length) + } + return Processed; + } ++ ++// --- cTsStreamErrorCounter ------------------------------------------------- ++ ++cTsStreamErrorCounter::cTsStreamErrorCounter(void) ++{ ++ errorCount = 0; ++ for(int i=0; i < MAX_PIDS; i++) pids[i] = -1; ++ memset(counters, 0, sizeof(counters)); ++} ++ ++void cTsStreamErrorCounter::CheckTsPackets(uchar* Data, int Length) ++{ ++ while (Length >= TS_SIZE) { ++ int pid = ((Data[1] & 0x1F) << 8) + Data[2]; ++ char ctr = (Data[3] & 0x0F); ++ char adaption = (Data[3] & 0x30) >> 4; ++ ++ if (adaption != 2) ++ CheckTsPacketContinuity(pid, ctr); ++ ++ Length -= TS_SIZE; ++ Data += TS_SIZE; ++ } ++} ++ ++void cTsStreamErrorCounter::CheckTsPacketContinuity(int pid, int counter) ++{ ++ for (int i=0; i < MAX_PIDS; i++) { ++ if (pids[i] == -1) { ++ pids[i] = pid; ++ counters[i] = counter; ++ break; ++ } ++ if (pids[i] == pid) { ++ if (((counter - counters[i]) & 0x0F) != 1) ++ errorCount++; ++ counters[i] = counter; ++ break; ++ } ++ } ++} +diff --git a/remux.h b/remux.h +index b882279..72545e7 100644 +--- a/remux.h ++++ b/remux.h +@@ -390,4 +390,29 @@ public: + ///< available. + }; + ++ ++// TS stream error counter: ++// Count (continuity) errors in a TS packet stream ++ ++#define MAX_PIDS 20 ++ ++class cTsStreamErrorCounter { ++private: ++ int errorCount; ++ int pids[MAX_PIDS]; ++ uchar counters[MAX_PIDS]; ++public: ++ cTsStreamErrorCounter(void); ++ void CheckTsPackets(uchar* Data, int Length); ++ ///< Check for errors in the TS packets pointed to by Data. Length is the ++ ///< number of bytes Data points to, and must be a multiple of 188. ++ int ErrorCount(void) const { return errorCount; }; ++ ///< Return the number of errors found in the analyzed TS packet stream. ++ ///< Right now, only continuity errors are reported. ++ void ResetErrorCount(void) { errorCount = 0; }; ++ ///< Reset error count to 0 ++private: ++ void CheckTsPacketContinuity(int pid, int counter); ++ }; ++ + #endif // __REMUX_H +-- +1.7.6.3 + diff --git a/tsstreamerrorcounter.cc b/tsstreamerrorcounter.cc new file mode 100644 index 0000000..8cb5771 --- /dev/null +++ b/tsstreamerrorcounter.cc @@ -0,0 +1,44 @@ +#include "string.h" +#include <stdio.h> +#include "tsstreamerrorcounter.h" + +// --- cTsStreamErrorCounter ------------------------------------------------- + +cTsStreamErrorCounter::cTsStreamErrorCounter(void) +{ + errorCount = 0; + for(int i=0; i < MAX_PIDS; i++) pids[i] = -1; + memset(counters, 0, sizeof(counters)); +} + +void cTsStreamErrorCounter::CheckTsPackets(uchar* Data, int Length) +{ + while (Length >= TS_SIZE) { + int pid = ((Data[1] & 0x1F) << 8) + Data[2]; + char ctr = (Data[3] & 0x0F); + char adaption = (Data[3] & 0x30) >> 4; + + if (adaption != 2) + CheckTsPacketContinuity(pid, ctr); + + Length -= TS_SIZE; + Data += TS_SIZE; + } +} + +void cTsStreamErrorCounter::CheckTsPacketContinuity(int pid, int counter) +{ + for (int i=0; i < MAX_PIDS; i++) { + if (pids[i] == -1) { + pids[i] = pid; + counters[i] = counter; + break; + } + if (pids[i] == pid) { + if (((counter - counters[i]) & 0x0F) != 1) + errorCount++; + counters[i] = counter; + break; + } + } +} diff --git a/tsstreamerrorcounter.h b/tsstreamerrorcounter.h new file mode 100644 index 0000000..4f21fbc --- /dev/null +++ b/tsstreamerrorcounter.h @@ -0,0 +1,31 @@ +#ifndef __TSCHECKER_H +#define __TSCHECKER_H + +#define TS_SYNC_BYTE 0x47 +#define TS_SIZE 188 + +// TS stream error counter: +// Count (continuity) errors in a TS packet stream + +#define MAX_PIDS 20 +typedef unsigned char uchar; + +class cTsStreamErrorCounter { +private: + int errorCount; + int pids[MAX_PIDS]; + uchar counters[MAX_PIDS]; +public: + cTsStreamErrorCounter(void); + void CheckTsPackets(uchar* Data, int Length); + ///< Check for errors in the TS packets pointed to by Data. Length is the + ///< number of bytes Data points to, and must be a multiple of 188. + int ErrorCount(void) const { return errorCount; }; + ///< Return the number of errors found in the analyzed TS packet stream. + ///< Right now, only continuity errors are reported. +private: + void CheckTsPacketContinuity(int pid, int counter); + }; + + +#endif // __TSCHECKER_H diff --git a/vdr-checkts.cc b/vdr-checkts.cc new file mode 100644 index 0000000..45ca51c --- /dev/null +++ b/vdr-checkts.cc @@ -0,0 +1,91 @@ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <limits.h> +#include "tsstreamerrorcounter.h" + +#define TS_READ_BUFFER_SIZE 50 + +int maxErrors = 0; +const char* recordingDir; + +void displayHelp(void) +{ + printf("Usage: vdr-checkts [OPTIONS] RECORDING_DIR\n\n" + " -m errors, --max-errors=ERROR Stop scanning if error count reaches ERROR\n" + " -h, --help print this help and exit\n" + "\n" + ); +} + +int processCliArguments(int argc, char *argv[]) +{ + static struct option long_options[] = { + { "max-errors", required_argument, NULL, 'm' }, + { "help", no_argument, NULL, 'h' }, + { NULL, no_argument, NULL, 0 } + }; + + int c; + while ((c = getopt_long(argc, argv, "m:h", long_options, NULL)) != -1) { + switch (c) { + case 'm': maxErrors = strtol(optarg, (char**) NULL, 10); + if (errno != 0 || maxErrors <= 0) { + fprintf(stderr, "Invalid max error argument: %s\n", optarg); + return -2; + } + break; + case 'h': displayHelp(); + return -1; + } + } + if (optind != argc - 1) + { + displayHelp(); + return -1; + } + recordingDir = argv[optind]; + return 0; +} + +int checkRecording(void) +{ + int fileNumber = 1; + cTsStreamErrorCounter tsStreamErrorCounter; + while(!maxErrors || tsStreamErrorCounter.ErrorCount() < maxErrors) { + char fileName[PATH_MAX]; + + sprintf(fileName, "%s/%.5d.ts", recordingDir, fileNumber); + + FILE *file = fopen64(fileName, "rb"); + if (file) { + unsigned char buffer[TS_READ_BUFFER_SIZE * TS_SIZE]; + int read; + while (true) { + read = fread(buffer, TS_SIZE, TS_READ_BUFFER_SIZE, file); + if (read <= 0) break; + tsStreamErrorCounter.CheckTsPackets(buffer, read * TS_SIZE); + if (maxErrors && tsStreamErrorCounter.ErrorCount() >= maxErrors) + break; + } + } + else + break; + fclose(file); + fileNumber++; + } + + printf("Errors: %d\n", tsStreamErrorCounter.ErrorCount()); + return tsStreamErrorCounter.ErrorCount(); +} + +int main(int argc, char *argv[]) +{ + if (int res = processCliArguments(argc, argv) != 0) return res; + + if (checkRecording() == 0) + return 0; + else + return 1; +} |