diff options
Diffstat (limited to 'apps/plugins/video.c')
-rw-r--r-- | apps/plugins/video.c | 1030 |
1 files changed, 0 insertions, 1030 deletions
diff --git a/apps/plugins/video.c b/apps/plugins/video.c deleted file mode 100644 index 6a66324a79..0000000000 --- a/apps/plugins/video.c +++ /dev/null | |||
@@ -1,1030 +0,0 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Plugin for video playback | ||
11 | * Reads raw image data + audio data from a file | ||
12 | * !!!!!!!!!! Code Police free zone !!!!!!!!!! | ||
13 | * | ||
14 | * Copyright (C) 2003-2004 J�g Hohensohn aka [IDC]Dragon | ||
15 | * | ||
16 | * This program is free software; you can redistribute it and/or | ||
17 | * modify it under the terms of the GNU General Public License | ||
18 | * as published by the Free Software Foundation; either version 2 | ||
19 | * of the License, or (at your option) any later version. | ||
20 | * | ||
21 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
22 | * KIND, either express or implied. | ||
23 | * | ||
24 | ****************************************************************************/ | ||
25 | |||
26 | |||
27 | /****************** imports ******************/ | ||
28 | |||
29 | #include "plugin.h" | ||
30 | #include "sh7034.h" | ||
31 | #include "system.h" | ||
32 | #include "lib/helper.h" | ||
33 | |||
34 | #ifdef HAVE_LCD_BITMAP /* and definitely not for the Player, haha */ | ||
35 | |||
36 | |||
37 | |||
38 | /* variable button definitions */ | ||
39 | #if CONFIG_KEYPAD == RECORDER_PAD | ||
40 | #define VIDEO_STOP_SEEK BUTTON_PLAY | ||
41 | #define VIDEO_RESUME BUTTON_PLAY | ||
42 | #define VIDEO_DEBUG BUTTON_F1 | ||
43 | #define VIDEO_CONTRAST_DOWN BUTTON_F2 | ||
44 | #define VIDEO_CONTRAST_UP BUTTON_F3 | ||
45 | |||
46 | #elif CONFIG_KEYPAD == ONDIO_PAD | ||
47 | #define VIDEO_STOP_SEEK_PRE BUTTON_MENU | ||
48 | #define VIDEO_STOP_SEEK (BUTTON_MENU | BUTTON_REL) | ||
49 | #define VIDEO_RESUME BUTTON_RIGHT | ||
50 | #define VIDEO_CONTRAST_DOWN (BUTTON_MENU | BUTTON_DOWN) | ||
51 | #define VIDEO_CONTRAST_UP (BUTTON_MENU | BUTTON_UP) | ||
52 | |||
53 | #endif | ||
54 | /****************** constants ******************/ | ||
55 | |||
56 | #define SCREENSIZE (LCD_WIDTH*LCD_HEIGHT/8) /* in bytes */ | ||
57 | #define FPS 68 /* default fps for headerless (old video-only) file */ | ||
58 | #define MAX_ACC 20 /* maximum FF/FR speedup */ | ||
59 | #define FF_TICKS 3000; /* experimentally found nice */ | ||
60 | |||
61 | /* trigger levels, we need about 80 kB/sec */ | ||
62 | #define SPINUP_INIT 5000 /* from what level on to refill, in milliseconds */ | ||
63 | #define SPINUP_SAFETY 700 /* how much on top of the measured spinup time */ | ||
64 | #define CHUNK (1024*32) /* read size */ | ||
65 | |||
66 | |||
67 | /****************** prototypes ******************/ | ||
68 | static void timer4_isr(void); /* IMIA4 ISR */ | ||
69 | int check_button(void); /* determine next relative frame */ | ||
70 | |||
71 | |||
72 | /****************** data types ******************/ | ||
73 | |||
74 | /* plugins don't introduce headers, so structs are repeated from rvf_format.h */ | ||
75 | |||
76 | #define HEADER_MAGIC 0x52564668 /* "RVFh" at file start */ | ||
77 | #define AUDIO_MAGIC 0x41756446 /* "AudF" for each audio block */ | ||
78 | #define FILEVERSION 100 /* 1.00 */ | ||
79 | |||
80 | /* format type definitions */ | ||
81 | #define VIDEOFORMAT_NO_VIDEO 0 | ||
82 | #define VIDEOFORMAT_RAW 1 | ||
83 | #define AUDIOFORMAT_NO_AUDIO 0 | ||
84 | #define AUDIOFORMAT_MP3 1 | ||
85 | #define AUDIOFORMAT_MP3_BITSWAPPED 2 | ||
86 | |||
87 | /* bit flags */ | ||
88 | #define FLAG_LOOP 0x00000001 /* loop the playback, e.g. for stills */ | ||
89 | |||
90 | typedef struct /* contains whatever might be useful to the player */ | ||
91 | { | ||
92 | /* general info (16 entries = 64 byte) */ | ||
93 | unsigned long magic; /* HEADER_MAGIC */ | ||
94 | unsigned long version; /* file version */ | ||
95 | unsigned long flags; /* combination of FLAG_xx */ | ||
96 | unsigned long blocksize; /* how many bytes per block (=video frame) */ | ||
97 | unsigned long bps_average; /* bits per second of the whole stream */ | ||
98 | unsigned long bps_peak; /* max. of above (audio may be VBR) */ | ||
99 | unsigned long resume_pos; /* file position to resume to */ | ||
100 | unsigned long reserved[9]; /* reserved, should be zero */ | ||
101 | |||
102 | /* video info (16 entries = 64 byte) */ | ||
103 | unsigned long video_format; /* one of VIDEOFORMAT_xxx */ | ||
104 | unsigned long video_1st_frame; /* byte position of first video frame */ | ||
105 | unsigned long video_duration; /* total length of video part, in ms */ | ||
106 | unsigned long video_payload_size; /* total amount of video data, in bytes */ | ||
107 | unsigned long video_bitrate; /* derived from resolution and frame time, in bps */ | ||
108 | unsigned long video_frametime; /* frame interval in 11.0592 MHz clocks */ | ||
109 | long video_preroll; /* video is how much ahead, in 11.0592 MHz clocks */ | ||
110 | unsigned long video_width; /* in pixels */ | ||
111 | unsigned long video_height; /* in pixels */ | ||
112 | unsigned long video_reserved[7]; /* reserved, should be zero */ | ||
113 | |||
114 | /* audio info (16 entries = 64 byte) */ | ||
115 | unsigned long audio_format; /* one of AUDIOFORMAT_xxx */ | ||
116 | unsigned long audio_1st_frame; /* byte position of first video frame */ | ||
117 | unsigned long audio_duration; /* total length of audio part, in ms */ | ||
118 | unsigned long audio_payload_size; /* total amount of audio data, in bytes */ | ||
119 | unsigned long audio_avg_bitrate; /* average audio bitrate, in bits per second */ | ||
120 | unsigned long audio_peak_bitrate; /* maximum bitrate */ | ||
121 | unsigned long audio_headersize; /* offset to payload in audio frames */ | ||
122 | long audio_min_associated; /* minimum offset to video frame, in bytes */ | ||
123 | long audio_max_associated; /* maximum offset to video frame, in bytes */ | ||
124 | unsigned long audio_reserved[7]; /* reserved, should be zero */ | ||
125 | |||
126 | /* more to come... ? */ | ||
127 | |||
128 | /* Note: padding up to 'blocksize' with zero following this header */ | ||
129 | } tFileHeader; | ||
130 | |||
131 | typedef struct /* the little header for all audio blocks */ | ||
132 | { | ||
133 | unsigned long magic; /* AUDIO_MAGIC indicates an audio block */ | ||
134 | unsigned char previous_block; /* previous how many blocks backwards */ | ||
135 | unsigned char next_block; /* next how many blocks forward */ | ||
136 | short associated_video; /* offset to block with corresponding video */ | ||
137 | unsigned short frame_start; /* offset to first frame starting in this block */ | ||
138 | unsigned short frame_end; /* offset to behind last frame ending in this block */ | ||
139 | } tAudioFrameHeader; | ||
140 | |||
141 | |||
142 | |||
143 | /****************** globals ******************/ | ||
144 | |||
145 | static char gPrint[32]; /* a global printf buffer, saves stack */ | ||
146 | |||
147 | |||
148 | /* playstate */ | ||
149 | static struct | ||
150 | { | ||
151 | enum | ||
152 | { | ||
153 | paused, | ||
154 | playing, | ||
155 | } state; | ||
156 | bool bAudioUnderrun; | ||
157 | bool bVideoUnderrun; | ||
158 | bool bHasAudio; | ||
159 | bool bHasVideo; | ||
160 | int nTimeOSD; /* OSD should stay for this many frames */ | ||
161 | bool bDirtyOSD; /* OSD needs redraw */ | ||
162 | bool bRefilling; /* set if refilling buffer */ | ||
163 | bool bSeeking; | ||
164 | int nSeekAcc; /* accelleration value for seek */ | ||
165 | int nSeekPos; /* current file position for seek */ | ||
166 | bool bDiskSleep; /* disk is suspended */ | ||
167 | #if FREQ == 12000000 /* Ondio speed kludge */ | ||
168 | int nFrameTimeAdjusted; | ||
169 | #endif | ||
170 | } gPlay; | ||
171 | |||
172 | /* buffer information */ | ||
173 | static struct | ||
174 | { | ||
175 | ssize_t bufsize; | ||
176 | int granularity; /* common multiple of block and sector size */ | ||
177 | unsigned char* pBufStart; /* start of ring buffer */ | ||
178 | unsigned char* pBufEnd; /* end of ring buffer */ | ||
179 | int osd_ypos; | ||
180 | int osd_height; | ||
181 | |||
182 | int vidcount; /* how many video blocks are known in a row */ | ||
183 | unsigned char* pBufFill; /* write pointer for disk, owned by main task */ | ||
184 | unsigned char* pReadVideo; /* video readout, maintained by timer ISR */ | ||
185 | unsigned char* pReadAudio; /* audio readout, maintained by demand ISR */ | ||
186 | bool bEOF; /* flag for end of file */ | ||
187 | int low_water; /* reload threshold */ | ||
188 | int high_water; /* end of reload threshold */ | ||
189 | int spinup_safety; /* safety margin when recalculating low_water */ | ||
190 | int nReadChunk; /* how much data for normal buffer fill */ | ||
191 | int nSeekChunk; /* how much data while seeking */ | ||
192 | } gBuf; | ||
193 | |||
194 | /* statistics */ | ||
195 | static struct | ||
196 | { | ||
197 | int minAudioAvail; | ||
198 | int minVideoAvail; | ||
199 | int nAudioUnderruns; | ||
200 | int nVideoUnderruns; | ||
201 | long minSpinup; | ||
202 | long maxSpinup; | ||
203 | } gStats; | ||
204 | |||
205 | tFileHeader gFileHdr; /* file header */ | ||
206 | |||
207 | /****************** implementation ******************/ | ||
208 | |||
209 | /* tool function: return how much playable audio/video is left */ | ||
210 | static int Available(unsigned char* pSnapshot) | ||
211 | { | ||
212 | if (pSnapshot <= gBuf.pBufFill) | ||
213 | return gBuf.pBufFill - pSnapshot; | ||
214 | else | ||
215 | return gBuf.bufsize - (pSnapshot - gBuf.pBufFill); | ||
216 | } | ||
217 | |||
218 | /* debug function to draw buffer indicators */ | ||
219 | #ifdef VIDEO_DEBUG | ||
220 | static void DrawBuf(void) | ||
221 | { | ||
222 | int ypos, fill, video, audio; | ||
223 | |||
224 | rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); | ||
225 | rb->lcd_fillrect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height); | ||
226 | rb->lcd_set_drawmode(DRMODE_SOLID); | ||
227 | |||
228 | ypos = gBuf.osd_ypos + gBuf.osd_height/2 - 3; /* center vertically */ | ||
229 | |||
230 | rb->lcd_hline(1, LCD_WIDTH-2, ypos + 3); | ||
231 | rb->lcd_vline(0, ypos, ypos + 6); | ||
232 | rb->lcd_vline(LCD_WIDTH-1, ypos, ypos + 6); | ||
233 | |||
234 | /* calculate new tick positions */ | ||
235 | fill = 1 + ((gBuf.pBufFill - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize; | ||
236 | video = 1 + ((gBuf.pReadVideo - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize; | ||
237 | audio = 1 + ((gBuf.pReadAudio - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize; | ||
238 | |||
239 | rb->lcd_drawpixel(fill, ypos + 4); | ||
240 | rb->lcd_drawpixel(video, ypos + 2); | ||
241 | rb->lcd_drawpixel(audio, ypos + 1); | ||
242 | |||
243 | if (gPlay.state == paused) /* we have to draw ourselves */ | ||
244 | rb->lcd_update_rect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height); | ||
245 | else | ||
246 | gPlay.bDirtyOSD = true; /* redraw it with next timer IRQ */ | ||
247 | } | ||
248 | #endif | ||
249 | |||
250 | |||
251 | /* helper function to draw a position indicator */ | ||
252 | static void DrawPosition(int pos, int total) | ||
253 | { | ||
254 | int w, h; | ||
255 | int sec; /* estimated seconds */ | ||
256 | int ypos; | ||
257 | |||
258 | rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); | ||
259 | rb->lcd_fillrect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height); | ||
260 | rb->lcd_set_drawmode(DRMODE_SOLID); | ||
261 | |||
262 | /* print the estimated position */ | ||
263 | sec = pos / (gFileHdr.bps_average/8); | ||
264 | if (sec < 100*60) /* fits into mm:ss format */ | ||
265 | rb->snprintf(gPrint, sizeof(gPrint), "%02d:%02dm", sec/60, sec%60); | ||
266 | else /* a very long clip, hh:mm format */ | ||
267 | rb->snprintf(gPrint, sizeof(gPrint), "%02d:%02dh", sec/3600, (sec/60)%60); | ||
268 | |||
269 | rb->lcd_getstringsize(gPrint, &w, &h); | ||
270 | w++; | ||
271 | ypos = gBuf.osd_ypos + (gBuf.osd_height - h) / 2; | ||
272 | rb->lcd_putsxy(0, ypos, gPrint); | ||
273 | |||
274 | /* draw a slider over the rest of the line */ | ||
275 | rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN], w, ypos, LCD_WIDTH-w, | ||
276 | h, total, 0, pos, HORIZONTAL); | ||
277 | |||
278 | if (gPlay.state == paused) /* we have to draw ourselves */ | ||
279 | rb->lcd_update_rect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height); | ||
280 | else /* let the display time do it */ | ||
281 | { | ||
282 | gPlay.nTimeOSD = FPS; | ||
283 | gPlay.bDirtyOSD = true; /* redraw it with next timer IRQ */ | ||
284 | } | ||
285 | } | ||
286 | |||
287 | /* Put text on OSD and activate it for 1 second */ | ||
288 | static void osd_show_text(void) | ||
289 | { | ||
290 | int h, ypos; | ||
291 | |||
292 | rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); | ||
293 | rb->lcd_fillrect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height); | ||
294 | rb->lcd_set_drawmode(DRMODE_SOLID); | ||
295 | |||
296 | rb->lcd_getstringsize(gPrint, NULL, &h); | ||
297 | ypos = gBuf.osd_ypos + (gBuf.osd_height - h) / 2; | ||
298 | rb->lcd_putsxy(0, ypos, gPrint); | ||
299 | |||
300 | if (gPlay.state == paused) /* we have to draw ourselves */ | ||
301 | rb->lcd_update_rect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height); | ||
302 | else /* let the display time do it */ | ||
303 | { | ||
304 | gPlay.nTimeOSD = FPS; /* display it for 1 sec */ | ||
305 | gPlay.bDirtyOSD = true; /* let the refresh copy it to LCD */ | ||
306 | } | ||
307 | } | ||
308 | |||
309 | /* helper function to change the volume by a certain amount, +/- */ | ||
310 | static void ChangeVolume(int delta) | ||
311 | { | ||
312 | int minvol = rb->sound_min(SOUND_VOLUME); | ||
313 | int maxvol = rb->sound_max(SOUND_VOLUME); | ||
314 | int vol = rb->global_settings->volume + delta; | ||
315 | |||
316 | if (vol > maxvol) vol = maxvol; | ||
317 | else if (vol < minvol) vol = minvol; | ||
318 | if (vol != rb->global_settings->volume) | ||
319 | { | ||
320 | rb->sound_set(SOUND_VOLUME, vol); | ||
321 | rb->global_settings->volume = vol; | ||
322 | |||
323 | rb->snprintf(gPrint, sizeof(gPrint), "Vol: %d dB", vol); | ||
324 | osd_show_text(); | ||
325 | } | ||
326 | } | ||
327 | |||
328 | |||
329 | /* helper function to change the LCD contrast by a certain amount, +/- */ | ||
330 | static void ChangeContrast(int delta) | ||
331 | { | ||
332 | static int mycontrast = -1; /* the "permanent" value while running */ | ||
333 | int contrast; /* updated value */ | ||
334 | |||
335 | if (mycontrast == -1) | ||
336 | mycontrast = rb->global_settings->contrast; | ||
337 | |||
338 | contrast = mycontrast + delta; | ||
339 | if (contrast > 63) contrast = 63; | ||
340 | else if (contrast < 5) contrast = 5; | ||
341 | if (contrast != mycontrast) | ||
342 | { | ||
343 | rb->lcd_set_contrast(contrast); | ||
344 | mycontrast = contrast; | ||
345 | |||
346 | rb->snprintf(gPrint, sizeof(gPrint), "Contrast: %d", contrast); | ||
347 | osd_show_text(); | ||
348 | } | ||
349 | } | ||
350 | |||
351 | |||
352 | /* sync the video to the current audio */ | ||
353 | static void SyncVideo(void) | ||
354 | { | ||
355 | tAudioFrameHeader* pAudioBuf; | ||
356 | |||
357 | pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadAudio); | ||
358 | if (pAudioBuf->magic == AUDIO_MAGIC) | ||
359 | { | ||
360 | gBuf.vidcount = 0; /* nothing known */ | ||
361 | /* sync the video position */ | ||
362 | gBuf.pReadVideo = gBuf.pReadAudio + | ||
363 | (long)pAudioBuf->associated_video * (long)gFileHdr.blocksize; | ||
364 | |||
365 | /* handle possible wrap */ | ||
366 | if (gBuf.pReadVideo >= gBuf.pBufEnd) | ||
367 | gBuf.pReadVideo -= gBuf.bufsize; | ||
368 | else if (gBuf.pReadVideo < gBuf.pBufStart) | ||
369 | gBuf.pReadVideo += gBuf.bufsize; | ||
370 | } | ||
371 | } | ||
372 | |||
373 | |||
374 | /* timer interrupt handler to display a frame */ | ||
375 | static void timer4_isr(void) | ||
376 | { | ||
377 | int available; | ||
378 | tAudioFrameHeader* pAudioBuf; | ||
379 | int height; /* height to display */ | ||
380 | |||
381 | /* reduce height if we have OSD on */ | ||
382 | height = gFileHdr.video_height; | ||
383 | if (gPlay.nTimeOSD > 0) | ||
384 | { | ||
385 | gPlay.nTimeOSD--; | ||
386 | height = MIN(gBuf.osd_ypos, height); | ||
387 | if (gPlay.bDirtyOSD) | ||
388 | { | ||
389 | rb->lcd_update_rect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height); | ||
390 | gPlay.bDirtyOSD = false; | ||
391 | } | ||
392 | } | ||
393 | |||
394 | rb->lcd_blit_mono(gBuf.pReadVideo, 0, 0, | ||
395 | gFileHdr.video_width, height/8, gFileHdr.video_width); | ||
396 | |||
397 | available = Available(gBuf.pReadVideo); | ||
398 | |||
399 | /* loop to skip audio frame(s) */ | ||
400 | while(1) | ||
401 | { | ||
402 | /* just for the statistics */ | ||
403 | if (!gBuf.bEOF && available < gStats.minVideoAvail) | ||
404 | gStats.minVideoAvail = available; | ||
405 | |||
406 | if (available <= (int)gFileHdr.blocksize) | ||
407 | { /* no data for next frame */ | ||
408 | |||
409 | if (gBuf.bEOF && (gFileHdr.flags & FLAG_LOOP)) | ||
410 | { /* loop now, assuming the looped clip fits in memory */ | ||
411 | gBuf.pReadVideo = gBuf.pBufStart + gFileHdr.video_1st_frame; | ||
412 | /* FixMe: pReadVideo is incremented below */ | ||
413 | } | ||
414 | else | ||
415 | { | ||
416 | gPlay.bVideoUnderrun = true; | ||
417 | rb->timer_unregister(); /* disable ourselves */ | ||
418 | return; /* no data available */ | ||
419 | } | ||
420 | } | ||
421 | else /* normal advance for next time */ | ||
422 | { | ||
423 | gBuf.pReadVideo += gFileHdr.blocksize; | ||
424 | if (gBuf.pReadVideo >= gBuf.pBufEnd) | ||
425 | gBuf.pReadVideo -= gBuf.bufsize; /* wraparound */ | ||
426 | available -= gFileHdr.blocksize; | ||
427 | } | ||
428 | |||
429 | if (!gPlay.bHasAudio) | ||
430 | break; /* no need to skip any audio */ | ||
431 | |||
432 | if (gBuf.vidcount) | ||
433 | { | ||
434 | /* we know the next is a video frame */ | ||
435 | gBuf.vidcount--; | ||
436 | break; /* exit the loop */ | ||
437 | } | ||
438 | |||
439 | pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadVideo); | ||
440 | if (pAudioBuf->magic == AUDIO_MAGIC) | ||
441 | { /* we ran into audio, can happen after seek */ | ||
442 | gBuf.vidcount = pAudioBuf->next_block; | ||
443 | if (gBuf.vidcount) | ||
444 | gBuf.vidcount--; /* minus the audio block */ | ||
445 | } | ||
446 | } /* while */ | ||
447 | } | ||
448 | |||
449 | |||
450 | /* ISR function to get more mp3 data */ | ||
451 | static void GetMoreMp3(const void** start, size_t* size) | ||
452 | { | ||
453 | int available; | ||
454 | int advance; | ||
455 | |||
456 | tAudioFrameHeader* pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadAudio); | ||
457 | |||
458 | advance = pAudioBuf->next_block * gFileHdr.blocksize; | ||
459 | |||
460 | available = Available(gBuf.pReadAudio); | ||
461 | |||
462 | /* just for the statistics */ | ||
463 | if (!gBuf.bEOF && available < gStats.minAudioAvail) | ||
464 | gStats.minAudioAvail = available; | ||
465 | |||
466 | if (available < advance + (int)gFileHdr.blocksize || advance == 0) | ||
467 | { | ||
468 | gPlay.bAudioUnderrun = true; | ||
469 | return; /* no data available */ | ||
470 | } | ||
471 | |||
472 | gBuf.pReadAudio += advance; | ||
473 | if (gBuf.pReadAudio >= gBuf.pBufEnd) | ||
474 | gBuf.pReadAudio -= gBuf.bufsize; /* wraparound */ | ||
475 | |||
476 | *start = gBuf.pReadAudio + gFileHdr.audio_headersize; | ||
477 | *size = gFileHdr.blocksize - gFileHdr.audio_headersize; | ||
478 | } | ||
479 | |||
480 | |||
481 | static int WaitForButton(void) | ||
482 | { | ||
483 | int button; | ||
484 | |||
485 | do | ||
486 | { | ||
487 | button = rb->button_get(true); | ||
488 | rb->default_event_handler(button); | ||
489 | } while ((button & BUTTON_REL) && button != SYS_USB_CONNECTED); | ||
490 | |||
491 | return button; | ||
492 | } | ||
493 | |||
494 | |||
495 | static bool WantResume(int fd) | ||
496 | { | ||
497 | int button; | ||
498 | |||
499 | rb->lcd_puts(0, 0, "Resume to this"); | ||
500 | rb->lcd_puts(0, 1, "last position?"); | ||
501 | rb->lcd_puts(0, 2, "PLAY = yes"); | ||
502 | rb->lcd_puts(0, 3, "Any Other = no"); | ||
503 | rb->lcd_puts(0, 4, " (plays from start)"); | ||
504 | DrawPosition(gFileHdr.resume_pos, rb->filesize(fd)); | ||
505 | rb->lcd_update(); | ||
506 | |||
507 | button = WaitForButton(); | ||
508 | return (button == VIDEO_RESUME); | ||
509 | } | ||
510 | |||
511 | |||
512 | static int SeekTo(int fd, int nPos) | ||
513 | { | ||
514 | int read_now, got_now; | ||
515 | |||
516 | if (gPlay.bHasAudio) | ||
517 | rb->mp3_play_stop(); /* stop audio ISR */ | ||
518 | if (gPlay.bHasVideo) | ||
519 | rb->timer_unregister(); /* stop the timer */ | ||
520 | |||
521 | rb->lseek(fd, nPos, SEEK_SET); | ||
522 | |||
523 | gBuf.pBufFill = gBuf.pBufStart; /* all empty */ | ||
524 | gBuf.pReadVideo = gBuf.pReadAudio = gBuf.pBufStart; | ||
525 | |||
526 | read_now = gBuf.low_water - 1; /* less than low water, so loading will continue */ | ||
527 | read_now -= read_now % gBuf.granularity; /* round down to granularity */ | ||
528 | got_now = rb->read(fd, gBuf.pBufFill, read_now); | ||
529 | gBuf.bEOF = (read_now != got_now); | ||
530 | gBuf.pBufFill += got_now; | ||
531 | |||
532 | if (nPos == 0) | ||
533 | { /* we seeked to the start */ | ||
534 | if (gPlay.bHasVideo) | ||
535 | gBuf.pReadVideo += gFileHdr.video_1st_frame; | ||
536 | |||
537 | if (gPlay.bHasAudio) | ||
538 | gBuf.pReadAudio += gFileHdr.audio_1st_frame; | ||
539 | } | ||
540 | else | ||
541 | { /* we have to search for the positions */ | ||
542 | if (gPlay.bHasAudio) /* prepare audio playback, if contained */ | ||
543 | { | ||
544 | /* search for audio frame */ | ||
545 | while (((tAudioFrameHeader*)(gBuf.pReadAudio))->magic != AUDIO_MAGIC) | ||
546 | gBuf.pReadAudio += gFileHdr.blocksize; | ||
547 | |||
548 | if (gPlay.bHasVideo) | ||
549 | SyncVideo(); /* pick the right video for that */ | ||
550 | } | ||
551 | } | ||
552 | |||
553 | /* synchronous start */ | ||
554 | gPlay.state = playing; | ||
555 | if (gPlay.bHasAudio) | ||
556 | { | ||
557 | gPlay.bAudioUnderrun = false; | ||
558 | rb->mp3_play_data(gBuf.pReadAudio + gFileHdr.audio_headersize, | ||
559 | gFileHdr.blocksize - gFileHdr.audio_headersize, GetMoreMp3); | ||
560 | rb->mp3_play_pause(true); /* kickoff audio */ | ||
561 | } | ||
562 | if (gPlay.bHasVideo) | ||
563 | { | ||
564 | gPlay.bVideoUnderrun = false; | ||
565 | /* start display interrupt */ | ||
566 | #if FREQ == 12000000 /* Ondio speed kludge */ | ||
567 | rb->timer_register(1, NULL, gPlay.nFrameTimeAdjusted, | ||
568 | timer4_isr IF_COP(, CPU)); | ||
569 | #else | ||
570 | rb->timer_register(1, NULL, gFileHdr.video_frametime, | ||
571 | timer4_isr IF_COP(, CPU)); | ||
572 | #endif | ||
573 | } | ||
574 | |||
575 | return 0; | ||
576 | } | ||
577 | |||
578 | /* called from default_event_handler_ex() or at end of playback */ | ||
579 | static void Cleanup(void *fd) | ||
580 | { | ||
581 | rb->close(*(int*)fd); /* close the file */ | ||
582 | |||
583 | if (gPlay.bHasVideo) | ||
584 | rb->timer_unregister(); /* stop video ISR, now I can use the display again */ | ||
585 | |||
586 | if (gPlay.bHasAudio) | ||
587 | rb->mp3_play_stop(); /* stop audio ISR */ | ||
588 | |||
589 | /* Turn on backlight timeout (revert to settings) */ | ||
590 | backlight_use_settings(); | ||
591 | |||
592 | /* restore normal contrast */ | ||
593 | rb->lcd_set_contrast(rb->global_settings->contrast); | ||
594 | } | ||
595 | |||
596 | /* returns >0 if continue, =0 to stop, <0 to abort (USB) */ | ||
597 | static int PlayTick(int fd) | ||
598 | { | ||
599 | int button; | ||
600 | static int lastbutton = 0; | ||
601 | int avail_audio = -1, avail_video = -1; | ||
602 | int retval = 1; | ||
603 | int filepos; | ||
604 | |||
605 | /* check buffer level */ | ||
606 | |||
607 | if (gPlay.bHasAudio) | ||
608 | avail_audio = Available(gBuf.pReadAudio); | ||
609 | if (gPlay.bHasVideo) | ||
610 | avail_video = Available(gBuf.pReadVideo); | ||
611 | |||
612 | if ((gPlay.bHasAudio && avail_audio < gBuf.low_water) | ||
613 | || (gPlay.bHasVideo && avail_video < gBuf.low_water)) | ||
614 | { | ||
615 | gPlay.bRefilling = true; /* go to refill mode */ | ||
616 | } | ||
617 | |||
618 | if ((!gPlay.bHasAudio || gPlay.bAudioUnderrun) | ||
619 | && (!gPlay.bHasVideo || gPlay.bVideoUnderrun) | ||
620 | && gBuf.bEOF) | ||
621 | { | ||
622 | if (gFileHdr.resume_pos) | ||
623 | { /* we played til the end, clear resume position */ | ||
624 | gFileHdr.resume_pos = 0; | ||
625 | rb->lseek(fd, 0, SEEK_SET); /* save resume position */ | ||
626 | rb->write(fd, &gFileHdr, sizeof(gFileHdr)); | ||
627 | } | ||
628 | Cleanup(&fd); | ||
629 | return 0; /* all expired */ | ||
630 | } | ||
631 | |||
632 | if (!gPlay.bRefilling || gBuf.bEOF) | ||
633 | { /* nothing to do */ | ||
634 | button = rb->button_get_w_tmo(HZ/10); | ||
635 | } | ||
636 | else | ||
637 | { /* refill buffer */ | ||
638 | int read_now, got_now; | ||
639 | int buf_free; | ||
640 | long spinup; /* measure the spinup time */ | ||
641 | |||
642 | /* how much can we reload, don't fill completely, would appear empty */ | ||
643 | buf_free = gBuf.bufsize - MAX(avail_audio, avail_video) - gBuf.high_water; | ||
644 | if (buf_free < 0) | ||
645 | buf_free = 0; /* just for safety */ | ||
646 | buf_free -= buf_free % gBuf.granularity; /* round down to granularity */ | ||
647 | |||
648 | /* in one piece max. up to buffer end (wrap after that) */ | ||
649 | read_now = MIN(buf_free, gBuf.pBufEnd - gBuf.pBufFill); | ||
650 | |||
651 | /* load piecewise, to stay responsive */ | ||
652 | read_now = MIN(read_now, gBuf.nReadChunk); | ||
653 | |||
654 | if (read_now == buf_free) | ||
655 | gPlay.bRefilling = false; /* last piece requested */ | ||
656 | |||
657 | spinup = *rb->current_tick; /* in case this is interesting below */ | ||
658 | |||
659 | got_now = rb->read(fd, gBuf.pBufFill, read_now); | ||
660 | if (got_now != read_now || read_now == 0) | ||
661 | { | ||
662 | gBuf.bEOF = true; | ||
663 | gPlay.bRefilling = false; | ||
664 | } | ||
665 | |||
666 | if (gPlay.bDiskSleep) /* statistics about the spinup time */ | ||
667 | { | ||
668 | spinup = *rb->current_tick - spinup; | ||
669 | gPlay.bDiskSleep = false; | ||
670 | if (spinup > gStats.maxSpinup) | ||
671 | gStats.maxSpinup = spinup; | ||
672 | if (spinup < gStats.minSpinup) | ||
673 | gStats.minSpinup = spinup; | ||
674 | |||
675 | /* recalculate the low water mark from real measurements */ | ||
676 | gBuf.low_water = (gStats.maxSpinup + gBuf.spinup_safety) | ||
677 | * gFileHdr.bps_peak / 8 / HZ; | ||
678 | } | ||
679 | |||
680 | if (!gPlay.bRefilling | ||
681 | #ifdef HAVE_DISK_STORAGE | ||
682 | && rb->global_settings->disk_spindown < 20 /* condition for test only */ | ||
683 | #endif | ||
684 | ) | ||
685 | { | ||
686 | rb->storage_sleep(); /* no point in leaving the disk run til timeout */ | ||
687 | gPlay.bDiskSleep = true; | ||
688 | } | ||
689 | |||
690 | gBuf.pBufFill += got_now; | ||
691 | if (gBuf.pBufFill >= gBuf.pBufEnd) | ||
692 | gBuf.pBufFill = gBuf.pBufStart; /* wrap */ | ||
693 | |||
694 | rb->yield(); /* have mercy with the other threads */ | ||
695 | button = rb->button_get(false); | ||
696 | } | ||
697 | |||
698 | /* check keypresses */ | ||
699 | |||
700 | if (button != BUTTON_NONE) | ||
701 | { | ||
702 | filepos = rb->lseek(fd, 0, SEEK_CUR); | ||
703 | |||
704 | if (gPlay.bHasVideo) /* video position is more accurate */ | ||
705 | filepos -= Available(gBuf.pReadVideo); /* take video position */ | ||
706 | else | ||
707 | filepos -= Available(gBuf.pReadAudio); /* else audio */ | ||
708 | |||
709 | switch (button) { /* set exit conditions */ | ||
710 | case BUTTON_OFF: | ||
711 | if (gFileHdr.magic == HEADER_MAGIC /* only if file has header */ | ||
712 | && !(gFileHdr.flags & FLAG_LOOP)) /* not for stills */ | ||
713 | { | ||
714 | gFileHdr.resume_pos = filepos; | ||
715 | rb->lseek(fd, 0, SEEK_SET); /* save resume position */ | ||
716 | rb->write(fd, &gFileHdr, sizeof(gFileHdr)); | ||
717 | } | ||
718 | Cleanup(&fd); | ||
719 | retval = 0; /* signal "stopped" to caller */ | ||
720 | break; | ||
721 | case VIDEO_STOP_SEEK: | ||
722 | #ifdef VIDEO_STOP_SEEK_PRE | ||
723 | if (lastbutton != VIDEO_STOP_SEEK_PRE) | ||
724 | break; | ||
725 | #endif | ||
726 | if (gPlay.bSeeking) | ||
727 | { | ||
728 | gPlay.bSeeking = false; | ||
729 | gPlay.state = playing; | ||
730 | SeekTo(fd, gPlay.nSeekPos); | ||
731 | } | ||
732 | else if (gPlay.state == playing) | ||
733 | { | ||
734 | gPlay.state = paused; | ||
735 | if (gPlay.bHasAudio) | ||
736 | rb->mp3_play_pause(false); /* pause audio */ | ||
737 | if (gPlay.bHasVideo) | ||
738 | rb->timer_unregister(); /* stop the timer */ | ||
739 | } | ||
740 | else if (gPlay.state == paused) | ||
741 | { | ||
742 | gPlay.state = playing; | ||
743 | if (gPlay.bHasAudio) | ||
744 | { | ||
745 | if (gPlay.bHasVideo) | ||
746 | SyncVideo(); | ||
747 | rb->mp3_play_pause(true); /* play audio */ | ||
748 | } | ||
749 | if (gPlay.bHasVideo) | ||
750 | { /* start the video */ | ||
751 | #if FREQ == 12000000 /* Ondio speed kludge */ | ||
752 | rb->timer_register(1, NULL, | ||
753 | gPlay.nFrameTimeAdjusted, timer4_isr); | ||
754 | #else | ||
755 | rb->timer_register(1, NULL, | ||
756 | gFileHdr.video_frametime, timer4_isr); | ||
757 | #endif | ||
758 | } | ||
759 | } | ||
760 | break; | ||
761 | case BUTTON_UP: | ||
762 | case BUTTON_UP | BUTTON_REPEAT: | ||
763 | if (gPlay.bHasAudio) | ||
764 | ChangeVolume(1); | ||
765 | break; | ||
766 | case BUTTON_DOWN: | ||
767 | case BUTTON_DOWN | BUTTON_REPEAT: | ||
768 | if (gPlay.bHasAudio) | ||
769 | ChangeVolume(-1); | ||
770 | break; | ||
771 | case BUTTON_LEFT: | ||
772 | case BUTTON_LEFT | BUTTON_REPEAT: | ||
773 | if (!gPlay.bSeeking) /* prepare seek */ | ||
774 | { | ||
775 | gPlay.nSeekPos = filepos; | ||
776 | gPlay.bSeeking = true; | ||
777 | gPlay.nSeekAcc = 0; | ||
778 | } | ||
779 | else if (gPlay.nSeekAcc > 0) /* other direction, stop sliding */ | ||
780 | gPlay.nSeekAcc = 0; | ||
781 | else | ||
782 | gPlay.nSeekAcc--; | ||
783 | break; | ||
784 | case BUTTON_RIGHT: | ||
785 | case BUTTON_RIGHT | BUTTON_REPEAT: | ||
786 | if (!gPlay.bSeeking) /* prepare seek */ | ||
787 | { | ||
788 | gPlay.nSeekPos = filepos; | ||
789 | gPlay.bSeeking = true; | ||
790 | gPlay.nSeekAcc = 0; | ||
791 | } | ||
792 | else if (gPlay.nSeekAcc < 0) /* other direction, stop sliding */ | ||
793 | gPlay.nSeekAcc = 0; | ||
794 | else | ||
795 | gPlay.nSeekAcc++; | ||
796 | break; | ||
797 | #ifdef VIDEO_DEBUG | ||
798 | case VIDEO_DEBUG: /* debug key */ | ||
799 | case VIDEO_DEBUG | BUTTON_REPEAT: | ||
800 | DrawBuf(); /* show buffer status */ | ||
801 | gPlay.nTimeOSD = FPS/2; | ||
802 | gPlay.bDirtyOSD = true; | ||
803 | break; | ||
804 | #endif | ||
805 | case VIDEO_CONTRAST_DOWN: /* contrast down */ | ||
806 | case VIDEO_CONTRAST_DOWN | BUTTON_REPEAT: | ||
807 | if (gPlay.bHasVideo) | ||
808 | ChangeContrast(-1); | ||
809 | break; | ||
810 | case VIDEO_CONTRAST_UP: /* contrast up */ | ||
811 | case VIDEO_CONTRAST_UP | BUTTON_REPEAT: | ||
812 | if (gPlay.bHasVideo) | ||
813 | ChangeContrast(1); | ||
814 | break; | ||
815 | default: | ||
816 | if (rb->default_event_handler_ex(button, Cleanup, &fd) | ||
817 | == SYS_USB_CONNECTED) | ||
818 | retval = -1; /* signal "aborted" to caller */ | ||
819 | break; | ||
820 | } | ||
821 | |||
822 | lastbutton = button; | ||
823 | } /* if (button != BUTTON_NONE) */ | ||
824 | |||
825 | |||
826 | /* handle seeking */ | ||
827 | |||
828 | if (gPlay.bSeeking) /* seeking? */ | ||
829 | { | ||
830 | if (gPlay.nSeekAcc < -MAX_ACC) | ||
831 | gPlay.nSeekAcc = -MAX_ACC; | ||
832 | else if (gPlay.nSeekAcc > MAX_ACC) | ||
833 | gPlay.nSeekAcc = MAX_ACC; | ||
834 | |||
835 | gPlay.nSeekPos += gPlay.nSeekAcc * gBuf.nSeekChunk; | ||
836 | if (gPlay.nSeekPos < 0) | ||
837 | gPlay.nSeekPos = 0; | ||
838 | if (gPlay.nSeekPos > rb->filesize(fd) - gBuf.granularity) | ||
839 | { | ||
840 | gPlay.nSeekPos = rb->filesize(fd); | ||
841 | gPlay.nSeekPos -= gPlay.nSeekPos % gBuf.granularity; | ||
842 | } | ||
843 | DrawPosition(gPlay.nSeekPos, rb->filesize(fd)); | ||
844 | } | ||
845 | |||
846 | |||
847 | /* check + recover underruns */ | ||
848 | |||
849 | if ((gPlay.bAudioUnderrun || gPlay.bVideoUnderrun) && !gBuf.bEOF) | ||
850 | { | ||
851 | gBuf.spinup_safety += HZ/2; /* add extra spinup time for the future */ | ||
852 | filepos = rb->lseek(fd, 0, SEEK_CUR); | ||
853 | |||
854 | if (gPlay.bHasVideo && gPlay.bVideoUnderrun) | ||
855 | { | ||
856 | gStats.nVideoUnderruns++; | ||
857 | filepos -= Available(gBuf.pReadVideo); /* take video position */ | ||
858 | SeekTo(fd, filepos); | ||
859 | } | ||
860 | else if (gPlay.bHasAudio && gPlay.bAudioUnderrun) | ||
861 | { | ||
862 | gStats.nAudioUnderruns++; | ||
863 | filepos -= Available(gBuf.pReadAudio); /* else audio */ | ||
864 | SeekTo(fd, filepos); | ||
865 | } | ||
866 | } | ||
867 | |||
868 | return retval; | ||
869 | } | ||
870 | |||
871 | |||
872 | static int main(char* filename) | ||
873 | { | ||
874 | int file_size; | ||
875 | int fd; /* file descriptor handle */ | ||
876 | int read_now, got_now; | ||
877 | int button = 0; | ||
878 | int retval; | ||
879 | |||
880 | /* try to open the file */ | ||
881 | fd = rb->open(filename, O_RDWR); | ||
882 | if (fd < 0) | ||
883 | return PLUGIN_ERROR; | ||
884 | file_size = rb->filesize(fd); | ||
885 | |||
886 | /* reset pitch value to ensure synchronous playback */ | ||
887 | rb->sound_set_pitch(PITCH_SPEED_100); | ||
888 | |||
889 | /* init statistics */ | ||
890 | rb->memset(&gStats, 0, sizeof(gStats)); | ||
891 | gStats.minAudioAvail = gStats.minVideoAvail = INT_MAX; | ||
892 | gStats.minSpinup = INT_MAX; | ||
893 | |||
894 | /* init playback state */ | ||
895 | rb->memset(&gPlay, 0, sizeof(gPlay)); | ||
896 | |||
897 | /* init buffer */ | ||
898 | rb->memset(&gBuf, 0, sizeof(gBuf)); | ||
899 | gBuf.pBufStart = rb->plugin_get_audio_buffer((size_t *)&gBuf.bufsize); | ||
900 | /*gBuf.bufsize = 1700*1024; // test, like 2MB version!!!! */ | ||
901 | gBuf.pBufFill = gBuf.pBufStart; /* all empty */ | ||
902 | |||
903 | /* init OSD */ | ||
904 | rb->lcd_getstringsize("X", NULL, &retval); | ||
905 | gBuf.osd_height = (retval + 7) & ~7; | ||
906 | gBuf.osd_ypos = LCD_HEIGHT - gBuf.osd_height; | ||
907 | |||
908 | /* load file header */ | ||
909 | read_now = sizeof(gFileHdr); | ||
910 | got_now = rb->read(fd, &gFileHdr, read_now); | ||
911 | rb->lseek(fd, 0, SEEK_SET); /* rewind to restart sector-aligned */ | ||
912 | if (got_now != read_now) | ||
913 | { | ||
914 | rb->close(fd); | ||
915 | return (PLUGIN_ERROR); | ||
916 | } | ||
917 | |||
918 | /* check header */ | ||
919 | if (gFileHdr.magic != HEADER_MAGIC) | ||
920 | { /* old file, use default info */ | ||
921 | rb->memset(&gFileHdr, 0, sizeof(gFileHdr)); | ||
922 | gFileHdr.blocksize = SCREENSIZE; | ||
923 | if (file_size < SCREENSIZE * FPS) /* less than a second */ | ||
924 | gFileHdr.flags |= FLAG_LOOP; | ||
925 | gFileHdr.video_format = VIDEOFORMAT_RAW; | ||
926 | gFileHdr.video_width = LCD_WIDTH; | ||
927 | gFileHdr.video_height = LCD_HEIGHT; | ||
928 | gFileHdr.video_frametime = 11059200 / FPS; | ||
929 | gFileHdr.bps_peak = gFileHdr.bps_average = LCD_WIDTH * LCD_HEIGHT * FPS; | ||
930 | } | ||
931 | |||
932 | #if FREQ == 12000000 /* Ondio speed kludge, 625 / 576 == 12000000 / 11059200 */ | ||
933 | gPlay.nFrameTimeAdjusted = (gFileHdr.video_frametime * 625) / 576; | ||
934 | #endif | ||
935 | |||
936 | /* continue buffer init: align the end, calc low water, read sizes */ | ||
937 | gBuf.granularity = gFileHdr.blocksize; | ||
938 | while (gBuf.granularity % 512) /* common multiple of sector size */ | ||
939 | gBuf.granularity *= 2; | ||
940 | gBuf.bufsize -= gBuf.bufsize % gBuf.granularity; /* round down */ | ||
941 | gBuf.pBufEnd = gBuf.pBufStart + gBuf.bufsize; | ||
942 | gBuf.low_water = SPINUP_INIT * gFileHdr.bps_peak / 8000; | ||
943 | gBuf.spinup_safety = SPINUP_SAFETY * HZ / 1000; /* in time ticks */ | ||
944 | if (gFileHdr.audio_min_associated < 0) | ||
945 | gBuf.high_water = 0 - gFileHdr.audio_min_associated; | ||
946 | else | ||
947 | gBuf.high_water = 1; /* never fill buffer completely, would appear empty */ | ||
948 | gBuf.nReadChunk = (CHUNK + gBuf.granularity - 1); /* round up */ | ||
949 | gBuf.nReadChunk -= gBuf.nReadChunk % gBuf.granularity;/* and align */ | ||
950 | gBuf.nSeekChunk = rb->filesize(fd) / FF_TICKS; | ||
951 | gBuf.nSeekChunk += gBuf.granularity - 1; /* round up */ | ||
952 | gBuf.nSeekChunk -= gBuf.nSeekChunk % gBuf.granularity; /* and align */ | ||
953 | |||
954 | /* prepare video playback, if contained */ | ||
955 | if (gFileHdr.video_format == VIDEOFORMAT_RAW) | ||
956 | { | ||
957 | gPlay.bHasVideo = true; | ||
958 | /* Turn off backlight timeout */ | ||
959 | backlight_ignore_timeout(); | ||
960 | } | ||
961 | |||
962 | /* prepare audio playback, if contained */ | ||
963 | if (gFileHdr.audio_format == AUDIOFORMAT_MP3_BITSWAPPED) | ||
964 | { | ||
965 | gPlay.bHasAudio = true; | ||
966 | } | ||
967 | |||
968 | /* start playback by seeking to zero or resume position */ | ||
969 | if (gFileHdr.resume_pos && WantResume(fd)) /* ask the user */ | ||
970 | SeekTo(fd, gFileHdr.resume_pos); | ||
971 | else | ||
972 | SeekTo(fd, 0); | ||
973 | |||
974 | /* all that's left to do is keep the buffer full */ | ||
975 | do /* the main loop */ | ||
976 | { | ||
977 | retval = PlayTick(fd); | ||
978 | } while (retval > 0); | ||
979 | |||
980 | if (retval < 0) /* aborted? */ | ||
981 | { | ||
982 | return PLUGIN_USB_CONNECTED; | ||
983 | } | ||
984 | |||
985 | #ifndef DEBUG /* for release compilations, only display the stats in case of error */ | ||
986 | if (gStats.nAudioUnderruns || gStats.nVideoUnderruns) | ||
987 | #endif | ||
988 | { | ||
989 | /* display statistics */ | ||
990 | rb->lcd_clear_display(); | ||
991 | rb->snprintf(gPrint, sizeof(gPrint), "%d Audio Underruns", gStats.nAudioUnderruns); | ||
992 | rb->lcd_puts(0, 0, gPrint); | ||
993 | rb->snprintf(gPrint, sizeof(gPrint), "%d Video Underruns", gStats.nVideoUnderruns); | ||
994 | rb->lcd_puts(0, 1, gPrint); | ||
995 | rb->snprintf(gPrint, sizeof(gPrint), "%d MinAudio bytes", gStats.minAudioAvail); | ||
996 | rb->lcd_puts(0, 2, gPrint); | ||
997 | rb->snprintf(gPrint, sizeof(gPrint), "%d MinVideo bytes", gStats.minVideoAvail); | ||
998 | rb->lcd_puts(0, 3, gPrint); | ||
999 | rb->snprintf(gPrint, sizeof(gPrint), "MinSpinup %ld.%02ld", gStats.minSpinup/HZ, gStats.minSpinup%HZ); | ||
1000 | rb->lcd_puts(0, 4, gPrint); | ||
1001 | rb->snprintf(gPrint, sizeof(gPrint), "MaxSpinup %ld.%02ld", gStats.maxSpinup/HZ, gStats.maxSpinup%HZ); | ||
1002 | rb->lcd_puts(0, 5, gPrint); | ||
1003 | rb->snprintf(gPrint, sizeof(gPrint), "LowWater: %d", gBuf.low_water); | ||
1004 | rb->lcd_puts(0, 6, gPrint); | ||
1005 | rb->snprintf(gPrint, sizeof(gPrint), "HighWater: %d", gBuf.high_water); | ||
1006 | rb->lcd_puts(0, 7, gPrint); | ||
1007 | |||
1008 | rb->lcd_update(); | ||
1009 | button = WaitForButton(); | ||
1010 | } | ||
1011 | return (button == SYS_USB_CONNECTED) ? PLUGIN_USB_CONNECTED : PLUGIN_OK; | ||
1012 | } | ||
1013 | |||
1014 | |||
1015 | /***************** Plugin Entry Point *****************/ | ||
1016 | |||
1017 | enum plugin_status plugin_start(const void* parameter) | ||
1018 | { | ||
1019 | if (parameter == NULL) | ||
1020 | { | ||
1021 | rb->splash(HZ*2, "Play .rvf file!"); | ||
1022 | return PLUGIN_ERROR; | ||
1023 | } | ||
1024 | |||
1025 | /* now go ahead and have fun! */ | ||
1026 | return main((char*) parameter); | ||
1027 | } | ||
1028 | |||
1029 | #endif /* #ifdef HAVE_LCD_BITMAP */ | ||
1030 | |||