| /*************************************************************************** |
| * __________ __ ___. |
| * 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 "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; |
| }; |
| |
| #ifdef PROC_NEEDS_CACHEALIGN |
| /* Cache aligned and padded to avoid clobbering other processors' cacheable |
| * data */ |
| static uint8_t __vo_data[CACHEALIGN_UP(sizeof(struct vo_data))] |
| CACHEALIGN_ATTR; |
| #define vo (*((struct vo_data *)__vo_data)) |
| #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(void) |
| { |
| int foreground; |
| |
| video_lock(); |
| |
| foreground = lcd_(get_foreground)(); |
| |
| lcd_(set_foreground)(DRAW_BLACK); |
| |
| lcd_(fillrect)(vo.output_x, vo.output_y, vo.output_width, |
| vo.output_height); |
| lcd_(update_rect)(vo.output_x, vo.output_y, vo.output_width, |
| vo.output_height); |
| |
| lcd_(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 == 0) |
| { |
| /* Frame is hidden - copout */ |
| DEBUGF("vo hidden\n"); |
| return; |
| } |
| else if (buf == NULL) |
| { |
| /* No frame exists - draw black */ |
| vo_draw_black(); |
| DEBUGF("vo no frame\n"); |
| return; |
| } |
| |
| yuv_blit(buf, 0, 0, vo.image_width, |
| vo.output_x, vo.output_y, vo.output_width, |
| vo.output_height); |
| } |
| |
| 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; |
| uint8_t *yuv[3]; |
| struct vo_rect thumb_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; |
| |
| 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; |
| 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); |
| |
| /* 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"); |
| return false; |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| #if NUM_CORES > 1 |
| void vo_lock(void) |
| { |
| video_lock(); |
| } |
| |
| void vo_unlock(void) |
| { |
| video_unlock(); |
| } |
| #endif |