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