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:
parent
38d48afad9
commit
57a3169013
7
HISTORY
7
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.
|
||||
|
@ -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);
|
||||
|
694
remux.c
694
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 ----------------------------------------------------------
|
||||
|
||||
#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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
72
remux.h
72
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.
|
||||
|
Loading…
Reference in New Issue
Block a user