From 1832d396d437b4c62c765069fb0c223cd98f1ad8 Mon Sep 17 00:00:00 2001 From: roolku Date: Tue, 9 Oct 2007 20:42:20 +0000 Subject: [PATCH] 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 --- apps/plugin.c | 2 + apps/plugin.h | 4 +- apps/plugins/lib/configfile.c | 75 ++- apps/plugins/lib/configfile.h | 32 ++ apps/plugins/mpegplayer/alloc.c | 6 +- apps/plugins/mpegplayer/header.c | 7 +- apps/plugins/mpegplayer/idct.c | 6 +- apps/plugins/mpegplayer/idct_arm_c.c | 6 +- apps/plugins/mpegplayer/mpeg_settings.c | 410 ++++++++++++++- apps/plugins/mpegplayer/mpeg_settings.h | 17 +- apps/plugins/mpegplayer/mpegplayer.c | 770 +++++++++++++++++++++------- apps/plugins/mpegplayer/video_out.h | 2 + apps/plugins/mpegplayer/video_out_rockbox.c | 108 +++- docs/CREDITS | 2 + firmware/drivers/button.c | 5 + firmware/export/button.h | 1 + uisimulator/sdl/button.c | 5 + 17 files changed, 1240 insertions(+), 218 deletions(-) diff --git a/apps/plugin.c b/apps/plugin.c index d5f70be04..7981c36b9 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -518,6 +518,8 @@ static const struct plugin_api rockbox_api = { talk_disable_menus, talk_enable_menus, + button_available, + }; int plugin_load(const char* plugin, void* parameter) diff --git a/apps/plugin.h b/apps/plugin.h index f025704f3..7277d8031 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -112,7 +112,7 @@ #define PLUGIN_MAGIC 0x526F634B /* RocK */ /* increase this every time the api struct changes */ -#define PLUGIN_API_VERSION 79 +#define PLUGIN_API_VERSION 80 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any @@ -635,6 +635,8 @@ struct plugin_api { void (*talk_disable_menus)(void); void (*talk_enable_menus)(void); + + int (*button_available)(void); }; /* plugin header */ diff --git a/apps/plugins/lib/configfile.c b/apps/plugins/lib/configfile.c index 0fbba8158..476f77687 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, if(fd < 0) return fd*10 - 1; - cfg_rb->fdprintf(fd, "file version: %d\n", version); + /* pre-allocate 10 bytes for INT */ + cfg_rb->fdprintf(fd, "file version: %10d\n", version); for(i = 0;i < num_items;i++) { switch(cfg[i].type) { case TYPE_INT: - cfg_rb->fdprintf(fd, "%s: %d\n", + /* pre-allocate 10 bytes for INT */ + cfg_rb->fdprintf(fd, "%s: %10d\n", cfg[i].name, *cfg[i].val); break; @@ -141,3 +143,72 @@ int configfile_load(const char *filename, struct configdata *cfg, cfg_rb->close(fd); return 0; } + +int configfile_get_value(const char* filename, const char* name) +{ + int fd; + char *pname; + char *pval; + char buf[MAX_PATH]; + + get_cfg_filename(buf, MAX_PATH, filename); + fd = cfg_rb->open(buf, O_RDONLY); + if(fd < 0) + return -1; + + while(cfg_rb->read_line(fd, buf, MAX_PATH) > 0) + { + cfg_rb->settings_parseline(buf, &pname, &pval); + if(!cfg_rb->strcmp(name, pname)) + { + cfg_rb->close(fd); + return cfg_rb->atoi(pval); + } + } + + cfg_rb->close(fd); + return -1; +} + +int configfile_update_entry(const char* filename, const char* name, int val) +{ + int fd; + char *pname; + char *pval; + char path[MAX_PATH]; + char buf[256]; + int found = 0; + int line_len = 0; + int pos = 0; + + /* open the current config file */ + get_cfg_filename(path, MAX_PATH, filename); + fd = cfg_rb->open(path, O_RDWR); + if(fd < 0) + return -1; + + /* read in the current stored settings */ + while((line_len = cfg_rb->read_line(fd, buf, 256)) > 0) + { + cfg_rb->settings_parseline(buf, &pname, &pval); + + if(!cfg_rb->strcmp(name, pname)) + { + found = 1; + cfg_rb->lseek(fd, pos, SEEK_SET); + /* pre-allocate 10 bytes for INT */ + cfg_rb->fdprintf(fd, "%s: %10d\n", pname, val); + break; + } + pos += line_len; + } + + /* if (name/val) is a new entry just append to file */ + if (found == 0) + /* pre-allocate 10 bytes for INT */ + cfg_rb->fdprintf(fd, "%s: %10d\n", name, val); + + cfg_rb->close(fd); + + return found; +} diff --git a/apps/plugins/lib/configfile.h b/apps/plugins/lib/configfile.h index fcce7de27..7aa69f3ec 100644 --- a/apps/plugins/lib/configfile.h +++ b/apps/plugins/lib/configfile.h @@ -38,9 +38,41 @@ struct configdata }; void configfile_init(struct plugin_api* newrb); + +/* configfile_save - Given configdata entries this function will + create a config file with these entries, destroying any + previous config file of the same name */ int configfile_save(const char *filename, struct configdata *cfg, int num_items, int version); + int configfile_load(const char *filename, struct configdata *cfg, int num_items, int min_version); +/* configfile_get_value - Given a key name, this function will + return the integer value for that key. + + Input: + filename = config file filename + name = (name/value) pair name entry + Return: + value if (name/value) pair is found + -1 if entry is not found +*/ +int configfile_get_value(const char* filename, const char* name); + +/* configure_update_entry - Given a key name and integer value + this function will update the entry if found, or add it if + not found. + + Input: + filename = config file filename + name = (name/value) pair name entry + val = new value for (name/value) pair + Return: + 1 if the (name/value) pair was found and updated with the new value + 0 if the (name/value) pair was added as a new entry + -1 if error +*/ +int configfile_update_entry(const char* filename, const char* name, int val); + #endif diff --git a/apps/plugins/mpegplayer/alloc.c b/apps/plugins/mpegplayer/alloc.c index 0ba86a51f..ae482de11 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, x = &mallocbuf[*mem_ptr]; *mem_ptr += (size + 3) & ~3; /* Keep memory 32-bit aligned */ + rb->memset(x,0,size); + return x; (void)reason; } @@ -116,7 +118,7 @@ void * mpeg2_malloc(unsigned size, mpeg2_alloc_t reason) void mpeg2_free(void *ptr) { - (void)ptr; + mpeg2_mem_ptr = (void *)ptr - (void *)mpeg2_mallocbuf; } /* The following are expected by libmad */ @@ -141,7 +143,7 @@ void * codec_calloc(size_t nmemb, size_t size) void codec_free(void* ptr) { - (void)ptr; + mem_ptr = (void *)ptr - (void *)mallocbuf; } 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 7f94705f5..7486b0ebf 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 = { 83 }; -uint8_t mpeg2_scan_norm[64] IDATA_ATTR = { +uint8_t default_mpeg2_scan_norm[64] IDATA_ATTR = { /* Zig-Zag scan pattern */ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 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 = { 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; -uint8_t mpeg2_scan_alt[64] IDATA_ATTR = { +uint8_t default_mpeg2_scan_alt[64] IDATA_ATTR = { /* Alternate scan pattern */ 0, 8, 16, 24, 1, 9, 2, 10, 17, 25, 32, 40, 48, 56, 57, 49, 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 = { 53, 61, 22, 30, 7, 15, 23, 31, 38, 46, 54, 62, 39, 47, 55, 63 }; +uint8_t mpeg2_scan_norm[64] IDATA_ATTR; +uint8_t mpeg2_scan_alt[64] IDATA_ATTR; + void mpeg2_header_state_init (mpeg2dec_t * mpeg2dec) { if (mpeg2dec->sequence.width != (unsigned)-1) { diff --git a/apps/plugins/mpegplayer/idct.c b/apps/plugins/mpegplayer/idct.c index ee02e72a6..bf705c6a2 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, void mpeg2_idct_init (void) { + extern uint8_t default_mpeg2_scan_norm[64]; + extern uint8_t default_mpeg2_scan_alt[64]; extern uint8_t mpeg2_scan_norm[64]; extern uint8_t mpeg2_scan_alt[64]; int i, j; @@ -274,10 +276,10 @@ void mpeg2_idct_init (void) for (i = 0; i < 64; i++) { - j = mpeg2_scan_norm[i]; + j = default_mpeg2_scan_norm[i]; mpeg2_scan_norm[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); - j = mpeg2_scan_alt[i]; + j = default_mpeg2_scan_alt[i]; mpeg2_scan_alt[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); } } diff --git a/apps/plugins/mpegplayer/idct_arm_c.c b/apps/plugins/mpegplayer/idct_arm_c.c index be9971f5c..9805f421a 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, void mpeg2_idct_init (void) { + extern uint8_t default_mpeg2_scan_norm[64]; + extern uint8_t default_mpeg2_scan_alt[64]; extern uint8_t mpeg2_scan_norm[64]; extern uint8_t mpeg2_scan_alt[64]; int i, j; @@ -518,10 +520,10 @@ void mpeg2_idct_init (void) for (i = 0; i < 64; i++) { - j = mpeg2_scan_norm[i]; + j = default_mpeg2_scan_norm[i]; mpeg2_scan_norm[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); - j = mpeg2_scan_alt[i]; + j = default_mpeg2_scan_alt[i]; mpeg2_scan_alt[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); } } diff --git a/apps/plugins/mpegplayer/mpeg_settings.c b/apps/plugins/mpegplayer/mpeg_settings.c index 28062f456..197fa0923 100644 --- a/apps/plugins/mpegplayer/mpeg_settings.c +++ b/apps/plugins/mpegplayer/mpeg_settings.c @@ -7,20 +7,99 @@ extern struct plugin_api* rb; struct mpeg_settings settings; -static struct mpeg_settings old_settings; + +ssize_t seek_PTS(int in_file, int startTime, int accept_button); +void display_thumb(int in_file); + +#ifndef HAVE_LCD_COLOR +void gray_show(bool enable); +#endif #define SETTINGS_VERSION 2 #define SETTINGS_MIN_VERSION 1 #define SETTINGS_FILENAME "mpegplayer.cfg" +enum slider_state_t {state0, state1, state2, + state3, state4, state5} slider_state; + +volatile long thumbDelayTimer; + +/* button definitions */ +#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H300_PAD) +#define MPEG_SELECT BUTTON_ON +#define MPEG_RIGHT BUTTON_RIGHT +#define MPEG_LEFT BUTTON_LEFT +#define MPEG_SCROLL_DOWN BUTTON_UP +#define MPEG_SCROLL_UP BUTTON_DOWN +#define MPEG_EXIT BUTTON_OFF + +#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) +#define MPEG_SELECT BUTTON_PLAY +#define MPEG_RIGHT BUTTON_RIGHT +#define MPEG_LEFT BUTTON_LEFT +#define MPEG_SCROLL_DOWN BUTTON_UP +#define MPEG_SCROLL_UP BUTTON_DOWN +#define MPEG_EXIT BUTTON_POWER + +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ + (CONFIG_KEYPAD == IPOD_3G_PAD) || \ + (CONFIG_KEYPAD == IPOD_1G2G_PAD) +#define MPEG_SELECT BUTTON_SELECT +#define MPEG_RIGHT BUTTON_RIGHT +#define MPEG_LEFT BUTTON_LEFT +#define MPEG_SCROLL_DOWN BUTTON_SCROLL_FWD +#define MPEG_SCROLL_UP BUTTON_SCROLL_BACK +#define MPEG_EXIT BUTTON_MENU + +#elif CONFIG_KEYPAD == GIGABEAT_PAD +#define MPEG_SELECT BUTTON_SELECT +#define MPEG_LEFT BUTTON_LEFT +#define MPEG_RIGHT BUTTON_RIGHT +#define MPEG_UP BUTTON_UP +#define MPEG_DOWN BUTTON_DOWN +#define MPEG_SCROLL_DOWN BUTTON_VOL_DOWN +#define MPEG_SCROLL_UP BUTTON_VOL_UP +#define MPEG_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == IRIVER_H10_PAD +#define MPEG_SELECT BUTTON_PLAY +#define MPEG_SCROLL_UP BUTTON_SCROLL_UP +#define MPEG_SCROLL_DOWN BUTTON_SCROLL_DOWN +#define MPEG_LEFT BUTTON_LEFT +#define MPEG_RIGHT BUTTON_RIGHT +#define MPEG_EXIT BUTTON_POWER + +#elif (CONFIG_KEYPAD == SANSA_E200_PAD) +#define MPEG_SELECT BUTTON_SELECT +#define MPEG_SCROLL_UP BUTTON_SCROLL_UP +#define MPEG_SCROLL_DOWN BUTTON_SCROLL_DOWN +#define MPEG_LEFT BUTTON_LEFT +#define MPEG_RIGHT BUTTON_RIGHT +#define MPEG_UP BUTTON_UP +#define MPEG_DOWN BUTTON_DOWN +#define MPEG_EXIT BUTTON_POWER + +#elif (CONFIG_KEYPAD == SANSA_C200_PAD) +#define MPEG_SELECT BUTTON_SELECT +#define MPEG_SCROLL_UP BUTTON_VOL_UP +#define MPEG_SCROLL_DOWN BUTTON_VOL_DOWN +#define MPEG_LEFT BUTTON_LEFT +#define MPEG_RIGHT BUTTON_RIGHT +#define MPEG_UP BUTTON_UP +#define MPEG_DOWN BUTTON_DOWN +#define MPEG_EXIT BUTTON_POWER + +#else +#error MPEGPLAYER: Unsupported keypad +#endif + static struct configdata config[] = { - {TYPE_ENUM, 0, 2, &settings.showfps, "Show FPS", - (char *[]){ "No", "Yes" }, NULL}, - {TYPE_ENUM, 0, 2, &settings.limitfps, "Limit FPS", - (char *[]){ "No", "Yes" }, NULL}, - {TYPE_ENUM, 0, 2, &settings.skipframes, "Skip frames", - (char *[]){ "No", "Yes" }, NULL}, + {TYPE_INT, 0, 2, &settings.showfps, "Show FPS", NULL, NULL}, + {TYPE_INT, 0, 2, &settings.limitfps, "Limit FPS", NULL, NULL}, + {TYPE_INT, 0, 2, &settings.skipframes, "Skip frames", NULL, NULL}, + #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) {TYPE_INT, 0, INT_MAX, &settings.displayoptions, "Display options", NULL, NULL}, @@ -36,6 +115,7 @@ enum mpeg_menu_ids MPEG_OPTION_DISPLAY_FPS, MPEG_OPTION_LIMIT_FPS, MPEG_OPTION_SKIP_FRAMES, + MPEG_OPTION_CLEAR_RESUMES, MPEG_OPTION_QUIT, }; @@ -68,13 +148,250 @@ static void display_options(void) } #endif /* #ifdef TOSHIBA_GIGABEAT_F */ +void draw_slider(int slider_ypos, int max_val, int current_val) +{ + int slider_margin = LCD_WIDTH*12/100; /* 12% */ + int slider_width = LCD_WIDTH-(slider_margin*2); + char resume_str[32]; + + /* max_val and current_val are in half minutes + determine value .0 or .5 to display */ + int max_hol = max_val/2; + int max_rem = (max_val-(max_hol*2))*5; + int current_hol = current_val/2; + int current_rem = (current_val-(current_hol*2))*5; + + rb->snprintf(resume_str, sizeof(resume_str), "0.0"); + rb->lcd_putsxy(slider_margin, slider_ypos, resume_str); + + rb->snprintf(resume_str, sizeof(resume_str), "%u.%u", max_hol, max_rem); + rb->lcd_putsxy(LCD_WIDTH-slider_margin-25, slider_ypos, resume_str); + + rb->lcd_drawrect(slider_margin, slider_ypos+17, slider_width, 8); + rb->lcd_fillrect(slider_margin, slider_ypos+17, + current_val*slider_width/max_val, 8); + + rb->snprintf(resume_str, sizeof(resume_str), "%u.%u", current_hol, + current_rem); + rb->lcd_putsxy(slider_margin+(current_val*slider_width/max_val)-16, + slider_ypos+29, resume_str); + + rb->lcd_update_rect(0, slider_ypos, LCD_WIDTH, LCD_HEIGHT-slider_ypos); +} + +int get_start_time(int play_time, int in_file) +{ + int quit = 0; + int button = 0; + int resume_time = settings.resume_time; + int slider_ypos = LCD_HEIGHT-45; + int seek_rtn; + + slider_state = state0; + thumbDelayTimer = *(rb->current_tick); + rb->lcd_clear_display(); + rb->lcd_update(); + + while(quit == 0) + { + button = rb->button_get(false); + switch (button) + { +#if (CONFIG_KEYPAD == GIGABEAT_PAD) || \ + (CONFIG_KEYPAD == SANSA_E200_PAD) || \ + (CONFIG_KEYPAD == SANSA_C200_PAD) + case MPEG_DOWN: + case MPEG_DOWN | BUTTON_REPEAT: + if ((resume_time -= 20) < 0) + resume_time = 0; + slider_state = state0; + thumbDelayTimer = *(rb->current_tick); + break; + case MPEG_UP: + case MPEG_UP | BUTTON_REPEAT: + if ((resume_time += 20) > play_time) + resume_time = play_time; + slider_state = state0; + thumbDelayTimer = *(rb->current_tick); + break; +#endif + case MPEG_LEFT: + case MPEG_LEFT | BUTTON_REPEAT: + case MPEG_SCROLL_UP: + case MPEG_SCROLL_UP | BUTTON_REPEAT: + if (--resume_time < 0) + resume_time = 0; + slider_state = state0; + thumbDelayTimer = *(rb->current_tick); + break; + case MPEG_RIGHT: + case MPEG_RIGHT | BUTTON_REPEAT: + case MPEG_SCROLL_DOWN: + case MPEG_SCROLL_DOWN | BUTTON_REPEAT: + if (++resume_time > play_time) + resume_time = play_time; + slider_state = state0; + thumbDelayTimer = *(rb->current_tick); + break; + case MPEG_SELECT: + quit = 1; + break; + case MPEG_EXIT: + resume_time = -1; + quit = 1; + break; + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + { + resume_time = -1; + quit = 1; + } + break; + } + + rb->yield(); + + switch(slider_state) + { + case state0: + rb->lcd_clear_display(); + rb->lcd_update(); +#ifdef HAVE_LCD_COLOR + if (resume_time > 0) + rb->splash(0, "loading ..."); +#endif + slider_state = state1; + break; + case state1: + if (*(rb->current_tick) - thumbDelayTimer > 75) + slider_state = state2; + if (resume_time == 0) + { + seek_rtn = 0; + slider_state = state5; + } + draw_slider(slider_ypos, play_time, resume_time); + break; + case state2: + if ( (seek_rtn = seek_PTS(in_file, resume_time, 1)) >= 0) + slider_state = state3; + else if (seek_rtn == -101) + { + slider_state = state0; + thumbDelayTimer = *(rb->current_tick); + } + else + slider_state = state4; + break; + case state3: + display_thumb(in_file); + draw_slider(slider_ypos, play_time, resume_time); + slider_state = state4; + break; + case state4: + draw_slider(slider_ypos, play_time, resume_time); + slider_state = state5; + break; + case state5: + break; + } + } + + return resume_time; +} + +int mpeg_start_menu(int play_time, int in_file) +{ + int m; + int result = 0; + int menu_quit = 0; + + /* add the resume time to the menu display */ + char resume_str[32]; + int time_hol = (int)(settings.resume_time/2); + int time_rem = ((settings.resume_time%2)==0) ? 0 : 5; + rb->snprintf(resume_str, sizeof(resume_str), + "Resume time (min): %d.%d", time_hol, time_rem); + + struct menu_item items[] = { + { "Play from beginning", NULL }, + { resume_str, NULL }, + { "Set start time (min)", NULL }, + { "Quit mpegplayer", NULL }, + }; + + m = menu_init(rb, items, sizeof(items) / sizeof(*items), + NULL, NULL, NULL, NULL); + + rb->button_clear_queue(); + + while(menu_quit == 0) + { + result = menu_show(m); + + switch (result) + { + case 0: + menu_quit = 1; + result = 0; + break; + case 1: + menu_quit = 1; + result = settings.resume_time; + break; + case 2: +#ifndef HAVE_LCD_COLOR + gray_show(true); +#endif + if ((result = get_start_time(play_time, in_file)) >= 0) + menu_quit = 1; +#ifndef HAVE_LCD_COLOR + gray_show(false); +#endif + break; + case 3: + menu_quit = 1; + result = -1; + break; + default: + if (result == MENU_ATTACHED_USB) + { + menu_quit = 1; + result = -1; + } + break; + } + } + menu_exit(m); + + settings.resume_time = result; + return (int)result; +} + +void clear_resume_count(void) +{ + configfile_save(SETTINGS_FILENAME, config, + sizeof(config)/sizeof(*config), + SETTINGS_VERSION); + + settings.resume_count = 0; + + /* add this place holder so the count is above resume entries */ + configfile_update_entry(SETTINGS_FILENAME, "Resume count", 0); +} + bool mpeg_menu(void) { int m; int result; int menu_quit=0; - static const struct menu_item items[] = { + /* add the clear resume option to the menu display */ + char clear_str[32]; + rb->snprintf(clear_str, sizeof(clear_str), + "Clear all resumes: %u", settings.resume_count); + + struct menu_item items[] = { #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) [MPEG_OPTION_DISPLAY_SETTINGS] = { "Display Options", NULL }, @@ -85,6 +402,8 @@ bool mpeg_menu(void) { "Limit FPS", NULL }, [MPEG_OPTION_SKIP_FRAMES] = { "Skip frames", NULL }, + [MPEG_OPTION_CLEAR_RESUMES] = + { clear_str, NULL }, [MPEG_OPTION_QUIT] = { "Quit mpegplayer", NULL }, }; @@ -115,6 +434,11 @@ bool mpeg_menu(void) rb->set_option("Skip frames",&settings.skipframes,INT, noyes, 2, NULL); break; + case MPEG_OPTION_CLEAR_RESUMES: + clear_resume_count(); + rb->snprintf(clear_str, sizeof(clear_str), + "Clear all resumes: %u", 0); + break; case MPEG_OPTION_QUIT: default: menu_quit=1; @@ -132,48 +456,82 @@ bool mpeg_menu(void) return (result==MPEG_OPTION_QUIT); } - -void init_settings(void) +void init_settings(const char* filename) { /* Set the default settings */ settings.showfps = 0; /* Do not show FPS */ settings.limitfps = 1; /* Limit FPS */ settings.skipframes = 1; /* Skip frames */ + settings.resume_count = -1; #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) settings.displayoptions = 0; /* No visual effects */ #endif configfile_init(rb); - if (configfile_load(SETTINGS_FILENAME, config, - sizeof(config)/sizeof(*config), - SETTINGS_MIN_VERSION - ) < 0) + /* If the config file don't contain resume count + or the load fails, then rebuild the config file. + This eliminates the worry for older config files + having unused data. */ + if (((settings.resume_count = configfile_get_value + (SETTINGS_FILENAME, "Resume count")) < 0) || + (configfile_load(SETTINGS_FILENAME, config, + sizeof(config)/sizeof(*config), + SETTINGS_MIN_VERSION) < 0)) { - /* If the loading failed, save a new config file (as the disk is - already spinning) */ + /* Generate a new config file with default values */ configfile_save(SETTINGS_FILENAME, config, sizeof(config)/sizeof(*config), SETTINGS_VERSION); } - /* Keep a copy of the saved version of the settings - so we can check if - the settings have changed when we quit */ - old_settings = settings; #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) + if ((settings.displayoptions = + configfile_get_value(SETTINGS_FILENAME, "Display options")) < 0) + { + configfile_update_entry(SETTINGS_FILENAME, "Display options", + (settings.displayoptions=0)); + } rb->lcd_yuv_set_options(settings.displayoptions); #endif + + if (settings.resume_count < 0) + { + settings.resume_count = 0; + + /* add this place holder so the count is above resume entries */ + configfile_update_entry(SETTINGS_FILENAME, "Resume count", 0); + } + + rb->strcpy(settings.resume_filename, filename); + + /* get the resume time for the current mpeg if it exist */ + if ((settings.resume_time = configfile_get_value + (SETTINGS_FILENAME, filename)) < 0) + { + settings.resume_time = 0; + } } void save_settings(void) { - /* Save the user settings if they have changed */ - if (rb->memcmp(&settings,&old_settings,sizeof(settings))!=0) { - configfile_save(SETTINGS_FILENAME, config, - sizeof(config)/sizeof(*config), - SETTINGS_VERSION); + configfile_update_entry(SETTINGS_FILENAME, "Show FPS", + settings.showfps); + configfile_update_entry(SETTINGS_FILENAME, "Limit FPS", + settings.limitfps); + configfile_update_entry(SETTINGS_FILENAME, "Skip frames", + settings.skipframes); - /* Store the settings in old_settings - to check for future changes */ - old_settings = settings; + /* If this was a new resume entry then update the total resume count */ + if (configfile_update_entry(SETTINGS_FILENAME, settings.resume_filename, + settings.resume_time) == 0) + { + configfile_update_entry(SETTINGS_FILENAME, "Resume count", + ++settings.resume_count); } + +#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) + configfile_update_entry(SETTINGS_FILENAME, "Display options", + settings.displayoptions); +#endif } diff --git a/apps/plugins/mpegplayer/mpeg_settings.h b/apps/plugins/mpegplayer/mpeg_settings.h index 7721c46f6..690667f63 100644 --- a/apps/plugins/mpegplayer/mpeg_settings.h +++ b/apps/plugins/mpegplayer/mpeg_settings.h @@ -2,16 +2,23 @@ #include "plugin.h" struct mpeg_settings { - int showfps; - int limitfps; - int skipframes; + int showfps; /* flag to display fps */ + int limitfps; /* flag to limit fps */ + int skipframes; /* flag to skip frames */ + int resume_count; /* total # of resumes in config file */ + int resume_time; /* resume time for current mpeg (in half minutes) */ + char resume_filename[128]; /* filename of current mpeg */ + #if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) - unsigned displayoptions; + int displayoptions; #endif }; extern struct mpeg_settings settings; +int get_start_time(int play_time, int in_file); +int mpeg_start_menu(int play_time, int in_file); bool mpeg_menu(void); -void init_settings(void); +void init_settings(const char* filename); void save_settings(void); +void clear_resume_count(void); diff --git a/apps/plugins/mpegplayer/mpegplayer.c b/apps/plugins/mpegplayer/mpegplayer.c index 54fdf0535..128eb268a 100644 --- a/apps/plugins/mpegplayer/mpegplayer.c +++ b/apps/plugins/mpegplayer/mpegplayer.c @@ -110,6 +110,7 @@ FPS | 27Mhz | 100Hz | 44.1KHz | 48KHz #include "mpeg_settings.h" #include "video_out.h" #include "../../codecs/libmad/mad.h" +#include "splash.h" PLUGIN_HEADER PLUGIN_IRAM_DECLARE @@ -198,11 +199,8 @@ typedef struct uint8_t* curr_packet_end; /* Current stream packet end */ uint8_t* prev_packet; /* Previous stream packet beginning */ - uint8_t* next_packet; /* Next stream packet beginning */ - - size_t guard_bytes; /* Number of bytes in guardbuf used */ - uint64_t buffer_tail; /* Accumulation of bytes added */ - uint64_t buffer_head; /* Accumulation of bytes removed */ + size_t prev_packet_length; /* Lenth of previous packet */ + size_t buffer_remaining; /* How much data is left in the buffer */ uint32_t curr_pts; /* Current presentation timestamp */ uint32_t curr_time; /* Current time in samples */ uint32_t tagged; /* curr_pts is valid */ @@ -301,8 +299,7 @@ static intptr_t str_send_msg(Stream *str, int id, intptr_t data) return str->dispatch_fn(str, msg); #endif - /* Only one thread at a time, please - only one core may safely send - right now */ + /* Only one thread at a time, please */ rb->spinlock_lock(&str->msg_lock); str->ev.id = id; @@ -333,13 +330,62 @@ static intptr_t str_send_msg(Stream *str, int id, intptr_t data) /* NOTE: Putting the following variables in IRAM cause audio corruption on the ipod (reason unknown) */ -static uint8_t *disk_buf IBSS_ATTR; -static uint8_t *disk_buf_end IBSS_ATTR; -static uint8_t *disk_buf_tail IBSS_ATTR; -static size_t buffer_size IBSS_ATTR; +static uint8_t *disk_buf_start IBSS_ATTR; /* Start pointer */ +static uint8_t *disk_buf_end IBSS_ATTR; /* End of buffer pointer less + MPEG_GUARDBUF_SIZE. The + guard space is used to wrap + data at the buffer start to + pass continuous data + packets */ +static uint8_t *disk_buf_tail IBSS_ATTR; /* Location of last data + 1 + filled into the buffer */ +static size_t disk_buf_size IBSS_ATTR; /* The total buffer length + including the guard + space */ +static size_t file_remaining IBSS_ATTR; + +#if NUM_CORES > 1 +/* Some stream variables are shared between cores */ +struct mutex stream_lock IBSS_ATTR; +static inline void init_stream_lock(void) + { rb->spinlock_init(&stream_lock); } +static inline void lock_stream(void) + { rb->spinlock_lock(&stream_lock); } +static inline void unlock_stream(void) + { rb->spinlock_unlock(&stream_lock); } +#else +/* No RMW issue here */ +static inline void init_stream_lock(void) + { } +static inline void lock_stream(void) + { } +static inline void unlock_stream(void) + { } +#endif + +static int audio_sync_start IBSS_ATTR; /* If 0, the audio thread + yields waiting on the video + thread to synchronize with + the stream */ +static uint32_t audio_sync_time IBSS_ATTR; /* The time that the video + thread has reached after + synchronizing. The + audio thread now needs + to advance to this + time */ +static int video_sync_start IBSS_ATTR; /* While 0, the video thread + yields until the audio + thread has reached the + audio_sync_time */ +static int video_thumb_print IBSS_ATTR; /* If 1, the video thread is + only decoding one frame for + use in the menu. If 0, + normal operation */ +static int play_time IBSS_ATTR; /* The movie time as represented by + the maximum audio PTS tag in the + stream converted to half minutes */ +char *filename; /* hack for resume time storage */ -#define MSG_BUFFER_NEARLY_EMPTY 1 -#define MSG_EXIT_REQUESTED 2 /* Various buffers */ /* TODO: Can we reduce the PCM buffer size? */ @@ -350,7 +396,7 @@ static size_t buffer_size IBSS_ATTR; #define LIBMPEG2BUFFER_SIZE (2*1024*1024) /* 65536+6 is required since each PES has a 6 byte header with a 16 bit packet length field */ -#define MPEG_GUARDBUF_SIZE (64*1024+1024) /* Keep a bit extra - excessive for now */ +#define MPEG_GUARDBUF_SIZE (65*1024) /* Keep a bit extra - excessive for now */ #define MPEG_LOW_WATERMARK (1024*1024) static void pcm_playback_play_pause(bool play); @@ -471,8 +517,47 @@ static void init_mad(void* mad_frame_overlap) ((p)[b3] << 6) | \ ((p)[b4] >> 2 ))) -/* This function demuxes the streams and gives the next stream data pointer */ -static void get_next_data( Stream* str ) +/* This function synchronizes the mpeg stream. The function returns + true on error */ +bool sync_data_stream(uint8_t **p) +{ + for (;;) + { + while ( !CMP_4_CONST(*p, PACK_START_CODE) && (*p) < disk_buf_tail ) + (*p)++; + if ( (*p) >= disk_buf_tail ) + break; + uint8_t *p_save = (*p); + if ( ((*p)[4] & 0xc0) == 0x40 ) /* mpeg-2 */ + (*p) += 14 + ((*p)[13] & 7); + else if ( ((*p)[4] & 0xf0) == 0x20 ) /* mpeg-1 */ + (*p) += 12; + else + (*p) += 5; + if ( (*p) >= disk_buf_tail ) + break; + if ( CMP_3_CONST(*p, PACKET_START_CODE_PREFIX) ) + { + (*p) = p_save; + break; + } + else + (*p) = p_save+1; + } + + if ( (*p) >= disk_buf_tail ) + return true; + else + return false; +} + +/* This function demuxes the streams and gives the next stream data + pointer. Type 0 is normal operation. Type 1 and 2 have been added + for rapid seeks into the data stream. Type 1 and 2 ignore the + video_sync_start state (a signal to yield for refilling the + buffer). Type 1 will append more data to the buffer tail (minumal + bufer size reads that are increased only as needed). */ +static int get_next_data( Stream* str, uint8_t type ) { uint8_t *p; uint8_t *header; @@ -481,30 +566,49 @@ static void get_next_data( Stream* str ) static int mpeg1_skip_table[16] = { 0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - if (str->curr_packet_end == NULL) - { - /* What does this do? */ - while ((p = disk_buf) == NULL) - { - rb->lcd_putsxy(0,LCD_HEIGHT-10,"FREEZE!"); - rb->lcd_update(); - rb->sleep(HZ); - } - } - else - { - p = str->curr_packet_end; - } + if ( (p=str->curr_packet_end) == NULL) + p = disk_buf_start; while (1) { int length, bytes; - if (p >= disk_buf_end) + /* Yield for buffer filling */ + if ( (type == 0) && (str->buffer_remaining < 120*1024) && (file_remaining > 0) ) + while ( (str->buffer_remaining < 512*1024) && (file_remaining > 0) ) + rb->yield(); + + /* The packet start position (plus an arbitrary header length) + has exceeded the amount of data in the buffer */ + if ( type == 1 && (p+50) >= disk_buf_tail ) { - p = disk_buf + (p - disk_buf_end); + DEBUGF("disk buffer overflow\n"); + return 1; } + /* are we at the end of file? */ + { + size_t tmp_length; + if (p < str->prev_packet) + tmp_length = (disk_buf_end - str->prev_packet) + + (p - disk_buf_start); + else + tmp_length = (p - str->prev_packet); + if (0 == str->buffer_remaining-tmp_length-str->prev_packet_length) + { + str->curr_packet_end = str->curr_packet = NULL; + break; + } + } + + /* wrap the disk buffer */ + if (p >= disk_buf_end) + p = disk_buf_start + (p - disk_buf_end); + + /* wrap packet header if needed */ + if ( (p+50) >= disk_buf_end ) + rb->memcpy(disk_buf_end, disk_buf_start, 50); + /* Pack header, skip it */ if (CMP_4_CONST(p, PACK_START_CODE)) { @@ -521,7 +625,6 @@ static void get_next_data( Stream* str ) rb->splash( 30, "Weird Pack header!" ); p += 5; } - /*rb->splash( 30, "Pack header" );*/ } /* System header, parse and skip it - four bytes */ @@ -535,29 +638,29 @@ static void get_next_data( Stream* str ) p += header_length; - if (p >= disk_buf_end) - { - p = disk_buf + (p - disk_buf_end); - } - /*rb->splash( 30, "System header" );*/ + if ( p >= disk_buf_end ) + p = disk_buf_start + (p - disk_buf_end); } - + /* Packet header, parse it */ if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX)) { /* Problem */ - //rb->splash( HZ*3, "missing packet start code prefix : %X%X at %X", *p, *(p+2), p-disk_buf ); + rb->splash( HZ*3, "missing packet start code prefix : %X%X at %lX", + *p, *(p+2), (long unsigned int)(p-disk_buf_start) ); + + DEBUGF("end diff: %X,%X,%X,%X,%X,%X\n",(int)str->curr_packet_end, + (int)audio_str.curr_packet_end,(int)video_str.curr_packet_end, + (int)disk_buf_start,(int)disk_buf_end,(int)disk_buf_tail); + str->curr_packet_end = str->curr_packet = NULL; break; - //++p; - //break; } /* We retrieve basic infos */ stream = p[3]; length = (p[4] << 8) | p[5]; - /*rb->splash( 100, "Stream : %X", stream );*/ if (stream != str->id) { /* End of stream ? */ @@ -618,11 +721,9 @@ static void get_next_data( Stream* str ) break; } } - - if ((header[length - 1] & 0xc0) == 0x40) - { + + if ( (header[length - 1] & 0xc0) == 0x40 ) length += 2; - } len_skip = length; length += mpeg1_skip_table[header[length - 1] >> 4]; @@ -657,20 +758,19 @@ static void get_next_data( Stream* str ) if (bytes > 0) { str->curr_packet_end = p + bytes; - //DEBUGF("prev = %d, curr = %d\n",str->prev_packet,str->curr_packet); if (str->curr_packet != NULL) { + lock_stream(); + + str->buffer_remaining -= str->prev_packet_length; if (str->curr_packet < str->prev_packet) - { - str->buffer_head += (disk_buf_end - str->prev_packet) + - (str->curr_packet - disk_buf); - str->guard_bytes = 0; - } + str->prev_packet_length = (disk_buf_end - str->prev_packet) + + (str->curr_packet - disk_buf_start); else - { - str->buffer_head += (str->curr_packet - str->prev_packet); - } + str->prev_packet_length = (str->curr_packet - str->prev_packet); + + unlock_stream(); str->prev_packet = str->curr_packet; } @@ -678,14 +778,13 @@ static void get_next_data( Stream* str ) str->curr_packet = p; if (str->curr_packet_end > disk_buf_end) - { - str->guard_bytes = str->curr_packet_end - disk_buf_end; - rb->memcpy(disk_buf_end, disk_buf, str->guard_bytes); - } + rb->memcpy(disk_buf_end, disk_buf_start, + str->curr_packet_end - disk_buf_end ); } break; } /* end while */ + return 0; } /* Our clock rate in ticks/second - this won't be a constant for long */ @@ -943,6 +1042,8 @@ static int button_loop(void) int vol, minvol, maxvol; int button; + if (video_sync_start==1) { + if (str_have_msg(&audio_str)) { struct event ev; @@ -1014,6 +1115,7 @@ static int button_loop(void) rb->lcd_setfont(FONT_SYSFIXED); if (result) { + settings.resume_time = (int)(get_stream_time()/44100/30); str_send_msg(&video_str, STREAM_QUIT, 0); audio_str.status = STREAM_STOPPED; } else { @@ -1024,6 +1126,7 @@ static int button_loop(void) break; case MPEG_STOP: + settings.resume_time = (int)(get_stream_time()/44100/30); str_send_msg(&video_str, STREAM_QUIT, 0); audio_str.status = STREAM_STOPPED; break; @@ -1060,7 +1163,7 @@ static int button_loop(void) audio_str.status = STREAM_STOPPED; } } - + } quit: return audio_str.status; } @@ -1086,7 +1189,23 @@ static void audio_thread(void) pcm_playback_play(0); /* Get first packet */ - get_next_data(&audio_str); + get_next_data(&audio_str, 0 ); + + /* skip audio packets here */ + while (audio_sync_start==0) + { + audio_str.status = STREAM_PLAYING; + rb->yield(); + } + + if (audio_sync_time>10000) + { + while (TS_TO_TICKS(audio_str.curr_pts) < audio_sync_time - 10000) + { + get_next_data(&audio_str, 0 ); + rb->priority_yield(); + } + } if (audio_str.curr_packet == NULL) goto done; @@ -1165,7 +1284,7 @@ static void audio_thread(void) mpabuf = mpa_buffer; /* Get data from next audio packet */ - get_next_data(&audio_str); + get_next_data(&audio_str, 0 ); } while (audio_str.curr_packet != NULL && mpabuf_used < MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD); @@ -1198,8 +1317,6 @@ static void audio_thread(void) if (mad_stat != 0) { - DEBUGF("Audio stream error - %d\n", stream.error); - if (stream.error == MAD_FLAG_INCOMPLETE || stream.error == MAD_ERROR_BUFLEN) { @@ -1259,6 +1376,13 @@ static void audio_thread(void) rb->priority_yield(); } + if (video_sync_start == 0 && + pts->pts+(uint32_t)synth.pcm.lengthyield(); + } + /* TODO: This part will be replaced with dsp calls soon */ if (MAD_NCHANNELS(&frame.header) == 2) { @@ -1305,6 +1429,7 @@ static void audio_thread(void) audio_str.status = STREAM_PLAYING; pcmbuf_threshold = PCMBUF_PLAY_ALL; pcm_playback_seek_time(pcmbuf_tail->time); + video_sync_start = 1; } /* Make this data available to DMA */ @@ -1391,29 +1516,32 @@ static void video_thread(void) /* Clear the display - this is mainly just to indicate that the video thread has started successfully. */ - rb->lcd_clear_display(); - rb->lcd_update(); + if (!video_thumb_print) + { + rb->lcd_clear_display(); + rb->lcd_update(); + } /* Request the first packet data */ - get_next_data( &video_str ); + get_next_data( &video_str, 0 ); if (video_str.curr_packet == NULL) - goto done; + goto video_thread_quit; mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); total_offset += video_str.curr_packet_end - video_str.curr_packet; info = mpeg2_info (mpeg2dec); - /* Wait if the audio thread is buffering - i.e. before - the first frames are decoded */ - while (audio_str.status == STREAM_BUFFERING) - rb->priority_yield(); - while (1) { /* quickly check mailbox first */ - if (str_have_msg(&video_str)) + if (video_thumb_print) + { + if (video_str.status == STREAM_STOPPED) + break; + } + else if (str_have_msg(&video_str)) { while (1) { @@ -1450,7 +1578,8 @@ static void video_thread(void) { case STATE_BUFFER: /* Request next packet data */ - get_next_data( &video_str ); + get_next_data( &video_str, 0 ); + mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); total_offset += video_str.curr_packet_end - video_str.curr_packet; info = mpeg2_info (mpeg2dec); @@ -1458,7 +1587,7 @@ static void video_thread(void) if (video_str.curr_packet == NULL) { /* No more data. */ - goto done; + goto video_thread_quit; } continue; @@ -1528,8 +1657,12 @@ static void video_thread(void) break; /* No limiting => no dropping - draw this frame */ - if (!settings.limitfps) + if (!settings.limitfps && (video_thumb_print == 0)) + { + audio_sync_start = 1; + video_sync_start = 1; goto picture_draw; + } /* Get presentation times in audio samples - quite accurate enough - add previous frame duration if not stamped */ @@ -1538,7 +1671,19 @@ static void video_thread(void) period = TIME_TO_TICKS(info->sequence->frame_period); + if ( (video_thumb_print == 1 || video_sync_start == 0) && + ((int)(info->current_picture->flags & PIC_MASK_CODING_TYPE) + == PIC_FLAG_CODING_TYPE_B)) + break; + eta_video = curr_time; + + audio_sync_time = eta_video; + audio_sync_start = 1; + + while (video_sync_start == 0) + rb->yield(); + eta_audio = get_stream_time(); /* How early/late are we? > 0 = late, < 0 early */ @@ -1664,32 +1809,39 @@ static void video_thread(void) picture_wait: /* Wait until audio catches up */ - while (eta_video > eta_audio) - { - rb->priority_yield(); - - /* Make sure not to get stuck waiting here forever */ - if (str_have_msg(&video_str)) + if (video_thumb_print) + video_str.status = STREAM_STOPPED; + else + while (eta_video > eta_audio) { - str_look_msg(&video_str, &ev); - - /* If not to play, process up top */ - if (ev.id != STREAM_PLAY) - goto rendering_finished; + rb->priority_yield(); + + /* Make sure not to get stuck waiting here forever */ + if (str_have_msg(&video_str)) + { + str_look_msg(&video_str, &ev); + + /* If not to play, process up top */ + if (ev.id != STREAM_PLAY) + goto rendering_finished; + + /* Told to play but already playing */ + str_get_msg(&video_str, &ev); + str_reply_msg(&video_str, 1); + } - /* Told to play but already playing */ - str_get_msg(&video_str, &ev); - str_reply_msg(&video_str, 1); + eta_audio = get_stream_time(); } - - eta_audio = get_stream_time(); - } - + picture_draw: /* Record last frame time */ last_render = *rb->current_tick; - vo_draw_frame(info->display_fbuf->buf); + if (video_thumb_print) + vo_draw_frame_thumb(info->display_fbuf->buf); + else + vo_draw_frame(info->display_fbuf->buf); + num_drawn++; picture_skip: @@ -1724,53 +1876,298 @@ static void video_thread(void) rb->yield(); } -done: -#if NUM_CORES > 1 - flush_icache(); -#endif + video_thread_quit: + /* if video ends before time sync'd, + besure the audio thread is closed */ + if (video_sync_start == 0) + { + audio_str.status = STREAM_STOPPED; + audio_sync_start = 1; + } - video_str.status = STREAM_DONE; + #if NUM_CORES > 1 + flush_icache(); + #endif + + mpeg2_close (mpeg2dec); + + /* Commit suicide */ + video_str.status = STREAM_TERMINATED; +} - while (1) - { - str_get_msg(&video_str, &ev); +void initialize_stream( Stream *str, uint8_t *buffer_start, size_t disk_buf_len, int id ) +{ + str->curr_packet_end = str->curr_packet = NULL; + str->prev_packet_length = 0; + str->prev_packet = str->curr_packet_end = buffer_start; + str->buffer_remaining = disk_buf_len; + str->id = id; +} + +void display_thumb(int in_file) +{ + size_t disk_buf_len; - if (ev.id == STREAM_QUIT) - break; + video_thumb_print = 1; + audio_sync_start = 1; + video_sync_start = 1; + + disk_buf_len = rb->read (in_file, disk_buf_start, disk_buf_size - MPEG_GUARDBUF_SIZE); + disk_buf_tail = disk_buf_start + disk_buf_len; + file_remaining = 0; + initialize_stream(&video_str,disk_buf_start,disk_buf_len,0xe0); + + video_str.status = STREAM_PLAYING; - str_reply_msg(&video_str, 0); + if ((video_str.thread = rb->create_thread(video_thread, + (uint8_t*)video_stack,VIDEO_STACKSIZE,"mpgvideo" + IF_PRIO(,PRIORITY_PLAYBACK) + IF_COP(, COP, true))) == NULL) + { + rb->splash(HZ, "Cannot create video thread!"); + } + else + { + while (video_str.status != STREAM_TERMINATED) + rb->yield(); } -video_thread_quit: - /* Commit suicide */ - video_str.status = STREAM_TERMINATED; + if ( video_str.curr_packet_end == video_str.curr_packet) + rb->splash(0, "frame not available"); } +int find_length( int in_file ) +{ + uint8_t *p; + size_t read_length = 60*1024; + size_t disk_buf_len; + + play_time = 0; + + /* temporary read buffer size cannot exceed buffer size */ + if ( read_length > disk_buf_size ) + read_length = disk_buf_size; + + /* read tail of file */ + rb->lseek( in_file, -1*read_length, SEEK_END ); + disk_buf_len = rb->read( in_file, disk_buf_start, read_length ); + disk_buf_tail = disk_buf_start + disk_buf_len; + + /* sync reader to this segment of the stream */ + p=disk_buf_start; + if (sync_data_stream(&p)) + { + DEBUGF("Could not sync stream\n"); + return PLUGIN_ERROR; + } + + /* find last PTS in audio stream; will movie always have audio? if + the play time can not be determined, set play_time to 0 */ + audio_sync_start = 0; + audio_sync_time = 0; + video_sync_start = 0; + { + Stream tmp; + initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); + + do + { + get_next_data(&tmp, 2); + if (tmp.tagged == 1) + /* 10 sec less to insure the video frame exist */ + play_time = (int)((tmp.curr_pts/45000-10)/30); + } + while (tmp.curr_packet_end != NULL); + } + return 0; +} + +ssize_t seek_PTS( int in_file, int start_time, int accept_button ) +{ + static ssize_t last_seek_pos = 0; + static int last_start_time = 0; + ssize_t seek_pos; + size_t disk_buf_len; + uint8_t *p; + size_t read_length = 60*1024; + + /* temporary read buffer size cannot exceed buffer size */ + if ( read_length > disk_buf_size ) + read_length = disk_buf_size; + + if ( start_time == last_start_time ) + { + seek_pos = last_seek_pos; + rb->lseek(in_file,seek_pos,SEEK_SET); + } + else if ( start_time != 0 ) + { + seek_pos = rb->filesize(in_file)*start_time/play_time; + int seek_pos_sec_inc = rb->filesize(in_file)/play_time/30; + + if (seek_pos<0) + seek_pos=0; + if ((size_t)seek_pos > rb->filesize(in_file) - read_length) + seek_pos = rb->filesize(in_file) - read_length; + rb->lseek( in_file, seek_pos, SEEK_SET ); + disk_buf_len = rb->read( in_file, disk_buf_start, read_length ); + disk_buf_tail = disk_buf_start + disk_buf_len; + + /* sync reader to this segment of the stream */ + p=disk_buf_start; + if (sync_data_stream(&p)) + { + DEBUGF("Could not sync stream\n"); + return PLUGIN_ERROR; + } + + /* find PTS >= start_time */ + audio_sync_start = 0; + audio_sync_time = 0; + video_sync_start = 0; + { + Stream tmp; + initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); + int cont_seek_loop = 1; + int coarse_seek = 1; + do + { + if ( accept_button ) + { + rb->yield(); + if (rb->button_available()) + return -101; + } + + while ( get_next_data(&tmp, 1) == 1 ) + { + if ( tmp.curr_packet_end == disk_buf_start ) + seek_pos += disk_buf_tail - disk_buf_start; + else + seek_pos += tmp.curr_packet_end - disk_buf_start; + if ((size_t)seek_pos > rb->filesize(in_file) - read_length) + seek_pos = rb->filesize(in_file) - read_length; + rb->lseek( in_file, seek_pos, SEEK_SET ); + disk_buf_len = rb->read ( in_file, disk_buf_start, read_length ); + disk_buf_tail = disk_buf_start + disk_buf_len; + + /* sync reader to this segment of the stream */ + p=disk_buf_start; + initialize_stream(&tmp,p,disk_buf_len,0xc0); + } + + /* are we after start_time in the stream? */ + if ( coarse_seek && (int)(tmp.curr_pts/45000) >= start_time*30 ) + { + int time_to_backup = (int)(tmp.curr_pts/45000) - start_time*30; + if (time_to_backup == 0) + time_to_backup++; + seek_pos -= seek_pos_sec_inc * time_to_backup; + seek_pos_sec_inc -= seek_pos_sec_inc/20; /* for stability */ + if (seek_pos<0) + seek_pos=0; + if ((size_t)seek_pos > rb->filesize(in_file) - read_length) + seek_pos = rb->filesize(in_file) - read_length; + rb->lseek( in_file, seek_pos, SEEK_SET ); + disk_buf_len = rb->read( in_file, disk_buf_start, read_length ); + disk_buf_tail = disk_buf_start + disk_buf_len; + + /* sync reader to this segment of the stream */ + p=disk_buf_start; + if (sync_data_stream(&p)) + { + DEBUGF("Could not sync stream\n"); + return PLUGIN_ERROR; + } + initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); + continue; + } + + /* are we well before start_time in the stream? */ + if ( coarse_seek && start_time*30 - (int)(tmp.curr_pts/45000) > 2 ) + { + int time_to_advance = start_time*30 - (int)(tmp.curr_pts/45000) - 2; + if (time_to_advance <= 0) + time_to_advance = 1; + seek_pos += seek_pos_sec_inc * time_to_advance; + if (seek_pos<0) + seek_pos=0; + if ((size_t)seek_pos > rb->filesize(in_file) - read_length) + seek_pos = rb->filesize(in_file) - read_length; + rb->lseek( in_file, seek_pos, SEEK_SET ); + disk_buf_len = rb->read ( in_file, disk_buf_start, read_length ); + disk_buf_tail = disk_buf_start + disk_buf_len; + + /* sync reader to this segment of the stream */ + p=disk_buf_start; + if (sync_data_stream(&p)) + { + DEBUGF("Could not sync stream\n"); + return PLUGIN_ERROR; + } + initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); + continue; + } + + coarse_seek = 0; + + /* are we at start_time in the stream? */ + if ( (int)(tmp.curr_pts/45000) >= start_time*30 ) + cont_seek_loop = 0; + + } + while ( cont_seek_loop ); + + + DEBUGF("start diff: %u %u\n",(unsigned int)(tmp.curr_pts/45000),start_time*30); + seek_pos+=tmp.curr_packet_end-disk_buf_start; + + last_seek_pos = seek_pos; + last_start_time = start_time; + + rb->lseek(in_file,seek_pos,SEEK_SET); + } + } + else + { + seek_pos = 0; + rb->lseek(in_file,0,SEEK_SET); + last_seek_pos = seek_pos; + last_start_time = start_time; + } + return seek_pos; +} + enum plugin_status plugin_start(struct plugin_api* api, void* parameter) { int status = PLUGIN_ERROR; /* assume failure */ + int start_time=-1; void* audiobuf; ssize_t audiosize; int in_file; - uint8_t* buffer; - size_t file_remaining; size_t disk_buf_len; + ssize_t seek_pos; #ifndef HAVE_LCD_COLOR long graysize; int grayscales; #endif + audio_sync_start = 0; + audio_sync_time = 0; + video_sync_start = 0; + if (parameter == NULL) { api->splash(HZ*2, "No File"); - return PLUGIN_ERROR; } /* Initialize IRAM - stops audio and voice as well */ PLUGIN_IRAM_INIT(api) rb = api; + rb->splash(0, "loading ..."); + /* sets audiosize and returns buffer pointer */ audiobuf = rb->plugin_get_audio_buffer(&audiosize); #if INPUT_SRC_CAPS != 0 @@ -1781,49 +2178,38 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) rb->pcm_set_frequency(SAMPR_44); - /* Set disk pointers to NULL */ - disk_buf_end = disk_buf = NULL; - - /* Stream construction */ - /* We take the first stream of each (audio and video) */ - /* TODO : Search for these in the file first */ - audio_str.curr_packet_end = audio_str.curr_packet = audio_str.next_packet = NULL; - video_str = audio_str; - video_str.id = 0xe0; - audio_str.id = 0xc0; +#ifndef HAVE_LCD_COLOR + /* initialize the grayscale buffer: 32 bitplanes for 33 shades of gray. */ + grayscales = gray_init(rb, audiobuf, audiosize, false, LCD_WIDTH, LCD_HEIGHT, + 32, 2<<8, &graysize) + 1; + audiobuf += graysize; + audiosize -= graysize; + if (grayscales < 33 || audiosize <= 0) + { + rb->splash(HZ, "gray buf error"); + return PLUGIN_ERROR; + } +#endif /* Initialise our malloc buffer */ - audiosize = mpeg_alloc_init(audiobuf, audiosize, LIBMPEG2BUFFER_SIZE); + audiosize = mpeg_alloc_init(audiobuf,audiosize, LIBMPEG2BUFFER_SIZE); if (audiosize == 0) return PLUGIN_ERROR; + /* Set disk pointers to NULL */ + disk_buf_end = disk_buf_start = NULL; + /* Grab most of the buffer for the compressed video - leave some for PCM audio data and some for libmpeg2 malloc use. */ - buffer_size = audiosize - (PCMBUFFER_SIZE+PCMBUFFER_GUARD_SIZE+ + disk_buf_size = audiosize - (PCMBUFFER_SIZE+PCMBUFFER_GUARD_SIZE+ MPABUF_SIZE); - DEBUGF("audiosize=%ld, buffer_size=%ld\n",audiosize,buffer_size); - buffer = mpeg_malloc(buffer_size,-1); + DEBUGF("audiosize=%ld, disk_buf_size=%ld\n",audiosize,disk_buf_size); + disk_buf_start = mpeg_malloc(disk_buf_size,-1); - if (buffer == NULL) + if (disk_buf_start == NULL) return PLUGIN_ERROR; -#ifndef HAVE_LCD_COLOR - /* initialize the grayscale buffer: 32 bitplanes for 33 shades of gray. */ - grayscales = gray_init(rb, buffer, buffer_size, false, LCD_WIDTH, LCD_HEIGHT, - 32, 2<<8, &graysize) + 1; - buffer += graysize; - buffer_size -= graysize; - if (grayscales < 33 || buffer_size <= 0) - { - rb->splash(HZ, "gray buf error"); - return PLUGIN_ERROR; - } -#endif - - buffer_size &= ~(0x7ff); /* Round buffer down to nearest 2KB */ - DEBUGF("audiosize=%ld, buffer_size=%ld\n",audiosize,buffer_size); - if (!init_mpabuf()) return PLUGIN_ERROR; @@ -1836,9 +2222,10 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) in_file = rb->open((char*)parameter,O_RDONLY); if (in_file < 0){ - //fprintf(stderr,"Could not open %s\n",argv[1]); + DEBUGF("Could not open %s\n",(char*)parameter); return PLUGIN_ERROR; } + filename = (char*)parameter; #ifdef HAVE_LCD_COLOR rb->lcd_set_backdrop(NULL); @@ -1860,34 +2247,51 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) cannot just return PLUGIN_ERROR - instead drop though to cleanup code */ - init_settings(); + init_settings((char*)parameter); /* Initialise libmad */ rb->memset(mad_frame_overlap, 0, sizeof(mad_frame_overlap)); init_mad(mad_frame_overlap); - file_remaining = rb->filesize(in_file); - disk_buf_end = buffer + buffer_size-MPEG_GUARDBUF_SIZE; + disk_buf_end = disk_buf_start + disk_buf_size-MPEG_GUARDBUF_SIZE; + + /* initalize play_time with the length (in half minutes) of the movie + zero if the time could not be determined */ + find_length( in_file ); + + /* start menu */ + start_time = mpeg_start_menu(play_time, in_file); + if ( start_time == -1 ) + return 0; + else if ( start_time < 0 ) + start_time = 0; + else if ( start_time > play_time ) + start_time = play_time; + + rb->splash(0, "loading ..."); + + /* seek start time */ + seek_pos = seek_PTS( in_file, start_time, 0 ); + + rb->lseek(in_file,seek_pos,SEEK_SET); + video_thumb_print = 0; + audio_sync_start = 0; + audio_sync_time = 0; + video_sync_start = 0; /* Read some stream data */ - disk_buf_len = rb->read (in_file, buffer, MPEG_LOW_WATERMARK); - - DEBUGF("Initial Buffering - %d bytes\n",(int)disk_buf_len); - disk_buf = buffer; - disk_buf_tail = buffer+disk_buf_len; - file_remaining -= disk_buf_len; - - audio_str.guard_bytes = 0; - audio_str.prev_packet = disk_buf; - audio_str.buffer_head = 0; - audio_str.buffer_tail = disk_buf_len; - video_str.guard_bytes = 0; - video_str.prev_packet = disk_buf; - video_str.buffer_head = 0; - video_str.buffer_tail = disk_buf_len; + disk_buf_len = rb->read (in_file, disk_buf_start, disk_buf_size - MPEG_GUARDBUF_SIZE); + + disk_buf_tail = disk_buf_start + disk_buf_len; + file_remaining = rb->filesize(in_file); + file_remaining -= disk_buf_len + seek_pos; + + initialize_stream( &video_str, disk_buf_start, disk_buf_len, 0xe0 ); + initialize_stream( &audio_str, disk_buf_start, disk_buf_len, 0xc0 ); rb->spinlock_init(&audio_str.msg_lock); rb->spinlock_init(&video_str.msg_lock); + audio_str.status = STREAM_BUFFERING; video_str.status = STREAM_PLAYING; @@ -1895,6 +2299,8 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) gray_show(true); #endif + init_stream_lock(); + #if NUM_CORES > 1 flush_icache(); #endif @@ -1914,38 +2320,52 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) } else { - //DEBUGF("START: video = %d, audio = %d\n",audio_str.buffer_remaining,video_str.buffer_remaining); rb->lcd_setfont(FONT_SYSFIXED); /* Wait until both threads have finished their work */ while ((audio_str.status >= 0) || (video_str.status >= 0)) { - size_t audio_remaining = audio_str.buffer_tail - audio_str.buffer_head; - size_t video_remaining = video_str.buffer_tail - video_str.buffer_head; + size_t audio_remaining = audio_str.buffer_remaining; + size_t video_remaining = video_str.buffer_remaining; - if (MIN(audio_remaining,video_remaining) < MPEG_LOW_WATERMARK) { + if (MIN(audio_remaining,video_remaining) < MPEG_LOW_WATERMARK) + { - size_t bytes_to_read = buffer_size - MPEG_GUARDBUF_SIZE - + size_t bytes_to_read = disk_buf_size - MPEG_GUARDBUF_SIZE - MAX(audio_remaining,video_remaining); bytes_to_read = MIN(bytes_to_read,(size_t)(disk_buf_end-disk_buf_tail)); while (( bytes_to_read > 0) && (file_remaining > 0) && - ((audio_str.status >= 0) || (video_str.status >= 0))) { - size_t n = rb->read(in_file, disk_buf_tail, MIN(32*1024,bytes_to_read)); + ((audio_str.status != STREAM_DONE) || (video_str.status != STREAM_DONE))) + { + + size_t n; + if ( video_sync_start != 0 ) + n = rb->read(in_file, disk_buf_tail, MIN(32*1024,bytes_to_read)); + else + { + n = rb->read(in_file, disk_buf_tail,bytes_to_read); + if (n==0) + rb->splash(30,"buffer fill error"); + } + bytes_to_read -= n; file_remaining -= n; - audio_str.buffer_tail += n; - video_str.buffer_tail += n; + lock_stream(); + audio_str.buffer_remaining += n; + video_str.buffer_remaining += n; + unlock_stream(); + disk_buf_tail += n; rb->yield(); } if (disk_buf_tail == disk_buf_end) - disk_buf_tail = buffer; + disk_buf_tail = disk_buf_start; } rb->sleep(HZ/10); @@ -1968,6 +2388,8 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) invalidate_icache(); #endif + vo_cleanup(); + #ifndef HAVE_LCD_COLOR gray_release(); #endif diff --git a/apps/plugins/mpegplayer/video_out.h b/apps/plugins/mpegplayer/video_out.h index 0d91eb7b1..ec3f7c65d 100644 --- a/apps/plugins/mpegplayer/video_out.h +++ b/apps/plugins/mpegplayer/video_out.h @@ -22,4 +22,6 @@ */ void vo_draw_frame (uint8_t * const * buf); +void vo_draw_frame_thumb (uint8_t * const * buf); void vo_setup (const mpeg2_sequence_t * sequence); +void vo_cleanup (void); diff --git a/apps/plugins/mpegplayer/video_out_rockbox.c b/apps/plugins/mpegplayer/video_out_rockbox.c index 2aac0b803..490f04411 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; void vo_draw_frame (uint8_t * const * buf) { #ifdef HAVE_LCD_COLOR - rb->lcd_yuv_blit(buf, - 0,0,image_width, + rb->lcd_yuv_blit(buf, 0,0,image_width, output_x,output_y,output_width,output_height); #else gray_ub_gray_bitmap_part(buf[0],0,0,image_width, @@ -60,10 +59,105 @@ void vo_draw_frame (uint8_t * const * buf) #define SCREEN_HEIGHT LCD_WIDTH #endif +uint8_t* tmpbufa = 0; +uint8_t* tmpbufb = 0; +uint8_t* tmpbufc = 0; +uint8_t* tmpbuf[3]; + +void vo_draw_frame_thumb (uint8_t * const * buf) +{ + int r,c; + +#if LCD_WIDTH >= LCD_HEIGHT + for (r=0;rlcd_clear_display(); +rb->lcd_update(); + +#ifdef HAVE_LCD_COLOR +#ifdef SIMULATOR +#if LCD_WIDTH >= LCD_HEIGHT + rb->lcd_yuv_blit(tmpbuf,0,0,image_width/2, + (LCD_WIDTH-1-image_width/2)/2, + LCD_HEIGHT-50-(image_height/2), + output_width/2,output_height/2); + +#else + rb->lcd_yuv_blit(tmpbuf,0,0,image_height/2, + LCD_HEIGHT-50-(image_height/2), + (LCD_WIDTH-1-image_width/2)/2, + output_height/2,output_width/2); +#endif +#else +#if LCD_WIDTH >= LCD_HEIGHT + rb->lcd_yuv_blit(tmpbuf,0,0,image_width/2, + (LCD_WIDTH-1-image_width/2)/2, + LCD_HEIGHT-50-(image_height/2), + output_width/2,output_height/2); +#else + rb->lcd_yuv_blit(tmpbuf,0,0,image_height/2, + LCD_HEIGHT-50-(image_height/2), + (LCD_WIDTH-1-image_width/2)/2, + output_height/2,output_width/2); +#endif +#endif +#else +#if LCD_WIDTH >= LCD_HEIGHT + gray_ub_gray_bitmap_part(tmpbuf[0],0,0,image_width/2, + (LCD_WIDTH-1-image_width/2)/2, + LCD_HEIGHT-50-(image_height/2), + output_width/2,output_height/2); +#else + gray_ub_gray_bitmap_part(tmpbuf[0],0,0,image_height/2, + LCD_HEIGHT-50-(image_height/2), + (LCD_WIDTH-1-image_width/2)/2, + output_height/2,output_width/2); +#endif +#endif +} + void vo_setup(const mpeg2_sequence_t * sequence) { image_width=sequence->width; image_height=sequence->height; + + tmpbufa = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width/2* + image_height/2, -2); + tmpbufb = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width/4* + image_height/4, -2); + tmpbufc = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width/4* + image_height/4, -2); + tmpbuf[0] = tmpbufa; + tmpbuf[1] = tmpbufb; + tmpbuf[2] = tmpbufc; + image_chroma_x=image_width/sequence->chroma_width; image_chroma_y=image_height/sequence->chroma_height; @@ -83,3 +177,13 @@ void vo_setup(const mpeg2_sequence_t * sequence) output_y = (SCREEN_HEIGHT-sequence->display_height)/2; } } + +void vo_cleanup(void) +{ + if (tmpbufc) + mpeg2_free(tmpbufc); + if (tmpbufb) + mpeg2_free(tmpbufb); + if (tmpbufa) + mpeg2_free(tmpbufa); +} diff --git a/docs/CREDITS b/docs/CREDITS index cb12c9688..2abfcb8b4 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -339,6 +339,8 @@ David Bishop Hein-Pieter van Braam Przemysław Hołubowski Stepan Moskovchenko +John S. Gwynne +Brian J. Morey The libmad team The wavpack team diff --git a/firmware/drivers/button.c b/firmware/drivers/button.c index 3daa08b2c..d79a9333f 100644 --- a/firmware/drivers/button.c +++ b/firmware/drivers/button.c @@ -293,6 +293,11 @@ static void button_boost(bool state) } #endif /* HAVE_ADJUSTABLE_CPU_FREQ */ +int button_available( void ) +{ + return queue_count(&button_queue); +} + long button_get(bool block) { struct event ev; diff --git a/firmware/export/button.h b/firmware/export/button.h index 5322d814b..881d7c424 100644 --- a/firmware/export/button.h +++ b/firmware/export/button.h @@ -27,6 +27,7 @@ extern struct event_queue button_queue; void button_init (void); +int button_available(void); long button_get (bool block); long button_get_w_tmo(int ticks); intptr_t button_get_data(void); diff --git a/uisimulator/sdl/button.c b/uisimulator/sdl/button.c index 4869dd06b..2b44a7bac 100644 --- a/uisimulator/sdl/button.c +++ b/uisimulator/sdl/button.c @@ -736,6 +736,11 @@ void button_event(int key, bool pressed) /* Again copied from real button.c... */ +int button_available( void ) +{ + return queue_count(&button_queue); +} + long button_get(bool block) { struct event ev; -- 2.11.4.GIT