vdr/dvbplayer.c
Klaus Schmidinger 716c03a47e Version 2.1.3
VDR developer version 2.1.3 is now available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.3.tar.bz2

A 'diff' against the previous version is available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.2-2.1.3.diff

MD5 checksums:

054f80e0045aa6fad118e9285b52f4f2  vdr-2.1.3.tar.bz2
3c5ab05d5c4d0b984b34e84190e80949  vdr-2.1.2-2.1.3.diff

WARNING:
========

This is a *developer* version. Even though *I* use it in my productive
environment, I strongly recommend that you only use it under controlled
conditions and for testing and debugging.

Originally I intended to release this version only after the new DiSEqC
configuration dialog was finished. But in the meantime quite a few other things
have come up, so I decided to postpone that dialog and first release what has
piled up so far.

From the HISTORY file:
- Changed the return value of cPositioner::HorizonLongitude() to 0 in case the
  latitude of the antenna location is beyond +/-81 degrees.
- Updated the Finnish OSD texts (thanks to Rolf Ahrenberg).
- Fixed some compiler warnings with gcc-4.6.3 (thanks to Rolf Ahrenberg).
- Changed the name of the SVDRP command RENR to MOVR (suggested by Rolf Ahrenberg).
- When cutting a recording it is now checked whether there is already an edited
  version of this recording (with the same name, but starting with '%'), and the
  user is prompted for confirmation to overwrite it (suggested by Rolf Ahrenberg).
- Revoked "Added maximum signal strength value for TechniSat SkyStar 2 DVB-S rev 2.3P"
  because it broke things for the "TechniSat AirStar 2" DVB-T card.
- The LIRC remote control now connects to the socket even if it doesn't yet exist when
  VDR is started (thanks to Lars Hanisch).
- Changed the absolute latitude limit for visible satellites to 81.2 degrees.
- Added code for parsing LCN and AVC descriptors to libsi (thanks to Rolf Ahrenberg).
- In the "Select folder" menu pressing Ok now selects the folder, even if this is a
  folder that contains sub folders (marked with "..."). To open such a folder you
  can press the Red key.
- Fixed a possible access to uninitialized data in cEIT::cEIT() (reported by Dominik
  Strasser).
- The new menu category mcRecordingEdit is now used to mark menus that edit recording
  properties (suggested by Stefan Braun).
- Changes in the teletext PID no longer cause retuning (and thus interrupting a
  recording).
- Removed '_' from the FileNameChars and CharMap translations in uk_UA.po.
- Updated the Italian OSD texts (thanks to Diego Pierotto).
- Fixed a missing initialization in the c'tor of cSkinLCARSDisplayChannel (thanks to
  Marko Mäkelä).
- Simplified some conditional expressions in skinlcars.c and skinsttng.c (suggested
  by Marko Mäkelä).
- Fixed uninitialized item area coordinates in cSkinLCARSDisplayMenu (reported by
  Marko Mäkelä).
- Fixed a possible crash if the recordings list is updated externally while the
  Recordings menu is open (reported by Lars Hanisch).
- Added a missing closing ')' in the help and man page entry of the --vfat option
  (reported by Lars Hanisch).
- Fixed setting the name of the video directory to avoid a crash when using --genindex,
  and also to use the correct directory with --edit (the latter reported by Marko
  Mäkelä).
- The Recordings menu can now be called with a cRecordingFilter, which allows the
  caller to have it display only a certain subset of the recordings (thanks to Lars
  Hanisch).
- Added handling UTF-8 'umlaut' characters to cKbdRemote (thanks to Lars Hanisch).
- Made it clear that the Data parameter in cDevice::StillPicture() may point to a
  series of packets, not just a single one (thanks to Thomas Reufer).
- cDevice::TrickSpeed() now has an additional parameter named Forward, which indicates
  the direction in which replay is being done (suggested by Thomas Reufer). This
  information may be necessary for some output devices in order to properly implement
  trick modes. Authors of plugins that implement output devices will need to add this
  parameter to their derived cDevice class, regardless of whether they will make use
  of it or not.
- Added a note to ePlayMode in device.h that VDR itself always uses pmAudioVideo when
  replaying a recording (suggested by Thomas Reufer).
- Fixed some spellings in positioner.h and Doxyfile (thanks to Ville Skyttä).
- Changed '%a' to the POSIX compliant '%m' in all scanf() calls (thanks to Ville
  Skyttä).
- The new function cCamSlot::Decrypt() can be used by derived classes to implement a
  CAM slot that can be freely assigned to any device, without being directly inserted
  into the full TS data stream in hardware. A derived class that implements Decrypt()
  will also need to set the new parameter ReceiveCaPids in the call to the cCamSlot
  base class constructor to true, in order to receive the CA pid TS packets that
  contain data necessary for decrypting.
- Many member functions of cCamSlot have been made virtual to allow for easier
  implementation of derived classes.
- cTSBuffer now provides the number of available bytes in its Get() function.
- cDvbDevice::GetTSPacket() now calls CamSlot()->Decrypt() in order to allow CAM slots
  that can be freely assigned to any device access to the TS data stream.
- Added a check to avoid a possible NULL pointer dereference in cCiSession::SendData()
  (reported by Ville Skyttä).
- Deleted a superfluous assignment in cPipe::Open() (reported by Ville Skyttä).
- The script given to VDR with the '-r' option is now also called after the recording
  process has actually started (thanks to Christian Kaiser).
- Avoiding unnecessary pkg-config warnings in plugin Makefiles (thanks to Ville Skyttä).
  Plugin authors may want to apply the following change to their Makefile:
  -PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc))
  +PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell PKG_CONFIG_PATH="$$PKG_CONFIG_PATH:../../.." pkg-config --variable=$(1) vdr))
- Eliminated MAXDVBDEVICES (suggested by Oliver Endriss).
- Channels that are no longer contained in the current SDT of a transponder are now
  marked with the keyword OBSOLETE in their name and provider fields. That way you can
  identify obsolete channels when you switch to them, and you can get the complete
  overview of all obsolete channels by sorting the Channels list by provider (by
  pressing the 0 key twice). Automatic deletion of obsolete channels may follow later.
2014-01-06 18:37:32 +01:00

939 lines
26 KiB
C

/*
* dvbplayer.c: The DVB player
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: dvbplayer.c 3.1 2013/12/25 13:24:07 kls Exp $
*/
#include "dvbplayer.h"
#include <math.h>
#include <stdlib.h>
#include "recording.h"
#include "remux.h"
#include "ringbuffer.h"
#include "thread.h"
#include "tools.h"
// --- cPtsIndex -------------------------------------------------------------
#define PTSINDEX_ENTRIES 500
class cPtsIndex {
private:
struct tPtsIndex {
uint32_t pts; // no need for 33 bit - some devices don't even supply the msb
int index;
};
tPtsIndex pi[PTSINDEX_ENTRIES];
int w, r;
int lastFound;
cMutex mutex;
public:
cPtsIndex(void);
void Clear(void);
bool IsEmpty(void);
void Put(uint32_t Pts, int Index);
int FindIndex(uint32_t Pts);
};
cPtsIndex::cPtsIndex(void)
{
lastFound = 0;
Clear();
}
void cPtsIndex::Clear(void)
{
cMutexLock MutexLock(&mutex);
w = r = 0;
}
bool cPtsIndex::IsEmpty(void)
{
cMutexLock MutexLock(&mutex);
return w == r;
}
void cPtsIndex::Put(uint32_t Pts, int Index)
{
cMutexLock MutexLock(&mutex);
pi[w].pts = Pts;
pi[w].index = Index;
w = (w + 1) % PTSINDEX_ENTRIES;
if (w == r)
r = (r + 1) % PTSINDEX_ENTRIES;
}
int cPtsIndex::FindIndex(uint32_t Pts)
{
cMutexLock MutexLock(&mutex);
if (w == r)
return lastFound; // list is empty, let's not jump way off the last known position
uint32_t Delta = 0xFFFFFFFF;
int Index = -1;
for (int i = w; i != r; ) {
if (--i < 0)
i = PTSINDEX_ENTRIES - 1;
uint32_t d = pi[i].pts < Pts ? Pts - pi[i].pts : pi[i].pts - Pts;
if (d > 0x7FFFFFFF)
d = 0xFFFFFFFF - d; // handle rollover
if (d < Delta) {
Delta = d;
Index = pi[i].index;
}
}
lastFound = Index;
return Index;
}
// --- cNonBlockingFileReader ------------------------------------------------
class cNonBlockingFileReader : public cThread {
private:
cUnbufferedFile *f;
uchar *buffer;
int wanted;
int length;
cCondWait newSet;
cCondVar newDataCond;
cMutex newDataMutex;
protected:
void Action(void);
public:
cNonBlockingFileReader(void);
~cNonBlockingFileReader();
void Clear(void);
void Request(cUnbufferedFile *File, int Length);
int Result(uchar **Buffer);
bool Reading(void) { return buffer; }
bool WaitForDataMs(int msToWait);
};
cNonBlockingFileReader::cNonBlockingFileReader(void)
:cThread("non blocking file reader")
{
f = NULL;
buffer = NULL;
wanted = length = 0;
Start();
}
cNonBlockingFileReader::~cNonBlockingFileReader()
{
newSet.Signal();
Cancel(3);
free(buffer);
}
void cNonBlockingFileReader::Clear(void)
{
Lock();
f = NULL;
free(buffer);
buffer = NULL;
wanted = length = 0;
Unlock();
}
void cNonBlockingFileReader::Request(cUnbufferedFile *File, int Length)
{
Lock();
Clear();
wanted = Length;
buffer = MALLOC(uchar, wanted);
f = File;
Unlock();
newSet.Signal();
}
int cNonBlockingFileReader::Result(uchar **Buffer)
{
LOCK_THREAD;
if (buffer && length == wanted) {
*Buffer = buffer;
buffer = NULL;
return wanted;
}
errno = EAGAIN;
return -1;
}
void cNonBlockingFileReader::Action(void)
{
while (Running()) {
Lock();
if (f && buffer && length < wanted) {
int r = f->Read(buffer + length, wanted - length);
if (r > 0)
length += r;
else if (r == 0) { // r == 0 means EOF
if (length > 0)
wanted = length; // already read something, so return the rest
else
length = wanted = 0; // report EOF
}
else if (FATALERRNO) {
LOG_ERROR;
length = wanted = r; // this will forward the error status to the caller
}
if (length == wanted) {
cMutexLock NewDataLock(&newDataMutex);
newDataCond.Broadcast();
}
}
Unlock();
newSet.Wait(1000);
}
}
bool cNonBlockingFileReader::WaitForDataMs(int msToWait)
{
cMutexLock NewDataLock(&newDataMutex);
if (buffer && length == wanted)
return true;
return newDataCond.TimedWait(newDataMutex, msToWait);
}
// --- cDvbPlayer ------------------------------------------------------------
#define PLAYERBUFSIZE MEGABYTE(1)
#define RESUMEBACKUP 10 // number of seconds to back up when resuming an interrupted replay session
#define MAXSTUCKATEOF 3 // max. number of seconds to wait in case the device doesn't play the last frame
class cDvbPlayer : public cPlayer, cThread {
private:
enum ePlayModes { pmPlay, pmPause, pmSlow, pmFast, pmStill };
enum ePlayDirs { pdForward, pdBackward };
static int Speeds[];
cNonBlockingFileReader *nonBlockingFileReader;
cRingBufferFrame *ringBuffer;
cPtsIndex ptsIndex;
cFileName *fileName;
cIndexFile *index;
cUnbufferedFile *replayFile;
double framesPerSecond;
bool isPesRecording;
bool pauseLive;
bool eof;
bool firstPacket;
ePlayModes playMode;
ePlayDirs playDir;
int trickSpeed;
int readIndex;
bool readIndependent;
cFrame *readFrame;
cFrame *playFrame;
cFrame *dropFrame;
bool resyncAfterPause;
void TrickSpeed(int Increment);
void Empty(void);
bool NextFile(uint16_t FileNumber = 0, off_t FileOffset = -1);
int Resume(void);
bool Save(void);
protected:
virtual void Activate(bool On);
virtual void Action(void);
public:
cDvbPlayer(const char *FileName, bool PauseLive);
virtual ~cDvbPlayer();
bool Active(void) { return cThread::Running(); }
void Pause(void);
void Play(void);
void Forward(void);
void Backward(void);
int SkipFrames(int Frames);
void SkipSeconds(int Seconds);
void Goto(int Position, bool Still = false);
virtual double FramesPerSecond(void) { return framesPerSecond; }
virtual void SetAudioTrack(eTrackType Type, const tTrackId *TrackId);
virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false);
virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed);
};
#define MAX_VIDEO_SLOWMOTION 63 // max. arg to pass to VIDEO_SLOWMOTION // TODO is this value correct?
#define NORMAL_SPEED 4 // the index of the '1' entry in the following array
#define MAX_SPEEDS 3 // the offset of the maximum speed from normal speed in either direction
#define SPEED_MULT 12 // the speed multiplier
int cDvbPlayer::Speeds[] = { 0, -2, -4, -8, 1, 2, 4, 12, 0 };
cDvbPlayer::cDvbPlayer(const char *FileName, bool PauseLive)
:cThread("dvbplayer")
{
nonBlockingFileReader = NULL;
ringBuffer = NULL;
index = NULL;
cRecording Recording(FileName);
framesPerSecond = Recording.FramesPerSecond();
isPesRecording = Recording.IsPesRecording();
pauseLive = PauseLive;
eof = false;
firstPacket = true;
playMode = pmPlay;
playDir = pdForward;
trickSpeed = NORMAL_SPEED;
readIndex = -1;
readIndependent = false;
readFrame = NULL;
playFrame = NULL;
dropFrame = NULL;
resyncAfterPause = false;
isyslog("replay %s", FileName);
fileName = new cFileName(FileName, false, false, isPesRecording);
replayFile = fileName->Open();
if (!replayFile)
return;
ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE);
// Create the index file:
index = new cIndexFile(FileName, false, isPesRecording, pauseLive);
if (!index)
esyslog("ERROR: can't allocate index");
else if (!index->Ok()) {
delete index;
index = NULL;
}
else if (PauseLive)
framesPerSecond = cRecording(FileName).FramesPerSecond(); // the fps rate might have changed from the default
}
cDvbPlayer::~cDvbPlayer()
{
Save();
Detach();
delete readFrame; // might not have been stored in the buffer in Action()
delete index;
delete fileName;
delete ringBuffer;
}
void cDvbPlayer::TrickSpeed(int Increment)
{
int nts = trickSpeed + Increment;
if (Speeds[nts] == 1) {
trickSpeed = nts;
if (playMode == pmFast)
Play();
else
Pause();
}
else if (Speeds[nts]) {
trickSpeed = nts;
int Mult = (playMode == pmSlow && playDir == pdForward) ? 1 : SPEED_MULT;
int sp = (Speeds[nts] > 0) ? Mult / Speeds[nts] : -Speeds[nts] * Mult;
if (sp > MAX_VIDEO_SLOWMOTION)
sp = MAX_VIDEO_SLOWMOTION;
DeviceTrickSpeed(sp, playDir == pdForward);
}
}
void cDvbPlayer::Empty(void)
{
LOCK_THREAD;
if (nonBlockingFileReader)
nonBlockingFileReader->Clear();
if (!firstPacket) // don't set the readIndex twice if Empty() is called more than once
readIndex = ptsIndex.FindIndex(DeviceGetSTC()) - 1; // Action() will first increment it!
delete readFrame; // might not have been stored in the buffer in Action()
readFrame = NULL;
playFrame = NULL;
dropFrame = NULL;
ringBuffer->Clear();
ptsIndex.Clear();
DeviceClear();
firstPacket = true;
}
bool cDvbPlayer::NextFile(uint16_t FileNumber, off_t FileOffset)
{
if (FileNumber > 0)
replayFile = fileName->SetOffset(FileNumber, FileOffset);
else if (replayFile && eof)
replayFile = fileName->NextFile();
eof = false;
return replayFile != NULL;
}
int cDvbPlayer::Resume(void)
{
if (index) {
int Index = index->GetResume();
if (Index >= 0) {
uint16_t FileNumber;
off_t FileOffset;
if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset))
return Index;
}
}
return -1;
}
bool cDvbPlayer::Save(void)
{
if (index) {
int Index = ptsIndex.FindIndex(DeviceGetSTC());
if (Index >= 0) {
Index -= int(round(RESUMEBACKUP * framesPerSecond));
if (Index > 0)
Index = index->GetNextIFrame(Index, false);
else
Index = 0;
if (Index >= 0)
return index->StoreResume(Index);
}
}
return false;
}
void cDvbPlayer::Activate(bool On)
{
if (On) {
if (replayFile)
Start();
}
else
Cancel(9);
}
void cDvbPlayer::Action(void)
{
uchar *p = NULL;
int pc = 0;
readIndex = Resume();
if (readIndex >= 0)
isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
nonBlockingFileReader = new cNonBlockingFileReader;
int Length = 0;
bool Sleep = false;
bool WaitingForData = false;
time_t StuckAtEof = 0;
uint32_t LastStc = 0;
int LastReadIFrame = -1;
int SwitchToPlayFrame = 0;
if (pauseLive)
Goto(0, true);
while (Running()) {
if (WaitingForData)
WaitingForData = !nonBlockingFileReader->WaitForDataMs(3); // this keeps the CPU load low, but reacts immediately on new data
else if (Sleep) {
cPoller Poller;
DevicePoll(Poller, 10);
Sleep = false;
if (playMode == pmStill || playMode == pmPause)
cCondWait::SleepMs(3);
}
{
LOCK_THREAD;
// Read the next frame from the file:
if (playMode != pmStill && playMode != pmPause) {
if (!readFrame && (replayFile || readIndex >= 0)) {
if (!nonBlockingFileReader->Reading()) {
if (!SwitchToPlayFrame && (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward))) {
uint16_t FileNumber;
off_t FileOffset;
bool TimeShiftMode = index->IsStillRecording();
int Index = -1;
readIndependent = false;
if (DeviceHasIBPTrickSpeed() && playDir == pdForward) {
if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length))
Index = readIndex + 1;
}
else {
int d = int(round(0.4 * framesPerSecond));
if (playDir != pdForward)
d = -d;
int NewIndex = readIndex + d;
if (NewIndex <= 0 && readIndex > 0)
NewIndex = 1; // make sure the very first frame is delivered
NewIndex = index->GetNextIFrame(NewIndex, playDir == pdForward, &FileNumber, &FileOffset, &Length);
if (NewIndex < 0 && TimeShiftMode && playDir == pdForward)
SwitchToPlayFrame = readIndex;
Index = NewIndex;
readIndependent = true;
}
if (Index >= 0) {
readIndex = Index;
if (!NextFile(FileNumber, FileOffset))
continue;
}
else if (!(TimeShiftMode && playDir == pdForward))
eof = true;
}
else if (index) {
uint16_t FileNumber;
off_t FileOffset;
if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset))
readIndex++;
else
eof = true;
}
else // allows replay even if the index file is missing
Length = MAXFRAMESIZE;
if (Length == -1)
Length = MAXFRAMESIZE; // this means we read up to EOF (see cIndex)
else if (Length > MAXFRAMESIZE) {
esyslog("ERROR: frame larger than buffer (%d > %d)", Length, MAXFRAMESIZE);
Length = MAXFRAMESIZE;
}
if (!eof)
nonBlockingFileReader->Request(replayFile, Length);
}
if (!eof) {
uchar *b = NULL;
int r = nonBlockingFileReader->Result(&b);
if (r > 0) {
WaitingForData = false;
uint32_t Pts = 0;
if (readIndependent) {
Pts = isPesRecording ? PesGetPts(b) : TsGetPts(b, r);
LastReadIFrame = readIndex;
}
readFrame = new cFrame(b, -r, ftUnknown, readIndex, Pts); // hands over b to the ringBuffer
}
else if (r < 0) {
if (errno == EAGAIN)
WaitingForData = true;
else if (FATALERRNO) {
LOG_ERROR;
break;
}
}
else
eof = true;
}
}
// Store the frame in the buffer:
if (readFrame) {
if (ringBuffer->Put(readFrame))
readFrame = NULL;
else
Sleep = true;
}
}
else
Sleep = true;
if (dropFrame) {
if (!eof || (playDir != pdForward && dropFrame->Index() > 0) || (playDir == pdForward && dropFrame->Index() < readIndex)) {
ringBuffer->Drop(dropFrame); // the very first and last frame are continuously repeated to flush data through the device
dropFrame = NULL;
}
}
// Get the next frame from the buffer:
if (!playFrame) {
playFrame = ringBuffer->Get();
p = NULL;
pc = 0;
}
// Play the frame:
if (playFrame) {
if (!p) {
p = playFrame->Data();
pc = playFrame->Count();
if (p) {
if (playFrame->Index() >= 0 && playFrame->Pts() != 0)
ptsIndex.Put(playFrame->Pts(), playFrame->Index());
if (firstPacket) {
if (isPesRecording) {
PlayPes(NULL, 0);
cRemux::SetBrokenLink(p, pc);
}
else
PlayTs(NULL, 0);
firstPacket = false;
}
}
}
if (p) {
int w;
bool VideoOnly = (dropFrame || playMode != pmPlay && !(playMode == pmSlow && playDir == pdForward)) && DeviceIsPlayingVideo();
if (isPesRecording)
w = PlayPes(p, pc, VideoOnly);
else
w = PlayTs(p, pc, VideoOnly);
if (w > 0) {
p += w;
pc -= w;
}
else if (w < 0 && FATALERRNO)
LOG_ERROR;
else
Sleep = true;
}
if (pc <= 0) {
dropFrame = playFrame;
playFrame = NULL;
p = NULL;
}
}
else
Sleep = true;
// Handle hitting begin/end of recording:
if (eof || SwitchToPlayFrame) {
bool SwitchToPlay = false;
uint32_t Stc = DeviceGetSTC();
if (Stc != LastStc || playMode == pmPause)
StuckAtEof = 0;
else if (!StuckAtEof)
StuckAtEof = time(NULL);
else if (time(NULL) - StuckAtEof > MAXSTUCKATEOF) {
if (playDir == pdForward)
break; // automatically stop at end of recording
SwitchToPlay = true;
}
LastStc = Stc;
int Index = ptsIndex.FindIndex(Stc);
if (playDir == pdForward && !SwitchToPlayFrame) {
if (Index >= LastReadIFrame)
break; // automatically stop at end of recording
}
else if (Index <= 0 || SwitchToPlayFrame && Index >= SwitchToPlayFrame)
SwitchToPlay = true;
if (SwitchToPlay) {
if (!SwitchToPlayFrame)
Empty();
DevicePlay();
playMode = pmPlay;
playDir = pdForward;
SwitchToPlayFrame = 0;
}
}
}
}
cNonBlockingFileReader *nbfr = nonBlockingFileReader;
nonBlockingFileReader = NULL;
delete nbfr;
}
void cDvbPlayer::Pause(void)
{
if (playMode == pmPause || playMode == pmStill)
Play();
else {
LOCK_THREAD;
if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) {
if (!(DeviceHasIBPTrickSpeed() && playDir == pdForward))
Empty();
}
DeviceFreeze();
playMode = pmPause;
}
}
void cDvbPlayer::Play(void)
{
if (playMode != pmPlay) {
LOCK_THREAD;
if (playMode == pmStill || playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) {
if (!(DeviceHasIBPTrickSpeed() && playDir == pdForward))
Empty();
}
DevicePlay();
playMode = pmPlay;
playDir = pdForward;
if (resyncAfterPause) {
int Current, Total;
if (GetIndex(Current, Total, true))
Goto(Current);
resyncAfterPause = false;
}
}
}
void cDvbPlayer::Forward(void)
{
if (index) {
switch (playMode) {
case pmFast:
if (Setup.MultiSpeedMode) {
TrickSpeed(playDir == pdForward ? 1 : -1);
break;
}
else if (playDir == pdForward) {
Play();
break;
}
// run into pmPlay
case pmPlay: {
LOCK_THREAD;
if (!(DeviceHasIBPTrickSpeed() && playDir == pdForward))
Empty();
if (DeviceIsPlayingVideo())
DeviceMute();
playMode = pmFast;
playDir = pdForward;
trickSpeed = NORMAL_SPEED;
TrickSpeed(Setup.MultiSpeedMode ? 1 : MAX_SPEEDS);
}
break;
case pmSlow:
if (Setup.MultiSpeedMode) {
TrickSpeed(playDir == pdForward ? -1 : 1);
break;
}
else if (playDir == pdForward) {
Pause();
break;
}
Empty();
// run into pmPause
case pmStill:
case pmPause:
DeviceMute();
playMode = pmSlow;
playDir = pdForward;
trickSpeed = NORMAL_SPEED;
TrickSpeed(Setup.MultiSpeedMode ? -1 : -MAX_SPEEDS);
break;
default: esyslog("ERROR: unknown playMode %d (%s)", playMode, __FUNCTION__);
}
}
}
void cDvbPlayer::Backward(void)
{
if (index) {
switch (playMode) {
case pmFast:
if (Setup.MultiSpeedMode) {
TrickSpeed(playDir == pdBackward ? 1 : -1);
break;
}
else if (playDir == pdBackward) {
Play();
break;
}
// run into pmPlay
case pmPlay: {
LOCK_THREAD;
Empty();
if (DeviceIsPlayingVideo())
DeviceMute();
playMode = pmFast;
playDir = pdBackward;
trickSpeed = NORMAL_SPEED;
TrickSpeed(Setup.MultiSpeedMode ? 1 : MAX_SPEEDS);
}
break;
case pmSlow:
if (Setup.MultiSpeedMode) {
TrickSpeed(playDir == pdBackward ? -1 : 1);
break;
}
else if (playDir == pdBackward) {
Pause();
break;
}
Empty();
// run into pmPause
case pmStill:
case pmPause: {
LOCK_THREAD;
Empty();
DeviceMute();
playMode = pmSlow;
playDir = pdBackward;
trickSpeed = NORMAL_SPEED;
TrickSpeed(Setup.MultiSpeedMode ? -1 : -MAX_SPEEDS);
}
break;
default: esyslog("ERROR: unknown playMode %d (%s)", playMode, __FUNCTION__);
}
}
}
int cDvbPlayer::SkipFrames(int Frames)
{
if (index && Frames) {
int Current, Total;
GetIndex(Current, Total, true);
int OldCurrent = Current;
// As GetNextIFrame() increments/decrements at least once, the
// destination frame (= Current + Frames) must be adjusted by
// -1/+1 respectively.
Current = index->GetNextIFrame(Current + Frames + (Frames > 0 ? -1 : 1), Frames > 0);
return Current >= 0 ? Current : OldCurrent;
}
return -1;
}
void cDvbPlayer::SkipSeconds(int Seconds)
{
if (index && Seconds) {
LOCK_THREAD;
int Index = ptsIndex.FindIndex(DeviceGetSTC());
Empty();
if (Index >= 0) {
Index = max(Index + SecondsToFrames(Seconds, framesPerSecond), 0);
if (Index > 0)
Index = index->GetNextIFrame(Index, false, NULL, NULL, NULL);
if (Index >= 0)
readIndex = Index - 1; // Action() will first increment it!
}
Play();
}
}
void cDvbPlayer::Goto(int Index, bool Still)
{
if (index) {
LOCK_THREAD;
Empty();
if (++Index <= 0)
Index = 1; // not '0', to allow GetNextIFrame() below to work!
uint16_t FileNumber;
off_t FileOffset;
int Length;
Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset, &Length);
if (Index >= 0 && NextFile(FileNumber, FileOffset) && Still) {
uchar b[MAXFRAMESIZE];
int r = ReadFrame(replayFile, b, Length, sizeof(b));
if (r > 0) {
if (playMode == pmPause)
DevicePlay();
DeviceStillPicture(b, r);
ptsIndex.Put(isPesRecording ? PesGetPts(b) : TsGetPts(b, r), Index);
}
playMode = pmStill;
}
readIndex = Index;
}
}
void cDvbPlayer::SetAudioTrack(eTrackType Type, const tTrackId *TrackId)
{
if (!cThread::IsMainThread())
return; // only do this upon user interaction
if (playMode == pmPlay) {
if (!ptsIndex.IsEmpty()) {
int Current, Total;
if (GetIndex(Current, Total, true))
Goto(Current);
}
}
else if (playMode == pmPause)
resyncAfterPause = true;
}
bool cDvbPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame)
{
if (index) {
Current = ptsIndex.FindIndex(DeviceGetSTC());
if (SnapToIFrame) {
int i1 = index->GetNextIFrame(Current + 1, false);
int i2 = index->GetNextIFrame(Current, true);
Current = (abs(Current - i1) <= abs(Current - i2)) ? i1 : i2;
}
Total = index->Last();
return true;
}
Current = Total = -1;
return false;
}
bool cDvbPlayer::GetReplayMode(bool &Play, bool &Forward, int &Speed)
{
Play = (playMode == pmPlay || playMode == pmFast);
Forward = (playDir == pdForward);
if (playMode == pmFast || playMode == pmSlow)
Speed = Setup.MultiSpeedMode ? abs(trickSpeed - NORMAL_SPEED) : 0;
else
Speed = -1;
return true;
}
// --- cDvbPlayerControl -----------------------------------------------------
cDvbPlayerControl::cDvbPlayerControl(const char *FileName, bool PauseLive)
:cControl(player = new cDvbPlayer(FileName, PauseLive))
{
}
cDvbPlayerControl::~cDvbPlayerControl()
{
Stop();
}
bool cDvbPlayerControl::Active(void)
{
return player && player->Active();
}
void cDvbPlayerControl::Stop(void)
{
delete player;
player = NULL;
}
void cDvbPlayerControl::Pause(void)
{
if (player)
player->Pause();
}
void cDvbPlayerControl::Play(void)
{
if (player)
player->Play();
}
void cDvbPlayerControl::Forward(void)
{
if (player)
player->Forward();
}
void cDvbPlayerControl::Backward(void)
{
if (player)
player->Backward();
}
void cDvbPlayerControl::SkipSeconds(int Seconds)
{
if (player)
player->SkipSeconds(Seconds);
}
int cDvbPlayerControl::SkipFrames(int Frames)
{
if (player)
return player->SkipFrames(Frames);
return -1;
}
bool cDvbPlayerControl::GetIndex(int &Current, int &Total, bool SnapToIFrame)
{
if (player) {
player->GetIndex(Current, Total, SnapToIFrame);
return true;
}
return false;
}
bool cDvbPlayerControl::GetReplayMode(bool &Play, bool &Forward, int &Speed)
{
return player && player->GetReplayMode(Play, Forward, Speed);
}
void cDvbPlayerControl::Goto(int Position, bool Still)
{
if (player)
player->Goto(Position, Still);
}