1
0
mirror of https://github.com/VDR4Arch/vdr.git synced 2023-10-10 13:36:52 +02:00

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 commit is contained in:
Klaus Schmidinger 2012-11-02 14:35:57 +01:00
parent 38d48afad9
commit 57a3169013
4 changed files with 586 additions and 190 deletions

View File

@ -7272,7 +7272,7 @@ Video Disk Recorder Revision History
".keep" to prevent a directory from being deleted when it is empty. Currently the ".keep" to prevent a directory from being deleted when it is empty. Currently the
only file name that is ignored is ".sort". 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 - 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). if there is no "last viewed" recording (thanks to Alexander Wenzel).
@ -7305,3 +7305,8 @@ Video Disk Recorder Revision History
Sundararaj Reel). Sundararaj Reel).
- Fixed handling timers in case an event is modified and "phased out" while the timer - Fixed handling timers in case an event is modified and "phased out" while the timer
is recording. 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.

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "recording.h"
@ -1550,7 +1550,6 @@ void cIndexFileGenerator::Action(void)
if (Processed > 0) { if (Processed > 0) {
if (FrameDetector.Synced()) { if (FrameDetector.Synced()) {
// Synced FrameDetector, so rewind for actual processing: // Synced FrameDetector, so rewind for actual processing:
FrameDetector.Reset();
Rewind = true; Rewind = true;
} }
Buffer.Del(Processed); Buffer.Del(Processed);

694
remux.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "remux.h"
@ -23,6 +23,8 @@ static bool DebugFrames = false;
#define dbgpatpmt(a...) if (DebugPatPmt) fprintf(stderr, a) #define dbgpatpmt(a...) if (DebugPatPmt) fprintf(stderr, a)
#define dbgframes(a...) if (DebugFrames) 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) ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader)
{ {
if (Count < 7) 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::cPatPmtGenerator(const cChannel *Channel) cPatPmtGenerator::cPatPmtGenerator(const cChannel *Channel)
@ -665,6 +755,25 @@ void cPatPmtParser::ParsePmt(const uchar *Data, int Length)
pmtSize = 0; 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 bool cPatPmtParser::GetVersions(int &PatVersion, int &PmtVersion) const
{ {
PatVersion = patVersion; PatVersion = patVersion;
@ -809,23 +918,352 @@ void PesDump(const char *Name, const u_char *Data, int Length)
TsDump(Name, Data, Length); TsDump(Name, Data, Length);
} }
// --- cFrameDetector -------------------------------------------------------- // --- cFrameParser ----------------------------------------------------------
#define EMPTY_SCANNER (0xFFFFFFFF) 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;
}
// --- 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) cFrameDetector::cFrameDetector(int Pid, int Type)
{ {
parser = NULL;
SetPid(Pid, Type); SetPid(Pid, Type);
synced = false; synced = false;
newFrame = independentFrame = false; newFrame = independentFrame = false;
numPtsValues = 0; numPtsValues = 0;
numFrames = 0;
numIFrames = 0; numIFrames = 0;
framesPerSecond = 0; framesPerSecond = 0;
framesInPayloadUnit = framesPerPayloadUnit = 0; framesInPayloadUnit = framesPerPayloadUnit = 0;
payloadUnitOfFrame = 0;
scanning = false; scanning = false;
scanner = EMPTY_SCANNER;
} }
static int CmpUint32(const void *p1, const void *p2) static int CmpUint32(const void *p1, const void *p2)
@ -840,42 +1278,26 @@ void cFrameDetector::SetPid(int Pid, int Type)
pid = Pid; pid = Pid;
type = Type; type = Type;
isVideo = type == 0x01 || type == 0x02 || type == 0x1B; // MPEG 1, 2 or 4 isVideo = type == 0x01 || type == 0x02 || type == 0x1B; // MPEG 1, 2 or 4
} delete parser;
parser = NULL;
void cFrameDetector::Reset(void) if (type == 0x01 || type == 0x02)
{ parser = new cMpeg2Parser;
newFrame = independentFrame = false; else if (type == 0x1B)
payloadUnitOfFrame = 0; parser = new cMpeg4Parser;
scanning = false; else if (type == 0x04 || type == 0x06) // MPEG audio or AC3 audio
scanner = EMPTY_SCANNER; parser = new cAudioParser;
} else if (type != 0)
esyslog("ERROR: unknown stream type %d (PID %d) in frame detector", type, pid);
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;
} }
int cFrameDetector::Analyze(const uchar *Data, int Length) int cFrameDetector::Analyze(const uchar *Data, int Length)
{ {
bool SeenPayloadStart = false; if (!parser)
bool SeenAccessUnitDelimiter = false; return 0;
int Processed = 0; int Processed = 0;
newFrame = independentFrame = false; 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) { if (Data[0] != TS_SYNC_BYTE) {
int Skipped = 1; int Skipped = 1;
while (Skipped < Length && (Data[Skipped] != TS_SYNC_BYTE || Length - Skipped > TS_SIZE && Data[Skipped + TS_SIZE] != TS_SYNC_BYTE)) 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); esyslog("ERROR: skipped %d bytes to sync on start of TS packet", Skipped);
return Processed + Skipped; return Processed + Skipped;
} }
// Handle one TS packet:
int Handled = TS_SIZE;
if (TsHasPayload(Data) && !TsIsScrambled(Data)) { if (TsHasPayload(Data) && !TsIsScrambled(Data)) {
int Pid = TsPid(Data); int Pid = TsPid(Data);
if (Pid == pid) { if (Pid == pid) {
if (TsPayloadStart(Data)) { if (TsPayloadStart(Data)) {
SeenPayloadStart = true; if (Processed)
if (synced && Processed)
return Processed; return Processed;
if (Length < MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE) scanning = true;
return Processed; // need more data, in case the frame type is not stored in the first TS packet }
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) { if (framesPerSecond <= 0.0) {
// frame rate unknown, so collect a sequence of PTS values: // 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 if (numPtsValues < 2 || numPtsValues < MaxPtsValues && numIFrames < 2) { // collect a sequence containing at least two I-frames
const uchar *Pes = Data + TsPayloadOffset(Data); if (newFrame) { // only take PTS values at the beginning of a frame (in case if fields!)
if (numIFrames && PesHasPts(Pes)) { const uchar *Pes = Data + TsPayloadOffset(Data);
ptsValues[numPtsValues] = PesGetPts(Pes); if (numIFrames && PesHasPts(Pes)) {
// check for rollover: ptsValues[numPtsValues] = PesGetPts(Pes);
if (numPtsValues && ptsValues[numPtsValues - 1] > 0xF0000000 && ptsValues[numPtsValues] < 0x10000000) { // check for rollover:
dbgframes("#"); if (numPtsValues && ptsValues[numPtsValues - 1] > 0xF0000000 && ptsValues[numPtsValues] < 0x10000000) {
numPtsValues = 0; dbgframes("#");
numIFrames = 0; numPtsValues = 0;
numFrames = 0; numIFrames = 0;
}
else
numPtsValues++;
} }
else
numPtsValues++;
} }
} }
else { if (numPtsValues >= 2 && numIFrames >= 2) {
// find the smallest PTS delta: // find the smallest PTS delta:
qsort(ptsValues, numPtsValues, sizeof(uint32_t), CmpUint32); qsort(ptsValues, numPtsValues, sizeof(uint32_t), CmpUint32);
numPtsValues--; numPtsValues--;
for (int i = 0; i < numPtsValues; i++) for (int i = 0; i < numPtsValues; i++)
ptsValues[i] = ptsValues[i + 1] - ptsValues[i]; ptsValues[i] = ptsValues[i + 1] - ptsValues[i];
qsort(ptsValues, numPtsValues, sizeof(uint32_t), CmpUint32); qsort(ptsValues, numPtsValues, sizeof(uint32_t), CmpUint32);
uint32_t Delta = ptsValues[0]; uint32_t Delta = ptsValues[0] / framesPerPayloadUnit;
// determine frame info: // determine frame info:
if (isVideo) { if (isVideo) {
if (abs(Delta - 3600) <= 1) if (abs(Delta - 3600) <= 1)
framesPerSecond = 25.0; framesPerSecond = 25.0;
else if (Delta % 3003 == 0) else if (Delta % 3003 == 0)
framesPerSecond = 30.0 / 1.001; framesPerSecond = 30.0 / 1.001;
else if (abs(Delta - 1800) <= 1) { else if (abs(Delta - 1800) <= 1)
if (numFrames > 50) { framesPerSecond = 50.0;
// 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 (Delta == 1501) else if (Delta == 1501)
if (numFrames > 50) { framesPerSecond = 60.0 / 1.001;
// 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;
else { else {
framesPerSecond = DEFAULTFRAMESPERSECOND; framesPerSecond = DEFAULTFRAMESPERSECOND;
dsyslog("unknown frame delta (%d), assuming %5.2f fps", Delta, 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 else // audio
framesPerSecond = 90000.0 / Delta; // PTS of audio frames is always increasing 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) else if (Pid == PATPID && synced && Processed)
return Processed; // allow the caller to see any PAT packets return Processed; // allow the caller to see any PAT packets
} }
Data += TS_SIZE; Data += Handled;
Length -= TS_SIZE; Length -= Handled;
Processed += TS_SIZE; Processed += Handled;
if (newFrame)
break;
} }
return Processed; return Processed;
} }

72
remux.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __REMUX_H
@ -151,6 +151,60 @@ inline int64_t PesGetPts(const uchar *p)
((((int64_t)p[13]) & 0xFE) >> 1); ((((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: // PAT/PMT Generator:
#define MAX_SECTION_SIZE 4096 // maximum size of an SI section #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 ///< are delivered to the parser through several subsequent calls to
///< ParsePmt(). The whole PMT data will be processed once the last packet ///< ParsePmt(). The whole PMT data will be processed once the last packet
///< has been received. ///< 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; bool GetVersions(int &PatVersion, int &PmtVersion) const;
///< Returns true if a valid PAT/PMT has been parsed and stores ///< Returns true if a valid PAT/PMT has been parsed and stores
///< the current version numbers in the given variables. ///< 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 #define MIN_TS_PACKETS_FOR_FRAME_DETECTOR 5
class cFrameParser;
class cFrameDetector { class cFrameDetector {
private: private:
enum { MaxPtsValues = 150 }; enum { MaxPtsValues = 150 };
@ -354,12 +414,9 @@ private:
double framesPerSecond; double framesPerSecond;
int framesInPayloadUnit; int framesInPayloadUnit;
int framesPerPayloadUnit; // Some broadcasters send one frame per payload unit (== 1), int framesPerPayloadUnit; // Some broadcasters send one frame per payload unit (== 1),
// some put an entire GOP into one payload unit (> 1), and // while others put an entire GOP into one payload unit (> 1).
// some spread a single frame over several payload units (< 0).
int payloadUnitOfFrame;
bool scanning; bool scanning;
uint32_t scanner; cFrameParser *parser;
int SkipPackets(const uchar *&Data, int &Length, int &Processed, int &FrameTypeOffset);
public: public:
cFrameDetector(int Pid = 0, int Type = 0); cFrameDetector(int Pid = 0, int Type = 0);
///< Sets up a frame detector for the given Pid and stream Type. ///< Sets up a frame detector for the given Pid and stream Type.
@ -367,9 +424,6 @@ public:
///< call to SetPid(). ///< call to SetPid().
void SetPid(int Pid, int Type); void SetPid(int Pid, int Type);
///< Sets the Pid and stream Type to detect frames for. ///< 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); int Analyze(const uchar *Data, int Length);
///< Analyzes the TS packets pointed to by Data. Length is the number of ///< 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. ///< bytes Data points to, and must be a multiple of TS_SIZE.