summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Schmidinger <vdr@tvdr.de>2012-11-02 14:35:57 +0100
committerKlaus Schmidinger <vdr@tvdr.de>2012-11-02 14:35:57 +0100
commit57a3169013f67c8af7ab9ee505cf3b254b912168 (patch)
treeff978d96bed833f2029307e1c84e18e6b895b981
parent38d48afad921baa9edf49535ca9be8ce1fda1592 (diff)
downloadvdr-57a3169013f67c8af7ab9ee505cf3b254b912168.tar.gz
vdr-57a3169013f67c8af7ab9ee505cf3b254b912168.tar.bz2
Improved frame detection by parsing just far enough into the MPEG-4 NAL units to get the necessary information about frames and slices; the initial syncing of the frame detector is now done immediately after the first complete GOP has been seen
-rw-r--r--HISTORY7
-rw-r--r--recording.c3
-rw-r--r--remux.c694
-rw-r--r--remux.h72
4 files changed, 586 insertions, 190 deletions
diff --git a/HISTORY b/HISTORY
index 2900a737..76a5978f 100644
--- a/HISTORY
+++ b/HISTORY
@@ -7272,7 +7272,7 @@ Video Disk Recorder Revision History
".keep" to prevent a directory from being deleted when it is empty. Currently the
only file name that is ignored is ".sort".
-2012-10-16: Version 1.7.32
+2012-11-02: Version 1.7.32
- Pressing the Play key during normal live viewing mode now opens the Recordings menu
if there is no "last viewed" recording (thanks to Alexander Wenzel).
@@ -7305,3 +7305,8 @@ Video Disk Recorder Revision History
Sundararaj Reel).
- Fixed handling timers in case an event is modified and "phased out" while the timer
is recording.
+- Improved frame detection by parsing just far enough into the MPEG-4 NAL units to get
+ the necessary information about frames and slices.
+- The initial syncing of the frame detector is now done immediately after the first
+ complete GOP has been seen. This makes recordings and especially pausing live video
+ start up to twice as fast as before.
diff --git a/recording.c b/recording.c
index 71272fca..0edfe245 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.67 2012/10/15 10:23:37 kls Exp $
+ * $Id: recording.c 2.68 2012/11/01 11:51:52 kls Exp $
*/
#include "recording.h"
@@ -1550,7 +1550,6 @@ void cIndexFileGenerator::Action(void)
if (Processed > 0) {
if (FrameDetector.Synced()) {
// Synced FrameDetector, so rewind for actual processing:
- FrameDetector.Reset();
Rewind = true;
}
Buffer.Del(Processed);
diff --git a/remux.c b/remux.c
index 6450da40..498f35f6 100644
--- a/remux.c
+++ b/remux.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: remux.c 2.67 2012/09/19 10:28:42 kls Exp $
+ * $Id: remux.c 2.68 2012/11/02 14:35:57 kls Exp $
*/
#include "remux.h"
@@ -23,6 +23,8 @@ static bool DebugFrames = false;
#define dbgpatpmt(a...) if (DebugPatPmt) fprintf(stderr, a)
#define dbgframes(a...) if (DebugFrames) fprintf(stderr, a)
+#define EMPTY_SCANNER (0xFFFFFFFF)
+
ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader)
{
if (Count < 7)
@@ -146,6 +148,94 @@ void TsSetTeiOnBrokenPackets(uchar *p, int l)
}
}
+// --- cTsPayload ------------------------------------------------------------
+
+cTsPayload::cTsPayload(void)
+{
+ data = NULL;
+ length = 0;
+ pid = -1;
+ index = 0;
+}
+
+cTsPayload::cTsPayload(uchar *Data, int Length, int Pid)
+{
+ Setup(Data, Length, Pid);
+}
+
+void cTsPayload::Setup(uchar *Data, int Length, int Pid)
+{
+ data = Data;
+ length = Length;
+ pid = Pid >= 0 ? Pid : TsPid(Data);
+ index = 0;
+}
+
+uchar cTsPayload::GetByte(void)
+{
+ if (!Eof()) {
+ if (index % TS_SIZE == 0) { // encountered the next TS header
+ for (;; index += TS_SIZE) {
+ if (data[index] == TS_SYNC_BYTE && index + TS_SIZE <= length) { // to make sure we are at a TS header start and drop incomplete TS packets at the end
+ uchar *p = data + index;
+ if (TsPid(p) == pid) { // only handle TS packets for the initial PID
+ if (TsHasPayload(p)) {
+ if (index > 0 && TsPayloadStart(p)) { // checking index to not skip the very first TS packet
+ length = index; // triggers EOF
+ return 0x00;
+ }
+ index += TsPayloadOffset(p);
+ break;
+ }
+ }
+ }
+ else {
+ length = index; // triggers EOF
+ return 0x00;
+ }
+ }
+ }
+ return data[index++];
+ }
+ return 0x00;
+}
+
+bool cTsPayload::SkipBytes(int Bytes)
+{
+ while (Bytes-- > 0)
+ GetByte();
+ return !Eof();
+}
+
+bool cTsPayload::SkipPesHeader(void)
+{
+ return SkipBytes(PesPayloadOffset(data + TsPayloadOffset(data)));
+}
+
+int cTsPayload::GetLastIndex(void)
+{
+ return index - 1;
+}
+
+void cTsPayload::SetByte(uchar Byte, int Index)
+{
+ if (Index >= 0 && Index < length)
+ data[Index] = Byte;
+}
+
+bool cTsPayload::Find(uint32_t Code)
+{
+ int OldIndex = index;
+ uint32_t Scanner = EMPTY_SCANNER;
+ while (!Eof()) {
+ Scanner = (Scanner << 8) | GetByte();
+ if (Scanner == Code)
+ return true;
+ }
+ index = OldIndex;
+ return false;
+}
+
// --- cPatPmtGenerator ------------------------------------------------------
cPatPmtGenerator::cPatPmtGenerator(const cChannel *Channel)
@@ -665,6 +755,25 @@ void cPatPmtParser::ParsePmt(const uchar *Data, int Length)
pmtSize = 0;
}
+bool cPatPmtParser::ParsePatPmt(const uchar *Data, int Length)
+{
+ while (Length >= TS_SIZE) {
+ if (*Data != TS_SYNC_BYTE)
+ break; // just for safety
+ int Pid = TsPid(Data);
+ if (Pid == PATPID)
+ ParsePat(Data, TS_SIZE);
+ else if (Pid == PmtPid()) {
+ ParsePmt(Data, TS_SIZE);
+ if (patVersion >= 0 && pmtVersion >= 0)
+ return true;
+ }
+ Data += TS_SIZE;
+ Length -= TS_SIZE;
+ }
+ return false;
+}
+
bool cPatPmtParser::GetVersions(int &PatVersion, int &PmtVersion) const
{
PatVersion = patVersion;
@@ -809,23 +918,352 @@ void PesDump(const char *Name, const u_char *Data, int Length)
TsDump(Name, Data, Length);
}
-// --- cFrameDetector --------------------------------------------------------
+// --- cFrameParser ----------------------------------------------------------
+
+class cFrameParser {
+protected:
+ bool debug;
+ bool newFrame;
+ bool independentFrame;
+public:
+ cFrameParser(void);
+ virtual ~cFrameParser() {};
+ virtual int Parse(const uchar *Data, int Length, int Pid) = 0;
+ ///< Parses the given Data, which is a sequence of Length bytes of TS packets.
+ ///< The payload in the TS packets with the given Pid is searched for just
+ ///< enough information to determine the beginning and type of the next video
+ ///< frame.
+ ///< Returns the number of bytes parsed. Upon return, the functions NewFrame()
+ ///< and IndependentFrame() can be called to retrieve the required information.
+ void SetDebug(bool Debug) { debug = Debug; }
+ bool NewFrame(void) { return newFrame; }
+ bool IndependentFrame(void) { return independentFrame; }
+ };
+
+cFrameParser::cFrameParser(void)
+{
+ debug = true;
+ newFrame = false;
+ independentFrame = false;
+}
-#define EMPTY_SCANNER (0xFFFFFFFF)
+// --- cAudioParser ----------------------------------------------------------
+
+class cAudioParser : public cFrameParser {
+public:
+ cAudioParser(void);
+ virtual int Parse(const uchar *Data, int Length, int Pid);
+ };
+
+cAudioParser::cAudioParser(void)
+{
+}
+
+int cAudioParser::Parse(const uchar *Data, int Length, int Pid)
+{
+ if (TsPayloadStart(Data)) {
+ newFrame = independentFrame = true;
+ if (debug)
+ dbgframes("/");
+ }
+ else
+ newFrame = independentFrame = false;
+ return TS_SIZE;
+}
+
+// --- cMpeg2Parser ----------------------------------------------------------
+
+class cMpeg2Parser : public cFrameParser {
+private:
+ uint32_t scanner;
+ bool seenIndependentFrame;
+public:
+ cMpeg2Parser(void);
+ virtual int Parse(const uchar *Data, int Length, int Pid);
+ };
+
+cMpeg2Parser::cMpeg2Parser(void)
+{
+ scanner = EMPTY_SCANNER;
+ seenIndependentFrame = false;
+}
+
+int cMpeg2Parser::Parse(const uchar *Data, int Length, int Pid)
+{
+ newFrame = independentFrame = false;
+ if (Length < MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE)
+ return 0; // need more data
+ cTsPayload tsPayload(const_cast<uchar *>(Data), Length, Pid);
+ if (TsPayloadStart(Data)) {
+ tsPayload.SkipPesHeader();
+ scanner = EMPTY_SCANNER;
+ if (debug && seenIndependentFrame)
+ dbgframes("/");
+ }
+ do {
+ scanner = (scanner << 8) | tsPayload.GetByte();
+ if (scanner == 0x00000100) { // Picture Start Code
+ newFrame = true;
+ tsPayload.GetByte();
+ uchar FrameType = (tsPayload.GetByte() >> 3) & 0x07;
+ independentFrame = FrameType == 1; // I-Frame
+ if (debug) {
+ seenIndependentFrame |= independentFrame;
+ if (seenIndependentFrame) {
+ static const char FrameTypes[] = "?IPBD???";
+ dbgframes("%c", FrameTypes[FrameType]);
+ }
+ }
+ break;
+ }
+ } while (tsPayload.Available() > (MIN_TS_PACKETS_FOR_FRAME_DETECTOR - 1) * TS_SIZE);
+ return tsPayload.Used();
+}
+
+// --- cMpeg4Parser ----------------------------------------------------------
+
+class cMpeg4Parser : public cFrameParser {
+private:
+ enum eNalUnitType {
+ nutCodedSliceNonIdr = 1,
+ nutCodedSliceIdr = 5,
+ nutSequenceParameterSet = 7,
+ nutAccessUnitDelimiter = 9,
+ };
+ cTsPayload tsPayload;
+ uchar byte; // holds the current byte value in case of bitwise access
+ int bit; // the bit index into the current byte (-1 if we're not in bit reading mode)
+ int zeroBytes; // the number of consecutive zero bytes (to detect 0x000003)
+ uint32_t scanner;
+ // Identifiers written in '_' notation as in "ITU-T H.264":
+ bool separate_colour_plane_flag;
+ int log2_max_frame_num;
+ bool frame_mbs_only_flag;
+ //
+ bool gotAccessUnitDelimiter;
+ bool gotSequenceParameterSet;
+ uchar GetByte(bool Raw = false);
+ ///< Gets the next data byte. If Raw is true, no filtering will be done.
+ ///< With Raw set to false, if the byte sequence 0x000003 is encountered,
+ ///< the byte with 0x03 will be skipped.
+ uchar GetBit(void);
+ uint32_t GetBits(int Bits);
+ uint32_t GetGolombUe(void);
+ int32_t GetGolombSe(void);
+ void ParseAccessUnitDelimiter(void);
+ void ParseSequenceParameterSet(void);
+ void ParseSliceHeader(void);
+public:
+ cMpeg4Parser(void);
+ ///< Sets up a new MPEG-4 parser.
+ ///< This class parses only the data absolutely necessary to determine the
+ ///< frame borders and field count of the given H264 material.
+ virtual int Parse(const uchar *Data, int Length, int Pid);
+ };
+
+cMpeg4Parser::cMpeg4Parser(void)
+{
+ byte = 0;
+ bit = -1;
+ zeroBytes = 0;
+ scanner = EMPTY_SCANNER;
+ separate_colour_plane_flag = false;
+ log2_max_frame_num = 0;
+ frame_mbs_only_flag = false;
+ gotAccessUnitDelimiter = false;
+ gotSequenceParameterSet = false;
+}
+
+uchar cMpeg4Parser::GetByte(bool Raw)
+{
+ uchar b = tsPayload.GetByte();
+ if (!Raw) {
+ // If we encounter the byte sequence 0x000003, we need to skip the 0x03:
+ if (b == 0x00)
+ zeroBytes++;
+ else {
+ if (b == 0x03 && zeroBytes >= 2)
+ b = tsPayload.GetByte();
+ zeroBytes = 0;
+ }
+ }
+ else
+ zeroBytes = 0;
+ bit = -1;
+ return b;
+}
+
+uchar cMpeg4Parser::GetBit(void)
+{
+ if (bit < 0) {
+ byte = GetByte();
+ bit = 7;
+ }
+ return (byte & (1 << bit--)) ? 1 : 0;
+}
+
+uint32_t cMpeg4Parser::GetBits(int Bits)
+{
+ uint32_t b = 0;
+ while (Bits--)
+ b |= GetBit() << Bits;
+ return b;
+}
+
+uint32_t cMpeg4Parser::GetGolombUe(void)
+{
+ int z = -1;
+ for (int b = 0; !b; z++)
+ b = GetBit();
+ return (1 << z) - 1 + GetBits(z);
+}
+
+int32_t cMpeg4Parser::GetGolombSe(void)
+{
+ uint32_t v = GetGolombUe();
+ if (v) {
+ if ((v & 0x01) != 0)
+ return (v + 1) / 2; // fails for v == 0xFFFFFFFF, but that will probably never happen
+ else
+ return -int32_t(v / 2);
+ }
+ return v;
+}
+
+int cMpeg4Parser::Parse(const uchar *Data, int Length, int Pid)
+{
+ newFrame = independentFrame = false;
+ if (Length < MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE)
+ return 0; // need more data
+ tsPayload.Setup(const_cast<uchar *>(Data), Length, Pid);
+ if (TsPayloadStart(Data)) {
+ tsPayload.SkipPesHeader();
+ scanner = EMPTY_SCANNER;
+ if (debug && gotSequenceParameterSet) {
+ dbgframes("/");
+ }
+ }
+ do {
+ scanner = (scanner << 8) | GetByte(true);
+ if ((scanner & 0xFFFFFF00) == 0x00000100) { // NAL unit start
+ uchar NalUnitType = scanner & 0x1F;
+ switch (NalUnitType) {
+ case nutAccessUnitDelimiter: ParseAccessUnitDelimiter();
+ gotAccessUnitDelimiter = true;
+ break;
+ case nutSequenceParameterSet: ParseSequenceParameterSet();
+ gotSequenceParameterSet = true;
+ break;
+ case nutCodedSliceNonIdr:
+ case nutCodedSliceIdr: if (gotAccessUnitDelimiter && gotSequenceParameterSet) {
+ ParseSliceHeader();
+ gotAccessUnitDelimiter = false;
+ return tsPayload.Used();
+ }
+ break;
+ default: ;
+ }
+ }
+ } while (tsPayload.Available() > (MIN_TS_PACKETS_FOR_FRAME_DETECTOR - 1) * TS_SIZE);
+ return tsPayload.Used();
+}
+
+void cMpeg4Parser::ParseAccessUnitDelimiter(void)
+{
+ if (debug && gotSequenceParameterSet)
+ dbgframes("A");
+ GetByte(); // primary_pic_type
+}
+
+void cMpeg4Parser::ParseSequenceParameterSet(void)
+{
+ uchar profile_idc = GetByte(); // profile_idc
+ GetByte(); // constraint_set[0-5]_flags, reserved_zero_2bits
+ GetByte(); // level_idc
+ GetGolombUe(); // seq_parameter_set_id
+ if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 244 || profile_idc == 44 || profile_idc == 83 || profile_idc == 86 || profile_idc ==118 || profile_idc == 128) {
+ int chroma_format_idc = GetGolombUe(); // chroma_format_idc
+ if (chroma_format_idc == 3)
+ separate_colour_plane_flag = GetBit();
+ GetGolombUe(); // bit_depth_luma_minus8
+ GetGolombUe(); // bit_depth_chroma_minus8
+ GetBit(); // qpprime_y_zero_transform_bypass_flag
+ if (GetBit()) { // seq_scaling_matrix_present_flag
+ for (int i = 0; i < ((chroma_format_idc != 3) ? 8 : 12); i++) {
+ if (GetBit()) { // seq_scaling_list_present_flag
+ int SizeOfScalingList = (i < 6) ? 16 : 64;
+ int LastScale = 8;
+ int NextScale = 8;
+ for (int j = 0; j < SizeOfScalingList; j++) {
+ if (NextScale)
+ NextScale = (LastScale + GetGolombSe() + 256) % 256; // delta_scale
+ if (NextScale)
+ LastScale = NextScale;
+ }
+ }
+ }
+ }
+ }
+ log2_max_frame_num = GetGolombUe() + 4; // log2_max_frame_num_minus4
+ int pic_order_cnt_type = GetGolombUe(); // pic_order_cnt_type
+ if (pic_order_cnt_type == 0)
+ GetGolombUe(); // log2_max_pic_order_cnt_lsb_minus4
+ else if (pic_order_cnt_type == 1) {
+ GetBit(); // delta_pic_order_always_zero_flag
+ GetGolombSe(); // offset_for_non_ref_pic
+ GetGolombSe(); // offset_for_top_to_bottom_field
+ for (int i = GetGolombUe(); i--; ) // num_ref_frames_in_pic_order_cnt_cycle
+ GetGolombSe(); // offset_for_ref_frame
+ }
+ GetGolombUe(); // max_num_ref_frames
+ GetBit(); // gaps_in_frame_num_value_allowed_flag
+ GetGolombUe(); // pic_width_in_mbs_minus1
+ GetGolombUe(); // pic_height_in_map_units_minus1
+ frame_mbs_only_flag = GetBit(); // frame_mbs_only_flag
+ if (debug) {
+ if (gotAccessUnitDelimiter && !gotSequenceParameterSet)
+ dbgframes("A"); // just for completeness
+ dbgframes(frame_mbs_only_flag ? "S" : "s");
+ }
+}
+
+void cMpeg4Parser::ParseSliceHeader(void)
+{
+ newFrame = true;
+ GetGolombUe(); // first_mb_in_slice
+ int slice_type = GetGolombUe(); // slice_type, 0 = P, 1 = B, 2 = I, 3 = SP, 4 = SI
+ independentFrame = (slice_type % 5) == 2;
+ if (debug) {
+ static const char SliceTypes[] = "PBIpi";
+ dbgframes("%c", SliceTypes[slice_type % 5]);
+ }
+ if (frame_mbs_only_flag)
+ return; // don't need the rest - a frame is complete
+ GetGolombUe(); // pic_parameter_set_id
+ if (separate_colour_plane_flag)
+ GetBits(2); // colour_plane_id
+ GetBits(log2_max_frame_num); // frame_num
+ if (!frame_mbs_only_flag) {
+ if (GetBit()) // field_pic_flag
+ newFrame = !GetBit(); // bottom_field_flag
+ if (debug)
+ dbgframes(newFrame ? "t" : "b");
+ }
+}
+
+// --- cFrameDetector --------------------------------------------------------
cFrameDetector::cFrameDetector(int Pid, int Type)
{
+ parser = NULL;
SetPid(Pid, Type);
synced = false;
newFrame = independentFrame = false;
numPtsValues = 0;
- numFrames = 0;
numIFrames = 0;
framesPerSecond = 0;
framesInPayloadUnit = framesPerPayloadUnit = 0;
- payloadUnitOfFrame = 0;
scanning = false;
- scanner = EMPTY_SCANNER;
}
static int CmpUint32(const void *p1, const void *p2)
@@ -840,42 +1278,26 @@ void cFrameDetector::SetPid(int Pid, int Type)
pid = Pid;
type = Type;
isVideo = type == 0x01 || type == 0x02 || type == 0x1B; // MPEG 1, 2 or 4
-}
-
-void cFrameDetector::Reset(void)
-{
- newFrame = independentFrame = false;
- payloadUnitOfFrame = 0;
- scanning = false;
- scanner = EMPTY_SCANNER;
-}
-
-int cFrameDetector::SkipPackets(const uchar *&Data, int &Length, int &Processed, int &FrameTypeOffset)
-{
- if (!synced)
- dbgframes("%d>", FrameTypeOffset);
- while (Length >= TS_SIZE) {
- // switch to the next TS packet, but skip those that have a different PID:
- Data += TS_SIZE;
- Length -= TS_SIZE;
- Processed += TS_SIZE;
- if (TsPid(Data) == pid)
- break;
- else if (Length < TS_SIZE)
- esyslog("ERROR: out of data while skipping TS packets in cFrameDetector");
- }
- FrameTypeOffset -= TS_SIZE;
- FrameTypeOffset += TsPayloadOffset(Data);
- return FrameTypeOffset;
+ delete parser;
+ parser = NULL;
+ if (type == 0x01 || type == 0x02)
+ parser = new cMpeg2Parser;
+ else if (type == 0x1B)
+ parser = new cMpeg4Parser;
+ else if (type == 0x04 || type == 0x06) // MPEG audio or AC3 audio
+ parser = new cAudioParser;
+ else if (type != 0)
+ esyslog("ERROR: unknown stream type %d (PID %d) in frame detector", type, pid);
}
int cFrameDetector::Analyze(const uchar *Data, int Length)
{
- bool SeenPayloadStart = false;
- bool SeenAccessUnitDelimiter = false;
+ if (!parser)
+ return 0;
int Processed = 0;
newFrame = independentFrame = false;
- while (Length >= TS_SIZE) {
+ while (Length >= MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE) { // makes sure we are looking at enough data, in case the frame type is not stored in the first TS packet
+ // Sync on TS packet borders:
if (Data[0] != TS_SYNC_BYTE) {
int Skipped = 1;
while (Skipped < Length && (Data[Skipped] != TS_SYNC_BYTE || Length - Skipped > TS_SIZE && Data[Skipped + TS_SIZE] != TS_SYNC_BYTE))
@@ -883,63 +1305,80 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
esyslog("ERROR: skipped %d bytes to sync on start of TS packet", Skipped);
return Processed + Skipped;
}
+ // Handle one TS packet:
+ int Handled = TS_SIZE;
if (TsHasPayload(Data) && !TsIsScrambled(Data)) {
int Pid = TsPid(Data);
if (Pid == pid) {
if (TsPayloadStart(Data)) {
- SeenPayloadStart = true;
- if (synced && Processed)
+ if (Processed)
return Processed;
- if (Length < MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE)
- return Processed; // need more data, in case the frame type is not stored in the first TS packet
+ scanning = true;
+ }
+ if (scanning) {
+ // Detect the beginning of a new frame:
+ if (TsPayloadStart(Data)) {
+ if (!framesPerPayloadUnit)
+ framesPerPayloadUnit = framesInPayloadUnit;
+ }
+ int n = parser->Parse(Data, Length, pid);
+ if (n > 0) {
+ if (parser->NewFrame()) {
+ if (Processed)
+ return Processed; // flush everything before this new frame
+ newFrame = true;
+ independentFrame = parser->IndependentFrame();
+ if (synced) {
+ if (framesPerPayloadUnit <= 1)
+ scanning = false;
+ }
+ else {
+ framesInPayloadUnit++;
+ if (independentFrame)
+ numIFrames++;
+ }
+ }
+ Handled = n;
+ }
+ }
+ if (TsPayloadStart(Data)) {
+ // Determine the frame rate from the PTS values in the PES headers:
if (framesPerSecond <= 0.0) {
// frame rate unknown, so collect a sequence of PTS values:
if (numPtsValues < 2 || numPtsValues < MaxPtsValues && numIFrames < 2) { // collect a sequence containing at least two I-frames
- const uchar *Pes = Data + TsPayloadOffset(Data);
- if (numIFrames && PesHasPts(Pes)) {
- ptsValues[numPtsValues] = PesGetPts(Pes);
- // check for rollover:
- if (numPtsValues && ptsValues[numPtsValues - 1] > 0xF0000000 && ptsValues[numPtsValues] < 0x10000000) {
- dbgframes("#");
- numPtsValues = 0;
- numIFrames = 0;
- numFrames = 0;
+ if (newFrame) { // only take PTS values at the beginning of a frame (in case if fields!)
+ const uchar *Pes = Data + TsPayloadOffset(Data);
+ if (numIFrames && PesHasPts(Pes)) {
+ ptsValues[numPtsValues] = PesGetPts(Pes);
+ // check for rollover:
+ if (numPtsValues && ptsValues[numPtsValues - 1] > 0xF0000000 && ptsValues[numPtsValues] < 0x10000000) {
+ dbgframes("#");
+ numPtsValues = 0;
+ numIFrames = 0;
+ }
+ else
+ numPtsValues++;
}
- else
- numPtsValues++;
}
}
- else {
+ if (numPtsValues >= 2 && numIFrames >= 2) {
// find the smallest PTS delta:
qsort(ptsValues, numPtsValues, sizeof(uint32_t), CmpUint32);
numPtsValues--;
for (int i = 0; i < numPtsValues; i++)
ptsValues[i] = ptsValues[i + 1] - ptsValues[i];
qsort(ptsValues, numPtsValues, sizeof(uint32_t), CmpUint32);
- uint32_t Delta = ptsValues[0];
+ uint32_t Delta = ptsValues[0] / framesPerPayloadUnit;
// determine frame info:
if (isVideo) {
if (abs(Delta - 3600) <= 1)
framesPerSecond = 25.0;
else if (Delta % 3003 == 0)
framesPerSecond = 30.0 / 1.001;
- else if (abs(Delta - 1800) <= 1) {
- if (numFrames > 50) {
- // this is a "best guess": if there are more than 50 frames between two I-frames, we assume each "frame" actually contains a "field", so two "fields" make one "frame"
- framesPerSecond = 25.0;
- framesPerPayloadUnit = -2;
- }
- else
- framesPerSecond = 50.0;
- }
+ else if (abs(Delta - 1800) <= 1)
+ framesPerSecond = 50.0;
else if (Delta == 1501)
- if (numFrames > 50) {
- // this is a "best guess": if there are more than 50 frames between two I-frames, we assume each "frame" actually contains a "field", so two "fields" make one "frame"
- framesPerSecond = 30.0 / 1.001;
- framesPerPayloadUnit = -2;
- }
- else
- framesPerSecond = 60.0 / 1.001;
+ framesPerSecond = 60.0 / 1.001;
else {
framesPerSecond = DEFAULTFRAMESPERSECOND;
dsyslog("unknown frame delta (%d), assuming %5.2f fps", Delta, DEFAULTFRAMESPERSECOND);
@@ -947,122 +1386,21 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
}
else // audio
framesPerSecond = 90000.0 / Delta; // PTS of audio frames is always increasing
- dbgframes("\nDelta = %d FPS = %5.2f FPPU = %d NF = %d\n", Delta, framesPerSecond, framesPerPayloadUnit, numFrames);
+ dbgframes("\nDelta = %d FPS = %5.2f FPPU = %d NF = %d\n", Delta, framesPerSecond, framesPerPayloadUnit, numPtsValues + 1);
+ synced = true;
+ parser->SetDebug(false);
}
}
- scanner = EMPTY_SCANNER;
- scanning = true;
- }
- if (scanning) {
- int PayloadOffset = TsPayloadOffset(Data);
- if (TsPayloadStart(Data)) {
- PayloadOffset += PesPayloadOffset(Data + PayloadOffset);
- if (!framesPerPayloadUnit)
- framesPerPayloadUnit = framesInPayloadUnit;
- if (DebugFrames && !synced)
- dbgframes("/");
- }
- for (int i = PayloadOffset; scanning && i < TS_SIZE; i++) {
- scanner <<= 8;
- scanner |= Data[i];
- switch (type) {
- case 0x01: // MPEG 1 video
- case 0x02: // MPEG 2 video
- if (scanner == 0x00000100) { // Picture Start Code
- scanner = EMPTY_SCANNER;
- if (synced && !SeenPayloadStart && Processed)
- return Processed; // flush everything before this new frame
- int FrameTypeOffset = i + 2;
- if (FrameTypeOffset >= TS_SIZE) // the byte to check is in the next TS packet
- i = SkipPackets(Data, Length, Processed, FrameTypeOffset);
- newFrame = true;
- uchar FrameType = (Data[FrameTypeOffset] >> 3) & 0x07;
- independentFrame = FrameType == 1; // I-Frame
- if (synced) {
- if (framesPerPayloadUnit <= 1)
- scanning = false;
- }
- else {
- framesInPayloadUnit++;
- if (independentFrame)
- numIFrames++;
- if (numIFrames == 1)
- numFrames++;
- dbgframes("%u ", FrameType);
- }
- if (synced)
- return Processed + TS_SIZE; // flag this new frame
- }
- break;
- case 0x1B: // MPEG 4 video
- if (scanner == 0x00000109) { // Access Unit Delimiter
- scanner = EMPTY_SCANNER;
- if (synced && !SeenPayloadStart && Processed)
- return Processed; // flush everything before this new frame
- SeenAccessUnitDelimiter = true;
- }
- else if (SeenAccessUnitDelimiter && scanner == 0x00000001) { // NALU start
- SeenAccessUnitDelimiter = false;
- int FrameTypeOffset = i + 1;
- if (FrameTypeOffset >= TS_SIZE) // the byte to check is in the next TS packet
- i = SkipPackets(Data, Length, Processed, FrameTypeOffset);
- newFrame = true;
- uchar FrameType = Data[FrameTypeOffset] & 0x1F;
- independentFrame = FrameType == 0x07;
- if (synced) {
- if (framesPerPayloadUnit < 0) {
- payloadUnitOfFrame = (payloadUnitOfFrame + 1) % -framesPerPayloadUnit;
- if (payloadUnitOfFrame != 0 && independentFrame)
- payloadUnitOfFrame = 0;
- if (payloadUnitOfFrame)
- newFrame = false;
- }
- if (framesPerPayloadUnit <= 1)
- scanning = false;
- }
- else {
- framesInPayloadUnit++;
- if (independentFrame)
- numIFrames++;
- if (numIFrames == 1)
- numFrames++;
- dbgframes("%02X ", FrameType);
- }
- if (synced)
- return Processed + TS_SIZE; // flag this new frame
- }
- break;
- case 0x04: // MPEG audio
- case 0x06: // AC3 audio
- if (synced && Processed)
- return Processed;
- newFrame = true;
- independentFrame = true;
- if (!synced) {
- framesInPayloadUnit = 1;
- if (TsPayloadStart(Data))
- numIFrames++;
- }
- scanning = false;
- break;
- default: esyslog("ERROR: unknown stream type %d (PID %d) in frame detector", type, pid);
- pid = 0; // let's just ignore any further data
- }
- }
- if (!synced && framesPerSecond > 0.0 && independentFrame) {
- synced = true;
- dbgframes("*\n");
- Reset();
- return Processed + TS_SIZE;
- }
}
}
else if (Pid == PATPID && synced && Processed)
return Processed; // allow the caller to see any PAT packets
}
- Data += TS_SIZE;
- Length -= TS_SIZE;
- Processed += TS_SIZE;
+ Data += Handled;
+ Length -= Handled;
+ Processed += Handled;
+ if (newFrame)
+ break;
}
return Processed;
}
diff --git a/remux.h b/remux.h
index b8822796..500fcb95 100644
--- a/remux.h
+++ b/remux.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: remux.h 2.32 2011/09/04 12:48:26 kls Exp $
+ * $Id: remux.h 2.33 2012/11/02 14:33:11 kls Exp $
*/
#ifndef __REMUX_H
@@ -151,6 +151,60 @@ inline int64_t PesGetPts(const uchar *p)
((((int64_t)p[13]) & 0xFE) >> 1);
}
+// A transprent TS payload handler:
+
+class cTsPayload {
+private:
+ uchar *data;
+ int length;
+ int pid;
+ int index; // points to the next byte to process
+public:
+ cTsPayload(void);
+ cTsPayload(uchar *Data, int Length, int Pid = -1);
+ ///< Creates a new TS payload handler and calls Setup() with the given Data.
+ void Setup(uchar *Data, int Length, int Pid = -1);
+ ///< Sets up this TS payload handler with the given Data, which points to a
+ ///< sequence of Length bytes of complete TS packets. Any incomplete TS
+ ///< packet at the end will be ignored.
+ ///< If Pid is given, only TS packets with data for that PID will be processed.
+ ///< Otherwise the PID of the first TS packet defines which payload will be
+ ///< delivered.
+ ///< Any intermediate TS packets with different PIDs will be skipped.
+ int Available(void) { return length - index; }
+ ///< Returns the number of raw bytes (including any TS headers) still available
+ ///< in the TS payload handler.
+ int Used(void) { return (index + TS_SIZE - 1) / TS_SIZE * TS_SIZE; }
+ ///< Returns the number of raw bytes that have already been used (e.g. by calling
+ ///< GetByte()). Any TS packet of which at least a single byte has been delivered
+ ///< is counted with its full size.
+ bool Eof(void) const { return index >= length; }
+ ///< Returns true if all available bytes of the TS payload have been processed.
+ uchar GetByte(void);
+ ///< Gets the next byte of the TS payload, skipping any intermediate TS header data.
+ bool SkipBytes(int Bytes);
+ ///< Skips the given number of bytes in the payload and returns true if there
+ ///< is still data left to read.
+ bool SkipPesHeader(void);
+ ///< Skips all bytes belonging to the PES header of the payload.
+ int GetLastIndex(void);
+ ///< Returns the index into the TS data of the payload byte that has most recently
+ ///< been read. If no byte has been read yet, -1 will be returned.
+ void SetByte(uchar Byte, int Index);
+ ///< Sets the TS data byte at the given Index to the value Byte.
+ ///< Index should be one that has been retrieved by a previous call to GetIndex(),
+ ///< otherwise the behaviour is undefined. The current read index will not be
+ ///< altered by a call to this function.
+ bool Find(uint32_t Code);
+ ///< Searches for the four byte sequence given in Code and returns true if it
+ ///< was found within the payload data. The next call to GetByte() will return the
+ ///< value immediately following the Code. If the code was not found, the read
+ ///< index will remain the same as before this call, so that several calls to
+ ///< Find() can be performed starting at the same index..
+ ///< The special code 0xFFFFFFFF can not be searched, because this value is used
+ ///< to initialize the scanner.
+ };
+
// PAT/PMT Generator:
#define MAX_SECTION_SIZE 4096 // maximum size of an SI section
@@ -248,6 +302,10 @@ public:
///< are delivered to the parser through several subsequent calls to
///< ParsePmt(). The whole PMT data will be processed once the last packet
///< has been received.
+ bool ParsePatPmt(const uchar *Data, int Length);
+ ///< Parses the given Data (which may consist of several TS packets, typically
+ ///< an entire frame) and extracts the PAT and PMT.
+ ///< Returns true if a valid PAT/PMT has been detected.
bool GetVersions(int &PatVersion, int &PmtVersion) const;
///< Returns true if a valid PAT/PMT has been parsed and stores
///< the current version numbers in the given variables.
@@ -338,6 +396,8 @@ void PesDump(const char *Name, const u_char *Data, int Length);
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR 5
+class cFrameParser;
+
class cFrameDetector {
private:
enum { MaxPtsValues = 150 };
@@ -354,12 +414,9 @@ private:
double framesPerSecond;
int framesInPayloadUnit;
int framesPerPayloadUnit; // Some broadcasters send one frame per payload unit (== 1),
- // some put an entire GOP into one payload unit (> 1), and
- // some spread a single frame over several payload units (< 0).
- int payloadUnitOfFrame;
+ // while others put an entire GOP into one payload unit (> 1).
bool scanning;
- uint32_t scanner;
- int SkipPackets(const uchar *&Data, int &Length, int &Processed, int &FrameTypeOffset);
+ cFrameParser *parser;
public:
cFrameDetector(int Pid = 0, int Type = 0);
///< Sets up a frame detector for the given Pid and stream Type.
@@ -367,9 +424,6 @@ public:
///< call to SetPid().
void SetPid(int Pid, int Type);
///< Sets the Pid and stream Type to detect frames for.
- void Reset(void);
- ///< Resets any counters and flags used while syncing and prepares
- ///< the frame detector for actual work.
int Analyze(const uchar *Data, int Length);
///< Analyzes the TS packets pointed to by Data. Length is the number of
///< bytes Data points to, and must be a multiple of TS_SIZE.