mirror of
https://github.com/VDR4Arch/vdr.git
synced 2023-10-10 13:36:52 +02:00
Improved editing TS recordings
This commit is contained in:
parent
5b4e1fa793
commit
cca2cd35ad
17
HISTORY
17
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-11-12: Version 1.7.32
|
||||
2012-11-18: 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).
|
||||
@ -7315,3 +7315,18 @@ Video Disk Recorder Revision History
|
||||
- The return type of cMarks::Add() has been changed to void, since due to the sorting
|
||||
of the list of marks the returned pointer might have pointed to a totally different
|
||||
mark. Besides, the return value was never actually used.
|
||||
- Improved editing TS recordings by
|
||||
+ stripping dangling TS packets from the beginning of a sequence
|
||||
+ including pending TS packets at the end of a sequence
|
||||
+ fixing all timestamps and continuity counters
|
||||
+ generating editing marks for the edited version in such a way that each cutting
|
||||
point is marked by an "end" and "begin" mark with the same offset
|
||||
+ no longer generating an editing mark at the "end" of the edited recording (this
|
||||
was actually generated at the beginning of the last GOP, so that a subsequent
|
||||
edit would have cut off the last GOP)
|
||||
+ no longer generating any editing marks if the edited recording results on just
|
||||
one single sequence
|
||||
+ ignoring pairs of editing marks that are placed at exactly the same position of
|
||||
a recording when actually cutting the recording
|
||||
+ not doing anything if the editing marks in place would result in the edited
|
||||
version being the same as the original recording
|
||||
|
20
MANUAL
20
MANUAL
@ -367,13 +367,13 @@ Version 1.6
|
||||
- 7, 9 Jump back and forward between editing marks. Replay goes into still
|
||||
mode after jumping to a mark.
|
||||
- 8 Positions replay at a point 3 seconds before the current or next
|
||||
"start" mark and starts replay.
|
||||
"begin" mark and starts replay.
|
||||
- 2 Start the actual cutting process.
|
||||
|
||||
Editing marks are represented by black, vertical lines in the progress display.
|
||||
A small black triangle at the top of the mark means that this is a "start"
|
||||
A small black triangle at the top of the mark means that this is a "begin"
|
||||
mark, and a triangle at the bottom means that this is an "end" mark.
|
||||
The cutting process will save all video data between "start" and "end" marks
|
||||
The cutting process will save all video data between "begin" and "end" marks
|
||||
into a new file (the original recording remains untouched). The new file will
|
||||
have the same name as the original recording, preceded with a '%' character
|
||||
(imagine the '%' somehow looking like a pair of scissors ;-). Red bars in the
|
||||
@ -382,7 +382,7 @@ Version 1.6
|
||||
|
||||
The video sequences to be saved by the cutting process are determined by an
|
||||
"even/odd" algorithm. This means that every odd numbered editing mark (i.e.
|
||||
1, 3, 5,...) represents a "start" mark, while every even numbered mark (2, 4,
|
||||
1, 3, 5,...) represents a "begin" mark, while every even numbered mark (2, 4,
|
||||
6,...) is an "end" mark. Inserting or toggling a mark on or off automatically
|
||||
adjusts the sequence to the right side of that mark.
|
||||
|
||||
@ -395,11 +395,13 @@ Version 1.6
|
||||
version of the recording you can use the '8' key to jump to a point just
|
||||
before the next cut and have a look at the resulting sequence.
|
||||
|
||||
Currently editing marks can only be set at I-frames, which typically is
|
||||
every 12th frame. So editing can be done with a resolution of roughly half
|
||||
a second. A "start" mark marks the first frame of a resulting video
|
||||
sequence, and an "end" mark marks the last frame of that sequence.
|
||||
|
||||
Currently editing marks can only be set at I-frames, which typically appear
|
||||
every half of a second to a second. A "begin" mark marks the first frame of
|
||||
a resulting video sequence, and an "end" mark marks the last frame of that
|
||||
sequence. Note that the actual frame indicated by the an "end" mark will
|
||||
not be included in the edited version of the recording. That's because every
|
||||
recording (and every sequence of an edited recording) begins with an I-frame
|
||||
and ends right before the next I-frame.
|
||||
An edited recording (indicated by the '%' character) will never be deleted
|
||||
automatically in case the disk runs full (no matter what "lifetime" it has).
|
||||
|
||||
|
654
cutter.c
654
cutter.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: cutter.c 2.15 2012/10/04 12:19:37 kls Exp $
|
||||
* $Id: cutter.c 2.16 2012/11/18 12:09:00 kls Exp $
|
||||
*/
|
||||
|
||||
#include "cutter.h"
|
||||
@ -13,17 +13,261 @@
|
||||
#include "remux.h"
|
||||
#include "videodir.h"
|
||||
|
||||
// --- cPacketBuffer ---------------------------------------------------------
|
||||
|
||||
class cPacketBuffer {
|
||||
private:
|
||||
uchar *data;
|
||||
int size;
|
||||
int length;
|
||||
public:
|
||||
cPacketBuffer(void);
|
||||
~cPacketBuffer();
|
||||
void Append(uchar *Data, int Length);
|
||||
///< Appends Length bytes of Data to this packet buffer.
|
||||
void Flush(uchar *Data, int &Length, int MaxLength);
|
||||
///< Flushes the content of this packet buffer into the given Data, starting
|
||||
///< at position Length, and clears the buffer afterwards. Length will be
|
||||
///< incremented accordingly. If Length plus the total length of the stored
|
||||
///< packets would exceed MaxLength, nothing is copied.
|
||||
};
|
||||
|
||||
cPacketBuffer::cPacketBuffer(void)
|
||||
{
|
||||
data = NULL;
|
||||
size = length = 0;
|
||||
}
|
||||
|
||||
cPacketBuffer::~cPacketBuffer()
|
||||
{
|
||||
free(data);
|
||||
}
|
||||
|
||||
void cPacketBuffer::Append(uchar *Data, int Length)
|
||||
{
|
||||
if (length + Length >= size) {
|
||||
int NewSize = (length + Length) * 3 / 2;
|
||||
if (uchar *p = (uchar *)realloc(data, NewSize)) {
|
||||
data = p;
|
||||
size = NewSize;
|
||||
}
|
||||
else
|
||||
return; // out of memory
|
||||
}
|
||||
memcpy(data + length, Data, Length);
|
||||
length += Length;
|
||||
}
|
||||
|
||||
void cPacketBuffer::Flush(uchar *Data, int &Length, int MaxLength)
|
||||
{
|
||||
if (Data && length > 0 && Length + length <= MaxLength) {
|
||||
memcpy(Data + Length, data, length);
|
||||
Length += length;
|
||||
}
|
||||
length = 0;
|
||||
}
|
||||
|
||||
// --- cPacketStorage --------------------------------------------------------
|
||||
|
||||
class cPacketStorage {
|
||||
private:
|
||||
cPacketBuffer *buffers[MAXPID];
|
||||
public:
|
||||
cPacketStorage(void);
|
||||
~cPacketStorage();
|
||||
void Append(int Pid, uchar *Data, int Length);
|
||||
void Flush(int Pid, uchar *Data, int &Length, int MaxLength);
|
||||
};
|
||||
|
||||
cPacketStorage::cPacketStorage(void)
|
||||
{
|
||||
for (int i = 0; i < MAXPID; i++)
|
||||
buffers[i] = NULL;
|
||||
}
|
||||
|
||||
cPacketStorage::~cPacketStorage()
|
||||
{
|
||||
for (int i = 0; i < MAXPID; i++)
|
||||
delete buffers[i];
|
||||
}
|
||||
|
||||
void cPacketStorage::Append(int Pid, uchar *Data, int Length)
|
||||
{
|
||||
if (!buffers[Pid])
|
||||
buffers[Pid] = new cPacketBuffer;
|
||||
buffers[Pid]->Append(Data, Length);
|
||||
}
|
||||
|
||||
void cPacketStorage::Flush(int Pid, uchar *Data, int &Length, int MaxLength)
|
||||
{
|
||||
if (buffers[Pid])
|
||||
buffers[Pid]->Flush(Data, Length, MaxLength);
|
||||
}
|
||||
|
||||
// --- cDanglingPacketStripper -----------------------------------------------
|
||||
|
||||
class cDanglingPacketStripper {
|
||||
private:
|
||||
bool processed[MAXPID];
|
||||
cPatPmtParser patPmtParser;
|
||||
public:
|
||||
cDanglingPacketStripper(void);
|
||||
bool Process(uchar *Data, int Length, int64_t FirstPts);
|
||||
///< Scans the frame given in Data and hides the payloads of any TS packets
|
||||
///< that either didn't start within this frame, or have a PTS that is
|
||||
///< before FirstPts. The TS packets in question are not physically removed
|
||||
///< from Data in order to keep any frame counts and PCR timestamps intact.
|
||||
///< Returns true if any dangling packets have been found.
|
||||
};
|
||||
|
||||
cDanglingPacketStripper::cDanglingPacketStripper(void)
|
||||
{
|
||||
memset(processed, 0x00, sizeof(processed));
|
||||
}
|
||||
|
||||
bool cDanglingPacketStripper::Process(uchar *Data, int Length, int64_t FirstPts)
|
||||
{
|
||||
bool Found = false;
|
||||
while (Length >= TS_SIZE && *Data == TS_SYNC_BYTE) {
|
||||
int Pid = TsPid(Data);
|
||||
if (Pid == PATPID)
|
||||
patPmtParser.ParsePat(Data, TS_SIZE);
|
||||
else if (Pid == patPmtParser.PmtPid())
|
||||
patPmtParser.ParsePmt(Data, TS_SIZE);
|
||||
else {
|
||||
int64_t Pts = TsGetPts(Data, TS_SIZE);
|
||||
if (Pts >= 0)
|
||||
processed[Pid] = PtsDiff(FirstPts, Pts) >= 0; // Pts is at or after FirstPts
|
||||
if (!processed[Pid]) {
|
||||
TsHidePayload(Data);
|
||||
Found = true;
|
||||
}
|
||||
}
|
||||
Length -= TS_SIZE;
|
||||
Data += TS_SIZE;
|
||||
}
|
||||
return Found;
|
||||
}
|
||||
|
||||
// --- cPtsFixer -------------------------------------------------------------
|
||||
|
||||
class cPtsFixer {
|
||||
private:
|
||||
int delta; // time between two frames
|
||||
int64_t last; // the last (i.e. highest) video PTS value seen
|
||||
int64_t offset; // offset to add to PTS values
|
||||
bool fixCounters; // controls fixing the TS continuity counters (only from the second CutIn up)
|
||||
uchar counter[MAXPID]; // the TS continuity counter for each PID
|
||||
cPatPmtParser patPmtParser;
|
||||
public:
|
||||
cPtsFixer(void);
|
||||
void Setup(double FramesPerSecond);
|
||||
void Fix(uchar *Data, int Length, bool CutIn);
|
||||
};
|
||||
|
||||
cPtsFixer::cPtsFixer(void)
|
||||
{
|
||||
delta = 0;
|
||||
last = -1;
|
||||
offset = -1;
|
||||
fixCounters = false;
|
||||
memset(counter, 0x00, sizeof(counter));
|
||||
}
|
||||
|
||||
void cPtsFixer::Setup(double FramesPerSecond)
|
||||
{
|
||||
delta = int(round(PTSTICKS / FramesPerSecond));
|
||||
}
|
||||
|
||||
void cPtsFixer::Fix(uchar *Data, int Length, bool CutIn)
|
||||
{
|
||||
if (!patPmtParser.Vpid()) {
|
||||
if (!patPmtParser.ParsePatPmt(Data, Length))
|
||||
return;
|
||||
}
|
||||
// Determine the PTS offset at the beginning of each sequence (except the first one):
|
||||
if (CutIn && last >= 0) {
|
||||
int64_t Pts = TsGetPts(Data, Length);
|
||||
if (Pts >= 0) {
|
||||
// offset is calculated so that Pts + offset results in last + delta:
|
||||
offset = Pts - PtsAdd(last, delta);
|
||||
if (offset <= 0)
|
||||
offset = -offset;
|
||||
else
|
||||
offset = MAX33BIT + 1 - offset;
|
||||
}
|
||||
fixCounters = true;
|
||||
}
|
||||
// Keep track of the highest video PTS:
|
||||
uchar *p = Data;
|
||||
int len = Length;
|
||||
while (len >= TS_SIZE && *p == TS_SYNC_BYTE) {
|
||||
int Pid = TsPid(p);
|
||||
if (Pid == patPmtParser.Vpid()) {
|
||||
int64_t Pts = PtsAdd(TsGetPts(p, TS_SIZE), offset); // offset is taken into account here, to make last have the "new" value already!
|
||||
if (Pts >= 0 && (last < 0 || PtsDiff(last, Pts) > 0))
|
||||
last = Pts;
|
||||
}
|
||||
// Adjust the TS continuity counter:
|
||||
if (fixCounters) {
|
||||
counter[Pid] = (counter[Pid] + 1) & TS_CONT_CNT_MASK;
|
||||
TsSetContinuityCounter(p, counter[Pid]);
|
||||
}
|
||||
else
|
||||
counter[Pid] = TsGetContinuityCounter(p); // collect initial counters
|
||||
p += TS_SIZE;
|
||||
len -= TS_SIZE;
|
||||
}
|
||||
// Apply the PTS offset:
|
||||
if (offset > 0) {
|
||||
uchar *p = Data;
|
||||
int len = Length;
|
||||
while (len >= TS_SIZE && *p == TS_SYNC_BYTE) {
|
||||
// Adjust the various timestamps:
|
||||
int64_t Pts = TsGetPts(p, TS_SIZE);
|
||||
if (Pts >= 0)
|
||||
TsSetPts(p, TS_SIZE, PtsAdd(Pts, offset));
|
||||
int64_t Dts = TsGetDts(p, TS_SIZE);
|
||||
if (Dts >= 0)
|
||||
TsSetDts(p, TS_SIZE, PtsAdd(Dts, offset));
|
||||
int64_t Pcr = TsGetPcr(p);
|
||||
if (Pcr >= 0) {
|
||||
int64_t NewPcr = Pcr + offset * PCRFACTOR;
|
||||
if (NewPcr >= MAX27MHZ)
|
||||
NewPcr -= MAX27MHZ + 1;
|
||||
TsSetPcr(p, NewPcr);
|
||||
}
|
||||
p += TS_SIZE;
|
||||
len -= TS_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- cCuttingThread --------------------------------------------------------
|
||||
|
||||
class cCuttingThread : public cThread {
|
||||
private:
|
||||
const char *error;
|
||||
bool isPesRecording;
|
||||
double framesPerSecond;
|
||||
cUnbufferedFile *fromFile, *toFile;
|
||||
cFileName *fromFileName, *toFileName;
|
||||
cIndexFile *fromIndex, *toIndex;
|
||||
cMarks fromMarks, toMarks;
|
||||
int numSequences;
|
||||
off_t maxVideoFileSize;
|
||||
off_t fileSize;
|
||||
cPtsFixer ptsFixer;
|
||||
bool suspensionLogged;
|
||||
bool Throttled(void);
|
||||
bool SwitchFile(bool Force = false);
|
||||
bool LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length);
|
||||
bool FramesAreEqual(int Index1, int Index2);
|
||||
void GetPendingPackets(uchar *Buffer, int &Length, int Index, int64_t LastPts);
|
||||
// Gather all non-video TS packets from Index upward that either belong to
|
||||
// payloads that started before Index, or have a PTS that is before LastPts,
|
||||
// and add them to the end of the given Data.
|
||||
bool ProcessSequence(int LastEndIndex, int BeginIndex, int EndIndex, int NextBeginIndex);
|
||||
protected:
|
||||
virtual void Action(void);
|
||||
public:
|
||||
@ -41,16 +285,25 @@ cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName)
|
||||
fromIndex = toIndex = NULL;
|
||||
cRecording Recording(FromFileName);
|
||||
isPesRecording = Recording.IsPesRecording();
|
||||
if (fromMarks.Load(FromFileName, Recording.FramesPerSecond(), isPesRecording) && fromMarks.Count()) {
|
||||
fromFileName = new cFileName(FromFileName, false, true, isPesRecording);
|
||||
toFileName = new cFileName(ToFileName, true, true, isPesRecording);
|
||||
fromIndex = new cIndexFile(FromFileName, false, isPesRecording);
|
||||
toIndex = new cIndexFile(ToFileName, true, isPesRecording);
|
||||
toMarks.Load(ToFileName, Recording.FramesPerSecond(), isPesRecording); // doesn't actually load marks, just sets the file name
|
||||
maxVideoFileSize = MEGABYTE(Setup.MaxVideoFileSize);
|
||||
if (isPesRecording && maxVideoFileSize > MEGABYTE(MAXVIDEOFILESIZEPES))
|
||||
maxVideoFileSize = MEGABYTE(MAXVIDEOFILESIZEPES);
|
||||
Start();
|
||||
framesPerSecond = Recording.FramesPerSecond();
|
||||
suspensionLogged = false;
|
||||
fileSize = 0;
|
||||
ptsFixer.Setup(framesPerSecond);
|
||||
if (fromMarks.Load(FromFileName, framesPerSecond, isPesRecording) && fromMarks.Count()) {
|
||||
numSequences = fromMarks.GetNumSequences();
|
||||
if (numSequences > 0) {
|
||||
fromFileName = new cFileName(FromFileName, false, true, isPesRecording);
|
||||
toFileName = new cFileName(ToFileName, true, true, isPesRecording);
|
||||
fromIndex = new cIndexFile(FromFileName, false, isPesRecording);
|
||||
toIndex = new cIndexFile(ToFileName, true, isPesRecording);
|
||||
toMarks.Load(ToFileName, framesPerSecond, isPesRecording); // doesn't actually load marks, just sets the file name
|
||||
maxVideoFileSize = MEGABYTE(Setup.MaxVideoFileSize);
|
||||
if (isPesRecording && maxVideoFileSize > MEGABYTE(MAXVIDEOFILESIZEPES))
|
||||
maxVideoFileSize = MEGABYTE(MAXVIDEOFILESIZEPES);
|
||||
Start();
|
||||
}
|
||||
else
|
||||
esyslog("no editing sequences found for %s", FromFileName);
|
||||
}
|
||||
else
|
||||
esyslog("no editing marks found for %s", FromFileName);
|
||||
@ -65,168 +318,235 @@ cCuttingThread::~cCuttingThread()
|
||||
delete toIndex;
|
||||
}
|
||||
|
||||
bool cCuttingThread::Throttled(void)
|
||||
{
|
||||
if (cIoThrottle::Engaged()) {
|
||||
if (!suspensionLogged) {
|
||||
dsyslog("suspending cutter thread");
|
||||
suspensionLogged = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (suspensionLogged) {
|
||||
dsyslog("resuming cutter thread");
|
||||
suspensionLogged = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cCuttingThread::LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length)
|
||||
{
|
||||
uint16_t FileNumber;
|
||||
off_t FileOffset;
|
||||
if (fromIndex->Get(Index, &FileNumber, &FileOffset, &Independent, &Length)) {
|
||||
fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
|
||||
if (fromFile) {
|
||||
fromFile->SetReadAhead(MEGABYTE(20));
|
||||
int len = ReadFrame(fromFile, Buffer, Length, MAXFRAMESIZE);
|
||||
if (len < 0)
|
||||
error = "ReadFrame";
|
||||
else if (len != Length)
|
||||
Length = len;
|
||||
return error == NULL;
|
||||
}
|
||||
else
|
||||
error = "fromFile";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cCuttingThread::SwitchFile(bool Force)
|
||||
{
|
||||
if (fileSize > maxVideoFileSize || Force) {
|
||||
toFile = toFileName->NextFile();
|
||||
if (!toFile) {
|
||||
error = "toFile";
|
||||
return false;
|
||||
}
|
||||
fileSize = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cCuttingThread::FramesAreEqual(int Index1, int Index2)
|
||||
{
|
||||
bool Independent;
|
||||
uchar Buffer1[MAXFRAMESIZE];
|
||||
uchar Buffer2[MAXFRAMESIZE];
|
||||
int Length1;
|
||||
int Length2;
|
||||
if (LoadFrame(Index1, Buffer1, Independent, Length1) && LoadFrame(Index2, Buffer2, Independent, Length2)) {
|
||||
if (Length1 == Length2) {
|
||||
int Diffs = 0;
|
||||
for (int i = 0; i < Length1; i++) {
|
||||
if (Buffer1[i] != Buffer2[i]) {
|
||||
if (Diffs++ > 10) // the continuity counters of the PAT/PMT packets may differ
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cCuttingThread::GetPendingPackets(uchar *Data, int &Length, int Index, int64_t LastPts)
|
||||
{
|
||||
bool Processed[MAXPID] = { false };
|
||||
int NumIndependentFrames = 0;
|
||||
cPatPmtParser PatPmtParser;
|
||||
cPacketStorage PacketStorage;
|
||||
for (; NumIndependentFrames < 2; Index++) {
|
||||
uchar Buffer[MAXFRAMESIZE];
|
||||
bool Independent;
|
||||
int len;
|
||||
if (LoadFrame(Index, Buffer, Independent, len)) {
|
||||
if (Independent)
|
||||
NumIndependentFrames++;
|
||||
uchar *p = Buffer;
|
||||
while (len >= TS_SIZE && *p == TS_SYNC_BYTE) {
|
||||
int Pid = TsPid(p);
|
||||
if (Pid == PATPID)
|
||||
PatPmtParser.ParsePat(p, TS_SIZE);
|
||||
else if (Pid == PatPmtParser.PmtPid())
|
||||
PatPmtParser.ParsePmt(p, TS_SIZE);
|
||||
else if (!Processed[Pid]) {
|
||||
int64_t Pts = TsGetPts(p, TS_SIZE);
|
||||
if (Pts >= 0) {
|
||||
int64_t d = PtsDiff(LastPts, Pts);
|
||||
if (d <= 0) // Pts is before or at LastPts
|
||||
PacketStorage.Flush(Pid, Data, Length, MAXFRAMESIZE);
|
||||
if (d >= 0) { // Pts is at or after LastPts
|
||||
NumIndependentFrames = 0; // we search until we find two consecutive I-frames without any more pending packets
|
||||
Processed[Pid] = true;
|
||||
}
|
||||
}
|
||||
if (!Processed[Pid])
|
||||
PacketStorage.Append(Pid, p, TS_SIZE);
|
||||
}
|
||||
len -= TS_SIZE;
|
||||
p += TS_SIZE;
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool cCuttingThread::ProcessSequence(int LastEndIndex, int BeginIndex, int EndIndex, int NextBeginIndex)
|
||||
{
|
||||
// Check for seamless connections:
|
||||
bool SeamlessBegin = LastEndIndex >= 0 && FramesAreEqual(LastEndIndex, BeginIndex);
|
||||
bool SeamlessEnd = NextBeginIndex >= 0 && FramesAreEqual(EndIndex, NextBeginIndex);
|
||||
// Process all frames from BeginIndex (included) to EndIndex (excluded):
|
||||
cDanglingPacketStripper DanglingPacketStripper;
|
||||
int NumIndependentFrames = 0;
|
||||
int64_t FirstPts = -1;
|
||||
int64_t LastPts = -1;
|
||||
for (int Index = BeginIndex; Running() && Index < EndIndex; Index++) {
|
||||
uchar Buffer[MAXFRAMESIZE];
|
||||
bool Independent;
|
||||
int Length;
|
||||
if (LoadFrame(Index, Buffer, Independent, Length)) {
|
||||
if (!isPesRecording) {
|
||||
int64_t Pts = TsGetPts(Buffer, Length);
|
||||
if (FirstPts < 0)
|
||||
FirstPts = Pts; // the PTS of the first frame in the sequence
|
||||
else if (LastPts < 0 || PtsDiff(LastPts, Pts) > 0)
|
||||
LastPts = Pts; // the PTS of the frame that is displayed as the very last one of the sequence
|
||||
}
|
||||
// Fixup data at the beginning of the sequence:
|
||||
if (!SeamlessBegin) {
|
||||
if (isPesRecording) {
|
||||
if (Index == BeginIndex)
|
||||
cRemux::SetBrokenLink(Buffer, Length);
|
||||
}
|
||||
else if (NumIndependentFrames < 2) {
|
||||
if (DanglingPacketStripper.Process(Buffer, Length, FirstPts))
|
||||
NumIndependentFrames = 0; // we search until we find two consecutive I-frames without any more dangling packets
|
||||
}
|
||||
}
|
||||
// Fixup data at the end of the sequence:
|
||||
if (!SeamlessEnd) {
|
||||
if (Index == EndIndex - 1) {
|
||||
if (!isPesRecording)
|
||||
GetPendingPackets(Buffer, Length, EndIndex, LastPts + int(round(PTSTICKS / framesPerSecond))); // adding one frame length to fully cover the very last frame
|
||||
}
|
||||
}
|
||||
// Fixup timestamps and continuity counters:
|
||||
if (!isPesRecording) {
|
||||
if (numSequences > 1)
|
||||
ptsFixer.Fix(Buffer, Length, !SeamlessBegin && Index == BeginIndex);
|
||||
}
|
||||
// Every file shall start with an independent frame:
|
||||
if (Independent) {
|
||||
NumIndependentFrames++;
|
||||
if (!SwitchFile())
|
||||
return false;
|
||||
}
|
||||
// Write index:
|
||||
if (!toIndex->Write(Independent, toFileName->Number(), fileSize)) {
|
||||
error = "toIndex";
|
||||
return false;
|
||||
}
|
||||
// Write data:
|
||||
if (toFile->Write(Buffer, Length) < 0) {
|
||||
error = "safe_write";
|
||||
return false;
|
||||
}
|
||||
fileSize += Length;
|
||||
// Generate marks at the editing points in the edited recording:
|
||||
if (numSequences > 0 && Index == BeginIndex) {
|
||||
if (toMarks.Count() > 0)
|
||||
toMarks.Add(toIndex->Last());
|
||||
toMarks.Add(toIndex->Last());
|
||||
toMarks.Save();
|
||||
}
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void cCuttingThread::Action(void)
|
||||
{
|
||||
cMark *Mark = fromMarks.First();
|
||||
if (Mark) {
|
||||
if (cMark *BeginMark = fromMarks.GetNextBegin()) {
|
||||
fromFile = fromFileName->Open();
|
||||
toFile = toFileName->Open();
|
||||
if (!fromFile || !toFile)
|
||||
return;
|
||||
fromFile->SetReadAhead(MEGABYTE(20));
|
||||
int Index = Mark->Position();
|
||||
Mark = fromMarks.Next(Mark);
|
||||
off_t FileSize = 0;
|
||||
int CurrentFileNumber = 0;
|
||||
int LastIFrame = 0;
|
||||
toMarks.Add(0);
|
||||
toMarks.Save();
|
||||
uchar buffer[MAXFRAMESIZE], buffer2[MAXFRAMESIZE];
|
||||
int Length2;
|
||||
bool CheckForSeamlessStream = false;
|
||||
bool LastMark = false;
|
||||
bool cutIn = true;
|
||||
bool suspensionLogged = false;
|
||||
while (Running()) {
|
||||
uint16_t FileNumber;
|
||||
off_t FileOffset;
|
||||
int Length;
|
||||
bool Independent;
|
||||
|
||||
int LastEndIndex = -1;
|
||||
while (BeginMark && Running()) {
|
||||
// Suspend cutting if we have severe throughput problems:
|
||||
|
||||
if (cIoThrottle::Engaged()) {
|
||||
if (!suspensionLogged) {
|
||||
dsyslog("suspending cutter thread");
|
||||
suspensionLogged = true;
|
||||
}
|
||||
if (Throttled()) {
|
||||
cCondWait::SleepMs(100);
|
||||
continue;
|
||||
}
|
||||
else if (suspensionLogged) {
|
||||
dsyslog("resuming cutter thread");
|
||||
suspensionLogged = false;
|
||||
}
|
||||
|
||||
// Make sure there is enough disk space:
|
||||
|
||||
AssertFreeDiskSpace(-1);
|
||||
|
||||
// Read one frame:
|
||||
|
||||
if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) {
|
||||
if (FileNumber != CurrentFileNumber) {
|
||||
fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
|
||||
if (fromFile)
|
||||
fromFile->SetReadAhead(MEGABYTE(20));
|
||||
CurrentFileNumber = FileNumber;
|
||||
}
|
||||
if (fromFile) {
|
||||
int len = ReadFrame(fromFile, buffer, Length, sizeof(buffer));
|
||||
if (len < 0) {
|
||||
error = "ReadFrame";
|
||||
// Determine the actual begin and end marks, skipping any marks at the same position:
|
||||
cMark *EndMark = fromMarks.GetNextEnd(BeginMark);
|
||||
// Process the current sequence:
|
||||
int EndIndex = EndMark ? EndMark->Position() : fromIndex->Last() + 1;
|
||||
int NextBeginIndex = -1;
|
||||
if (EndMark) {
|
||||
if (cMark *NextBeginMark = fromMarks.GetNextBegin(EndMark))
|
||||
NextBeginIndex = NextBeginMark->Position();
|
||||
}
|
||||
if (!ProcessSequence(LastEndIndex, BeginMark->Position(), EndIndex, NextBeginIndex))
|
||||
break;
|
||||
if (!EndMark)
|
||||
break; // reached EOF
|
||||
LastEndIndex = EndIndex;
|
||||
// Switch to the next sequence:
|
||||
BeginMark = fromMarks.GetNextBegin(EndMark);
|
||||
if (BeginMark) {
|
||||
// Split edited files:
|
||||
if (Setup.SplitEditedFiles) {
|
||||
if (!SwitchFile(true))
|
||||
break;
|
||||
}
|
||||
if (len != Length) {
|
||||
CurrentFileNumber = 0; // this re-syncs in case the frame was larger than the buffer
|
||||
Length = len;
|
||||
}
|
||||
}
|
||||
else {
|
||||
error = "fromFile";
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Error, unless we're past the last cut-in and there's no cut-out
|
||||
if (Mark || LastMark)
|
||||
error = "index";
|
||||
break;
|
||||
}
|
||||
|
||||
// Write one frame:
|
||||
|
||||
if (Independent) { // every file shall start with an independent frame
|
||||
if (LastMark) // edited version shall end before next I-frame
|
||||
break;
|
||||
if (FileSize > maxVideoFileSize) {
|
||||
toFile = toFileName->NextFile();
|
||||
if (!toFile) {
|
||||
error = "toFile 1";
|
||||
break;
|
||||
}
|
||||
FileSize = 0;
|
||||
}
|
||||
LastIFrame = 0;
|
||||
// Compare the current frame with the previously stored one, to see if this is a seamlessly merged recording of the same stream:
|
||||
if (CheckForSeamlessStream) {
|
||||
if (Length == Length2) {
|
||||
int diffs = 0;
|
||||
for (int i = 0; i < Length; i++) {
|
||||
if (buffer[i] != buffer2[i]) {
|
||||
if (diffs++ > 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (diffs < 10) // the continuity counters of the PAT/PMT packets may differ
|
||||
cutIn = false; // it's apparently a seamless stream, so no need for "broken" handling
|
||||
}
|
||||
CheckForSeamlessStream = false;
|
||||
}
|
||||
if (cutIn) {
|
||||
if (isPesRecording)
|
||||
cRemux::SetBrokenLink(buffer, Length);
|
||||
else
|
||||
TsSetTeiOnBrokenPackets(buffer, Length);
|
||||
cutIn = false;
|
||||
}
|
||||
}
|
||||
if (toFile->Write(buffer, Length) < 0) {
|
||||
error = "safe_write";
|
||||
break;
|
||||
}
|
||||
if (!toIndex->Write(Independent, toFileName->Number(), FileSize)) {
|
||||
error = "toIndex";
|
||||
break;
|
||||
}
|
||||
FileSize += Length;
|
||||
if (!LastIFrame)
|
||||
LastIFrame = toIndex->Last();
|
||||
|
||||
// Check editing marks:
|
||||
|
||||
if (Mark && Index >= Mark->Position()) {
|
||||
Mark = fromMarks.Next(Mark);
|
||||
toMarks.Add(LastIFrame);
|
||||
if (Mark)
|
||||
toMarks.Add(toIndex->Last() + 1);
|
||||
toMarks.Save();
|
||||
if (Mark) {
|
||||
// Read the next frame, for later comparison with the first frame at this mark:
|
||||
if (fromIndex->Get(Index, &FileNumber, &FileOffset, &Independent, &Length2)) {
|
||||
if (FileNumber != CurrentFileNumber)
|
||||
fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
|
||||
if (fromFile) {
|
||||
int len = ReadFrame(fromFile, buffer2, Length2, sizeof(buffer2));
|
||||
if (len >= 0 && len == Length2)
|
||||
CheckForSeamlessStream = true;
|
||||
}
|
||||
}
|
||||
Index = Mark->Position();
|
||||
Mark = fromMarks.Next(Mark);
|
||||
CurrentFileNumber = 0; // triggers SetOffset before reading next frame
|
||||
cutIn = true;
|
||||
if (Setup.SplitEditedFiles) {
|
||||
toFile = toFileName->NextFile();
|
||||
if (!toFile) {
|
||||
error = "toFile 2";
|
||||
break;
|
||||
}
|
||||
FileSize = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
LastMark = true;
|
||||
}
|
||||
}
|
||||
Recordings.TouchUpdate();
|
||||
@ -255,7 +575,7 @@ bool cCutter::Start(const char *FileName)
|
||||
|
||||
cMarks FromMarks;
|
||||
FromMarks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording());
|
||||
if (cMark *First = FromMarks.First())
|
||||
if (cMark *First = FromMarks.GetNextBegin())
|
||||
Recording.SetStartTime(Recording.Start() + (int(First->Position() / Recording.FramesPerSecond() + 30) / 60) * 60);
|
||||
|
||||
const char *evn = Recording.PrefixFileName('%');
|
||||
@ -343,13 +663,17 @@ bool CutRecording(const char *FileName)
|
||||
if (Recording.Name()) {
|
||||
cMarks Marks;
|
||||
if (Marks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording()) && Marks.Count()) {
|
||||
if (cCutter::Start(FileName)) {
|
||||
while (cCutter::Active())
|
||||
cCondWait::SleepMs(CUTTINGCHECKINTERVAL);
|
||||
return true;
|
||||
if (Marks.GetNumSequences()) {
|
||||
if (cCutter::Start(FileName)) {
|
||||
while (cCutter::Active())
|
||||
cCondWait::SleepMs(CUTTINGCHECKINTERVAL);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
fprintf(stderr, "can't start editing process\n");
|
||||
}
|
||||
else
|
||||
fprintf(stderr, "can't start editing process\n");
|
||||
fprintf(stderr, "'%s' has no editing sequences\n", FileName);
|
||||
}
|
||||
else
|
||||
fprintf(stderr, "'%s' has no editing marks\n", FileName);
|
||||
|
4
menu.c
4
menu.c
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: menu.c 2.62 2012/10/03 10:14:53 kls Exp $
|
||||
* $Id: menu.c 2.63 2012/11/13 11:23:25 kls Exp $
|
||||
*/
|
||||
|
||||
#include "menu.h"
|
||||
@ -4796,6 +4796,8 @@ void cReplayControl::EditCut(void)
|
||||
if (!cCutter::Active()) {
|
||||
if (!marks.Count())
|
||||
Skins.Message(mtError, tr("No editing marks defined!"));
|
||||
else if (!marks.GetNumSequences())
|
||||
Skins.Message(mtError, tr("No editing sequences defined!"));
|
||||
else if (!cCutter::Start(fileName))
|
||||
Skins.Message(mtError, tr("Can't start editing process!"));
|
||||
else
|
||||
|
5
po/ar.po
5
po/ar.po
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.7.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2008-10-16 11:16-0400\n"
|
||||
"Last-Translator: Osama Alrawab <alrawab@hotmail.com>\n"
|
||||
"Language-Team: Arabic <ar@li.org>\n"
|
||||
@ -1241,6 +1241,9 @@ msgstr "اقفز الى "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "لاتوجد علامات تعديل معرفة"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "لا يمكن البدء فى عملية التعديل"
|
||||
|
||||
|
@ -10,7 +10,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2008-03-02 19:02+0100\n"
|
||||
"Last-Translator: Luca Olivetti <luca@ventoso.org>\n"
|
||||
"Language-Team: Catalan <vdr@linuxtv.org>\n"
|
||||
@ -1216,6 +1216,9 @@ msgstr "Salta a:"
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "No hi ha marques d'edició definides"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "No puc iniciar el procés d'edició!"
|
||||
|
||||
|
@ -9,7 +9,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.7.14\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2010-05-06 11:00+0200\n"
|
||||
"Last-Translator: Radek Šťastný <dedkus@gmail.com>\n"
|
||||
"Language-Team: Czech <vdr@linuxtv.org>\n"
|
||||
@ -1215,6 +1215,9 @@ msgstr "Skok: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Nejsou definovány editační značky!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Nelze začít editační proces!"
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2007-08-12 14:17+0200\n"
|
||||
"Last-Translator: Mogens Elneff <mogens@elneff.dk>\n"
|
||||
"Language-Team: Danish <vdr@linuxtv.org>\n"
|
||||
@ -1213,6 +1213,9 @@ msgstr "Hop: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Der er ikke sat nogen redigeringsmærker!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Kan ikke starte redigeringsprocessen!"
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2010-01-16 16:46+0100\n"
|
||||
"Last-Translator: Klaus Schmidinger <kls@tvdr.de>\n"
|
||||
"Language-Team: German <vdr@linuxtv.org>\n"
|
||||
@ -1213,6 +1213,9 @@ msgstr "Springen: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Keine Schnittmarken gesetzt!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr "Keine Schnittsequenzen definiert!"
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Schnitt kann nicht gestartet werden!"
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2007-08-12 14:17+0200\n"
|
||||
"Last-Translator: Dimitrios Dimitrakos <mail@dimitrios.de>\n"
|
||||
"Language-Team: Greek <vdr@linuxtv.org>\n"
|
||||
@ -1213,6 +1213,9 @@ msgstr "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "ÄÝí Ý÷ïõí ïñéóôåß óçìåßá åðåîåñãáóßáò"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Áäõíáìßá åêêßíçóçò ôçò åðåîåñãáóßáò!"
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2008-03-02 19:02+0100\n"
|
||||
"Last-Translator: Luca Olivetti <luca@ventoso.org>\n"
|
||||
"Language-Team: Spanish <vdr@linuxtv.org>\n"
|
||||
@ -1214,6 +1214,9 @@ msgstr "Saltar: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "¡No se definieron marcas de edición!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "¡No se puede iniciar el proceso de edición!"
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2007-08-12 14:17+0200\n"
|
||||
"Last-Translator: Arthur Konovalov <artlov@gmail.com>\n"
|
||||
"Language-Team: Estonian <vdr@linuxtv.org>\n"
|
||||
@ -1213,6 +1213,9 @@ msgstr "Hüpe: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Redigeerimise markerid puuduvad!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Redigeerimise start nurjus!"
|
||||
|
||||
|
@ -10,7 +10,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-13 13:15+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2007-08-15 15:52+0200\n"
|
||||
"Last-Translator: Rolf Ahrenberg <rahrenbe@cc.hut.fi>\n"
|
||||
"Language-Team: Finnish <vdr@linuxtv.org>\n"
|
||||
@ -1216,6 +1216,9 @@ msgstr "Siirry: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Muokkausmerkinnät puuttuvat!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Muokkauksen aloitus epäonnistui!"
|
||||
|
||||
|
@ -13,7 +13,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2008-02-27 18:14+0100\n"
|
||||
"Last-Translator: Jean-Claude Repetto <jc@repetto.org>\n"
|
||||
"Language-Team: French <vdr@linuxtv.org>\n"
|
||||
@ -1219,6 +1219,9 @@ msgstr "Acc
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Pas de marques d'édition définies !"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Impossible de commencer le montage !"
|
||||
|
||||
|
@ -9,7 +9,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2008-03-17 19:00+0100\n"
|
||||
"Last-Translator: Adrian Caval <anrxc@sysphere.org>\n"
|
||||
"Language-Team: Croatian <vdr@linuxtv.org>\n"
|
||||
@ -1215,6 +1215,9 @@ msgstr "Sko
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Nijedna toèka rezanja nije odreðena!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Ne mogu zapoèeti ureðivanje!"
|
||||
|
||||
|
@ -10,7 +10,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2012-01-02 11:54+0200\n"
|
||||
"Last-Translator: István Füley <ifuley@tigercomp.ro>\n"
|
||||
"Language-Team: Hungarian <vdr@linuxtv.org>\n"
|
||||
@ -1217,6 +1217,9 @@ msgstr "Ugr
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Nincs vágópont kijelölve"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "A vágás nem indítható!"
|
||||
|
||||
|
@ -11,7 +11,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2012-06-06 22:50+0100\n"
|
||||
"Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n"
|
||||
"Language-Team: Italian <vdr@linuxtv.org>\n"
|
||||
@ -1220,6 +1220,9 @@ msgstr "Vai a: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Nessun marcatore di modifica definito!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Impossibile avviare il processo di modifica!"
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.7.16\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2010-10-30 11:55+0200\n"
|
||||
"Last-Translator: Valdemaras Pipiras <varas@ambernet.lt>\n"
|
||||
"Language-Team: Lithuanian <vdr@linuxtv.org>\n"
|
||||
@ -1213,6 +1213,9 @@ msgstr "Peršokti: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Nenustatytos koregavimo žymės!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Negali pradėti koregavimo!"
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR-1.7.14\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2010-03-11 00:54+0100\n"
|
||||
"Last-Translator: Dimitar Petrovski <dimeptr@gmail.com>\n"
|
||||
"Language-Team: Macedonian <en@li.org>\n"
|
||||
@ -1214,6 +1214,9 @@ msgstr "Скокни:"
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Нема одредено ознаки за сечење!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Не може да почне уредување!"
|
||||
|
||||
|
@ -11,7 +11,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2008-02-26 17:20+0100\n"
|
||||
"Last-Translator: Johan Schuring <johan.schuring@vetteblei.nl>\n"
|
||||
"Language-Team: Dutch <vdr@linuxtv.org>\n"
|
||||
@ -1217,6 +1217,9 @@ msgstr "Springen: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Geen bewerkingsmarkeringen gedefinieerd!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Kan niet beginnen met bewerken!"
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2007-08-12 14:17+0200\n"
|
||||
"Last-Translator: Truls Slevigen <truls@slevigen.no>\n"
|
||||
"Language-Team: Norwegian Nynorsk <vdr@linuxtv.org>\n"
|
||||
@ -1214,6 +1214,9 @@ msgstr "Hopp: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Kan ikke starte redigeringsprosessen!"
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2008-03-09 12:59+0100\n"
|
||||
"Last-Translator: Michael Rakowski <mrak@gmx.de>\n"
|
||||
"Language-Team: Polish <vdr@linuxtv.org>\n"
|
||||
@ -1214,6 +1214,9 @@ msgstr "Skok: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Nie zdefiniowano znaczników monta¿u!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Nie mo¿na uruchomiæ procesu edycji!"
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.7.15\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2010-03-28 22:49+0100\n"
|
||||
"Last-Translator: Cris Silva <hudokkow@gmail.com>\n"
|
||||
"Language-Team: Portuguese <vdr@linuxtv.org>\n"
|
||||
@ -1214,6 +1214,9 @@ msgstr "Saltar: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Marcas de edição não foram definidas!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Impossível iniciar processo de edição!"
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.7.12\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2012-11-05 01:28+0100\n"
|
||||
"Last-Translator: Lucian Muresan <lucianm@users.sourceforge.net>\n"
|
||||
"Language-Team: Romanian <vdr@linuxtv.org>\n"
|
||||
@ -1216,6 +1216,9 @@ msgstr "Salt la: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Nu s-au pus marcaje de montaj pentru această înregistrare"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Nu pot porni montajul înregistrării!"
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2008-12-15 14:37+0100\n"
|
||||
"Last-Translator: Oleg Roitburd <oleg@roitburd.de>\n"
|
||||
"Language-Team: Russian <vdr@linuxtv.org>\n"
|
||||
@ -1214,6 +1214,9 @@ msgstr "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "½Õ ×ÐÔÐÝë ÜÕâÚØ ÔÛï ÜÞÝâÐÖÐ!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "½ÕÒÞ×ÜÞÖÝÞ ÝÐçÐâì ÜÞÝâÐÖ ×ÐßØáØ!"
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.7.16\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2011-02-15 16:29+0100\n"
|
||||
"Last-Translator: Milan Hrala <hrala.milan@gmail.com>\n"
|
||||
"Language-Team: Slovak <vdr@linuxtv.org>\n"
|
||||
@ -1213,6 +1213,9 @@ msgstr "Skok: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Nie sú urèené znaèky úprav!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Nemô¾e zaèa» spracovanie úprav!"
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2008-02-28 19:44+0100\n"
|
||||
"Last-Translator: Matjaz Thaler <matjaz.thaler@guest.arnes.si>\n"
|
||||
"Language-Team: Slovenian <vdr@linuxtv.org>\n"
|
||||
@ -1214,6 +1214,9 @@ msgstr "Sko
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Nobena prekinitvena toèka ni definirana!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Ne morem zaèeti urejanja!"
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.7.1\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2011-01-09 15:57+0100\n"
|
||||
"Last-Translator: Milan Cvijanoviæ <elcom_cvijo@hotmail.com>\n"
|
||||
"Language-Team: Serbian <vdr@linuxtv.org>\n"
|
||||
@ -1239,6 +1239,9 @@ msgstr "Sko
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Nijedna taèka rezanja nije odreðena!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Ne mogu zapoèeti ureðivanje!"
|
||||
|
||||
|
@ -10,7 +10,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2008-03-12 18:25+0100\n"
|
||||
"Last-Translator: Magnus Andersson <svankan@bahnhof.se>\n"
|
||||
"Language-Team: Swedish <vdr@linuxtv.org>\n"
|
||||
@ -1216,6 +1216,9 @@ msgstr "Hopp: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Det finns inga redigeringsmärken"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Kan inte starta redigering!"
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2008-02-28 00:33+0100\n"
|
||||
"Last-Translator: Oktay Yolgeçen <oktay_73@yahoo.de>\n"
|
||||
"Language-Team: Turkish <vdr@linuxtv.org>\n"
|
||||
@ -1213,6 +1213,9 @@ msgstr "Atla: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Kesim iþaretleri belirtilmemiþ!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Kesim baþlatýlamýyor!"
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.7.7\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2010-04-25 16:35+0200\n"
|
||||
"Last-Translator: Yarema aka Knedlyk <yupadmin@gmail.com>\n"
|
||||
"Language-Team: Ukrainian <vdr@linuxtv.org>\n"
|
||||
@ -1213,6 +1213,9 @@ msgstr "Перейти: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "Не задано міток для монтажу!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "Неможливо почати монтаж запису!"
|
||||
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: VDR 1.6.0\n"
|
||||
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
|
||||
"POT-Creation-Date: 2012-09-15 14:04+0200\n"
|
||||
"POT-Creation-Date: 2012-11-18 14:31+0100\n"
|
||||
"PO-Revision-Date: 2009-09-23 23:50+0800\n"
|
||||
"Last-Translator: Nan Feng <nfgx@21cn.com>\n"
|
||||
"Language-Team: Chinese (simplified) <vdr@linuxtv.org>\n"
|
||||
@ -1216,6 +1216,9 @@ msgstr "跳过: "
|
||||
msgid "No editing marks defined!"
|
||||
msgstr "无编辑标记定义!"
|
||||
|
||||
msgid "No editing sequences defined!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can't start editing process!"
|
||||
msgstr "不能开始编辑处理"
|
||||
|
||||
|
50
recording.c
50
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.72 2012/11/12 14:51:09 kls Exp $
|
||||
* $Id: recording.c 2.73 2012/11/13 13:46:49 kls Exp $
|
||||
*/
|
||||
|
||||
#include "recording.h"
|
||||
@ -1456,6 +1456,54 @@ cMark *cMarks::GetNext(int Position)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cMark *cMarks::GetNextBegin(cMark *EndMark)
|
||||
{
|
||||
cMark *BeginMark = EndMark ? Next(EndMark) : First();
|
||||
if (BeginMark) {
|
||||
while (cMark *NextMark = Next(BeginMark)) {
|
||||
if (BeginMark->Position() == NextMark->Position()) { // skip Begin/End at the same position
|
||||
if (!(BeginMark = Next(NextMark)))
|
||||
break;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
return BeginMark;
|
||||
}
|
||||
|
||||
cMark *cMarks::GetNextEnd(cMark *BeginMark)
|
||||
{
|
||||
if (!BeginMark)
|
||||
return NULL;
|
||||
cMark *EndMark = Next(BeginMark);
|
||||
if (EndMark) {
|
||||
while (cMark *NextMark = Next(EndMark)) {
|
||||
if (EndMark->Position() == NextMark->Position()) { // skip End/Begin at the same position
|
||||
if (!(EndMark = Next(NextMark)))
|
||||
break;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
return EndMark;
|
||||
}
|
||||
|
||||
int cMarks::GetNumSequences(void)
|
||||
{
|
||||
int NumSequences = 0;
|
||||
if (cMark *BeginMark = GetNextBegin()) {
|
||||
while (cMark *EndMark = GetNextEnd(BeginMark)) {
|
||||
NumSequences++;
|
||||
BeginMark = GetNextBegin(EndMark);
|
||||
}
|
||||
if (NumSequences == 0 && BeginMark->Position() > 0)
|
||||
NumSequences = 1; // there is only one actual "begin" mark at a non-zero offset, and no actual "end" mark
|
||||
}
|
||||
return NumSequences;
|
||||
}
|
||||
|
||||
// --- cRecordingUserCommand -------------------------------------------------
|
||||
|
||||
const char *cRecordingUserCommand::command = NULL;
|
||||
|
15
recording.h
15
recording.h
@ -4,7 +4,7 @@
|
||||
* See the main source file 'vdr.c' for copyright information and
|
||||
* how to reach the author.
|
||||
*
|
||||
* $Id: recording.h 2.39 2012/11/12 14:51:09 kls Exp $
|
||||
* $Id: recording.h 2.40 2012/11/13 11:43:59 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __RECORDING_H
|
||||
@ -238,6 +238,19 @@ public:
|
||||
cMark *Get(int Position);
|
||||
cMark *GetPrev(int Position);
|
||||
cMark *GetNext(int Position);
|
||||
cMark *GetNextBegin(cMark *EndMark = NULL);
|
||||
///< Returns the next "begin" mark after EndMark, skipping any marks at the
|
||||
///< same position as EndMark. If EndMark is NULL, the first actual "begin"
|
||||
///< will be returned (if any).
|
||||
cMark *GetNextEnd(cMark *BeginMark);
|
||||
///< Returns the next "end" mark after BeginMark, skipping any marks at the
|
||||
///< same position as BeginMark.
|
||||
int GetNumSequences(void);
|
||||
///< Returns the actual number of sequences to be cut from the recording.
|
||||
///< If there is only one actual "begin" mark, and it is positioned at index
|
||||
///< 0 (the beginning of the recording), and there is no "end" mark, the
|
||||
///< return value is 0, which means that the result is the same as the original
|
||||
///< recording.
|
||||
};
|
||||
|
||||
#define RUC_BEFORERECORDING "before"
|
||||
|
112
remux.c
112
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.70 2012/11/13 10:00:00 kls Exp $
|
||||
* $Id: remux.c 2.71 2012/11/18 12:18:08 kls Exp $
|
||||
*/
|
||||
|
||||
#include "remux.h"
|
||||
@ -114,6 +114,32 @@ void cRemux::SetBrokenLink(uchar *Data, int Length)
|
||||
|
||||
// --- Some TS handling tools ------------------------------------------------
|
||||
|
||||
void TsHidePayload(uchar *p)
|
||||
{
|
||||
p[1] &= ~TS_PAYLOAD_START;
|
||||
p[3] |= TS_ADAPT_FIELD_EXISTS;
|
||||
p[3] &= ~TS_PAYLOAD_EXISTS;
|
||||
p[4] = TS_SIZE - 5;
|
||||
p[5] = 0x00;
|
||||
memset(p + 6, 0xFF, TS_SIZE - 6);
|
||||
}
|
||||
|
||||
void TsSetPcr(uchar *p, int64_t Pcr)
|
||||
{
|
||||
if (TsHasAdaptationField(p)) {
|
||||
if (p[4] >= 7 && (p[5] & TS_ADAPT_PCR)) {
|
||||
int64_t b = Pcr / PCRFACTOR;
|
||||
int e = Pcr % PCRFACTOR;
|
||||
p[ 6] = b >> 25;
|
||||
p[ 7] = b >> 17;
|
||||
p[ 8] = b >> 9;
|
||||
p[ 9] = b >> 1;
|
||||
p[10] = (b << 7) | (p[10] & 0x7E) | ((e >> 8) & 0x01);
|
||||
p[11] = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t TsGetPts(const uchar *p, int l)
|
||||
{
|
||||
// Find the first packet with a PTS and use it:
|
||||
@ -127,25 +153,75 @@ int64_t TsGetPts(const uchar *p, int l)
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TsSetTeiOnBrokenPackets(uchar *p, int l)
|
||||
int64_t TsGetDts(const uchar *p, int l)
|
||||
{
|
||||
bool Processed[MAXPID] = { false };
|
||||
while (l >= TS_SIZE) {
|
||||
if (*p != TS_SYNC_BYTE)
|
||||
break;
|
||||
int Pid = TsPid(p);
|
||||
if (!Processed[Pid]) {
|
||||
if (!TsPayloadStart(p))
|
||||
p[1] |= TS_ERROR;
|
||||
else {
|
||||
Processed[Pid] = true;
|
||||
int offs = TsPayloadOffset(p);
|
||||
cRemux::SetBrokenLink(p + offs, TS_SIZE - offs);
|
||||
}
|
||||
}
|
||||
l -= TS_SIZE;
|
||||
// Find the first packet with a DTS and use it:
|
||||
while (l > 0) {
|
||||
const uchar *d = p;
|
||||
if (TsPayloadStart(d) && TsGetPayload(&d) && PesHasDts(d))
|
||||
return PesGetDts(d);
|
||||
p += TS_SIZE;
|
||||
l -= TS_SIZE;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TsSetPts(uchar *p, int l, int64_t Pts)
|
||||
{
|
||||
// Find the first packet with a PTS and use it:
|
||||
while (l > 0) {
|
||||
const uchar *d = p;
|
||||
if (TsPayloadStart(d) && TsGetPayload(&d) && PesHasPts(d)) {
|
||||
PesSetPts(const_cast<uchar *>(d), Pts);
|
||||
return;
|
||||
}
|
||||
p += TS_SIZE;
|
||||
l -= TS_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
void TsSetDts(uchar *p, int l, int64_t Dts)
|
||||
{
|
||||
// Find the first packet with a DTS and use it:
|
||||
while (l > 0) {
|
||||
const uchar *d = p;
|
||||
if (TsPayloadStart(d) && TsGetPayload(&d) && PesHasDts(d)) {
|
||||
PesSetDts(const_cast<uchar *>(d), Dts);
|
||||
return;
|
||||
}
|
||||
p += TS_SIZE;
|
||||
l -= TS_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Some PES handling tools -----------------------------------------------
|
||||
|
||||
void PesSetPts(uchar *p, int64_t Pts)
|
||||
{
|
||||
p[ 9] = ((Pts >> 29) & 0x0E) | (p[9] & 0xF1);
|
||||
p[10] = Pts >> 22;
|
||||
p[11] = ((Pts >> 14) & 0xFE) | 0x01;
|
||||
p[12] = Pts >> 7;
|
||||
p[13] = ((Pts << 1) & 0xFE) | 0x01;
|
||||
}
|
||||
|
||||
void PesSetDts(uchar *p, int64_t Dts)
|
||||
{
|
||||
p[14] = ((Dts >> 29) & 0x0E) | (p[14] & 0xF1);
|
||||
p[15] = Dts >> 22;
|
||||
p[16] = ((Dts >> 14) & 0xFE) | 0x01;
|
||||
p[17] = Dts >> 7;
|
||||
p[18] = ((Dts << 1) & 0xFE) | 0x01;
|
||||
}
|
||||
|
||||
int64_t PtsDiff(int64_t Pts1, int64_t Pts2)
|
||||
{
|
||||
int64_t d = Pts2 - Pts1;
|
||||
if (d > MAX33BIT / 2)
|
||||
return d - (MAX33BIT + 1);
|
||||
if (d < -MAX33BIT / 2)
|
||||
return d + (MAX33BIT + 1);
|
||||
return d;
|
||||
}
|
||||
|
||||
// --- cTsPayload ------------------------------------------------------------
|
||||
@ -1395,7 +1471,7 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
|
||||
}
|
||||
}
|
||||
else // audio
|
||||
framesPerSecond = 90000.0 / Delta; // PTS of audio frames is always increasing
|
||||
framesPerSecond = double(PTSTICKS) / Delta; // PTS of audio frames is always increasing
|
||||
dbgframes("\nDelta = %d FPS = %5.2f FPPU = %d NF = %d\n", Delta, framesPerSecond, framesPerPayloadUnit, numPtsValues + 1);
|
||||
synced = true;
|
||||
parser->SetDebug(false);
|
||||
|
66
remux.h
66
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.34 2012/11/06 11:03:06 kls Exp $
|
||||
* $Id: remux.h 2.35 2012/11/18 12:17:23 kls Exp $
|
||||
*/
|
||||
|
||||
#ifndef __REMUX_H
|
||||
@ -52,6 +52,11 @@ public:
|
||||
#define PATPID 0x0000 // PAT PID (constant 0)
|
||||
#define MAXPID 0x2000 // for arrays that use a PID as the index
|
||||
|
||||
#define PTSTICKS 90000 // number of PTS ticks per second
|
||||
#define PCRFACTOR 300 // conversion from 27MHz PCR extension to 90kHz PCR base
|
||||
#define MAX33BIT 0x00000001FFFFFFFFLL // max. possible value with 33 bit
|
||||
#define MAX27MHZ ((MAX33BIT + 1) * PCRFACTOR - 1) // max. possible PCR value
|
||||
|
||||
inline bool TsHasPayload(const uchar *p)
|
||||
{
|
||||
return p[3] & TS_PAYLOAD_EXISTS;
|
||||
@ -82,6 +87,16 @@ inline bool TsIsScrambled(const uchar *p)
|
||||
return p[3] & TS_SCRAMBLING_CONTROL;
|
||||
}
|
||||
|
||||
inline uchar TsGetContinuityCounter(const uchar *p)
|
||||
{
|
||||
return p[3] & TS_CONT_CNT_MASK;
|
||||
}
|
||||
|
||||
inline void TsSetContinuityCounter(uchar *p, uchar Counter)
|
||||
{
|
||||
p[3] = (p[3] & ~TS_CONT_CNT_MASK) | (Counter & TS_CONT_CNT_MASK);
|
||||
}
|
||||
|
||||
inline int TsPayloadOffset(const uchar *p)
|
||||
{
|
||||
int o = TsHasAdaptationField(p) ? p[4] + 5 : 4;
|
||||
@ -103,15 +118,31 @@ inline int TsContinuityCounter(const uchar *p)
|
||||
return p[3] & TS_CONT_CNT_MASK;
|
||||
}
|
||||
|
||||
inline int TsGetAdaptationField(const uchar *p)
|
||||
inline int64_t TsGetPcr(const uchar *p)
|
||||
{
|
||||
return TsHasAdaptationField(p) ? p[5] : 0x00;
|
||||
if (TsHasAdaptationField(p)) {
|
||||
if (p[4] >= 7 && (p[5] & TS_ADAPT_PCR)) {
|
||||
return ((((int64_t)p[ 6]) << 25) |
|
||||
(((int64_t)p[ 7]) << 17) |
|
||||
(((int64_t)p[ 8]) << 9) |
|
||||
(((int64_t)p[ 9]) << 1) |
|
||||
(((int64_t)p[10]) >> 7)) * PCRFACTOR +
|
||||
(((((int)p[10]) & 0x01) << 8) |
|
||||
( ((int)p[11])));
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TsHidePayload(uchar *p);
|
||||
void TsSetPcr(uchar *p, int64_t Pcr);
|
||||
|
||||
// The following functions all take a pointer to a sequence of complete TS packets.
|
||||
|
||||
int64_t TsGetPts(const uchar *p, int l);
|
||||
void TsSetTeiOnBrokenPackets(uchar *p, int l);
|
||||
int64_t TsGetDts(const uchar *p, int l);
|
||||
void TsSetPts(uchar *p, int l, int64_t Pts);
|
||||
void TsSetDts(uchar *p, int l, int64_t Dts);
|
||||
|
||||
// Some PES handling tools:
|
||||
// The following functions that take a pointer to PES data all assume that
|
||||
@ -142,6 +173,11 @@ inline bool PesHasPts(const uchar *p)
|
||||
return (p[7] & 0x80) && p[8] >= 5;
|
||||
}
|
||||
|
||||
inline bool PesHasDts(const uchar *p)
|
||||
{
|
||||
return (p[7] & 0x40) && p[8] >= 10;
|
||||
}
|
||||
|
||||
inline int64_t PesGetPts(const uchar *p)
|
||||
{
|
||||
return ((((int64_t)p[ 9]) & 0x0E) << 29) |
|
||||
@ -151,6 +187,28 @@ inline int64_t PesGetPts(const uchar *p)
|
||||
((((int64_t)p[13]) & 0xFE) >> 1);
|
||||
}
|
||||
|
||||
inline int64_t PesGetDts(const uchar *p)
|
||||
{
|
||||
return ((((int64_t)p[14]) & 0x0E) << 29) |
|
||||
(( (int64_t)p[15]) << 22) |
|
||||
((((int64_t)p[16]) & 0xFE) << 14) |
|
||||
(( (int64_t)p[17]) << 7) |
|
||||
((((int64_t)p[18]) & 0xFE) >> 1);
|
||||
}
|
||||
|
||||
void PesSetPts(uchar *p, int64_t Pts);
|
||||
void PesSetDts(uchar *p, int64_t Dts);
|
||||
|
||||
// PTS handling:
|
||||
|
||||
inline int64_t PtsAdd(int64_t Pts1, int64_t Pts2) { return (Pts1 + Pts2) & MAX33BIT; }
|
||||
///< Adds the given PTS values, taking into account the 33bit wrap around.
|
||||
int64_t PtsDiff(int64_t Pts1, int64_t Pts2);
|
||||
///< Returns the difference between two PTS values. The result of Pts2 - Pts1
|
||||
///< is the actual number of 90kHz time ticks that pass from Pts1 to Pts2,
|
||||
///< properly taking into account the 33bit wrap around. If Pts2 is "before"
|
||||
///< Pts1, the result is negative.
|
||||
|
||||
// A transprent TS payload handler:
|
||||
|
||||
class cTsPayload {
|
||||
|
Loading…
Reference in New Issue
Block a user