]> git.vomp.tv Git - vompclient.git/blob - player.cc
Add basic audio playback (stereo), fast forward, ThreadP bugfix and Stream interface...
[vompclient.git] / player.cc
1 /*\r
2     Copyright 2004-2008 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 "player.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 "vdr.h"\r
29 #include "messagequeue.h"\r
30 #include "remote.h"\r
31 #include "message.h"\r
32 #include "dvbsubtitles.h"\r
33 #include "osdreceiver.h"\r
34 \r
35 #define USER_RESPONSE_TIME 500 // Milliseconds\r
36 \r
37 // ----------------------------------- Called from outside, one offs or info funcs\r
38 \r
39 Player::Player(MessageQueue* tmessageQueue, void* tmessageReceiver, OSDReceiver* tosdReceiver)\r
40 : vfeed(this), afeed(this), tfeed(this)\r
41 {\r
42   messageQueue = tmessageQueue;\r
43   messageReceiver = tmessageReceiver;\r
44   osdReceiver = tosdReceiver;\r
45   audio = Audio::getInstance();\r
46   video = Video::getInstance();\r
47   logger = Log::getInstance();\r
48   vdr = VDR::getInstance();\r
49   initted = false;\r
50   lengthBytes = 0;\r
51   lengthFrames = 0;\r
52   currentFrameNumber = 0;\r
53   state = S_STOP;\r
54   ifactor = 4;\r
55   is_pesrecording=true;\r
56 \r
57   subtitlesShowing = false;\r
58 \r
59   videoStartup = false;\r
60   threadBuffer = NULL;\r
61  \r
62   blockSize = 100000;\r
63   startupBlockSize = 250000;\r
64   video->turnVideoOn();\r
65 }\r
66 \r
67 Player::~Player()\r
68 {\r
69   if (initted) shutdown();\r
70 }\r
71 \r
72 int Player::init(bool p_isPesRecording,double framespersecond)\r
73 {\r
74   if (initted) return 0;\r
75 #ifndef WIN32\r
76   pthread_mutex_init(&mutex, NULL);\r
77 #else\r
78   mutex=CreateMutex(NULL,FALSE,NULL);\r
79 #endif\r
80   is_pesrecording = p_isPesRecording;\r
81   fps=framespersecond;\r
82   if (is_pesrecording)\r
83     demuxer = new DemuxerVDR();\r
84   else\r
85     demuxer = new DemuxerTS();\r
86   if (!demuxer) return 0;\r
87   subtitles = new DVBSubtitles(osdReceiver);\r
88   if (!subtitles) return 0;\r
89 \r
90   teletext = new TeletextDecoderVBIEBU();\r
91   if (!teletext) return 0;\r
92   teletext->setRecordigMode(true);\r
93   unsigned int demux_video_size=2097152;\r
94   unsigned int demux_audio_size=524288;\r
95   if (video->supportsh264()) {\r
96           demux_video_size*=5*2;//5;\r
97           demux_audio_size*=2;\r
98   }\r
99  \r
100   if (!demuxer->init(this, audio, video,teletext, demux_video_size,demux_audio_size,65536, framespersecond, subtitles))\r
101   {\r
102     logger->log("Player", Log::ERR, "Demuxer failed to init");\r
103     shutdown();\r
104     return 0;\r
105   }\r
106 \r
107   vfeed.init();\r
108   afeed.init();\r
109   tfeed.init();\r
110 \r
111   video->stop();\r
112   video->blank();\r
113   audio->stop();\r
114 \r
115   initted = true;\r
116   return 1;\r
117 }\r
118 \r
119 int Player::shutdown()\r
120 {\r
121   if (!initted) return 0;\r
122   switchState(S_STOP);\r
123   initted = false;\r
124 \r
125   delete demuxer;\r
126   demuxer = NULL;\r
127   delete subtitles;\r
128   subtitles = NULL;\r
129   delete teletext;\r
130   teletext = NULL;\r
131 \r
132 #ifdef WIN32\r
133   CloseHandle(mutex);\r
134 #endif\r
135 \r
136   return 1;\r
137 }\r
138 \r
139 void Player::setStartFrame(ULONG startFrame)\r
140 {\r
141   currentFrameNumber = startFrame;\r
142 }\r
143 \r
144 void Player::setLengthBytes(ULLONG length)\r
145 {\r
146   lengthBytes = length;\r
147   logger->log("Player", Log::DEBUG, "Player has received length bytes of %llu", lengthBytes);\r
148 }\r
149 \r
150 void Player::setLengthFrames(ULONG length)\r
151 {\r
152   lengthFrames = length;\r
153   logger->log("Player", Log::DEBUG, "Player has received length frames of %lu", lengthFrames);\r
154 }\r
155 \r
156 ULONG Player::getLengthFrames()\r
157 {\r
158   return lengthFrames;\r
159 }\r
160 \r
161 ULONG Player::getCurrentFrameNum()\r
162 {\r
163   if (startup) return 0;\r
164   switch(state)\r
165   {\r
166     case S_PLAY:\r
167     case S_PAUSE_P:\r
168       return demuxer->getFrameNumFromPTS(video->getCurrentTimestamp());\r
169     case S_PAUSE_I:\r
170     case S_FFWD:\r
171     case S_FBWD:\r
172       return currentFrameNumber;\r
173     default:\r
174       return 0; // shouldn't happen\r
175   }\r
176 }\r
177 \r
178 bool* Player::getDemuxerMpegAudioChannels()\r
179 {\r
180   return demuxer->getmpAudioChannels();\r
181 }\r
182 \r
183 bool* Player::getDemuxerAc3AudioChannels()\r
184 {\r
185   return demuxer->getac3AudioChannels();\r
186 }\r
187 \r
188 bool* Player::getDemuxerSubtitleChannels()\r
189 {\r
190   return demuxer->getSubtitleChannels();\r
191 }\r
192 \r
193 int Player::getCurrentAudioChannel()\r
194 {\r
195     if (is_pesrecording) {\r
196         return demuxer->getselAudioChannel();\r
197     } else {\r
198         return ((DemuxerTS*)demuxer)->getAID();\r
199     }\r
200 }\r
201 \r
202 int Player::getCurrentSubtitleChannel()\r
203 {\r
204     if (is_pesrecording) {\r
205         return demuxer->getselSubtitleChannel();\r
206     } else {\r
207         return ((DemuxerTS*)demuxer)->getSubID();\r
208     }\r
209 }\r
210 \r
211 void Player::setSubtitleChannel(int newChannel)\r
212 {\r
213     if (is_pesrecording) {\r
214          demuxer->setDVBSubtitleStream(newChannel);\r
215     } else {\r
216         ((DemuxerTS*)demuxer)->setSubID(newChannel);\r
217     }\r
218 }\r
219 \r
220 int *Player::getTeletxtSubtitlePages()\r
221 {\r
222     return teletext->getSubtitlePages();\r
223 }\r
224 \r
225 void Player::setAudioChannel(int newChannel, int type)\r
226 {\r
227     if (is_pesrecording) {\r
228         demuxer->setAudioStream(newChannel);\r
229         return;\r
230     } else {\r
231         ((DemuxerTS*)demuxer)->setAID(newChannel,type);\r
232         return;\r
233     }\r
234 }\r
235 \r
236 bool Player::toggleSubtitles()\r
237 {\r
238   if (!subtitlesShowing)\r
239   {\r
240     subtitlesShowing = true;\r
241     subtitles->show();\r
242   }\r
243   else\r
244   {\r
245     subtitlesShowing = false;\r
246     subtitles->hide();\r
247   }\r
248   return subtitlesShowing;\r
249 }\r
250 \r
251 void  Player::turnSubtitlesOn(bool ison) {\r
252  if (ison)\r
253   {\r
254     subtitlesShowing = true;\r
255     subtitles->show();\r
256   }\r
257   else\r
258   {\r
259     subtitlesShowing = false;\r
260     subtitles->hide();\r
261   }\r
262 \r
263 }\r
264 \r
265 Channel Player::getDemuxerChannel() {\r
266     if (!is_pesrecording) {\r
267         return ((DemuxerTS*) demuxer)->getChannelInfo();\r
268     } \r
269     return Channel(); //Should not happen!\r
270 }\r
271 \r
272 \r
273 // ----------------------------------- Externally called events\r
274 \r
275 void Player::play()\r
276 {\r
277   if (!initted) return;\r
278   if (state == S_PLAY) return;\r
279   lock();\r
280 \r
281   bool doUnlock = false;\r
282   if (state == S_PAUSE_P) doUnlock = true;\r
283   switchState(S_PLAY);\r
284   if (doUnlock) unLock();\r
285 }\r
286 \r
287 void Player::playpause()\r
288 {\r
289   if (!initted) return;\r
290   lock();\r
291 \r
292   bool doUnlock = false;\r
293   if (state==S_PLAY) {\r
294           doUnlock=true;\r
295           switchState(S_PAUSE_P);\r
296   } else {\r
297           if (state == S_PAUSE_P) doUnlock = true;\r
298           switchState(S_PLAY);\r
299   }\r
300   if (doUnlock) unLock();\r
301 \r
302 }\r
303 \r
304 \r
305 \r
306 \r
307 void Player::stop()\r
308 {\r
309   if (!initted) return;\r
310   if (state == S_STOP) return;\r
311   lock();\r
312   logger->log("Player", Log::DEBUG, "Stop called lock");\r
313   switchState(S_STOP);\r
314   unLock();\r
315 }\r
316 \r
317 void Player::pause()\r
318 {\r
319   if (!initted) return;\r
320   lock();\r
321 \r
322   if ((state == S_FFWD) || (state == S_FBWD))\r
323   {\r
324     switchState(S_PAUSE_I);\r
325   }\r
326   else if ((state == S_PAUSE_I) || (state == S_PAUSE_P))\r
327   {\r
328     switchState(S_PLAY);\r
329   }\r
330   else\r
331   {\r
332     switchState(S_PAUSE_P);\r
333   }\r
334 \r
335   unLock();\r
336 }\r
337 \r
338 void Player::fastForward()\r
339 {\r
340   if (!initted) return;\r
341   lock();\r
342 \r
343   if (state == S_FFWD)\r
344   {\r
345     // change the rate\r
346     switch(ifactor)\r
347     {\r
348       case 4: ifactor = 8; break;\r
349       case 8: ifactor = 16; break;\r
350       case 16: ifactor = 32; break;\r
351       case 32: ifactor = 4; break;\r
352     }\r
353   }\r
354   else\r
355   {\r
356     ifactor = 4;\r
357     switchState(S_FFWD);\r
358   }\r
359   unLock();\r
360 }\r
361 \r
362 void Player::fastBackward()\r
363 {\r
364   if (!initted) return;\r
365   lock();\r
366 \r
367   if (state == S_FBWD)\r
368   {\r
369     // change the rate\r
370     switch(ifactor)\r
371     {\r
372       case 4: ifactor = 8; break;\r
373       case 8: ifactor = 16; break;\r
374       case 16: ifactor = 32; break;\r
375       case 32: ifactor = 4; break;\r
376     }\r
377   }\r
378   else\r
379   {\r
380     ifactor = 4;\r
381     switchState(S_FBWD);\r
382   }\r
383   unLock();\r
384 }\r
385 \r
386 void Player::jumpToPercent(double percent)\r
387 {\r
388   lock();\r
389   logger->log("Player", Log::DEBUG, "JUMP TO %f%%", percent);\r
390   ULONG newFrame = (ULONG)(percent * lengthFrames / 100);\r
391   switchState(S_JUMP, newFrame);\r
392 //  unLock(); - let thread unlock this\r
393 }\r
394 \r
395 void Player::jumpToMark(int mark)\r
396 {\r
397   lock();\r
398   logger->log("Player", Log::DEBUG, "JUMP TO MARK %i%%", mark);\r
399   switchState(S_JUMP, mark);\r
400 //  unLock(); - let thread unlock this\r
401 }\r
402 \r
403 void Player::jumpToFrameP(int newFrame)\r
404 {\r
405   lock();\r
406   logger->log("Player", Log::DEBUG, "JUMP TO FRAME AND PAUSE %i", newFrame);\r
407   switchState(S_JUMP_PI, newFrame);\r
408   unLock();\r
409 }\r
410 \r
411 void Player::skipForward(int seconds)\r
412 {\r
413   lock();\r
414   logger->log("Player", Log::DEBUG, "SKIP FORWARD %i SECONDS", seconds);\r
415   ULONG newFrame = getCurrentFrameNum();\r
416   if (newFrame == 0) { unLock(); return; } // Current pos from demuxer is not valid\r
417   newFrame +=(ULONG) (((double)seconds) * fps);\r
418   if (newFrame > lengthFrames) { switchState(S_PLAY); unLock(); }\r
419   else switchState(S_JUMP, newFrame);\r
420 //  unLock(); - let thread unlock this\r
421 }\r
422 \r
423 void Player::skipBackward(int seconds)\r
424 {\r
425   lock();\r
426   logger->log("Player", Log::DEBUG, "SKIP BACKWARD %i SECONDS", seconds);\r
427   long newFrame = getCurrentFrameNum();\r
428   if (newFrame == 0) { unLock(); return; } // Current pos from demuxer is not valid\r
429   newFrame -= (ULONG) (((double)seconds) * fps);\r
430   if (newFrame < 0) newFrame = 0;\r
431   switchState(S_JUMP, newFrame);\r
432 //  unLock(); - let thread unlock this\r
433 }\r
434 \r
435 // ----------------------------------- Implementations called events\r
436 \r
437 void Player::switchState(UCHAR toState, ULONG jumpFrame)\r
438 {\r
439   if (!initted) return;\r
440 \r
441   logger->log("Player", Log::DEBUG, "Switch state from %u to %u", state, toState);\r
442 \r
443   switch(state) // current state selector\r
444   {\r
445     case S_PLAY: // from S_PLAY -----------------------------------\r
446     {\r
447       switch(toState)\r
448       {\r
449         case S_PLAY: // to S_PLAY\r
450         {\r
451           return;\r
452         }\r
453         case S_PAUSE_P: // to S_PAUSE_P\r
454         {\r
455           video->pause();\r
456           audio->pause();\r
457           state = S_PAUSE_P;\r
458           return;\r
459         }\r
460         case S_PAUSE_I: // to S_PAUSE_I\r
461         {\r
462           // can't occur\r
463           return;\r
464         }\r
465         case S_FFWD: // to S_FFWD\r
466         {\r
467           currentFrameNumber = getCurrentFrameNum();\r
468           audio->systemMuteOn();\r
469           threadStop();\r
470           vfeed.stop();\r
471           afeed.stop();\r
472           tfeed.stop();\r
473           subtitles->stop();\r
474           demuxer->flush();\r
475           state = S_FFWD;\r
476           threadStart();\r
477           return;\r
478         }\r
479         case S_FBWD: // to S_FBWD\r
480         {\r
481           currentFrameNumber = getCurrentFrameNum();\r
482           audio->systemMuteOn();\r
483           threadStop();\r
484           vfeed.stop();\r
485           afeed.stop();\r
486           tfeed.stop();\r
487           subtitles->stop();\r
488           demuxer->flush();\r
489           state = S_FBWD;\r
490           threadStart();\r
491           return;\r
492         }\r
493         case S_STOP: // to S_STOP\r
494         {\r
495           vfeed.stop();\r
496           afeed.stop();\r
497           tfeed.stop();\r
498           subtitles->stop();\r
499           threadStop();\r
500           video->stop();\r
501           video->blank();\r
502           audio->stop();\r
503           audio->unPause();\r
504           video->reset();\r
505           demuxer->reset();\r
506           state = S_STOP;\r
507           return;\r
508         }\r
509         case S_JUMP: // to S_JUMP\r
510         {\r
511           restartAtFrame(jumpFrame);\r
512           return;\r
513         }\r
514         case S_JUMP_PI: // to S_JUMP_PI\r
515         {\r
516           audio->systemMuteOn();\r
517           threadStop();\r
518           vfeed.stop();\r
519           afeed.stop();\r
520           tfeed.stop();\r
521           subtitles->stop();\r
522           demuxer->flush();\r
523           state = S_PAUSE_I;\r
524           video->reset();\r
525           video->play();\r
526           restartAtFramePI(jumpFrame);\r
527           return;\r
528         }\r
529       }\r
530     }\r
531     case S_PAUSE_P: // from S_PAUSE_P -----------------------------------\r
532     {\r
533       switch(toState)\r
534       {\r
535         case S_PLAY: // to S_PLAY\r
536         {\r
537           video->unPause();\r
538           audio->unPause();\r
539           state = S_PLAY;\r
540           return;\r
541         }\r
542         case S_PAUSE_P: // to S_PAUSE_P\r
543         {\r
544           return;\r
545         }\r
546         case S_PAUSE_I: // to S_PAUSE_I\r
547         {\r
548           return;\r
549         }\r
550         case S_FFWD: // to S_FFWD\r
551         {\r
552           currentFrameNumber = getCurrentFrameNum();\r
553           audio->systemMuteOn();\r
554           vfeed.stop();\r
555           afeed.stop();\r
556           tfeed.stop();\r
557           subtitles->stop();\r
558           threadStop();\r
559           video->unPause();\r
560           audio->unPause();\r
561           state = S_FFWD;\r
562           threadStart();\r
563           return;\r
564         }\r
565         case S_FBWD: // to S_FBWD\r
566         {\r
567           currentFrameNumber = getCurrentFrameNum();\r
568           audio->systemMuteOn();\r
569           vfeed.stop();\r
570           afeed.stop();\r
571           tfeed.stop();\r
572           subtitles->stop();\r
573           threadStop();\r
574           video->unPause();\r
575           audio->unPause();\r
576           state = S_FBWD;\r
577           threadStart();\r
578           return;\r
579         }\r
580         case S_STOP: // to S_STOP\r
581         {\r
582           vfeed.stop();\r
583           afeed.stop();\r
584           tfeed.stop();\r
585           subtitles->stop();\r
586           threadStop();\r
587           video->stop();\r
588           video->blank();\r
589           audio->stop();\r
590           video->reset();\r
591           audio->unPause();\r
592           demuxer->reset();\r
593           audio->systemMuteOff();\r
594           state = S_STOP;\r
595           return;\r
596         }\r
597         case S_JUMP: // to S_JUMP\r
598         {\r
599           state = S_PLAY;\r
600           audio->systemMuteOn();\r
601           audio->unPause();\r
602           restartAtFrame(jumpFrame);\r
603           return;\r
604         }\r
605         case S_JUMP_PI: // to S_JUMP_PI\r
606         {\r
607           audio->systemMuteOn();\r
608           audio->unPause();\r
609           threadStop();\r
610           vfeed.stop();\r
611           afeed.stop();\r
612           tfeed.stop();\r
613           subtitles->stop();\r
614           demuxer->flush();\r
615           state = S_PAUSE_I;\r
616           video->reset();\r
617           video->play();\r
618           restartAtFramePI(jumpFrame);\r
619           return;\r
620         }\r
621       }\r
622     }\r
623     case S_PAUSE_I: // from S_PAUSE_I -----------------------------------\r
624     {\r
625       switch(toState)\r
626       {\r
627         case S_PLAY: // to S_PLAY\r
628         {\r
629           state = S_PLAY;\r
630           restartAtFrame(currentFrameNumber);\r
631           return;\r
632         }\r
633         case S_PAUSE_P: // to S_PAUSE_P\r
634         {\r
635           return;\r
636         }\r
637         case S_PAUSE_I: // to S_PAUSE_I\r
638         {\r
639           return;\r
640         }\r
641         case S_FFWD: // to S_FFWD\r
642         {\r
643           state = S_FFWD;\r
644           threadStart();\r
645           return;\r
646         }\r
647         case S_FBWD: // to S_FBWD\r
648         {\r
649           state = S_FBWD;\r
650           threadStart();\r
651           return;\r
652         }\r
653         case S_STOP: // to S_STOP\r
654         {\r
655           video->stop();\r
656           video->blank();\r
657           audio->stop();\r
658           video->reset();\r
659           demuxer->reset();\r
660           audio->systemMuteOff();\r
661           state = S_STOP;\r
662           return;\r
663         }\r
664         case S_JUMP: // to S_JUMP\r
665         {\r
666           state = S_PLAY;\r
667           restartAtFrame(jumpFrame);\r
668           return;\r
669         }\r
670         case S_JUMP_PI: // to S_JUMP_PI\r
671         {\r
672           restartAtFramePI(jumpFrame);\r
673           return;\r
674         }\r
675       }\r
676     }\r
677     case S_FFWD: // from S_FFWD -----------------------------------\r
678     {\r
679       switch(toState)\r
680       {\r
681         case S_PLAY: // to S_PLAY\r
682         {\r
683           state = S_PLAY;\r
684           ULONG stepback = (ULONG)(((double)USER_RESPONSE_TIME * ifactor) * fps / 1000.);\r
685           if (stepback < currentFrameNumber)\r
686             currentFrameNumber -= stepback;\r
687           else\r
688             currentFrameNumber = 0;\r
689           restartAtFrame(currentFrameNumber);\r
690           return;\r
691         }\r
692         case S_PAUSE_P: // to S_PAUSE_P\r
693         {\r
694           // can't occur\r
695           return;\r
696         }\r
697         case S_PAUSE_I: // to S_PAUSE_I\r
698         {\r
699           threadStop();\r
700           state = S_PAUSE_I;\r
701           return;\r
702         }\r
703         case S_FFWD: // to S_FFWD\r
704         {\r
705           return;\r
706         }\r
707         case S_FBWD: // to S_FBWD\r
708         {\r
709           threadStop();\r
710           state = S_FBWD;\r
711           threadStart();\r
712           return;\r
713         }\r
714         case S_STOP: // to S_STOP\r
715         {\r
716           threadStop();\r
717           video->stop();\r
718           video->blank();\r
719           audio->stop();\r
720           video->reset();\r
721           demuxer->reset();\r
722           state = S_STOP;\r
723           return;\r
724         }\r
725         case S_JUMP: // to S_JUMP\r
726         {\r
727           state = S_PLAY;\r
728           restartAtFrame(jumpFrame);\r
729           return;\r
730         }\r
731         case S_JUMP_PI: // to S_JUMP_PI\r
732         {\r
733           threadStop();\r
734           state = S_PAUSE_I;\r
735           restartAtFramePI(jumpFrame);\r
736           return;\r
737         }\r
738       }\r
739     }\r
740     case S_FBWD: // from S_FBWD -----------------------------------\r
741     {\r
742       switch(toState)\r
743       {\r
744         case S_PLAY: // to S_PLAY\r
745         {\r
746           state = S_PLAY;\r
747           restartAtFrame(currentFrameNumber);\r
748           return;\r
749         }\r
750         case S_PAUSE_P: // to S_PAUSE_P\r
751         {\r
752           // can't occur\r
753           return;\r
754         }\r
755         case S_PAUSE_I: // to S_PAUSE_I\r
756         {\r
757           threadStop();\r
758           state = S_PAUSE_I;\r
759           return;\r
760         }\r
761         case S_FFWD: // to S_FFWD\r
762         {\r
763           threadStop();\r
764           state = S_FFWD;\r
765           threadStart();\r
766           return;\r
767         }\r
768         case S_FBWD: // to S_FBWD\r
769         {\r
770           return;\r
771         }\r
772         case S_STOP: // to S_STOP\r
773         {\r
774           threadStop();\r
775           video->stop();\r
776           video->blank();\r
777           audio->stop();\r
778           video->reset();\r
779           demuxer->reset();\r
780           state = S_STOP;\r
781           return;\r
782         }\r
783         case S_JUMP: // to S_JUMP\r
784         {\r
785           state = S_PLAY;\r
786           restartAtFrame(jumpFrame);\r
787           return;\r
788         }\r
789         case S_JUMP_PI: // to S_JUMP_PI\r
790         {\r
791           threadStop();\r
792           state = S_PAUSE_I;\r
793           restartAtFramePI(jumpFrame);\r
794           return;\r
795         }\r
796       }\r
797     }\r
798     case S_STOP: // from S_STOP -----------------------------------\r
799     {\r
800       switch(toState)\r
801       {\r
802         case S_PLAY: // to S_PLAY\r
803         {\r
804           startup = true;\r
805 \r
806           audio->reset();\r
807           audio->setStreamType(Audio::MPEG2_PES);\r
808           audio->systemMuteOff();\r
809           video->reset();\r
810           demuxer->reset();\r
811           // FIXME use restartAtFrame here?\r
812           if (currentFrameNumber > lengthFrames) currentFrameNumber = 0;\r
813           demuxer->setFrameNum(currentFrameNumber);\r
814           demuxer->seek();\r
815           videoStartup = true;\r
816           state = S_PLAY;\r
817           threadStart();\r
818           logger->log("Player", Log::DEBUG, "Immediate play");\r
819           afeed.start();\r
820           vfeed.start();\r
821           tfeed.start();\r
822           subtitles->start();\r
823           video->sync();\r
824           audio->sync();\r
825           audio->play();\r
826           video->pause();\r
827           return;\r
828         }\r
829         case S_PAUSE_P: // to S_PAUSE_P\r
830         {\r
831           return;\r
832         }\r
833         case S_PAUSE_I: // to S_PAUSE_I\r
834         {\r
835           return;\r
836         }\r
837         case S_FFWD: // to S_FFWD\r
838         {\r
839           return;\r
840         }\r
841         case S_FBWD: // to S_FBWD\r
842         {\r
843           return;\r
844         }\r
845         case S_STOP: // to S_STOP\r
846         {\r
847           return;\r
848         }\r
849         case S_JUMP: // to S_JUMP\r
850         {\r
851           return;\r
852         }\r
853         case S_JUMP_PI: // to S_JUMP_PI\r
854         {\r
855           return;\r
856         }\r
857       }\r
858     }\r
859     // case S_JUMP cannot be a start state because it auto flips to play\r
860     // case S_JUMP_PI cannot be a start state because it auto flips to S_PAUSE_I\r
861   }\r
862 }\r
863 \r
864 // ----------------------------------- Internal functions\r
865 \r
866 void Player::lock()\r
867 {\r
868 #ifndef WIN32\r
869   pthread_mutex_lock(&mutex);\r
870   logger->log("Player", Log::DEBUG, "LOCKED");\r
871 \r
872 #else\r
873    WaitForSingleObject(mutex, INFINITE);\r
874 #endif\r
875 }\r
876 \r
877 void Player::unLock()\r
878 {\r
879 #ifndef WIN32\r
880   logger->log("Player", Log::DEBUG, "UNLOCKING");\r
881   pthread_mutex_unlock(&mutex);\r
882 #else\r
883    ReleaseMutex(mutex);\r
884 #endif\r
885 }\r
886 \r
887 void Player::restartAtFrame(ULONG newFrame)\r
888 {\r
889   vfeed.stop();\r
890   afeed.stop();\r
891   tfeed.stop();\r
892   subtitles->stop();\r
893   threadStop();\r
894   video->stop();\r
895   video->reset();\r
896   audio->reset();\r
897   audio->setStreamType(Audio::MPEG2_PES);\r
898   demuxer->flush();\r
899   demuxer->seek();\r
900   currentFrameNumber = newFrame;\r
901   demuxer->setFrameNum(newFrame);\r
902   videoStartup = true;\r
903   afeed.start();\r
904   tfeed.start();\r
905   vfeed.start();\r
906   subtitles->start();\r
907   threadStart();\r
908   audio->play();\r
909   video->sync();\r
910   audio->sync();\r
911   audio->systemMuteOff();\r
912   audio->doMuting();\r
913 }\r
914 \r
915 \r
916 void Player::restartAtFramePI(ULONG newFrame)\r
917 {\r
918   ULLONG filePos;\r
919   ULONG nextiframeNumber;\r
920   ULONG iframeLength;\r
921   ULONG iframeNumber;\r
922 \r
923   UCHAR* buffer;\r
924   UINT amountReceived;\r
925   UINT videoLength;\r
926 \r
927   // newFrame could be anywhere, go forwards to next I-Frame\r
928   if (!vdr->getNextIFrame(newFrame, 1, &filePos, &nextiframeNumber, &iframeLength)) return;\r
929 \r
930   // Now step back a GOP. This ensures we go to the greatest I-Frame equal to or less than the requested frame\r
931   vdr->getNextIFrame(nextiframeNumber, 0, &filePos, &iframeNumber, &iframeLength);\r
932 \r
933   buffer = vdr->getBlock(filePos, iframeLength, &amountReceived);\r
934   if (!vdr->isConnected())\r
935   {\r
936     if (buffer) free(buffer);\r
937     doConnectionLost();\r
938   }\r
939   else\r
940   {\r
941     videoLength = demuxer->stripAudio(buffer, amountReceived);\r
942     video->displayIFrame(buffer, videoLength);\r
943     video->displayIFrame(buffer, videoLength); // If you do it twice, it works :)\r
944     free(buffer);\r
945     currentFrameNumber = iframeNumber;\r
946   }\r
947 }\r
948 \r
949 void Player::doConnectionLost()\r
950 {\r
951   logger->log("Player", Log::DEBUG, "Connection lost, sending message");\r
952   Message* m = new Message();\r
953   m->to = messageReceiver;\r
954   m->from = this;\r
955   m->message = Message::PLAYER_EVENT;\r
956   m->parameter = Player::CONNECTION_LOST;\r
957   messageQueue->postMessage(m);\r
958 }\r
959 \r
960 // ----------------------------------- Callback\r
961 \r
962 void Player::call(void* caller)\r
963 {\r
964   if (caller == demuxer)\r
965   {\r
966     logger->log("Player", Log::DEBUG, "Callback from demuxer");\r
967 \r
968     if (video->getTVsize() == Video::ASPECT4X3)\r
969     {\r
970       logger->log("Player", Log::DEBUG, "TV is 4:3, ignoring aspect switching");\r
971       return;\r
972     }\r
973 \r
974     int dxCurrentAspect = demuxer->getAspectRatio();\r
975     if (dxCurrentAspect == Demuxer::ASPECT_4_3)\r
976     {\r
977       logger->log("Player", Log::DEBUG, "Demuxer said video is 4:3 aspect, switching TV");\r
978       video->setAspectRatio(Video::ASPECT4X3);\r
979 \r
980       Message* m = new Message();\r
981       m->from = this;\r
982       m->to = messageReceiver;\r
983       m->message = Message::PLAYER_EVENT;\r
984       m->parameter = Player::ASPECT43;\r
985       messageQueue->postMessageFromOuterSpace(m);\r
986     }\r
987     else if (dxCurrentAspect == Demuxer::ASPECT_16_9)\r
988     {\r
989       logger->log("Player", Log::DEBUG, "Demuxer said video is 16:9 aspect, switching TV");\r
990       video->setAspectRatio(Video::ASPECT16X9);\r
991 \r
992       Message* m = new Message();\r
993       m->from = this;\r
994       m->to = messageReceiver;\r
995       m->message = Message::PLAYER_EVENT;\r
996       m->parameter = Player::ASPECT169;\r
997       messageQueue->postMessageFromOuterSpace(m);\r
998     }\r
999     else\r
1000     {\r
1001       logger->log("Player", Log::DEBUG, "Demuxer said video is something else... ignoring");\r
1002     }\r
1003 \r
1004   }\r
1005   else\r
1006   {\r
1007     if (videoStartup)\r
1008     {\r
1009       videoStartup = false;\r
1010       video->reset();\r
1011       video->play();\r
1012       video->sync();\r
1013       vfeed.release();\r
1014       unLock();\r
1015     }\r
1016 \r
1017     threadSignalNoLock();\r
1018   }\r
1019 }\r
1020 \r
1021 // ----------------------------------- Feed thread\r
1022 \r
1023 void Player::threadMethod()\r
1024 {\r
1025   // this method used to be simple, the only thing it does\r
1026   // is farm out to threadFeed Live/Play/Scan\r
1027   // All the guff is to support scan hitting one end\r
1028 \r
1029   if ((state == S_FFWD) || (state == S_FBWD))\r
1030   {\r
1031     if (video->PTSIFramePlayback()) threadPTSFeedScan();\r
1032     else threadFeedScan();\r
1033     // if this returns then scan hit one end\r
1034     if (state == S_FFWD) // scan hit the end. stop\r
1035     {\r
1036       threadCheckExit();\r
1037       Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex\r
1038       m->to = messageReceiver;\r
1039       m->from = this;\r
1040       m->message = Message::PLAYER_EVENT;\r
1041       m->parameter = STOP_PLAYBACK;\r
1042       logger->log("Player", Log::DEBUG, "Posting message to %p...", messageQueue);\r
1043       messageQueue->postMessage(m);\r
1044       logger->log("Player", Log::DEBUG, "Message posted...");\r
1045       return;\r
1046     }\r
1047     // if execution gets to here, threadFeedScan hit the start, go to play mode\r
1048     state = S_PLAY;\r
1049     audio->reset();\r
1050     audio->setStreamType(Audio::MPEG2_PES);\r
1051     demuxer->flush();\r
1052     demuxer->seek();\r
1053     demuxer->setFrameNum(currentFrameNumber);\r
1054     videoStartup = true;\r
1055     afeed.start();\r
1056     tfeed.start();\r
1057     vfeed.start();\r
1058     subtitles->start();\r
1059     audio->play();\r
1060     audio->sync();\r
1061     audio->systemMuteOff();\r
1062     audio->doMuting();\r
1063   }\r
1064 \r
1065   if (state == S_PLAY) threadFeedPlay();\r
1066 }\r
1067 \r
1068 void Player::threadFeedPlay()\r
1069 {\r
1070   ULLONG feedPosition;\r
1071   UINT thisRead, writeLength, thisWrite, askFor;\r
1072   time_t lastRescan = time(NULL);\r
1073 \r
1074   feedPosition = vdr->positionFromFrameNumber(currentFrameNumber);\r
1075   if (!vdr->isConnected()) { doConnectionLost(); return; }\r
1076   logger->log("Player", Log::DEBUG, "startFeedPlay: wantedframe %i goto %llu", currentFrameNumber, feedPosition);\r
1077 \r
1078 \r
1079   while(1)\r
1080   {\r
1081     thisRead = 0;\r
1082     writeLength = 0;\r
1083     thisWrite = 0;\r
1084 \r
1085     threadCheckExit();\r
1086 \r
1087     // If we havn't rescanned for a while..\r
1088     if ((lastRescan + 60) < time(NULL))\r
1089     {\r
1090       lengthBytes = vdr->rescanRecording(&lengthFrames);\r
1091       if (!vdr->isConnected()) { doConnectionLost(); return; }\r
1092       logger->log("Player", Log::DEBUG, "Rescanned and reset length: %llu", lengthBytes);\r
1093       lastRescan = time(NULL);\r
1094     }\r
1095 \r
1096     if (feedPosition >= lengthBytes) break;  // finished playback\r
1097 \r
1098     if (startup)\r
1099     {\r
1100       if (startupBlockSize > lengthBytes)\r
1101         askFor = lengthBytes; // is a very small recording!\r
1102       else\r
1103         askFor = startupBlockSize; // normal, but a startup sized block to detect all the audio streams\r
1104     }\r
1105     else\r
1106     {\r
1107       if ((feedPosition + blockSize) > lengthBytes) // last block of recording\r
1108         askFor = lengthBytes - feedPosition;\r
1109       else // normal\r
1110         askFor = blockSize;\r
1111     }\r
1112     //logger->log("Player", Log::DEBUG, "Get Block in");\r
1113 \r
1114     threadBuffer = vdr->getBlock(feedPosition, askFor, &thisRead);\r
1115     //logger->log("Player", Log::DEBUG, "Get Block out");\r
1116 \r
1117     feedPosition += thisRead;\r
1118 \r
1119     if (!vdr->isConnected())\r
1120     {\r
1121       doConnectionLost();\r
1122       return;\r
1123     }\r
1124 \r
1125     if (!threadBuffer) break;\r
1126 \r
1127     if (startup)\r
1128     {\r
1129       int a_stream = demuxer->scan(threadBuffer, thisRead);\r
1130       demuxer->setAudioStream(a_stream);\r
1131       logger->log("Player", Log::DEBUG, "Startup Audio stream chosen %x", a_stream);\r
1132       startup = false;\r
1133     }\r
1134 \r
1135     threadCheckExit();\r
1136 \r
1137     while(writeLength < thisRead)\r
1138     {\r
1139       //logger->log("Player", Log::DEBUG, "Put in");\r
1140       thisWrite = demuxer->put(threadBuffer + writeLength, thisRead - writeLength);\r
1141       //logger->log("Player", Log::DEBUG, "Put out");\r
1142       writeLength += thisWrite;\r
1143 \r
1144       if (!thisWrite)\r
1145       {\r
1146         // demuxer is full and can't take anymore\r
1147         threadLock();\r
1148         threadWaitForSignal();\r
1149         threadUnlock();\r
1150       }\r
1151 \r
1152       threadCheckExit();\r
1153     }\r
1154 \r
1155     free(threadBuffer);\r
1156     threadBuffer = NULL;\r
1157 \r
1158   }\r
1159 \r
1160   // end of recording\r
1161   logger->log("Player", Log::DEBUG, "Recording playback ends");\r
1162 \r
1163   if (videoStartup) // oh woe. there never was a stream, I was conned!\r
1164   {\r
1165     videoStartup = false;\r
1166     unLock();\r
1167     MILLISLEEP(500); // I think this will solve a race\r
1168   }\r
1169 \r
1170   threadCheckExit();\r
1171 \r
1172 \r
1173   Message* m = new Message(); // Must be done after this thread finishes, and must break into master mutex\r
1174   m->to = messageReceiver;\r
1175   m->from = this;\r
1176   m->message = Message::PLAYER_EVENT;\r
1177   m->parameter = Player::STOP_PLAYBACK;\r
1178   logger->log("Player", Log::DEBUG, "Posting message to %p...", messageQueue);\r
1179   messageQueue->postMessage(m);\r
1180 }\r
1181 \r
1182 \r
1183 void Player::threadPTSFeedScan()\r
1184 {\r
1185   // This method manipulates the PTS instead of waiting, this is for the android devices, maybe later for windows?\r
1186 \r
1187   ULONG direction = 0;\r
1188   int dir_fac=-1;\r
1189   ULONG baseFrameNumber = 0;\r
1190   ULONG iframeNumber = 0;\r
1191   ULONG iframeLength = 0;\r
1192   ULONG currentfeedFrameNumber=currentFrameNumber;\r
1193   ULONG firstFrameNumber=currentFrameNumber;\r
1194   ULLONG filePos;\r
1195   UINT amountReceived;\r
1196   UINT videoLength;\r
1197 \r
1198   UINT playtime=0;\r
1199 \r
1200 #ifndef WIN32\r
1201   struct timeval clock0 = {0,0};  // Time stamp after fetching I-frame info\r
1202   struct timeval clock1 = {0,0};  // Time stamp after fetching I-frame data\r
1203   struct timeval clock2 = {0,0} ; // Time stamp after displaying I-frame\r
1204 #else\r
1205   DWORD clock0 = 0, clock1 = 0, clock2 = 0;\r
1206 #endif\r
1207 \r
1208   int frameTimeOffset = 0; // Time in msec between frames\r
1209   int disp_msec = 0;  // Time taken to display data\r
1210   int total_msec = 0; // Time taken to fetch data and display it\r
1211   int sleepTime = 0;\r
1212 \r
1213   if (state == S_FFWD) {\r
1214           direction = 1; // and 0 for backward\r
1215           dir_fac=1;\r
1216   }\r
1217   video->EnterIframePlayback();\r
1218 \r
1219   while(1)\r
1220   {\r
1221     // Fetch I-frames until we get one that can be displayed in good time\r
1222     // Repeat while clock0 + total_msec > clock2 + frameTimeOffset\r
1223 \r
1224     baseFrameNumber = currentfeedFrameNumber;\r
1225 \r
1226     threadCheckExit();\r
1227     if (!vdr->getNextIFrame(baseFrameNumber, direction, &filePos, &iframeNumber, &iframeLength))\r
1228        return;\r
1229 \r
1230     if (iframeNumber >= lengthFrames) return;\r
1231         // scan has got to the end of what we knew to be there before we started scanning\r
1232 \r
1233     baseFrameNumber = iframeNumber;\r
1234 \r
1235     frameTimeOffset =(int) ((double)(abs((int)iframeNumber - (int)currentfeedFrameNumber) * 1000) / (fps * (double)ifactor));\r
1236 \r
1237     logger->log("Player", Log::DEBUG, "XXX Got frame");\r
1238 \r
1239     threadBuffer = vdr->getBlock(filePos, iframeLength, &amountReceived);\r
1240 \r
1241     if (!vdr->isConnected())\r
1242     {\r
1243       if (threadBuffer) free(threadBuffer);\r
1244       doConnectionLost();\r
1245       break;\r
1246     }\r
1247 \r
1248 \r
1249     threadCheckExit();\r
1250 \r
1251 \r
1252     videoLength = demuxer->stripAudio(threadBuffer, amountReceived);\r
1253     demuxer->changeTimes(threadBuffer,videoLength,playtime);\r
1254     int count=0;\r
1255     while (!video->displayIFrame(threadBuffer, videoLength)) // the device might block\r
1256     {\r
1257         MILLISLEEP(20);\r
1258         threadCheckExit();\r
1259         count++;\r
1260         if (count%300==0) {\r
1261                 ULLONG cur_time=video->getCurrentTimestamp();\r
1262         //       logger->log("Player", Log::ERR, "FFN: %d CFN: %d dirfac %d time %lld ifac %d fps %g",\r
1263                 //              firstFrameNumber, currentFrameNumber,dir_fac,cur_time,ifactor,fps);\r
1264                 if (cur_time!=0) {\r
1265                     currentFrameNumber=firstFrameNumber+(int)(((double)(dir_fac*((long long)cur_time)*((int)ifactor))*fps/90000.));\r
1266                 }\r
1267         }\r
1268 \r
1269     }\r
1270     playtime +=frameTimeOffset;\r
1271     currentfeedFrameNumber = iframeNumber;\r
1272     {\r
1273                 ULLONG cur_time=video->getCurrentTimestamp();\r
1274         //       logger->log("Player", Log::ERR, "FFN: %d CFN: %d dirfac %d time %lld ifac %d fps %g",\r
1275                 //              firstFrameNumber, currentFrameNumber,dir_fac,cur_time,ifactor,fps);\r
1276                 if (cur_time!=0) {\r
1277                     currentFrameNumber=firstFrameNumber+(int)(((double)(dir_fac*((long long)cur_time)*((int)ifactor))*fps/90000.));\r
1278                 }\r
1279         }\r
1280 \r
1281     free(threadBuffer);\r
1282     threadBuffer = NULL;\r
1283   }\r
1284 }\r
1285 \r
1286 \r
1287 \r
1288 void Player::threadFeedScan()\r
1289 {\r
1290   // This method is actually really simple - get frame from vdr,\r
1291   // spit it at the video chip, wait for a time. Most of the code here\r
1292   // is to get the wait right so that the scan occurs at the correct rate.\r
1293 \r
1294   ULONG direction = 0;\r
1295   ULONG baseFrameNumber = 0;\r
1296   ULONG iframeNumber = 0;\r
1297   ULONG iframeLength = 0;\r
1298   ULLONG filePos;\r
1299   UINT amountReceived;\r
1300   UINT videoLength;\r
1301 \r
1302 #ifndef WIN32\r
1303   struct timeval clock0 = {0,0};  // Time stamp after fetching I-frame info\r
1304   struct timeval clock1 = {0,0};  // Time stamp after fetching I-frame data\r
1305   struct timeval clock2 = {0,0} ; // Time stamp after displaying I-frame\r
1306 #else\r
1307   DWORD clock0 = 0, clock1 = 0, clock2 = 0;\r
1308 #endif\r
1309 \r
1310   int frameTimeOffset = 0; // Time in msec between frames\r
1311   int disp_msec = 0;  // Time taken to display data\r
1312   int total_msec = 0; // Time taken to fetch data and display it\r
1313   int sleepTime = 0;\r
1314 \r
1315   if (state == S_FFWD) direction = 1; // and 0 for backward\r
1316 \r
1317   while(1)\r
1318   {\r
1319     // Fetch I-frames until we get one that can be displayed in good time\r
1320     // Repeat while clock0 + total_msec > clock2 + frameTimeOffset\r
1321 \r
1322     baseFrameNumber = currentFrameNumber;\r
1323     do\r
1324     {\r
1325       threadCheckExit();\r
1326       if (!vdr->getNextIFrame(baseFrameNumber, direction, &filePos, &iframeNumber, &iframeLength))\r
1327         return;\r
1328 \r
1329       if (iframeNumber >= lengthFrames) return;\r
1330         // scan has got to the end of what we knew to be there before we started scanning\r
1331 \r
1332       baseFrameNumber = iframeNumber;\r
1333       frameTimeOffset =(int) ((double)(abs((int)iframeNumber - (int)currentFrameNumber) * 1000) / (fps * (double)ifactor));\r
1334 #ifndef WIN32\r
1335       gettimeofday(&clock0, NULL);\r
1336 #else\r
1337       clock0 = timeGetTime();\r
1338 #endif\r
1339     }\r
1340 #ifndef WIN32\r
1341     while (clock2.tv_sec != 0 &&\r
1342           (clock0.tv_sec - clock2.tv_sec) * 1000 +\r
1343           (clock0.tv_usec - clock2.tv_usec) / 1000 > frameTimeOffset - total_msec);\r
1344 #else\r
1345     while (clock2 != 0 && clock0 + total_msec > clock2 + frameTimeOffset);\r
1346 #endif\r
1347     logger->log("Player", Log::DEBUG, "XXX Got frame");\r
1348 \r
1349     threadBuffer = vdr->getBlock(filePos, iframeLength, &amountReceived);\r
1350     \r
1351     if (!vdr->isConnected())\r
1352     {\r
1353       if (threadBuffer) free(threadBuffer);\r
1354       doConnectionLost();\r
1355       break;\r
1356     }\r
1357     \r
1358 #ifndef WIN32\r
1359     gettimeofday(&clock1, NULL);\r
1360     if (clock2.tv_sec != 0)\r
1361       sleepTime = (clock2.tv_sec - clock1.tv_sec) * 1000\r
1362                 + (clock2.tv_usec - clock1.tv_usec) / 1000\r
1363                 + frameTimeOffset - disp_msec;\r
1364 #else\r
1365     clock1 = timeGetTime();\r
1366     if (clock2 != 0)\r
1367       sleepTime = clock2 + frameTimeOffset - disp_msec - clock1;\r
1368 #endif\r
1369     if (sleepTime < 0) sleepTime = 0;\r
1370     threadCheckExit();\r
1371     MILLISLEEP(sleepTime);\r
1372    logger->log("Player", Log::DEBUG, "XXX Slept for %d", sleepTime);\r
1373 \r
1374     videoLength = demuxer->stripAudio(threadBuffer, amountReceived);\r
1375     video->displayIFrame(threadBuffer, videoLength);\r
1376     currentFrameNumber = iframeNumber;\r
1377     free(threadBuffer);\r
1378     threadBuffer = NULL;\r
1379 \r
1380 #ifndef WIN32\r
1381     gettimeofday(&clock2, NULL);\r
1382     total_msec = (clock2.tv_sec - clock0.tv_sec) * 1000\r
1383                + (clock2.tv_usec - clock0.tv_usec) / 1000\r
1384                - sleepTime;\r
1385     disp_msec = (clock2.tv_sec - clock1.tv_sec) * 1000\r
1386               + (clock2.tv_usec - clock1.tv_usec) / 1000\r
1387               - sleepTime;\r
1388 #else\r
1389     clock2 = timeGetTime();\r
1390     total_msec = clock2 - clock0 - sleepTime;\r
1391     disp_msec = clock2 - clock1 - sleepTime;\r
1392 #endif\r
1393     logger->log("Player", Log::DEBUG, "XXX disp_msec = %4d total_msec = %4d", disp_msec, total_msec);\r
1394   }\r
1395 }\r
1396 \r
1397 void Player::threadPostStopCleanup()\r
1398 {\r
1399   if (threadBuffer)\r
1400   {\r
1401     free(threadBuffer);\r
1402     threadBuffer = NULL;\r
1403   }\r
1404 }\r
1405 \r
1406 // ----------------------------------- Dev\r
1407 \r
1408 #ifdef DEV\r
1409 void Player::test1()\r
1410 {\r
1411   logger->log("Player", Log::DEBUG, "PLAYER TEST 1");\r
1412 }\r
1413 \r
1414 void Player::test2()\r
1415 {\r
1416   logger->log("Player", Log::DEBUG, "PLAYER TEST 2");\r
1417 }\r
1418 #endif\r