diff options
Diffstat (limited to 'apps/plugins/video.c')
-rw-r--r-- | apps/plugins/video.c | 1013 |
1 files changed, 742 insertions, 271 deletions
diff --git a/apps/plugins/video.c b/apps/plugins/video.c index 0800745b3c..3f21f7c9dd 100644 --- a/apps/plugins/video.c +++ b/apps/plugins/video.c | |||
@@ -9,6 +9,7 @@ | |||
9 | * | 9 | * |
10 | * Plugin for video playback | 10 | * Plugin for video playback |
11 | * Reads raw image data + audio data from a file | 11 | * Reads raw image data + audio data from a file |
12 | * !!!!!!!!!! Code Police free zone !!!!!!!!!! | ||
12 | * | 13 | * |
13 | * Copyright (C) 2003-2004 Jörg Hohensohn aka [IDC]Dragon | 14 | * Copyright (C) 2003-2004 Jörg Hohensohn aka [IDC]Dragon |
14 | * | 15 | * |
@@ -19,132 +20,439 @@ | |||
19 | * KIND, either express or implied. | 20 | * KIND, either express or implied. |
20 | * | 21 | * |
21 | ****************************************************************************/ | 22 | ****************************************************************************/ |
23 | |||
24 | |||
25 | /****************** imports ******************/ | ||
26 | |||
22 | #include "plugin.h" | 27 | #include "plugin.h" |
23 | #include "sh7034.h" | 28 | #include "sh7034.h" |
24 | #include "system.h" | 29 | #include "system.h" |
25 | #include "../apps/recorder/widgets.h" /* not in search path, booh */ | 30 | #include "../apps/recorder/widgets.h" // not in search path, booh |
31 | |||
32 | #ifndef SIMULATOR // not for simulator by now | ||
33 | #ifdef HAVE_LCD_BITMAP // and definitely not for the Player, haha | ||
34 | |||
35 | /****************** constants ******************/ | ||
36 | |||
37 | #define IMIA4 (*((volatile unsigned long*)0x09000180)) // timer 4 | ||
38 | |||
39 | #define INT_MAX ((int)(~(unsigned)0 >> 1)) | ||
40 | #define INT_MIN (-INT_MAX-1) | ||
41 | |||
42 | #define SCREENSIZE (LCD_WIDTH*LCD_HEIGHT/8) // in bytes | ||
43 | #define FPS 68 // default fps for headerless (old video-only) file | ||
44 | #define MAX_ACC 20 // maximum FF/FR speedup | ||
45 | #define FF_TICKS 3000; // experimentally found nice | ||
26 | 46 | ||
27 | #ifndef SIMULATOR /* not for simulator by now */ | 47 | // trigger levels, we need about 80 kB/sec |
28 | #ifdef HAVE_LCD_BITMAP /* and definitely not for the Player, haha */ | 48 | #define PRECHARGE (1024 * 64) // the initial filling before starting to play |
49 | #define SPINUP 2500 // 2200 // from what level on to refill, in milliseconds | ||
50 | #define CHUNK (1024*32) // read size | ||
29 | 51 | ||
30 | #define SCREENSIZE (LCD_WIDTH*LCD_HEIGHT/8) /* in bytes */ | ||
31 | #define FILEBUFSIZE (SCREENSIZE*4) /* must result in a multiple of 512 */ | ||
32 | #define FPS 71 /* desired framerate */ | ||
33 | #define WIND_MAX 9 /* max FF/FR speed */ | ||
34 | 52 | ||
35 | #define FRAMETIME (FREQ/8/FPS) /* internal timer4 value */ | 53 | /****************** prototypes ******************/ |
54 | void timer_set(unsigned period); // setup ISR and timer registers | ||
55 | void timer4_isr(void) __attribute__((interrupt_handler)); // IMIA4 ISR | ||
56 | int check_button(void); // determine next relative frame | ||
57 | |||
58 | |||
59 | /****************** data types ******************/ | ||
60 | |||
61 | // plugins don't introduce headers, so structs are repeated from rvf_format.h | ||
62 | |||
63 | #define HEADER_MAGIC 0x52564668 // "RVFh" at file start | ||
64 | #define AUDIO_MAGIC 0x41756446 // "AudF" for each audio block | ||
65 | #define FILEVERSION 100 // 1.00 | ||
66 | #define CLOCK 11059200 // SH CPU clock | ||
67 | |||
68 | // format type definitions | ||
69 | #define VIDEOFORMAT_NO_VIDEO 0 | ||
70 | #define VIDEOFORMAT_RAW 1 | ||
71 | #define AUDIOFORMAT_NO_AUDIO 0 | ||
72 | #define AUDIOFORMAT_MP3 1 | ||
73 | #define AUDIOFORMAT_MP3_BITSWAPPED 2 | ||
74 | |||
75 | // bit flags | ||
76 | #define FLAG_LOOP 0x00000001 // loop the playback, e.g. for stills | ||
77 | |||
78 | typedef struct // contains whatever might be useful to the player | ||
79 | { | ||
80 | // general info (16 entries = 64 byte) | ||
81 | unsigned long magic; // HEADER_MAGIC | ||
82 | unsigned long version; // file version | ||
83 | unsigned long flags; // combination of FLAG_xx | ||
84 | unsigned long blocksize; // how many bytes per block (=video frame) | ||
85 | unsigned long bps_average; // bits per second of the whole stream | ||
86 | unsigned long bps_peak; // max. of above (audio may be VBR) | ||
87 | unsigned long reserved[10]; // reserved, should be zero | ||
88 | |||
89 | // video info (16 entries = 64 byte) | ||
90 | unsigned long video_format; // one of VIDEOFORMAT_xxx | ||
91 | unsigned long video_1st_frame; // byte position of first video frame | ||
92 | unsigned long video_duration; // total length of video part, in ms | ||
93 | unsigned long video_payload_size; // total amount of video data, in bytes | ||
94 | unsigned long video_bitrate; // derived from resolution and frame time, in bps | ||
95 | unsigned long video_frametime; // frame interval in 11.0592 MHz clocks | ||
96 | long video_preroll; // video is how much ahead, in 11.0592 MHz clocks | ||
97 | unsigned long video_width; // in pixels | ||
98 | unsigned long video_height; // in pixels | ||
99 | unsigned long video_reserved[7]; // reserved, should be zero | ||
100 | |||
101 | // audio info (16 entries = 64 byte) | ||
102 | unsigned long audio_format; // one of AUDIOFORMAT_xxx | ||
103 | unsigned long audio_1st_frame; // byte position of first video frame | ||
104 | unsigned long audio_duration; // total length of audio part, in ms | ||
105 | unsigned long audio_payload_size; // total amount of audio data, in bytes | ||
106 | unsigned long audio_avg_bitrate; // average audio bitrate, in bits per second | ||
107 | unsigned long audio_peak_bitrate; // maximum bitrate | ||
108 | unsigned long audio_headersize; // offset to payload in audio frames | ||
109 | long audio_min_associated; // minimum offset to video frame, in bytes | ||
110 | long audio_max_associated; // maximum offset to video frame, in bytes | ||
111 | unsigned long audio_reserved[7]; // reserved, should be zero | ||
112 | |||
113 | // more to come... ? | ||
114 | |||
115 | // Note: padding up to 'blocksize' with zero following this header | ||
116 | } tFileHeader; | ||
117 | |||
118 | typedef struct // the little header for all audio blocks | ||
119 | { | ||
120 | unsigned long magic; // AUDIO_MAGIC indicates an audio block | ||
121 | unsigned char previous_block; // previous how many blocks backwards | ||
122 | unsigned char next_block; // next how many blocks forward | ||
123 | short associated_video; // offset to block with corresponding video | ||
124 | unsigned short frame_start; // offset to first frame starting in this block | ||
125 | unsigned short frame_end; // offset to behind last frame ending in this block | ||
126 | } tAudioFrameHeader; | ||
127 | |||
128 | |||
129 | |||
130 | /****************** globals ******************/ | ||
36 | 131 | ||
37 | /* globals */ | ||
38 | static struct plugin_api* rb; /* here is a global api struct pointer */ | 132 | static struct plugin_api* rb; /* here is a global api struct pointer */ |
133 | static char gPrint[32]; /* a global printf buffer, saves stack */ | ||
134 | |||
39 | 135 | ||
40 | static enum | 136 | // playstate |
137 | static struct | ||
138 | { | ||
139 | enum | ||
140 | { | ||
141 | playing, | ||
142 | paused, | ||
143 | } state; | ||
144 | bool bAudioUnderrun; | ||
145 | bool bVideoUnderrun; | ||
146 | bool bHasAudio; | ||
147 | bool bHasVideo; | ||
148 | int nTimeOSD; // OSD should stay for this many frames | ||
149 | bool bDirtyOSD; // OSD needs redraw | ||
150 | bool bRefilling; // set if refilling buffer | ||
151 | bool bSeeking; | ||
152 | int nSeekAcc; // accelleration value for seek | ||
153 | int nSeekPos; // current file position for seek | ||
154 | } gPlay; | ||
155 | |||
156 | // buffer information | ||
157 | static struct | ||
158 | { | ||
159 | int bufsize; | ||
160 | int granularity; // common multiple of block and sector size | ||
161 | unsigned char* pBufStart; // start of ring buffer | ||
162 | unsigned char* pBufEnd; // end of ring buffer | ||
163 | unsigned char* pOSD; // OSD memory (112 bytes for 112*8 pixels) | ||
164 | |||
165 | int vidcount; // how many video blocks are known in a row | ||
166 | unsigned char* pBufFill; // write pointer for disk, owned by main task | ||
167 | unsigned char* pReadVideo; // video readout, maintained by timer ISR | ||
168 | unsigned char* pReadAudio; // audio readout, maintained by demand ISR | ||
169 | bool bEOF; // flag for end of file | ||
170 | int low_water; // reload threshold | ||
171 | int high_water; // end of reload threshold | ||
172 | int nReadChunk; // how much data for normal buffer fill | ||
173 | int nSeekChunk; // how much data while seeking | ||
174 | } gBuf; | ||
175 | |||
176 | // statistics | ||
177 | static struct | ||
41 | { | 178 | { |
42 | playing, | 179 | int minAudioAvail; |
43 | paused, | 180 | int minVideoAvail; |
44 | stop, | 181 | int nAudioUnderruns; |
45 | exit | 182 | int nVideoUnderruns; |
46 | } state = playing; | 183 | } gStats; |
47 | 184 | ||
48 | static int playstep = 1; /* for current speed and direction */ | 185 | tFileHeader gFileHdr; // file header |
49 | static int acceleration = 0; | ||
50 | static long time; /* to calculate the playing time */ | ||
51 | 186 | ||
187 | /****************** implementation ******************/ | ||
52 | 188 | ||
53 | /* test for button, returns relative frame and may change state */ | 189 | // tool function: return how much playable audio/video is left |
54 | int check_button(void) | 190 | int Available(unsigned char* pSnapshot) |
55 | { | 191 | { |
56 | int button; | 192 | if (pSnapshot <= gBuf.pBufFill) |
57 | int frame; /* result: relative frame */ | 193 | return gBuf.pBufFill - pSnapshot; |
58 | bool loop; | 194 | else |
59 | 195 | return gBuf.bufsize - (pSnapshot - gBuf.pBufFill); | |
60 | frame = playstep; /* default */ | 196 | } |
61 | 197 | ||
62 | do | 198 | // debug function to draw buffer indicators |
199 | void DrawBuf(void) | ||
200 | { | ||
201 | static int old_fill = -1; // indicate not initialized | ||
202 | static int old_video; | ||
203 | static int old_audio; | ||
204 | int fill, video, audio; | ||
205 | |||
206 | // first call? | ||
207 | if (old_fill == -1) | ||
63 | { | 208 | { |
64 | loop = false; | 209 | rb->memset(gBuf.pOSD, 0x10, LCD_WIDTH); // draw line |
65 | if (state == playing) | 210 | gBuf.pOSD[0] = gBuf.pOSD[LCD_WIDTH-1] = 0xFE; // ends |
66 | button = rb->button_get(false); | 211 | old_fill = 1; // do no harm below |
67 | else | 212 | } |
213 | |||
214 | // calculate new tick positions | ||
215 | fill = 1 + ((gBuf.pBufFill - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize; | ||
216 | video = 1 + ((gBuf.pReadVideo - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize; | ||
217 | audio = 1 + ((gBuf.pReadAudio - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize; | ||
218 | |||
219 | if (fill != old_fill || video != old_video || audio != old_audio) | ||
220 | { | ||
221 | // erase old ticks | ||
222 | gBuf.pOSD[old_fill] = 0x10; | ||
223 | gBuf.pOSD[old_video] = 0x10; | ||
224 | gBuf.pOSD[old_audio] = 0x10; | ||
225 | |||
226 | gBuf.pOSD[fill] |= 0x20; // below the line, two pixels | ||
227 | gBuf.pOSD[video] |= 0x08; // one above | ||
228 | gBuf.pOSD[audio] |= 0x04; // two above | ||
229 | |||
230 | old_fill = fill; | ||
231 | old_video = video; | ||
232 | old_audio = audio; | ||
233 | |||
234 | gPlay.bDirtyOSD = true; // redraw it with next timer IRQ | ||
235 | } | ||
236 | } | ||
237 | |||
238 | |||
239 | // helper function to draw a position indicator | ||
240 | void DrawPosition(int pos, int total) | ||
241 | { | ||
242 | int w,h; | ||
243 | int sec; // estimated seconds | ||
244 | int percent; | ||
245 | |||
246 | |||
247 | /* print the estimated position */ | ||
248 | sec = pos / (gFileHdr.bps_average/8); | ||
249 | if (sec < 100*60) /* fits into mm:ss format */ | ||
250 | rb->snprintf(gPrint, sizeof(gPrint), "%02d:%02dm", sec/60, sec%60); | ||
251 | else /* a very long clip, hh:mm format */ | ||
252 | rb->snprintf(gPrint, sizeof(gPrint), "%02d:%02dh", sec/3600, (sec/60)%60); | ||
253 | rb->lcd_puts(0, 7, gPrint); | ||
254 | |||
255 | /* draw a slider over the rest of the line */ | ||
256 | rb->lcd_getstringsize(gPrint, &w, &h); | ||
257 | w++; | ||
258 | percent = pos/(total/100); | ||
259 | rb->slidebar(w, LCD_HEIGHT-7, LCD_WIDTH-w, 7, percent, Grow_Right); | ||
260 | |||
261 | if (gPlay.state == paused) // we have to draw ourselves | ||
262 | rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8); | ||
263 | else // let the display time do it | ||
264 | { | ||
265 | gPlay.nTimeOSD = 70; | ||
266 | gPlay.bDirtyOSD = true; // redraw it with next timer IRQ | ||
267 | } | ||
268 | } | ||
269 | |||
270 | |||
271 | // helper function to change the volume by a certain amount, +/- | ||
272 | void ChangeVolume(int delta) | ||
273 | { | ||
274 | int vol = rb->global_settings->volume + delta; | ||
275 | |||
276 | if (vol > 100) vol = 100; | ||
277 | else if (vol < 0) vol = 0; | ||
278 | if (vol != rb->global_settings->volume) | ||
279 | { | ||
280 | rb->mpeg_sound_set(SOUND_VOLUME, vol); | ||
281 | rb->global_settings->volume = vol; | ||
282 | rb->snprintf(gPrint, sizeof(gPrint), "Vol: %d", vol); | ||
283 | rb->lcd_puts(0, 7, gPrint); | ||
284 | if (gPlay.state == paused) // we have to draw ourselves | ||
285 | rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8); | ||
286 | else // let the display time do it | ||
68 | { | 287 | { |
69 | long current = *rb->current_tick; | 288 | gPlay.nTimeOSD = 50; // display it for 50 frames |
70 | button = rb->button_get(true); /* block */ | 289 | gPlay.bDirtyOSD = true; // let the refresh copy it to LCD |
71 | time += *rb->current_tick - current; /* don't time while waiting */ | ||
72 | } | 290 | } |
291 | } | ||
292 | } | ||
73 | 293 | ||
74 | switch(button) | ||
75 | { | ||
76 | case BUTTON_NONE: | ||
77 | break; /* quick exit */ | ||
78 | 294 | ||
79 | case BUTTON_LEFT | BUTTON_REPEAT: | 295 | // sync the video to the current audio |
80 | if (state == paused) | 296 | void SyncVideo(void) |
81 | frame = -1; /* single step back */ | 297 | { |
82 | else if (state == playing) | 298 | tAudioFrameHeader* pAudioBuf; |
83 | { | ||
84 | acceleration--; | ||
85 | playstep = acceleration/4; /* FR */ | ||
86 | if (playstep > -2) | ||
87 | playstep = -2; | ||
88 | if (playstep < -WIND_MAX) | ||
89 | playstep = -WIND_MAX; | ||
90 | } | ||
91 | break; | ||
92 | 299 | ||
93 | case BUTTON_RIGHT | BUTTON_REPEAT: | 300 | pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadAudio); |
94 | if (state == paused) | 301 | if (pAudioBuf->magic == AUDIO_MAGIC) |
95 | frame = 1; /* single step */ | 302 | { |
96 | else if (state == playing) | 303 | gBuf.vidcount = 0; // nothing known |
304 | // sync the video position | ||
305 | gBuf.pReadVideo = gBuf.pReadAudio + | ||
306 | (long)pAudioBuf->associated_video * (long)gFileHdr.blocksize; | ||
307 | |||
308 | // handle possible wrap | ||
309 | if (gBuf.pReadVideo >= gBuf.pBufEnd) | ||
310 | gBuf.pReadVideo -= gBuf.bufsize; | ||
311 | else if (gBuf.pReadVideo < gBuf.pBufStart) | ||
312 | gBuf.pReadVideo += gBuf.bufsize; | ||
313 | } | ||
314 | } | ||
315 | |||
316 | |||
317 | // setup ISR and timer registers | ||
318 | void timer_set(unsigned period) | ||
319 | { | ||
320 | if (period) | ||
321 | { | ||
322 | and_b(~0x10, &TSTR); // Stop the timer 4 | ||
323 | and_b(~0x10, &TSNC); // No synchronization | ||
324 | and_b(~0x10, &TMDR); // Operate normally | ||
325 | |||
326 | IMIA4 = (unsigned long)timer4_isr; // install ISR | ||
327 | |||
328 | TSR4 &= ~0x01; | ||
329 | TIER4 = 0xF9; // Enable GRA match interrupt | ||
330 | |||
331 | GRA4 = (unsigned short)(period/4 - 1); | ||
332 | TCR4 = 0x22; // clear at GRA match, sysclock/4 | ||
333 | IPRD = (IPRD & 0xFF0F) | 0x0010; // interrupt priority 1 (lowest) | ||
334 | or_b(0x10, &TSTR); // start timer 4 | ||
335 | } | ||
336 | else | ||
337 | { | ||
338 | and_b(~0x10, &TSTR); // stop the timer 4 | ||
339 | IPRD = (IPRD & 0xFF0F); // disable interrupt | ||
340 | } | ||
341 | } | ||
342 | |||
343 | |||
344 | // timer interrupt handler to display a frame | ||
345 | void timer4_isr(void) // IMIA4 | ||
346 | { | ||
347 | int available; | ||
348 | tAudioFrameHeader* pAudioBuf; | ||
349 | int height; // height to display | ||
350 | |||
351 | TSR4 &= ~0x01; // clear the interrupt | ||
352 | |||
353 | // xor_b(0x40, &PBDRL); // test: toggle LED (PB6) | ||
354 | // debug code | ||
355 | /* | ||
356 | gPlay.nTimeOSD = 1; | ||
357 | DrawBuf(); | ||
358 | gPlay.bDirtyOSD = true; | ||
359 | */ | ||
360 | |||
361 | // reduce height if we have OSD on | ||
362 | height = gFileHdr.video_height/8; | ||
363 | if (gPlay.nTimeOSD > 0) | ||
364 | { | ||
365 | gPlay.nTimeOSD--; | ||
366 | height = MIN(LCD_HEIGHT/8-1, height); // reserve bottom line | ||
367 | if (gPlay.bDirtyOSD) | ||
368 | { // OSD to bottom line | ||
369 | rb->lcd_blit(gBuf.pOSD, 0, LCD_HEIGHT/8-1, | ||
370 | LCD_WIDTH, 1, LCD_WIDTH); | ||
371 | gPlay.bDirtyOSD = false; | ||
372 | } | ||
373 | } | ||
374 | |||
375 | rb->lcd_blit(gBuf.pReadVideo, 0, 0, | ||
376 | gFileHdr.video_width, height, gFileHdr.video_width); | ||
377 | |||
378 | available = Available(gBuf.pReadVideo); | ||
379 | |||
380 | // loop to skip audio frame(s) | ||
381 | while(1) | ||
382 | { | ||
383 | // just for the statistics | ||
384 | if (!gBuf.bEOF && available < gStats.minVideoAvail) | ||
385 | gStats.minVideoAvail = available; | ||
386 | |||
387 | if (available < (int)gFileHdr.blocksize) | ||
388 | { // no data for next frame | ||
389 | |||
390 | if (gBuf.bEOF && (gFileHdr.flags & FLAG_LOOP)) | ||
391 | { // loop now, assuming the looped clip fits in memory | ||
392 | gBuf.pReadVideo = gBuf.pBufStart + gFileHdr.video_1st_frame; | ||
393 | } | ||
394 | else | ||
97 | { | 395 | { |
98 | acceleration++; | 396 | gPlay.bVideoUnderrun = true; |
99 | playstep = acceleration/4; /* FF */ | 397 | timer_set(0); // disable ourselves |
100 | if (playstep < 2) | 398 | return; // no data available |
101 | playstep = 2; | ||
102 | if (playstep > WIND_MAX) | ||
103 | playstep = WIND_MAX; | ||
104 | } | 399 | } |
105 | break; | 400 | } |
106 | 401 | ||
107 | case BUTTON_PLAY: | 402 | gBuf.pReadVideo += gFileHdr.blocksize; |
108 | if (state == playing && (playstep == 1 || playstep == -1)) | 403 | if (gBuf.pReadVideo >= gBuf.pBufEnd) |
109 | state = paused; | 404 | gBuf.pReadVideo -= gBuf.bufsize; // wraparound |
110 | else if (state == paused) | 405 | available -= gFileHdr.blocksize; |
111 | state = playing; | ||
112 | playstep = 1; | ||
113 | acceleration = 0; | ||
114 | break; | ||
115 | 406 | ||
116 | case BUTTON_LEFT: | 407 | if (!gPlay.bHasAudio) |
117 | if (state == paused) | 408 | break; // no need to skip any audio |
118 | frame = -1; /* single step back */ | ||
119 | else if (state == playing) | ||
120 | playstep = -1; /* rewind */ | ||
121 | acceleration = 0; | ||
122 | break; | ||
123 | 409 | ||
124 | case BUTTON_RIGHT: | 410 | if (gBuf.vidcount) |
125 | if (state == paused) | 411 | { |
126 | frame = 1; /* single step */ | 412 | // we know the next is a video frame |
127 | else if (state == playing) | 413 | gBuf.vidcount--; |
128 | playstep = 1; /* forward */ | 414 | break; // exit the loop |
129 | acceleration = 0; | 415 | } |
130 | break; | 416 | |
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 | ||
423 | } | ||
424 | } // while | ||
425 | } | ||
131 | 426 | ||
132 | case BUTTON_OFF: | ||
133 | state = stop; | ||
134 | break; | ||
135 | 427 | ||
136 | case SYS_USB_CONNECTED: | 428 | // ISR function to get more mp3 data |
137 | state = exit; | 429 | void GetMoreMp3(unsigned char** start, int* size) |
138 | break; | 430 | { |
431 | int available; | ||
432 | int advance; | ||
139 | 433 | ||
140 | default: | 434 | tAudioFrameHeader* pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadAudio); |
141 | if (state != playing) | 435 | |
142 | loop = true; | 436 | advance = pAudioBuf->next_block * gFileHdr.blocksize; |
143 | } | 437 | |
438 | available = Available(gBuf.pReadAudio); | ||
439 | |||
440 | // just for the statistics | ||
441 | if (!gBuf.bEOF && available < gStats.minAudioAvail) | ||
442 | gStats.minAudioAvail = available; | ||
443 | |||
444 | if (available < advance || advance == 0) | ||
445 | { | ||
446 | gPlay.bAudioUnderrun = true; | ||
447 | return; // no data available | ||
144 | } | 448 | } |
145 | while (loop); | ||
146 | 449 | ||
147 | return frame; | 450 | gBuf.pReadAudio += advance; |
451 | if (gBuf.pReadAudio >= gBuf.pBufEnd) | ||
452 | gBuf.pReadAudio -= gBuf.bufsize; // wraparound | ||
453 | |||
454 | *start = gBuf.pReadAudio + gFileHdr.audio_headersize; | ||
455 | *size = gFileHdr.blocksize - gFileHdr.audio_headersize; | ||
148 | } | 456 | } |
149 | 457 | ||
150 | 458 | ||
@@ -161,225 +469,388 @@ int WaitForButton(void) | |||
161 | } | 469 | } |
162 | 470 | ||
163 | 471 | ||
164 | /* play from memory, loop until OFF is pressed */ | 472 | int SeekTo(int fd, int nPos) |
165 | int show_buffer(unsigned char* p_start, int frames) | ||
166 | { | 473 | { |
167 | unsigned char* p_current = p_start; | 474 | int read_now, got_now; |
168 | unsigned char* p_end = p_start + SCREENSIZE * frames; | ||
169 | int shown = 0; | ||
170 | int delta; /* next frame */ | ||
171 | 475 | ||
172 | do | 476 | if (gPlay.bHasAudio) |
173 | { | 477 | rb->mp3_play_stop(); // stop audio ISR |
174 | /* wait for frame to be due */ | 478 | if (gPlay.bHasVideo) |
175 | while (TCNT4 < FRAMETIME) /* use our timer 4 */ | 479 | timer_set(0); // stop the timer 4 |
176 | rb->yield(); /* yield to the other treads */ | 480 | |
177 | TCNT4 -= FRAMETIME; | 481 | rb->lseek(fd, nPos, SEEK_SET); |
178 | |||
179 | rb->lcd_blit(p_current, 0, 0, LCD_WIDTH, LCD_HEIGHT/8, LCD_WIDTH); | ||
180 | 482 | ||
181 | shown++; | 483 | gBuf.pBufFill = gBuf.pBufStart; // all empty |
484 | gBuf.pReadVideo = gBuf.pReadAudio = gBuf.pBufStart; | ||
182 | 485 | ||
183 | delta = check_button(); | 486 | read_now = (PRECHARGE + gBuf.granularity - 1); // round up |
184 | p_current += delta * SCREENSIZE; | 487 | read_now -= read_now % gBuf.granularity; // to granularity |
185 | if (p_current >= p_end || p_current < p_start) | 488 | got_now = rb->read(fd, gBuf.pBufFill, read_now); |
186 | p_current = p_start; /* wrap */ | 489 | gBuf.bEOF = (read_now != got_now); |
187 | } while(state != stop && state != exit); | 490 | gBuf.pBufFill += got_now; |
188 | 491 | ||
189 | return (state != exit) ? shown : -1; | 492 | if (nPos == 0) |
493 | { // we seeked to the start | ||
494 | if (gPlay.bHasVideo) | ||
495 | gBuf.pReadVideo += gFileHdr.video_1st_frame; | ||
496 | |||
497 | if (gPlay.bHasAudio) | ||
498 | gBuf.pReadAudio += gFileHdr.audio_1st_frame; | ||
499 | } | ||
500 | else | ||
501 | { // we have to search for the positions | ||
502 | if (gPlay.bHasAudio) // prepare audio playback, if contained | ||
503 | { | ||
504 | // search for audio frame | ||
505 | while (((tAudioFrameHeader*)(gBuf.pReadAudio))->magic != AUDIO_MAGIC) | ||
506 | gBuf.pReadAudio += gFileHdr.blocksize; | ||
507 | |||
508 | rb->mp3_play_data(gBuf.pReadAudio + gFileHdr.audio_headersize, | ||
509 | gFileHdr.blocksize - gFileHdr.audio_headersize, GetMoreMp3); | ||
510 | |||
511 | if (gPlay.bHasVideo) | ||
512 | SyncVideo(); // pick the right video for that | ||
513 | } | ||
514 | } | ||
515 | |||
516 | // synchronous start | ||
517 | if (gPlay.bHasAudio) | ||
518 | { | ||
519 | gPlay.bAudioUnderrun = false; | ||
520 | rb->mp3_play_pause(true); // kickoff audio | ||
521 | } | ||
522 | if (gPlay.bHasVideo) | ||
523 | { | ||
524 | gPlay.bVideoUnderrun = false; | ||
525 | timer_set(gFileHdr.video_frametime); // start display interrupt | ||
526 | } | ||
527 | |||
528 | return 0; | ||
190 | } | 529 | } |
191 | 530 | ||
192 | 531 | ||
193 | /* play from file, exit if OFF is pressed */ | 532 | // returns >0 if continue, =0 to stop, <0 to abort (USB) |
194 | int show_file(unsigned char* p_buffer, int fd) | 533 | int PlayTick(int fd) |
195 | { | 534 | { |
196 | long tag[FILEBUFSIZE/512]; /* I treat the buffer as direct-mapped cache */ | 535 | int button; |
197 | long framepos = 0; /* position of frame in file */ | 536 | int avail_audio = -1, avail_video = -1; |
198 | long filesize = rb->filesize(fd); | 537 | int retval = 1; |
199 | long filepos = 0; /* my own counting */ | 538 | int filepos; |
200 | int shown = 0; | 539 | |
201 | 540 | if (gPlay.bHasAudio) | |
202 | long readfrom; | 541 | avail_audio = Available(gBuf.pReadAudio); |
203 | long readto; | 542 | if (gPlay.bHasVideo) |
204 | int read; /* amount read from disk */ | 543 | avail_video = Available(gBuf.pReadVideo); |
205 | long frame_offset; /* pos of frame in buffer */ | 544 | |
206 | long writefrom; /* round down to sector */ | 545 | if ((gPlay.bHasAudio && avail_audio < gBuf.low_water) |
207 | long sector; /* sector in frame buffer */ | 546 | || (gPlay.bHasVideo && avail_video < gBuf.low_water)) |
208 | long pos_aligned, orig_aligned; /* round down to sector */ | 547 | { |
209 | char buf[10]; | 548 | gPlay.bRefilling = true; /* go to refill mode */ |
210 | 549 | } | |
211 | rb->memset(&tag, 0xFF, sizeof(tag)); /* invalidate cache */ | 550 | |
212 | 551 | if ((!gPlay.bHasAudio || gPlay.bAudioUnderrun) | |
213 | do | 552 | && (!gPlay.bHasVideo || gPlay.bVideoUnderrun)) |
214 | { | 553 | return 0; // all expired |
215 | /* load the frame into memory */ | 554 | |
216 | readfrom = readto = -1; /* invalidate */ | 555 | if (!gPlay.bRefilling || gBuf.bEOF) |
217 | frame_offset = framepos % FILEBUFSIZE; /* pos of frame in buffer */ | 556 | { // nothing to do |
218 | writefrom = frame_offset & ~511; /* round down to sector */ | 557 | button = rb->button_get_w_tmo(HZ/10); |
219 | sector = frame_offset / 512; /* sector in frame buffer */ | 558 | } |
220 | orig_aligned = pos_aligned = framepos & ~511; /* down to sector */ | 559 | else |
221 | 560 | { // refill buffer | |
222 | do | 561 | int read_now, got_now; |
562 | int buf_free; | ||
563 | |||
564 | // how much can we reload, don't fill completely, would appear empty | ||
565 | buf_free = gBuf.bufsize - MAX(avail_audio, avail_video) - gBuf.high_water; | ||
566 | if (buf_free < 0) | ||
567 | buf_free = 0; // just for safety | ||
568 | buf_free -= buf_free % gBuf.granularity; // round down to granularity | ||
569 | |||
570 | // in one piece max. up to buffer end (wrap after that) | ||
571 | read_now = MIN(buf_free, gBuf.pBufEnd - gBuf.pBufFill); | ||
572 | |||
573 | // load piecewise, to stay responsive | ||
574 | read_now = MIN(read_now, gBuf.nReadChunk); | ||
575 | |||
576 | if (read_now == buf_free) | ||
577 | gPlay.bRefilling = false; // last piece requested | ||
578 | |||
579 | got_now = rb->read(fd, gBuf.pBufFill, read_now); | ||
580 | if (got_now != read_now || read_now == 0) | ||
223 | { | 581 | { |
224 | if (tag[sector] != pos_aligned) /* in cache? */ | 582 | gBuf.bEOF = true; |
225 | { /* not cached */ | 583 | gPlay.bRefilling = false; |
226 | tag[sector] = pos_aligned; | ||
227 | if (readfrom == -1) /* not used yet? */ | ||
228 | { | ||
229 | readfrom = pos_aligned; /* set start */ | ||
230 | writefrom += pos_aligned - orig_aligned; | ||
231 | } | ||
232 | readto = pos_aligned; /* set stop */ | ||
233 | } | ||
234 | pos_aligned += 512; | ||
235 | sector++; | ||
236 | } while (pos_aligned < framepos + SCREENSIZE); | ||
237 | |||
238 | if (readfrom != -1) | ||
239 | { /* need to read from disk */ | ||
240 | if (filepos != readfrom) | ||
241 | { /* need to seek */ | ||
242 | filepos = rb->lseek(fd, readfrom, SEEK_SET); | ||
243 | } | ||
244 | read = readto - readfrom + 512; | ||
245 | /* read the sector(s) */ | ||
246 | filepos += rb->read(fd, p_buffer + writefrom, read); | ||
247 | } | 584 | } |
248 | else | ||
249 | read = 0; | ||
250 | 585 | ||
251 | /* wait for frame to be due */ | 586 | if (!gPlay.bRefilling) |
252 | while (TCNT4 < FRAMETIME) /* use our timer 4 */ | 587 | rb->ata_sleep(); // no point in leaving the disk run til timeout |
253 | rb->yield(); /* yield to the other treads */ | ||
254 | TCNT4 -= FRAMETIME; | ||
255 | 588 | ||
256 | /* display OSD if FF/FR */ | 589 | gBuf.pBufFill += got_now; |
257 | if (playstep != 1 && playstep != -1) | 590 | if (gBuf.pBufFill >= gBuf.pBufEnd) |
258 | { | 591 | gBuf.pBufFill = gBuf.pBufStart; // wrap |
259 | int w,h; | ||
260 | 592 | ||
261 | if (playstep > 0) | 593 | rb->yield(); // have mercy with the other threads |
262 | rb->snprintf(buf, sizeof(buf), "%d>>", playstep); | 594 | button = rb->button_get(false); |
263 | else | 595 | } |
264 | rb->snprintf(buf, sizeof(buf), "%d<<", -playstep); | ||
265 | 596 | ||
266 | rb->lcd_getstringsize(buf, &w, &h); | 597 | if (button != BUTTON_NONE) |
267 | rb->lcd_putsxy(0, LCD_HEIGHT-h, buf); | 598 | { |
268 | 599 | filepos = rb->lseek(fd, 0, SEEK_CUR); | |
269 | w++; | 600 | |
270 | rb->slidebar(w, LCD_HEIGHT-7, LCD_WIDTH-w, 7, | 601 | if (gPlay.bHasVideo) // video position is more accurate |
271 | (100 * filepos)/filesize, Grow_Right); | 602 | filepos -= Available(gBuf.pReadVideo); // take video position |
272 | rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8); | ||
273 | rb->lcd_blit(p_buffer + frame_offset, 0, 0, | ||
274 | LCD_WIDTH, LCD_HEIGHT/8 - 1, LCD_WIDTH); | ||
275 | } | ||
276 | else | 603 | else |
277 | { /* display the frame normally */ | 604 | filepos -= Available(gBuf.pReadAudio); // else audio |
278 | rb->lcd_blit(p_buffer + frame_offset, 0, 0, LCD_WIDTH, | ||
279 | LCD_HEIGHT/8, LCD_WIDTH); | ||
280 | } | ||
281 | 605 | ||
282 | /* query keys to determine next frame */ | 606 | switch (button) |
283 | framepos += check_button() * SCREENSIZE; | 607 | { // set exit conditions |
284 | if (framepos <= 0) | 608 | case BUTTON_OFF: |
285 | { | 609 | retval = 0; // signal "stop" to caller |
286 | state = paused; | 610 | break; |
287 | framepos = 0; | 611 | case SYS_USB_CONNECTED: |
288 | } | 612 | retval = -1; // signal "abort" to caller |
289 | else if (framepos >= filesize) | 613 | break; |
290 | { | 614 | case BUTTON_PLAY: |
291 | if (state == playing) | 615 | if (gPlay.bSeeking) |
616 | { | ||
617 | gPlay.bSeeking = false; | ||
618 | gPlay.state = playing; | ||
619 | SeekTo(fd, gPlay.nSeekPos); | ||
620 | } | ||
621 | else if (gPlay.state == playing) | ||
292 | { | 622 | { |
293 | if (playstep == 1) | 623 | gPlay.state = paused; |
294 | state = stop; /* reached the end while playing normally */ | 624 | if (gPlay.bHasAudio) |
295 | else | 625 | rb->mp3_play_pause(false); // pause audio |
626 | if (gPlay.bHasVideo) | ||
627 | and_b(~0x10, &TSTR); // stop the timer 4 | ||
628 | } | ||
629 | else if (gPlay.state == paused) | ||
630 | { | ||
631 | gPlay.state = playing; | ||
632 | if (gPlay.bHasAudio) | ||
296 | { | 633 | { |
297 | state = paused; /* it may have been FF */ | 634 | if (gPlay.bHasVideo) |
298 | playstep = 1; | 635 | SyncVideo(); |
636 | rb->mp3_play_pause(true); // play audio | ||
299 | } | 637 | } |
638 | if (gPlay.bHasVideo) | ||
639 | or_b(0x10, &TSTR); // start the video | ||
300 | } | 640 | } |
301 | 641 | break; | |
302 | framepos = filesize - SCREENSIZE; | 642 | case BUTTON_UP: |
303 | /* in case the file size is no integer multiple */ | 643 | case BUTTON_UP | BUTTON_REPEAT: |
304 | framepos -= framepos % SCREENSIZE; | 644 | if (gPlay.bHasAudio) |
645 | ChangeVolume(1); | ||
646 | break; | ||
647 | case BUTTON_DOWN: | ||
648 | case BUTTON_DOWN | BUTTON_REPEAT: | ||
649 | if (gPlay.bHasAudio) | ||
650 | ChangeVolume(-1); | ||
651 | break; | ||
652 | case BUTTON_LEFT: | ||
653 | case BUTTON_LEFT | BUTTON_REPEAT: | ||
654 | if (!gPlay.bSeeking) // prepare seek | ||
655 | { | ||
656 | gPlay.nSeekPos = filepos; | ||
657 | gPlay.bSeeking = true; | ||
658 | gPlay.nSeekAcc = 0; | ||
659 | } | ||
660 | else if (gPlay.nSeekAcc > 0) // other direction, stop sliding | ||
661 | gPlay.nSeekAcc = 0; | ||
662 | else | ||
663 | gPlay.nSeekAcc--; | ||
664 | break; | ||
665 | case BUTTON_RIGHT: | ||
666 | case BUTTON_RIGHT | BUTTON_REPEAT: | ||
667 | if (!gPlay.bSeeking) // prepare seek | ||
668 | { | ||
669 | gPlay.nSeekPos = filepos; | ||
670 | gPlay.bSeeking = true; | ||
671 | gPlay.nSeekAcc = 0; | ||
672 | } | ||
673 | else if (gPlay.nSeekAcc < 0) // other direction, stop sliding | ||
674 | gPlay.nSeekAcc = 0; | ||
675 | else | ||
676 | gPlay.nSeekAcc++; | ||
677 | break; | ||
305 | } | 678 | } |
679 | } /* if (button != BUTTON_NONE) */ | ||
306 | 680 | ||
307 | shown++; | 681 | if (gPlay.bSeeking) // seeking? |
308 | 682 | { | |
309 | } while (state != stop && state != exit); | 683 | if (gPlay.nSeekAcc < -MAX_ACC) |
684 | gPlay.nSeekAcc = -MAX_ACC; | ||
685 | else if (gPlay.nSeekAcc > MAX_ACC) | ||
686 | gPlay.nSeekAcc = MAX_ACC; | ||
687 | |||
688 | gPlay.nSeekPos += gPlay.nSeekAcc * gBuf.nSeekChunk; | ||
689 | if (gPlay.nSeekPos < 0) | ||
690 | gPlay.nSeekPos = 0; | ||
691 | if (gPlay.nSeekPos > rb->filesize(fd) - gBuf.granularity) | ||
692 | { | ||
693 | gPlay.nSeekPos = rb->filesize(fd); | ||
694 | gPlay.nSeekPos -= gPlay.nSeekPos % gBuf.granularity; | ||
695 | } | ||
696 | DrawPosition(gPlay.nSeekPos, rb->filesize(fd)); | ||
697 | } | ||
310 | 698 | ||
311 | return (state != exit) ? shown : -1; | 699 | return retval; |
312 | } | 700 | } |
313 | 701 | ||
314 | 702 | ||
315 | int main(char* filename) | 703 | int main(char* filename) |
316 | { | 704 | { |
317 | char buf[32]; | 705 | int file_size; |
318 | int buffer_size, file_size; | ||
319 | unsigned char* p_buffer; | ||
320 | int fd; /* file descriptor handle */ | 706 | int fd; /* file descriptor handle */ |
321 | int got_now; /* how many bytes read from file */ | 707 | int read_now, got_now; |
322 | int frames, shown; | ||
323 | int button; | 708 | int button; |
324 | int fps; | 709 | int retval; |
325 | 710 | ||
326 | p_buffer = rb->plugin_get_buffer(&buffer_size); | 711 | // try to open the file |
327 | if (buffer_size < FILEBUFSIZE) | ||
328 | return PLUGIN_ERROR; /* not enough memory */ | ||
329 | |||
330 | fd = rb->open(filename, O_RDONLY); | 712 | fd = rb->open(filename, O_RDONLY); |
331 | if (fd < 0) | 713 | if (fd < 0) |
332 | return PLUGIN_ERROR; | 714 | return PLUGIN_ERROR; |
333 | |||
334 | /* init timer 4, crude code */ | ||
335 | IPRD = (IPRD & 0xFF0F); // disable interrupt | ||
336 | and_b(~0x10, &TSTR); // Stop the timer 4 | ||
337 | and_b(~0x10, &TSNC); // No synchronization | ||
338 | and_b(~0x10, &TMDR); // Operate normally | ||
339 | TCR4 = 0x03; // no clear at GRA match, sysclock/8 | ||
340 | TCNT4 = 0; // start counting at 0 | ||
341 | |||
342 | file_size = rb->filesize(fd); | 715 | file_size = rb->filesize(fd); |
343 | if (file_size <= buffer_size) | 716 | |
344 | { /* we can read the whole file in advance */ | 717 | // init statistics |
345 | got_now = rb->read(fd, p_buffer, file_size); | 718 | rb->memset(&gStats, 0, sizeof(gStats)); |
719 | gStats.minAudioAvail = gStats.minVideoAvail = INT_MAX; | ||
720 | |||
721 | // init playback state | ||
722 | rb->memset(&gPlay, 0, sizeof(gPlay)); | ||
723 | gPlay.state = playing; | ||
724 | |||
725 | // init buffer | ||
726 | rb->memset(&gBuf, 0, sizeof(gBuf)); | ||
727 | gBuf.pOSD = rb->lcd_framebuffer + LCD_WIDTH*7; // last screen line | ||
728 | gBuf.pBufStart = rb->plugin_get_mp3_buffer(&gBuf.bufsize); | ||
729 | //gBuf.bufsize = 1700*1024; // test!!!! | ||
730 | gBuf.pBufFill = gBuf.pBufStart; // all empty | ||
731 | gBuf.pReadVideo = gBuf.pReadAudio = gBuf.pBufStart; | ||
732 | |||
733 | // load file header | ||
734 | read_now = sizeof(gFileHdr); | ||
735 | got_now = rb->read(fd, &gFileHdr, read_now); | ||
736 | rb->lseek(fd, 0, SEEK_SET); // rewind to restart sector-aligned | ||
737 | if (got_now != read_now) | ||
738 | { | ||
346 | rb->close(fd); | 739 | rb->close(fd); |
347 | frames = got_now / (LCD_WIDTH*LCD_HEIGHT/8); | 740 | return (PLUGIN_ERROR); |
348 | or_b(0x10, &TSTR); // start timer 4 | 741 | } |
349 | time = *rb->current_tick; | 742 | |
350 | shown = show_buffer(p_buffer, frames); | 743 | // check header |
351 | time = *rb->current_tick - time; | 744 | if (gFileHdr.magic != HEADER_MAGIC) |
745 | { // old file, use default info | ||
746 | rb->memset(&gFileHdr, 0, sizeof(gFileHdr)); | ||
747 | gFileHdr.blocksize = SCREENSIZE; | ||
748 | if (file_size < SCREENSIZE * FPS) // less than a second | ||
749 | gFileHdr.flags |= FLAG_LOOP; | ||
750 | gFileHdr.video_format = VIDEOFORMAT_RAW; | ||
751 | gFileHdr.video_width = LCD_WIDTH; | ||
752 | gFileHdr.video_height = LCD_HEIGHT; | ||
753 | gFileHdr.video_frametime = CLOCK / FPS; | ||
754 | gFileHdr.bps_peak = gFileHdr.bps_average = LCD_WIDTH * LCD_HEIGHT * FPS; | ||
352 | } | 755 | } |
756 | |||
757 | // continue buffer init: align the end, calc low water, read sizes | ||
758 | gBuf.granularity = gFileHdr.blocksize; | ||
759 | while (gBuf.granularity % 512) // common multiple of sector size | ||
760 | gBuf.granularity *= 2; | ||
761 | gBuf.bufsize -= gBuf.bufsize % gBuf.granularity; // round down | ||
762 | gBuf.pBufEnd = gBuf.pBufStart + gBuf.bufsize; | ||
763 | gBuf.low_water = SPINUP * gFileHdr.bps_peak / 8000; | ||
764 | if (gFileHdr.audio_min_associated < 0) | ||
765 | gBuf.high_water = 0 - gFileHdr.audio_min_associated; | ||
353 | else | 766 | else |
354 | { /* we need to stream */ | 767 | gBuf.high_water = 1; // never fill buffer completely, would appear empty |
355 | or_b(0x10, &TSTR); // start timer 4 | 768 | gBuf.nReadChunk = (CHUNK + gBuf.granularity - 1); // round up |
356 | time = *rb->current_tick; | 769 | gBuf.nReadChunk -= gBuf.nReadChunk % gBuf.granularity;// and align |
357 | shown = show_file(p_buffer, fd); | 770 | gBuf.nSeekChunk = rb->filesize(fd) / FF_TICKS; |
358 | time = *rb->current_tick - time; | 771 | gBuf.nSeekChunk += gBuf.granularity - 1; // round up |
359 | rb->close(fd); | 772 | gBuf.nSeekChunk -= gBuf.nSeekChunk % gBuf.granularity; // and align |
773 | |||
774 | // precharge buffer with more data | ||
775 | read_now = MAX(gFileHdr.audio_1st_frame, gFileHdr.video_1st_frame); | ||
776 | read_now = (read_now + PRECHARGE + gBuf.granularity - 1); | ||
777 | read_now -= read_now % gBuf.granularity; // round up to granularity | ||
778 | got_now = rb->read(fd, gBuf.pBufFill, read_now); | ||
779 | gBuf.pBufFill += got_now; | ||
780 | |||
781 | // prepare video playback, if contained | ||
782 | if (gFileHdr.video_format == VIDEOFORMAT_RAW) | ||
783 | { | ||
784 | gBuf.pReadVideo += gFileHdr.video_1st_frame; | ||
785 | gPlay.bHasVideo = true; | ||
786 | |||
787 | if (rb->global_settings->backlight_timeout > 0) | ||
788 | rb->backlight_set_timeout(1); // keep the light on | ||
360 | } | 789 | } |
361 | 790 | ||
362 | if (shown == -1) /* exception */ | 791 | // prepare audio playback, if contained |
363 | return PLUGIN_USB_CONNECTED; | 792 | if (gFileHdr.audio_format == AUDIOFORMAT_MP3_BITSWAPPED) |
793 | { | ||
794 | gBuf.pReadAudio += gFileHdr.audio_1st_frame; | ||
795 | gPlay.bHasAudio = true; | ||
364 | 796 | ||
365 | fps = (shown * HZ *100) / time; /* 100 times fps */ | 797 | rb->mp3_play_init(); |
798 | rb->mp3_play_data(gBuf.pReadAudio + gFileHdr.audio_headersize, | ||
799 | gFileHdr.blocksize - gFileHdr.audio_headersize, GetMoreMp3); | ||
800 | rb->mpeg_sound_set(SOUND_VOLUME, rb->global_settings->volume); | ||
801 | } | ||
802 | |||
803 | // synchronous start | ||
804 | gPlay.state = playing; | ||
805 | if (gPlay.bHasAudio) | ||
806 | rb->mp3_play_pause(true); // kickoff audio | ||
807 | if (gPlay.bHasVideo) | ||
808 | timer_set(gFileHdr.video_frametime); // start display interrupt | ||
809 | |||
810 | // all that's left to do is keep the buffer full | ||
811 | do // the main loop | ||
812 | { | ||
813 | retval = PlayTick(fd); | ||
814 | } while (retval > 0); | ||
815 | |||
816 | rb->close(fd); // close the file | ||
817 | |||
818 | if (gPlay.bHasVideo) | ||
819 | timer_set(0); // stop video ISR, now I can use the display again | ||
820 | |||
821 | if (gPlay.bHasAudio) | ||
822 | rb->mp3_play_stop(); // stop audio ISR | ||
366 | 823 | ||
824 | // restore normal backlight setting | ||
825 | rb->backlight_set_timeout(rb->global_settings->backlight_timeout); | ||
826 | |||
827 | if (retval < 0) // aborted? | ||
828 | { | ||
829 | return PLUGIN_USB_CONNECTED; | ||
830 | } | ||
831 | |||
832 | // display statistics | ||
367 | rb->lcd_clear_display(); | 833 | rb->lcd_clear_display(); |
368 | rb->snprintf(buf, sizeof(buf), "%d frames shown", shown); | 834 | rb->snprintf(gPrint, sizeof(gPrint), "AudioUnderrun: %d", gPlay.bAudioUnderrun); |
369 | rb->lcd_puts(0, 0, buf); | 835 | rb->lcd_puts(0, 0, gPrint); |
370 | rb->snprintf(buf, sizeof(buf), "%d.%02d seconds", time/HZ, time%HZ); | 836 | rb->snprintf(gPrint, sizeof(gPrint), "VideoUnderrun: %d", gPlay.bVideoUnderrun); |
371 | rb->lcd_puts(0, 1, buf); | 837 | rb->lcd_puts(0, 1, gPrint); |
372 | rb->snprintf(buf, sizeof(buf), "%d.%02d fps", fps/100, fps%100); | 838 | rb->snprintf(gPrint, sizeof(gPrint), "MinAudio: %d bytes", gStats.minAudioAvail); |
373 | rb->lcd_puts(0, 2, buf); | 839 | rb->lcd_puts(0, 2, gPrint); |
374 | rb->snprintf(buf, sizeof(buf), "file: %d bytes", file_size); | 840 | rb->snprintf(gPrint, sizeof(gPrint), "MinVideo: %d bytes", gStats.minVideoAvail); |
375 | rb->lcd_puts(0, 6, buf); | 841 | rb->lcd_puts(0, 3, gPrint); |
376 | rb->snprintf(buf, sizeof(buf), "buffer: %d bytes", buffer_size); | 842 | rb->snprintf(gPrint, sizeof(gPrint), "ReadChunk: %d", gBuf.nReadChunk); |
377 | rb->lcd_puts(0, 7, buf); | 843 | rb->lcd_puts(0, 4, gPrint); |
844 | rb->snprintf(gPrint, sizeof(gPrint), "SeekChunk: %d", gBuf.nSeekChunk); | ||
845 | rb->lcd_puts(0, 5, gPrint); | ||
846 | rb->snprintf(gPrint, sizeof(gPrint), "1st Video: %d", gFileHdr.video_1st_frame); | ||
847 | rb->lcd_puts(0, 6, gPrint); | ||
848 | rb->snprintf(gPrint, sizeof(gPrint), "pBufStart: %x", gBuf.pBufStart); | ||
849 | rb->lcd_puts(0, 7, gPrint); | ||
850 | |||
378 | rb->lcd_update(); | 851 | rb->lcd_update(); |
379 | button = WaitForButton(); | 852 | button = WaitForButton(); |
380 | return (button == SYS_USB_CONNECTED) ? PLUGIN_USB_CONNECTED : PLUGIN_OK; | 853 | return (button == SYS_USB_CONNECTED) ? PLUGIN_USB_CONNECTED : PLUGIN_OK; |
381 | |||
382 | |||
383 | } | 854 | } |
384 | 855 | ||
385 | 856 | ||
@@ -393,7 +864,7 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) | |||
393 | matches the machine it is running on */ | 864 | matches the machine it is running on */ |
394 | TEST_PLUGIN_API(api); | 865 | TEST_PLUGIN_API(api); |
395 | 866 | ||
396 | rb = api; /* copy to global api pointer */ | 867 | rb = api; // copy to global api pointer |
397 | 868 | ||
398 | if (parameter == NULL) | 869 | if (parameter == NULL) |
399 | { | 870 | { |
@@ -401,13 +872,13 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) | |||
401 | return PLUGIN_ERROR; | 872 | return PLUGIN_ERROR; |
402 | } | 873 | } |
403 | 874 | ||
404 | /* now go ahead and have fun! */ | 875 | // now go ahead and have fun! |
405 | ret = main((char*) parameter); | 876 | ret = main((char*) parameter); |
406 | if (ret==PLUGIN_USB_CONNECTED) | 877 | if (ret==PLUGIN_USB_CONNECTED) |
407 | rb->usb_screen(); | 878 | rb->usb_screen(); |
408 | return ret; | 879 | return ret; |
409 | } | 880 | } |
410 | 881 | ||
411 | #endif /* #ifdef HAVE_LCD_BITMAP */ | 882 | #endif // #ifdef HAVE_LCD_BITMAP |
412 | #endif /* #ifndef SIMULATOR */ | 883 | #endif // #ifndef SIMULATOR |
413 | 884 | ||