diff options
author | Robert Kukla <roolku@rockbox.org> | 2007-10-09 20:42:20 +0000 |
---|---|---|
committer | Robert Kukla <roolku@rockbox.org> | 2007-10-09 20:42:20 +0000 |
commit | fd3fe45bc14a0a540f2525102551c92a64a73b76 (patch) | |
tree | 1ef8103bbfa5b33f684a94bddc5ecb4685ec5e88 | |
parent | ce135909b9393d9824b3f69a70659400480cc069 (diff) | |
download | rockbox-fd3fe45bc14a0a540f2525102551c92a64a73b76.tar.gz rockbox-fd3fe45bc14a0a540f2525102551c92a64a73b76.zip |
FS#7487 - mpegplayer - video start time seek with resume
by John S. Gwynne & Brian J. Morey
This should stop the patch from breaking again and give them opportunity to improve it further.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15052 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r-- | apps/plugin.c | 2 | ||||
-rw-r--r-- | apps/plugin.h | 4 | ||||
-rw-r--r-- | apps/plugins/lib/configfile.c | 75 | ||||
-rw-r--r-- | apps/plugins/lib/configfile.h | 32 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/alloc.c | 6 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/header.c | 7 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/idct.c | 6 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/idct_arm_c.c | 6 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/mpeg_settings.c | 410 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/mpeg_settings.h | 17 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/mpegplayer.c | 770 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/video_out.h | 2 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/video_out_rockbox.c | 108 | ||||
-rw-r--r-- | docs/CREDITS | 2 | ||||
-rw-r--r-- | firmware/drivers/button.c | 5 | ||||
-rw-r--r-- | firmware/export/button.h | 1 | ||||
-rw-r--r-- | uisimulator/sdl/button.c | 5 |
17 files changed, 1240 insertions, 218 deletions
diff --git a/apps/plugin.c b/apps/plugin.c index d5f70be043..7981c36b92 100644 --- a/apps/plugin.c +++ b/apps/plugin.c | |||
@@ -518,6 +518,8 @@ static const struct plugin_api rockbox_api = { | |||
518 | talk_disable_menus, | 518 | talk_disable_menus, |
519 | talk_enable_menus, | 519 | talk_enable_menus, |
520 | 520 | ||
521 | button_available, | ||
522 | |||
521 | }; | 523 | }; |
522 | 524 | ||
523 | int plugin_load(const char* plugin, void* parameter) | 525 | int plugin_load(const char* plugin, void* parameter) |
diff --git a/apps/plugin.h b/apps/plugin.h index f025704f31..7277d8031c 100644 --- a/apps/plugin.h +++ b/apps/plugin.h | |||
@@ -112,7 +112,7 @@ | |||
112 | #define PLUGIN_MAGIC 0x526F634B /* RocK */ | 112 | #define PLUGIN_MAGIC 0x526F634B /* RocK */ |
113 | 113 | ||
114 | /* increase this every time the api struct changes */ | 114 | /* increase this every time the api struct changes */ |
115 | #define PLUGIN_API_VERSION 79 | 115 | #define PLUGIN_API_VERSION 80 |
116 | 116 | ||
117 | /* update this to latest version if a change to the api struct breaks | 117 | /* update this to latest version if a change to the api struct breaks |
118 | backwards compatibility (and please take the opportunity to sort in any | 118 | backwards compatibility (and please take the opportunity to sort in any |
@@ -635,6 +635,8 @@ struct plugin_api { | |||
635 | 635 | ||
636 | void (*talk_disable_menus)(void); | 636 | void (*talk_disable_menus)(void); |
637 | void (*talk_enable_menus)(void); | 637 | void (*talk_enable_menus)(void); |
638 | |||
639 | int (*button_available)(void); | ||
638 | }; | 640 | }; |
639 | 641 | ||
640 | /* plugin header */ | 642 | /* plugin header */ |
diff --git a/apps/plugins/lib/configfile.c b/apps/plugins/lib/configfile.c index 0fbba81580..476f776878 100644 --- a/apps/plugins/lib/configfile.c +++ b/apps/plugins/lib/configfile.c | |||
@@ -55,12 +55,14 @@ int configfile_save(const char *filename, struct configdata *cfg, | |||
55 | if(fd < 0) | 55 | if(fd < 0) |
56 | return fd*10 - 1; | 56 | return fd*10 - 1; |
57 | 57 | ||
58 | cfg_rb->fdprintf(fd, "file version: %d\n", version); | 58 | /* pre-allocate 10 bytes for INT */ |
59 | cfg_rb->fdprintf(fd, "file version: %10d\n", version); | ||
59 | 60 | ||
60 | for(i = 0;i < num_items;i++) { | 61 | for(i = 0;i < num_items;i++) { |
61 | switch(cfg[i].type) { | 62 | switch(cfg[i].type) { |
62 | case TYPE_INT: | 63 | case TYPE_INT: |
63 | cfg_rb->fdprintf(fd, "%s: %d\n", | 64 | /* pre-allocate 10 bytes for INT */ |
65 | cfg_rb->fdprintf(fd, "%s: %10d\n", | ||
64 | cfg[i].name, | 66 | cfg[i].name, |
65 | *cfg[i].val); | 67 | *cfg[i].val); |
66 | break; | 68 | break; |
@@ -141,3 +143,72 @@ int configfile_load(const char *filename, struct configdata *cfg, | |||
141 | cfg_rb->close(fd); | 143 | cfg_rb->close(fd); |
142 | return 0; | 144 | return 0; |
143 | } | 145 | } |
146 | |||
147 | int configfile_get_value(const char* filename, const char* name) | ||
148 | { | ||
149 | int fd; | ||
150 | char *pname; | ||
151 | char *pval; | ||
152 | char buf[MAX_PATH]; | ||
153 | |||
154 | get_cfg_filename(buf, MAX_PATH, filename); | ||
155 | fd = cfg_rb->open(buf, O_RDONLY); | ||
156 | if(fd < 0) | ||
157 | return -1; | ||
158 | |||
159 | while(cfg_rb->read_line(fd, buf, MAX_PATH) > 0) | ||
160 | { | ||
161 | cfg_rb->settings_parseline(buf, &pname, &pval); | ||
162 | if(!cfg_rb->strcmp(name, pname)) | ||
163 | { | ||
164 | cfg_rb->close(fd); | ||
165 | return cfg_rb->atoi(pval); | ||
166 | } | ||
167 | } | ||
168 | |||
169 | cfg_rb->close(fd); | ||
170 | return -1; | ||
171 | } | ||
172 | |||
173 | int configfile_update_entry(const char* filename, const char* name, int val) | ||
174 | { | ||
175 | int fd; | ||
176 | char *pname; | ||
177 | char *pval; | ||
178 | char path[MAX_PATH]; | ||
179 | char buf[256]; | ||
180 | int found = 0; | ||
181 | int line_len = 0; | ||
182 | int pos = 0; | ||
183 | |||
184 | /* open the current config file */ | ||
185 | get_cfg_filename(path, MAX_PATH, filename); | ||
186 | fd = cfg_rb->open(path, O_RDWR); | ||
187 | if(fd < 0) | ||
188 | return -1; | ||
189 | |||
190 | /* read in the current stored settings */ | ||
191 | while((line_len = cfg_rb->read_line(fd, buf, 256)) > 0) | ||
192 | { | ||
193 | cfg_rb->settings_parseline(buf, &pname, &pval); | ||
194 | |||
195 | if(!cfg_rb->strcmp(name, pname)) | ||
196 | { | ||
197 | found = 1; | ||
198 | cfg_rb->lseek(fd, pos, SEEK_SET); | ||
199 | /* pre-allocate 10 bytes for INT */ | ||
200 | cfg_rb->fdprintf(fd, "%s: %10d\n", pname, val); | ||
201 | break; | ||
202 | } | ||
203 | pos += line_len; | ||
204 | } | ||
205 | |||
206 | /* if (name/val) is a new entry just append to file */ | ||
207 | if (found == 0) | ||
208 | /* pre-allocate 10 bytes for INT */ | ||
209 | cfg_rb->fdprintf(fd, "%s: %10d\n", name, val); | ||
210 | |||
211 | cfg_rb->close(fd); | ||
212 | |||
213 | return found; | ||
214 | } | ||
diff --git a/apps/plugins/lib/configfile.h b/apps/plugins/lib/configfile.h index fcce7de275..7aa69f3ecf 100644 --- a/apps/plugins/lib/configfile.h +++ b/apps/plugins/lib/configfile.h | |||
@@ -38,9 +38,41 @@ struct configdata | |||
38 | }; | 38 | }; |
39 | 39 | ||
40 | void configfile_init(struct plugin_api* newrb); | 40 | void configfile_init(struct plugin_api* newrb); |
41 | |||
42 | /* configfile_save - Given configdata entries this function will | ||
43 | create a config file with these entries, destroying any | ||
44 | previous config file of the same name */ | ||
41 | int configfile_save(const char *filename, struct configdata *cfg, | 45 | int configfile_save(const char *filename, struct configdata *cfg, |
42 | int num_items, int version); | 46 | int num_items, int version); |
47 | |||
43 | int configfile_load(const char *filename, struct configdata *cfg, | 48 | int configfile_load(const char *filename, struct configdata *cfg, |
44 | int num_items, int min_version); | 49 | int num_items, int min_version); |
45 | 50 | ||
51 | /* configfile_get_value - Given a key name, this function will | ||
52 | return the integer value for that key. | ||
53 | |||
54 | Input: | ||
55 | filename = config file filename | ||
56 | name = (name/value) pair name entry | ||
57 | Return: | ||
58 | value if (name/value) pair is found | ||
59 | -1 if entry is not found | ||
60 | */ | ||
61 | int configfile_get_value(const char* filename, const char* name); | ||
62 | |||
63 | /* configure_update_entry - Given a key name and integer value | ||
64 | this function will update the entry if found, or add it if | ||
65 | not found. | ||
66 | |||
67 | Input: | ||
68 | filename = config file filename | ||
69 | name = (name/value) pair name entry | ||
70 | val = new value for (name/value) pair | ||
71 | Return: | ||
72 | 1 if the (name/value) pair was found and updated with the new value | ||
73 | 0 if the (name/value) pair was added as a new entry | ||
74 | -1 if error | ||
75 | */ | ||
76 | int configfile_update_entry(const char* filename, const char* name, int val); | ||
77 | |||
46 | #endif | 78 | #endif |
diff --git a/apps/plugins/mpegplayer/alloc.c b/apps/plugins/mpegplayer/alloc.c index 0ba86a51f3..ae482de112 100644 --- a/apps/plugins/mpegplayer/alloc.c +++ b/apps/plugins/mpegplayer/alloc.c | |||
@@ -54,6 +54,8 @@ static void * mpeg_malloc_internal (unsigned char *mallocbuf, | |||
54 | x = &mallocbuf[*mem_ptr]; | 54 | x = &mallocbuf[*mem_ptr]; |
55 | *mem_ptr += (size + 3) & ~3; /* Keep memory 32-bit aligned */ | 55 | *mem_ptr += (size + 3) & ~3; /* Keep memory 32-bit aligned */ |
56 | 56 | ||
57 | rb->memset(x,0,size); | ||
58 | |||
57 | return x; | 59 | return x; |
58 | (void)reason; | 60 | (void)reason; |
59 | } | 61 | } |
@@ -116,7 +118,7 @@ void * mpeg2_malloc(unsigned size, mpeg2_alloc_t reason) | |||
116 | 118 | ||
117 | void mpeg2_free(void *ptr) | 119 | void mpeg2_free(void *ptr) |
118 | { | 120 | { |
119 | (void)ptr; | 121 | mpeg2_mem_ptr = (void *)ptr - (void *)mpeg2_mallocbuf; |
120 | } | 122 | } |
121 | 123 | ||
122 | /* The following are expected by libmad */ | 124 | /* The following are expected by libmad */ |
@@ -141,7 +143,7 @@ void * codec_calloc(size_t nmemb, size_t size) | |||
141 | 143 | ||
142 | void codec_free(void* ptr) | 144 | void codec_free(void* ptr) |
143 | { | 145 | { |
144 | (void)ptr; | 146 | mem_ptr = (void *)ptr - (void *)mallocbuf; |
145 | } | 147 | } |
146 | 148 | ||
147 | void *memmove(void *dest, const void *src, size_t n) | 149 | void *memmove(void *dest, const void *src, size_t n) |
diff --git a/apps/plugins/mpegplayer/header.c b/apps/plugins/mpegplayer/header.c index 7f94705f52..7486b0ebf0 100644 --- a/apps/plugins/mpegplayer/header.c +++ b/apps/plugins/mpegplayer/header.c | |||
@@ -58,7 +58,7 @@ static const uint8_t default_intra_quantizer_matrix[64] ICONST_ATTR = { | |||
58 | 83 | 58 | 83 |
59 | }; | 59 | }; |
60 | 60 | ||
61 | uint8_t mpeg2_scan_norm[64] IDATA_ATTR = { | 61 | uint8_t default_mpeg2_scan_norm[64] IDATA_ATTR = { |
62 | /* Zig-Zag scan pattern */ | 62 | /* Zig-Zag scan pattern */ |
63 | 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, | 63 | 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, |
64 | 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, | 64 | 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, |
@@ -66,7 +66,7 @@ uint8_t mpeg2_scan_norm[64] IDATA_ATTR = { | |||
66 | 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 | 66 | 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 |
67 | }; | 67 | }; |
68 | 68 | ||
69 | uint8_t mpeg2_scan_alt[64] IDATA_ATTR = { | 69 | uint8_t default_mpeg2_scan_alt[64] IDATA_ATTR = { |
70 | /* Alternate scan pattern */ | 70 | /* Alternate scan pattern */ |
71 | 0, 8, 16, 24, 1, 9, 2, 10, 17, 25, 32, 40, 48, 56, 57, 49, | 71 | 0, 8, 16, 24, 1, 9, 2, 10, 17, 25, 32, 40, 48, 56, 57, 49, |
72 | 41, 33, 26, 18, 3, 11, 4, 12, 19, 27, 34, 42, 50, 58, 35, 43, | 72 | 41, 33, 26, 18, 3, 11, 4, 12, 19, 27, 34, 42, 50, 58, 35, 43, |
@@ -74,6 +74,9 @@ uint8_t mpeg2_scan_alt[64] IDATA_ATTR = { | |||
74 | 53, 61, 22, 30, 7, 15, 23, 31, 38, 46, 54, 62, 39, 47, 55, 63 | 74 | 53, 61, 22, 30, 7, 15, 23, 31, 38, 46, 54, 62, 39, 47, 55, 63 |
75 | }; | 75 | }; |
76 | 76 | ||
77 | uint8_t mpeg2_scan_norm[64] IDATA_ATTR; | ||
78 | uint8_t mpeg2_scan_alt[64] IDATA_ATTR; | ||
79 | |||
77 | void mpeg2_header_state_init (mpeg2dec_t * mpeg2dec) | 80 | void mpeg2_header_state_init (mpeg2dec_t * mpeg2dec) |
78 | { | 81 | { |
79 | if (mpeg2dec->sequence.width != (unsigned)-1) { | 82 | if (mpeg2dec->sequence.width != (unsigned)-1) { |
diff --git a/apps/plugins/mpegplayer/idct.c b/apps/plugins/mpegplayer/idct.c index ee02e72a6f..bf705c6a2f 100644 --- a/apps/plugins/mpegplayer/idct.c +++ b/apps/plugins/mpegplayer/idct.c | |||
@@ -260,6 +260,8 @@ static void mpeg2_idct_add_c (const int last, int16_t * block, | |||
260 | 260 | ||
261 | void mpeg2_idct_init (void) | 261 | void mpeg2_idct_init (void) |
262 | { | 262 | { |
263 | extern uint8_t default_mpeg2_scan_norm[64]; | ||
264 | extern uint8_t default_mpeg2_scan_alt[64]; | ||
263 | extern uint8_t mpeg2_scan_norm[64]; | 265 | extern uint8_t mpeg2_scan_norm[64]; |
264 | extern uint8_t mpeg2_scan_alt[64]; | 266 | extern uint8_t mpeg2_scan_alt[64]; |
265 | int i, j; | 267 | int i, j; |
@@ -274,10 +276,10 @@ void mpeg2_idct_init (void) | |||
274 | 276 | ||
275 | for (i = 0; i < 64; i++) | 277 | for (i = 0; i < 64; i++) |
276 | { | 278 | { |
277 | j = mpeg2_scan_norm[i]; | 279 | j = default_mpeg2_scan_norm[i]; |
278 | mpeg2_scan_norm[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); | 280 | mpeg2_scan_norm[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); |
279 | 281 | ||
280 | j = mpeg2_scan_alt[i]; | 282 | j = default_mpeg2_scan_alt[i]; |
281 | mpeg2_scan_alt[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); | 283 | mpeg2_scan_alt[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); |
282 | } | 284 | } |
283 | } | 285 | } |
diff --git a/apps/plugins/mpegplayer/idct_arm_c.c b/apps/plugins/mpegplayer/idct_arm_c.c index be9971f5c3..9805f421a6 100644 --- a/apps/plugins/mpegplayer/idct_arm_c.c +++ b/apps/plugins/mpegplayer/idct_arm_c.c | |||
@@ -509,6 +509,8 @@ static void mpeg2_idct_add_c (int last, int16_t * block, | |||
509 | 509 | ||
510 | void mpeg2_idct_init (void) | 510 | void mpeg2_idct_init (void) |
511 | { | 511 | { |
512 | extern uint8_t default_mpeg2_scan_norm[64]; | ||
513 | extern uint8_t default_mpeg2_scan_alt[64]; | ||
512 | extern uint8_t mpeg2_scan_norm[64]; | 514 | extern uint8_t mpeg2_scan_norm[64]; |
513 | extern uint8_t mpeg2_scan_alt[64]; | 515 | extern uint8_t mpeg2_scan_alt[64]; |
514 | int i, j; | 516 | int i, j; |
@@ -518,10 +520,10 @@ void mpeg2_idct_init (void) | |||
518 | 520 | ||
519 | for (i = 0; i < 64; i++) | 521 | for (i = 0; i < 64; i++) |
520 | { | 522 | { |
521 | j = mpeg2_scan_norm[i]; | 523 | j = default_mpeg2_scan_norm[i]; |
522 | mpeg2_scan_norm[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); | 524 | mpeg2_scan_norm[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); |
523 | 525 | ||
524 | j = mpeg2_scan_alt[i]; | 526 | j = default_mpeg2_scan_alt[i]; |
525 | mpeg2_scan_alt[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); | 527 | mpeg2_scan_alt[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); |
526 | } | 528 | } |
527 | } | 529 | } |
diff --git a/apps/plugins/mpegplayer/mpeg_settings.c b/apps/plugins/mpegplayer/mpeg_settings.c index 28062f4567..197fa09236 100644 --- a/apps/plugins/mpegplayer/mpeg_settings.c +++ b/apps/plugins/mpegplayer/mpeg_settings.c | |||
@@ -7,20 +7,99 @@ | |||
7 | extern struct plugin_api* rb; | 7 | extern struct plugin_api* rb; |
8 | 8 | ||
9 | struct mpeg_settings settings; | 9 | struct mpeg_settings settings; |
10 | static struct mpeg_settings old_settings; | 10 | |
11 | ssize_t seek_PTS(int in_file, int startTime, int accept_button); | ||
12 | void display_thumb(int in_file); | ||
13 | |||
14 | #ifndef HAVE_LCD_COLOR | ||
15 | void gray_show(bool enable); | ||
16 | #endif | ||
11 | 17 | ||
12 | #define SETTINGS_VERSION 2 | 18 | #define SETTINGS_VERSION 2 |
13 | #define SETTINGS_MIN_VERSION 1 | 19 | #define SETTINGS_MIN_VERSION 1 |
14 | #define SETTINGS_FILENAME "mpegplayer.cfg" | 20 | #define SETTINGS_FILENAME "mpegplayer.cfg" |
15 | 21 | ||
22 | enum slider_state_t {state0, state1, state2, | ||
23 | state3, state4, state5} slider_state; | ||
24 | |||
25 | volatile long thumbDelayTimer; | ||
26 | |||
27 | /* button definitions */ | ||
28 | #if (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ | ||
29 | (CONFIG_KEYPAD == IRIVER_H300_PAD) | ||
30 | #define MPEG_SELECT BUTTON_ON | ||
31 | #define MPEG_RIGHT BUTTON_RIGHT | ||
32 | #define MPEG_LEFT BUTTON_LEFT | ||
33 | #define MPEG_SCROLL_DOWN BUTTON_UP | ||
34 | #define MPEG_SCROLL_UP BUTTON_DOWN | ||
35 | #define MPEG_EXIT BUTTON_OFF | ||
36 | |||
37 | #elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) | ||
38 | #define MPEG_SELECT BUTTON_PLAY | ||
39 | #define MPEG_RIGHT BUTTON_RIGHT | ||
40 | #define MPEG_LEFT BUTTON_LEFT | ||
41 | #define MPEG_SCROLL_DOWN BUTTON_UP | ||
42 | #define MPEG_SCROLL_UP BUTTON_DOWN | ||
43 | #define MPEG_EXIT BUTTON_POWER | ||
44 | |||
45 | #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ | ||
46 | (CONFIG_KEYPAD == IPOD_3G_PAD) || \ | ||
47 | (CONFIG_KEYPAD == IPOD_1G2G_PAD) | ||
48 | #define MPEG_SELECT BUTTON_SELECT | ||
49 | #define MPEG_RIGHT BUTTON_RIGHT | ||
50 | #define MPEG_LEFT BUTTON_LEFT | ||
51 | #define MPEG_SCROLL_DOWN BUTTON_SCROLL_FWD | ||
52 | #define MPEG_SCROLL_UP BUTTON_SCROLL_BACK | ||
53 | #define MPEG_EXIT BUTTON_MENU | ||
54 | |||
55 | #elif CONFIG_KEYPAD == GIGABEAT_PAD | ||
56 | #define MPEG_SELECT BUTTON_SELECT | ||
57 | #define MPEG_LEFT BUTTON_LEFT | ||
58 | #define MPEG_RIGHT BUTTON_RIGHT | ||
59 | #define MPEG_UP BUTTON_UP | ||
60 | #define MPEG_DOWN BUTTON_DOWN | ||
61 | #define MPEG_SCROLL_DOWN BUTTON_VOL_DOWN | ||
62 | #define MPEG_SCROLL_UP BUTTON_VOL_UP | ||
63 | #define MPEG_EXIT BUTTON_POWER | ||
64 | |||
65 | #elif CONFIG_KEYPAD == IRIVER_H10_PAD | ||
66 | #define MPEG_SELECT BUTTON_PLAY | ||
67 | #define MPEG_SCROLL_UP BUTTON_SCROLL_UP | ||
68 | #define MPEG_SCROLL_DOWN BUTTON_SCROLL_DOWN | ||
69 | #define MPEG_LEFT BUTTON_LEFT | ||
70 | #define MPEG_RIGHT BUTTON_RIGHT | ||
71 | #define MPEG_EXIT BUTTON_POWER | ||
72 | |||
73 | #elif (CONFIG_KEYPAD == SANSA_E200_PAD) | ||
74 | #define MPEG_SELECT BUTTON_SELECT | ||
75 | #define MPEG_SCROLL_UP BUTTON_SCROLL_UP | ||
76 | #define MPEG_SCROLL_DOWN BUTTON_SCROLL_DOWN | ||
77 | #define MPEG_LEFT BUTTON_LEFT | ||
78 | #define MPEG_RIGHT BUTTON_RIGHT | ||
79 | #define MPEG_UP BUTTON_UP | ||
80 | #define MPEG_DOWN BUTTON_DOWN | ||
81 | #define MPEG_EXIT BUTTON_POWER | ||
82 | |||
83 | #elif (CONFIG_KEYPAD == SANSA_C200_PAD) | ||
84 | #define MPEG_SELECT BUTTON_SELECT | ||
85 | #define MPEG_SCROLL_UP BUTTON_VOL_UP | ||
86 | #define MPEG_SCROLL_DOWN BUTTON_VOL_DOWN | ||
87 | #define MPEG_LEFT BUTTON_LEFT | ||
88 | #define MPEG_RIGHT BUTTON_RIGHT | ||
89 | #define MPEG_UP BUTTON_UP | ||
90 | #define MPEG_DOWN BUTTON_DOWN | ||
91 | #define MPEG_EXIT BUTTON_POWER | ||
92 | |||
93 | #else | ||
94 | #error MPEGPLAYER: Unsupported keypad | ||
95 | #endif | ||
96 | |||
16 | static struct configdata config[] = | 97 | static struct configdata config[] = |
17 | { | 98 | { |
18 | {TYPE_ENUM, 0, 2, &settings.showfps, "Show FPS", | 99 | {TYPE_INT, 0, 2, &settings.showfps, "Show FPS", NULL, NULL}, |
19 | (char *[]){ "No", "Yes" }, NULL}, | 100 | {TYPE_INT, 0, 2, &settings.limitfps, "Limit FPS", NULL, NULL}, |
20 | {TYPE_ENUM, 0, 2, &settings.limitfps, "Limit FPS", | 101 | {TYPE_INT, 0, 2, &settings.skipframes, "Skip frames", NULL, NULL}, |
21 | (char *[]){ "No", "Yes" }, NULL}, | 102 | |
22 | {TYPE_ENUM, 0, 2, &settings.skipframes, "Skip frames", | ||
23 | (char *[]){ "No", "Yes" }, NULL}, | ||
24 | #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) | 103 | #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) |
25 | {TYPE_INT, 0, INT_MAX, &settings.displayoptions, "Display options", | 104 | {TYPE_INT, 0, INT_MAX, &settings.displayoptions, "Display options", |
26 | NULL, NULL}, | 105 | NULL, NULL}, |
@@ -36,6 +115,7 @@ enum mpeg_menu_ids | |||
36 | MPEG_OPTION_DISPLAY_FPS, | 115 | MPEG_OPTION_DISPLAY_FPS, |
37 | MPEG_OPTION_LIMIT_FPS, | 116 | MPEG_OPTION_LIMIT_FPS, |
38 | MPEG_OPTION_SKIP_FRAMES, | 117 | MPEG_OPTION_SKIP_FRAMES, |
118 | MPEG_OPTION_CLEAR_RESUMES, | ||
39 | MPEG_OPTION_QUIT, | 119 | MPEG_OPTION_QUIT, |
40 | }; | 120 | }; |
41 | 121 | ||
@@ -68,13 +148,250 @@ static void display_options(void) | |||
68 | } | 148 | } |
69 | #endif /* #ifdef TOSHIBA_GIGABEAT_F */ | 149 | #endif /* #ifdef TOSHIBA_GIGABEAT_F */ |
70 | 150 | ||
151 | void draw_slider(int slider_ypos, int max_val, int current_val) | ||
152 | { | ||
153 | int slider_margin = LCD_WIDTH*12/100; /* 12% */ | ||
154 | int slider_width = LCD_WIDTH-(slider_margin*2); | ||
155 | char resume_str[32]; | ||
156 | |||
157 | /* max_val and current_val are in half minutes | ||
158 | determine value .0 or .5 to display */ | ||
159 | int max_hol = max_val/2; | ||
160 | int max_rem = (max_val-(max_hol*2))*5; | ||
161 | int current_hol = current_val/2; | ||
162 | int current_rem = (current_val-(current_hol*2))*5; | ||
163 | |||
164 | rb->snprintf(resume_str, sizeof(resume_str), "0.0"); | ||
165 | rb->lcd_putsxy(slider_margin, slider_ypos, resume_str); | ||
166 | |||
167 | rb->snprintf(resume_str, sizeof(resume_str), "%u.%u", max_hol, max_rem); | ||
168 | rb->lcd_putsxy(LCD_WIDTH-slider_margin-25, slider_ypos, resume_str); | ||
169 | |||
170 | rb->lcd_drawrect(slider_margin, slider_ypos+17, slider_width, 8); | ||
171 | rb->lcd_fillrect(slider_margin, slider_ypos+17, | ||
172 | current_val*slider_width/max_val, 8); | ||
173 | |||
174 | rb->snprintf(resume_str, sizeof(resume_str), "%u.%u", current_hol, | ||
175 | current_rem); | ||
176 | rb->lcd_putsxy(slider_margin+(current_val*slider_width/max_val)-16, | ||
177 | slider_ypos+29, resume_str); | ||
178 | |||
179 | rb->lcd_update_rect(0, slider_ypos, LCD_WIDTH, LCD_HEIGHT-slider_ypos); | ||
180 | } | ||
181 | |||
182 | int get_start_time(int play_time, int in_file) | ||
183 | { | ||
184 | int quit = 0; | ||
185 | int button = 0; | ||
186 | int resume_time = settings.resume_time; | ||
187 | int slider_ypos = LCD_HEIGHT-45; | ||
188 | int seek_rtn; | ||
189 | |||
190 | slider_state = state0; | ||
191 | thumbDelayTimer = *(rb->current_tick); | ||
192 | rb->lcd_clear_display(); | ||
193 | rb->lcd_update(); | ||
194 | |||
195 | while(quit == 0) | ||
196 | { | ||
197 | button = rb->button_get(false); | ||
198 | switch (button) | ||
199 | { | ||
200 | #if (CONFIG_KEYPAD == GIGABEAT_PAD) || \ | ||
201 | (CONFIG_KEYPAD == SANSA_E200_PAD) || \ | ||
202 | (CONFIG_KEYPAD == SANSA_C200_PAD) | ||
203 | case MPEG_DOWN: | ||
204 | case MPEG_DOWN | BUTTON_REPEAT: | ||
205 | if ((resume_time -= 20) < 0) | ||
206 | resume_time = 0; | ||
207 | slider_state = state0; | ||
208 | thumbDelayTimer = *(rb->current_tick); | ||
209 | break; | ||
210 | case MPEG_UP: | ||
211 | case MPEG_UP | BUTTON_REPEAT: | ||
212 | if ((resume_time += 20) > play_time) | ||
213 | resume_time = play_time; | ||
214 | slider_state = state0; | ||
215 | thumbDelayTimer = *(rb->current_tick); | ||
216 | break; | ||
217 | #endif | ||
218 | case MPEG_LEFT: | ||
219 | case MPEG_LEFT | BUTTON_REPEAT: | ||
220 | case MPEG_SCROLL_UP: | ||
221 | case MPEG_SCROLL_UP | BUTTON_REPEAT: | ||
222 | if (--resume_time < 0) | ||
223 | resume_time = 0; | ||
224 | slider_state = state0; | ||
225 | thumbDelayTimer = *(rb->current_tick); | ||
226 | break; | ||
227 | case MPEG_RIGHT: | ||
228 | case MPEG_RIGHT | BUTTON_REPEAT: | ||
229 | case MPEG_SCROLL_DOWN: | ||
230 | case MPEG_SCROLL_DOWN | BUTTON_REPEAT: | ||
231 | if (++resume_time > play_time) | ||
232 | resume_time = play_time; | ||
233 | slider_state = state0; | ||
234 | thumbDelayTimer = *(rb->current_tick); | ||
235 | break; | ||
236 | case MPEG_SELECT: | ||
237 | quit = 1; | ||
238 | break; | ||
239 | case MPEG_EXIT: | ||
240 | resume_time = -1; | ||
241 | quit = 1; | ||
242 | break; | ||
243 | default: | ||
244 | if (rb->default_event_handler(button) == SYS_USB_CONNECTED) | ||
245 | { | ||
246 | resume_time = -1; | ||
247 | quit = 1; | ||
248 | } | ||
249 | break; | ||
250 | } | ||
251 | |||
252 | rb->yield(); | ||
253 | |||
254 | switch(slider_state) | ||
255 | { | ||
256 | case state0: | ||
257 | rb->lcd_clear_display(); | ||
258 | rb->lcd_update(); | ||
259 | #ifdef HAVE_LCD_COLOR | ||
260 | if (resume_time > 0) | ||
261 | rb->splash(0, "loading ..."); | ||
262 | #endif | ||
263 | slider_state = state1; | ||
264 | break; | ||
265 | case state1: | ||
266 | if (*(rb->current_tick) - thumbDelayTimer > 75) | ||
267 | slider_state = state2; | ||
268 | if (resume_time == 0) | ||
269 | { | ||
270 | seek_rtn = 0; | ||
271 | slider_state = state5; | ||
272 | } | ||
273 | draw_slider(slider_ypos, play_time, resume_time); | ||
274 | break; | ||
275 | case state2: | ||
276 | if ( (seek_rtn = seek_PTS(in_file, resume_time, 1)) >= 0) | ||
277 | slider_state = state3; | ||
278 | else if (seek_rtn == -101) | ||
279 | { | ||
280 | slider_state = state0; | ||
281 | thumbDelayTimer = *(rb->current_tick); | ||
282 | } | ||
283 | else | ||
284 | slider_state = state4; | ||
285 | break; | ||
286 | case state3: | ||
287 | display_thumb(in_file); | ||
288 | draw_slider(slider_ypos, play_time, resume_time); | ||
289 | slider_state = state4; | ||
290 | break; | ||
291 | case state4: | ||
292 | draw_slider(slider_ypos, play_time, resume_time); | ||
293 | slider_state = state5; | ||
294 | break; | ||
295 | case state5: | ||
296 | break; | ||
297 | } | ||
298 | } | ||
299 | |||
300 | return resume_time; | ||
301 | } | ||
302 | |||
303 | int mpeg_start_menu(int play_time, int in_file) | ||
304 | { | ||
305 | int m; | ||
306 | int result = 0; | ||
307 | int menu_quit = 0; | ||
308 | |||
309 | /* add the resume time to the menu display */ | ||
310 | char resume_str[32]; | ||
311 | int time_hol = (int)(settings.resume_time/2); | ||
312 | int time_rem = ((settings.resume_time%2)==0) ? 0 : 5; | ||
313 | rb->snprintf(resume_str, sizeof(resume_str), | ||
314 | "Resume time (min): %d.%d", time_hol, time_rem); | ||
315 | |||
316 | struct menu_item items[] = { | ||
317 | { "Play from beginning", NULL }, | ||
318 | { resume_str, NULL }, | ||
319 | { "Set start time (min)", NULL }, | ||
320 | { "Quit mpegplayer", NULL }, | ||
321 | }; | ||
322 | |||
323 | m = menu_init(rb, items, sizeof(items) / sizeof(*items), | ||
324 | NULL, NULL, NULL, NULL); | ||
325 | |||
326 | rb->button_clear_queue(); | ||
327 | |||
328 | while(menu_quit == 0) | ||
329 | { | ||
330 | result = menu_show(m); | ||
331 | |||
332 | switch (result) | ||
333 | { | ||
334 | case 0: | ||
335 | menu_quit = 1; | ||
336 | result = 0; | ||
337 | break; | ||
338 | case 1: | ||
339 | menu_quit = 1; | ||
340 | result = settings.resume_time; | ||
341 | break; | ||
342 | case 2: | ||
343 | #ifndef HAVE_LCD_COLOR | ||
344 | gray_show(true); | ||
345 | #endif | ||
346 | if ((result = get_start_time(play_time, in_file)) >= 0) | ||
347 | menu_quit = 1; | ||
348 | #ifndef HAVE_LCD_COLOR | ||
349 | gray_show(false); | ||
350 | #endif | ||
351 | break; | ||
352 | case 3: | ||
353 | menu_quit = 1; | ||
354 | result = -1; | ||
355 | break; | ||
356 | default: | ||
357 | if (result == MENU_ATTACHED_USB) | ||
358 | { | ||
359 | menu_quit = 1; | ||
360 | result = -1; | ||
361 | } | ||
362 | break; | ||
363 | } | ||
364 | } | ||
365 | menu_exit(m); | ||
366 | |||
367 | settings.resume_time = result; | ||
368 | return (int)result; | ||
369 | } | ||
370 | |||
371 | void clear_resume_count(void) | ||
372 | { | ||
373 | configfile_save(SETTINGS_FILENAME, config, | ||
374 | sizeof(config)/sizeof(*config), | ||
375 | SETTINGS_VERSION); | ||
376 | |||
377 | settings.resume_count = 0; | ||
378 | |||
379 | /* add this place holder so the count is above resume entries */ | ||
380 | configfile_update_entry(SETTINGS_FILENAME, "Resume count", 0); | ||
381 | } | ||
382 | |||
71 | bool mpeg_menu(void) | 383 | bool mpeg_menu(void) |
72 | { | 384 | { |
73 | int m; | 385 | int m; |
74 | int result; | 386 | int result; |
75 | int menu_quit=0; | 387 | int menu_quit=0; |
76 | 388 | ||
77 | static const struct menu_item items[] = { | 389 | /* add the clear resume option to the menu display */ |
390 | char clear_str[32]; | ||
391 | rb->snprintf(clear_str, sizeof(clear_str), | ||
392 | "Clear all resumes: %u", settings.resume_count); | ||
393 | |||
394 | struct menu_item items[] = { | ||
78 | #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) | 395 | #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) |
79 | [MPEG_OPTION_DISPLAY_SETTINGS] = | 396 | [MPEG_OPTION_DISPLAY_SETTINGS] = |
80 | { "Display Options", NULL }, | 397 | { "Display Options", NULL }, |
@@ -85,6 +402,8 @@ bool mpeg_menu(void) | |||
85 | { "Limit FPS", NULL }, | 402 | { "Limit FPS", NULL }, |
86 | [MPEG_OPTION_SKIP_FRAMES] = | 403 | [MPEG_OPTION_SKIP_FRAMES] = |
87 | { "Skip frames", NULL }, | 404 | { "Skip frames", NULL }, |
405 | [MPEG_OPTION_CLEAR_RESUMES] = | ||
406 | { clear_str, NULL }, | ||
88 | [MPEG_OPTION_QUIT] = | 407 | [MPEG_OPTION_QUIT] = |
89 | { "Quit mpegplayer", NULL }, | 408 | { "Quit mpegplayer", NULL }, |
90 | }; | 409 | }; |
@@ -115,6 +434,11 @@ bool mpeg_menu(void) | |||
115 | rb->set_option("Skip frames",&settings.skipframes,INT, | 434 | rb->set_option("Skip frames",&settings.skipframes,INT, |
116 | noyes, 2, NULL); | 435 | noyes, 2, NULL); |
117 | break; | 436 | break; |
437 | case MPEG_OPTION_CLEAR_RESUMES: | ||
438 | clear_resume_count(); | ||
439 | rb->snprintf(clear_str, sizeof(clear_str), | ||
440 | "Clear all resumes: %u", 0); | ||
441 | break; | ||
118 | case MPEG_OPTION_QUIT: | 442 | case MPEG_OPTION_QUIT: |
119 | default: | 443 | default: |
120 | menu_quit=1; | 444 | menu_quit=1; |
@@ -132,48 +456,82 @@ bool mpeg_menu(void) | |||
132 | return (result==MPEG_OPTION_QUIT); | 456 | return (result==MPEG_OPTION_QUIT); |
133 | } | 457 | } |
134 | 458 | ||
135 | 459 | void init_settings(const char* filename) | |
136 | void init_settings(void) | ||
137 | { | 460 | { |
138 | /* Set the default settings */ | 461 | /* Set the default settings */ |
139 | settings.showfps = 0; /* Do not show FPS */ | 462 | settings.showfps = 0; /* Do not show FPS */ |
140 | settings.limitfps = 1; /* Limit FPS */ | 463 | settings.limitfps = 1; /* Limit FPS */ |
141 | settings.skipframes = 1; /* Skip frames */ | 464 | settings.skipframes = 1; /* Skip frames */ |
465 | settings.resume_count = -1; | ||
142 | #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) | 466 | #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) |
143 | settings.displayoptions = 0; /* No visual effects */ | 467 | settings.displayoptions = 0; /* No visual effects */ |
144 | #endif | 468 | #endif |
145 | 469 | ||
146 | configfile_init(rb); | 470 | configfile_init(rb); |
147 | 471 | ||
148 | if (configfile_load(SETTINGS_FILENAME, config, | 472 | /* If the config file don't contain resume count |
149 | sizeof(config)/sizeof(*config), | 473 | or the load fails, then rebuild the config file. |
150 | SETTINGS_MIN_VERSION | 474 | This eliminates the worry for older config files |
151 | ) < 0) | 475 | having unused data. */ |
476 | if (((settings.resume_count = configfile_get_value | ||
477 | (SETTINGS_FILENAME, "Resume count")) < 0) || | ||
478 | (configfile_load(SETTINGS_FILENAME, config, | ||
479 | sizeof(config)/sizeof(*config), | ||
480 | SETTINGS_MIN_VERSION) < 0)) | ||
152 | { | 481 | { |
153 | /* If the loading failed, save a new config file (as the disk is | 482 | /* Generate a new config file with default values */ |
154 | already spinning) */ | ||
155 | configfile_save(SETTINGS_FILENAME, config, | 483 | configfile_save(SETTINGS_FILENAME, config, |
156 | sizeof(config)/sizeof(*config), | 484 | sizeof(config)/sizeof(*config), |
157 | SETTINGS_VERSION); | 485 | SETTINGS_VERSION); |
158 | } | 486 | } |
159 | 487 | ||
160 | /* Keep a copy of the saved version of the settings - so we can check if | ||
161 | the settings have changed when we quit */ | ||
162 | old_settings = settings; | ||
163 | #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) | 488 | #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) |
489 | if ((settings.displayoptions = | ||
490 | configfile_get_value(SETTINGS_FILENAME, "Display options")) < 0) | ||
491 | { | ||
492 | configfile_update_entry(SETTINGS_FILENAME, "Display options", | ||
493 | (settings.displayoptions=0)); | ||
494 | } | ||
164 | rb->lcd_yuv_set_options(settings.displayoptions); | 495 | rb->lcd_yuv_set_options(settings.displayoptions); |
165 | #endif | 496 | #endif |
497 | |||
498 | if (settings.resume_count < 0) | ||
499 | { | ||
500 | settings.resume_count = 0; | ||
501 | |||
502 | /* add this place holder so the count is above resume entries */ | ||
503 | configfile_update_entry(SETTINGS_FILENAME, "Resume count", 0); | ||
504 | } | ||
505 | |||
506 | rb->strcpy(settings.resume_filename, filename); | ||
507 | |||
508 | /* get the resume time for the current mpeg if it exist */ | ||
509 | if ((settings.resume_time = configfile_get_value | ||
510 | (SETTINGS_FILENAME, filename)) < 0) | ||
511 | { | ||
512 | settings.resume_time = 0; | ||
513 | } | ||
166 | } | 514 | } |
167 | 515 | ||
168 | void save_settings(void) | 516 | void save_settings(void) |
169 | { | 517 | { |
170 | /* Save the user settings if they have changed */ | 518 | configfile_update_entry(SETTINGS_FILENAME, "Show FPS", |
171 | if (rb->memcmp(&settings,&old_settings,sizeof(settings))!=0) { | 519 | settings.showfps); |
172 | configfile_save(SETTINGS_FILENAME, config, | 520 | configfile_update_entry(SETTINGS_FILENAME, "Limit FPS", |
173 | sizeof(config)/sizeof(*config), | 521 | settings.limitfps); |
174 | SETTINGS_VERSION); | 522 | configfile_update_entry(SETTINGS_FILENAME, "Skip frames", |
523 | settings.skipframes); | ||
175 | 524 | ||
176 | /* Store the settings in old_settings - to check for future changes */ | 525 | /* If this was a new resume entry then update the total resume count */ |
177 | old_settings = settings; | 526 | if (configfile_update_entry(SETTINGS_FILENAME, settings.resume_filename, |
527 | settings.resume_time) == 0) | ||
528 | { | ||
529 | configfile_update_entry(SETTINGS_FILENAME, "Resume count", | ||
530 | ++settings.resume_count); | ||
178 | } | 531 | } |
532 | |||
533 | #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) | ||
534 | configfile_update_entry(SETTINGS_FILENAME, "Display options", | ||
535 | settings.displayoptions); | ||
536 | #endif | ||
179 | } | 537 | } |
diff --git a/apps/plugins/mpegplayer/mpeg_settings.h b/apps/plugins/mpegplayer/mpeg_settings.h index 7721c46f64..690667f632 100644 --- a/apps/plugins/mpegplayer/mpeg_settings.h +++ b/apps/plugins/mpegplayer/mpeg_settings.h | |||
@@ -2,16 +2,23 @@ | |||
2 | #include "plugin.h" | 2 | #include "plugin.h" |
3 | 3 | ||
4 | struct mpeg_settings { | 4 | struct mpeg_settings { |
5 | int showfps; | 5 | int showfps; /* flag to display fps */ |
6 | int limitfps; | 6 | int limitfps; /* flag to limit fps */ |
7 | int skipframes; | 7 | int skipframes; /* flag to skip frames */ |
8 | int resume_count; /* total # of resumes in config file */ | ||
9 | int resume_time; /* resume time for current mpeg (in half minutes) */ | ||
10 | char resume_filename[128]; /* filename of current mpeg */ | ||
11 | |||
8 | #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) | 12 | #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) |
9 | unsigned displayoptions; | 13 | int displayoptions; |
10 | #endif | 14 | #endif |
11 | }; | 15 | }; |
12 | 16 | ||
13 | extern struct mpeg_settings settings; | 17 | extern struct mpeg_settings settings; |
14 | 18 | ||
19 | int get_start_time(int play_time, int in_file); | ||
20 | int mpeg_start_menu(int play_time, int in_file); | ||
15 | bool mpeg_menu(void); | 21 | bool mpeg_menu(void); |
16 | void init_settings(void); | 22 | void init_settings(const char* filename); |
17 | void save_settings(void); | 23 | void save_settings(void); |
24 | void clear_resume_count(void); | ||
diff --git a/apps/plugins/mpegplayer/mpegplayer.c b/apps/plugins/mpegplayer/mpegplayer.c index 54fdf05355..128eb268a6 100644 --- a/apps/plugins/mpegplayer/mpegplayer.c +++ b/apps/plugins/mpegplayer/mpegplayer.c | |||
@@ -110,6 +110,7 @@ FPS | 27Mhz | 100Hz | 44.1KHz | 48KHz | |||
110 | #include "mpeg_settings.h" | 110 | #include "mpeg_settings.h" |
111 | #include "video_out.h" | 111 | #include "video_out.h" |
112 | #include "../../codecs/libmad/mad.h" | 112 | #include "../../codecs/libmad/mad.h" |
113 | #include "splash.h" | ||
113 | 114 | ||
114 | PLUGIN_HEADER | 115 | PLUGIN_HEADER |
115 | PLUGIN_IRAM_DECLARE | 116 | PLUGIN_IRAM_DECLARE |
@@ -198,11 +199,8 @@ typedef struct | |||
198 | uint8_t* curr_packet_end; /* Current stream packet end */ | 199 | uint8_t* curr_packet_end; /* Current stream packet end */ |
199 | 200 | ||
200 | uint8_t* prev_packet; /* Previous stream packet beginning */ | 201 | uint8_t* prev_packet; /* Previous stream packet beginning */ |
201 | uint8_t* next_packet; /* Next stream packet beginning */ | 202 | size_t prev_packet_length; /* Lenth of previous packet */ |
202 | 203 | size_t buffer_remaining; /* How much data is left in the buffer */ | |
203 | size_t guard_bytes; /* Number of bytes in guardbuf used */ | ||
204 | uint64_t buffer_tail; /* Accumulation of bytes added */ | ||
205 | uint64_t buffer_head; /* Accumulation of bytes removed */ | ||
206 | uint32_t curr_pts; /* Current presentation timestamp */ | 204 | uint32_t curr_pts; /* Current presentation timestamp */ |
207 | uint32_t curr_time; /* Current time in samples */ | 205 | uint32_t curr_time; /* Current time in samples */ |
208 | uint32_t tagged; /* curr_pts is valid */ | 206 | uint32_t tagged; /* curr_pts is valid */ |
@@ -301,8 +299,7 @@ static intptr_t str_send_msg(Stream *str, int id, intptr_t data) | |||
301 | return str->dispatch_fn(str, msg); | 299 | return str->dispatch_fn(str, msg); |
302 | #endif | 300 | #endif |
303 | 301 | ||
304 | /* Only one thread at a time, please - only one core may safely send | 302 | /* Only one thread at a time, please */ |
305 | right now */ | ||
306 | rb->spinlock_lock(&str->msg_lock); | 303 | rb->spinlock_lock(&str->msg_lock); |
307 | 304 | ||
308 | str->ev.id = id; | 305 | str->ev.id = id; |
@@ -333,13 +330,62 @@ static intptr_t str_send_msg(Stream *str, int id, intptr_t data) | |||
333 | /* NOTE: Putting the following variables in IRAM cause audio corruption | 330 | /* NOTE: Putting the following variables in IRAM cause audio corruption |
334 | on the ipod (reason unknown) | 331 | on the ipod (reason unknown) |
335 | */ | 332 | */ |
336 | static uint8_t *disk_buf IBSS_ATTR; | 333 | static uint8_t *disk_buf_start IBSS_ATTR; /* Start pointer */ |
337 | static uint8_t *disk_buf_end IBSS_ATTR; | 334 | static uint8_t *disk_buf_end IBSS_ATTR; /* End of buffer pointer less |
338 | static uint8_t *disk_buf_tail IBSS_ATTR; | 335 | MPEG_GUARDBUF_SIZE. The |
339 | static size_t buffer_size IBSS_ATTR; | 336 | guard space is used to wrap |
337 | data at the buffer start to | ||
338 | pass continuous data | ||
339 | packets */ | ||
340 | static uint8_t *disk_buf_tail IBSS_ATTR; /* Location of last data + 1 | ||
341 | filled into the buffer */ | ||
342 | static size_t disk_buf_size IBSS_ATTR; /* The total buffer length | ||
343 | including the guard | ||
344 | space */ | ||
345 | static size_t file_remaining IBSS_ATTR; | ||
346 | |||
347 | #if NUM_CORES > 1 | ||
348 | /* Some stream variables are shared between cores */ | ||
349 | struct mutex stream_lock IBSS_ATTR; | ||
350 | static inline void init_stream_lock(void) | ||
351 | { rb->spinlock_init(&stream_lock); } | ||
352 | static inline void lock_stream(void) | ||
353 | { rb->spinlock_lock(&stream_lock); } | ||
354 | static inline void unlock_stream(void) | ||
355 | { rb->spinlock_unlock(&stream_lock); } | ||
356 | #else | ||
357 | /* No RMW issue here */ | ||
358 | static inline void init_stream_lock(void) | ||
359 | { } | ||
360 | static inline void lock_stream(void) | ||
361 | { } | ||
362 | static inline void unlock_stream(void) | ||
363 | { } | ||
364 | #endif | ||
365 | |||
366 | static int audio_sync_start IBSS_ATTR; /* If 0, the audio thread | ||
367 | yields waiting on the video | ||
368 | thread to synchronize with | ||
369 | the stream */ | ||
370 | static uint32_t audio_sync_time IBSS_ATTR; /* The time that the video | ||
371 | thread has reached after | ||
372 | synchronizing. The | ||
373 | audio thread now needs | ||
374 | to advance to this | ||
375 | time */ | ||
376 | static int video_sync_start IBSS_ATTR; /* While 0, the video thread | ||
377 | yields until the audio | ||
378 | thread has reached the | ||
379 | audio_sync_time */ | ||
380 | static int video_thumb_print IBSS_ATTR; /* If 1, the video thread is | ||
381 | only decoding one frame for | ||
382 | use in the menu. If 0, | ||
383 | normal operation */ | ||
384 | static int play_time IBSS_ATTR; /* The movie time as represented by | ||
385 | the maximum audio PTS tag in the | ||
386 | stream converted to half minutes */ | ||
387 | char *filename; /* hack for resume time storage */ | ||
340 | 388 | ||
341 | #define MSG_BUFFER_NEARLY_EMPTY 1 | ||
342 | #define MSG_EXIT_REQUESTED 2 | ||
343 | 389 | ||
344 | /* Various buffers */ | 390 | /* Various buffers */ |
345 | /* TODO: Can we reduce the PCM buffer size? */ | 391 | /* TODO: Can we reduce the PCM buffer size? */ |
@@ -350,7 +396,7 @@ static size_t buffer_size IBSS_ATTR; | |||
350 | #define LIBMPEG2BUFFER_SIZE (2*1024*1024) | 396 | #define LIBMPEG2BUFFER_SIZE (2*1024*1024) |
351 | 397 | ||
352 | /* 65536+6 is required since each PES has a 6 byte header with a 16 bit packet length field */ | 398 | /* 65536+6 is required since each PES has a 6 byte header with a 16 bit packet length field */ |
353 | #define MPEG_GUARDBUF_SIZE (64*1024+1024) /* Keep a bit extra - excessive for now */ | 399 | #define MPEG_GUARDBUF_SIZE (65*1024) /* Keep a bit extra - excessive for now */ |
354 | #define MPEG_LOW_WATERMARK (1024*1024) | 400 | #define MPEG_LOW_WATERMARK (1024*1024) |
355 | 401 | ||
356 | static void pcm_playback_play_pause(bool play); | 402 | static void pcm_playback_play_pause(bool play); |
@@ -471,8 +517,47 @@ static void init_mad(void* mad_frame_overlap) | |||
471 | ((p)[b3] << 6) | \ | 517 | ((p)[b3] << 6) | \ |
472 | ((p)[b4] >> 2 ))) | 518 | ((p)[b4] >> 2 ))) |
473 | 519 | ||
474 | /* This function demuxes the streams and gives the next stream data pointer */ | 520 | /* This function synchronizes the mpeg stream. The function returns |
475 | static void get_next_data( Stream* str ) | 521 | true on error */ |
522 | bool sync_data_stream(uint8_t **p) | ||
523 | { | ||
524 | for (;;) | ||
525 | { | ||
526 | while ( !CMP_4_CONST(*p, PACK_START_CODE) && (*p) < disk_buf_tail ) | ||
527 | (*p)++; | ||
528 | if ( (*p) >= disk_buf_tail ) | ||
529 | break; | ||
530 | uint8_t *p_save = (*p); | ||
531 | if ( ((*p)[4] & 0xc0) == 0x40 ) /* mpeg-2 */ | ||
532 | (*p) += 14 + ((*p)[13] & 7); | ||
533 | else if ( ((*p)[4] & 0xf0) == 0x20 ) /* mpeg-1 */ | ||
534 | (*p) += 12; | ||
535 | else | ||
536 | (*p) += 5; | ||
537 | if ( (*p) >= disk_buf_tail ) | ||
538 | break; | ||
539 | if ( CMP_3_CONST(*p, PACKET_START_CODE_PREFIX) ) | ||
540 | { | ||
541 | (*p) = p_save; | ||
542 | break; | ||
543 | } | ||
544 | else | ||
545 | (*p) = p_save+1; | ||
546 | } | ||
547 | |||
548 | if ( (*p) >= disk_buf_tail ) | ||
549 | return true; | ||
550 | else | ||
551 | return false; | ||
552 | } | ||
553 | |||
554 | /* This function demuxes the streams and gives the next stream data | ||
555 | pointer. Type 0 is normal operation. Type 1 and 2 have been added | ||
556 | for rapid seeks into the data stream. Type 1 and 2 ignore the | ||
557 | video_sync_start state (a signal to yield for refilling the | ||
558 | buffer). Type 1 will append more data to the buffer tail (minumal | ||
559 | bufer size reads that are increased only as needed). */ | ||
560 | static int get_next_data( Stream* str, uint8_t type ) | ||
476 | { | 561 | { |
477 | uint8_t *p; | 562 | uint8_t *p; |
478 | uint8_t *header; | 563 | uint8_t *header; |
@@ -481,30 +566,49 @@ static void get_next_data( Stream* str ) | |||
481 | static int mpeg1_skip_table[16] = | 566 | static int mpeg1_skip_table[16] = |
482 | { 0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; | 567 | { 0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
483 | 568 | ||
484 | if (str->curr_packet_end == NULL) | 569 | if ( (p=str->curr_packet_end) == NULL) |
485 | { | 570 | p = disk_buf_start; |
486 | /* What does this do? */ | ||
487 | while ((p = disk_buf) == NULL) | ||
488 | { | ||
489 | rb->lcd_putsxy(0,LCD_HEIGHT-10,"FREEZE!"); | ||
490 | rb->lcd_update(); | ||
491 | rb->sleep(HZ); | ||
492 | } | ||
493 | } | ||
494 | else | ||
495 | { | ||
496 | p = str->curr_packet_end; | ||
497 | } | ||
498 | 571 | ||
499 | while (1) | 572 | while (1) |
500 | { | 573 | { |
501 | int length, bytes; | 574 | int length, bytes; |
502 | 575 | ||
503 | if (p >= disk_buf_end) | 576 | /* Yield for buffer filling */ |
577 | if ( (type == 0) && (str->buffer_remaining < 120*1024) && (file_remaining > 0) ) | ||
578 | while ( (str->buffer_remaining < 512*1024) && (file_remaining > 0) ) | ||
579 | rb->yield(); | ||
580 | |||
581 | /* The packet start position (plus an arbitrary header length) | ||
582 | has exceeded the amount of data in the buffer */ | ||
583 | if ( type == 1 && (p+50) >= disk_buf_tail ) | ||
504 | { | 584 | { |
505 | p = disk_buf + (p - disk_buf_end); | 585 | DEBUGF("disk buffer overflow\n"); |
586 | return 1; | ||
506 | } | 587 | } |
507 | 588 | ||
589 | /* are we at the end of file? */ | ||
590 | { | ||
591 | size_t tmp_length; | ||
592 | if (p < str->prev_packet) | ||
593 | tmp_length = (disk_buf_end - str->prev_packet) + | ||
594 | (p - disk_buf_start); | ||
595 | else | ||
596 | tmp_length = (p - str->prev_packet); | ||
597 | if (0 == str->buffer_remaining-tmp_length-str->prev_packet_length) | ||
598 | { | ||
599 | str->curr_packet_end = str->curr_packet = NULL; | ||
600 | break; | ||
601 | } | ||
602 | } | ||
603 | |||
604 | /* wrap the disk buffer */ | ||
605 | if (p >= disk_buf_end) | ||
606 | p = disk_buf_start + (p - disk_buf_end); | ||
607 | |||
608 | /* wrap packet header if needed */ | ||
609 | if ( (p+50) >= disk_buf_end ) | ||
610 | rb->memcpy(disk_buf_end, disk_buf_start, 50); | ||
611 | |||
508 | /* Pack header, skip it */ | 612 | /* Pack header, skip it */ |
509 | if (CMP_4_CONST(p, PACK_START_CODE)) | 613 | if (CMP_4_CONST(p, PACK_START_CODE)) |
510 | { | 614 | { |
@@ -521,7 +625,6 @@ static void get_next_data( Stream* str ) | |||
521 | rb->splash( 30, "Weird Pack header!" ); | 625 | rb->splash( 30, "Weird Pack header!" ); |
522 | p += 5; | 626 | p += 5; |
523 | } | 627 | } |
524 | /*rb->splash( 30, "Pack header" );*/ | ||
525 | } | 628 | } |
526 | 629 | ||
527 | /* System header, parse and skip it - four bytes */ | 630 | /* System header, parse and skip it - four bytes */ |
@@ -535,29 +638,29 @@ static void get_next_data( Stream* str ) | |||
535 | 638 | ||
536 | p += header_length; | 639 | p += header_length; |
537 | 640 | ||
538 | if (p >= disk_buf_end) | 641 | if ( p >= disk_buf_end ) |
539 | { | 642 | p = disk_buf_start + (p - disk_buf_end); |
540 | p = disk_buf + (p - disk_buf_end); | ||
541 | } | ||
542 | /*rb->splash( 30, "System header" );*/ | ||
543 | } | 643 | } |
544 | 644 | ||
545 | /* Packet header, parse it */ | 645 | /* Packet header, parse it */ |
546 | if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX)) | 646 | if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX)) |
547 | { | 647 | { |
548 | /* Problem */ | 648 | /* Problem */ |
549 | //rb->splash( HZ*3, "missing packet start code prefix : %X%X at %X", *p, *(p+2), p-disk_buf ); | 649 | rb->splash( HZ*3, "missing packet start code prefix : %X%X at %lX", |
650 | *p, *(p+2), (long unsigned int)(p-disk_buf_start) ); | ||
651 | |||
652 | DEBUGF("end diff: %X,%X,%X,%X,%X,%X\n",(int)str->curr_packet_end, | ||
653 | (int)audio_str.curr_packet_end,(int)video_str.curr_packet_end, | ||
654 | (int)disk_buf_start,(int)disk_buf_end,(int)disk_buf_tail); | ||
655 | |||
550 | str->curr_packet_end = str->curr_packet = NULL; | 656 | str->curr_packet_end = str->curr_packet = NULL; |
551 | break; | 657 | break; |
552 | //++p; | ||
553 | //break; | ||
554 | } | 658 | } |
555 | 659 | ||
556 | /* We retrieve basic infos */ | 660 | /* We retrieve basic infos */ |
557 | stream = p[3]; | 661 | stream = p[3]; |
558 | length = (p[4] << 8) | p[5]; | 662 | length = (p[4] << 8) | p[5]; |
559 | 663 | ||
560 | /*rb->splash( 100, "Stream : %X", stream );*/ | ||
561 | if (stream != str->id) | 664 | if (stream != str->id) |
562 | { | 665 | { |
563 | /* End of stream ? */ | 666 | /* End of stream ? */ |
@@ -618,11 +721,9 @@ static void get_next_data( Stream* str ) | |||
618 | break; | 721 | break; |
619 | } | 722 | } |
620 | } | 723 | } |
621 | 724 | ||
622 | if ((header[length - 1] & 0xc0) == 0x40) | 725 | if ( (header[length - 1] & 0xc0) == 0x40 ) |
623 | { | ||
624 | length += 2; | 726 | length += 2; |
625 | } | ||
626 | 727 | ||
627 | len_skip = length; | 728 | len_skip = length; |
628 | length += mpeg1_skip_table[header[length - 1] >> 4]; | 729 | length += mpeg1_skip_table[header[length - 1] >> 4]; |
@@ -657,20 +758,19 @@ static void get_next_data( Stream* str ) | |||
657 | if (bytes > 0) | 758 | if (bytes > 0) |
658 | { | 759 | { |
659 | str->curr_packet_end = p + bytes; | 760 | str->curr_packet_end = p + bytes; |
660 | //DEBUGF("prev = %d, curr = %d\n",str->prev_packet,str->curr_packet); | ||
661 | 761 | ||
662 | if (str->curr_packet != NULL) | 762 | if (str->curr_packet != NULL) |
663 | { | 763 | { |
764 | lock_stream(); | ||
765 | |||
766 | str->buffer_remaining -= str->prev_packet_length; | ||
664 | if (str->curr_packet < str->prev_packet) | 767 | if (str->curr_packet < str->prev_packet) |
665 | { | 768 | str->prev_packet_length = (disk_buf_end - str->prev_packet) + |
666 | str->buffer_head += (disk_buf_end - str->prev_packet) + | 769 | (str->curr_packet - disk_buf_start); |
667 | (str->curr_packet - disk_buf); | ||
668 | str->guard_bytes = 0; | ||
669 | } | ||
670 | else | 770 | else |
671 | { | 771 | str->prev_packet_length = (str->curr_packet - str->prev_packet); |
672 | str->buffer_head += (str->curr_packet - str->prev_packet); | 772 | |
673 | } | 773 | unlock_stream(); |
674 | 774 | ||
675 | str->prev_packet = str->curr_packet; | 775 | str->prev_packet = str->curr_packet; |
676 | } | 776 | } |
@@ -678,14 +778,13 @@ static void get_next_data( Stream* str ) | |||
678 | str->curr_packet = p; | 778 | str->curr_packet = p; |
679 | 779 | ||
680 | if (str->curr_packet_end > disk_buf_end) | 780 | if (str->curr_packet_end > disk_buf_end) |
681 | { | 781 | rb->memcpy(disk_buf_end, disk_buf_start, |
682 | str->guard_bytes = str->curr_packet_end - disk_buf_end; | 782 | str->curr_packet_end - disk_buf_end ); |
683 | rb->memcpy(disk_buf_end, disk_buf, str->guard_bytes); | ||
684 | } | ||
685 | } | 783 | } |
686 | 784 | ||
687 | break; | 785 | break; |
688 | } /* end while */ | 786 | } /* end while */ |
787 | return 0; | ||
689 | } | 788 | } |
690 | 789 | ||
691 | /* Our clock rate in ticks/second - this won't be a constant for long */ | 790 | /* Our clock rate in ticks/second - this won't be a constant for long */ |
@@ -943,6 +1042,8 @@ static int button_loop(void) | |||
943 | int vol, minvol, maxvol; | 1042 | int vol, minvol, maxvol; |
944 | int button; | 1043 | int button; |
945 | 1044 | ||
1045 | if (video_sync_start==1) { | ||
1046 | |||
946 | if (str_have_msg(&audio_str)) | 1047 | if (str_have_msg(&audio_str)) |
947 | { | 1048 | { |
948 | struct event ev; | 1049 | struct event ev; |
@@ -1014,6 +1115,7 @@ static int button_loop(void) | |||
1014 | rb->lcd_setfont(FONT_SYSFIXED); | 1115 | rb->lcd_setfont(FONT_SYSFIXED); |
1015 | 1116 | ||
1016 | if (result) { | 1117 | if (result) { |
1118 | settings.resume_time = (int)(get_stream_time()/44100/30); | ||
1017 | str_send_msg(&video_str, STREAM_QUIT, 0); | 1119 | str_send_msg(&video_str, STREAM_QUIT, 0); |
1018 | audio_str.status = STREAM_STOPPED; | 1120 | audio_str.status = STREAM_STOPPED; |
1019 | } else { | 1121 | } else { |
@@ -1024,6 +1126,7 @@ static int button_loop(void) | |||
1024 | break; | 1126 | break; |
1025 | 1127 | ||
1026 | case MPEG_STOP: | 1128 | case MPEG_STOP: |
1129 | settings.resume_time = (int)(get_stream_time()/44100/30); | ||
1027 | str_send_msg(&video_str, STREAM_QUIT, 0); | 1130 | str_send_msg(&video_str, STREAM_QUIT, 0); |
1028 | audio_str.status = STREAM_STOPPED; | 1131 | audio_str.status = STREAM_STOPPED; |
1029 | break; | 1132 | break; |
@@ -1060,7 +1163,7 @@ static int button_loop(void) | |||
1060 | audio_str.status = STREAM_STOPPED; | 1163 | audio_str.status = STREAM_STOPPED; |
1061 | } | 1164 | } |
1062 | } | 1165 | } |
1063 | 1166 | } | |
1064 | quit: | 1167 | quit: |
1065 | return audio_str.status; | 1168 | return audio_str.status; |
1066 | } | 1169 | } |
@@ -1086,7 +1189,23 @@ static void audio_thread(void) | |||
1086 | pcm_playback_play(0); | 1189 | pcm_playback_play(0); |
1087 | 1190 | ||
1088 | /* Get first packet */ | 1191 | /* Get first packet */ |
1089 | get_next_data(&audio_str); | 1192 | get_next_data(&audio_str, 0 ); |
1193 | |||
1194 | /* skip audio packets here */ | ||
1195 | while (audio_sync_start==0) | ||
1196 | { | ||
1197 | audio_str.status = STREAM_PLAYING; | ||
1198 | rb->yield(); | ||
1199 | } | ||
1200 | |||
1201 | if (audio_sync_time>10000) | ||
1202 | { | ||
1203 | while (TS_TO_TICKS(audio_str.curr_pts) < audio_sync_time - 10000) | ||
1204 | { | ||
1205 | get_next_data(&audio_str, 0 ); | ||
1206 | rb->priority_yield(); | ||
1207 | } | ||
1208 | } | ||
1090 | 1209 | ||
1091 | if (audio_str.curr_packet == NULL) | 1210 | if (audio_str.curr_packet == NULL) |
1092 | goto done; | 1211 | goto done; |
@@ -1165,7 +1284,7 @@ static void audio_thread(void) | |||
1165 | mpabuf = mpa_buffer; | 1284 | mpabuf = mpa_buffer; |
1166 | 1285 | ||
1167 | /* Get data from next audio packet */ | 1286 | /* Get data from next audio packet */ |
1168 | get_next_data(&audio_str); | 1287 | get_next_data(&audio_str, 0 ); |
1169 | } | 1288 | } |
1170 | while (audio_str.curr_packet != NULL && | 1289 | while (audio_str.curr_packet != NULL && |
1171 | mpabuf_used < MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD); | 1290 | mpabuf_used < MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD); |
@@ -1198,8 +1317,6 @@ static void audio_thread(void) | |||
1198 | 1317 | ||
1199 | if (mad_stat != 0) | 1318 | if (mad_stat != 0) |
1200 | { | 1319 | { |
1201 | DEBUGF("Audio stream error - %d\n", stream.error); | ||
1202 | |||
1203 | if (stream.error == MAD_FLAG_INCOMPLETE | 1320 | if (stream.error == MAD_FLAG_INCOMPLETE |
1204 | || stream.error == MAD_ERROR_BUFLEN) | 1321 | || stream.error == MAD_ERROR_BUFLEN) |
1205 | { | 1322 | { |
@@ -1259,6 +1376,13 @@ static void audio_thread(void) | |||
1259 | rb->priority_yield(); | 1376 | rb->priority_yield(); |
1260 | } | 1377 | } |
1261 | 1378 | ||
1379 | if (video_sync_start == 0 && | ||
1380 | pts->pts+(uint32_t)synth.pcm.length<audio_sync_time) { | ||
1381 | synth.pcm.length = 0; | ||
1382 | size = 0; | ||
1383 | rb->yield(); | ||
1384 | } | ||
1385 | |||
1262 | /* TODO: This part will be replaced with dsp calls soon */ | 1386 | /* TODO: This part will be replaced with dsp calls soon */ |
1263 | if (MAD_NCHANNELS(&frame.header) == 2) | 1387 | if (MAD_NCHANNELS(&frame.header) == 2) |
1264 | { | 1388 | { |
@@ -1305,6 +1429,7 @@ static void audio_thread(void) | |||
1305 | audio_str.status = STREAM_PLAYING; | 1429 | audio_str.status = STREAM_PLAYING; |
1306 | pcmbuf_threshold = PCMBUF_PLAY_ALL; | 1430 | pcmbuf_threshold = PCMBUF_PLAY_ALL; |
1307 | pcm_playback_seek_time(pcmbuf_tail->time); | 1431 | pcm_playback_seek_time(pcmbuf_tail->time); |
1432 | video_sync_start = 1; | ||
1308 | } | 1433 | } |
1309 | 1434 | ||
1310 | /* Make this data available to DMA */ | 1435 | /* Make this data available to DMA */ |
@@ -1391,29 +1516,32 @@ static void video_thread(void) | |||
1391 | 1516 | ||
1392 | /* Clear the display - this is mainly just to indicate that the | 1517 | /* Clear the display - this is mainly just to indicate that the |
1393 | video thread has started successfully. */ | 1518 | video thread has started successfully. */ |
1394 | rb->lcd_clear_display(); | 1519 | if (!video_thumb_print) |
1395 | rb->lcd_update(); | 1520 | { |
1521 | rb->lcd_clear_display(); | ||
1522 | rb->lcd_update(); | ||
1523 | } | ||
1396 | 1524 | ||
1397 | /* Request the first packet data */ | 1525 | /* Request the first packet data */ |
1398 | get_next_data( &video_str ); | 1526 | get_next_data( &video_str, 0 ); |
1399 | 1527 | ||
1400 | if (video_str.curr_packet == NULL) | 1528 | if (video_str.curr_packet == NULL) |
1401 | goto done; | 1529 | goto video_thread_quit; |
1402 | 1530 | ||
1403 | mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); | 1531 | mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); |
1404 | total_offset += video_str.curr_packet_end - video_str.curr_packet; | 1532 | total_offset += video_str.curr_packet_end - video_str.curr_packet; |
1405 | 1533 | ||
1406 | info = mpeg2_info (mpeg2dec); | 1534 | info = mpeg2_info (mpeg2dec); |
1407 | 1535 | ||
1408 | /* Wait if the audio thread is buffering - i.e. before | ||
1409 | the first frames are decoded */ | ||
1410 | while (audio_str.status == STREAM_BUFFERING) | ||
1411 | rb->priority_yield(); | ||
1412 | |||
1413 | while (1) | 1536 | while (1) |
1414 | { | 1537 | { |
1415 | /* quickly check mailbox first */ | 1538 | /* quickly check mailbox first */ |
1416 | if (str_have_msg(&video_str)) | 1539 | if (video_thumb_print) |
1540 | { | ||
1541 | if (video_str.status == STREAM_STOPPED) | ||
1542 | break; | ||
1543 | } | ||
1544 | else if (str_have_msg(&video_str)) | ||
1417 | { | 1545 | { |
1418 | while (1) | 1546 | while (1) |
1419 | { | 1547 | { |
@@ -1450,7 +1578,8 @@ static void video_thread(void) | |||
1450 | { | 1578 | { |
1451 | case STATE_BUFFER: | 1579 | case STATE_BUFFER: |
1452 | /* Request next packet data */ | 1580 | /* Request next packet data */ |
1453 | get_next_data( &video_str ); | 1581 | get_next_data( &video_str, 0 ); |
1582 | |||
1454 | mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); | 1583 | mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); |
1455 | total_offset += video_str.curr_packet_end - video_str.curr_packet; | 1584 | total_offset += video_str.curr_packet_end - video_str.curr_packet; |
1456 | info = mpeg2_info (mpeg2dec); | 1585 | info = mpeg2_info (mpeg2dec); |
@@ -1458,7 +1587,7 @@ static void video_thread(void) | |||
1458 | if (video_str.curr_packet == NULL) | 1587 | if (video_str.curr_packet == NULL) |
1459 | { | 1588 | { |
1460 | /* No more data. */ | 1589 | /* No more data. */ |
1461 | goto done; | 1590 | goto video_thread_quit; |
1462 | } | 1591 | } |
1463 | continue; | 1592 | continue; |
1464 | 1593 | ||
@@ -1528,8 +1657,12 @@ static void video_thread(void) | |||
1528 | break; | 1657 | break; |
1529 | 1658 | ||
1530 | /* No limiting => no dropping - draw this frame */ | 1659 | /* No limiting => no dropping - draw this frame */ |
1531 | if (!settings.limitfps) | 1660 | if (!settings.limitfps && (video_thumb_print == 0)) |
1661 | { | ||
1662 | audio_sync_start = 1; | ||
1663 | video_sync_start = 1; | ||
1532 | goto picture_draw; | 1664 | goto picture_draw; |
1665 | } | ||
1533 | 1666 | ||
1534 | /* Get presentation times in audio samples - quite accurate | 1667 | /* Get presentation times in audio samples - quite accurate |
1535 | enough - add previous frame duration if not stamped */ | 1668 | enough - add previous frame duration if not stamped */ |
@@ -1538,7 +1671,19 @@ static void video_thread(void) | |||
1538 | 1671 | ||
1539 | period = TIME_TO_TICKS(info->sequence->frame_period); | 1672 | period = TIME_TO_TICKS(info->sequence->frame_period); |
1540 | 1673 | ||
1674 | if ( (video_thumb_print == 1 || video_sync_start == 0) && | ||
1675 | ((int)(info->current_picture->flags & PIC_MASK_CODING_TYPE) | ||
1676 | == PIC_FLAG_CODING_TYPE_B)) | ||
1677 | break; | ||
1678 | |||
1541 | eta_video = curr_time; | 1679 | eta_video = curr_time; |
1680 | |||
1681 | audio_sync_time = eta_video; | ||
1682 | audio_sync_start = 1; | ||
1683 | |||
1684 | while (video_sync_start == 0) | ||
1685 | rb->yield(); | ||
1686 | |||
1542 | eta_audio = get_stream_time(); | 1687 | eta_audio = get_stream_time(); |
1543 | 1688 | ||
1544 | /* How early/late are we? > 0 = late, < 0 early */ | 1689 | /* How early/late are we? > 0 = late, < 0 early */ |
@@ -1664,32 +1809,39 @@ static void video_thread(void) | |||
1664 | 1809 | ||
1665 | picture_wait: | 1810 | picture_wait: |
1666 | /* Wait until audio catches up */ | 1811 | /* Wait until audio catches up */ |
1667 | while (eta_video > eta_audio) | 1812 | if (video_thumb_print) |
1668 | { | 1813 | video_str.status = STREAM_STOPPED; |
1669 | rb->priority_yield(); | 1814 | else |
1670 | 1815 | while (eta_video > eta_audio) | |
1671 | /* Make sure not to get stuck waiting here forever */ | ||
1672 | if (str_have_msg(&video_str)) | ||
1673 | { | 1816 | { |
1674 | str_look_msg(&video_str, &ev); | 1817 | rb->priority_yield(); |
1675 | 1818 | ||
1676 | /* If not to play, process up top */ | 1819 | /* Make sure not to get stuck waiting here forever */ |
1677 | if (ev.id != STREAM_PLAY) | 1820 | if (str_have_msg(&video_str)) |
1678 | goto rendering_finished; | 1821 | { |
1822 | str_look_msg(&video_str, &ev); | ||
1823 | |||
1824 | /* If not to play, process up top */ | ||
1825 | if (ev.id != STREAM_PLAY) | ||
1826 | goto rendering_finished; | ||
1827 | |||
1828 | /* Told to play but already playing */ | ||
1829 | str_get_msg(&video_str, &ev); | ||
1830 | str_reply_msg(&video_str, 1); | ||
1831 | } | ||
1679 | 1832 | ||
1680 | /* Told to play but already playing */ | 1833 | eta_audio = get_stream_time(); |
1681 | str_get_msg(&video_str, &ev); | ||
1682 | str_reply_msg(&video_str, 1); | ||
1683 | } | 1834 | } |
1684 | 1835 | ||
1685 | eta_audio = get_stream_time(); | ||
1686 | } | ||
1687 | |||
1688 | picture_draw: | 1836 | picture_draw: |
1689 | /* Record last frame time */ | 1837 | /* Record last frame time */ |
1690 | last_render = *rb->current_tick; | 1838 | last_render = *rb->current_tick; |
1691 | 1839 | ||
1692 | vo_draw_frame(info->display_fbuf->buf); | 1840 | if (video_thumb_print) |
1841 | vo_draw_frame_thumb(info->display_fbuf->buf); | ||
1842 | else | ||
1843 | vo_draw_frame(info->display_fbuf->buf); | ||
1844 | |||
1693 | num_drawn++; | 1845 | num_drawn++; |
1694 | 1846 | ||
1695 | picture_skip: | 1847 | picture_skip: |
@@ -1724,53 +1876,298 @@ static void video_thread(void) | |||
1724 | rb->yield(); | 1876 | rb->yield(); |
1725 | } | 1877 | } |
1726 | 1878 | ||
1727 | done: | 1879 | video_thread_quit: |
1728 | #if NUM_CORES > 1 | 1880 | /* if video ends before time sync'd, |
1729 | flush_icache(); | 1881 | besure the audio thread is closed */ |
1730 | #endif | 1882 | if (video_sync_start == 0) |
1883 | { | ||
1884 | audio_str.status = STREAM_STOPPED; | ||
1885 | audio_sync_start = 1; | ||
1886 | } | ||
1731 | 1887 | ||
1732 | video_str.status = STREAM_DONE; | 1888 | #if NUM_CORES > 1 |
1889 | flush_icache(); | ||
1890 | #endif | ||
1891 | |||
1892 | mpeg2_close (mpeg2dec); | ||
1893 | |||
1894 | /* Commit suicide */ | ||
1895 | video_str.status = STREAM_TERMINATED; | ||
1896 | } | ||
1733 | 1897 | ||
1734 | while (1) | 1898 | void initialize_stream( Stream *str, uint8_t *buffer_start, size_t disk_buf_len, int id ) |
1735 | { | 1899 | { |
1736 | str_get_msg(&video_str, &ev); | 1900 | str->curr_packet_end = str->curr_packet = NULL; |
1901 | str->prev_packet_length = 0; | ||
1902 | str->prev_packet = str->curr_packet_end = buffer_start; | ||
1903 | str->buffer_remaining = disk_buf_len; | ||
1904 | str->id = id; | ||
1905 | } | ||
1906 | |||
1907 | void display_thumb(int in_file) | ||
1908 | { | ||
1909 | size_t disk_buf_len; | ||
1737 | 1910 | ||
1738 | if (ev.id == STREAM_QUIT) | 1911 | video_thumb_print = 1; |
1739 | break; | 1912 | audio_sync_start = 1; |
1913 | video_sync_start = 1; | ||
1914 | |||
1915 | disk_buf_len = rb->read (in_file, disk_buf_start, disk_buf_size - MPEG_GUARDBUF_SIZE); | ||
1916 | disk_buf_tail = disk_buf_start + disk_buf_len; | ||
1917 | file_remaining = 0; | ||
1918 | initialize_stream(&video_str,disk_buf_start,disk_buf_len,0xe0); | ||
1919 | |||
1920 | video_str.status = STREAM_PLAYING; | ||
1740 | 1921 | ||
1741 | str_reply_msg(&video_str, 0); | 1922 | if ((video_str.thread = rb->create_thread(video_thread, |
1923 | (uint8_t*)video_stack,VIDEO_STACKSIZE,"mpgvideo" | ||
1924 | IF_PRIO(,PRIORITY_PLAYBACK) | ||
1925 | IF_COP(, COP, true))) == NULL) | ||
1926 | { | ||
1927 | rb->splash(HZ, "Cannot create video thread!"); | ||
1928 | } | ||
1929 | else | ||
1930 | { | ||
1931 | while (video_str.status != STREAM_TERMINATED) | ||
1932 | rb->yield(); | ||
1742 | } | 1933 | } |
1743 | 1934 | ||
1744 | video_thread_quit: | 1935 | if ( video_str.curr_packet_end == video_str.curr_packet) |
1745 | /* Commit suicide */ | 1936 | rb->splash(0, "frame not available"); |
1746 | video_str.status = STREAM_TERMINATED; | ||
1747 | } | 1937 | } |
1748 | 1938 | ||
1939 | int find_length( int in_file ) | ||
1940 | { | ||
1941 | uint8_t *p; | ||
1942 | size_t read_length = 60*1024; | ||
1943 | size_t disk_buf_len; | ||
1944 | |||
1945 | play_time = 0; | ||
1946 | |||
1947 | /* temporary read buffer size cannot exceed buffer size */ | ||
1948 | if ( read_length > disk_buf_size ) | ||
1949 | read_length = disk_buf_size; | ||
1950 | |||
1951 | /* read tail of file */ | ||
1952 | rb->lseek( in_file, -1*read_length, SEEK_END ); | ||
1953 | disk_buf_len = rb->read( in_file, disk_buf_start, read_length ); | ||
1954 | disk_buf_tail = disk_buf_start + disk_buf_len; | ||
1955 | |||
1956 | /* sync reader to this segment of the stream */ | ||
1957 | p=disk_buf_start; | ||
1958 | if (sync_data_stream(&p)) | ||
1959 | { | ||
1960 | DEBUGF("Could not sync stream\n"); | ||
1961 | return PLUGIN_ERROR; | ||
1962 | } | ||
1963 | |||
1964 | /* find last PTS in audio stream; will movie always have audio? if | ||
1965 | the play time can not be determined, set play_time to 0 */ | ||
1966 | audio_sync_start = 0; | ||
1967 | audio_sync_time = 0; | ||
1968 | video_sync_start = 0; | ||
1969 | { | ||
1970 | Stream tmp; | ||
1971 | initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); | ||
1972 | |||
1973 | do | ||
1974 | { | ||
1975 | get_next_data(&tmp, 2); | ||
1976 | if (tmp.tagged == 1) | ||
1977 | /* 10 sec less to insure the video frame exist */ | ||
1978 | play_time = (int)((tmp.curr_pts/45000-10)/30); | ||
1979 | } | ||
1980 | while (tmp.curr_packet_end != NULL); | ||
1981 | } | ||
1982 | return 0; | ||
1983 | } | ||
1984 | |||
1985 | ssize_t seek_PTS( int in_file, int start_time, int accept_button ) | ||
1986 | { | ||
1987 | static ssize_t last_seek_pos = 0; | ||
1988 | static int last_start_time = 0; | ||
1989 | ssize_t seek_pos; | ||
1990 | size_t disk_buf_len; | ||
1991 | uint8_t *p; | ||
1992 | size_t read_length = 60*1024; | ||
1993 | |||
1994 | /* temporary read buffer size cannot exceed buffer size */ | ||
1995 | if ( read_length > disk_buf_size ) | ||
1996 | read_length = disk_buf_size; | ||
1997 | |||
1998 | if ( start_time == last_start_time ) | ||
1999 | { | ||
2000 | seek_pos = last_seek_pos; | ||
2001 | rb->lseek(in_file,seek_pos,SEEK_SET); | ||
2002 | } | ||
2003 | else if ( start_time != 0 ) | ||
2004 | { | ||
2005 | seek_pos = rb->filesize(in_file)*start_time/play_time; | ||
2006 | int seek_pos_sec_inc = rb->filesize(in_file)/play_time/30; | ||
2007 | |||
2008 | if (seek_pos<0) | ||
2009 | seek_pos=0; | ||
2010 | if ((size_t)seek_pos > rb->filesize(in_file) - read_length) | ||
2011 | seek_pos = rb->filesize(in_file) - read_length; | ||
2012 | rb->lseek( in_file, seek_pos, SEEK_SET ); | ||
2013 | disk_buf_len = rb->read( in_file, disk_buf_start, read_length ); | ||
2014 | disk_buf_tail = disk_buf_start + disk_buf_len; | ||
2015 | |||
2016 | /* sync reader to this segment of the stream */ | ||
2017 | p=disk_buf_start; | ||
2018 | if (sync_data_stream(&p)) | ||
2019 | { | ||
2020 | DEBUGF("Could not sync stream\n"); | ||
2021 | return PLUGIN_ERROR; | ||
2022 | } | ||
2023 | |||
2024 | /* find PTS >= start_time */ | ||
2025 | audio_sync_start = 0; | ||
2026 | audio_sync_time = 0; | ||
2027 | video_sync_start = 0; | ||
2028 | { | ||
2029 | Stream tmp; | ||
2030 | initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); | ||
2031 | int cont_seek_loop = 1; | ||
2032 | int coarse_seek = 1; | ||
2033 | do | ||
2034 | { | ||
2035 | if ( accept_button ) | ||
2036 | { | ||
2037 | rb->yield(); | ||
2038 | if (rb->button_available()) | ||
2039 | return -101; | ||
2040 | } | ||
2041 | |||
2042 | while ( get_next_data(&tmp, 1) == 1 ) | ||
2043 | { | ||
2044 | if ( tmp.curr_packet_end == disk_buf_start ) | ||
2045 | seek_pos += disk_buf_tail - disk_buf_start; | ||
2046 | else | ||
2047 | seek_pos += tmp.curr_packet_end - disk_buf_start; | ||
2048 | if ((size_t)seek_pos > rb->filesize(in_file) - read_length) | ||
2049 | seek_pos = rb->filesize(in_file) - read_length; | ||
2050 | rb->lseek( in_file, seek_pos, SEEK_SET ); | ||
2051 | disk_buf_len = rb->read ( in_file, disk_buf_start, read_length ); | ||
2052 | disk_buf_tail = disk_buf_start + disk_buf_len; | ||
2053 | |||
2054 | /* sync reader to this segment of the stream */ | ||
2055 | p=disk_buf_start; | ||
2056 | initialize_stream(&tmp,p,disk_buf_len,0xc0); | ||
2057 | } | ||
2058 | |||
2059 | /* are we after start_time in the stream? */ | ||
2060 | if ( coarse_seek && (int)(tmp.curr_pts/45000) >= start_time*30 ) | ||
2061 | { | ||
2062 | int time_to_backup = (int)(tmp.curr_pts/45000) - start_time*30; | ||
2063 | if (time_to_backup == 0) | ||
2064 | time_to_backup++; | ||
2065 | seek_pos -= seek_pos_sec_inc * time_to_backup; | ||
2066 | seek_pos_sec_inc -= seek_pos_sec_inc/20; /* for stability */ | ||
2067 | if (seek_pos<0) | ||
2068 | seek_pos=0; | ||
2069 | if ((size_t)seek_pos > rb->filesize(in_file) - read_length) | ||
2070 | seek_pos = rb->filesize(in_file) - read_length; | ||
2071 | rb->lseek( in_file, seek_pos, SEEK_SET ); | ||
2072 | disk_buf_len = rb->read( in_file, disk_buf_start, read_length ); | ||
2073 | disk_buf_tail = disk_buf_start + disk_buf_len; | ||
2074 | |||
2075 | /* sync reader to this segment of the stream */ | ||
2076 | p=disk_buf_start; | ||
2077 | if (sync_data_stream(&p)) | ||
2078 | { | ||
2079 | DEBUGF("Could not sync stream\n"); | ||
2080 | return PLUGIN_ERROR; | ||
2081 | } | ||
2082 | initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); | ||
2083 | continue; | ||
2084 | } | ||
2085 | |||
2086 | /* are we well before start_time in the stream? */ | ||
2087 | if ( coarse_seek && start_time*30 - (int)(tmp.curr_pts/45000) > 2 ) | ||
2088 | { | ||
2089 | int time_to_advance = start_time*30 - (int)(tmp.curr_pts/45000) - 2; | ||
2090 | if (time_to_advance <= 0) | ||
2091 | time_to_advance = 1; | ||
2092 | seek_pos += seek_pos_sec_inc * time_to_advance; | ||
2093 | if (seek_pos<0) | ||
2094 | seek_pos=0; | ||
2095 | if ((size_t)seek_pos > rb->filesize(in_file) - read_length) | ||
2096 | seek_pos = rb->filesize(in_file) - read_length; | ||
2097 | rb->lseek( in_file, seek_pos, SEEK_SET ); | ||
2098 | disk_buf_len = rb->read ( in_file, disk_buf_start, read_length ); | ||
2099 | disk_buf_tail = disk_buf_start + disk_buf_len; | ||
2100 | |||
2101 | /* sync reader to this segment of the stream */ | ||
2102 | p=disk_buf_start; | ||
2103 | if (sync_data_stream(&p)) | ||
2104 | { | ||
2105 | DEBUGF("Could not sync stream\n"); | ||
2106 | return PLUGIN_ERROR; | ||
2107 | } | ||
2108 | initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); | ||
2109 | continue; | ||
2110 | } | ||
2111 | |||
2112 | coarse_seek = 0; | ||
2113 | |||
2114 | /* are we at start_time in the stream? */ | ||
2115 | if ( (int)(tmp.curr_pts/45000) >= start_time*30 ) | ||
2116 | cont_seek_loop = 0; | ||
2117 | |||
2118 | } | ||
2119 | while ( cont_seek_loop ); | ||
2120 | |||
2121 | |||
2122 | DEBUGF("start diff: %u %u\n",(unsigned int)(tmp.curr_pts/45000),start_time*30); | ||
2123 | seek_pos+=tmp.curr_packet_end-disk_buf_start; | ||
2124 | |||
2125 | last_seek_pos = seek_pos; | ||
2126 | last_start_time = start_time; | ||
2127 | |||
2128 | rb->lseek(in_file,seek_pos,SEEK_SET); | ||
2129 | } | ||
2130 | } | ||
2131 | else | ||
2132 | { | ||
2133 | seek_pos = 0; | ||
2134 | rb->lseek(in_file,0,SEEK_SET); | ||
2135 | last_seek_pos = seek_pos; | ||
2136 | last_start_time = start_time; | ||
2137 | } | ||
2138 | return seek_pos; | ||
2139 | } | ||
2140 | |||
1749 | enum plugin_status plugin_start(struct plugin_api* api, void* parameter) | 2141 | enum plugin_status plugin_start(struct plugin_api* api, void* parameter) |
1750 | { | 2142 | { |
1751 | int status = PLUGIN_ERROR; /* assume failure */ | 2143 | int status = PLUGIN_ERROR; /* assume failure */ |
2144 | int start_time=-1; | ||
1752 | void* audiobuf; | 2145 | void* audiobuf; |
1753 | ssize_t audiosize; | 2146 | ssize_t audiosize; |
1754 | int in_file; | 2147 | int in_file; |
1755 | uint8_t* buffer; | ||
1756 | size_t file_remaining; | ||
1757 | size_t disk_buf_len; | 2148 | size_t disk_buf_len; |
2149 | ssize_t seek_pos; | ||
1758 | #ifndef HAVE_LCD_COLOR | 2150 | #ifndef HAVE_LCD_COLOR |
1759 | long graysize; | 2151 | long graysize; |
1760 | int grayscales; | 2152 | int grayscales; |
1761 | #endif | 2153 | #endif |
1762 | 2154 | ||
2155 | audio_sync_start = 0; | ||
2156 | audio_sync_time = 0; | ||
2157 | video_sync_start = 0; | ||
2158 | |||
1763 | if (parameter == NULL) | 2159 | if (parameter == NULL) |
1764 | { | 2160 | { |
1765 | api->splash(HZ*2, "No File"); | 2161 | api->splash(HZ*2, "No File"); |
1766 | return PLUGIN_ERROR; | ||
1767 | } | 2162 | } |
1768 | 2163 | ||
1769 | /* Initialize IRAM - stops audio and voice as well */ | 2164 | /* Initialize IRAM - stops audio and voice as well */ |
1770 | PLUGIN_IRAM_INIT(api) | 2165 | PLUGIN_IRAM_INIT(api) |
1771 | 2166 | ||
1772 | rb = api; | 2167 | rb = api; |
2168 | rb->splash(0, "loading ..."); | ||
1773 | 2169 | ||
2170 | /* sets audiosize and returns buffer pointer */ | ||
1774 | audiobuf = rb->plugin_get_audio_buffer(&audiosize); | 2171 | audiobuf = rb->plugin_get_audio_buffer(&audiosize); |
1775 | 2172 | ||
1776 | #if INPUT_SRC_CAPS != 0 | 2173 | #if INPUT_SRC_CAPS != 0 |
@@ -1781,49 +2178,38 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) | |||
1781 | 2178 | ||
1782 | rb->pcm_set_frequency(SAMPR_44); | 2179 | rb->pcm_set_frequency(SAMPR_44); |
1783 | 2180 | ||
1784 | /* Set disk pointers to NULL */ | 2181 | #ifndef HAVE_LCD_COLOR |
1785 | disk_buf_end = disk_buf = NULL; | 2182 | /* initialize the grayscale buffer: 32 bitplanes for 33 shades of gray. */ |
1786 | 2183 | grayscales = gray_init(rb, audiobuf, audiosize, false, LCD_WIDTH, LCD_HEIGHT, | |
1787 | /* Stream construction */ | 2184 | 32, 2<<8, &graysize) + 1; |
1788 | /* We take the first stream of each (audio and video) */ | 2185 | audiobuf += graysize; |
1789 | /* TODO : Search for these in the file first */ | 2186 | audiosize -= graysize; |
1790 | audio_str.curr_packet_end = audio_str.curr_packet = audio_str.next_packet = NULL; | 2187 | if (grayscales < 33 || audiosize <= 0) |
1791 | video_str = audio_str; | 2188 | { |
1792 | video_str.id = 0xe0; | 2189 | rb->splash(HZ, "gray buf error"); |
1793 | audio_str.id = 0xc0; | 2190 | return PLUGIN_ERROR; |
2191 | } | ||
2192 | #endif | ||
1794 | 2193 | ||
1795 | /* Initialise our malloc buffer */ | 2194 | /* Initialise our malloc buffer */ |
1796 | audiosize = mpeg_alloc_init(audiobuf, audiosize, LIBMPEG2BUFFER_SIZE); | 2195 | audiosize = mpeg_alloc_init(audiobuf,audiosize, LIBMPEG2BUFFER_SIZE); |
1797 | if (audiosize == 0) | 2196 | if (audiosize == 0) |
1798 | return PLUGIN_ERROR; | 2197 | return PLUGIN_ERROR; |
1799 | 2198 | ||
2199 | /* Set disk pointers to NULL */ | ||
2200 | disk_buf_end = disk_buf_start = NULL; | ||
2201 | |||
1800 | /* Grab most of the buffer for the compressed video - leave some for | 2202 | /* Grab most of the buffer for the compressed video - leave some for |
1801 | PCM audio data and some for libmpeg2 malloc use. */ | 2203 | PCM audio data and some for libmpeg2 malloc use. */ |
1802 | buffer_size = audiosize - (PCMBUFFER_SIZE+PCMBUFFER_GUARD_SIZE+ | 2204 | disk_buf_size = audiosize - (PCMBUFFER_SIZE+PCMBUFFER_GUARD_SIZE+ |
1803 | MPABUF_SIZE); | 2205 | MPABUF_SIZE); |
1804 | 2206 | ||
1805 | DEBUGF("audiosize=%ld, buffer_size=%ld\n",audiosize,buffer_size); | 2207 | DEBUGF("audiosize=%ld, disk_buf_size=%ld\n",audiosize,disk_buf_size); |
1806 | buffer = mpeg_malloc(buffer_size,-1); | 2208 | disk_buf_start = mpeg_malloc(disk_buf_size,-1); |
1807 | 2209 | ||
1808 | if (buffer == NULL) | 2210 | if (disk_buf_start == NULL) |
1809 | return PLUGIN_ERROR; | 2211 | return PLUGIN_ERROR; |
1810 | 2212 | ||
1811 | #ifndef HAVE_LCD_COLOR | ||
1812 | /* initialize the grayscale buffer: 32 bitplanes for 33 shades of gray. */ | ||
1813 | grayscales = gray_init(rb, buffer, buffer_size, false, LCD_WIDTH, LCD_HEIGHT, | ||
1814 | 32, 2<<8, &graysize) + 1; | ||
1815 | buffer += graysize; | ||
1816 | buffer_size -= graysize; | ||
1817 | if (grayscales < 33 || buffer_size <= 0) | ||
1818 | { | ||
1819 | rb->splash(HZ, "gray buf error"); | ||
1820 | return PLUGIN_ERROR; | ||
1821 | } | ||
1822 | #endif | ||
1823 | |||
1824 | buffer_size &= ~(0x7ff); /* Round buffer down to nearest 2KB */ | ||
1825 | DEBUGF("audiosize=%ld, buffer_size=%ld\n",audiosize,buffer_size); | ||
1826 | |||
1827 | if (!init_mpabuf()) | 2213 | if (!init_mpabuf()) |
1828 | return PLUGIN_ERROR; | 2214 | return PLUGIN_ERROR; |
1829 | 2215 | ||
@@ -1836,9 +2222,10 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) | |||
1836 | in_file = rb->open((char*)parameter,O_RDONLY); | 2222 | in_file = rb->open((char*)parameter,O_RDONLY); |
1837 | 2223 | ||
1838 | if (in_file < 0){ | 2224 | if (in_file < 0){ |
1839 | //fprintf(stderr,"Could not open %s\n",argv[1]); | 2225 | DEBUGF("Could not open %s\n",(char*)parameter); |
1840 | return PLUGIN_ERROR; | 2226 | return PLUGIN_ERROR; |
1841 | } | 2227 | } |
2228 | filename = (char*)parameter; | ||
1842 | 2229 | ||
1843 | #ifdef HAVE_LCD_COLOR | 2230 | #ifdef HAVE_LCD_COLOR |
1844 | rb->lcd_set_backdrop(NULL); | 2231 | rb->lcd_set_backdrop(NULL); |
@@ -1860,34 +2247,51 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) | |||
1860 | cannot just return PLUGIN_ERROR - instead drop though to cleanup code | 2247 | cannot just return PLUGIN_ERROR - instead drop though to cleanup code |
1861 | */ | 2248 | */ |
1862 | 2249 | ||
1863 | init_settings(); | 2250 | init_settings((char*)parameter); |
1864 | 2251 | ||
1865 | /* Initialise libmad */ | 2252 | /* Initialise libmad */ |
1866 | rb->memset(mad_frame_overlap, 0, sizeof(mad_frame_overlap)); | 2253 | rb->memset(mad_frame_overlap, 0, sizeof(mad_frame_overlap)); |
1867 | init_mad(mad_frame_overlap); | 2254 | init_mad(mad_frame_overlap); |
1868 | 2255 | ||
1869 | file_remaining = rb->filesize(in_file); | 2256 | disk_buf_end = disk_buf_start + disk_buf_size-MPEG_GUARDBUF_SIZE; |
1870 | disk_buf_end = buffer + buffer_size-MPEG_GUARDBUF_SIZE; | 2257 | |
2258 | /* initalize play_time with the length (in half minutes) of the movie | ||
2259 | zero if the time could not be determined */ | ||
2260 | find_length( in_file ); | ||
2261 | |||
2262 | /* start menu */ | ||
2263 | start_time = mpeg_start_menu(play_time, in_file); | ||
2264 | if ( start_time == -1 ) | ||
2265 | return 0; | ||
2266 | else if ( start_time < 0 ) | ||
2267 | start_time = 0; | ||
2268 | else if ( start_time > play_time ) | ||
2269 | start_time = play_time; | ||
2270 | |||
2271 | rb->splash(0, "loading ..."); | ||
2272 | |||
2273 | /* seek start time */ | ||
2274 | seek_pos = seek_PTS( in_file, start_time, 0 ); | ||
2275 | |||
2276 | rb->lseek(in_file,seek_pos,SEEK_SET); | ||
2277 | video_thumb_print = 0; | ||
2278 | audio_sync_start = 0; | ||
2279 | audio_sync_time = 0; | ||
2280 | video_sync_start = 0; | ||
1871 | 2281 | ||
1872 | /* Read some stream data */ | 2282 | /* Read some stream data */ |
1873 | disk_buf_len = rb->read (in_file, buffer, MPEG_LOW_WATERMARK); | 2283 | disk_buf_len = rb->read (in_file, disk_buf_start, disk_buf_size - MPEG_GUARDBUF_SIZE); |
1874 | 2284 | ||
1875 | DEBUGF("Initial Buffering - %d bytes\n",(int)disk_buf_len); | 2285 | disk_buf_tail = disk_buf_start + disk_buf_len; |
1876 | disk_buf = buffer; | 2286 | file_remaining = rb->filesize(in_file); |
1877 | disk_buf_tail = buffer+disk_buf_len; | 2287 | file_remaining -= disk_buf_len + seek_pos; |
1878 | file_remaining -= disk_buf_len; | 2288 | |
1879 | 2289 | initialize_stream( &video_str, disk_buf_start, disk_buf_len, 0xe0 ); | |
1880 | audio_str.guard_bytes = 0; | 2290 | initialize_stream( &audio_str, disk_buf_start, disk_buf_len, 0xc0 ); |
1881 | audio_str.prev_packet = disk_buf; | ||
1882 | audio_str.buffer_head = 0; | ||
1883 | audio_str.buffer_tail = disk_buf_len; | ||
1884 | video_str.guard_bytes = 0; | ||
1885 | video_str.prev_packet = disk_buf; | ||
1886 | video_str.buffer_head = 0; | ||
1887 | video_str.buffer_tail = disk_buf_len; | ||
1888 | 2291 | ||
1889 | rb->spinlock_init(&audio_str.msg_lock); | 2292 | rb->spinlock_init(&audio_str.msg_lock); |
1890 | rb->spinlock_init(&video_str.msg_lock); | 2293 | rb->spinlock_init(&video_str.msg_lock); |
2294 | |||
1891 | audio_str.status = STREAM_BUFFERING; | 2295 | audio_str.status = STREAM_BUFFERING; |
1892 | video_str.status = STREAM_PLAYING; | 2296 | video_str.status = STREAM_PLAYING; |
1893 | 2297 | ||
@@ -1895,6 +2299,8 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) | |||
1895 | gray_show(true); | 2299 | gray_show(true); |
1896 | #endif | 2300 | #endif |
1897 | 2301 | ||
2302 | init_stream_lock(); | ||
2303 | |||
1898 | #if NUM_CORES > 1 | 2304 | #if NUM_CORES > 1 |
1899 | flush_icache(); | 2305 | flush_icache(); |
1900 | #endif | 2306 | #endif |
@@ -1914,38 +2320,52 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) | |||
1914 | } | 2320 | } |
1915 | else | 2321 | else |
1916 | { | 2322 | { |
1917 | //DEBUGF("START: video = %d, audio = %d\n",audio_str.buffer_remaining,video_str.buffer_remaining); | ||
1918 | rb->lcd_setfont(FONT_SYSFIXED); | 2323 | rb->lcd_setfont(FONT_SYSFIXED); |
1919 | 2324 | ||
1920 | /* Wait until both threads have finished their work */ | 2325 | /* Wait until both threads have finished their work */ |
1921 | while ((audio_str.status >= 0) || (video_str.status >= 0)) | 2326 | while ((audio_str.status >= 0) || (video_str.status >= 0)) |
1922 | { | 2327 | { |
1923 | size_t audio_remaining = audio_str.buffer_tail - audio_str.buffer_head; | 2328 | size_t audio_remaining = audio_str.buffer_remaining; |
1924 | size_t video_remaining = video_str.buffer_tail - video_str.buffer_head; | 2329 | size_t video_remaining = video_str.buffer_remaining; |
1925 | 2330 | ||
1926 | if (MIN(audio_remaining,video_remaining) < MPEG_LOW_WATERMARK) { | 2331 | if (MIN(audio_remaining,video_remaining) < MPEG_LOW_WATERMARK) |
2332 | { | ||
1927 | 2333 | ||
1928 | size_t bytes_to_read = buffer_size - MPEG_GUARDBUF_SIZE - | 2334 | size_t bytes_to_read = disk_buf_size - MPEG_GUARDBUF_SIZE - |
1929 | MAX(audio_remaining,video_remaining); | 2335 | MAX(audio_remaining,video_remaining); |
1930 | 2336 | ||
1931 | bytes_to_read = MIN(bytes_to_read,(size_t)(disk_buf_end-disk_buf_tail)); | 2337 | bytes_to_read = MIN(bytes_to_read,(size_t)(disk_buf_end-disk_buf_tail)); |
1932 | 2338 | ||
1933 | while (( bytes_to_read > 0) && (file_remaining > 0) && | 2339 | while (( bytes_to_read > 0) && (file_remaining > 0) && |
1934 | ((audio_str.status >= 0) || (video_str.status >= 0))) { | 2340 | ((audio_str.status != STREAM_DONE) || (video_str.status != STREAM_DONE))) |
1935 | size_t n = rb->read(in_file, disk_buf_tail, MIN(32*1024,bytes_to_read)); | 2341 | { |
2342 | |||
2343 | size_t n; | ||
2344 | if ( video_sync_start != 0 ) | ||
2345 | n = rb->read(in_file, disk_buf_tail, MIN(32*1024,bytes_to_read)); | ||
2346 | else | ||
2347 | { | ||
2348 | n = rb->read(in_file, disk_buf_tail,bytes_to_read); | ||
2349 | if (n==0) | ||
2350 | rb->splash(30,"buffer fill error"); | ||
2351 | } | ||
2352 | |||
1936 | 2353 | ||
1937 | bytes_to_read -= n; | 2354 | bytes_to_read -= n; |
1938 | file_remaining -= n; | 2355 | file_remaining -= n; |
1939 | 2356 | ||
1940 | audio_str.buffer_tail += n; | 2357 | lock_stream(); |
1941 | video_str.buffer_tail += n; | 2358 | audio_str.buffer_remaining += n; |
2359 | video_str.buffer_remaining += n; | ||
2360 | unlock_stream(); | ||
2361 | |||
1942 | disk_buf_tail += n; | 2362 | disk_buf_tail += n; |
1943 | 2363 | ||
1944 | rb->yield(); | 2364 | rb->yield(); |
1945 | } | 2365 | } |
1946 | 2366 | ||
1947 | if (disk_buf_tail == disk_buf_end) | 2367 | if (disk_buf_tail == disk_buf_end) |
1948 | disk_buf_tail = buffer; | 2368 | disk_buf_tail = disk_buf_start; |
1949 | } | 2369 | } |
1950 | 2370 | ||
1951 | rb->sleep(HZ/10); | 2371 | rb->sleep(HZ/10); |
@@ -1968,6 +2388,8 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) | |||
1968 | invalidate_icache(); | 2388 | invalidate_icache(); |
1969 | #endif | 2389 | #endif |
1970 | 2390 | ||
2391 | vo_cleanup(); | ||
2392 | |||
1971 | #ifndef HAVE_LCD_COLOR | 2393 | #ifndef HAVE_LCD_COLOR |
1972 | gray_release(); | 2394 | gray_release(); |
1973 | #endif | 2395 | #endif |
diff --git a/apps/plugins/mpegplayer/video_out.h b/apps/plugins/mpegplayer/video_out.h index 0d91eb7b1c..ec3f7c65d3 100644 --- a/apps/plugins/mpegplayer/video_out.h +++ b/apps/plugins/mpegplayer/video_out.h | |||
@@ -22,4 +22,6 @@ | |||
22 | */ | 22 | */ |
23 | 23 | ||
24 | void vo_draw_frame (uint8_t * const * buf); | 24 | void vo_draw_frame (uint8_t * const * buf); |
25 | void vo_draw_frame_thumb (uint8_t * const * buf); | ||
25 | void vo_setup (const mpeg2_sequence_t * sequence); | 26 | void vo_setup (const mpeg2_sequence_t * sequence); |
27 | void vo_cleanup (void); | ||
diff --git a/apps/plugins/mpegplayer/video_out_rockbox.c b/apps/plugins/mpegplayer/video_out_rockbox.c index 2aac0b8039..490f04411f 100644 --- a/apps/plugins/mpegplayer/video_out_rockbox.c +++ b/apps/plugins/mpegplayer/video_out_rockbox.c | |||
@@ -43,8 +43,7 @@ static int output_height; | |||
43 | void vo_draw_frame (uint8_t * const * buf) | 43 | void vo_draw_frame (uint8_t * const * buf) |
44 | { | 44 | { |
45 | #ifdef HAVE_LCD_COLOR | 45 | #ifdef HAVE_LCD_COLOR |
46 | rb->lcd_yuv_blit(buf, | 46 | rb->lcd_yuv_blit(buf, 0,0,image_width, |
47 | 0,0,image_width, | ||
48 | output_x,output_y,output_width,output_height); | 47 | output_x,output_y,output_width,output_height); |
49 | #else | 48 | #else |
50 | gray_ub_gray_bitmap_part(buf[0],0,0,image_width, | 49 | gray_ub_gray_bitmap_part(buf[0],0,0,image_width, |
@@ -60,10 +59,105 @@ void vo_draw_frame (uint8_t * const * buf) | |||
60 | #define SCREEN_HEIGHT LCD_WIDTH | 59 | #define SCREEN_HEIGHT LCD_WIDTH |
61 | #endif | 60 | #endif |
62 | 61 | ||
62 | uint8_t* tmpbufa = 0; | ||
63 | uint8_t* tmpbufb = 0; | ||
64 | uint8_t* tmpbufc = 0; | ||
65 | uint8_t* tmpbuf[3]; | ||
66 | |||
67 | void vo_draw_frame_thumb (uint8_t * const * buf) | ||
68 | { | ||
69 | int r,c; | ||
70 | |||
71 | #if LCD_WIDTH >= LCD_HEIGHT | ||
72 | for (r=0;r<image_width/2;r++) | ||
73 | for (c=0;c<image_height/2;c++) | ||
74 | *(tmpbuf[0]+c*image_width/2+r) = | ||
75 | *(buf[0]+2*c*image_width+2*r); | ||
76 | |||
77 | for (r=0;r<image_width/4;r++) | ||
78 | for (c=0;c<image_height/4;c++) | ||
79 | { | ||
80 | *(tmpbuf[1]+c*image_width/4+r) = | ||
81 | *(buf[1]+2*c*image_width/2+2*r); | ||
82 | *(tmpbuf[2]+c*image_width/4+r) = | ||
83 | *(buf[2]+2*c*image_width/2+2*r); | ||
84 | } | ||
85 | #else | ||
86 | for (r=0;r<image_width/2;r++) | ||
87 | for (c=0;c<image_height/2;c++) | ||
88 | *(tmpbuf[0]+(image_width/2-1-r)*image_height/2+c) = | ||
89 | *(buf[0]+2*c*image_width+2*r); | ||
90 | |||
91 | for (r=0;r<image_width/4;r++) | ||
92 | for (c=0;c<image_height/4;c++) | ||
93 | { | ||
94 | *(tmpbuf[1]+(image_width/4-1-r)*image_height/4+c) = | ||
95 | *(buf[1]+2*c*image_width/2+2*r); | ||
96 | *(tmpbuf[2]+(image_width/4-1-r)*image_height/4+c) = | ||
97 | *(buf[2]+2*c*image_width/2+2*r); | ||
98 | } | ||
99 | #endif | ||
100 | |||
101 | rb->lcd_clear_display(); | ||
102 | rb->lcd_update(); | ||
103 | |||
104 | #ifdef HAVE_LCD_COLOR | ||
105 | #ifdef SIMULATOR | ||
106 | #if LCD_WIDTH >= LCD_HEIGHT | ||
107 | rb->lcd_yuv_blit(tmpbuf,0,0,image_width/2, | ||
108 | (LCD_WIDTH-1-image_width/2)/2, | ||
109 | LCD_HEIGHT-50-(image_height/2), | ||
110 | output_width/2,output_height/2); | ||
111 | |||
112 | #else | ||
113 | rb->lcd_yuv_blit(tmpbuf,0,0,image_height/2, | ||
114 | LCD_HEIGHT-50-(image_height/2), | ||
115 | (LCD_WIDTH-1-image_width/2)/2, | ||
116 | output_height/2,output_width/2); | ||
117 | #endif | ||
118 | #else | ||
119 | #if LCD_WIDTH >= LCD_HEIGHT | ||
120 | rb->lcd_yuv_blit(tmpbuf,0,0,image_width/2, | ||
121 | (LCD_WIDTH-1-image_width/2)/2, | ||
122 | LCD_HEIGHT-50-(image_height/2), | ||
123 | output_width/2,output_height/2); | ||
124 | #else | ||
125 | rb->lcd_yuv_blit(tmpbuf,0,0,image_height/2, | ||
126 | LCD_HEIGHT-50-(image_height/2), | ||
127 | (LCD_WIDTH-1-image_width/2)/2, | ||
128 | output_height/2,output_width/2); | ||
129 | #endif | ||
130 | #endif | ||
131 | #else | ||
132 | #if LCD_WIDTH >= LCD_HEIGHT | ||
133 | gray_ub_gray_bitmap_part(tmpbuf[0],0,0,image_width/2, | ||
134 | (LCD_WIDTH-1-image_width/2)/2, | ||
135 | LCD_HEIGHT-50-(image_height/2), | ||
136 | output_width/2,output_height/2); | ||
137 | #else | ||
138 | gray_ub_gray_bitmap_part(tmpbuf[0],0,0,image_height/2, | ||
139 | LCD_HEIGHT-50-(image_height/2), | ||
140 | (LCD_WIDTH-1-image_width/2)/2, | ||
141 | output_height/2,output_width/2); | ||
142 | #endif | ||
143 | #endif | ||
144 | } | ||
145 | |||
63 | void vo_setup(const mpeg2_sequence_t * sequence) | 146 | void vo_setup(const mpeg2_sequence_t * sequence) |
64 | { | 147 | { |
65 | image_width=sequence->width; | 148 | image_width=sequence->width; |
66 | image_height=sequence->height; | 149 | image_height=sequence->height; |
150 | |||
151 | tmpbufa = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width/2* | ||
152 | image_height/2, -2); | ||
153 | tmpbufb = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width/4* | ||
154 | image_height/4, -2); | ||
155 | tmpbufc = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width/4* | ||
156 | image_height/4, -2); | ||
157 | tmpbuf[0] = tmpbufa; | ||
158 | tmpbuf[1] = tmpbufb; | ||
159 | tmpbuf[2] = tmpbufc; | ||
160 | |||
67 | image_chroma_x=image_width/sequence->chroma_width; | 161 | image_chroma_x=image_width/sequence->chroma_width; |
68 | image_chroma_y=image_height/sequence->chroma_height; | 162 | image_chroma_y=image_height/sequence->chroma_height; |
69 | 163 | ||
@@ -83,3 +177,13 @@ void vo_setup(const mpeg2_sequence_t * sequence) | |||
83 | output_y = (SCREEN_HEIGHT-sequence->display_height)/2; | 177 | output_y = (SCREEN_HEIGHT-sequence->display_height)/2; |
84 | } | 178 | } |
85 | } | 179 | } |
180 | |||
181 | void vo_cleanup(void) | ||
182 | { | ||
183 | if (tmpbufc) | ||
184 | mpeg2_free(tmpbufc); | ||
185 | if (tmpbufb) | ||
186 | mpeg2_free(tmpbufb); | ||
187 | if (tmpbufa) | ||
188 | mpeg2_free(tmpbufa); | ||
189 | } | ||
diff --git a/docs/CREDITS b/docs/CREDITS index cb12c9688b..2abfcb8b46 100644 --- a/docs/CREDITS +++ b/docs/CREDITS | |||
@@ -339,6 +339,8 @@ David Bishop | |||
339 | Hein-Pieter van Braam | 339 | Hein-Pieter van Braam |
340 | Przemysław Hołubowski | 340 | Przemysław Hołubowski |
341 | Stepan Moskovchenko | 341 | Stepan Moskovchenko |
342 | John S. Gwynne | ||
343 | Brian J. Morey | ||
342 | 344 | ||
343 | The libmad team | 345 | The libmad team |
344 | The wavpack team | 346 | The wavpack team |
diff --git a/firmware/drivers/button.c b/firmware/drivers/button.c index 3daa08b2c3..d79a9333ff 100644 --- a/firmware/drivers/button.c +++ b/firmware/drivers/button.c | |||
@@ -293,6 +293,11 @@ static void button_boost(bool state) | |||
293 | } | 293 | } |
294 | #endif /* HAVE_ADJUSTABLE_CPU_FREQ */ | 294 | #endif /* HAVE_ADJUSTABLE_CPU_FREQ */ |
295 | 295 | ||
296 | int button_available( void ) | ||
297 | { | ||
298 | return queue_count(&button_queue); | ||
299 | } | ||
300 | |||
296 | long button_get(bool block) | 301 | long button_get(bool block) |
297 | { | 302 | { |
298 | struct event ev; | 303 | struct event ev; |
diff --git a/firmware/export/button.h b/firmware/export/button.h index 5322d814bf..881d7c424d 100644 --- a/firmware/export/button.h +++ b/firmware/export/button.h | |||
@@ -27,6 +27,7 @@ | |||
27 | extern struct event_queue button_queue; | 27 | extern struct event_queue button_queue; |
28 | 28 | ||
29 | void button_init (void); | 29 | void button_init (void); |
30 | int button_available(void); | ||
30 | long button_get (bool block); | 31 | long button_get (bool block); |
31 | long button_get_w_tmo(int ticks); | 32 | long button_get_w_tmo(int ticks); |
32 | intptr_t button_get_data(void); | 33 | intptr_t button_get_data(void); |
diff --git a/uisimulator/sdl/button.c b/uisimulator/sdl/button.c index 4869dd06b1..2b44a7bace 100644 --- a/uisimulator/sdl/button.c +++ b/uisimulator/sdl/button.c | |||
@@ -736,6 +736,11 @@ void button_event(int key, bool pressed) | |||
736 | 736 | ||
737 | /* Again copied from real button.c... */ | 737 | /* Again copied from real button.c... */ |
738 | 738 | ||
739 | int button_available( void ) | ||
740 | { | ||
741 | return queue_count(&button_queue); | ||
742 | } | ||
743 | |||
739 | long button_get(bool block) | 744 | long button_get(bool block) |
740 | { | 745 | { |
741 | struct event ev; | 746 | struct event ev; |