Theme Editor: Factored out code to skip over enum/arg lists while scanning for childr...
[kugel-rb.git] / apps / plugins / video.c
blob6b0a47c7c6d175b34d278d38e19ed665d23970af
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Plugin for video playback
11 * Reads raw image data + audio data from a file
12 * !!!!!!!!!! Code Police free zone !!!!!!!!!!
14 * Copyright (C) 2003-2004 J�g Hohensohn aka [IDC]Dragon
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.
21 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22 * KIND, either express or implied.
24 ****************************************************************************/
27 /****************** imports ******************/
29 #include "plugin.h"
30 #include "sh7034.h"
31 #include "system.h"
32 #include "lib/helper.h"
34 #ifdef HAVE_LCD_BITMAP /* and definitely not for the Player, haha */
36 PLUGIN_HEADER
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
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)
53 #endif
54 /****************** constants ******************/
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 */
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 */
67 /****************** prototypes ******************/
68 void timer4_isr(void); /* IMIA4 ISR */
69 int check_button(void); /* determine next relative frame */
72 /****************** data types ******************/
74 /* plugins don't introduce headers, so structs are repeated from rvf_format.h */
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 */
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
87 /* bit flags */
88 #define FLAG_LOOP 0x00000001 /* loop the playback, e.g. for stills */
90 typedef struct /* contains whatever might be useful to the player */
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 */
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 */
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 */
126 /* more to come... ? */
128 /* Note: padding up to 'blocksize' with zero following this header */
129 } tFileHeader;
131 typedef struct /* the little header for all audio blocks */
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;
143 /****************** globals ******************/
145 static char gPrint[32]; /* a global printf buffer, saves stack */
148 /* playstate */
149 static struct
151 enum
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;
172 /* buffer information */
173 static struct
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;
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;
194 /* statistics */
195 static struct
197 int minAudioAvail;
198 int minVideoAvail;
199 int nAudioUnderruns;
200 int nVideoUnderruns;
201 long minSpinup;
202 long maxSpinup;
203 } gStats;
205 tFileHeader gFileHdr; /* file header */
207 /****************** implementation ******************/
209 /* tool function: return how much playable audio/video is left */
210 int Available(unsigned char* pSnapshot)
212 if (pSnapshot <= gBuf.pBufFill)
213 return gBuf.pBufFill - pSnapshot;
214 else
215 return gBuf.bufsize - (pSnapshot - gBuf.pBufFill);
218 /* debug function to draw buffer indicators */
219 void DrawBuf(void)
221 int ypos, fill, video, audio;
223 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
224 rb->lcd_fillrect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height);
225 rb->lcd_set_drawmode(DRMODE_SOLID);
227 ypos = gBuf.osd_ypos + gBuf.osd_height/2 - 3; /* center vertically */
229 rb->lcd_hline(1, LCD_WIDTH-2, ypos + 3);
230 rb->lcd_vline(0, ypos, ypos + 6);
231 rb->lcd_vline(LCD_WIDTH-1, ypos, ypos + 6);
233 /* calculate new tick positions */
234 fill = 1 + ((gBuf.pBufFill - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize;
235 video = 1 + ((gBuf.pReadVideo - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize;
236 audio = 1 + ((gBuf.pReadAudio - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize;
238 rb->lcd_drawpixel(fill, ypos + 4);
239 rb->lcd_drawpixel(video, ypos + 2);
240 rb->lcd_drawpixel(audio, ypos + 1);
242 if (gPlay.state == paused) /* we have to draw ourselves */
243 rb->lcd_update_rect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height);
244 else
245 gPlay.bDirtyOSD = true; /* redraw it with next timer IRQ */
249 /* helper function to draw a position indicator */
250 void DrawPosition(int pos, int total)
252 int w, h;
253 int sec; /* estimated seconds */
254 int ypos;
256 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
257 rb->lcd_fillrect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height);
258 rb->lcd_set_drawmode(DRMODE_SOLID);
260 /* print the estimated position */
261 sec = pos / (gFileHdr.bps_average/8);
262 if (sec < 100*60) /* fits into mm:ss format */
263 rb->snprintf(gPrint, sizeof(gPrint), "%02d:%02dm", sec/60, sec%60);
264 else /* a very long clip, hh:mm format */
265 rb->snprintf(gPrint, sizeof(gPrint), "%02d:%02dh", sec/3600, (sec/60)%60);
267 rb->lcd_getstringsize(gPrint, &w, &h);
268 w++;
269 ypos = gBuf.osd_ypos + (gBuf.osd_height - h) / 2;
270 rb->lcd_putsxy(0, ypos, gPrint);
272 /* draw a slider over the rest of the line */
273 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN], w, ypos, LCD_WIDTH-w,
274 h, total, 0, pos, HORIZONTAL);
276 if (gPlay.state == paused) /* we have to draw ourselves */
277 rb->lcd_update_rect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height);
278 else /* let the display time do it */
280 gPlay.nTimeOSD = FPS;
281 gPlay.bDirtyOSD = true; /* redraw it with next timer IRQ */
285 /* Put text on OSD and activate it for 1 second */
286 void osd_show_text(void)
288 int h, ypos;
290 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
291 rb->lcd_fillrect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height);
292 rb->lcd_set_drawmode(DRMODE_SOLID);
294 rb->lcd_getstringsize(gPrint, NULL, &h);
295 ypos = gBuf.osd_ypos + (gBuf.osd_height - h) / 2;
296 rb->lcd_putsxy(0, ypos, gPrint);
298 if (gPlay.state == paused) /* we have to draw ourselves */
299 rb->lcd_update_rect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height);
300 else /* let the display time do it */
302 gPlay.nTimeOSD = FPS; /* display it for 1 sec */
303 gPlay.bDirtyOSD = true; /* let the refresh copy it to LCD */
307 /* helper function to change the volume by a certain amount, +/- */
308 void ChangeVolume(int delta)
310 int minvol = rb->sound_min(SOUND_VOLUME);
311 int maxvol = rb->sound_max(SOUND_VOLUME);
312 int vol = rb->global_settings->volume + delta;
314 if (vol > maxvol) vol = maxvol;
315 else if (vol < minvol) vol = minvol;
316 if (vol != rb->global_settings->volume)
318 rb->sound_set(SOUND_VOLUME, vol);
319 rb->global_settings->volume = vol;
321 rb->snprintf(gPrint, sizeof(gPrint), "Vol: %d dB", vol);
322 osd_show_text();
327 /* helper function to change the LCD contrast by a certain amount, +/- */
328 void ChangeContrast(int delta)
330 static int mycontrast = -1; /* the "permanent" value while running */
331 int contrast; /* updated value */
333 if (mycontrast == -1)
334 mycontrast = rb->global_settings->contrast;
336 contrast = mycontrast + delta;
337 if (contrast > 63) contrast = 63;
338 else if (contrast < 5) contrast = 5;
339 if (contrast != mycontrast)
341 rb->lcd_set_contrast(contrast);
342 mycontrast = contrast;
344 rb->snprintf(gPrint, sizeof(gPrint), "Contrast: %d", contrast);
345 osd_show_text();
350 /* sync the video to the current audio */
351 void SyncVideo(void)
353 tAudioFrameHeader* pAudioBuf;
355 pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadAudio);
356 if (pAudioBuf->magic == AUDIO_MAGIC)
358 gBuf.vidcount = 0; /* nothing known */
359 /* sync the video position */
360 gBuf.pReadVideo = gBuf.pReadAudio +
361 (long)pAudioBuf->associated_video * (long)gFileHdr.blocksize;
363 /* handle possible wrap */
364 if (gBuf.pReadVideo >= gBuf.pBufEnd)
365 gBuf.pReadVideo -= gBuf.bufsize;
366 else if (gBuf.pReadVideo < gBuf.pBufStart)
367 gBuf.pReadVideo += gBuf.bufsize;
372 /* timer interrupt handler to display a frame */
373 void timer4_isr(void)
375 int available;
376 tAudioFrameHeader* pAudioBuf;
377 int height; /* height to display */
379 /* reduce height if we have OSD on */
380 height = gFileHdr.video_height;
381 if (gPlay.nTimeOSD > 0)
383 gPlay.nTimeOSD--;
384 height = MIN(gBuf.osd_ypos, height);
385 if (gPlay.bDirtyOSD)
387 rb->lcd_update_rect(0, gBuf.osd_ypos, LCD_WIDTH, gBuf.osd_height);
388 gPlay.bDirtyOSD = false;
392 rb->lcd_blit_mono(gBuf.pReadVideo, 0, 0,
393 gFileHdr.video_width, height/8, gFileHdr.video_width);
395 available = Available(gBuf.pReadVideo);
397 /* loop to skip audio frame(s) */
398 while(1)
400 /* just for the statistics */
401 if (!gBuf.bEOF && available < gStats.minVideoAvail)
402 gStats.minVideoAvail = available;
404 if (available <= (int)gFileHdr.blocksize)
405 { /* no data for next frame */
407 if (gBuf.bEOF && (gFileHdr.flags & FLAG_LOOP))
408 { /* loop now, assuming the looped clip fits in memory */
409 gBuf.pReadVideo = gBuf.pBufStart + gFileHdr.video_1st_frame;
410 /* FixMe: pReadVideo is incremented below */
412 else
414 gPlay.bVideoUnderrun = true;
415 rb->timer_unregister(); /* disable ourselves */
416 return; /* no data available */
419 else /* normal advance for next time */
421 gBuf.pReadVideo += gFileHdr.blocksize;
422 if (gBuf.pReadVideo >= gBuf.pBufEnd)
423 gBuf.pReadVideo -= gBuf.bufsize; /* wraparound */
424 available -= gFileHdr.blocksize;
427 if (!gPlay.bHasAudio)
428 break; /* no need to skip any audio */
430 if (gBuf.vidcount)
432 /* we know the next is a video frame */
433 gBuf.vidcount--;
434 break; /* exit the loop */
437 pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadVideo);
438 if (pAudioBuf->magic == AUDIO_MAGIC)
439 { /* we ran into audio, can happen after seek */
440 gBuf.vidcount = pAudioBuf->next_block;
441 if (gBuf.vidcount)
442 gBuf.vidcount--; /* minus the audio block */
444 } /* while */
448 /* ISR function to get more mp3 data */
449 void GetMoreMp3(unsigned char** start, size_t* size)
451 int available;
452 int advance;
454 tAudioFrameHeader* pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadAudio);
456 advance = pAudioBuf->next_block * gFileHdr.blocksize;
458 available = Available(gBuf.pReadAudio);
460 /* just for the statistics */
461 if (!gBuf.bEOF && available < gStats.minAudioAvail)
462 gStats.minAudioAvail = available;
464 if (available < advance + (int)gFileHdr.blocksize || advance == 0)
466 gPlay.bAudioUnderrun = true;
467 return; /* no data available */
470 gBuf.pReadAudio += advance;
471 if (gBuf.pReadAudio >= gBuf.pBufEnd)
472 gBuf.pReadAudio -= gBuf.bufsize; /* wraparound */
474 *start = gBuf.pReadAudio + gFileHdr.audio_headersize;
475 *size = gFileHdr.blocksize - gFileHdr.audio_headersize;
479 int WaitForButton(void)
481 int button;
485 button = rb->button_get(true);
486 rb->default_event_handler(button);
487 } while ((button & BUTTON_REL) && button != SYS_USB_CONNECTED);
489 return button;
493 bool WantResume(int fd)
495 int button;
497 rb->lcd_puts(0, 0, "Resume to this");
498 rb->lcd_puts(0, 1, "last position?");
499 rb->lcd_puts(0, 2, "PLAY = yes");
500 rb->lcd_puts(0, 3, "Any Other = no");
501 rb->lcd_puts(0, 4, " (plays from start)");
502 DrawPosition(gFileHdr.resume_pos, rb->filesize(fd));
503 rb->lcd_update();
505 button = WaitForButton();
506 return (button == VIDEO_RESUME);
510 int SeekTo(int fd, int nPos)
512 int read_now, got_now;
514 if (gPlay.bHasAudio)
515 rb->mp3_play_stop(); /* stop audio ISR */
516 if (gPlay.bHasVideo)
517 rb->timer_unregister(); /* stop the timer */
519 rb->lseek(fd, nPos, SEEK_SET);
521 gBuf.pBufFill = gBuf.pBufStart; /* all empty */
522 gBuf.pReadVideo = gBuf.pReadAudio = gBuf.pBufStart;
524 read_now = gBuf.low_water - 1; /* less than low water, so loading will continue */
525 read_now -= read_now % gBuf.granularity; /* round down to granularity */
526 got_now = rb->read(fd, gBuf.pBufFill, read_now);
527 gBuf.bEOF = (read_now != got_now);
528 gBuf.pBufFill += got_now;
530 if (nPos == 0)
531 { /* we seeked to the start */
532 if (gPlay.bHasVideo)
533 gBuf.pReadVideo += gFileHdr.video_1st_frame;
535 if (gPlay.bHasAudio)
536 gBuf.pReadAudio += gFileHdr.audio_1st_frame;
538 else
539 { /* we have to search for the positions */
540 if (gPlay.bHasAudio) /* prepare audio playback, if contained */
542 /* search for audio frame */
543 while (((tAudioFrameHeader*)(gBuf.pReadAudio))->magic != AUDIO_MAGIC)
544 gBuf.pReadAudio += gFileHdr.blocksize;
546 if (gPlay.bHasVideo)
547 SyncVideo(); /* pick the right video for that */
551 /* synchronous start */
552 gPlay.state = playing;
553 if (gPlay.bHasAudio)
555 gPlay.bAudioUnderrun = false;
556 rb->mp3_play_data(gBuf.pReadAudio + gFileHdr.audio_headersize,
557 gFileHdr.blocksize - gFileHdr.audio_headersize, GetMoreMp3);
558 rb->mp3_play_pause(true); /* kickoff audio */
560 if (gPlay.bHasVideo)
562 gPlay.bVideoUnderrun = false;
563 /* start display interrupt */
564 #if FREQ == 12000000 /* Ondio speed kludge */
565 rb->timer_register(1, NULL, gPlay.nFrameTimeAdjusted,
566 timer4_isr IF_COP(, CPU));
567 #else
568 rb->timer_register(1, NULL, gFileHdr.video_frametime,
569 timer4_isr IF_COP(, CPU));
570 #endif
573 return 0;
576 /* called from default_event_handler_ex() or at end of playback */
577 void Cleanup(void *fd)
579 rb->close(*(int*)fd); /* close the file */
581 if (gPlay.bHasVideo)
582 rb->timer_unregister(); /* stop video ISR, now I can use the display again */
584 if (gPlay.bHasAudio)
585 rb->mp3_play_stop(); /* stop audio ISR */
587 /* Turn on backlight timeout (revert to settings) */
588 backlight_use_settings(); /* backlight control in lib/helper.c */
590 /* restore normal contrast */
591 rb->lcd_set_contrast(rb->global_settings->contrast);
594 /* returns >0 if continue, =0 to stop, <0 to abort (USB) */
595 int PlayTick(int fd)
597 int button;
598 static int lastbutton = 0;
599 int avail_audio = -1, avail_video = -1;
600 int retval = 1;
601 int filepos;
603 /* check buffer level */
605 if (gPlay.bHasAudio)
606 avail_audio = Available(gBuf.pReadAudio);
607 if (gPlay.bHasVideo)
608 avail_video = Available(gBuf.pReadVideo);
610 if ((gPlay.bHasAudio && avail_audio < gBuf.low_water)
611 || (gPlay.bHasVideo && avail_video < gBuf.low_water))
613 gPlay.bRefilling = true; /* go to refill mode */
616 if ((!gPlay.bHasAudio || gPlay.bAudioUnderrun)
617 && (!gPlay.bHasVideo || gPlay.bVideoUnderrun)
618 && gBuf.bEOF)
620 if (gFileHdr.resume_pos)
621 { /* we played til the end, clear resume position */
622 gFileHdr.resume_pos = 0;
623 rb->lseek(fd, 0, SEEK_SET); /* save resume position */
624 rb->write(fd, &gFileHdr, sizeof(gFileHdr));
626 Cleanup(&fd);
627 return 0; /* all expired */
630 if (!gPlay.bRefilling || gBuf.bEOF)
631 { /* nothing to do */
632 button = rb->button_get_w_tmo(HZ/10);
634 else
635 { /* refill buffer */
636 int read_now, got_now;
637 int buf_free;
638 long spinup; /* measure the spinup time */
640 /* how much can we reload, don't fill completely, would appear empty */
641 buf_free = gBuf.bufsize - MAX(avail_audio, avail_video) - gBuf.high_water;
642 if (buf_free < 0)
643 buf_free = 0; /* just for safety */
644 buf_free -= buf_free % gBuf.granularity; /* round down to granularity */
646 /* in one piece max. up to buffer end (wrap after that) */
647 read_now = MIN(buf_free, gBuf.pBufEnd - gBuf.pBufFill);
649 /* load piecewise, to stay responsive */
650 read_now = MIN(read_now, gBuf.nReadChunk);
652 if (read_now == buf_free)
653 gPlay.bRefilling = false; /* last piece requested */
655 spinup = *rb->current_tick; /* in case this is interesting below */
657 got_now = rb->read(fd, gBuf.pBufFill, read_now);
658 if (got_now != read_now || read_now == 0)
660 gBuf.bEOF = true;
661 gPlay.bRefilling = false;
664 if (gPlay.bDiskSleep) /* statistics about the spinup time */
666 spinup = *rb->current_tick - spinup;
667 gPlay.bDiskSleep = false;
668 if (spinup > gStats.maxSpinup)
669 gStats.maxSpinup = spinup;
670 if (spinup < gStats.minSpinup)
671 gStats.minSpinup = spinup;
673 /* recalculate the low water mark from real measurements */
674 gBuf.low_water = (gStats.maxSpinup + gBuf.spinup_safety)
675 * gFileHdr.bps_peak / 8 / HZ;
678 if (!gPlay.bRefilling
679 #ifdef HAVE_DISK_STORAGE
680 && rb->global_settings->disk_spindown < 20 /* condition for test only */
681 #endif
684 rb->storage_sleep(); /* no point in leaving the disk run til timeout */
685 gPlay.bDiskSleep = true;
688 gBuf.pBufFill += got_now;
689 if (gBuf.pBufFill >= gBuf.pBufEnd)
690 gBuf.pBufFill = gBuf.pBufStart; /* wrap */
692 rb->yield(); /* have mercy with the other threads */
693 button = rb->button_get(false);
696 /* check keypresses */
698 if (button != BUTTON_NONE)
700 filepos = rb->lseek(fd, 0, SEEK_CUR);
702 if (gPlay.bHasVideo) /* video position is more accurate */
703 filepos -= Available(gBuf.pReadVideo); /* take video position */
704 else
705 filepos -= Available(gBuf.pReadAudio); /* else audio */
707 switch (button) { /* set exit conditions */
708 case BUTTON_OFF:
709 if (gFileHdr.magic == HEADER_MAGIC /* only if file has header */
710 && !(gFileHdr.flags & FLAG_LOOP)) /* not for stills */
712 gFileHdr.resume_pos = filepos;
713 rb->lseek(fd, 0, SEEK_SET); /* save resume position */
714 rb->write(fd, &gFileHdr, sizeof(gFileHdr));
716 Cleanup(&fd);
717 retval = 0; /* signal "stopped" to caller */
718 break;
719 case VIDEO_STOP_SEEK:
720 #ifdef VIDEO_STOP_SEEK_PRE
721 if (lastbutton != VIDEO_STOP_SEEK_PRE)
722 break;
723 #endif
724 if (gPlay.bSeeking)
726 gPlay.bSeeking = false;
727 gPlay.state = playing;
728 SeekTo(fd, gPlay.nSeekPos);
730 else if (gPlay.state == playing)
732 gPlay.state = paused;
733 if (gPlay.bHasAudio)
734 rb->mp3_play_pause(false); /* pause audio */
735 if (gPlay.bHasVideo)
736 rb->timer_unregister(); /* stop the timer */
738 else if (gPlay.state == paused)
740 gPlay.state = playing;
741 if (gPlay.bHasAudio)
743 if (gPlay.bHasVideo)
744 SyncVideo();
745 rb->mp3_play_pause(true); /* play audio */
747 if (gPlay.bHasVideo)
748 { /* start the video */
749 #if FREQ == 12000000 /* Ondio speed kludge */
750 rb->timer_register(1, NULL,
751 gPlay.nFrameTimeAdjusted, timer4_isr);
752 #else
753 rb->timer_register(1, NULL,
754 gFileHdr.video_frametime, timer4_isr);
755 #endif
758 break;
759 case BUTTON_UP:
760 case BUTTON_UP | BUTTON_REPEAT:
761 if (gPlay.bHasAudio)
762 ChangeVolume(1);
763 break;
764 case BUTTON_DOWN:
765 case BUTTON_DOWN | BUTTON_REPEAT:
766 if (gPlay.bHasAudio)
767 ChangeVolume(-1);
768 break;
769 case BUTTON_LEFT:
770 case BUTTON_LEFT | BUTTON_REPEAT:
771 if (!gPlay.bSeeking) /* prepare seek */
773 gPlay.nSeekPos = filepos;
774 gPlay.bSeeking = true;
775 gPlay.nSeekAcc = 0;
777 else if (gPlay.nSeekAcc > 0) /* other direction, stop sliding */
778 gPlay.nSeekAcc = 0;
779 else
780 gPlay.nSeekAcc--;
781 break;
782 case BUTTON_RIGHT:
783 case BUTTON_RIGHT | BUTTON_REPEAT:
784 if (!gPlay.bSeeking) /* prepare seek */
786 gPlay.nSeekPos = filepos;
787 gPlay.bSeeking = true;
788 gPlay.nSeekAcc = 0;
790 else if (gPlay.nSeekAcc < 0) /* other direction, stop sliding */
791 gPlay.nSeekAcc = 0;
792 else
793 gPlay.nSeekAcc++;
794 break;
795 #ifdef VIDEO_DEBUG
796 case VIDEO_DEBUG: /* debug key */
797 case VIDEO_DEBUG | BUTTON_REPEAT:
798 DrawBuf(); /* show buffer status */
799 gPlay.nTimeOSD = FPS/2;
800 gPlay.bDirtyOSD = true;
801 break;
802 #endif
803 case VIDEO_CONTRAST_DOWN: /* contrast down */
804 case VIDEO_CONTRAST_DOWN | BUTTON_REPEAT:
805 if (gPlay.bHasVideo)
806 ChangeContrast(-1);
807 break;
808 case VIDEO_CONTRAST_UP: /* contrast up */
809 case VIDEO_CONTRAST_UP | BUTTON_REPEAT:
810 if (gPlay.bHasVideo)
811 ChangeContrast(1);
812 break;
813 default:
814 if (rb->default_event_handler_ex(button, Cleanup, &fd)
815 == SYS_USB_CONNECTED)
816 retval = -1; /* signal "aborted" to caller */
817 break;
820 lastbutton = button;
821 } /* if (button != BUTTON_NONE) */
824 /* handle seeking */
826 if (gPlay.bSeeking) /* seeking? */
828 if (gPlay.nSeekAcc < -MAX_ACC)
829 gPlay.nSeekAcc = -MAX_ACC;
830 else if (gPlay.nSeekAcc > MAX_ACC)
831 gPlay.nSeekAcc = MAX_ACC;
833 gPlay.nSeekPos += gPlay.nSeekAcc * gBuf.nSeekChunk;
834 if (gPlay.nSeekPos < 0)
835 gPlay.nSeekPos = 0;
836 if (gPlay.nSeekPos > rb->filesize(fd) - gBuf.granularity)
838 gPlay.nSeekPos = rb->filesize(fd);
839 gPlay.nSeekPos -= gPlay.nSeekPos % gBuf.granularity;
841 DrawPosition(gPlay.nSeekPos, rb->filesize(fd));
845 /* check + recover underruns */
847 if ((gPlay.bAudioUnderrun || gPlay.bVideoUnderrun) && !gBuf.bEOF)
849 gBuf.spinup_safety += HZ/2; /* add extra spinup time for the future */
850 filepos = rb->lseek(fd, 0, SEEK_CUR);
852 if (gPlay.bHasVideo && gPlay.bVideoUnderrun)
854 gStats.nVideoUnderruns++;
855 filepos -= Available(gBuf.pReadVideo); /* take video position */
856 SeekTo(fd, filepos);
858 else if (gPlay.bHasAudio && gPlay.bAudioUnderrun)
860 gStats.nAudioUnderruns++;
861 filepos -= Available(gBuf.pReadAudio); /* else audio */
862 SeekTo(fd, filepos);
866 return retval;
870 int main(char* filename)
872 int file_size;
873 int fd; /* file descriptor handle */
874 int read_now, got_now;
875 int button = 0;
876 int retval;
878 /* try to open the file */
879 fd = rb->open(filename, O_RDWR);
880 if (fd < 0)
881 return PLUGIN_ERROR;
882 file_size = rb->filesize(fd);
884 /* reset pitch value to ensure synchronous playback */
885 rb->sound_set_pitch(PITCH_SPEED_100);
887 /* init statistics */
888 rb->memset(&gStats, 0, sizeof(gStats));
889 gStats.minAudioAvail = gStats.minVideoAvail = INT_MAX;
890 gStats.minSpinup = INT_MAX;
892 /* init playback state */
893 rb->memset(&gPlay, 0, sizeof(gPlay));
895 /* init buffer */
896 rb->memset(&gBuf, 0, sizeof(gBuf));
897 gBuf.pBufStart = rb->plugin_get_audio_buffer((size_t *)&gBuf.bufsize);
898 /*gBuf.bufsize = 1700*1024; // test, like 2MB version!!!! */
899 gBuf.pBufFill = gBuf.pBufStart; /* all empty */
901 /* init OSD */
902 rb->lcd_getstringsize("X", NULL, &retval);
903 gBuf.osd_height = (retval + 7) & ~7;
904 gBuf.osd_ypos = LCD_HEIGHT - gBuf.osd_height;
906 /* load file header */
907 read_now = sizeof(gFileHdr);
908 got_now = rb->read(fd, &gFileHdr, read_now);
909 rb->lseek(fd, 0, SEEK_SET); /* rewind to restart sector-aligned */
910 if (got_now != read_now)
912 rb->close(fd);
913 return (PLUGIN_ERROR);
916 /* check header */
917 if (gFileHdr.magic != HEADER_MAGIC)
918 { /* old file, use default info */
919 rb->memset(&gFileHdr, 0, sizeof(gFileHdr));
920 gFileHdr.blocksize = SCREENSIZE;
921 if (file_size < SCREENSIZE * FPS) /* less than a second */
922 gFileHdr.flags |= FLAG_LOOP;
923 gFileHdr.video_format = VIDEOFORMAT_RAW;
924 gFileHdr.video_width = LCD_WIDTH;
925 gFileHdr.video_height = LCD_HEIGHT;
926 gFileHdr.video_frametime = 11059200 / FPS;
927 gFileHdr.bps_peak = gFileHdr.bps_average = LCD_WIDTH * LCD_HEIGHT * FPS;
930 #if FREQ == 12000000 /* Ondio speed kludge, 625 / 576 == 12000000 / 11059200 */
931 gPlay.nFrameTimeAdjusted = (gFileHdr.video_frametime * 625) / 576;
932 #endif
934 /* continue buffer init: align the end, calc low water, read sizes */
935 gBuf.granularity = gFileHdr.blocksize;
936 while (gBuf.granularity % 512) /* common multiple of sector size */
937 gBuf.granularity *= 2;
938 gBuf.bufsize -= gBuf.bufsize % gBuf.granularity; /* round down */
939 gBuf.pBufEnd = gBuf.pBufStart + gBuf.bufsize;
940 gBuf.low_water = SPINUP_INIT * gFileHdr.bps_peak / 8000;
941 gBuf.spinup_safety = SPINUP_SAFETY * HZ / 1000; /* in time ticks */
942 if (gFileHdr.audio_min_associated < 0)
943 gBuf.high_water = 0 - gFileHdr.audio_min_associated;
944 else
945 gBuf.high_water = 1; /* never fill buffer completely, would appear empty */
946 gBuf.nReadChunk = (CHUNK + gBuf.granularity - 1); /* round up */
947 gBuf.nReadChunk -= gBuf.nReadChunk % gBuf.granularity;/* and align */
948 gBuf.nSeekChunk = rb->filesize(fd) / FF_TICKS;
949 gBuf.nSeekChunk += gBuf.granularity - 1; /* round up */
950 gBuf.nSeekChunk -= gBuf.nSeekChunk % gBuf.granularity; /* and align */
952 /* prepare video playback, if contained */
953 if (gFileHdr.video_format == VIDEOFORMAT_RAW)
955 gPlay.bHasVideo = true;
956 /* Turn off backlight timeout */
957 backlight_force_on(); /* backlight control in lib/helper.c */
960 /* prepare audio playback, if contained */
961 if (gFileHdr.audio_format == AUDIOFORMAT_MP3_BITSWAPPED)
963 gPlay.bHasAudio = true;
966 /* start playback by seeking to zero or resume position */
967 if (gFileHdr.resume_pos && WantResume(fd)) /* ask the user */
968 SeekTo(fd, gFileHdr.resume_pos);
969 else
970 SeekTo(fd, 0);
972 /* all that's left to do is keep the buffer full */
973 do /* the main loop */
975 retval = PlayTick(fd);
976 } while (retval > 0);
978 if (retval < 0) /* aborted? */
980 return PLUGIN_USB_CONNECTED;
983 #ifndef DEBUG /* for release compilations, only display the stats in case of error */
984 if (gStats.nAudioUnderruns || gStats.nVideoUnderruns)
985 #endif
987 /* display statistics */
988 rb->lcd_clear_display();
989 rb->snprintf(gPrint, sizeof(gPrint), "%d Audio Underruns", gStats.nAudioUnderruns);
990 rb->lcd_puts(0, 0, gPrint);
991 rb->snprintf(gPrint, sizeof(gPrint), "%d Video Underruns", gStats.nVideoUnderruns);
992 rb->lcd_puts(0, 1, gPrint);
993 rb->snprintf(gPrint, sizeof(gPrint), "%d MinAudio bytes", gStats.minAudioAvail);
994 rb->lcd_puts(0, 2, gPrint);
995 rb->snprintf(gPrint, sizeof(gPrint), "%d MinVideo bytes", gStats.minVideoAvail);
996 rb->lcd_puts(0, 3, gPrint);
997 rb->snprintf(gPrint, sizeof(gPrint), "MinSpinup %ld.%02ld", gStats.minSpinup/HZ, gStats.minSpinup%HZ);
998 rb->lcd_puts(0, 4, gPrint);
999 rb->snprintf(gPrint, sizeof(gPrint), "MaxSpinup %ld.%02ld", gStats.maxSpinup/HZ, gStats.maxSpinup%HZ);
1000 rb->lcd_puts(0, 5, gPrint);
1001 rb->snprintf(gPrint, sizeof(gPrint), "LowWater: %d", gBuf.low_water);
1002 rb->lcd_puts(0, 6, gPrint);
1003 rb->snprintf(gPrint, sizeof(gPrint), "HighWater: %d", gBuf.high_water);
1004 rb->lcd_puts(0, 7, gPrint);
1006 rb->lcd_update();
1007 button = WaitForButton();
1009 return (button == SYS_USB_CONNECTED) ? PLUGIN_USB_CONNECTED : PLUGIN_OK;
1013 /***************** Plugin Entry Point *****************/
1015 enum plugin_status plugin_start(const void* parameter)
1017 if (parameter == NULL)
1019 rb->splash(HZ*2, "Play .rvf file!");
1020 return PLUGIN_ERROR;
1023 /* now go ahead and have fun! */
1024 return main((char*) parameter);
1027 #endif /* #ifdef HAVE_LCD_BITMAP */