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