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