From c8e69dfb71d936b4bc5e18f6246ac126c629f772 Mon Sep 17 00:00:00 2001 From: Dave Chapman Date: Sun, 20 Aug 2006 23:12:56 +0000 Subject: Move FPS display out of video_out_rockbox.c and into mpegplayer.c. Also add frame-rate limiting and frame-skipping (skipping display only, not decoding) to try and achieve real-time playback. Frame-rate limiting and frame skipping (and FPS display) are enabled via options in a new menu and are currently all off by default. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@10669 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/mpegplayer/SOURCES | 1 + apps/plugins/mpegplayer/mpeg_settings.c | 118 +++++++++++++++++++++ apps/plugins/mpegplayer/mpeg_settings.h | 14 +++ apps/plugins/mpegplayer/mpegplayer.c | 153 ++++++++++++++++++++++++++-- apps/plugins/mpegplayer/video_out_rockbox.c | 25 ----- 5 files changed, 279 insertions(+), 32 deletions(-) create mode 100644 apps/plugins/mpegplayer/mpeg_settings.c create mode 100644 apps/plugins/mpegplayer/mpeg_settings.h (limited to 'apps/plugins/mpegplayer') diff --git a/apps/plugins/mpegplayer/SOURCES b/apps/plugins/mpegplayer/SOURCES index 2ea6f303ec..4646599440 100644 --- a/apps/plugins/mpegplayer/SOURCES +++ b/apps/plugins/mpegplayer/SOURCES @@ -7,4 +7,5 @@ idct.c motion_comp.c slice.c video_out_rockbox.c +mpeg_settings.c mpegplayer.c diff --git a/apps/plugins/mpegplayer/mpeg_settings.c b/apps/plugins/mpegplayer/mpeg_settings.c new file mode 100644 index 0000000000..dc63626c10 --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_settings.c @@ -0,0 +1,118 @@ +#include "plugin.h" +#include "lib/configfile.h" + +#include "mpeg_settings.h" + +extern struct plugin_api* rb; + +struct mpeg_settings settings; +static struct mpeg_settings old_settings; + +#define SETTINGS_VERSION 1 +#define SETTINGS_MIN_VERSION 1 +#define SETTINGS_FILENAME "mpegplayer.cfg" + +static char* showfps_options[] = {"No", "Yes"}; +static char* limitfps_options[] = {"No", "Yes"}; +static char* skipframes_options[] = {"No", "Yes"}; + +static struct configdata config[] = +{ + {TYPE_ENUM, 0, 2, &settings.showfps, "Show FPS", showfps_options, NULL}, + {TYPE_ENUM, 0, 2, &settings.limitfps, "Limit FPS", limitfps_options, NULL}, + {TYPE_ENUM, 0, 2, &settings.skipframes, "Skip frames", skipframes_options, NULL}, +}; + +bool mpeg_menu(void) +{ + int m; + int result; + int menu_quit=0; + + static const struct opt_items noyes[2] = { + { "No", -1 }, + { "Yes", -1 }, + }; + + static const struct menu_item items[] = { + { "Display FPS", NULL }, + { "Limit FPS", NULL }, + { "Skip frames", NULL }, + { "Quit mpegplayer", NULL }, + }; + + m = rb->menu_init(items, sizeof(items) / sizeof(*items), + NULL, NULL, NULL, NULL); + + rb->button_clear_queue(); + + while (!menu_quit) { + result=rb->menu_show(m); + + switch(result) + { + case 0: /* Show FPS */ + rb->set_option("Display FPS",&settings.showfps,INT, + noyes, 2, NULL); + break; + case 1: /* Limit FPS */ + rb->set_option("Limit FPS",&settings.limitfps,INT, + noyes, 2, NULL); + break; + case 2: /* Skip frames */ + rb->set_option("Skip frames",&settings.skipframes,INT, + noyes, 2, NULL); + break; + default: + menu_quit=1; + break; + } + } + + rb->menu_exit(m); + + rb->lcd_clear_display(); + rb->lcd_update(); + + return (result==3); +} + + +void init_settings(void) +{ + /* Set the default settings */ + settings.showfps = 0; /* Do not show FPS */ + settings.limitfps = 0; /* Do not limit FPS */ + settings.skipframes = 0; /* Do not skip frames */ + + configfile_init(rb); + + if (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) */ + 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; +} + +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); + + /* Store the settings in old_settings - to check for future changes */ + old_settings = settings; + } +} diff --git a/apps/plugins/mpegplayer/mpeg_settings.h b/apps/plugins/mpegplayer/mpeg_settings.h new file mode 100644 index 0000000000..cd2dcd2343 --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_settings.h @@ -0,0 +1,14 @@ + +#include "plugin.h" + +struct mpeg_settings { + int showfps; + int limitfps; + int skipframes; +}; + +extern struct mpeg_settings settings; + +bool mpeg_menu(void); +void init_settings(void); +void save_settings(void); diff --git a/apps/plugins/mpegplayer/mpegplayer.c b/apps/plugins/mpegplayer/mpegplayer.c index be028c01f8..67569a7e1c 100644 --- a/apps/plugins/mpegplayer/mpegplayer.c +++ b/apps/plugins/mpegplayer/mpegplayer.c @@ -27,6 +27,7 @@ #include "plugin.h" #include "mpeg2.h" +#include "mpeg_settings.h" #include "video_out.h" PLUGIN_HEADER @@ -41,44 +42,88 @@ extern char iend[]; struct plugin_api* rb; -/* The main buffer storing the compressed video data */ -#define BUFFER_SIZE (MEM-6)*1024*1024 - static mpeg2dec_t * mpeg2dec; static int total_offset = 0; /* button definitions */ #if (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD) +#define MPEG_MENU BUTTON_MODE #define MPEG_STOP BUTTON_OFF #define MPEG_PAUSE BUTTON_ON #elif (CONFIG_KEYPAD == IPOD_3G_PAD) || (CONFIG_KEYPAD == IPOD_4G_PAD) -#define MPEG_STOP BUTTON_MENU -#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_MENU BUTTON_MENU +#define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL) +#define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT) #elif CONFIG_KEYPAD == IAUDIO_X5_PAD +#define MPEG_MENU (BUTTON_REC | BUTTON_REL) #define MPEG_STOP BUTTON_POWER #define MPEG_PAUSE BUTTON_PLAY #elif CONFIG_KEYPAD == GIGABEAT_PAD +#define MPEG_MENU BUTTON_MENU #define MPEG_STOP BUTTON_A #define MPEG_PAUSE BUTTON_SELECT #elif CONFIG_KEYPAD == IRIVER_H10_PAD +#define MPEG_MENU (BUTTON_REW | BUTTON_REL) #define MPEG_STOP BUTTON_POWER #define MPEG_PAUSE BUTTON_PLAY #else #error MPEGPLAYER: Unsupported keypad #endif + +static int tick_enabled = 0; + +#define MPEG_CURRENT_TICK ((unsigned int)((*rb->current_tick - tick_offset))) + +/* The value to subtract from current_tick to get the current mpeg tick */ +static int tick_offset; + +/* The last tick - i.e. the time to reset the tick_offset to when unpausing */ +static int last_tick; + +void start_timer(void) +{ + last_tick = 0; + tick_offset = *rb->current_tick; +} + +void unpause_timer(void) +{ + tick_offset = *rb->current_tick - last_tick; +} + +void pause_timer(void) +{ + /* Save the current MPEG tick */ + last_tick = *rb->current_tick - tick_offset; +} + + static bool button_loop(void) { + bool result; int button = rb->button_get(false); + switch (button) { + case MPEG_MENU: + pause_timer(); + + result = mpeg_menu(); + + unpause_timer(); + + return result; + case MPEG_STOP: return true; + case MPEG_PAUSE: + pause_timer(); /* Freeze time */ button = BUTTON_NONE; #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(false); @@ -91,7 +136,9 @@ static bool button_loop(void) #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(true); #endif + unpause_timer(); /* Resume time */ break; + default: if(rb->default_event_handler(button) == SYS_USB_CONNECTED) return true; @@ -99,10 +146,59 @@ static bool button_loop(void) return false; } +/* + +NOTES: + +MPEG System Clock is 27MHz - i.e. 27000000 ticks/second. + +FPS is represented in terms of a frame period - this is always an +integer number of 27MHz ticks. + +e.g. 29.97fps (30000/1001) NTSC video has an exact frame period of +900900 27MHz ticks. + +In libmpeg2, info->sequence->frame_period contains the frame_period. + +Working with Rockbox's 100Hz tick, the common frame rates would need +to be as follows: + +FPS | 27Mhz | 100Hz +--------|---------------- +10* | 2700000 | 10 +12* | 2250000 | 8.3333 +15* | 1800000 | 6.6667 +23.9760 | 1126125 | 4.170833333 +24 | 1125000 | 4.166667 +25 | 1080000 | 4 +29.9700 | 900900 | 3.336667 +30 | 900000 | 3.333333 + + +*Unofficial framerates + +*/ + +static uint64_t eta; + static bool decode_mpeg2 (uint8_t * current, uint8_t * end) { const mpeg2_info_t * info; mpeg2_state_t state; + char str[80]; + static int skipped = 0; + static int frame = 0; + static int starttick = 0; + static int lasttick; + unsigned int eta2; + unsigned int x; + + int fps; + + if (starttick == 0) { + starttick=*rb->current_tick-1; /* Avoid divby0 */ + lasttick=starttick; + } mpeg2_buffer (mpeg2dec, current, end); total_offset += end - current; @@ -122,6 +218,7 @@ static bool decode_mpeg2 (uint8_t * current, uint8_t * end) info->sequence->chroma_width, info->sequence->chroma_height); mpeg2_skip (mpeg2dec, false); + break; case STATE_PICTURE: break; @@ -129,8 +226,44 @@ static bool decode_mpeg2 (uint8_t * current, uint8_t * end) case STATE_END: case STATE_INVALID_END: /* draw current picture */ - if (info->display_fbuf) - vo_draw_frame(info->display_fbuf->buf); + if (info->display_fbuf) { + /* We start the timer when we draw the first frame */ + if (!tick_enabled) { + start_timer(); + tick_enabled = 1 ; + } + + eta += (info->sequence->frame_period); + eta2 = eta / (27000000 / HZ); + + if (settings.limitfps) { + if (eta2 > MPEG_CURRENT_TICK) { + rb->sleep(eta2-MPEG_CURRENT_TICK); + } + } + x = MPEG_CURRENT_TICK; + + /* If we are more than 1/20 second behind schedule (and + more than 1/20 second into the decoding), skip frame */ + if (settings.skipframes && (x > HZ/20) && + (eta2 < (x - (HZ/20)))) { + skipped++; + } else { + vo_draw_frame(info->display_fbuf->buf); + } + + /* Calculate fps */ + frame++; + if (settings.showfps && (*rb->current_tick-lasttick>=HZ)) { + fps=(frame*(HZ*10))/x; + rb->snprintf(str,sizeof(str),"%d.%d %d %d %d", + (fps/10),fps%10,skipped,x,eta2); + rb->lcd_putsxy(0,0,str); + rb->lcd_update_rect(0,0,LCD_WIDTH,8); + + lasttick = *rb->current_tick; + } + } break; default: break; @@ -147,8 +280,10 @@ static void es_loop (int in_file, uint8_t* buffer, size_t buffer_size) if (buffer==NULL) return; + eta = 0; do { rb->splash(0,true,"Buffering..."); + save_settings(); /* Save settings (if they have changed) */ end = buffer + rb->read (in_file, buffer, buffer_size); if (decode_mpeg2 (buffer, end)) break; @@ -202,6 +337,8 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) return PLUGIN_ERROR; } + init_settings(); + mpeg2dec = mpeg2_init (); if (mpeg2dec == NULL) @@ -230,6 +367,8 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) rb->cpu_boost(false); #endif + save_settings(); /* Save settings (if they have changed) */ + #ifdef CONFIG_BACKLIGHT /* reset backlight settings */ rb->backlight_set_timeout(rb->global_settings->backlight_timeout); diff --git a/apps/plugins/mpegplayer/video_out_rockbox.c b/apps/plugins/mpegplayer/video_out_rockbox.c index 258416fcdd..38a76a7747 100644 --- a/apps/plugins/mpegplayer/video_out_rockbox.c +++ b/apps/plugins/mpegplayer/video_out_rockbox.c @@ -30,9 +30,6 @@ extern struct plugin_api* rb; #include "mpeg2.h" #include "video_out.h" -static int starttick = 0; -static int lasttick = 0; - #define CSUB_X 2 #define CSUB_Y 2 @@ -191,10 +188,6 @@ static void yuv_bitmap_part(unsigned char * const src[3], void vo_draw_frame (uint8_t * const * buf) { - char str[80]; - static int frame=0; - int ticks,fps; - #ifdef SIMULATOR yuv_bitmap_part(buf,0,0,image_width, output_x,output_y,output_width,output_height); @@ -204,24 +197,6 @@ void vo_draw_frame (uint8_t * const * buf) 0,0,image_width, output_x,output_y,output_width,output_height); #endif - - if (starttick==0) { - starttick=*rb->current_tick-1; /* Avoid divby0 */ - lasttick=starttick; - } - - /* Calculate fps */ - if (*rb->current_tick-lasttick>=2*HZ) { - ticks=(*rb->current_tick)-starttick; - - fps=(frame*1000)/ticks; - rb->snprintf(str,sizeof(str),"%d.%d",(fps/10),fps%10); - rb->lcd_putsxy(0,0,str); - rb->lcd_update_rect(0,0,80,8); - - lasttick+=2*HZ; - } - frame++; } void vo_setup(unsigned int width, unsigned int height, -- cgit v1.2.3