vdr 2.8.2
dvbplayer.c
Go to the documentation of this file.
1/*
2 * dvbplayer.c: The DVB player
3 *
4 * See the main source file 'vdr.c' for copyright information and
5 * how to reach the author.
6 *
7 * $Id: dvbplayer.c 5.16 2026/05/30 12:27:12 kls Exp $
8 */
9
10#include "dvbplayer.h"
11#include <math.h>
12#include <stdlib.h>
13#include "remux.h"
14#include "ringbuffer.h"
15#include "thread.h"
16#include "tools.h"
17
18// --- cPtsIndex -------------------------------------------------------------
19
20#define PTSINDEX_ENTRIES 1024
21
22class cPtsIndex {
23private:
24 struct tPtsIndex {
25 uint32_t pts; // no need for 33 bit - some devices don't even supply the msb
26 int index;
28 };
30 int w, r;
33public:
34 cPtsIndex(void);
35 void Clear(void);
36 bool IsEmpty(void);
37 void Put(uint32_t Pts, int Index, bool Independent);
38 int FindIndex(uint32_t Pts, bool Still);
39 int FindFrameNumber(uint32_t Pts, bool Forward, bool Still);
40 };
41
43{
44 lastFound = 0;
45 Clear();
46}
47
49{
50 cMutexLock MutexLock(&mutex);
51 w = r = 0;
52}
53
55{
56 cMutexLock MutexLock(&mutex);
57 return w == r;
58}
59
60void cPtsIndex::Put(uint32_t Pts, int Index, bool Independent)
61{
62 cMutexLock MutexLock(&mutex);
63 pi[w].pts = Pts;
64 pi[w].independent = Independent;
65 pi[w].index = Index;
66 w = (w + 1) % PTSINDEX_ENTRIES;
67 if (w == r)
68 r = (r + 1) % PTSINDEX_ENTRIES;
69}
70
71int cPtsIndex::FindIndex(uint32_t Pts, bool Still)
72{
73 cMutexLock MutexLock(&mutex);
74 if (w == r || Pts == 0 && !Still) // while 0 is a valid PTS, DeviceGetSTC() might return 0 if, after a jump, the device hasn't displayed a frame, yet
75 return lastFound; // list is empty, let's not jump way off the last known position
76 uint32_t Delta = 0xFFFFFFFF;
77 int Index = -1;
78 for (int i = w; i != r; ) {
79 if (--i < 0)
80 i = PTSINDEX_ENTRIES - 1;
81 uint32_t d = pi[i].pts < Pts ? Pts - pi[i].pts : pi[i].pts - Pts;
82 if (d > 0x7FFFFFFF)
83 d = 0xFFFFFFFF - d; // handle rollover
84 if (d < Delta) {
85 Delta = d;
86 Index = pi[i].index;
87 }
88 }
89 lastFound = Index;
90 return Index;
91}
92
93int cPtsIndex::FindFrameNumber(uint32_t Pts, bool Forward, bool Still)
94{
95 if (!Forward)
96 return FindIndex(Pts, Still); // there are only I frames in backward
97 cMutexLock MutexLock(&mutex);
98 if (w == r || Pts == 0 && !Still) // while 0 is a valid PTS, DeviceGetSTC() might return 0 if, after a jump, the device hasn't displayed a frame, yet
99 return lastFound; // replay always starts at an I frame
100 bool Valid = false;
101 int FrameNumber = 0;
102 int UnplayedIFrame = 2; // GOPs may intersect, so we loop until we processed a complete unplayed GOP
103 for (int i = r; i != w && UnplayedIFrame; ) {
104 int32_t d = int32_t(Pts - pi[i].pts); // typecast handles rollover
105 if (d >= 0) {
106 if (pi[i].independent) {
107 FrameNumber = pi[i].index; // an I frame's index represents its frame number
108 Valid = true;
109 if (d == 0)
110 UnplayedIFrame = 1; // if Pts is at an I frame we only need to check up to the next I frame
111 }
112 else
113 FrameNumber++; // for every played non-I frame, increase frame number
114 }
115 else if (pi[i].independent)
116 --UnplayedIFrame;
117 if (++i >= PTSINDEX_ENTRIES)
118 i = 0;
119 }
120 if (Valid) {
121 lastFound = FrameNumber;
122 return FrameNumber;
123 }
124 return FindIndex(Pts, Still); // fall back during trick speeds
125}
126
127// --- cNonBlockingFileReader ------------------------------------------------
128
130private:
138protected:
139 void Action(void);
140public:
143 void Clear(void);
144 void Request(cUnbufferedFile *File, int Length);
145 int Result(uchar **Buffer);
146 bool Reading(void) { return buffer; }
147 bool WaitForDataMs(int msToWait);
148 };
149
151:cThread("non blocking file reader")
152{
153 f = NULL;
154 buffer = NULL;
155 wanted = length = 0;
156 Start();
157}
158
160{
161 newSet.Signal();
162 Cancel(3);
163 free(buffer);
164}
165
167{
168 Lock();
169 f = NULL;
170 free(buffer);
171 buffer = NULL;
172 wanted = length = 0;
173 Unlock();
174}
175
177{
178 Lock();
179 Clear();
180 wanted = Length;
182 f = File;
183 Unlock();
184 newSet.Signal();
185}
186
188{
190 if (buffer && length == wanted) {
191 *Buffer = buffer;
192 buffer = NULL;
193 return wanted;
194 }
195 errno = EAGAIN;
196 return -1;
197}
198
200{
201 while (Running()) {
202 Lock();
203 if (f && buffer && length < wanted) {
204 int r = f->Read(buffer + length, wanted - length);
205 if (r > 0)
206 length += r;
207 else if (r == 0) { // r == 0 means EOF
208 if (length > 0)
209 wanted = length; // already read something, so return the rest
210 else
211 length = wanted = 0; // report EOF
212 }
213 else if (FATALERRNO) {
214 LOG_ERROR;
215 length = wanted = r; // this will forward the error status to the caller
216 }
217 if (length == wanted) {
218 cMutexLock NewDataLock(&newDataMutex);
219 newDataCond.Broadcast();
220 }
221 }
222 Unlock();
223 newSet.Wait(1000);
224 }
225}
226
228{
229 cMutexLock NewDataLock(&newDataMutex);
230 if (buffer && length == wanted)
231 return true;
232 return newDataCond.TimedWait(newDataMutex, msToWait);
233}
234
235// --- cDvbPlayer ------------------------------------------------------------
236
237#define PLAYERBUFSIZE (MAXFRAMESIZE * 5)
238
239#define RESUMEBACKUP 10 // number of seconds to back up when resuming an interrupted replay session
240#define MAXSTUCKATEOF 3 // max. number of seconds to wait in case the device doesn't play the last frame
241
242class cDvbPlayer : public cPlayer, cThread {
243private:
246 static int Speeds[];
250 const cMarks *marks;
257 bool eof;
268 void TrickSpeed(int Increment);
269 void Empty(void);
270 bool NextFile(uint16_t FileNumber = 0, off_t FileOffset = -1);
271 int Resume(void);
272 bool Save(void);
273protected:
274 virtual void Activate(bool On) override;
275 virtual void Action(void) override;
276public:
277 cDvbPlayer(const char *FileName, bool PauseLive);
278 virtual ~cDvbPlayer() override;
279 void SetMarks(const cMarks *Marks);
280 bool Active(void) { return cThread::Running(); }
281 void Pause(void);
282 void Play(void);
283 void Forward(void);
284 void Backward(void);
285 int SkipFrames(int Frames);
286 void SkipSeconds(int Seconds);
287 void Goto(int Position, bool Still = false);
288 virtual double FramesPerSecond(void) override { return framesPerSecond; }
289 virtual void SetAudioTrack(eTrackType Type, const tTrackId *TrackId) override;
290 virtual const cErrors *GetErrors(void) override;
291 virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) override;
292 virtual bool GetFrameNumber(int &Current, int &Total) override;
293 virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed) override;
294 };
295
296#define MAX_VIDEO_SLOWMOTION 63 // max. arg to pass to VIDEO_SLOWMOTION // TODO is this value correct?
297#define NORMAL_SPEED 4 // the index of the '1' entry in the following array
298#define MAX_SPEEDS 3 // the offset of the maximum speed from normal speed in either direction
299#define SPEED_MULT 12 // the speed multiplier
300int cDvbPlayer::Speeds[] = { 0, -2, -4, -8, 1, 2, 4, 12, 0 };
301
302cDvbPlayer::cDvbPlayer(const char *FileName, bool PauseLive)
303:cThread("dvbplayer")
304{
306 ringBuffer = NULL;
307 marks = NULL;
308 index = NULL;
309 cRecording Recording(FileName);
310 framesPerSecond = Recording.FramesPerSecond();
311 isPesRecording = Recording.IsPesRecording();
312 pauseLive = PauseLive;
313 eof = false;
314 firstPacket = true;
318 readIndex = -1;
319 readIndependent = false;
320 readFrame = NULL;
321 playFrame = NULL;
322 dropFrame = NULL;
323 resyncAfterPause = false;
324 isyslog("replay %s", FileName);
325 fileName = new cFileName(FileName, false, false, isPesRecording);
326 replayFile = fileName->Open();
327 if (!replayFile)
328 return;
330 // Create the index file:
331 index = new cIndexFile(FileName, false, isPesRecording, pauseLive);
332 if (!index)
333 esyslog("ERROR: can't allocate index");
334 else if (!index->Ok()) {
335 delete index;
336 index = NULL;
337 }
338 else if (PauseLive)
339 framesPerSecond = cRecording(FileName).FramesPerSecond(); // the fps rate might have changed from the default
340}
341
343{
344 Save();
345 Detach();
346 delete readFrame; // might not have been stored in the buffer in Action()
347 delete index;
348 delete fileName;
349 delete ringBuffer;
350 // don't delete marks here, we don't own them!
351}
352
353void cDvbPlayer::SetMarks(const cMarks *Marks)
354{
355 marks = Marks;
356}
357
358void cDvbPlayer::TrickSpeed(int Increment)
359{
360 int nts = trickSpeed + Increment;
361 if (Speeds[nts] == 1) {
362 trickSpeed = nts;
363 if (playMode == pmFast)
364 Play();
365 else
366 Pause();
367 }
368 else if (Speeds[nts]) {
369 trickSpeed = nts;
370 int Mult = (playMode == pmSlow && playDir == pdForward) ? 1 : SPEED_MULT;
371 int sp = (Speeds[nts] > 0) ? Mult / Speeds[nts] : -Speeds[nts] * Mult;
372 if (sp > MAX_VIDEO_SLOWMOTION)
375 }
376}
377
379{
382 nonBlockingFileReader->Clear();
383 if (!firstPacket) // don't set the readIndex twice if Empty() is called more than once
384 readIndex = ptsIndex.FindIndex(DeviceGetSTC(), playMode == pmStill) - 1; // Action() will first increment it!
385 delete readFrame; // might not have been stored in the buffer in Action()
386 readFrame = NULL;
387 playFrame = NULL;
388 dropFrame = NULL;
389 ringBuffer->Clear();
390 ptsIndex.Clear();
391 DeviceClear();
392 firstPacket = true;
393}
394
395bool cDvbPlayer::NextFile(uint16_t FileNumber, off_t FileOffset)
396{
397 if (FileNumber > 0)
398 replayFile = fileName->SetOffset(FileNumber, FileOffset);
399 else if (replayFile && eof)
400 replayFile = fileName->NextFile();
401 eof = false;
402 return replayFile != NULL;
403}
404
406{
407 if (index) {
408 int Index = index->GetResume();
409 if (Index < 0)
410 index->StoreResume(0); // resume file doesn't exist, so create it to have the recording marked as "last replayed"
411 else {
412 uint16_t FileNumber;
413 off_t FileOffset;
414 if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset)) {
415 index->StoreResume(Index); // to have the recording marked as "last replayed"
416 return Index;
417 }
418 }
419 }
420 return -1;
421}
422
424{
425 if (index) {
426 int Index = ptsIndex.FindIndex(DeviceGetSTC(), playMode == pmStill);
427 if (Index >= 0) {
428 if (Setup.SkipEdited && marks) {
429 cStateKey StateKey;
430 marks->Lock(StateKey);
431 if (marks->First() && abs(Index - marks->First()->Position()) <= int(round(RESUMEBACKUP * framesPerSecond)))
432 Index = 0; // when stopping within RESUMEBACKUP seconds of the first mark the recording shall still be considered unviewed
433 StateKey.Remove();
434 }
435 Index -= int(round(RESUMEBACKUP * framesPerSecond));
436 if (Index > 0)
437 Index = index->GetNextIFrame(Index, false);
438 else
439 Index = 0;
440 if (Index >= 0)
441 return index->StoreResume(Index);
442 }
443 }
444 return false;
445}
446
448{
449 if (On) {
450 if (replayFile)
451 Start();
452 }
453 else
454 Cancel(9);
455}
456
458{
459 uchar *p = NULL;
460 int pc = 0;
461
462 readIndex = Resume();
463 if (readIndex > 0)
464 isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond));
465 else if (Setup.SkipEdited && marks) {
466 cStateKey StateKey;
467 marks->Lock(StateKey);
468 if (marks->First() && index) {
469 int Index = marks->First()->Position();
470 uint16_t FileNumber;
471 off_t FileOffset;
472 if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset)) {
473 isyslog("starting replay at first mark %d (%s)", Index, *IndexToHMSF(Index, true, framesPerSecond));
474 readIndex = Index;
475 }
476 }
477 StateKey.Remove();
478 }
479 if (readIndex > 0) // will first be incremented in the loop!
480 --readIndex;
481
483 int Length = 0;
484 bool Sleep = false;
485 bool WaitingForData = false;
486 time_t StuckAtEof = 0;
487 uint32_t LastStc = 0;
488 int LastReadFrame = -1;
489 int SwitchToPlayFrame = 0;
490 bool CutIn = false;
491 bool AtLastMark = false;
492 int64_t EndPts = -1;
493
494 if (pauseLive)
495 Goto(0, true);
496 while (Running()) {
497 if (WaitingForData)
498 WaitingForData = !nonBlockingFileReader->WaitForDataMs(3); // this keeps the CPU load low, but reacts immediately on new data
499 else if (Sleep) {
500 cPoller Poller;
501 DevicePoll(Poller, 10);
502 Sleep = false;
503 if (playMode == pmStill || playMode == pmPause)
505 }
506 {
508
509 // Read the next frame from the file:
510
511 if (playMode != pmStill && playMode != pmPause) {
512 if (!readFrame && (replayFile || readIndex >= 0)) {
513 if (!nonBlockingFileReader->Reading()) {
514 if (!SwitchToPlayFrame && (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward))) {
515 uint16_t FileNumber;
516 off_t FileOffset;
517 bool TimeShiftMode = index->IsStillRecording();
518 int Index = -1;
519 readIndependent = false;
521 if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length))
522 Index = readIndex + 1;
523 }
524 else {
525 int d = int(round(0.4 * framesPerSecond));
526 if (playDir != pdForward)
527 d = -d;
528 int NewIndex = readIndex + d;
529 if (NewIndex <= 0 && readIndex > 0)
530 NewIndex = 1; // make sure the very first frame is delivered
531 NewIndex = index->GetNextIFrame(NewIndex, playDir == pdForward, &FileNumber, &FileOffset, &Length);
532 if (NewIndex < 0 && TimeShiftMode && playDir == pdForward)
533 SwitchToPlayFrame = readIndex;
534 Index = NewIndex;
535 readIndependent = true;
536 }
537 if (Index >= 0) {
538 readIndex = Index;
539 if (!NextFile(FileNumber, FileOffset))
540 continue;
541 }
542 else if (!(TimeShiftMode && playDir == pdForward))
543 eof = true;
544 }
545 else if (index) {
546 uint16_t FileNumber;
547 off_t FileOffset;
548 if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset)) {
549 readIndex++;
550 if ((Setup.SkipEdited || Setup.PauseAtLastMark) && marks) {
551 cStateKey StateKey;
552 marks->Lock(StateKey);
553 const cMark *m = marks->Get(readIndex);
554 if (m && (m->Index() & 0x01) != 0) { // we're at an end mark
555 m = marks->GetNextBegin(m);
556 int Index = -1;
557 if (m)
558 Index = m->Position(); // skip to next begin mark
559 else if (Setup.PauseAtLastMark)
560 AtLastMark = true; // triggers going into Pause mode
561 else if (index->IsStillRecording())
562 Index = index->GetNextIFrame(index->Last() - int(round(MAXSTUCKATEOF * framesPerSecond)), false); // skip, but stay off end of live-recordings
563 else
564 AtLastMark = true; // triggers stopping replay
565 if (Setup.SkipEdited && Index > readIndex) {
566 isyslog("skipping from %d (%s) to %d (%s)", readIndex - 1, *IndexToHMSF(readIndex - 1, true, framesPerSecond), Index, *IndexToHMSF(Index, true, framesPerSecond));
567 readIndex = Index;
568 CutIn = true;
569 }
570 }
571 StateKey.Remove();
572 }
573 }
574 else
575 eof = true;
576 }
577 else // allows replay even if the index file is missing
578 Length = MAXFRAMESIZE;
579 if (Length == -1)
580 Length = MAXFRAMESIZE; // this means we read up to EOF (see cIndex)
581 else if (Length > MAXFRAMESIZE) {
582 esyslog("ERROR: frame larger than buffer (%d > %d)", Length, MAXFRAMESIZE);
583 Length = MAXFRAMESIZE;
584 }
585 if (!eof)
586 nonBlockingFileReader->Request(replayFile, Length);
587 }
588 if (!eof) {
589 uchar *b = NULL;
590 int r = nonBlockingFileReader->Result(&b);
591 if (r > 0) {
592 WaitingForData = false;
593 LastReadFrame = readIndex;
594 int64_t Pts = isPesRecording ? (PesHasPts(b) ? PesGetPts(b) : -1) : TsGetPts(b, r);
595 readFrame = new cFrame(b, -r, ftUnknown, readIndex, Pts, readIndependent); // hands over b to the ringBuffer
596 if (AtLastMark) {
597 EndPts = Pts;
598 AtLastMark = false;
599 }
600 }
601 else if (r < 0) {
602 if (errno == EAGAIN)
603 WaitingForData = true;
604 else if (FATALERRNO) {
605 LOG_ERROR;
606 break;
607 }
608 }
609 else
610 eof = true;
611 }
612 }
613
614 // Store the frame in the buffer:
615
616 if (readFrame) {
617 if (CutIn) {
618 if (isPesRecording)
619 cRemux::SetBrokenLink(readFrame->Data(), readFrame->Count());
620 CutIn = false;
621 }
622 if (ringBuffer->Put(readFrame))
623 readFrame = NULL;
624 else
625 Sleep = true;
626 }
627 }
628 else {
629 Sleep = true;
630 continue;
631 }
632
633 if (dropFrame) {
634 if (!eof || (playDir != pdForward && dropFrame->Index() > 0) || (playDir == pdForward && dropFrame->Index() < readIndex)) {
635 ringBuffer->Drop(dropFrame); // the very first and last frame are continuously repeated to flush data through the device
636 dropFrame = NULL;
637 }
638 }
639
640 // Get the next frame from the buffer:
641
642 if (!playFrame) {
643 playFrame = ringBuffer->Get();
644 p = NULL;
645 pc = 0;
646 }
647
648 // Play the frame:
649
650 if (playFrame) {
651 if (!p) {
652 p = playFrame->Data();
653 pc = playFrame->Count();
654 if (p) {
655 if (playFrame->Index() >= 0 && playFrame->Pts() != 0)
656 ptsIndex.Put(playFrame->Pts(), playFrame->Index(), playFrame->Independent());
657 if (firstPacket) {
658 if (isPesRecording) {
659 PlayPes(NULL, 0);
661 }
662 else
663 PlayTs(NULL, 0);
664 firstPacket = false;
665 }
666 }
667 }
668 if (p) {
669 int w;
670 bool VideoOnly = (dropFrame || playMode != pmPlay && !(playMode == pmSlow && playDir == pdForward)) && DeviceIsPlayingVideo();
671 if (isPesRecording)
672 w = PlayPes(p, pc, VideoOnly);
673 else
674 w = PlayTs(p, pc, VideoOnly);
675 if (w > 0) {
676 p += w;
677 pc -= w;
678 }
679 else if (w < 0 && FATALERRNO)
680 LOG_ERROR;
681 else
682 Sleep = true;
683 }
684 if (pc <= 0) {
686 playFrame = NULL;
687 p = NULL;
688 }
689 if ((Setup.SkipEdited || Setup.PauseAtLastMark) && EndPts >= 0) {
690 if (PtsDiff(DeviceGetSTC(), EndPts) <= 0) {
691 if (Setup.PauseAtLastMark) {
692 DeviceFreeze();
694 }
695 else
696 break; // stop replay
697 EndPts = -1;
698 }
699 }
700 }
701 else
702 Sleep = true;
703
704 // Handle hitting begin/end of recording:
705
706 if (eof || SwitchToPlayFrame) {
707 bool SwitchToPlay = false;
708 uint32_t Stc = DeviceGetSTC();
709 if (Stc != LastStc || playMode == pmPause)
710 StuckAtEof = 0;
711 else if (!StuckAtEof)
712 StuckAtEof = time(NULL);
713 else if (time(NULL) - StuckAtEof > MAXSTUCKATEOF) {
714 if (playDir == pdForward)
715 break; // automatically stop at end of recording
716 SwitchToPlay = true;
717 }
718 LastStc = Stc;
719 int Index = ptsIndex.FindIndex(Stc, playMode == pmStill);
720 if (playDir == pdForward && !SwitchToPlayFrame) {
721 if (Index >= LastReadFrame)
722 break; // automatically stop at end of recording
723 }
724 else if (Index <= 0 || SwitchToPlayFrame && Index >= SwitchToPlayFrame)
725 SwitchToPlay = true;
726 if (SwitchToPlay) {
727 if (!SwitchToPlayFrame)
728 Empty();
729 DevicePlay();
732 SwitchToPlayFrame = 0;
733 }
734 }
735 }
736 }
737
740 delete nbfr;
741}
742
744{
745 if (playMode == pmPause || playMode == pmStill)
746 Play();
747 else {
749 if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) {
751 Empty();
752 }
753 DeviceFreeze();
755 }
756}
757
759{
760 if (playMode != pmPlay) {
762 if (playMode == pmStill || playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) {
764 Empty();
765 }
766 DevicePlay();
769 if (resyncAfterPause) {
770 int Current, Total;
771 if (GetIndex(Current, Total, true))
772 Goto(Current);
773 resyncAfterPause = false;
774 }
775 }
776}
777
779{
780 if (index) {
781 switch (playMode) {
782 case pmFast:
783 if (Setup.MultiSpeedMode) {
784 TrickSpeed(playDir == pdForward ? 1 : -1);
785 break;
786 }
787 else if (playDir == pdForward) {
788 Play();
789 break;
790 }
791 // run into pmPlay
792 case pmPlay: {
795 Empty();
797 DeviceMute();
801 TrickSpeed(Setup.MultiSpeedMode ? 1 : MAX_SPEEDS);
802 }
803 break;
804 case pmSlow:
805 if (Setup.MultiSpeedMode) {
806 TrickSpeed(playDir == pdForward ? -1 : 1);
807 break;
808 }
809 else if (playDir == pdForward) {
810 Pause();
811 break;
812 }
813 Empty();
814 // run into pmPause
815 case pmStill:
816 case pmPause: {
818 DeviceMute();
822 TrickSpeed(Setup.MultiSpeedMode ? -1 : -MAX_SPEEDS);
823 }
824 break;
825 default: esyslog("ERROR: unknown playMode %d (%s)", playMode, __FUNCTION__);
826 }
827 }
828}
829
831{
832 if (index) {
833 switch (playMode) {
834 case pmFast:
835 if (Setup.MultiSpeedMode) {
836 TrickSpeed(playDir == pdBackward ? 1 : -1);
837 break;
838 }
839 else if (playDir == pdBackward) {
840 Play();
841 break;
842 }
843 // run into pmPlay
844 case pmPlay: {
847 Empty();
849 DeviceMute();
853 TrickSpeed(Setup.MultiSpeedMode ? 1 : MAX_SPEEDS);
854 }
855 break;
856 case pmSlow:
857 if (Setup.MultiSpeedMode) {
858 TrickSpeed(playDir == pdBackward ? -1 : 1);
859 break;
860 }
861 else if (playDir == pdBackward) {
862 Pause();
863 break;
864 }
865 // run into pmPause
866 case pmStill:
867 case pmPause: {
869 Empty();
870 DeviceMute();
874 TrickSpeed(Setup.MultiSpeedMode ? -1 : -MAX_SPEEDS);
875 }
876 break;
877 default: esyslog("ERROR: unknown playMode %d (%s)", playMode, __FUNCTION__);
878 }
879 }
880}
881
883{
884 if (index && Frames) {
885 int Current, Total;
886 GetIndex(Current, Total, true);
887 int OldCurrent = Current;
888 // As GetNextIFrame() increments/decrements at least once, the
889 // destination frame (= Current + Frames) must be adjusted by
890 // -1/+1 respectively.
891 Current = index->GetNextIFrame(Current + Frames + (Frames > 0 ? -1 : 1), Frames > 0);
892 return Current >= 0 ? Current : OldCurrent;
893 }
894 return -1;
895}
896
897void cDvbPlayer::SkipSeconds(int Seconds)
898{
899 if (index && Seconds) {
901 int Index = ptsIndex.FindIndex(DeviceGetSTC(), playMode == pmStill);
902 Empty();
903 if (Index >= 0) {
904 Index = max(Index + SecondsToFrames(Seconds, framesPerSecond), 0);
905 if (Index > 0)
906 Index = index->GetNextIFrame(Index, false, NULL, NULL, NULL);
907 if (Index >= 0)
908 readIndex = Index - 1; // Action() will first increment it!
909 }
910 Play();
911 }
912}
913
914void cDvbPlayer::Goto(int Index, bool Still)
915{
916 if (index) {
918 Empty();
919 if (++Index <= 0)
920 Index = 1; // not '0', to allow GetNextIFrame() below to work!
921 uint16_t FileNumber;
922 off_t FileOffset;
923 int Length;
924 Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset, &Length);
925 if (Index >= 0) {
926 if (Still) {
927 if (NextFile(FileNumber, FileOffset)) {
929 int r = ReadFrame(replayFile, b, Length, sizeof(b));
930 if (r > 0) {
931 if (playMode == pmPause)
932 DevicePlay();
933 DeviceStillPicture(b, r);
934 ptsIndex.Put(isPesRecording ? PesGetPts(b) : TsGetPts(b, r), Index, true);
935 }
937 readIndex = Index - 1; // makes sure a later play starts with this I-frame
938 }
939 }
940 else {
941 readIndex = Index - 1; // Action() will first increment it!
942 Play();
943 }
944 }
945 }
946}
947
949{
951 return; // only do this upon user interaction
952 if (playMode == pmPlay) {
953 if (!ptsIndex.IsEmpty()) {
954 int Current, Total;
955 if (GetIndex(Current, Total, true))
956 Goto(Current);
957 }
958 }
959 else if (playMode == pmPause)
960 resyncAfterPause = true;
961}
962
964{
965 if (index)
966 return index->GetErrors();
967 return NULL;
968}
969
970bool cDvbPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame)
971{
972 if (index) {
973 Current = ptsIndex.FindIndex(DeviceGetSTC(), playMode == pmStill);
974 if (SnapToIFrame) {
975 int i1 = index->GetNextIFrame(Current + 1, false);
976 int i2 = index->GetNextIFrame(Current, true);
977 Current = (abs(Current - i1) <= abs(Current - i2)) ? i1 : i2;
978 }
979 Total = index->Last();
980 return true;
981 }
982 Current = Total = -1;
983 return false;
984}
985
986bool cDvbPlayer::GetFrameNumber(int &Current, int &Total)
987{
988 if (index) {
989 Current = ptsIndex.FindFrameNumber(DeviceGetSTC(), playDir == pdForward, playMode == pmStill);
990 Total = index->Last();
991 return true;
992 }
993 Current = Total = -1;
994 return false;
995}
996
997bool cDvbPlayer::GetReplayMode(bool &Play, bool &Forward, int &Speed)
998{
999 Play = (playMode == pmPlay || playMode == pmFast);
1000 Forward = (playDir == pdForward);
1001 if (playMode == pmFast || playMode == pmSlow)
1002 Speed = Setup.MultiSpeedMode ? abs(trickSpeed - NORMAL_SPEED) : 0;
1003 else
1004 Speed = -1;
1005 return true;
1006}
1007
1008// --- cDvbPlayerControl -----------------------------------------------------
1009
1010cDvbPlayerControl::cDvbPlayerControl(const char *FileName, bool PauseLive)
1011:cControl(NULL)
1012{
1013 player = new cDvbPlayer(FileName, PauseLive);
1015}
1016
1021
1023{
1024 if (player)
1025 player->SetMarks(Marks);
1026}
1027
1029{
1030 return player && player->Active();
1031}
1032
1034{
1035 cControl::player = NULL;
1036 delete player;
1037 player = NULL;
1038}
1039
1041{
1042 if (player)
1043 player->Pause();
1044}
1045
1047{
1048 if (player)
1049 player->Play();
1050}
1051
1053{
1054 if (player)
1055 player->Forward();
1056}
1057
1059{
1060 if (player)
1061 player->Backward();
1062}
1063
1065{
1066 if (player)
1067 player->SkipSeconds(Seconds);
1068}
1069
1071{
1072 if (player)
1073 return player->SkipFrames(Frames);
1074 return -1;
1075}
1076
1078{
1079 if (player)
1080 return player->GetErrors();
1081 return NULL;
1082}
1083
1084bool cDvbPlayerControl::GetIndex(int &Current, int &Total, bool SnapToIFrame)
1085{
1086 if (player) {
1087 player->GetIndex(Current, Total, SnapToIFrame);
1088 return true;
1089 }
1090 return false;
1091}
1092
1093bool cDvbPlayerControl::GetFrameNumber(int &Current, int &Total)
1094{
1095 if (player) {
1096 player->GetFrameNumber(Current, Total);
1097 return true;
1098 }
1099 return false;
1100}
1101
1102bool cDvbPlayerControl::GetReplayMode(bool &Play, bool &Forward, int &Speed)
1103{
1104 return player && player->GetReplayMode(Play, Forward, Speed);
1105}
1106
1107void cDvbPlayerControl::Goto(int Position, bool Still)
1108{
1109 if (player)
1110 player->Goto(Position, Still);
1111}
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
Definition thread.c:73
void SetPlayer(cPlayer *Player)
Definition player.h:113
cControl(cPlayer *Player, bool Hidden=false)
Definition player.c:45
cPlayer * player
Definition player.h:90
void SetMarks(const cMarks *Marks)
Definition dvbplayer.c:1022
virtual ~cDvbPlayerControl() override
Definition dvbplayer.c:1017
bool GetIndex(int &Current, int &Total, bool SnapToIFrame=false)
Definition dvbplayer.c:1084
const cErrors * GetErrors(void)
Definition dvbplayer.c:1077
void SkipSeconds(int Seconds)
Definition dvbplayer.c:1064
cDvbPlayerControl(const char *FileName, bool PauseLive=false)
Definition dvbplayer.c:1010
bool GetReplayMode(bool &Play, bool &Forward, int &Speed)
Definition dvbplayer.c:1102
void Pause(void)
Definition dvbplayer.c:1040
int SkipFrames(int Frames)
Definition dvbplayer.c:1070
void Goto(int Index, bool Still=false)
Definition dvbplayer.c:1107
void Stop(void)
Definition dvbplayer.c:1033
void Forward(void)
Definition dvbplayer.c:1052
bool Active(void)
Definition dvbplayer.c:1028
bool GetFrameNumber(int &Current, int &Total)
Definition dvbplayer.c:1093
void Play(void)
Definition dvbplayer.c:1046
void Backward(void)
Definition dvbplayer.c:1058
cDvbPlayer * player
Definition dvbplayer.h:21
const cMarks * marks
Definition dvbplayer.c:250
virtual const cErrors * GetErrors(void) override
Definition dvbplayer.c:963
cFrame * readFrame
Definition dvbplayer.c:264
cRingBufferFrame * ringBuffer
Definition dvbplayer.c:248
void SetMarks(const cMarks *Marks)
Definition dvbplayer.c:353
virtual void Action(void) override
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition dvbplayer.c:457
cFrame * playFrame
Definition dvbplayer.c:265
virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed) override
Definition dvbplayer.c:997
bool Save(void)
Definition dvbplayer.c:423
virtual void Activate(bool On) override
Definition dvbplayer.c:447
bool firstPacket
Definition dvbplayer.c:258
void SkipSeconds(int Seconds)
Definition dvbplayer.c:897
int Resume(void)
Definition dvbplayer.c:405
cDvbPlayer(const char *FileName, bool PauseLive)
Definition dvbplayer.c:302
cNonBlockingFileReader * nonBlockingFileReader
Definition dvbplayer.c:247
cIndexFile * index
Definition dvbplayer.c:252
cUnbufferedFile * replayFile
Definition dvbplayer.c:253
bool Active(void)
Definition dvbplayer.c:280
virtual void SetAudioTrack(eTrackType Type, const tTrackId *TrackId) override
Definition dvbplayer.c:948
void Goto(int Position, bool Still=false)
Definition dvbplayer.c:914
double framesPerSecond
Definition dvbplayer.c:254
bool isPesRecording
Definition dvbplayer.c:255
void Play(void)
Definition dvbplayer.c:758
cFileName * fileName
Definition dvbplayer.c:251
void Empty(void)
Definition dvbplayer.c:378
ePlayDirs playDir
Definition dvbplayer.c:260
static int Speeds[]
Definition dvbplayer.c:300
int trickSpeed
Definition dvbplayer.c:261
bool NextFile(uint16_t FileNumber=0, off_t FileOffset=-1)
Definition dvbplayer.c:395
void Pause(void)
Definition dvbplayer.c:743
cPtsIndex ptsIndex
Definition dvbplayer.c:249
bool resyncAfterPause
Definition dvbplayer.c:267
virtual ~cDvbPlayer() override
Definition dvbplayer.c:342
virtual bool GetFrameNumber(int &Current, int &Total) override
Definition dvbplayer.c:986
int readIndex
Definition dvbplayer.c:262
void Forward(void)
Definition dvbplayer.c:778
void TrickSpeed(int Increment)
Definition dvbplayer.c:358
virtual double FramesPerSecond(void) override
Definition dvbplayer.c:288
int SkipFrames(int Frames)
Definition dvbplayer.c:882
void Backward(void)
Definition dvbplayer.c:830
bool readIndependent
Definition dvbplayer.c:263
virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame=false) override
Definition dvbplayer.c:970
cFrame * dropFrame
Definition dvbplayer.c:266
bool pauseLive
Definition dvbplayer.c:256
ePlayModes playMode
Definition dvbplayer.c:259
int Index(void) const
Definition tools.c:2118
int Position(void) const
Definition recording.h:412
void Request(cUnbufferedFile *File, int Length)
Definition dvbplayer.c:176
void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition dvbplayer.c:199
bool WaitForDataMs(int msToWait)
Definition dvbplayer.c:227
int Result(uchar **Buffer)
Definition dvbplayer.c:187
cUnbufferedFile * f
Definition dvbplayer.c:131
void Detach(void)
Definition player.c:34
void DeviceStillPicture(const uchar *Data, int Length)
Definition player.h:36
uint64_t DeviceGetSTC(void)
Definition player.h:38
int PlayTs(const uchar *Data, int Length, bool VideoOnly=false)
Definition player.h:48
void DevicePlay(void)
Definition player.h:32
int PlayPes(const uchar *Data, int Length, bool VideoOnly=false)
Definition player.c:26
bool DevicePoll(cPoller &Poller, int TimeoutMs=0)
Definition player.h:26
void DeviceMute(void)
Definition player.h:34
void DeviceFreeze(void)
Definition player.h:33
void DeviceSetTempSubtitles(void)
Definition player.h:37
bool DeviceHasIBPTrickSpeed(void)
Definition player.h:28
cPlayer(ePlayMode PlayMode=pmAudioVideo)
Definition player.c:15
bool DeviceIsPlayingVideo(void)
Definition player.h:29
void DeviceClear(void)
Definition player.h:31
void DeviceTrickSpeed(int Speed, bool Forward)
Definition player.h:30
int FindIndex(uint32_t Pts, bool Still)
Definition dvbplayer.c:71
int FindFrameNumber(uint32_t Pts, bool Forward, bool Still)
Definition dvbplayer.c:93
cPtsIndex(void)
Definition dvbplayer.c:42
tPtsIndex pi[PTSINDEX_ENTRIES]
Definition dvbplayer.c:29
void Clear(void)
Definition dvbplayer.c:48
bool IsEmpty(void)
Definition dvbplayer.c:54
int lastFound
Definition dvbplayer.c:31
cMutex mutex
Definition dvbplayer.c:32
void Put(uint32_t Pts, int Index, bool Independent)
Definition dvbplayer.c:60
double FramesPerSecond(void) const
Definition recording.h:191
bool IsPesRecording(void) const
Definition recording.h:215
static void SetBrokenLink(uchar *Data, int Length)
Definition remux.c:102
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
Definition thread.c:885
void Unlock(void)
Definition thread.h:96
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
Definition thread.c:320
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
Definition thread.h:102
void Lock(void)
Definition thread.h:95
cThread(const char *Description=NULL, bool LowPriority=false)
Creates a new thread.
Definition thread.c:254
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
Definition thread.c:370
static tThreadId IsMainThread(void)
Definition thread.h:132
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner,...
Definition tools.h:507
cSetup Setup
Definition config.c:372
eTrackType
Definition device.h:63
#define MAX_VIDEO_SLOWMOTION
Definition dvbplayer.c:296
#define SPEED_MULT
Definition dvbplayer.c:299
#define PTSINDEX_ENTRIES
Definition dvbplayer.c:20
#define NORMAL_SPEED
Definition dvbplayer.c:297
#define RESUMEBACKUP
Definition dvbplayer.c:239
#define MAX_SPEEDS
Definition dvbplayer.c:298
#define MAXSTUCKATEOF
Definition dvbplayer.c:240
#define PLAYERBUFSIZE
Definition dvbplayer.c:237
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
Definition recording.c:3526
int SecondsToFrames(int Seconds, double FramesPerSecond)
Definition recording.c:3553
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
Definition recording.c:3560
#define MAXFRAMESIZE
Definition recording.h:497
int64_t PtsDiff(int64_t Pts1, int64_t Pts2)
Returns the difference between two PTS values.
Definition remux.c:236
int64_t TsGetPts(const uchar *p, int l, int Pid)
Definition remux.c:160
bool PesHasPts(const uchar *p)
Definition remux.h:183
int64_t PesGetPts(const uchar *p)
Definition remux.h:193
@ ftUnknown
Definition ringbuffer.h:107
#define LOCK_THREAD
Definition thread.h:168
#define FATALERRNO
Definition tools.h:52
unsigned char uchar
Definition tools.h:31
#define MALLOC(type, size)
Definition tools.h:47
T max(T a, T b)
Definition tools.h:64
#define esyslog(a...)
Definition tools.h:35
#define LOG_ERROR
Definition tools.h:39
#define isyslog(a...)
Definition tools.h:36