MPEGPlayer graphics mutation: Implement a more visible FPS display and remove the debugging info from it. Tweak thumbnailing and printing of unavailable frames.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28960 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Michael Sevakis 2011-01-03 16:41:19 +00:00
parent f8fde296a6
commit b664f62e36
8 changed files with 333 additions and 93 deletions

View file

@ -601,20 +601,37 @@ static void draw_slider(uint32_t range, uint32_t pos, struct vo_rect *rc)
static bool display_thumb_image(const struct vo_rect *rc)
{
bool retval = true;
unsigned ltgray = MYLCD_LIGHTGRAY;
unsigned dkgray = MYLCD_DARKGRAY;
int oldcolor = mylcd_get_foreground();
if (!stream_display_thumb(rc))
{
mylcd_splash(0, "Frame not available");
return false;
/* Display "No Frame" and erase any border */
const char * const str = "No Frame";
int x, y, w, h;
mylcd_getstringsize(str, &w, &h);
x = (rc->r + rc->l - w) / 2;
y = (rc->b + rc->t - h) / 2;
mylcd_putsxy(x, y, str);
mylcd_update_rect(x, y, w, h);
ltgray = dkgray = mylcd_get_background();
retval = false;
}
/* Draw a raised border around the frame */
int oldcolor = mylcd_get_foreground();
mylcd_set_foreground(MYLCD_LIGHTGRAY);
/* Draw a raised border around the frame (or erase if no frame) */
mylcd_set_foreground(ltgray);
mylcd_hline(rc->l-1, rc->r-1, rc->t-1);
mylcd_vline(rc->l-1, rc->t, rc->b-1);
mylcd_set_foreground(MYLCD_DARKGRAY);
mylcd_set_foreground(dkgray);
mylcd_hline(rc->l-1, rc->r, rc->b);
mylcd_vline(rc->r, rc->t-1, rc->b);
@ -626,7 +643,7 @@ static bool display_thumb_image(const struct vo_rect *rc)
mylcd_update_rect(rc->l-1, rc->b, rc->r - rc->l + 2, 1);
mylcd_update_rect(rc->r, rc->t, 1, rc->b - rc->t);
return true;
return retval;
}
/* Add an amount to the specified time - with saturation */

View file

@ -380,6 +380,7 @@ CONFIG_KEYPAD == SANSA_M200_PAD
/* 3% of 30min file == 54s step size */
#define MIN_FF_REWIND_STEP (TS_SECOND/2)
#define OSD_MIN_UPDATE_INTERVAL (HZ/2)
#define FPS_UPDATE_INTERVAL (HZ) /* Get new FPS reading each second */
enum video_action
{
@ -457,9 +458,25 @@ struct osd
uint32_t curr_time;
unsigned auto_refresh;
unsigned flags;
int font;
};
struct fps
{
/* FPS Display */
struct vo_rect rect; /* OSD coordinates */
int pf_x; /* Screen coordinates */
int pf_y;
int pf_width;
int pf_height;
long update_tick; /* When to next update FPS reading */
#define FPS_FORMAT "%d.%02d"
#define FPS_DIMSTR "999.99" /* For establishing rect size */
#define FPS_BUFSIZE sizeof("999.99")
};
static struct osd osd;
static struct fps fps NOCACHEBSS_ATTR; /* Accessed on other processor */
static void osd_show(unsigned show);
@ -573,6 +590,12 @@ static void draw_scrollbar_draw_rect(const struct vo_rect *rc, int min,
min, max, val);
}
static void draw_setfont(int font)
{
osd.font = font;
mylcd_setfont(font);
}
#ifdef LCD_PORTRAIT
/* Portrait displays need rotated text rendering */
@ -646,7 +669,7 @@ static void draw_putsxy_oriented(int x, int y, const char *str)
unsigned short ch;
unsigned short *ucs;
int ofs = MIN(x, 0);
struct font* pf = rb->font_get(FONT_UI);
struct font* pf = rb->font_get(osd.font);
ucs = rb->bidi_l2v(str, 1);
@ -696,6 +719,96 @@ static void draw_putsxy_oriented(int x, int y, const char *str)
}
#endif /* LCD_PORTRAIT */
/** FPS Display **/
/* Post-frame callback (on video thread) - update the FPS rectangle from the
* framebuffer */
static void fps_post_frame_callback(void)
{
vo_lock();
mylcd_update_rect(fps.pf_x, fps.pf_y,
fps.pf_width, fps.pf_height);
vo_unlock();
}
/* Set up to have the callback only update the intersection of the video
* rectangle and the FPS text rectangle - if they don't intersect, then
* the callback is set to NULL */
static void fps_update_post_frame_callback(void)
{
void (*cb)(void) = NULL;
if (settings.showfps) {
struct vo_rect cliprect;
if (stream_vo_get_clip(&cliprect)) {
/* Oriented screen coordinates -> OSD coordinates */
vo_rect_offset(&cliprect, -osd.x, -osd.y);
if (vo_rect_intersect(&cliprect, &cliprect, &fps.rect)) {
int x = cliprect.l;
int y = cliprect.t;
int width = cliprect.r - cliprect.l;
int height = cliprect.b - cliprect.t;
/* OSD coordinates -> framebuffer coordinates */
fps.pf_x = _X;
fps.pf_y = _Y;
fps.pf_width = _W;
fps.pf_height = _H;
cb = fps_post_frame_callback;
}
}
}
stream_set_callback(VIDEO_SET_POST_FRAME_CALLBACK, cb);
}
/* Refresh the FPS display */
static void fps_refresh(void)
{
char str[FPS_BUFSIZE];
struct video_output_stats stats;
int w, h, sw;
long tick;
tick = *rb->current_tick;
if (TIME_BEFORE(tick, fps.update_tick))
return;
fps.update_tick = tick + FPS_UPDATE_INTERVAL;
stream_video_stats(&stats);
rb->snprintf(str, FPS_BUFSIZE, FPS_FORMAT,
stats.fps / 100, stats.fps % 100);
w = fps.rect.r - fps.rect.l;
h = fps.rect.b - fps.rect.t;
draw_clear_area(fps.rect.l, fps.rect.t, w, h);
mylcd_getstringsize(str, &sw, NULL);
draw_putsxy_oriented(fps.rect.r - sw, fps.rect.t, str);
vo_lock();
draw_update_rect(fps.rect.l, fps.rect.t, w, h);
vo_unlock();
}
/* Initialize the FPS display */
static void fps_init(void)
{
fps.update_tick = *rb->current_tick;
fps.rect.l = fps.rect.t = 0;
mylcd_getstringsize(FPS_DIMSTR, &fps.rect.r, &fps.rect.b);
vo_rect_offset(&fps.rect, -osd.x, -osd.y);
fps_update_post_frame_callback();
}
/** OSD **/
#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
/* So we can refresh the overlay */
static void osd_lcd_enable_hook(void* param)
@ -743,7 +856,7 @@ static void osd_text_init(void)
int phys;
int spc_width;
mylcd_setfont(FONT_UI);
draw_setfont(FONT_UI);
osd.x = 0;
osd.width = SCREEN_WIDTH;
@ -812,7 +925,7 @@ static void osd_text_init(void)
#endif
osd.y = SCREEN_HEIGHT - osd.height;
mylcd_setfont(FONT_SYSFIXED);
draw_setfont(FONT_SYSFIXED);
}
static void osd_init(void)
@ -835,6 +948,7 @@ static void osd_init(void)
osd.auto_refresh = OSD_REFRESH_TIME;
osd.next_auto_refresh = *rb->current_tick;
osd_text_init();
fps_init();
}
#ifdef HAVE_HEADPHONE_DETECTION
@ -1047,6 +1161,9 @@ static void osd_refresh(int hint)
tick = *rb->current_tick;
if (settings.showfps)
fps_refresh();
if (hint == OSD_REFRESH_DEFAULT) {
/* The default which forces no updates */
@ -1116,7 +1233,7 @@ static void osd_refresh(int hint)
oldfg = mylcd_get_foreground();
oldbg = mylcd_get_background();
mylcd_setfont(FONT_UI);
draw_setfont(FONT_UI);
mylcd_set_foreground(osd.fgcolor);
mylcd_set_background(osd.bgcolor);
@ -1140,7 +1257,7 @@ static void osd_refresh(int hint)
}
/* Go back to defaults */
mylcd_setfont(FONT_SYSFIXED);
draw_setfont(FONT_SYSFIXED);
mylcd_set_foreground(oldfg);
mylcd_set_background(oldbg);
@ -1391,6 +1508,7 @@ static int osd_play(uint32_t time)
osd_backlight_on_video_mode(true);
osd_backlight_brightness_video_mode(true);
stream_show_vo(true);
retval = stream_play();
if (retval >= STREAM_OK)
@ -1747,6 +1865,8 @@ static int button_loop(void)
next_action = (settings.play_mode == 0) ? VIDEO_STOP : VIDEO_NEXT;
fps_update_post_frame_callback();
/* The menu can change the font, so restore */
rb->lcd_setfont(FONT_SYSFIXED);
#ifdef HAVE_LCD_COLOR

View file

@ -695,8 +695,8 @@ static intptr_t send_video_msg(long id, intptr_t data)
if (disk_buf_status() != STREAM_STOPPED)
break; /* Prepare image if not playing */
if (!parser_prepare_image(str_parser.last_seek_time))
return false; /* Preparation failed */
/* Ignore return and try video thread anyway */
parser_prepare_image(str_parser.last_seek_time);
/* Image ready - pass message to video thread */
break;
@ -766,6 +766,25 @@ void stream_vo_set_clip(const struct vo_rect *rc)
stream_mgr_unlock();
}
bool stream_vo_get_clip(struct vo_rect *rc)
{
bool retval;
if (!rc)
return false;
stream_mgr_lock();
retval = send_video_msg(VIDEO_GET_CLIP_RECT,
(intptr_t)&stream_mgr.parms.rc);
*rc = stream_mgr.parms.rc;
stream_mgr_unlock();
return retval;
}
#ifndef HAVE_LCD_COLOR
/* Show/hide the gray video overlay (independently of vo visibility). */
void stream_gray_show(bool show)
@ -810,6 +829,23 @@ bool stream_draw_frame(bool no_prepare)
return retval;
}
bool stream_set_callback(long id, void *fn)
{
bool retval = false;
stream_mgr_lock();
switch (id)
{
case VIDEO_SET_POST_FRAME_CALLBACK:
retval = send_video_msg(id, (intptr_t)fn);
}
stream_mgr_unlock();
return retval;
}
/* Return the time playback should resume if interrupted */
uint32_t stream_get_resume_time(void)
{

View file

@ -106,6 +106,9 @@ bool stream_show_vo(bool show);
/* Set the visible section of video */
void stream_vo_set_clip(const struct vo_rect *rc);
/* Return current visible section of video */
bool stream_vo_get_clip(struct vo_rect *rc);
#ifndef HAVE_LCD_COLOR
void stream_gray_show(bool show);
#endif
@ -149,6 +152,11 @@ static inline uint32_t stream_get_duration(void)
static inline bool stream_can_seek(void)
{ return parser_can_seek(); }
static inline void stream_video_stats(struct video_output_stats *s)
{ video_thread_get_stats(s); }
bool stream_set_callback(long id, void * fn);
/* Keep the disk spinning (for seeking and browsing) */
static inline void stream_keep_disk_active(void)
{

View file

@ -108,6 +108,8 @@ enum stream_message
VIDEO_PRINT_FRAME, /* Print the frame at the current position */
VIDEO_PRINT_THUMBNAIL, /* Print a thumbnail of the current position */
VIDEO_SET_CLIP_RECT, /* Set the visible video area */
VIDEO_GET_CLIP_RECT, /* Return the visible video area */
VIDEO_SET_POST_FRAME_CALLBACK, /* Set a callback after frame is drawn */
STREAM_MESSAGE_LAST,
};
@ -160,9 +162,20 @@ extern struct stream audio_str IBSS_ATTR;
bool video_thread_init(void);
void video_thread_exit(void);
struct video_output_stats
{
int num_drawn; /* Number of frames drawn since reset */
int num_skipped; /* Number of frames skipped since reset */
int fps; /* fps rate in 100ths of a frame per second */
};
void video_thread_get_stats(struct video_output_stats *s);
bool audio_thread_init(void);
void audio_thread_exit(void);
/* Some queue function wrappers to keep things clean-ish */
/* For stream use only */

View file

@ -58,8 +58,10 @@ bool vo_show (bool show);
bool vo_is_visible(void);
void vo_setup (const mpeg2_sequence_t * sequence);
void vo_set_clip_rect(const struct vo_rect *rc);
bool vo_get_clip_rect(struct vo_rect *rc);
void vo_dimensions(struct vo_ext *sz);
void vo_cleanup (void);
void vo_set_post_draw_callback(void (*cb)(void));
#if NUM_CORES > 1
void vo_lock(void);

View file

@ -41,6 +41,7 @@ struct vo_data
unsigned flags;
struct vo_rect rc_vid;
struct vo_rect rc_clip;
void (*post_draw_callback)(void);
};
#if NUM_CORES > 1
@ -80,9 +81,10 @@ static inline void video_unlock(void)
/* Draw a black rectangle if no video frame is available */
static void vo_draw_black(void)
static void vo_draw_black(struct vo_rect *rc)
{
int foreground;
int x, y, w, h;
video_lock();
@ -90,10 +92,30 @@ static void vo_draw_black(void)
mylcd_set_foreground(MYLCD_BLACK);
mylcd_fillrect(vo.output_x, vo.output_y, vo.output_width,
vo.output_height);
mylcd_update_rect(vo.output_x, vo.output_y, vo.output_width,
vo.output_height);
if (rc)
{
x = rc->l;
y = rc->t;
w = rc->r - rc->l;
h = rc->b - rc->t;
}
else
{
#if LCD_WIDTH >= LCD_HEIGHT
x = vo.output_x;
y = vo.output_y;
w = vo.output_width;
h = vo.output_height;
#else
x = LCD_WIDTH - vo.output_height - vo.output_y;
y = vo.output_x;
w = vo.output_height;
h = vo.output_width;
#endif
}
mylcd_fillrect(x, y, w, h);
mylcd_update_rect(x, y, w, h);
mylcd_set_foreground(foreground);
@ -122,19 +144,22 @@ void vo_draw_frame(uint8_t * const * buf)
/* Frame is hidden - either by being set invisible or is clipped
* away - copout */
DEBUGF("vo hidden\n");
return;
}
else if (buf == NULL)
{
/* No frame exists - draw black */
vo_draw_black();
vo_draw_black(NULL);
DEBUGF("vo no frame\n");
return;
}
else
{
yuv_blit(buf, 0, 0, vo.image_width,
vo.output_x, vo.output_y, vo.output_width,
vo.output_height);
}
if (vo.post_draw_callback)
vo.post_draw_callback();
}
static inline void vo_rect_clear_inl(struct vo_rect *rc)
@ -348,14 +373,14 @@ bool vo_draw_frame_thumb(uint8_t * const * buf, const struct vo_rect *rc)
int thumb_width, thumb_height;
int thumb_uv_width, thumb_uv_height;
if (buf == NULL)
return false;
/* Obtain rectangle as clipped to the screen */
vo_rect_set_ext(&thumb_rc, 0, 0, LCD_WIDTH, LCD_HEIGHT);
if (!vo_rect_intersect(&thumb_rc, rc, &thumb_rc))
return true;
if (buf == NULL)
goto no_thumb_exit;
DEBUGF("thumb_rc: %d, %d, %d, %d\n", thumb_rc.l, thumb_rc.t,
thumb_rc.r, thumb_rc.b);
@ -377,7 +402,7 @@ bool vo_draw_frame_thumb(uint8_t * const * buf, const struct vo_rect *rc)
)
{
DEBUGF("thumb: insufficient buffer\n");
return false;
goto no_thumb_exit;
}
yuv[0] = mem;
@ -411,6 +436,10 @@ bool vo_draw_frame_thumb(uint8_t * const * buf, const struct vo_rect *rc)
#endif /* LCD_WIDTH >= LCD_HEIGHT */
return true;
no_thumb_exit:
vo_draw_black(&thumb_rc);
return false;
}
void vo_setup(const mpeg2_sequence_t * sequence)
@ -512,6 +541,20 @@ void vo_set_clip_rect(const struct vo_rect *rc)
vo.output_height = rc_out.b - rc_out.t;
}
bool vo_get_clip_rect(struct vo_rect *rc)
{
rc->l = vo.output_x;
rc->t = vo.output_y;
rc->r = rc->l + vo.output_width;
rc->b = rc->t + vo.output_height;
return (vo.flags & VO_NON_NULL_RECT) != 0;
}
void vo_set_post_draw_callback(void (*cb)(void))
{
vo.post_draw_callback = cb;
}
#if NUM_CORES > 1
void vo_lock(void)
{

View file

@ -37,21 +37,23 @@ struct video_thread_data
int state; /* Thread state */
int status; /* Media status */
struct queue_event ev; /* Our event queue to receive commands */
int num_drawn; /* Number of frames drawn since reset */
int num_skipped; /* Number of frames skipped since reset */
uint32_t eta_stream; /* Current time of stream */
uint32_t eta_video; /* Time that frame has been scheduled for */
int32_t eta_early; /* How early has the frame been decoded? */
int32_t eta_late; /* How late has the frame been decoded? */
int frame_drop_level; /* Drop severity */
int skip_level; /* Skip severity */
long last_showfps; /* Last time the FPS display was updated */
long last_render; /* Last time a frame was drawn */
uint32_t curr_time; /* Current due time of frame */
uint32_t period; /* Frame period in clock ticks */
int syncf_perfect; /* Last sync fit result */
};
/* Number drawn since reset */
static int video_num_drawn SHAREDBSS_ATTR;
/* Number skipped since reset */
static int video_num_skipped SHAREDBSS_ATTR;
/* TODO: Check if 4KB is appropriate - it works for my test streams,
so maybe we can reduce it. */
#define VIDEO_STACKSIZE (4*1024)
@ -60,35 +62,6 @@ static struct event_queue video_str_queue SHAREDBSS_ATTR;
static struct queue_sender_list video_str_queue_send SHAREDBSS_ATTR;
struct stream video_str IBSS_ATTR;
static void draw_fps(struct video_thread_data *td)
{
uint32_t start;
uint32_t clock_ticks = stream_get_ticks(&start);
int fps = 0;
int buf_pct;
char str[80];
clock_ticks -= start;
if (clock_ticks != 0)
fps = muldiv_uint32(CLOCK_RATE*100, td->num_drawn, clock_ticks);
buf_pct = muldiv_uint32(100, pcm_output_used(), PCMOUT_BUFSIZE);
rb->snprintf(str, sizeof(str), "v:%d.%02d %d %d a:%02d%% %d %d ",
/* Video information */
fps / 100, fps % 100, td->num_skipped,
td->info->display_picture->temporal_reference,
/* Audio information */
buf_pct, pcm_underruns, pcm_skipped);
mylcd_putsxy(0, 0, str);
vo_lock();
mylcd_update_rect(0, 0, LCD_WIDTH, 8);
vo_unlock();
td->last_showfps = *rb->current_tick;
}
#if defined(DEBUG) || defined(SIMULATOR)
static unsigned char pic_coding_type_char(unsigned type)
{
@ -452,6 +425,31 @@ sync_finished:
return retval;
}
static bool frame_print_handler(struct video_thread_data *td)
{
bool retval;
uint8_t * const * buf = NULL;
if (td->info != NULL && td->info->display_fbuf != NULL &&
td->syncf_perfect > 0)
buf = td->info->display_fbuf->buf;
if (td->ev.id == VIDEO_PRINT_THUMBNAIL)
{
/* Print a thumbnail of whatever was last decoded - scale and
* position to fill the specified rectangle */
retval = vo_draw_frame_thumb(buf, (struct vo_rect *)td->ev.data);
}
else
{
/* Print the last frame decoded */
vo_draw_frame(buf);
retval = buf != NULL;
}
return retval;
}
/* This only returns to play or quit */
static void video_thread_msg(struct video_thread_data *td)
{
@ -520,8 +518,7 @@ static void video_thread_msg(struct video_thread_data *td)
if (td->ev.data)
{
if (td->info != NULL && td->info->display_fbuf != NULL)
vo_draw_frame(td->info->display_fbuf->buf);
frame_print_handler(td);
}
else
{
@ -547,10 +544,9 @@ static void video_thread_msg(struct video_thread_data *td)
td->eta_late = 0;
td->frame_drop_level = 0;
td->skip_level = 0;
td->num_drawn = 0;
td->num_skipped = 0;
td->last_showfps = *rb->current_tick - HZ;
td->last_render = td->last_showfps;
td->last_render = *rb->current_tick - HZ;
video_num_drawn = 0;
video_num_skipped = 0;
reply = true;
break;
@ -573,28 +569,17 @@ static void video_thread_msg(struct video_thread_data *td)
str_data_notify_received(&video_str);
break;
case VIDEO_PRINT_FRAME:
case VIDEO_PRINT_THUMBNAIL:
/* Print a thumbnail of whatever was last decoded - scale and
* position to fill the specified rectangle */
if (td->info != NULL && td->info->display_fbuf != NULL)
{
vo_draw_frame_thumb(td->info->display_fbuf->buf,
(struct vo_rect *)td->ev.data);
reply = true;
}
reply = frame_print_handler(td);
break;
case VIDEO_SET_CLIP_RECT:
vo_set_clip_rect((const struct vo_rect *)td->ev.data);
break;
case VIDEO_PRINT_FRAME:
/* Print the last frame decoded */
if (td->info != NULL && td->info->display_fbuf != NULL)
{
vo_draw_frame(td->info->display_fbuf->buf);
reply = true;
}
case VIDEO_GET_CLIP_RECT:
reply = vo_get_clip_rect((struct vo_rect *)td->ev.data);
break;
case VIDEO_GET_SIZE:
@ -621,6 +606,11 @@ static void video_thread_msg(struct video_thread_data *td)
reply = video_str_scan(td, (struct str_sync_data *)td->ev.data);
break;
case VIDEO_SET_POST_FRAME_CALLBACK:
vo_set_post_draw_callback((void (*)(void))td->ev.data);
reply = true;
break;
case STREAM_QUIT:
/* Time to go - make thread exit */
td->state = TSTATE_EOS;
@ -802,6 +792,8 @@ static void video_thread(void)
if (td.info->display_fbuf == NULL)
break; /* No picture */
td.syncf_perfect = 1; /* yes, a frame exists */
/* Get presentation times in audio samples - quite accurate
enough - add previous frame duration if not stamped */
td.curr_time = (td.info->display_picture->flags & PIC_FLAG_TAGS) ?
@ -902,8 +894,8 @@ static void video_thread(void)
{
/* This frame was set to skip so skip it after having updated
timing information */
td.num_skipped++;
td.eta_early = INT32_MIN;
video_num_skipped++;
goto picture_skip;
}
@ -913,8 +905,8 @@ static void video_thread(void)
/* Render drop was set previously but nothing was dropped in the
decoder or it's been to long since drawing the last frame. */
td.skip_level = 0;
td.num_skipped++;
td.eta_early = INT32_MIN;
video_num_skipped++;
goto picture_skip;
}
@ -970,18 +962,11 @@ static void video_thread(void)
picture_draw:
/* Record last frame time */
td.last_render = *rb->current_tick;
vo_draw_frame(td.info->display_fbuf->buf);
td.num_drawn++;
video_num_drawn++;
picture_skip:
if (!settings.showfps)
break;
if (TIME_BEFORE(*rb->current_tick, td.last_showfps + HZ))
break;
/* Calculate and display fps */
draw_fps(&td);
break;
}
@ -1032,3 +1017,19 @@ void video_thread_exit(void)
video_str.thread = 0;
}
}
/** Misc **/
void video_thread_get_stats(struct video_output_stats *s)
{
uint32_t start;
uint32_t now = stream_get_ticks(&start);
s->num_drawn = video_num_drawn;
s->num_skipped = video_num_skipped;
s->fps = 0;
if (now > start)
s->fps = muldiv_uint32(CLOCK_RATE*100, s->num_drawn, now - start);
}