summaryrefslogtreecommitdiff
path: root/apps/plugins/video.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/video.c')
-rw-r--r--apps/plugins/video.c1013
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 ******************/
54void timer_set(unsigned period); // setup ISR and timer registers
55void timer4_isr(void) __attribute__((interrupt_handler)); // IMIA4 ISR
56int 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
78typedef 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
118typedef 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 */
38static struct plugin_api* rb; /* here is a global api struct pointer */ 132static struct plugin_api* rb; /* here is a global api struct pointer */
133static char gPrint[32]; /* a global printf buffer, saves stack */
134
39 135
40static enum 136// playstate
137static 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
157static 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
177static 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
48static int playstep = 1; /* for current speed and direction */ 185tFileHeader gFileHdr; // file header
49static int acceleration = 0;
50static 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
54int check_button(void) 190int 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
199void 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
240void 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, +/-
272void 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) 296void 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
318void 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
345void 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; 429void 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 */ 472int SeekTo(int fd, int nPos)
165int 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)
194int show_file(unsigned char* p_buffer, int fd) 533int 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
315int main(char* filename) 703int 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