very minor code police. also fix a possible but unlikely missed cpu_boost(false)
[Rockbox.git] / apps / plugins / video.c
blobfbd05bd006106d6408fcd9ef9169408c08b0f989
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 "helper.h"
34 #ifndef SIMULATOR /* not for simulator by now */
35 #ifdef HAVE_LCD_BITMAP /* and definitely not for the Player, haha */
37 PLUGIN_HEADER
39 /* variable button definitions */
40 #if CONFIG_KEYPAD == RECORDER_PAD
41 #define VIDEO_STOP_SEEK BUTTON_PLAY
42 #define VIDEO_RESUME BUTTON_PLAY
43 #define VIDEO_DEBUG BUTTON_F1
44 #define VIDEO_CONTRAST_DOWN BUTTON_F2
45 #define VIDEO_CONTRAST_UP BUTTON_F3
47 #elif CONFIG_KEYPAD == ONDIO_PAD
48 #define VIDEO_STOP_SEEK_PRE BUTTON_MENU
49 #define VIDEO_STOP_SEEK (BUTTON_MENU | BUTTON_REL)
50 #define VIDEO_RESUME BUTTON_RIGHT
51 #define VIDEO_CONTRAST_DOWN (BUTTON_MENU | BUTTON_DOWN)
52 #define VIDEO_CONTRAST_UP (BUTTON_MENU | BUTTON_UP)
54 #endif
55 /****************** constants ******************/
57 #define SCREENSIZE (LCD_WIDTH*LCD_HEIGHT/8) /* in bytes */
58 #define FPS 68 /* default fps for headerless (old video-only) file */
59 #define MAX_ACC 20 /* maximum FF/FR speedup */
60 #define FF_TICKS 3000; /* experimentally found nice */
62 /* trigger levels, we need about 80 kB/sec */
63 #define SPINUP_INIT 5000 /* from what level on to refill, in milliseconds */
64 #define SPINUP_SAFETY 700 /* how much on top of the measured spinup time */
65 #define CHUNK (1024*32) /* read size */
68 /****************** prototypes ******************/
69 void timer4_isr(void); /* IMIA4 ISR */
70 int check_button(void); /* determine next relative frame */
73 /****************** data types ******************/
75 /* plugins don't introduce headers, so structs are repeated from rvf_format.h */
77 #define HEADER_MAGIC 0x52564668 /* "RVFh" at file start */
78 #define AUDIO_MAGIC 0x41756446 /* "AudF" for each audio block */
79 #define FILEVERSION 100 /* 1.00 */
81 /* format type definitions */
82 #define VIDEOFORMAT_NO_VIDEO 0
83 #define VIDEOFORMAT_RAW 1
84 #define AUDIOFORMAT_NO_AUDIO 0
85 #define AUDIOFORMAT_MP3 1
86 #define AUDIOFORMAT_MP3_BITSWAPPED 2
88 /* bit flags */
89 #define FLAG_LOOP 0x00000001 /* loop the playback, e.g. for stills */
91 typedef struct /* contains whatever might be useful to the player */
93 /* general info (16 entries = 64 byte) */
94 unsigned long magic; /* HEADER_MAGIC */
95 unsigned long version; /* file version */
96 unsigned long flags; /* combination of FLAG_xx */
97 unsigned long blocksize; /* how many bytes per block (=video frame) */
98 unsigned long bps_average; /* bits per second of the whole stream */
99 unsigned long bps_peak; /* max. of above (audio may be VBR) */
100 unsigned long resume_pos; /* file position to resume to */
101 unsigned long reserved[9]; /* reserved, should be zero */
103 /* video info (16 entries = 64 byte) */
104 unsigned long video_format; /* one of VIDEOFORMAT_xxx */
105 unsigned long video_1st_frame; /* byte position of first video frame */
106 unsigned long video_duration; /* total length of video part, in ms */
107 unsigned long video_payload_size; /* total amount of video data, in bytes */
108 unsigned long video_bitrate; /* derived from resolution and frame time, in bps */
109 unsigned long video_frametime; /* frame interval in 11.0592 MHz clocks */
110 long video_preroll; /* video is how much ahead, in 11.0592 MHz clocks */
111 unsigned long video_width; /* in pixels */
112 unsigned long video_height; /* in pixels */
113 unsigned long video_reserved[7]; /* reserved, should be zero */
115 /* audio info (16 entries = 64 byte) */
116 unsigned long audio_format; /* one of AUDIOFORMAT_xxx */
117 unsigned long audio_1st_frame; /* byte position of first video frame */
118 unsigned long audio_duration; /* total length of audio part, in ms */
119 unsigned long audio_payload_size; /* total amount of audio data, in bytes */
120 unsigned long audio_avg_bitrate; /* average audio bitrate, in bits per second */
121 unsigned long audio_peak_bitrate; /* maximum bitrate */
122 unsigned long audio_headersize; /* offset to payload in audio frames */
123 long audio_min_associated; /* minimum offset to video frame, in bytes */
124 long audio_max_associated; /* maximum offset to video frame, in bytes */
125 unsigned long audio_reserved[7]; /* reserved, should be zero */
127 /* more to come... ? */
129 /* Note: padding up to 'blocksize' with zero following this header */
130 } tFileHeader;
132 typedef struct /* the little header for all audio blocks */
134 unsigned long magic; /* AUDIO_MAGIC indicates an audio block */
135 unsigned char previous_block; /* previous how many blocks backwards */
136 unsigned char next_block; /* next how many blocks forward */
137 short associated_video; /* offset to block with corresponding video */
138 unsigned short frame_start; /* offset to first frame starting in this block */
139 unsigned short frame_end; /* offset to behind last frame ending in this block */
140 } tAudioFrameHeader;
144 /****************** globals ******************/
146 static const struct plugin_api* rb; /* here is a global api struct pointer */
147 static char gPrint[32]; /* a global printf buffer, saves stack */
150 /* playstate */
151 static struct
153 enum
155 paused,
156 playing,
157 } state;
158 bool bAudioUnderrun;
159 bool bVideoUnderrun;
160 bool bHasAudio;
161 bool bHasVideo;
162 int nTimeOSD; /* OSD should stay for this many frames */
163 bool bDirtyOSD; /* OSD needs redraw */
164 bool bRefilling; /* set if refilling buffer */
165 bool bSeeking;
166 int nSeekAcc; /* accelleration value for seek */
167 int nSeekPos; /* current file position for seek */
168 bool bDiskSleep; /* disk is suspended */
169 #if FREQ == 12000000 /* Ondio speed kludge */
170 int nFrameTimeAdjusted;
171 #endif
172 } gPlay;
174 /* buffer information */
175 static struct
177 ssize_t bufsize;
178 int granularity; /* common multiple of block and sector size */
179 unsigned char* pBufStart; /* start of ring buffer */
180 unsigned char* pBufEnd; /* end of ring buffer */
181 unsigned char* pOSD; /* OSD memory (112 bytes for 112*8 pixels) */
183 int vidcount; /* how many video blocks are known in a row */
184 unsigned char* pBufFill; /* write pointer for disk, owned by main task */
185 unsigned char* pReadVideo; /* video readout, maintained by timer ISR */
186 unsigned char* pReadAudio; /* audio readout, maintained by demand ISR */
187 bool bEOF; /* flag for end of file */
188 int low_water; /* reload threshold */
189 int high_water; /* end of reload threshold */
190 int spinup_safety; /* safety margin when recalculating low_water */
191 int nReadChunk; /* how much data for normal buffer fill */
192 int nSeekChunk; /* how much data while seeking */
193 } gBuf;
195 /* statistics */
196 static struct
198 int minAudioAvail;
199 int minVideoAvail;
200 int nAudioUnderruns;
201 int nVideoUnderruns;
202 long minSpinup;
203 long maxSpinup;
204 } gStats;
206 tFileHeader gFileHdr; /* file header */
208 /****************** implementation ******************/
210 /* tool function: return how much playable audio/video is left */
211 int Available(unsigned char* pSnapshot)
213 if (pSnapshot <= gBuf.pBufFill)
214 return gBuf.pBufFill - pSnapshot;
215 else
216 return gBuf.bufsize - (pSnapshot - gBuf.pBufFill);
219 /* debug function to draw buffer indicators */
220 void DrawBuf(void)
222 int fill, video, audio;
224 rb->memset(gBuf.pOSD, 0x10, LCD_WIDTH); /* draw line */
225 gBuf.pOSD[0] = gBuf.pOSD[LCD_WIDTH-1] = 0xFE; /* ends */
227 /* calculate new tick positions */
228 fill = 1 + ((gBuf.pBufFill - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize;
229 video = 1 + ((gBuf.pReadVideo - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize;
230 audio = 1 + ((gBuf.pReadAudio - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize;
232 gBuf.pOSD[fill] |= 0x20; /* below the line, two pixels */
233 gBuf.pOSD[video] |= 0x08; /* one above */
234 gBuf.pOSD[audio] |= 0x04; /* two above */
236 if (gPlay.state == paused) /* we have to draw ourselves */
237 rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8);
238 else
239 gPlay.bDirtyOSD = true; /* redraw it with next timer IRQ */
243 /* helper function to draw a position indicator */
244 void DrawPosition(int pos, int total)
246 int w,h;
247 int sec; /* estimated seconds */
250 /* print the estimated position */
251 sec = pos / (gFileHdr.bps_average/8);
252 if (sec < 100*60) /* fits into mm:ss format */
253 rb->snprintf(gPrint, sizeof(gPrint), "%02d:%02dm", sec/60, sec%60);
254 else /* a very long clip, hh:mm format */
255 rb->snprintf(gPrint, sizeof(gPrint), "%02d:%02dh", sec/3600, (sec/60)%60);
256 rb->lcd_puts(0, 7, gPrint);
258 /* draw a slider over the rest of the line */
259 rb->lcd_getstringsize(gPrint, &w, &h);
260 w++;
261 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],w, LCD_HEIGHT-7, LCD_WIDTH-w,
262 7, total, 0, pos, HORIZONTAL);
264 if (gPlay.state == paused) /* we have to draw ourselves */
265 rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8);
266 else /* let the display time do it */
268 gPlay.nTimeOSD = 70;
269 gPlay.bDirtyOSD = true; /* redraw it with next timer IRQ */
274 /* helper function to change the volume by a certain amount, +/- */
275 void ChangeVolume(int delta)
277 int minvol = rb->sound_min(SOUND_VOLUME);
278 int maxvol = rb->sound_max(SOUND_VOLUME);
279 int vol = rb->global_settings->volume + delta;
281 if (vol > maxvol) vol = maxvol;
282 else if (vol < minvol) vol = minvol;
283 if (vol != rb->global_settings->volume)
285 rb->sound_set(SOUND_VOLUME, vol);
286 rb->global_settings->volume = vol;
287 rb->snprintf(gPrint, sizeof(gPrint), "Vol: %d dB", vol);
288 rb->lcd_puts(0, 7, gPrint);
289 if (gPlay.state == paused) /* we have to draw ourselves */
290 rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8);
291 else /* let the display time do it */
293 gPlay.nTimeOSD = 50; /* display it for 50 frames */
294 gPlay.bDirtyOSD = true; /* let the refresh copy it to LCD */
300 /* helper function to change the LCD contrast by a certain amount, +/- */
301 void ChangeContrast(int delta)
303 static int mycontrast = -1; /* the "permanent" value while running */
304 int contrast; /* updated value */
306 if (mycontrast == -1)
307 mycontrast = rb->global_settings->contrast;
309 contrast = mycontrast + delta;
310 if (contrast > 63) contrast = 63;
311 else if (contrast < 5) contrast = 5;
312 if (contrast != mycontrast)
314 rb->lcd_set_contrast(contrast);
315 mycontrast = contrast;
316 rb->snprintf(gPrint, sizeof(gPrint), "Contrast: %d", contrast);
317 rb->lcd_puts(0, 7, gPrint);
318 if (gPlay.state == paused) /* we have to draw ourselves */
319 rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8);
320 else /* let the display time do it */
322 gPlay.nTimeOSD = 50; /* display it for 50 frames */
323 gPlay.bDirtyOSD = true; /* let the refresh copy it to LCD */
329 /* sync the video to the current audio */
330 void SyncVideo(void)
332 tAudioFrameHeader* pAudioBuf;
334 pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadAudio);
335 if (pAudioBuf->magic == AUDIO_MAGIC)
337 gBuf.vidcount = 0; /* nothing known */
338 /* sync the video position */
339 gBuf.pReadVideo = gBuf.pReadAudio +
340 (long)pAudioBuf->associated_video * (long)gFileHdr.blocksize;
342 /* handle possible wrap */
343 if (gBuf.pReadVideo >= gBuf.pBufEnd)
344 gBuf.pReadVideo -= gBuf.bufsize;
345 else if (gBuf.pReadVideo < gBuf.pBufStart)
346 gBuf.pReadVideo += gBuf.bufsize;
351 /* timer interrupt handler to display a frame */
352 void timer4_isr(void)
354 int available;
355 tAudioFrameHeader* pAudioBuf;
356 int height; /* height to display */
358 /* reduce height if we have OSD on */
359 height = gFileHdr.video_height/8;
360 if (gPlay.nTimeOSD > 0)
362 gPlay.nTimeOSD--;
363 height = MIN(LCD_HEIGHT/8-1, height); /* reserve bottom line */
364 if (gPlay.bDirtyOSD)
365 { /* OSD to bottom line */
366 rb->lcd_blit_mono(gBuf.pOSD, 0, LCD_HEIGHT/8-1,
367 LCD_WIDTH, 1, LCD_WIDTH);
368 gPlay.bDirtyOSD = false;
372 rb->lcd_blit_mono(gBuf.pReadVideo, 0, 0,
373 gFileHdr.video_width, height, gFileHdr.video_width);
375 available = Available(gBuf.pReadVideo);
377 /* loop to skip audio frame(s) */
378 while(1)
380 /* just for the statistics */
381 if (!gBuf.bEOF && available < gStats.minVideoAvail)
382 gStats.minVideoAvail = available;
384 if (available <= (int)gFileHdr.blocksize)
385 { /* no data for next frame */
387 if (gBuf.bEOF && (gFileHdr.flags & FLAG_LOOP))
388 { /* loop now, assuming the looped clip fits in memory */
389 gBuf.pReadVideo = gBuf.pBufStart + gFileHdr.video_1st_frame;
390 /* FixMe: pReadVideo is incremented below */
392 else
394 gPlay.bVideoUnderrun = true;
395 rb->timer_unregister(); /* disable ourselves */
396 return; /* no data available */
399 else /* normal advance for next time */
401 gBuf.pReadVideo += gFileHdr.blocksize;
402 if (gBuf.pReadVideo >= gBuf.pBufEnd)
403 gBuf.pReadVideo -= gBuf.bufsize; /* wraparound */
404 available -= gFileHdr.blocksize;
407 if (!gPlay.bHasAudio)
408 break; /* no need to skip any audio */
410 if (gBuf.vidcount)
412 /* we know the next is a video frame */
413 gBuf.vidcount--;
414 break; /* exit the loop */
417 pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadVideo);
418 if (pAudioBuf->magic == AUDIO_MAGIC)
419 { /* we ran into audio, can happen after seek */
420 gBuf.vidcount = pAudioBuf->next_block;
421 if (gBuf.vidcount)
422 gBuf.vidcount--; /* minus the audio block */
424 } /* while */
428 /* ISR function to get more mp3 data */
429 void GetMoreMp3(unsigned char** start, size_t* size)
431 int available;
432 int advance;
434 tAudioFrameHeader* pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadAudio);
436 advance = pAudioBuf->next_block * gFileHdr.blocksize;
438 available = Available(gBuf.pReadAudio);
440 /* just for the statistics */
441 if (!gBuf.bEOF && available < gStats.minAudioAvail)
442 gStats.minAudioAvail = available;
444 if (available < advance + (int)gFileHdr.blocksize || advance == 0)
446 gPlay.bAudioUnderrun = true;
447 return; /* no data available */
450 gBuf.pReadAudio += advance;
451 if (gBuf.pReadAudio >= gBuf.pBufEnd)
452 gBuf.pReadAudio -= gBuf.bufsize; /* wraparound */
454 *start = gBuf.pReadAudio + gFileHdr.audio_headersize;
455 *size = gFileHdr.blocksize - gFileHdr.audio_headersize;
459 int WaitForButton(void)
461 int button;
465 button = rb->button_get(true);
466 rb->default_event_handler(button);
467 } while ((button & BUTTON_REL) && button != SYS_USB_CONNECTED);
469 return button;
473 bool WantResume(int fd)
475 int button;
477 rb->lcd_puts(0, 0, "Resume to this");
478 rb->lcd_puts(0, 1, "last position?");
479 rb->lcd_puts(0, 2, "PLAY = yes");
480 rb->lcd_puts(0, 3, "Any Other = no");
481 rb->lcd_puts(0, 4, " (plays from start)");
482 DrawPosition(gFileHdr.resume_pos, rb->filesize(fd));
483 rb->lcd_update();
485 button = WaitForButton();
486 return (button == VIDEO_RESUME);
490 int SeekTo(int fd, int nPos)
492 int read_now, got_now;
494 if (gPlay.bHasAudio)
495 rb->mp3_play_stop(); /* stop audio ISR */
496 if (gPlay.bHasVideo)
497 rb->timer_unregister(); /* stop the timer */
499 rb->lseek(fd, nPos, SEEK_SET);
501 gBuf.pBufFill = gBuf.pBufStart; /* all empty */
502 gBuf.pReadVideo = gBuf.pReadAudio = gBuf.pBufStart;
504 read_now = gBuf.low_water - 1; /* less than low water, so loading will continue */
505 read_now -= read_now % gBuf.granularity; /* round down to granularity */
506 got_now = rb->read(fd, gBuf.pBufFill, read_now);
507 gBuf.bEOF = (read_now != got_now);
508 gBuf.pBufFill += got_now;
510 if (nPos == 0)
511 { /* we seeked to the start */
512 if (gPlay.bHasVideo)
513 gBuf.pReadVideo += gFileHdr.video_1st_frame;
515 if (gPlay.bHasAudio)
516 gBuf.pReadAudio += gFileHdr.audio_1st_frame;
518 else
519 { /* we have to search for the positions */
520 if (gPlay.bHasAudio) /* prepare audio playback, if contained */
522 /* search for audio frame */
523 while (((tAudioFrameHeader*)(gBuf.pReadAudio))->magic != AUDIO_MAGIC)
524 gBuf.pReadAudio += gFileHdr.blocksize;
526 if (gPlay.bHasVideo)
527 SyncVideo(); /* pick the right video for that */
531 /* synchronous start */
532 gPlay.state = playing;
533 if (gPlay.bHasAudio)
535 gPlay.bAudioUnderrun = false;
536 rb->mp3_play_data(gBuf.pReadAudio + gFileHdr.audio_headersize,
537 gFileHdr.blocksize - gFileHdr.audio_headersize, GetMoreMp3);
538 rb->mp3_play_pause(true); /* kickoff audio */
540 if (gPlay.bHasVideo)
542 gPlay.bVideoUnderrun = false;
543 /* start display interrupt */
544 #if FREQ == 12000000 /* Ondio speed kludge */
545 rb->timer_register(1, NULL, gPlay.nFrameTimeAdjusted, 1,
546 timer4_isr IF_COP(, CPU));
547 #else
548 rb->timer_register(1, NULL, gFileHdr.video_frametime, 1,
549 timer4_isr IF_COP(, CPU));
550 #endif
553 return 0;
556 /* called from default_event_handler_ex() or at end of playback */
557 void Cleanup(void *fd)
559 rb->close(*(int*)fd); /* close the file */
561 if (gPlay.bHasVideo)
562 rb->timer_unregister(); /* stop video ISR, now I can use the display again */
564 if (gPlay.bHasAudio)
565 rb->mp3_play_stop(); /* stop audio ISR */
567 /* Turn on backlight timeout (revert to settings) */
568 backlight_use_settings(rb); /* backlight control in lib/helper.c */
570 /* restore normal contrast */
571 rb->lcd_set_contrast(rb->global_settings->contrast);
574 /* returns >0 if continue, =0 to stop, <0 to abort (USB) */
575 int PlayTick(int fd)
577 int button;
578 static int lastbutton = 0;
579 int avail_audio = -1, avail_video = -1;
580 int retval = 1;
581 int filepos;
583 /* check buffer level */
585 if (gPlay.bHasAudio)
586 avail_audio = Available(gBuf.pReadAudio);
587 if (gPlay.bHasVideo)
588 avail_video = Available(gBuf.pReadVideo);
590 if ((gPlay.bHasAudio && avail_audio < gBuf.low_water)
591 || (gPlay.bHasVideo && avail_video < gBuf.low_water))
593 gPlay.bRefilling = true; /* go to refill mode */
596 if ((!gPlay.bHasAudio || gPlay.bAudioUnderrun)
597 && (!gPlay.bHasVideo || gPlay.bVideoUnderrun)
598 && gBuf.bEOF)
600 if (gFileHdr.resume_pos)
601 { /* we played til the end, clear resume position */
602 gFileHdr.resume_pos = 0;
603 rb->lseek(fd, 0, SEEK_SET); /* save resume position */
604 rb->write(fd, &gFileHdr, sizeof(gFileHdr));
606 Cleanup(&fd);
607 return 0; /* all expired */
610 if (!gPlay.bRefilling || gBuf.bEOF)
611 { /* nothing to do */
612 button = rb->button_get_w_tmo(HZ/10);
614 else
615 { /* refill buffer */
616 int read_now, got_now;
617 int buf_free;
618 long spinup; /* measure the spinup time */
620 /* how much can we reload, don't fill completely, would appear empty */
621 buf_free = gBuf.bufsize - MAX(avail_audio, avail_video) - gBuf.high_water;
622 if (buf_free < 0)
623 buf_free = 0; /* just for safety */
624 buf_free -= buf_free % gBuf.granularity; /* round down to granularity */
626 /* in one piece max. up to buffer end (wrap after that) */
627 read_now = MIN(buf_free, gBuf.pBufEnd - gBuf.pBufFill);
629 /* load piecewise, to stay responsive */
630 read_now = MIN(read_now, gBuf.nReadChunk);
632 if (read_now == buf_free)
633 gPlay.bRefilling = false; /* last piece requested */
635 spinup = *rb->current_tick; /* in case this is interesting below */
637 got_now = rb->read(fd, gBuf.pBufFill, read_now);
638 if (got_now != read_now || read_now == 0)
640 gBuf.bEOF = true;
641 gPlay.bRefilling = false;
644 if (gPlay.bDiskSleep) /* statistics about the spinup time */
646 spinup = *rb->current_tick - spinup;
647 gPlay.bDiskSleep = false;
648 if (spinup > gStats.maxSpinup)
649 gStats.maxSpinup = spinup;
650 if (spinup < gStats.minSpinup)
651 gStats.minSpinup = spinup;
653 /* recalculate the low water mark from real measurements */
654 gBuf.low_water = (gStats.maxSpinup + gBuf.spinup_safety)
655 * gFileHdr.bps_peak / 8 / HZ;
658 if (!gPlay.bRefilling
659 #ifndef HAVE_FLASH_STORAGE
660 && rb->global_settings->disk_spindown < 20 /* condition for test only */
661 #endif
664 rb->ata_sleep(); /* no point in leaving the disk run til timeout */
665 gPlay.bDiskSleep = true;
668 gBuf.pBufFill += got_now;
669 if (gBuf.pBufFill >= gBuf.pBufEnd)
670 gBuf.pBufFill = gBuf.pBufStart; /* wrap */
672 rb->yield(); /* have mercy with the other threads */
673 button = rb->button_get(false);
676 /* check keypresses */
678 if (button != BUTTON_NONE)
680 filepos = rb->lseek(fd, 0, SEEK_CUR);
682 if (gPlay.bHasVideo) /* video position is more accurate */
683 filepos -= Available(gBuf.pReadVideo); /* take video position */
684 else
685 filepos -= Available(gBuf.pReadAudio); /* else audio */
687 switch (button) { /* set exit conditions */
688 case BUTTON_OFF:
689 if (gFileHdr.magic == HEADER_MAGIC /* only if file has header */
690 && !(gFileHdr.flags & FLAG_LOOP)) /* not for stills */
692 gFileHdr.resume_pos = filepos;
693 rb->lseek(fd, 0, SEEK_SET); /* save resume position */
694 rb->write(fd, &gFileHdr, sizeof(gFileHdr));
696 Cleanup(&fd);
697 retval = 0; /* signal "stopped" to caller */
698 break;
699 case VIDEO_STOP_SEEK:
700 #ifdef VIDEO_STOP_SEEK_PRE
701 if (lastbutton != VIDEO_STOP_SEEK_PRE)
702 break;
703 #endif
704 if (gPlay.bSeeking)
706 gPlay.bSeeking = false;
707 gPlay.state = playing;
708 SeekTo(fd, gPlay.nSeekPos);
710 else if (gPlay.state == playing)
712 gPlay.state = paused;
713 if (gPlay.bHasAudio)
714 rb->mp3_play_pause(false); /* pause audio */
715 if (gPlay.bHasVideo)
716 rb->timer_unregister(); /* stop the timer */
718 else if (gPlay.state == paused)
720 gPlay.state = playing;
721 if (gPlay.bHasAudio)
723 if (gPlay.bHasVideo)
724 SyncVideo();
725 rb->mp3_play_pause(true); /* play audio */
727 if (gPlay.bHasVideo)
728 { /* start the video */
729 #if FREQ == 12000000 /* Ondio speed kludge */
730 rb->timer_register(1, NULL,
731 gPlay.nFrameTimeAdjusted, 1, timer4_isr);
732 #else
733 rb->timer_register(1, NULL,
734 gFileHdr.video_frametime, 1, timer4_isr);
735 #endif
738 break;
739 case BUTTON_UP:
740 case BUTTON_UP | BUTTON_REPEAT:
741 if (gPlay.bHasAudio)
742 ChangeVolume(1);
743 break;
744 case BUTTON_DOWN:
745 case BUTTON_DOWN | BUTTON_REPEAT:
746 if (gPlay.bHasAudio)
747 ChangeVolume(-1);
748 break;
749 case BUTTON_LEFT:
750 case BUTTON_LEFT | BUTTON_REPEAT:
751 if (!gPlay.bSeeking) /* prepare seek */
753 gPlay.nSeekPos = filepos;
754 gPlay.bSeeking = true;
755 gPlay.nSeekAcc = 0;
757 else if (gPlay.nSeekAcc > 0) /* other direction, stop sliding */
758 gPlay.nSeekAcc = 0;
759 else
760 gPlay.nSeekAcc--;
761 break;
762 case BUTTON_RIGHT:
763 case BUTTON_RIGHT | BUTTON_REPEAT:
764 if (!gPlay.bSeeking) /* prepare seek */
766 gPlay.nSeekPos = filepos;
767 gPlay.bSeeking = true;
768 gPlay.nSeekAcc = 0;
770 else if (gPlay.nSeekAcc < 0) /* other direction, stop sliding */
771 gPlay.nSeekAcc = 0;
772 else
773 gPlay.nSeekAcc++;
774 break;
775 #ifdef VIDEO_DEBUG
776 case VIDEO_DEBUG: /* debug key */
777 case VIDEO_DEBUG | BUTTON_REPEAT:
778 DrawBuf(); /* show buffer status */
779 gPlay.nTimeOSD = 30;
780 gPlay.bDirtyOSD = true;
781 break;
782 #endif
783 case VIDEO_CONTRAST_DOWN: /* contrast down */
784 case VIDEO_CONTRAST_DOWN | BUTTON_REPEAT:
785 if (gPlay.bHasVideo)
786 ChangeContrast(-1);
787 break;
788 case VIDEO_CONTRAST_UP: /* contrast up */
789 case VIDEO_CONTRAST_UP | BUTTON_REPEAT:
790 if (gPlay.bHasVideo)
791 ChangeContrast(1);
792 break;
793 default:
794 if (rb->default_event_handler_ex(button, Cleanup, &fd)
795 == SYS_USB_CONNECTED)
796 retval = -1; /* signal "aborted" to caller */
797 break;
800 lastbutton = button;
801 } /* if (button != BUTTON_NONE) */
804 /* handle seeking */
806 if (gPlay.bSeeking) /* seeking? */
808 if (gPlay.nSeekAcc < -MAX_ACC)
809 gPlay.nSeekAcc = -MAX_ACC;
810 else if (gPlay.nSeekAcc > MAX_ACC)
811 gPlay.nSeekAcc = MAX_ACC;
813 gPlay.nSeekPos += gPlay.nSeekAcc * gBuf.nSeekChunk;
814 if (gPlay.nSeekPos < 0)
815 gPlay.nSeekPos = 0;
816 if (gPlay.nSeekPos > rb->filesize(fd) - gBuf.granularity)
818 gPlay.nSeekPos = rb->filesize(fd);
819 gPlay.nSeekPos -= gPlay.nSeekPos % gBuf.granularity;
821 DrawPosition(gPlay.nSeekPos, rb->filesize(fd));
825 /* check + recover underruns */
827 if ((gPlay.bAudioUnderrun || gPlay.bVideoUnderrun) && !gBuf.bEOF)
829 gBuf.spinup_safety += HZ/2; /* add extra spinup time for the future */
830 filepos = rb->lseek(fd, 0, SEEK_CUR);
832 if (gPlay.bHasVideo && gPlay.bVideoUnderrun)
834 gStats.nVideoUnderruns++;
835 filepos -= Available(gBuf.pReadVideo); /* take video position */
836 SeekTo(fd, filepos);
838 else if (gPlay.bHasAudio && gPlay.bAudioUnderrun)
840 gStats.nAudioUnderruns++;
841 filepos -= Available(gBuf.pReadAudio); /* else audio */
842 SeekTo(fd, filepos);
846 return retval;
850 int main(char* filename)
852 int file_size;
853 int fd; /* file descriptor handle */
854 int read_now, got_now;
855 int button = 0;
856 int retval;
858 /* try to open the file */
859 fd = rb->open(filename, O_RDWR);
860 if (fd < 0)
861 return PLUGIN_ERROR;
862 file_size = rb->filesize(fd);
864 /* reset pitch value to ensure synchronous playback */
865 rb->sound_set_pitch(1000);
867 /* init statistics */
868 rb->memset(&gStats, 0, sizeof(gStats));
869 gStats.minAudioAvail = gStats.minVideoAvail = INT_MAX;
870 gStats.minSpinup = INT_MAX;
872 /* init playback state */
873 rb->memset(&gPlay, 0, sizeof(gPlay));
875 /* init buffer */
876 rb->memset(&gBuf, 0, sizeof(gBuf));
877 gBuf.pOSD = rb->lcd_framebuffer + LCD_WIDTH*7; /* last screen line */
878 gBuf.pBufStart = rb->plugin_get_audio_buffer((size_t *)&gBuf.bufsize);
879 /*gBuf.bufsize = 1700*1024; // test, like 2MB version!!!! */
880 gBuf.pBufFill = gBuf.pBufStart; /* all empty */
882 /* load file header */
883 read_now = sizeof(gFileHdr);
884 got_now = rb->read(fd, &gFileHdr, read_now);
885 rb->lseek(fd, 0, SEEK_SET); /* rewind to restart sector-aligned */
886 if (got_now != read_now)
888 rb->close(fd);
889 return (PLUGIN_ERROR);
892 /* check header */
893 if (gFileHdr.magic != HEADER_MAGIC)
894 { /* old file, use default info */
895 rb->memset(&gFileHdr, 0, sizeof(gFileHdr));
896 gFileHdr.blocksize = SCREENSIZE;
897 if (file_size < SCREENSIZE * FPS) /* less than a second */
898 gFileHdr.flags |= FLAG_LOOP;
899 gFileHdr.video_format = VIDEOFORMAT_RAW;
900 gFileHdr.video_width = LCD_WIDTH;
901 gFileHdr.video_height = LCD_HEIGHT;
902 gFileHdr.video_frametime = 11059200 / FPS;
903 gFileHdr.bps_peak = gFileHdr.bps_average = LCD_WIDTH * LCD_HEIGHT * FPS;
906 #if FREQ == 12000000 /* Ondio speed kludge, 625 / 576 == 12000000 / 11059200 */
907 gPlay.nFrameTimeAdjusted = (gFileHdr.video_frametime * 625) / 576;
908 #endif
910 /* continue buffer init: align the end, calc low water, read sizes */
911 gBuf.granularity = gFileHdr.blocksize;
912 while (gBuf.granularity % 512) /* common multiple of sector size */
913 gBuf.granularity *= 2;
914 gBuf.bufsize -= gBuf.bufsize % gBuf.granularity; /* round down */
915 gBuf.pBufEnd = gBuf.pBufStart + gBuf.bufsize;
916 gBuf.low_water = SPINUP_INIT * gFileHdr.bps_peak / 8000;
917 gBuf.spinup_safety = SPINUP_SAFETY * HZ / 1000; /* in time ticks */
918 if (gFileHdr.audio_min_associated < 0)
919 gBuf.high_water = 0 - gFileHdr.audio_min_associated;
920 else
921 gBuf.high_water = 1; /* never fill buffer completely, would appear empty */
922 gBuf.nReadChunk = (CHUNK + gBuf.granularity - 1); /* round up */
923 gBuf.nReadChunk -= gBuf.nReadChunk % gBuf.granularity;/* and align */
924 gBuf.nSeekChunk = rb->filesize(fd) / FF_TICKS;
925 gBuf.nSeekChunk += gBuf.granularity - 1; /* round up */
926 gBuf.nSeekChunk -= gBuf.nSeekChunk % gBuf.granularity; /* and align */
928 /* prepare video playback, if contained */
929 if (gFileHdr.video_format == VIDEOFORMAT_RAW)
931 gPlay.bHasVideo = true;
932 /* Turn off backlight timeout */
933 backlight_force_on(rb); /* backlight control in lib/helper.c */
936 /* prepare audio playback, if contained */
937 if (gFileHdr.audio_format == AUDIOFORMAT_MP3_BITSWAPPED)
939 gPlay.bHasAudio = true;
942 /* start playback by seeking to zero or resume position */
943 if (gFileHdr.resume_pos && WantResume(fd)) /* ask the user */
944 SeekTo(fd, gFileHdr.resume_pos);
945 else
946 SeekTo(fd, 0);
948 /* all that's left to do is keep the buffer full */
949 do /* the main loop */
951 retval = PlayTick(fd);
952 } while (retval > 0);
954 if (retval < 0) /* aborted? */
956 return PLUGIN_USB_CONNECTED;
959 #ifndef DEBUG /* for release compilations, only display the stats in case of error */
960 if (gStats.nAudioUnderruns || gStats.nVideoUnderruns)
961 #endif
963 /* display statistics */
964 rb->lcd_clear_display();
965 rb->snprintf(gPrint, sizeof(gPrint), "%d Audio Underruns", gStats.nAudioUnderruns);
966 rb->lcd_puts(0, 0, gPrint);
967 rb->snprintf(gPrint, sizeof(gPrint), "%d Video Underruns", gStats.nVideoUnderruns);
968 rb->lcd_puts(0, 1, gPrint);
969 rb->snprintf(gPrint, sizeof(gPrint), "%d MinAudio bytes", gStats.minAudioAvail);
970 rb->lcd_puts(0, 2, gPrint);
971 rb->snprintf(gPrint, sizeof(gPrint), "%d MinVideo bytes", gStats.minVideoAvail);
972 rb->lcd_puts(0, 3, gPrint);
973 rb->snprintf(gPrint, sizeof(gPrint), "MinSpinup %ld.%02ld", gStats.minSpinup/HZ, gStats.minSpinup%HZ);
974 rb->lcd_puts(0, 4, gPrint);
975 rb->snprintf(gPrint, sizeof(gPrint), "MaxSpinup %ld.%02ld", gStats.maxSpinup/HZ, gStats.maxSpinup%HZ);
976 rb->lcd_puts(0, 5, gPrint);
977 rb->snprintf(gPrint, sizeof(gPrint), "LowWater: %d", gBuf.low_water);
978 rb->lcd_puts(0, 6, gPrint);
979 rb->snprintf(gPrint, sizeof(gPrint), "HighWater: %d", gBuf.high_water);
980 rb->lcd_puts(0, 7, gPrint);
982 rb->lcd_update();
983 button = WaitForButton();
985 return (button == SYS_USB_CONNECTED) ? PLUGIN_USB_CONNECTED : PLUGIN_OK;
989 /***************** Plugin Entry Point *****************/
991 enum plugin_status plugin_start(const struct plugin_api* api, const void* parameter)
993 rb = api; /* copy to global api pointer */
995 if (parameter == NULL)
997 rb->splash(HZ*2, "Play .rvf file!");
998 return PLUGIN_ERROR;
1001 /* now go ahead and have fun! */
1002 return main((char*) parameter);
1005 #endif /* #ifdef HAVE_LCD_BITMAP */
1006 #endif /* #ifndef SIMULATOR */