/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * mpegplayer video output routines * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "libmpeg2/mpeg2dec_config.h" #include "plugin.h" #include "mpegplayer.h" #define VO_NON_NULL_RECT 0x1 #define VO_VISIBLE 0x2 struct vo_data { int image_width; int image_height; int image_chroma_x; int image_chroma_y; int display_width; int display_height; int output_x; int output_y; int output_width; int output_height; unsigned flags; struct vo_rect rc_vid; struct vo_rect rc_clip; void (*post_draw_callback)(void); }; #if NUM_CORES > 1 /* Cache aligned and padded to avoid clobbering other processors' cacheable * data */ static union { uint8_t __vo_data[CACHEALIGN_UP(sizeof(struct vo_data))]; struct vo_data vo; } vo_raw CACHEALIGN_ATTR; #define vo vo_raw.vo #else static struct vo_data vo; #endif #if NUM_CORES > 1 static struct mutex vo_mtx SHAREDBSS_ATTR; #endif static inline void video_lock_init(void) { #if NUM_CORES > 1 rb->mutex_init(&vo_mtx); #endif } static inline void video_lock(void) { #if NUM_CORES > 1 rb->mutex_lock(&vo_mtx); #endif } static inline void video_unlock(void) { #if NUM_CORES > 1 rb->mutex_unlock(&vo_mtx); #endif } /* Draw a black rectangle if no video frame is available */ static void vo_draw_black(struct vo_rect *rc) { int foreground; int x, y, w, h; video_lock(); foreground = mylcd_get_foreground(); mylcd_set_foreground(MYLCD_BLACK); 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); video_unlock(); } static inline void yuv_blit(uint8_t * const * buf, int src_x, int src_y, int stride, int x, int y, int width, int height) { video_lock(); #ifdef HAVE_LCD_COLOR rb->lcd_blit_yuv(buf, src_x, src_y, stride, x, y , width, height); #else grey_ub_gray_bitmap_part(buf[0], src_x, src_y, stride, x, y, width, height); #endif video_unlock(); } void vo_draw_frame(uint8_t * const * buf) { if ((vo.flags & (VO_NON_NULL_RECT | VO_VISIBLE)) != (VO_NON_NULL_RECT | VO_VISIBLE)) { /* Frame is hidden - either by being set invisible or is clipped * away - copout */ DEBUGF("vo hidden\n"); } else if (buf == NULL) { /* No frame exists - draw black */ vo_draw_black(NULL); DEBUGF("vo no frame\n"); } 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) { rc->l = rc->t = rc->r = rc->b = 0; } static inline bool vo_rect_empty_inl(const struct vo_rect *rc) { return rc == NULL || rc->l >= rc->r || rc->t >= rc->b; } static inline bool vo_rects_intersect_inl(const struct vo_rect *rc1, const struct vo_rect *rc2) { return !vo_rect_empty_inl(rc1) && !vo_rect_empty_inl(rc2) && rc1->l < rc2->r && rc1->r > rc2->l && rc1->t < rc2->b && rc1->b > rc2->t; } /* Sets all coordinates of a vo_rect to 0 */ void vo_rect_clear(struct vo_rect *rc) { vo_rect_clear_inl(rc); } /* Returns true if left >= right or top >= bottom */ bool vo_rect_empty(const struct vo_rect *rc) { return vo_rect_empty_inl(rc); } /* Initializes a vo_rect using upper-left corner and extents */ void vo_rect_set_ext(struct vo_rect *rc, int x, int y, int width, int height) { rc->l = x; rc->t = y; rc->r = x + width; rc->b = y + height; } /* Query if two rectangles intersect */ bool vo_rects_intersect(const struct vo_rect *rc1, const struct vo_rect *rc2) { return vo_rects_intersect_inl(rc1, rc2); } /* Intersect two rectangles, placing the result in rc_dst */ bool vo_rect_intersect(struct vo_rect *rc_dst, const struct vo_rect *rc1, const struct vo_rect *rc2) { if (rc_dst != NULL) { if (vo_rects_intersect_inl(rc1, rc2)) { rc_dst->l = MAX(rc1->l, rc2->l); rc_dst->r = MIN(rc1->r, rc2->r); rc_dst->t = MAX(rc1->t, rc2->t); rc_dst->b = MIN(rc1->b, rc2->b); return true; } vo_rect_clear_inl(rc_dst); } return false; } bool vo_rect_union(struct vo_rect *rc_dst, const struct vo_rect *rc1, const struct vo_rect *rc2) { if (rc_dst != NULL) { if (!vo_rect_empty_inl(rc1)) { if (!vo_rect_empty_inl(rc2)) { rc_dst->l = MIN(rc1->l, rc2->l); rc_dst->t = MIN(rc1->t, rc2->t); rc_dst->r = MAX(rc1->r, rc2->r); rc_dst->b = MAX(rc1->b, rc2->b); } else { *rc_dst = *rc1; } return true; } else if (!vo_rect_empty_inl(rc2)) { *rc_dst = *rc2; return true; } vo_rect_clear_inl(rc_dst); } return false; } void vo_rect_offset(struct vo_rect *rc, int dx, int dy) { rc->l += dx; rc->t += dy; rc->r += dx; rc->b += dy; } /* Shink or stretch each axis - rotate counter-clockwise to retain upright * orientation on rotated displays (they rotate clockwise) */ void stretch_image_plane(const uint8_t * src, uint8_t *dst, int stride, int src_w, int src_h, int dst_w, int dst_h) { uint8_t *dst_end = dst + dst_w*dst_h; #if LCD_WIDTH >= LCD_HEIGHT int src_w2 = src_w*2; /* 2x dimensions (for rounding before division) */ int dst_w2 = dst_w*2; int src_h2 = src_h*2; int dst_h2 = dst_h*2; int qw = src_w2 / dst_w2; /* src-dst width ratio quotient */ int rw = src_w2 - qw*dst_w2; /* src-dst width ratio remainder */ int qh = src_h2 / dst_h2; /* src-dst height ratio quotient */ int rh = src_h2 - qh*dst_h2; /* src-dst height ratio remainder */ int dw = dst_w; /* Width error accumulator */ int dh = dst_h; /* Height error accumulator */ #else int src_w2 = src_w*2; int dst_w2 = dst_h*2; int src_h2 = src_h*2; int dst_h2 = dst_w*2; int qw = src_h2 / dst_w2; int rw = src_h2 - qw*dst_w2; int qh = src_w2 / dst_h2; int rh = src_w2 - qh*dst_h2; int dw = dst_h; int dh = dst_w; src += src_w - 1; #endif while (1) { const uint8_t *s = src; #if LCD_WIDTH >= LCD_HEIGHT uint8_t * const dst_line_end = dst + dst_w; #else uint8_t * const dst_line_end = dst + dst_h; #endif while (1) { *dst++ = *s; if (dst >= dst_line_end) { dw = dst_w; break; } #if LCD_WIDTH >= LCD_HEIGHT s += qw; #else s += qw*stride; #endif dw += rw; if (dw >= dst_w2) { dw -= dst_w2; #if LCD_WIDTH >= LCD_HEIGHT s++; #else s += stride; #endif } } if (dst >= dst_end) break; #if LCD_WIDTH >= LCD_HEIGHT src += qh*stride; #else src -= qh; #endif dh += rh; if (dh >= dst_h2) { dh -= dst_h2; #if LCD_WIDTH >= LCD_HEIGHT src += stride; #else src--; #endif } } } bool vo_draw_frame_thumb(uint8_t * const * buf, const struct vo_rect *rc) { void *mem; size_t bufsize = 0; uint8_t *yuv[3]; struct vo_rect thumb_rc; int thumb_width, thumb_height; #ifdef HAVE_LCD_COLOR int thumb_uv_width, thumb_uv_height; #endif /* 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); thumb_width = rc->r - rc->l; thumb_height = rc->b - rc->t; #ifdef HAVE_LCD_COLOR thumb_uv_width = thumb_width / 2; thumb_uv_height = thumb_height / 2; DEBUGF("thumb: w: %d h: %d uvw: %d uvh: %d\n", thumb_width, thumb_height, thumb_uv_width, thumb_uv_height); #else DEBUGF("thumb: w: %d h: %d\n", thumb_width, thumb_height); #endif /* Use remaining mpeg2 buffer as temp space */ mem = mpeg2_get_buf(&bufsize); if (bufsize < (size_t)(thumb_width*thumb_height) #ifdef HAVE_LCD_COLOR + 2u*(thumb_uv_width * thumb_uv_height) #endif ) { DEBUGF("thumb: insufficient buffer\n"); goto no_thumb_exit; } yuv[0] = mem; stretch_image_plane(buf[0], yuv[0], vo.image_width, vo.display_width, vo.display_height, thumb_width, thumb_height); #ifdef HAVE_LCD_COLOR yuv[1] = yuv[0] + thumb_width*thumb_height; yuv[2] = yuv[1] + thumb_uv_width*thumb_uv_height; stretch_image_plane(buf[1], yuv[1], vo.image_width / 2, vo.display_width / 2, vo.display_height / 2, thumb_uv_width, thumb_uv_height); stretch_image_plane(buf[2], yuv[2], vo.image_width / 2, vo.display_width / 2, vo.display_height / 2, thumb_uv_width, thumb_uv_height); #endif #if LCD_WIDTH >= LCD_HEIGHT yuv_blit(yuv, 0, 0, thumb_width, thumb_rc.l, thumb_rc.t, thumb_rc.r - thumb_rc.l, thumb_rc.b - thumb_rc.t); #else yuv_blit(yuv, 0, 0, thumb_height, thumb_rc.t, thumb_rc.l, thumb_rc.b - thumb_rc.t, thumb_rc.r - thumb_rc.l); #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) { vo.image_width = sequence->width; vo.image_height = sequence->height; vo.display_width = sequence->display_width; vo.display_height = sequence->display_height; DEBUGF("vo_setup - w:%d h:%d\n", vo.display_width, vo.display_height); vo.image_chroma_x = vo.image_width / sequence->chroma_width; vo.image_chroma_y = vo.image_height / sequence->chroma_height; if (sequence->display_width >= SCREEN_WIDTH) { vo.rc_vid.l = 0; vo.rc_vid.r = SCREEN_WIDTH; } else { vo.rc_vid.l = (SCREEN_WIDTH - sequence->display_width) / 2; #ifdef HAVE_LCD_COLOR vo.rc_vid.l &= ~1; #endif vo.rc_vid.r = vo.rc_vid.l + sequence->display_width; } if (sequence->display_height >= SCREEN_HEIGHT) { vo.rc_vid.t = 0; vo.rc_vid.b = SCREEN_HEIGHT; } else { vo.rc_vid.t = (SCREEN_HEIGHT - sequence->display_height) / 2; #ifdef HAVE_LCD_COLOR vo.rc_vid.t &= ~1; #endif vo.rc_vid.b = vo.rc_vid.t + sequence->display_height; } vo_set_clip_rect(&vo.rc_clip); } void vo_dimensions(struct vo_ext *sz) { sz->w = vo.display_width; sz->h = vo.display_height; } bool vo_init(void) { vo.flags = 0; vo_rect_set_ext(&vo.rc_clip, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); video_lock_init(); return true; } bool vo_show(bool show) { bool vis = vo.flags & VO_VISIBLE; if (show) vo.flags |= VO_VISIBLE; else vo.flags &= ~VO_VISIBLE; return vis; } bool vo_is_visible(void) { return vo.flags & VO_VISIBLE; } void vo_cleanup(void) { vo.flags = 0; } void vo_set_clip_rect(const struct vo_rect *rc) { struct vo_rect rc_out; if (rc == NULL) vo_rect_set_ext(&vo.rc_clip, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); else vo.rc_clip = *rc; if (!vo_rect_intersect(&rc_out, &vo.rc_vid, &vo.rc_clip)) vo.flags &= ~VO_NON_NULL_RECT; else vo.flags |= VO_NON_NULL_RECT; vo.output_x = rc_out.l; vo.output_y = rc_out.t; vo.output_width = rc_out.r - rc_out.l; 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) { video_lock(); } void vo_unlock(void) { video_unlock(); } #endif