1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ******************/
32 #include "lib/helper.h"
34 #ifdef HAVE_LCD_BITMAP /* and definitely not for the Player, haha */
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)
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
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 */
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 */
143 /****************** globals ******************/
145 static char gPrint
[32]; /* a global printf buffer, saves stack */
160 int nTimeOSD
; /* OSD should stay for this many frames */
161 bool bDirtyOSD
; /* OSD needs redraw */
162 bool bRefilling
; /* set if refilling buffer */
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
;
172 /* buffer information */
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 */
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 */
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
;
215 return gBuf
.bufsize
- (pSnapshot
- gBuf
.pBufFill
);
218 /* debug function to draw buffer indicators */
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
);
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
)
253 int sec
; /* estimated seconds */
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
);
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)
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
);
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
);
350 /* sync the video to the current audio */
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)
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)
384 height
= MIN(gBuf
.osd_ypos
, height
);
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) */
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 */
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 */
432 /* we know the next is a video frame */
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
;
442 gBuf
.vidcount
--; /* minus the audio block */
448 /* ISR function to get more mp3 data */
449 void GetMoreMp3(unsigned char** start
, size_t* size
)
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)
485 button
= rb
->button_get(true);
486 rb
->default_event_handler(button
);
487 } while ((button
& BUTTON_REL
) && button
!= SYS_USB_CONNECTED
);
493 bool WantResume(int fd
)
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
));
505 button
= WaitForButton();
506 return (button
== VIDEO_RESUME
);
510 int SeekTo(int fd
, int nPos
)
512 int read_now
, got_now
;
515 rb
->mp3_play_stop(); /* stop audio ISR */
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
;
531 { /* we seeked to the start */
533 gBuf
.pReadVideo
+= gFileHdr
.video_1st_frame
;
536 gBuf
.pReadAudio
+= gFileHdr
.audio_1st_frame
;
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
;
547 SyncVideo(); /* pick the right video for that */
551 /* synchronous start */
552 gPlay
.state
= playing
;
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 */
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
));
568 rb
->timer_register(1, NULL
, gFileHdr
.video_frametime
,
569 timer4_isr
IF_COP(, CPU
));
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 */
582 rb
->timer_unregister(); /* stop video ISR, now I can use the display again */
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) */
598 static int lastbutton
= 0;
599 int avail_audio
= -1, avail_video
= -1;
603 /* check buffer level */
606 avail_audio
= Available(gBuf
.pReadAudio
);
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
)
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
));
627 return 0; /* all expired */
630 if (!gPlay
.bRefilling
|| gBuf
.bEOF
)
631 { /* nothing to do */
632 button
= rb
->button_get_w_tmo(HZ
/10);
635 { /* refill buffer */
636 int read_now
, got_now
;
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
;
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)
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 */
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 */
705 filepos
-= Available(gBuf
.pReadAudio
); /* else audio */
707 switch (button
) { /* set exit conditions */
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
));
717 retval
= 0; /* signal "stopped" to caller */
719 case VIDEO_STOP_SEEK
:
720 #ifdef VIDEO_STOP_SEEK_PRE
721 if (lastbutton
!= VIDEO_STOP_SEEK_PRE
)
726 gPlay
.bSeeking
= false;
727 gPlay
.state
= playing
;
728 SeekTo(fd
, gPlay
.nSeekPos
);
730 else if (gPlay
.state
== playing
)
732 gPlay
.state
= paused
;
734 rb
->mp3_play_pause(false); /* pause audio */
736 rb
->timer_unregister(); /* stop the timer */
738 else if (gPlay
.state
== paused
)
740 gPlay
.state
= playing
;
745 rb
->mp3_play_pause(true); /* play audio */
748 { /* start the video */
749 #if FREQ == 12000000 /* Ondio speed kludge */
750 rb
->timer_register(1, NULL
,
751 gPlay
.nFrameTimeAdjusted
, timer4_isr
);
753 rb
->timer_register(1, NULL
,
754 gFileHdr
.video_frametime
, timer4_isr
);
760 case BUTTON_UP
| BUTTON_REPEAT
:
765 case BUTTON_DOWN
| BUTTON_REPEAT
:
770 case BUTTON_LEFT
| BUTTON_REPEAT
:
771 if (!gPlay
.bSeeking
) /* prepare seek */
773 gPlay
.nSeekPos
= filepos
;
774 gPlay
.bSeeking
= true;
777 else if (gPlay
.nSeekAcc
> 0) /* other direction, stop sliding */
783 case BUTTON_RIGHT
| BUTTON_REPEAT
:
784 if (!gPlay
.bSeeking
) /* prepare seek */
786 gPlay
.nSeekPos
= filepos
;
787 gPlay
.bSeeking
= true;
790 else if (gPlay
.nSeekAcc
< 0) /* other direction, stop sliding */
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;
803 case VIDEO_CONTRAST_DOWN
: /* contrast down */
804 case VIDEO_CONTRAST_DOWN
| BUTTON_REPEAT
:
808 case VIDEO_CONTRAST_UP
: /* contrast up */
809 case VIDEO_CONTRAST_UP
| BUTTON_REPEAT
:
814 if (rb
->default_event_handler_ex(button
, Cleanup
, &fd
)
815 == SYS_USB_CONNECTED
)
816 retval
= -1; /* signal "aborted" to caller */
821 } /* if (button != BUTTON_NONE) */
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)
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 */
858 else if (gPlay
.bHasAudio
&& gPlay
.bAudioUnderrun
)
860 gStats
.nAudioUnderruns
++;
861 filepos
-= Available(gBuf
.pReadAudio
); /* else audio */
870 int main(char* filename
)
873 int fd
; /* file descriptor handle */
874 int read_now
, got_now
;
878 /* try to open the file */
879 fd
= rb
->open(filename
, O_RDWR
);
882 file_size
= rb
->filesize(fd
);
884 /* reset pitch value to ensure synchronous playback */
885 rb
->sound_set_pitch(1000);
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
));
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 */
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
)
913 return (PLUGIN_ERROR
);
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;
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
;
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
);
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
)
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
);
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 */