]> git.vomp.tv Git - vompclient.git/blob - playerradio.cc
Rewrite timers class using std::thread/mutex/cond/chrono
[vompclient.git] / playerradio.cc
1 /*
2     Copyright 2004-2006 Chris Tallon
3
4     This file is part of VOMP.
5
6     VOMP is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     VOMP is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with VOMP.  If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "playerradio.h"
21
22 #include "log.h"
23 #include "audio.h"
24 #include "video.h"
25 #include "demuxervdr.h"
26 #include "demuxerts.h"
27 #include "input.h"
28 #include "vdr.h"
29 #include "message.h"
30 #include "messagequeue.h"
31
32 // ----------------------------------- Called from outside, one offs or info funcs
33
34 PlayerRadio::PlayerRadio(MessageQueue* tmessageQueue, void* tmessageReceiver)
35 : afeed(this)
36 {
37   messageQueue = tmessageQueue;
38   messageReceiver = tmessageReceiver;
39   audio = Audio::getInstance();
40   logger = Log::getInstance();
41   vdr = VDR::getInstance();
42   initted = false;
43   lengthBytes = 0;
44   lengthFrames = 0;
45   currentFrameNumber = 0;
46   state = S_STOP;
47
48   startPTS = 0;
49   lengthSeconds = 0;
50
51   threadBuffer = NULL;
52
53   blockSize = 10000;
54   startupBlockSize = 20000;
55
56   Video::getInstance()->turnVideoOff();
57 }
58
59 PlayerRadio::~PlayerRadio()
60 {
61   if (initted) shutdown();
62 }
63
64 int PlayerRadio::init(ULLONG tlengthBytes, ULONG tlengthFrames, bool isPesRecording)
65 {
66   if (initted) return 0;
67 #ifndef WIN32
68   pthread_mutex_init(&mutex, NULL);
69 #else
70   mutex=CreateMutex(NULL,FALSE,NULL);
71 #endif
72
73   if (isPesRecording)
74     demuxer = new DemuxerVDR();
75   else
76     demuxer = new DemuxerTS();
77   if (!demuxer) return 0;
78
79   if (!demuxer->init(this, audio, NULL, NULL, 0, 40000, 0))
80   {
81     logger->log("PlayerRadio", Log::ERR, "Demuxer failed to init");
82     shutdown();
83     return 0;
84   }
85
86   afeed.init();
87   audio->stop();
88
89   lengthBytes = tlengthBytes;
90   lengthFrames = tlengthFrames;
91
92   logger->log("PlayerRadio", Log::DEBUG, "PlayerRadio has received length bytes of %llu", lengthBytes);
93
94   UINT thisRead = 0;
95   int success;
96
97   UCHAR* buffer = vdr->getBlock(0, 10000, &thisRead);
98   if (!buffer)
99   {
100     logger->log("PlayerRadio", Log::ERR, "Failed to get start block");
101     shutdown();
102     if (!vdr->isConnected()) doConnectionLost();
103     return 0;
104   }
105
106   success = demuxer->findPTS(buffer, thisRead, &startPTS);
107   if (!success)
108   {
109     logger->log("PlayerRadio", Log::ERR, "Failed to get start PTS");
110     free(buffer);
111     shutdown();
112     return 0;
113   }
114
115   free(buffer);
116
117   if (!setLengthSeconds())
118   {
119     logger->log("PlayerRadio", Log::ERR, "Failed to setLengthSeconds");
120     shutdown();
121     return 0;
122   }
123
124   initted = true;
125   return 1;
126 }
127
128 bool PlayerRadio::setLengthSeconds()
129 {
130   int success;
131   ULLONG endPTS = 0;
132   UINT thisRead = 0;
133   UCHAR* buffer = vdr->getBlock(lengthBytes - 10000, 10000, &thisRead);
134   if (!buffer)
135   {
136     logger->log("PlayerRadio", Log::ERR, "Failed to get end block");
137     if (!vdr->isConnected()) doConnectionLost();    
138     return false;
139   }
140
141   success = demuxer->findPTS(buffer, thisRead, &endPTS);
142   free(buffer);
143   if (!success)
144   {
145     logger->log("PlayerRadio", Log::ERR, "Failed to get end PTS");
146     return false;
147   }
148
149   if (startPTS < endPTS)
150   {
151     lengthSeconds = (endPTS - startPTS) / 90000;
152   }
153   else
154   {
155     lengthSeconds = (startPTS - endPTS) / 90000;
156   }
157
158   return true;
159 }
160
161 int PlayerRadio::shutdown()
162 {
163   if (!initted) return 0;
164   switchState(S_STOP);
165   initted = false;
166
167   delete demuxer;
168   demuxer = NULL;
169
170 #ifdef WIN32
171   CloseHandle(mutex);
172 #endif
173
174   return 1;
175 }
176
177
178 void PlayerRadio::setCurrentFrameNumber(ULONG num)
179 {
180   currentFrameNumber = num;
181 }
182
183 ULONG PlayerRadio::getLengthSeconds()
184 {
185   return lengthSeconds;
186 }
187
188 ULONG PlayerRadio::getCurrentSeconds()
189 {
190   if (startup) return 0;
191
192   long long currentPTS = demuxer->getAudioPTS();
193   currentPTS -= startPTS;
194   if (currentPTS < 0) currentPTS += 8589934592ULL;
195   ULONG ret = currentPTS / 90000;
196   return ret;
197 }
198
199 // ----------------------------------- Externally called events
200
201 void PlayerRadio::play()
202 {
203   if (!initted) return;
204   if (state == S_PLAY) return;
205   lock();
206   switchState(S_PLAY);
207   unLock();
208 }
209
210
211 void PlayerRadio::playpause()
212 {
213   if (!initted) return;
214   lock();
215   if (state==S_PLAY) {
216           switchState(S_PAUSE_P);
217   } else {
218           switchState(S_PLAY);
219   }
220   unLock();
221 }
222
223 void PlayerRadio::stop()
224 {
225   if (!initted) return;
226   if (state == S_STOP) return;
227   lock();
228   logger->log("PlayerRadio", Log::DEBUG, "Stop called lock");
229   switchState(S_STOP);
230   unLock();
231 }
232
233 void PlayerRadio::pause()
234 {
235   if (!initted) return;
236   lock();
237
238   if (state == S_PAUSE_P)
239   {
240     switchState(S_PLAY);
241   }
242   else
243   {
244     switchState(S_PAUSE_P);
245   }
246
247   unLock();
248 }
249
250 void PlayerRadio::jumpToPercent(double percent)
251 {
252   lock();
253   logger->log("PlayerRadio", Log::DEBUG, "JUMP TO %i%%", percent);
254   ULONG newFrame = (ULONG)(percent * lengthFrames / 100);
255   switchState(S_JUMP, newFrame);
256   unLock();
257 }
258
259 void PlayerRadio::skipForward(UINT seconds)
260 {
261   lock();
262   logger->log("PlayerRadio", Log::DEBUG, "SKIP FORWARD %i SECONDS", seconds);
263   ULONG currentSeconds = getCurrentSeconds();
264   ULONG currentFrame = demuxer->getPacketNum();
265
266   if (currentSeconds == 0) { unLock(); return; } // div by zero
267   if (currentFrame == 0) { unLock(); return; } // Current pos from demuxer is not valid
268
269   ULONG newFrame = currentFrame + (currentFrame * seconds / currentSeconds);
270   if (newFrame > lengthFrames) { switchState(S_PLAY); unLock(); }
271   else switchState(S_JUMP, newFrame);
272   unLock();
273 }
274
275 void PlayerRadio::skipBackward(UINT seconds)
276 {
277   lock();
278   logger->log("PlayerRadio", Log::DEBUG, "SKIP BACKWARD %i SECONDS", seconds);
279
280   ULONG currentSeconds = getCurrentSeconds();
281   ULONG currentFrame = demuxer->getPacketNum();
282
283   if (currentSeconds == 0) { unLock(); return; } // div by zero
284   if (currentFrame == 0) { unLock(); return; } // Current pos from demuxer is not valid
285
286   ULONG newFrame;
287   if ((UINT)seconds > currentSeconds)
288     newFrame = 0;
289   else
290     newFrame = currentFrame - (currentFrame * seconds / currentSeconds);
291
292   switchState(S_JUMP, newFrame);
293   unLock();
294 }
295
296 // ----------------------------------- Implementations called events
297
298 void PlayerRadio::switchState(UCHAR toState, ULONG jumpToFrame)
299 {
300   if (!initted) return;
301
302   logger->log("PlayerRadio", Log::DEBUG, "Switch state from %u to %u", state, toState);
303
304   switch(state) // current state selector
305   {
306     case S_PLAY: // from S_PLAY -----------------------------------
307     {
308       switch(toState)
309       {
310         case S_PLAY: // to S_PLAY
311         {
312           return;
313         }
314         case S_PAUSE_P: // to S_PAUSE_P
315         {
316           audio->pause();
317           state = S_PAUSE_P;
318           return;
319         }
320         case S_STOP: // to S_STOP
321         {
322           afeed.stop();
323           threadStop();
324           audio->stop();
325           audio->unPause();
326           demuxer->reset();
327           state = S_STOP;
328           return;
329         }
330         case S_JUMP: // to S_JUMP
331         {
332           restartAtFrame(jumpToFrame);
333           return;
334         }
335       }
336     }
337     FALLTHROUGH // keep compiler happy (all posibilities return)
338     case S_PAUSE_P: // from S_PAUSE_P -----------------------------------
339     {
340       switch(toState)
341       {
342         case S_PLAY: // to S_PLAY
343         {
344           audio->unPause();
345           state = S_PLAY;
346           return;
347         }
348         case S_PAUSE_P: // to S_PAUSE_P
349         {
350           return;
351         }
352         case S_STOP: // to S_STOP
353         {
354           afeed.stop();
355           threadStop();
356           audio->stop();
357           audio->unPause();
358           demuxer->reset();
359           audio->systemMuteOff();
360           state = S_STOP;
361           return;
362         }
363         case S_JUMP: // to S_JUMP
364         {
365           state = S_PLAY;
366           audio->unPause();
367           restartAtFrame(jumpToFrame);
368           return;
369         }
370       }
371     }
372     FALLTHROUGH // keep compiler happy (all posibilities return)
373     case S_STOP: // from S_STOP -----------------------------------
374     {
375       switch(toState)
376       {
377         case S_PLAY: // to S_PLAY
378         {
379           startup = true;
380
381           audio->reset();
382           audio->setStreamType(Audio::MPEG2_PES);
383           audio->systemMuteOff();
384           demuxer->reset();
385
386           // FIXME use restartAtFrame here?
387           if (currentFrameNumber > lengthFrames) currentFrameNumber = 0;
388           demuxer->setPacketNum(currentFrameNumber);
389           state = S_PLAY;
390           threadStart();
391           logger->log("PlayerRadio", Log::DEBUG, "Immediate play");
392           afeed.start();
393           audio->play();
394
395           return;
396         }
397         case S_PAUSE_P: // to S_PAUSE_P
398         {
399           return;
400         }
401         case S_STOP: // to S_STOP
402         {
403           return;
404         }
405         case S_JUMP: // to S_JUMP
406         {
407           return;
408         }
409       }
410     }
411     // case S_JUMP cannot be selected as a start state because it auto flips to play
412   }
413 }
414
415 // ----------------------------------- Internal functions
416
417 void PlayerRadio::lock()
418 {
419 #ifndef WIN32
420   pthread_mutex_lock(&mutex);
421   logger->log("PlayerRadio", Log::DEBUG, "LOCKED");
422
423 #else
424    WaitForSingleObject(mutex, INFINITE);
425 #endif
426 }
427
428 void PlayerRadio::unLock()
429 {
430 #ifndef WIN32
431   logger->log("PlayerRadio", Log::DEBUG, "UNLOCKING");
432   pthread_mutex_unlock(&mutex);
433 #else
434    ReleaseMutex(mutex);
435 #endif
436 }
437
438 void PlayerRadio::restartAtFrame(ULONG newFrame)
439 {
440   afeed.stop();
441   threadStop();
442   audio->reset();
443   audio->setStreamType(Audio::MPEG2_PES);
444   demuxer->flush();
445   currentFrameNumber = newFrame;
446   demuxer->setPacketNum(newFrame);
447   afeed.start();
448   threadStart();
449   audio->play();
450   audio->systemMuteOff();
451   audio->doMuting();
452 }
453
454 void PlayerRadio::doConnectionLost()
455 {
456   logger->log("PlayerRadio", Log::DEBUG, "Connection lost, sending message");
457   Message* m = new Message();
458   m->to = messageReceiver;
459   m->from = this;
460   m->message = Message::PLAYER_EVENT;
461   m->parameter = PlayerRadio::CONNECTION_LOST;
462   messageQueue->postMessage(m);
463 }
464
465 // ----------------------------------- Callback
466
467 void PlayerRadio::call(void* /*caller*/)
468 {
469   threadSignalNoLock();
470 }
471
472 // ----------------------------------- Feed thread
473
474 void PlayerRadio::threadMethod()
475 {
476   if (state == S_PLAY) threadFeedPlay();
477 }
478
479 void PlayerRadio::threadFeedPlay()
480 {
481   ULLONG feedPosition;
482   UINT thisRead, writeLength, thisWrite, askFor;
483   time_t lastRescan = time(NULL);
484
485   feedPosition = vdr->positionFromFrameNumber(currentFrameNumber);
486   if (!vdr->isConnected()) { doConnectionLost(); return; }
487   logger->log("PlayerRadio", Log::DEBUG, "startFeedPlay: wantedFrame %i goto %llu", currentFrameNumber, feedPosition);
488
489
490   while(1)
491   {
492     thisRead = 0;
493     writeLength = 0;
494     thisWrite = 0;
495
496     threadCheckExit();
497
498     // If we havn't rescanned for a while..
499     if ((lastRescan + 60) < time(NULL))
500     {
501       lengthBytes = vdr->rescanRecording(&lengthFrames);
502       if (!vdr->isConnected()) { doConnectionLost(); return; }
503       logger->log("PlayerRadio", Log::DEBUG, "Rescanned and reset length: %llu", lengthBytes);
504       lastRescan = time(NULL);
505
506       if (!setLengthSeconds())
507       {
508         logger->log("PlayerRadio", Log::ERR, "Failed to setLengthSeconds in thread");
509         return;
510       }
511     }
512
513     if (feedPosition >= lengthBytes) break;  // finished playback
514
515     if (startup)
516     {
517       if (startupBlockSize > lengthBytes)
518         askFor = lengthBytes; // is a very small recording!
519       else
520         askFor = startupBlockSize; // normal, but a startup sized block to detect all the audio streams
521     }
522     else
523     {
524       if ((feedPosition + blockSize) > lengthBytes) // last block of recording
525         askFor = lengthBytes - feedPosition;
526       else // normal
527         askFor = blockSize;
528     }
529
530     threadBuffer = vdr->getBlock(feedPosition, askFor, &thisRead);
531     feedPosition += thisRead;
532
533     if (!vdr->isConnected())
534     {
535       doConnectionLost();
536       return;
537     }
538
539     if (!threadBuffer) break;
540
541     if (startup)
542     {
543       int a_stream = demuxer->scan(threadBuffer, thisRead);
544       demuxer->setAudioStream(a_stream);
545       logger->log("PlayerRadio", Log::DEBUG, "Startup Audio stream chosen %x", a_stream);
546       startup = false;
547     }
548
549     threadCheckExit();
550
551     while(writeLength < thisRead)
552     {
553       thisWrite = demuxer->put(threadBuffer + writeLength, thisRead - writeLength);
554       writeLength += thisWrite;
555
556       if (!thisWrite)
557       {
558         // demuxer is full and can't take anymore
559         threadLock();
560         threadWaitForSignal();
561         threadUnlock();
562       }
563
564       threadCheckExit();
565     }
566
567     free(threadBuffer);
568     threadBuffer = NULL;
569
570   }
571
572   // end of recording
573   logger->log("PlayerRadio", Log::DEBUG, "Recording playback ends");
574
575   threadCheckExit();
576
577
578   Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex
579   m->to = messageReceiver;
580   m->from = this;
581   m->message = Message::PLAYER_EVENT;
582   m->parameter = PlayerRadio::STOP_PLAYBACK;
583   logger->log("PlayerRadio", Log::DEBUG, "Posting message to %p...", messageQueue);
584   messageQueue->postMessage(m);
585 }
586
587 void PlayerRadio::threadPostStopCleanup()
588 {
589   if (threadBuffer)
590   {
591     free(threadBuffer);
592     threadBuffer = NULL;
593   }
594 }
595