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