1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * mpegplayer main entrypoint and UI implementation
12 * Copyright (c) 2007 Michael Sevakis
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 /****************************************************************************
25 * mpegplayer is structured as follows:
27 * +-->Video Thread-->Video Output-->LCD
29 * UI-->Stream Manager-->+-->Audio Thread-->PCM buffer--Audio Device
30 * | | | | (ref. clock)
31 * | | +-->Buffer Thread |
32 * Stream Data | | (clock intf./
33 * Requests | File Cache drift adj.)
39 * 1) The main thread - Handles user input, settings, basic playback control
42 * 2) Stream Manager thread - Handles playback state, events from streams
43 * such as when a stream is finished, stream commands, PCM state. The
44 * layer in which this thread run also handles arbitration of data
45 * requests between the streams and the disk buffer. The actual specific
46 * transport layer code may get moved out to support multiple container
49 * 3) Buffer thread - Buffers data in the background, generates notifications
50 * to streams when their data has been buffered, and watches streams'
51 * progress to keep data available during playback. Handles synchronous
52 * random access requests when the file cache is missed.
54 * 4) Video thread (running on the COP for PortalPlayer targets) - Decodes
55 * the video stream and renders video frames to the LCD. Handles
56 * miscellaneous video tasks like frame and thumbnail printing.
58 * 5) Audio thread (running on the main CPU to maintain consistency with the
59 * audio FIQ hander on PP) - Decodes audio frames and places them into
60 * the PCM buffer for rendering by the audio device.
62 * Streams are neither aware of one another nor care about one another. All
63 * streams shall have their own thread (unless it is _really_ efficient to
64 * have a single thread handle a couple minor streams). All coordination of
65 * the streams is done through the stream manager. The clocking is controlled
66 * by and exposed by the stream manager to other streams and implemented at
69 * Notes about MPEG files:
71 * MPEG System Clock is 27MHz - i.e. 27000000 ticks/second.
73 * FPS is represented in terms of a frame period - this is always an
74 * integer number of 27MHz ticks.
76 * e.g. 29.97fps (30000/1001) NTSC video has an exact frame period of
79 * In libmpeg2, info->sequence->frame_period contains the frame_period.
81 * Working with Rockbox's 100Hz tick, the common frame rates would need
82 * to be as follows (1):
84 * FPS | 27Mhz | 100Hz | 44.1KHz | 48KHz
85 * --------|-----------------------------------------------------------
86 * 10* | 2700000 | 10 | 4410 | 4800
87 * 12* | 2250000 | 8.3333 | 3675 | 4000
88 * 15* | 1800000 | 6.6667 | 2940 | 3200
89 * 23.9760 | 1126125 | 4.170833333 | 1839.3375 | 2002
90 * 24 | 1125000 | 4.166667 | 1837.5 | 2000
91 * 25 | 1080000 | 4 | 1764 | 1920
92 * 29.9700 | 900900 | 3.336667 | 1471,47 | 1601.6
93 * 30 | 900000 | 3.333333 | 1470 | 1600
95 * *Unofficial framerates
97 * (1) But we don't really care since the audio clock is used anyway and has
98 * very fine resolution ;-)
99 *****************************************************************************/
101 #include "mpegplayer.h"
103 #include "mpeg_settings.h"
105 #include "video_out.h"
106 #include "stream_thread.h"
107 #include "stream_mgr.h"
112 /* button definitions */
113 #if (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)
114 #define MPEG_MENU BUTTON_MODE
115 #define MPEG_STOP BUTTON_OFF
116 #define MPEG_PAUSE BUTTON_ON
117 #define MPEG_VOLDOWN BUTTON_DOWN
118 #define MPEG_VOLUP BUTTON_UP
119 #define MPEG_RW BUTTON_LEFT
120 #define MPEG_FF BUTTON_RIGHT
122 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \
123 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
124 #define MPEG_MENU BUTTON_MENU
125 #define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL)
126 #define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT)
127 #define MPEG_VOLDOWN BUTTON_SCROLL_BACK
128 #define MPEG_VOLUP BUTTON_SCROLL_FWD
129 #define MPEG_RW BUTTON_LEFT
130 #define MPEG_FF BUTTON_RIGHT
132 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
133 #define MPEG_MENU (BUTTON_REC | BUTTON_REL)
134 #define MPEG_STOP BUTTON_POWER
135 #define MPEG_PAUSE BUTTON_PLAY
136 #define MPEG_VOLDOWN BUTTON_DOWN
137 #define MPEG_VOLUP BUTTON_UP
138 #define MPEG_RW BUTTON_LEFT
139 #define MPEG_FF BUTTON_RIGHT
141 #elif CONFIG_KEYPAD == GIGABEAT_PAD
142 #define MPEG_MENU BUTTON_MENU
143 #define MPEG_STOP BUTTON_POWER
144 #define MPEG_PAUSE BUTTON_SELECT
145 #define MPEG_PAUSE2 BUTTON_A
146 #define MPEG_VOLDOWN BUTTON_LEFT
147 #define MPEG_VOLUP BUTTON_RIGHT
148 #define MPEG_VOLDOWN2 BUTTON_VOL_DOWN
149 #define MPEG_VOLUP2 BUTTON_VOL_UP
150 #define MPEG_RW BUTTON_UP
151 #define MPEG_FF BUTTON_DOWN
152 #define MPEG_RC_MENU BUTTON_RC_DSP
153 #define MPEG_RC_STOP (BUTTON_RC_PLAY | BUTTON_REPEAT)
154 #define MPEG_RC_PAUSE (BUTTON_RC_PLAY | BUTTON_REL)
155 #define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN
156 #define MPEG_RC_VOLUP BUTTON_RC_VOL_UP
157 #define MPEG_RC_RW BUTTON_RC_REW
158 #define MPEG_RC_FF BUTTON_RC_FF
160 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
161 #define MPEG_MENU BUTTON_LEFT
162 #define MPEG_STOP BUTTON_POWER
163 #define MPEG_PAUSE BUTTON_PLAY
164 #define MPEG_VOLDOWN BUTTON_SCROLL_DOWN
165 #define MPEG_VOLUP BUTTON_SCROLL_UP
166 #define MPEG_RW BUTTON_REW
167 #define MPEG_FF BUTTON_FF
169 #elif CONFIG_KEYPAD == SANSA_E200_PAD
170 #define MPEG_MENU BUTTON_SELECT
171 #define MPEG_STOP BUTTON_POWER
172 #define MPEG_PAUSE BUTTON_UP
173 #define MPEG_VOLDOWN BUTTON_SCROLL_BACK
174 #define MPEG_VOLUP BUTTON_SCROLL_FWD
175 #define MPEG_RW BUTTON_LEFT
176 #define MPEG_FF BUTTON_RIGHT
178 #elif CONFIG_KEYPAD == SANSA_C200_PAD
179 #define MPEG_MENU BUTTON_SELECT
180 #define MPEG_STOP BUTTON_POWER
181 #define MPEG_PAUSE BUTTON_UP
182 #define MPEG_VOLDOWN BUTTON_VOL_DOWN
183 #define MPEG_VOLUP BUTTON_VOL_UP
184 #define MPEG_RW BUTTON_LEFT
185 #define MPEG_FF BUTTON_RIGHT
187 #elif CONFIG_KEYPAD == MROBE500_PAD
188 #define MPEG_MENU BUTTON_RC_HEART
189 #define MPEG_STOP BUTTON_POWER
190 #define MPEG_PAUSE BUTTON_TOUCHPAD
191 #define MPEG_VOLDOWN BUTTON_RC_VOL_DOWN
192 #define MPEG_VOLUP BUTTON_RC_VOL_UP
193 #define MPEG_RW BUTTON_RC_REW
194 #define MPEG_FF BUTTON_RC_FF
197 #error MPEGPLAYER: Unsupported keypad
200 struct plugin_api
* rb
;
202 CACHE_FUNCTION_WRAPPERS(rb
);
203 ALIGN_BUFFER_WRAPPER(rb
);
205 /* One thing we can do here for targets with remotes is having a display
206 * always on the remote instead of always forcing a popup on the main display */
208 #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */
209 /* 3% of 30min file == 54s step size */
210 #define MIN_FF_REWIND_STEP (TS_SECOND/2)
211 #define WVS_MIN_UPDATE_INTERVAL (HZ/2)
213 /* WVS status - same order as icon array */
216 WVS_STATUS_STOPPED
= 0,
222 WVS_STATUS_MASK
= 0x7
227 WVS_REFRESH_DEFAULT
= 0x0000, /* Only refresh elements when due */
229 WVS_REFRESH_VOLUME
= 0x0001, /* ...volume display */
230 WVS_REFRESH_TIME
= 0x0002, /* ...time display+progress */
231 WVS_REFRESH_STATUS
= 0x0004, /* ...playback status icon */
232 WVS_REFRESH_BACKGROUND
= 0x0008, /* ...background (implies ALL) */
233 WVS_REFRESH_VIDEO
= 0x0010, /* ...video image upon timeout */
234 WVS_REFRESH_RESUME
= 0x0020, /* Resume playback upon timeout */
235 WVS_NODRAW
= 0x8000, /* OR bitflag - don't draw anything */
236 WVS_SHOW
= 0x4000, /* OR bitflag - show the WVS */
237 WVS_HP_PAUSE
= 0x2000,
238 WVS_HIDE
= 0x0000, /* hide the WVS (aid readability) */
239 WVS_REFRESH_ALL
= 0x000f, /* Only immediate graphical elements */
242 /* Status icons selected according to font height */
243 extern const unsigned char mpegplayer_status_icons_8x8x1
[];
244 extern const unsigned char mpegplayer_status_icons_12x12x1
[];
245 extern const unsigned char mpegplayer_status_icons_16x16x1
[];
247 /* Main border areas that contain WVS elements */
261 long next_auto_refresh
;
268 unsigned prog_fillcolor
;
269 struct vo_rect update_rect
;
270 struct vo_rect prog_rect
;
271 struct vo_rect time_rect
;
272 struct vo_rect dur_rect
;
273 struct vo_rect vol_rect
;
274 const unsigned char *icons
;
275 struct vo_rect stat_rect
;
278 unsigned auto_refresh
;
282 static struct wvs wvs
;
284 static void wvs_show(unsigned show
);
287 #define _X (x + wvs.x)
288 #define _Y (y + wvs.y)
292 #define _X (LCD_WIDTH - (y + wvs.y) - height)
293 #define _Y (x + wvs.x)
298 #ifdef HAVE_LCD_COLOR
299 /* Blend two colors in 0-100% (0-255) mix of c2 into c1 */
300 static unsigned draw_blendcolor(unsigned c1
, unsigned c2
, unsigned char amount
)
302 int r1
= RGB_UNPACK_RED(c1
);
303 int g1
= RGB_UNPACK_GREEN(c1
);
304 int b1
= RGB_UNPACK_BLUE(c1
);
306 int r2
= RGB_UNPACK_RED(c2
);
307 int g2
= RGB_UNPACK_GREEN(c2
);
308 int b2
= RGB_UNPACK_BLUE(c2
);
310 return LCD_RGBPACK(amount
*(r2
- r1
) / 255 + r1
,
311 amount
*(g2
- g1
) / 255 + g1
,
312 amount
*(b2
- b1
) / 255 + b1
);
316 /* Drawing functions that operate rotated on LCD_PORTRAIT displays -
317 * most are just wrappers of lcd_* functions with transforms applied.
318 * The origin is the upper-left corner of the WVS area */
319 static void draw_update_rect(int x
, int y
, int width
, int height
)
321 lcd_(update_rect
)(_X
, _Y
, _W
, _H
);
324 static void draw_clear_area(int x
, int y
, int width
, int height
)
326 #ifdef HAVE_LCD_COLOR
327 rb
->screen_clear_area(rb
->screens
[SCREEN_MAIN
], _X
, _Y
, _W
, _H
);
329 int oldmode
= grey_get_drawmode();
330 grey_set_drawmode(DRMODE_SOLID
| DRMODE_INVERSEVID
);
331 grey_fillrect(_X
, _Y
, _W
, _H
);
332 grey_set_drawmode(oldmode
);
336 static void draw_clear_area_rect(const struct vo_rect
*rc
)
340 int width
= rc
->r
- rc
->l
;
341 int height
= rc
->b
- rc
->t
;
342 #ifdef HAVE_LCD_COLOR
343 rb
->screen_clear_area(rb
->screens
[SCREEN_MAIN
], _X
, _Y
, _W
, _H
);
345 int oldmode
= grey_get_drawmode();
346 grey_set_drawmode(DRMODE_SOLID
| DRMODE_INVERSEVID
);
347 grey_fillrect(_X
, _Y
, _W
, _H
);
348 grey_set_drawmode(oldmode
);
352 static void draw_fillrect(int x
, int y
, int width
, int height
)
354 lcd_(fillrect
)(_X
, _Y
, _W
, _H
);
357 static void draw_hline(int x1
, int x2
, int y
)
360 lcd_(hline
)(x1
+ wvs
.x
, x2
+ wvs
.x
, y
+ wvs
.y
);
362 y
= LCD_WIDTH
- (y
+ wvs
.y
) - 1;
363 lcd_(vline
)(y
, x1
+ wvs
.x
, x2
+ wvs
.x
);
367 static void draw_vline(int x
, int y1
, int y2
)
370 lcd_(vline
)(x
+ wvs
.x
, y1
+ wvs
.y
, y2
+ wvs
.y
);
372 y1
= LCD_WIDTH
- (y1
+ wvs
.y
) - 1;
373 y2
= LCD_WIDTH
- (y2
+ wvs
.y
) - 1;
374 lcd_(hline
)(y1
, y2
, x
+ wvs
.x
);
378 static void draw_scrollbar_draw(int x
, int y
, int width
, int height
,
379 uint32_t min
, uint32_t max
, uint32_t val
)
381 unsigned oldfg
= lcd_(get_foreground
)();
383 draw_hline(x
+ 1, x
+ width
- 2, y
);
384 draw_hline(x
+ 1, x
+ width
- 2, y
+ height
- 1);
385 draw_vline(x
, y
+ 1, y
+ height
- 2);
386 draw_vline(x
+ width
- 1, y
+ 1, y
+ height
- 2);
388 val
= muldiv_uint32(width
- 2, val
, max
- min
);
389 val
= MIN(val
, (uint32_t)(width
- 2));
391 draw_fillrect(x
+ 1, y
+ 1, val
, height
- 2);
393 lcd_(set_foreground
)(wvs
.prog_fillcolor
);
395 draw_fillrect(x
+ 1 + val
, y
+ 1, width
- 2 - val
, height
- 2);
397 lcd_(set_foreground
)(oldfg
);
400 static void draw_scrollbar_draw_rect(const struct vo_rect
*rc
, int min
,
403 draw_scrollbar_draw(rc
->l
, rc
->t
, rc
->r
- rc
->l
, rc
->b
- rc
->t
,
408 /* Portrait displays need rotated text rendering */
410 /* Limited function that only renders in DRMODE_FG and uses absolute screen
412 static void draw_oriented_mono_bitmap_part(const unsigned char *src
,
413 int src_x
, int src_y
,
414 int stride
, int x
, int y
,
415 int width
, int height
)
417 const unsigned char *src_end
;
418 fb_data
*dst
, *dst_end
;
419 unsigned fg_pattern
, bg_pattern
;
421 if (x
+ width
> SCREEN_WIDTH
)
422 width
= SCREEN_WIDTH
- x
; /* Clip right */
424 width
+= x
, x
= 0; /* Clip left */
426 return; /* nothing left to do */
428 if (y
+ height
> SCREEN_HEIGHT
)
429 height
= SCREEN_HEIGHT
- y
; /* Clip bottom */
431 height
+= y
, y
= 0; /* Clip top */
433 return; /* nothing left to do */
435 fg_pattern
= rb
->lcd_get_foreground();
436 bg_pattern
= rb
->lcd_get_background();
438 src
+= stride
* (src_y
>> 3) + src_x
; /* move starting point */
440 src_end
= src
+ width
;
442 dst
= rb
->lcd_framebuffer
+ (LCD_WIDTH
- y
) + x
*LCD_WIDTH
;
445 const unsigned char *src_col
= src
++;
446 unsigned data
= *src_col
>> src_y
;
447 int numbits
= 8 - src_y
;
449 fb_data
*dst_col
= dst
;
450 dst_end
= dst_col
- height
;
458 *dst_col
= fg_pattern
;
461 *dst_col
= bg_pattern
;
464 if (--numbits
== 0) {
470 while (dst_col
> dst_end
);
472 while (src
< src_end
);
475 static void draw_putsxy_oriented(int x
, int y
, const char *str
)
480 struct font
* pf
= rb
->font_get(FONT_UI
);
482 ucs
= rb
->bidi_l2v(str
, 1);
487 while ((ch
= *ucs
++) != 0 && x
< SCREEN_WIDTH
)
490 const unsigned char *bits
;
492 /* get proportional width and glyph bits */
493 width
= rb
->font_get_width(pf
, ch
);
500 bits
= rb
->font_get_bits(pf
, ch
);
502 draw_oriented_mono_bitmap_part(bits
, ofs
, 0, width
, x
, y
,
503 width
- ofs
, pf
->height
);
510 static void draw_oriented_mono_bitmap_part(const unsigned char *src
,
511 int src_x
, int src_y
,
512 int stride
, int x
, int y
,
513 int width
, int height
)
515 int mode
= lcd_(get_drawmode
)();
516 lcd_(set_drawmode
)(DRMODE_FG
);
517 lcd_(mono_bitmap_part
)(src
, src_x
, src_y
, stride
, x
, y
, width
, height
);
518 lcd_(set_drawmode
)(mode
);
521 static void draw_putsxy_oriented(int x
, int y
, const char *str
)
523 int mode
= lcd_(get_drawmode
)();
524 lcd_(set_drawmode
)(DRMODE_FG
);
525 lcd_(putsxy
)(x
+ wvs
.x
, y
+ wvs
.y
, str
);
526 lcd_(set_drawmode
)(mode
);
528 #endif /* LCD_PORTRAIT */
531 static void wvs_text_init(void)
538 lcd_(setfont
)(FONT_UI
);
541 wvs
.width
= SCREEN_WIDTH
;
543 vo_rect_clear(&wvs
.time_rect
);
544 vo_rect_clear(&wvs
.stat_rect
);
545 vo_rect_clear(&wvs
.prog_rect
);
546 vo_rect_clear(&wvs
.vol_rect
);
548 ts_to_hms(stream_get_duration(), &hms
);
549 hms_format(buf
, sizeof (buf
), &hms
);
550 lcd_(getstringsize
)(buf
, &wvs
.time_rect
.r
, &wvs
.time_rect
.b
);
552 /* Choose well-sized bitmap images relative to font height */
553 if (wvs
.time_rect
.b
< 12) {
554 wvs
.icons
= mpegplayer_status_icons_8x8x1
;
555 wvs
.stat_rect
.r
= wvs
.stat_rect
.b
= 8;
556 } else if (wvs
.time_rect
.b
< 16) {
557 wvs
.icons
= mpegplayer_status_icons_12x12x1
;
558 wvs
.stat_rect
.r
= wvs
.stat_rect
.b
= 12;
560 wvs
.icons
= mpegplayer_status_icons_16x16x1
;
561 wvs
.stat_rect
.r
= wvs
.stat_rect
.b
= 16;
564 if (wvs
.stat_rect
.b
< wvs
.time_rect
.b
) {
565 vo_rect_offset(&wvs
.stat_rect
, 0,
566 (wvs
.time_rect
.b
- wvs
.stat_rect
.b
) / 2 + WVS_BDR_T
);
567 vo_rect_offset(&wvs
.time_rect
, WVS_BDR_L
, WVS_BDR_T
);
569 vo_rect_offset(&wvs
.time_rect
, WVS_BDR_L
,
570 wvs
.stat_rect
.b
- wvs
.time_rect
.b
+ WVS_BDR_T
);
571 vo_rect_offset(&wvs
.stat_rect
, 0, WVS_BDR_T
);
574 wvs
.dur_rect
= wvs
.time_rect
;
576 phys
= rb
->sound_val2phys(SOUND_VOLUME
, rb
->sound_min(SOUND_VOLUME
));
577 rb
->snprintf(buf
, sizeof(buf
), "%d%s", phys
,
578 rb
->sound_unit(SOUND_VOLUME
));
580 lcd_(getstringsize
)(" ", &spc_width
, NULL
);
581 lcd_(getstringsize
)(buf
, &wvs
.vol_rect
.r
, &wvs
.vol_rect
.b
);
583 wvs
.prog_rect
.r
= SCREEN_WIDTH
- WVS_BDR_L
- spc_width
-
584 wvs
.vol_rect
.r
- WVS_BDR_R
;
585 wvs
.prog_rect
.b
= 3*wvs
.stat_rect
.b
/ 4;
586 vo_rect_offset(&wvs
.prog_rect
, wvs
.time_rect
.l
,
589 vo_rect_offset(&wvs
.stat_rect
,
590 (wvs
.prog_rect
.r
+ wvs
.prog_rect
.l
- wvs
.stat_rect
.r
) / 2,
593 vo_rect_offset(&wvs
.dur_rect
,
594 wvs
.prog_rect
.r
- wvs
.dur_rect
.r
, 0);
596 vo_rect_offset(&wvs
.vol_rect
, wvs
.prog_rect
.r
+ spc_width
,
597 (wvs
.prog_rect
.b
+ wvs
.prog_rect
.t
- wvs
.vol_rect
.b
) / 2);
599 wvs
.height
= WVS_BDR_T
+ MAX(wvs
.prog_rect
.b
, wvs
.vol_rect
.b
) -
600 MIN(wvs
.time_rect
.t
, wvs
.stat_rect
.t
) + WVS_BDR_B
;
602 #ifdef HAVE_LCD_COLOR
603 wvs
.height
= ALIGN_UP(wvs
.height
, 2);
605 wvs
.y
= SCREEN_HEIGHT
- wvs
.height
;
607 lcd_(setfont
)(FONT_SYSFIXED
);
610 static void wvs_init(void)
614 wvs
.print_delay
= 75*HZ
/100;
615 wvs
.resume_delay
= HZ
/2;
616 #ifdef HAVE_LCD_COLOR
617 wvs
.bgcolor
= LCD_RGBPACK(0x73, 0x75, 0xbd);
618 wvs
.fgcolor
= LCD_WHITE
;
619 wvs
.prog_fillcolor
= LCD_BLACK
;
621 wvs
.bgcolor
= GREY_LIGHTGRAY
;
622 wvs
.fgcolor
= GREY_BLACK
;
623 wvs
.prog_fillcolor
= GREY_WHITE
;
626 wvs
.status
= WVS_STATUS_STOPPED
;
627 wvs
.auto_refresh
= WVS_REFRESH_TIME
;
628 wvs
.next_auto_refresh
= *rb
->current_tick
;
632 static void wvs_schedule_refresh(unsigned refresh
)
634 long tick
= *rb
->current_tick
;
636 if (refresh
& WVS_REFRESH_VIDEO
)
637 wvs
.print_tick
= tick
+ wvs
.print_delay
;
639 if (refresh
& WVS_REFRESH_RESUME
)
640 wvs
.resume_tick
= tick
+ wvs
.resume_delay
;
642 wvs
.auto_refresh
|= refresh
;
645 static void wvs_cancel_refresh(unsigned refresh
)
647 wvs
.auto_refresh
&= ~refresh
;
650 /* Refresh the background area */
651 static void wvs_refresh_background(void)
656 unsigned bg
= lcd_(get_background
)();
657 lcd_(set_drawmode
)(DRMODE_SOLID
| DRMODE_INVERSEVID
);
659 #ifdef HAVE_LCD_COLOR
660 /* Draw a "raised" area for our graphics */
661 lcd_(set_background
)(draw_blendcolor(bg
, DRAW_WHITE
, 192));
662 draw_hline(0, wvs
.width
, 0);
664 lcd_(set_background
)(draw_blendcolor(bg
, DRAW_WHITE
, 80));
665 draw_hline(0, wvs
.width
, 1);
667 lcd_(set_background
)(draw_blendcolor(bg
, DRAW_BLACK
, 48));
668 draw_hline(0, wvs
.width
, wvs
.height
-2);
670 lcd_(set_background
)(draw_blendcolor(bg
, DRAW_BLACK
, 128));
671 draw_hline(0, wvs
.width
, wvs
.height
-1);
673 lcd_(set_background
)(bg
);
674 draw_clear_area(0, 2, wvs
.width
, wvs
.height
- 4);
676 /* Give contrast with the main background */
677 lcd_(set_background
)(GREY_WHITE
);
678 draw_hline(0, wvs
.width
, 0);
680 lcd_(set_background
)(GREY_DARKGRAY
);
681 draw_hline(0, wvs
.width
, wvs
.height
-1);
683 lcd_(set_background
)(bg
);
684 draw_clear_area(0, 1, wvs
.width
, wvs
.height
- 2);
687 vo_rect_set_ext(&wvs
.update_rect
, 0, 0, wvs
.width
, wvs
.height
);
688 lcd_(set_drawmode
)(DRMODE_SOLID
);
690 if (stream_get_duration() != INVALID_TIMESTAMP
) {
691 /* Draw the movie duration */
692 ts_to_hms(stream_get_duration(), &hms
);
693 hms_format(buf
, sizeof (buf
), &hms
);
694 draw_putsxy_oriented(wvs
.dur_rect
.l
, wvs
.dur_rect
.t
, buf
);
696 /* else don't know the duration */
699 /* Refresh the current time display + the progress bar */
700 static void wvs_refresh_time(void)
705 uint32_t duration
= stream_get_duration();
707 draw_scrollbar_draw_rect(&wvs
.prog_rect
, 0, duration
,
710 ts_to_hms(wvs
.curr_time
, &hms
);
711 hms_format(buf
, sizeof (buf
), &hms
);
713 draw_clear_area_rect(&wvs
.time_rect
);
714 draw_putsxy_oriented(wvs
.time_rect
.l
, wvs
.time_rect
.t
, buf
);
716 vo_rect_union(&wvs
.update_rect
, &wvs
.update_rect
,
718 vo_rect_union(&wvs
.update_rect
, &wvs
.update_rect
,
722 /* Refresh the volume display area */
723 static void wvs_refresh_volume(void)
728 int volume
= rb
->global_settings
->volume
;
729 rb
->snprintf(buf
, sizeof (buf
), "%d%s",
730 rb
->sound_val2phys(SOUND_VOLUME
, volume
),
731 rb
->sound_unit(SOUND_VOLUME
));
732 lcd_(getstringsize
)(buf
, &width
, NULL
);
734 /* Right-justified */
735 draw_clear_area_rect(&wvs
.vol_rect
);
736 draw_putsxy_oriented(wvs
.vol_rect
.r
- width
, wvs
.vol_rect
.t
, buf
);
738 vo_rect_union(&wvs
.update_rect
, &wvs
.update_rect
, &wvs
.vol_rect
);
741 /* Refresh the status icon */
742 static void wvs_refresh_status(void)
744 int icon_size
= wvs
.stat_rect
.r
- wvs
.stat_rect
.l
;
746 draw_clear_area_rect(&wvs
.stat_rect
);
748 #ifdef HAVE_LCD_COLOR
749 /* Draw status icon with a drop shadow */
750 unsigned oldfg
= lcd_(get_foreground
)();
753 lcd_(set_foreground
)(draw_blendcolor(lcd_(get_background
)(),
758 draw_oriented_mono_bitmap_part(wvs
.icons
,
759 icon_size
*wvs
.status
,
761 icon_size
*WVS_STATUS_COUNT
,
762 wvs
.stat_rect
.l
+ wvs
.x
+ i
,
763 wvs
.stat_rect
.t
+ wvs
.y
+ i
,
764 icon_size
, icon_size
);
769 lcd_(set_foreground
)(oldfg
);
772 vo_rect_union(&wvs
.update_rect
, &wvs
.update_rect
, &wvs
.stat_rect
);
774 draw_oriented_mono_bitmap_part(wvs
.icons
,
775 icon_size
*wvs
.status
,
777 icon_size
*WVS_STATUS_COUNT
,
778 wvs
.stat_rect
.l
+ wvs
.x
,
779 wvs
.stat_rect
.t
+ wvs
.y
,
780 icon_size
, icon_size
);
781 vo_rect_union(&wvs
.update_rect
, &wvs
.update_rect
, &wvs
.stat_rect
);
785 /* Update the current status which determines which icon is displayed */
786 static bool wvs_update_status(void)
790 switch (stream_status())
793 status
= WVS_STATUS_STOPPED
;
796 /* If paused with a pending resume, coerce it to WVS_STATUS_PLAYING */
797 status
= (wvs
.auto_refresh
& WVS_REFRESH_RESUME
) ?
798 WVS_STATUS_PLAYING
: WVS_STATUS_PAUSED
;
801 status
= WVS_STATUS_PLAYING
;
805 if (status
!= wvs
.status
) {
806 /* A refresh is needed */
814 /* Update the current time that will be displayed */
815 static void wvs_update_time(void)
818 wvs
.curr_time
= stream_get_seek_time(&start
);
819 wvs
.curr_time
-= start
;
822 /* Refresh various parts of the WVS - showing it if it is hidden */
823 static void wvs_refresh(int hint
)
826 unsigned oldbg
, oldfg
;
828 tick
= *rb
->current_tick
;
830 if (hint
== WVS_REFRESH_DEFAULT
) {
831 /* The default which forces no updates */
833 /* Make sure Rockbox doesn't turn off the player because of
834 too little activity */
835 if (wvs
.status
== WVS_STATUS_PLAYING
)
836 rb
->reset_poweroff_timer();
838 /* Redraw the current or possibly extract a new video frame */
839 if ((wvs
.auto_refresh
& WVS_REFRESH_VIDEO
) &&
840 TIME_AFTER(tick
, wvs
.print_tick
)) {
841 wvs
.auto_refresh
&= ~WVS_REFRESH_VIDEO
;
842 stream_draw_frame(false);
845 /* Restart playback if the timout was reached */
846 if ((wvs
.auto_refresh
& WVS_REFRESH_RESUME
) &&
847 TIME_AFTER(tick
, wvs
.resume_tick
)) {
848 wvs
.auto_refresh
&= ~(WVS_REFRESH_RESUME
| WVS_REFRESH_VIDEO
);
852 /* If not visible, return */
853 if (!(wvs
.flags
& WVS_SHOW
))
856 /* Hide if the visibility duration was reached */
857 if (TIME_AFTER(tick
, wvs
.hide_tick
)) {
862 /* A forced update of some region */
864 /* Show if currently invisible */
865 if (!(wvs
.flags
& WVS_SHOW
)) {
866 /* Avoid call back into this function - it will be drawn */
867 wvs_show(WVS_SHOW
| WVS_NODRAW
);
868 hint
= WVS_REFRESH_ALL
;
871 /* Move back timeouts for frame print and hide */
872 wvs
.print_tick
= tick
+ wvs
.print_delay
;
873 wvs
.hide_tick
= tick
+ wvs
.show_for
;
876 if (TIME_AFTER(tick
, wvs
.next_auto_refresh
)) {
877 /* Refresh whatever graphical elements are due automatically */
878 wvs
.next_auto_refresh
= tick
+ WVS_MIN_UPDATE_INTERVAL
;
880 if (wvs
.auto_refresh
& WVS_REFRESH_STATUS
) {
881 if (wvs_update_status())
882 hint
|= WVS_REFRESH_STATUS
;
885 if (wvs
.auto_refresh
& WVS_REFRESH_TIME
) {
887 hint
|= WVS_REFRESH_TIME
;
892 return; /* No drawing needed */
894 /* Set basic drawing params that are used. Elements that perform variations
895 * will restore them. */
896 oldfg
= lcd_(get_foreground
)();
897 oldbg
= lcd_(get_background
)();
899 lcd_(setfont
)(FONT_UI
);
900 lcd_(set_foreground
)(wvs
.fgcolor
);
901 lcd_(set_background
)(wvs
.bgcolor
);
903 vo_rect_clear(&wvs
.update_rect
);
905 if (hint
& WVS_REFRESH_BACKGROUND
) {
906 wvs_refresh_background();
907 hint
|= WVS_REFRESH_ALL
; /* Requires a redraw of everything */
910 if (hint
& WVS_REFRESH_TIME
) {
914 if (hint
& WVS_REFRESH_VOLUME
) {
915 wvs_refresh_volume();
918 if (hint
& WVS_REFRESH_STATUS
) {
919 wvs_refresh_status();
922 /* Go back to defaults */
923 lcd_(setfont
)(FONT_SYSFIXED
);
924 lcd_(set_foreground
)(oldfg
);
925 lcd_(set_background
)(oldbg
);
927 /* Update the dirty rectangle */
930 draw_update_rect(wvs
.update_rect
.l
,
932 wvs
.update_rect
.r
- wvs
.update_rect
.l
,
933 wvs
.update_rect
.b
- wvs
.update_rect
.t
);
938 /* Show/Hide the WVS */
939 static void wvs_show(unsigned show
)
941 if (((show
^ wvs
.flags
) & WVS_SHOW
) == 0)
943 if (show
& WVS_SHOW
) {
944 wvs
.hide_tick
= *rb
->current_tick
+ wvs
.show_for
;
949 if (show
& WVS_SHOW
) {
950 /* Clip away the part of video that is covered */
951 struct vo_rect rc
= { 0, 0, SCREEN_WIDTH
, wvs
.y
};
953 wvs
.flags
|= WVS_SHOW
;
955 stream_vo_set_clip(&rc
);
957 if (!(show
& WVS_NODRAW
))
958 wvs_refresh(WVS_REFRESH_ALL
);
960 /* Uncover clipped video area and redraw it */
961 wvs
.flags
&= ~WVS_SHOW
;
963 draw_clear_area(0, 0, wvs
.width
, wvs
.height
);
965 if (!(show
& WVS_NODRAW
)) {
967 draw_update_rect(0, 0, wvs
.width
, wvs
.height
);
970 stream_vo_set_clip(NULL
);
971 stream_draw_frame(false);
973 stream_vo_set_clip(NULL
);
978 /* Set the current status - update screen if specified */
979 static void wvs_set_status(int status
)
981 bool draw
= (status
& WVS_NODRAW
) == 0;
983 status
&= WVS_STATUS_MASK
;
985 if (wvs
.status
!= status
) {
990 wvs_refresh(WVS_REFRESH_STATUS
);
994 /* Get the current status value */
995 static int wvs_get_status(void)
997 return wvs
.status
& WVS_STATUS_MASK
;
1000 /* Handle Fast-forward/Rewind keys using WPS settings (and some nicked code ;) */
1001 static uint32_t wvs_ff_rw(int btn
, unsigned refresh
)
1003 unsigned int step
= TS_SECOND
*rb
->global_settings
->ff_rewind_min_step
;
1004 const long ff_rw_accel
= rb
->global_settings
->ff_rewind_accel
;
1005 long accel_tick
= *rb
->current_tick
+ ff_rw_accel
*HZ
;
1007 uint32_t time
= stream_get_seek_time(&start
);
1008 const uint32_t duration
= stream_get_duration();
1009 unsigned int max_step
= 0;
1010 uint32_t ff_rw_count
= 0;
1011 unsigned status
= wvs
.status
;
1013 wvs_cancel_refresh(WVS_REFRESH_VIDEO
| WVS_REFRESH_RESUME
|
1016 time
-= start
; /* Absolute clock => stream-relative */
1024 wvs_set_status(WVS_STATUS_FF
);
1030 wvs_set_status(WVS_STATUS_RW
);
1036 btn
|= BUTTON_REPEAT
;
1040 long tick
= *rb
->current_tick
;
1041 stream_keep_disk_active();
1046 wvs_refresh(WVS_REFRESH_DEFAULT
);
1049 case MPEG_FF
| BUTTON_REPEAT
:
1050 case MPEG_RW
| BUTTON_REPEAT
:
1052 case MPEG_RC_FF
| BUTTON_REPEAT
:
1053 case MPEG_RC_RW
| BUTTON_REPEAT
:
1057 case MPEG_FF
| BUTTON_REL
:
1058 case MPEG_RW
| BUTTON_REL
:
1060 case MPEG_RC_FF
| BUTTON_REL
:
1061 case MPEG_RC_RW
| BUTTON_REL
:
1063 if (wvs
.status
== WVS_STATUS_FF
)
1064 time
+= ff_rw_count
;
1065 else if (wvs
.status
== WVS_STATUS_RW
)
1066 time
-= ff_rw_count
;
1071 wvs_schedule_refresh(refresh
);
1072 wvs_set_status(status
);
1073 wvs_schedule_refresh(WVS_REFRESH_TIME
);
1077 if (wvs
.status
== WVS_STATUS_FF
) {
1078 /* fast forwarding, calc max step relative to end */
1079 max_step
= muldiv_uint32(duration
- (time
+ ff_rw_count
),
1080 FF_REWIND_MAX_PERCENT
, 100);
1082 /* rewinding, calc max step relative to start */
1083 max_step
= muldiv_uint32(time
- ff_rw_count
,
1084 FF_REWIND_MAX_PERCENT
, 100);
1087 max_step
= MAX(max_step
, MIN_FF_REWIND_STEP
);
1089 if (step
> max_step
)
1092 ff_rw_count
+= step
;
1094 if (ff_rw_accel
!= 0 && TIME_AFTER(tick
, accel_tick
)) {
1096 accel_tick
= tick
+ ff_rw_accel
*HZ
;
1099 if (wvs
.status
== WVS_STATUS_FF
) {
1100 if (duration
- time
<= ff_rw_count
)
1101 ff_rw_count
= duration
- time
;
1103 wvs
.curr_time
= time
+ ff_rw_count
;
1105 if (time
<= ff_rw_count
)
1108 wvs
.curr_time
= time
- ff_rw_count
;
1111 wvs_refresh(WVS_REFRESH_TIME
);
1113 btn
= rb
->button_get_w_tmo(WVS_MIN_UPDATE_INTERVAL
);
1117 static int wvs_status(void)
1119 int status
= stream_status();
1121 /* Coerce to STREAM_PLAYING if paused with a pending resume */
1122 if (status
== STREAM_PAUSED
) {
1123 if (wvs
.auto_refresh
& WVS_REFRESH_RESUME
)
1124 status
= STREAM_PLAYING
;
1130 /* Change the current audio volume by a specified amount */
1131 static void wvs_set_volume(int delta
)
1133 int vol
= rb
->global_settings
->volume
;
1139 /* Volume down - clip to lower limit */
1140 limit
= rb
->sound_min(SOUND_VOLUME
);
1144 /* Volume up - clip to upper limit */
1145 limit
= rb
->sound_max(SOUND_VOLUME
);
1150 /* Sync the global settings */
1151 if (vol
!= rb
->global_settings
->volume
) {
1152 rb
->sound_set(SOUND_VOLUME
, vol
);
1153 rb
->global_settings
->volume
= vol
;
1156 /* Update the volume display */
1157 wvs_refresh(WVS_REFRESH_VOLUME
);
1160 /* Begin playback at the specified time */
1161 static int wvs_play(uint32_t time
)
1165 wvs_cancel_refresh(WVS_REFRESH_VIDEO
| WVS_REFRESH_RESUME
);
1167 retval
= stream_seek(time
, SEEK_SET
);
1169 if (retval
>= STREAM_OK
) {
1170 stream_show_vo(true);
1171 retval
= stream_play();
1173 if (retval
>= STREAM_OK
)
1174 wvs_set_status(WVS_STATUS_PLAYING
| WVS_NODRAW
);
1180 /* Halt playback - pause engine and return logical state */
1181 static int wvs_halt(void)
1183 int status
= stream_pause();
1185 /* Coerce to STREAM_PLAYING if paused with a pending resume */
1186 if (status
== STREAM_PAUSED
) {
1187 if (wvs_get_status() == WVS_STATUS_PLAYING
)
1188 status
= STREAM_PLAYING
;
1191 /* Cancel some auto refreshes - caller will restart them if desired */
1192 wvs_cancel_refresh(WVS_REFRESH_VIDEO
| WVS_REFRESH_RESUME
);
1197 /* Pause playback if playing */
1198 static int wvs_pause(void)
1200 unsigned refresh
= wvs
.auto_refresh
;
1201 int status
= wvs_halt();
1203 if (status
== STREAM_PLAYING
&& (refresh
& WVS_REFRESH_RESUME
)) {
1204 /* Resume pending - change to a still video frame update */
1205 wvs_schedule_refresh(WVS_REFRESH_VIDEO
);
1208 wvs_set_status(WVS_STATUS_PAUSED
);
1213 /* Resume playback if halted or paused */
1214 static void wvs_resume(void)
1216 /* Cancel video and resume auto refresh - the resyc when starting playback
1217 * will perform those tasks */
1218 wvs_cancel_refresh(WVS_REFRESH_VIDEO
| WVS_REFRESH_RESUME
);
1219 wvs_set_status(WVS_STATUS_PLAYING
);
1223 /* Stop playback - remember the resume point if not closed */
1224 static void wvs_stop(void)
1226 uint32_t resume_time
;
1228 wvs_cancel_refresh(WVS_REFRESH_VIDEO
| WVS_REFRESH_RESUME
);
1229 wvs_set_status(WVS_STATUS_STOPPED
| WVS_NODRAW
);
1230 wvs_show(WVS_HIDE
| WVS_NODRAW
);
1234 resume_time
= stream_get_resume_time();
1236 if (resume_time
!= INVALID_TIMESTAMP
)
1237 settings
.resume_time
= resume_time
;
1240 /* Perform a seek if seeking is possible for this stream - if playing, a delay
1241 * will be inserted before restarting in case the user decides to seek again */
1242 static void wvs_seek(int btn
)
1248 if (!stream_can_seek())
1251 /* Halt playback - not strictly nescessary but nice */
1252 status
= wvs_halt();
1254 if (status
== STREAM_STOPPED
)
1259 if (status
== STREAM_PLAYING
)
1260 refresh
= WVS_REFRESH_RESUME
; /* delay resume if playing */
1262 refresh
= WVS_REFRESH_VIDEO
; /* refresh if paused */
1264 /* Obtain a new playback point */
1265 time
= wvs_ff_rw(btn
, refresh
);
1267 /* Tell engine to resume at that time */
1268 stream_seek(time
, SEEK_SET
);
1271 #ifdef HAVE_HEADPHONE_DETECTION
1272 /* Handle SYS_PHONE_PLUGGED/UNPLUGGED */
1273 static void wvs_handle_phone_plug(bool inserted
)
1275 if (rb
->global_settings
->unplug_mode
== 0)
1278 /* Wait for any incomplete state transition to complete first */
1279 stream_wait_status();
1281 int status
= wvs_status();
1284 if (rb
->global_settings
->unplug_mode
> 1) {
1285 if (status
== STREAM_PAUSED
) {
1286 backlight_force_on(rb
);
1291 if (status
== STREAM_PLAYING
) {
1293 backlight_use_settings(rb
);
1295 if (stream_can_seek() && rb
->global_settings
->unplug_rw
) {
1296 stream_seek(-rb
->global_settings
->unplug_rw
*TS_SECOND
,
1298 wvs_schedule_refresh(WVS_REFRESH_VIDEO
);
1299 /* Update time display now */
1301 wvs_refresh(WVS_REFRESH_TIME
);
1308 static void button_loop(void)
1310 rb
->lcd_setfont(FONT_SYSFIXED
);
1311 rb
->lcd_clear_display();
1314 /* Turn off backlight timeout */
1315 /* backlight control in lib/helper.c */
1316 backlight_force_on(rb
);
1320 /* Start playback at the specified starting time */
1321 if (wvs_play(settings
.resume_time
) < STREAM_OK
) {
1322 rb
->splash(HZ
*2, "Playback failed");
1326 /* Gently poll the video player for EOS and handle UI */
1327 while (stream_status() != STREAM_STOPPED
)
1331 mpeg_menu_sysevent_clear();
1332 button
= rb
->button_get_w_tmo(WVS_MIN_UPDATE_INTERVAL
);
1334 button
= mpeg_menu_sysevent_callback(button
, -1);
1340 wvs_refresh(WVS_REFRESH_DEFAULT
);
1342 } /* BUTTON_NONE: */
1345 case MPEG_VOLUP
|BUTTON_REPEAT
:
1348 case MPEG_VOLUP2
|BUTTON_REPEAT
:
1350 #ifdef MPEG_RC_VOLUP
1352 case MPEG_RC_VOLUP
|BUTTON_REPEAT
:
1357 } /* MPEG_VOLUP*: */
1360 case MPEG_VOLDOWN
|BUTTON_REPEAT
:
1361 #ifdef MPEG_VOLDOWN2
1363 case MPEG_VOLDOWN2
|BUTTON_REPEAT
:
1365 #ifdef MPEG_RC_VOLDOWN
1366 case MPEG_RC_VOLDOWN
:
1367 case MPEG_RC_VOLDOWN
|BUTTON_REPEAT
:
1372 } /* MPEG_VOLDOWN*: */
1379 int state
= wvs_halt(); /* save previous state */
1382 /* Hide video output */
1383 wvs_show(WVS_HIDE
| WVS_NODRAW
);
1384 stream_show_vo(false);
1385 backlight_use_settings(rb
);
1387 result
= mpeg_menu(0);
1389 /* The menu can change the font, so restore */
1390 rb
->lcd_setfont(FONT_SYSFIXED
);
1394 case MPEG_MENU_QUIT
:
1399 /* If not stopped, show video again */
1400 if (state
!= STREAM_STOPPED
) {
1402 stream_show_vo(true);
1405 /* If stream was playing, restart it */
1406 if (state
== STREAM_PLAYING
) {
1407 backlight_force_on(rb
);
1419 case ACTION_STD_CANCEL
:
1429 #ifdef MPEG_RC_PAUSE
1433 int status
= wvs_status();
1435 if (status
== STREAM_PLAYING
) {
1436 /* Playing => Paused */
1438 backlight_use_settings(rb
);
1440 else if (status
== STREAM_PAUSED
) {
1441 /* Paused => Playing */
1442 backlight_force_on(rb
);
1447 } /* MPEG_PAUSE*: */
1458 } /* MPEG_RW: MPEG_FF: */
1460 #ifdef HAVE_HEADPHONE_DETECTION
1461 case SYS_PHONE_PLUGGED
:
1462 case SYS_PHONE_UNPLUGGED
:
1464 wvs_handle_phone_plug(button
== SYS_PHONE_PLUGGED
);
1466 } /* SYS_PHONE_*: */
1471 rb
->default_event_handler(button
);
1481 rb
->lcd_setfont(FONT_UI
);
1483 /* Turn on backlight timeout (revert to settings) */
1484 backlight_use_settings(rb
);
1487 enum plugin_status
plugin_start(struct plugin_api
* api
, void* parameter
)
1489 int status
= PLUGIN_ERROR
; /* assume failure */
1492 const char *errstring
;
1494 if (parameter
== NULL
) {
1495 /* No file = GTFO */
1496 api
->splash(HZ
*2, "No File");
1497 return PLUGIN_ERROR
;
1500 /* Disable all talking before initializing IRAM */
1501 api
->talk_disable(true);
1503 /* Initialize IRAM - stops audio and voice as well */
1504 PLUGIN_IRAM_INIT(api
)
1508 #ifdef HAVE_LCD_COLOR
1509 rb
->lcd_set_backdrop(NULL
);
1510 rb
->lcd_set_foreground(LCD_WHITE
);
1511 rb
->lcd_set_background(LCD_BLACK
);
1514 rb
->lcd_clear_display();
1517 if (stream_init() < STREAM_OK
) {
1518 DEBUGF("Could not initialize streams\n");
1520 rb
->splash(0, "Loading...");
1521 init_settings((char*)parameter
);
1523 err
= stream_open((char *)parameter
);
1525 if (err
>= STREAM_OK
) {
1527 rb
->lcd_clear_display();
1529 result
= mpeg_start_menu(stream_get_duration());
1531 if (result
!= MPEG_START_QUIT
) {
1532 /* Enter button loop and process UI */
1538 rb
->lcd_clear_display();
1541 save_settings(); /* Save settings (if they have changed) */
1544 mpeg_menu_sysevent_handle();
1546 DEBUGF("Could not open %s\n", (char*)parameter
);
1549 case STREAM_UNSUPPORTED
:
1550 errstring
= "Unsupported format";
1553 errstring
= "Error opening file: %d";
1556 rb
->splash(HZ
*2, errstring
, err
);
1562 rb
->talk_disable(false);