From 9d3d925295112a0080bc1d70fad170db9e1af2a9 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Thu, 13 Oct 2022 11:04:12 -0400 Subject: Revert "RFC: Get rid of mpegplayer plugin" This reverts commit d25d24812e8120c0eb133a412287ac030eb185c9. Change-Id: I1563223e343fb1e2eda72a45823b38350025ff93 --- apps/lang/english.lang | 291 +- apps/plugins/CATEGORIES | 1 + apps/plugins/SUBDIRS | 4 + apps/plugins/bitmaps/mono/SOURCES | 5 + .../mono/mpegplayer_status_icons_12x12x1.bmp | Bin 0 -> 158 bytes .../mono/mpegplayer_status_icons_16x16x1.bmp | Bin 0 -> 254 bytes .../bitmaps/mono/mpegplayer_status_icons_8x8x1.bmp | Bin 0 -> 126 bytes apps/plugins/mpegplayer/SOURCES | 35 + apps/plugins/mpegplayer/alloc.c | 233 ++ apps/plugins/mpegplayer/audio_thread.c | 721 +++++ apps/plugins/mpegplayer/disk_buf.c | 989 +++++++ apps/plugins/mpegplayer/disk_buf.h | 152 + apps/plugins/mpegplayer/libmpeg2/AUTHORS | 33 + apps/plugins/mpegplayer/libmpeg2/README | 204 ++ apps/plugins/mpegplayer/libmpeg2/README.rockbox | 44 + apps/plugins/mpegplayer/libmpeg2/attributes.h | 42 + apps/plugins/mpegplayer/libmpeg2/decode.c | 527 ++++ apps/plugins/mpegplayer/libmpeg2/header.c | 1287 +++++++++ apps/plugins/mpegplayer/libmpeg2/idct.c | 274 ++ apps/plugins/mpegplayer/libmpeg2/idct_arm.S | 443 +++ apps/plugins/mpegplayer/libmpeg2/idct_armv6.S | 297 ++ apps/plugins/mpegplayer/libmpeg2/idct_coldfire.S | 575 ++++ apps/plugins/mpegplayer/libmpeg2/motion_comp.c | 66 + apps/plugins/mpegplayer/libmpeg2/motion_comp.h | 86 + .../mpegplayer/libmpeg2/motion_comp_arm_c.c | 39 + .../mpegplayer/libmpeg2/motion_comp_arm_s.S | 342 +++ apps/plugins/mpegplayer/libmpeg2/motion_comp_c.c | 40 + .../mpegplayer/libmpeg2/motion_comp_coldfire_c.c | 38 + .../mpegplayer/libmpeg2/motion_comp_coldfire_s.S | 436 +++ apps/plugins/mpegplayer/libmpeg2/mpeg2.h | 223 ++ apps/plugins/mpegplayer/libmpeg2/mpeg2_internal.h | 274 ++ apps/plugins/mpegplayer/libmpeg2/mpeg2dec_config.h | 15 + apps/plugins/mpegplayer/libmpeg2/slice.c | 2898 ++++++++++++++++++++ apps/plugins/mpegplayer/libmpeg2/vlc.h | 433 +++ apps/plugins/mpegplayer/mpeg_alloc.h | 32 + apps/plugins/mpegplayer/mpeg_misc.c | 227 ++ apps/plugins/mpegplayer/mpeg_misc.h | 258 ++ apps/plugins/mpegplayer/mpeg_parser.c | 1203 ++++++++ apps/plugins/mpegplayer/mpeg_settings.c | 1454 ++++++++++ apps/plugins/mpegplayer/mpeg_settings.h | 110 + apps/plugins/mpegplayer/mpeg_stream.h | 122 + apps/plugins/mpegplayer/mpegplayer.c | 2638 ++++++++++++++++++ apps/plugins/mpegplayer/mpegplayer.h | 95 + apps/plugins/mpegplayer/mpegplayer.make | 32 + apps/plugins/mpegplayer/parser.h | 103 + apps/plugins/mpegplayer/pcm_output.c | 396 +++ apps/plugins/mpegplayer/pcm_output.h | 48 + apps/plugins/mpegplayer/stream_mgr.c | 1163 ++++++++ apps/plugins/mpegplayer/stream_mgr.h | 168 ++ apps/plugins/mpegplayer/stream_thread.h | 201 ++ apps/plugins/mpegplayer/video_out.h | 102 + apps/plugins/mpegplayer/video_out_rockbox.c | 576 ++++ apps/plugins/mpegplayer/video_thread.c | 1059 +++++++ apps/plugins/viewers.config | 4 + docs/MAINTAINERS | 1 + lib/rbcodec/codecs/libmad/libmad.make | 25 + lib/rbcodec/codecs/libmad/mad_iram.h | 7 +- lib/rbcodec/codecs/mpa.c | 2 +- manual/plugins/main.tex | 2 + manual/plugins/mpegplayer.tex | 119 + 60 files changed, 21189 insertions(+), 5 deletions(-) create mode 100644 apps/plugins/bitmaps/mono/mpegplayer_status_icons_12x12x1.bmp create mode 100644 apps/plugins/bitmaps/mono/mpegplayer_status_icons_16x16x1.bmp create mode 100644 apps/plugins/bitmaps/mono/mpegplayer_status_icons_8x8x1.bmp create mode 100644 apps/plugins/mpegplayer/SOURCES create mode 100644 apps/plugins/mpegplayer/alloc.c create mode 100644 apps/plugins/mpegplayer/audio_thread.c create mode 100644 apps/plugins/mpegplayer/disk_buf.c create mode 100644 apps/plugins/mpegplayer/disk_buf.h create mode 100644 apps/plugins/mpegplayer/libmpeg2/AUTHORS create mode 100644 apps/plugins/mpegplayer/libmpeg2/README create mode 100644 apps/plugins/mpegplayer/libmpeg2/README.rockbox create mode 100644 apps/plugins/mpegplayer/libmpeg2/attributes.h create mode 100644 apps/plugins/mpegplayer/libmpeg2/decode.c create mode 100644 apps/plugins/mpegplayer/libmpeg2/header.c create mode 100644 apps/plugins/mpegplayer/libmpeg2/idct.c create mode 100644 apps/plugins/mpegplayer/libmpeg2/idct_arm.S create mode 100644 apps/plugins/mpegplayer/libmpeg2/idct_armv6.S create mode 100644 apps/plugins/mpegplayer/libmpeg2/idct_coldfire.S create mode 100644 apps/plugins/mpegplayer/libmpeg2/motion_comp.c create mode 100644 apps/plugins/mpegplayer/libmpeg2/motion_comp.h create mode 100644 apps/plugins/mpegplayer/libmpeg2/motion_comp_arm_c.c create mode 100644 apps/plugins/mpegplayer/libmpeg2/motion_comp_arm_s.S create mode 100644 apps/plugins/mpegplayer/libmpeg2/motion_comp_c.c create mode 100644 apps/plugins/mpegplayer/libmpeg2/motion_comp_coldfire_c.c create mode 100644 apps/plugins/mpegplayer/libmpeg2/motion_comp_coldfire_s.S create mode 100644 apps/plugins/mpegplayer/libmpeg2/mpeg2.h create mode 100644 apps/plugins/mpegplayer/libmpeg2/mpeg2_internal.h create mode 100644 apps/plugins/mpegplayer/libmpeg2/mpeg2dec_config.h create mode 100644 apps/plugins/mpegplayer/libmpeg2/slice.c create mode 100644 apps/plugins/mpegplayer/libmpeg2/vlc.h create mode 100644 apps/plugins/mpegplayer/mpeg_alloc.h create mode 100644 apps/plugins/mpegplayer/mpeg_misc.c create mode 100644 apps/plugins/mpegplayer/mpeg_misc.h create mode 100644 apps/plugins/mpegplayer/mpeg_parser.c create mode 100644 apps/plugins/mpegplayer/mpeg_settings.c create mode 100644 apps/plugins/mpegplayer/mpeg_settings.h create mode 100644 apps/plugins/mpegplayer/mpeg_stream.h create mode 100644 apps/plugins/mpegplayer/mpegplayer.c create mode 100644 apps/plugins/mpegplayer/mpegplayer.h create mode 100644 apps/plugins/mpegplayer/mpegplayer.make create mode 100644 apps/plugins/mpegplayer/parser.h create mode 100644 apps/plugins/mpegplayer/pcm_output.c create mode 100644 apps/plugins/mpegplayer/pcm_output.h create mode 100644 apps/plugins/mpegplayer/stream_mgr.c create mode 100644 apps/plugins/mpegplayer/stream_mgr.h create mode 100644 apps/plugins/mpegplayer/stream_thread.h create mode 100644 apps/plugins/mpegplayer/video_out.h create mode 100644 apps/plugins/mpegplayer/video_out_rockbox.c create mode 100644 apps/plugins/mpegplayer/video_thread.c create mode 100644 manual/plugins/mpegplayer.tex diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 990bc9fbcd..e8d646b258 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -14263,9 +14263,128 @@ *: "View Played Games" + + id: LANG_MENU_AUDIO_OPTIONS + desc: in mpegplayer menus + user: core + + *: "Audio Options" + lowmem: none + + + *: "Audio Options" + lowmem: none + + + *: "Audio Options" + lowmem: none + + + + id: LANG_MENU_RESUME_OPTIONS + desc: in mpegplayer menus + user: core + + *: "Resume Options" + lowmem: none + + + *: "Resume Options" + lowmem: none + + + *: "Resume Options" + lowmem: none + + + + id: LANG_MENU_PLAY_MODE + desc: in mpegplayer menus + user: core + + *: "Play Mode" + lowmem: none + + + *: "Play Mode" + lowmem: none + + + *: "Play Mode" + lowmem: none + + + + id: LANG_SINGLE + desc: in mpegplayer menus + user: core + + *: "Single" + lowmem: none + + + *: "Single" + lowmem: none + + + *: "Single" + lowmem: none + + + + id: LANG_USE_SOUND_SETTING + desc: in mpegplayer menus + user: core + + *: "Use sound setting" + lowmem: none + + + *: "Use sound setting" + lowmem: none + + + *: "Use sound setting" + lowmem: none + + + + id: LANG_RESTART_PLAYBACK + desc: in the mpegplayer settings menu + user: core + + *: "Play from beginning" + lowmem: none + + + *: "Play from beginning" + lowmem: none + + + *: "Play from beginning" + lowmem: none + + + + id: LANG_SET_RESUME_TIME + desc: in the mpegplayer settings menu + user: core + + *: "Set resume time (min)" + lowmem: none + + + *: "Set resume time (min)" + lowmem: none + + + *: "Set resume time" + lowmem: none + + id: LANG_DISPLAY_FPS - desc: in the pictureflow settings menus + desc: in the mpegplayer and pictureflow settings menus user: core *: "Display FPS" @@ -14277,6 +14396,176 @@ *: "Display FPS" + + id: LANG_LIMIT_FPS + desc: in the mpegplayer settings menu + user: core + + *: "Limit FPS" + lowmem: none + + + *: "Limit FPS" + lowmem: none + + + *: "Limit FPS" + lowmem: none + + + + id: LANG_SKIP_FRAMES + desc: in the mpegplayer settings menu + user: core + + *: "Skip frames" + lowmem: none + + + *: "Skip frames" + lowmem: none + + + *: "Skip frames" + lowmem: none + + + + id: LANG_BACKLIGHT_BRIGHTNESS + desc: in the mpegplayer settings menu + user: core + + *: "Backlight brightness" + lowmem: none + + + *: "Backlight brightness" + lowmem: none + + + *: "Backlight brightness" + lowmem: none + + + + id: LANG_USE_COMMON_SETTING + desc: in the mpegplayer settings menu + user: core + + *: "Use common setting" + lowmem: none + + + *: "Use common setting" + lowmem: none + + + *: "Use common setting" + lowmem: none + + + + id: LANG_TONE_CONTROLS + desc: in the mpegplayer settings menu + user: core + + *: "Tone controls" + lowmem: none + + + *: "Tone controls" + lowmem: none + + + *: "Tone controls" + lowmem: none + + + + id: LANG_FORCE_START_MENU + desc: in mpegplayer menus + user: core + + *: "Start menu" + lowmem: none + + + *: "Start menu" + lowmem: none + + + *: "Start menu" + lowmem: none + + + + id: LANG_CONDITIONAL_START_MENU + desc: in mpegplayer menus + user: core + + *: "Start menu if not completed" + lowmem: none + + + *: "Start menu if not completed" + lowmem: none + + + *: "Start menu if not completed" + lowmem: none + + + + id: LANG_AUTO_RESUME + desc: in mpegplayer menus + user: core + + *: "Resume automatically" + lowmem: none + + + *: "Resume automatically" + lowmem: none + + + *: "Resume automatically" + lowmem: none + + + + id: LANG_CLEAR_ALL_RESUMES + desc: in the mpegplayer settings menu + user: core + + *: "Clear all resumes" + lowmem: none + + + *: "Clear all resumes" + lowmem: none + + + *: "Clear all resumes" + lowmem: none + + + + id: LANG_UNAVAILABLE + desc: in mpegplayer settings + user: core + + *: "Unavailable" + lowmem: none + + + *: "Unavailable" + lowmem: none + + + *: "Unavailable" + lowmem: none + + id: LANG_TOGGLE_ITEM desc: in main_menu_config diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 13db81f004..f2ab4843c2 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -68,6 +68,7 @@ mikmod,viewers minesweeper,games mosaique,demos mp3_encoder,apps +mpegplayer,viewers multiboot_select,apps nim,games open_plugins,viewers diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS index 23cd029ccc..4cb57edb1b 100644 --- a/apps/plugins/SUBDIRS +++ b/apps/plugins/SUBDIRS @@ -78,6 +78,10 @@ mikmod pdbox #endif +#if !defined(RB_PROFILE) && MEMORYSIZE > 2 /* mpegplayer allocates at least 2MB of RAM */ +mpegplayer +#endif + /* Lua needs at least 160 KB to work in */ #if PLUGIN_BUFFER_SIZE >= 0x80000 lua diff --git a/apps/plugins/bitmaps/mono/SOURCES b/apps/plugins/bitmaps/mono/SOURCES index df8521dd0a..eb00bd9e8a 100644 --- a/apps/plugins/bitmaps/mono/SOURCES +++ b/apps/plugins/bitmaps/mono/SOURCES @@ -49,6 +49,11 @@ invadrox_fire.6x6x1.bmp #endif #endif +/* MPEGplayer */ +mpegplayer_status_icons_8x8x1.bmp +mpegplayer_status_icons_12x12x1.bmp +mpegplayer_status_icons_16x16x1.bmp + #if LCD_WIDTH == 160 && LCD_HEIGHT == 128 && LCD_DEPTH < 16 superdom_boarditems.160x128x1.bmp #endif diff --git a/apps/plugins/bitmaps/mono/mpegplayer_status_icons_12x12x1.bmp b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_12x12x1.bmp new file mode 100644 index 0000000000..61f6a52fbc Binary files /dev/null and b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_12x12x1.bmp differ diff --git a/apps/plugins/bitmaps/mono/mpegplayer_status_icons_16x16x1.bmp b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_16x16x1.bmp new file mode 100644 index 0000000000..944bd5132e Binary files /dev/null and b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_16x16x1.bmp differ diff --git a/apps/plugins/bitmaps/mono/mpegplayer_status_icons_8x8x1.bmp b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_8x8x1.bmp new file mode 100644 index 0000000000..d4b71fe1e0 Binary files /dev/null and b/apps/plugins/bitmaps/mono/mpegplayer_status_icons_8x8x1.bmp differ diff --git a/apps/plugins/mpegplayer/SOURCES b/apps/plugins/mpegplayer/SOURCES new file mode 100644 index 0000000000..3fc079dfbd --- /dev/null +++ b/apps/plugins/mpegplayer/SOURCES @@ -0,0 +1,35 @@ +libmpeg2/decode.c +libmpeg2/header.c +libmpeg2/idct.c +libmpeg2/motion_comp.c +libmpeg2/slice.c + +#ifdef CPU_COLDFIRE +libmpeg2/idct_coldfire.S +libmpeg2/motion_comp_coldfire_c.c +libmpeg2/motion_comp_coldfire_s.S +#elif defined CPU_ARM +#if ARM_ARCH >= 6 +libmpeg2/idct_armv6.S +#else +libmpeg2/idct_arm.S +#endif +libmpeg2/motion_comp_arm_c.c +libmpeg2/motion_comp_arm_s.S +#else /* other CPU or SIM */ +libmpeg2/motion_comp_c.c +#endif /* CPU_* */ + + + +alloc.c +video_out_rockbox.c +video_thread.c +pcm_output.c +audio_thread.c +disk_buf.c +mpeg_settings.c +stream_mgr.c +mpegplayer.c +mpeg_parser.c +mpeg_misc.c diff --git a/apps/plugins/mpegplayer/alloc.c b/apps/plugins/mpegplayer/alloc.c new file mode 100644 index 0000000000..cbf930a7eb --- /dev/null +++ b/apps/plugins/mpegplayer/alloc.c @@ -0,0 +1,233 @@ +/* + * alloc.c + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + * libmpeg2 sync history: + * 2008-07-01 - CVS revision 1.13 + */ + +#include "plugin.h" +#include "mpegplayer.h" +#include + +/* Main allocator */ +static off_t mem_ptr; +static size_t bufsize; +static unsigned char* mallocbuf; + +/* libmpeg2 allocator */ +static off_t mpeg2_mem_ptr SHAREDBSS_ATTR; +static size_t mpeg2_bufsize SHAREDBSS_ATTR; +static unsigned char *mpeg2_mallocbuf SHAREDBSS_ATTR; +static unsigned char *mpeg2_bufallocbuf SHAREDBSS_ATTR; + +#if defined(DEBUG) || defined(SIMULATOR) +const char * mpeg_get_reason_str(int reason) +{ + const char *str; + + switch (reason) + { + case MPEG2_ALLOC_MPEG2DEC: + str = "MPEG2_ALLOC_MPEG2DEC"; + break; + case MPEG2_ALLOC_CHUNK: + str = "MPEG2_ALLOC_CHUNK"; + break; + case MPEG2_ALLOC_YUV: + str = "MPEG2_ALLOC_YUV"; + break; + case MPEG2_ALLOC_CONVERT_ID: + str = "MPEG2_ALLOC_CONVERT_ID"; + break; + case MPEG2_ALLOC_CONVERTED: + str = "MPEG2_ALLOC_CONVERTED"; + break; + case MPEG_ALLOC_MPEG2_BUFFER: + str = "MPEG_ALLOC_MPEG2_BUFFER"; + break; + case MPEG_ALLOC_AUDIOBUF: + str = "MPEG_ALLOC_AUDIOBUF"; + break; + case MPEG_ALLOC_PCMOUT: + str = "MPEG_ALLOC_PCMOUT"; + break; + case MPEG_ALLOC_DISKBUF: + str = "MPEG_ALLOC_DISKBUF"; + break; + case MPEG_ALLOC_CODEC_MALLOC: + str = "MPEG_ALLOC_CODEC_MALLOC"; + break; + case MPEG_ALLOC_CODEC_CALLOC: + str = "MPEG_ALLOC_CODEC_CALLOC"; + break; + default: + str = "Unknown"; + } + + return str; +} +#endif + +static void * mpeg_malloc_internal (unsigned char *mallocbuf, + off_t *mem_ptr, + size_t bufsize, + unsigned size, + int reason) +{ + void *x; + + DEBUGF("mpeg_alloc_internal: bs:%lu s:%u reason:%s (%d)\n", + (unsigned long)bufsize, size, mpeg_get_reason_str(reason), reason); + + if ((size_t) (*mem_ptr + size) > bufsize) + { + DEBUGF("OUT OF MEMORY\n"); + return NULL; + } + + x = &mallocbuf[*mem_ptr]; + *mem_ptr += (size + 3) & ~3; /* Keep memory 32-bit aligned */ + + return x; + (void)reason; +} + +void *mpeg_malloc(size_t size, mpeg2_alloc_t reason) +{ + return mpeg_malloc_internal(mallocbuf, &mem_ptr, bufsize, size, + reason); +} + +void *mpeg_malloc_all(size_t *size_out, mpeg2_alloc_t reason) +{ + /* Can steal all but MIN_MEMMARGIN */ + if (bufsize - mem_ptr < MIN_MEMMARGIN) + return NULL; + + *size_out = bufsize - mem_ptr - MIN_MEMMARGIN; + return mpeg_malloc(*size_out, reason); +} + +bool mpeg_alloc_init(unsigned char *buf, size_t mallocsize) +{ + mem_ptr = 0; + /* Cache-align buffer or 4-byte align */ + mallocbuf = buf; + bufsize = mallocsize; + ALIGN_BUFFER(mallocbuf, bufsize, CACHEALIGN_UP(4)); + + /* Separate allocator for video */ + mpeg2_mem_ptr = 0; + mpeg2_mallocbuf = mallocbuf; + mpeg2_bufallocbuf = mallocbuf; + mpeg2_bufsize = CACHEALIGN_UP(LIBMPEG2_ALLOC_SIZE); + + if (mpeg_malloc_internal(mallocbuf, &mem_ptr, + bufsize, mpeg2_bufsize, + MPEG_ALLOC_MPEG2_BUFFER) == NULL) + { + return false; + } + + IF_COP(rb->commit_discard_dcache()); + return true; +} + +/* allocate non-dedicated buffer space which mpeg2_mem_reset will free */ +void * mpeg2_malloc(unsigned size, mpeg2_alloc_t reason) +{ + void *ptr = mpeg_malloc_internal(mpeg2_mallocbuf, &mpeg2_mem_ptr, + mpeg2_bufsize, size, reason); + /* libmpeg2 expects zero-initialized allocations */ + if (ptr) + rb->memset(ptr, 0, size); + + return ptr; +} + +/* allocate dedicated buffer - memory behind buffer pointer becomes dedicated + so order is important */ +void * mpeg2_bufalloc(unsigned size, mpeg2_alloc_t reason) +{ + void *buf = mpeg2_malloc(size, reason); + + if (buf == NULL) + return NULL; + + mpeg2_bufallocbuf = &mpeg2_mallocbuf[mpeg2_mem_ptr]; + return buf; +} + +/* return unused buffer portion and size */ +void * mpeg2_get_buf(size_t *size) +{ + if ((size_t)mpeg2_mem_ptr + 32 >= mpeg2_bufsize) + return NULL; + + *size = mpeg2_bufsize - mpeg2_mem_ptr; + return &mpeg2_mallocbuf[mpeg2_mem_ptr]; +} + +/* de-allocate all non-dedicated buffer space */ +void mpeg2_mem_reset(void) +{ + DEBUGF("mpeg2_mem_reset\n"); + mpeg2_mem_ptr = mpeg2_bufallocbuf - mpeg2_mallocbuf; +} + +/* The following are expected by libmad */ +void * codec_malloc(size_t size) +{ + void* ptr; + + ptr = mpeg_malloc_internal(mallocbuf, &mem_ptr, + bufsize, size, MPEG_ALLOC_CODEC_MALLOC); + + if (ptr) + rb->memset(ptr,0,size); + + return ptr; +} + +void * codec_calloc(size_t nmemb, size_t size) +{ + void* ptr; + + ptr = mpeg_malloc_internal(mallocbuf, &mem_ptr, + bufsize, nmemb*size, + MPEG_ALLOC_CODEC_CALLOC); + + if (ptr) + rb->memset(ptr,0,size); + + return ptr; +} + +void codec_free(void* ptr) +{ + DEBUGF("codec_free - %p\n", ptr); +#if 0 + mem_ptr = (void *)ptr - (void *)mallocbuf; +#endif + (void)ptr; +} diff --git a/apps/plugins/mpegplayer/audio_thread.c b/apps/plugins/mpegplayer/audio_thread.c new file mode 100644 index 0000000000..764ad111f2 --- /dev/null +++ b/apps/plugins/mpegplayer/audio_thread.c @@ -0,0 +1,721 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * mpegplayer audio thread implementation + * + * Copyright (c) 2007 Michael Sevakis + * + * 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 "plugin.h" +#include "mpegplayer.h" +#include "codecs/libmad/bit.h" +#include "codecs/libmad/mad.h" + +/** Audio stream and thread **/ +struct pts_queue_slot; +struct audio_thread_data +{ + struct queue_event ev; /* Our event queue to receive commands */ + int state; /* Thread state */ + int status; /* Media status (STREAM_PLAYING, etc.) */ + int mad_errors; /* A count of the errors in each frame */ + unsigned samplerate; /* Current stream sample rate */ + int nchannels; /* Number of audio channels */ + struct dsp_config *dsp; /* The DSP we're using */ + struct dsp_buffer src; /* Current audio data for DSP processing */ +}; + +/* The audio thread is stolen from the core codec thread */ +static struct event_queue audio_str_queue SHAREDBSS_ATTR; +static struct queue_sender_list audio_str_queue_send SHAREDBSS_ATTR; +struct stream audio_str IBSS_ATTR; + +/* libmad related definitions */ +static struct mad_stream stream IBSS_ATTR; +static struct mad_frame frame IBSS_ATTR; +static struct mad_synth synth IBSS_ATTR; + +/*sbsample buffer for mad_frame*/ +mad_fixed_t sbsample[2][36][32]; + +/* 2567 bytes */ +static unsigned char mad_main_data[MAD_BUFFER_MDLEN]; + +/* There isn't enough room for this in IRAM on PortalPlayer, but there + is for Coldfire. */ + +/* 4608 bytes */ +#if defined(CPU_COLDFIRE) || defined(CPU_S5L870X) +static mad_fixed_t mad_frame_overlap[2][32][18] IBSS_ATTR; +#else +static mad_fixed_t mad_frame_overlap[2][32][18]; +#endif + +/** A queue for saving needed information about MPEG audio packets **/ +#define AUDIODESC_QUEUE_LEN (1 << 5) /* 32 should be way more than sufficient - + if not, the case is handled */ +#define AUDIODESC_QUEUE_MASK (AUDIODESC_QUEUE_LEN-1) +struct audio_frame_desc +{ + uint32_t time; /* Time stamp for packet in audio ticks */ + ssize_t size; /* Number of unprocessed bytes left in packet */ +}; + + /* This starts out wr == rd but will never be emptied to zero during + streaming again in order to support initializing the first packet's + timestamp without a special case */ +struct +{ + /* Compressed audio data */ + uint8_t *start; /* Start of encoded audio buffer */ + uint8_t *ptr; /* Pointer to next encoded audio data */ + ssize_t used; /* Number of bytes in MPEG audio buffer */ + /* Compressed audio data descriptors */ + unsigned read, write; + struct audio_frame_desc *curr; /* Current slot */ + struct audio_frame_desc descs[AUDIODESC_QUEUE_LEN]; +} audio_queue; + +static inline int audiodesc_queue_count(void) +{ + return audio_queue.write - audio_queue.read; +} + +static inline bool audiodesc_queue_full(void) +{ + return audio_queue.used >= MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD || + audiodesc_queue_count() >= AUDIODESC_QUEUE_LEN; +} + +/* Increments the queue tail postion - should be used to preincrement */ +static inline void audiodesc_queue_add_tail(void) +{ + if (audiodesc_queue_full()) + { + DEBUGF("audiodesc_queue_add_tail: audiodesc queue full!\n"); + return; + } + + audio_queue.write++; +} + +/* Increments the queue head position - leaves one slot as current */ +static inline bool audiodesc_queue_remove_head(void) +{ + if (audio_queue.write == audio_queue.read) + return false; + + audio_queue.read++; + return true; +} + +/* Returns the "tail" at the index just behind the write index */ +static inline struct audio_frame_desc * audiodesc_queue_tail(void) +{ + return &audio_queue.descs[(audio_queue.write - 1) & AUDIODESC_QUEUE_MASK]; +} + +/* Returns a pointer to the current head */ +static inline struct audio_frame_desc * audiodesc_queue_head(void) +{ + return &audio_queue.descs[audio_queue.read & AUDIODESC_QUEUE_MASK]; +} + +/* Resets the pts queue - call when starting and seeking */ +static void audio_queue_reset(void) +{ + audio_queue.ptr = audio_queue.start; + audio_queue.used = 0; + audio_queue.read = 0; + audio_queue.write = 0; + rb->memset(audio_queue.descs, 0, sizeof (audio_queue.descs)); + audio_queue.curr = audiodesc_queue_head(); +} + +static void audio_queue_advance_pos(ssize_t len) +{ + audio_queue.ptr += len; + audio_queue.used -= len; + audio_queue.curr->size -= len; +} + +static int audio_buffer(struct stream *str, enum stream_parse_mode type) +{ + int ret = STREAM_OK; + + /* Carry any overshoot to the next size since we're technically + -size bytes into it already. If size is negative an audio + frame was split across packets. Old has to be saved before + moving the head. */ + if (audio_queue.curr->size <= 0 && audiodesc_queue_remove_head()) + { + struct audio_frame_desc *old = audio_queue.curr; + audio_queue.curr = audiodesc_queue_head(); + audio_queue.curr->size += old->size; + old->size = 0; + } + + /* Add packets to compressed audio buffer until it's full or the + * timestamp queue is full - whichever happens first */ + while (!audiodesc_queue_full()) + { + ret = parser_get_next_data(str, type); + struct audio_frame_desc *curr; + ssize_t len; + + if (ret != STREAM_OK) + break; + + /* Get data from next audio packet */ + len = str->curr_packet_end - str->curr_packet; + + if (str->pkt_flags & PKT_HAS_TS) + { + audiodesc_queue_add_tail(); + curr = audiodesc_queue_tail(); + curr->time = TS_TO_TICKS(str->pts); + /* pts->size should have been zeroed when slot was + freed */ + } + else + { + /* Add to the one just behind the tail - this may be + * the head or the previouly added tail - whether or + * not we'll ever reach this is quite in question + * since audio always seems to have every packet + * timestamped */ + curr = audiodesc_queue_tail(); + } + + curr->size += len; + + /* Slide any remainder over to beginning */ + if (audio_queue.ptr > audio_queue.start && audio_queue.used > 0) + { + rb->memmove(audio_queue.start, audio_queue.ptr, + audio_queue.used); + } + + /* Splice this packet onto any remainder */ + rb->memcpy(audio_queue.start + audio_queue.used, + str->curr_packet, len); + + audio_queue.used += len; + audio_queue.ptr = audio_queue.start; + + rb->yield(); + } + + return ret; +} + +/* Initialise libmad */ +static void init_mad(void) +{ + /* init the sbsample buffer */ + frame.sbsample_prev = &sbsample; + frame.sbsample = &sbsample; + + /* We do this so libmad doesn't try to call codec_calloc(). This needs to + * be called before mad_stream_init(), mad_frame_inti() and + * mad_synth_init(). */ + frame.overlap = &mad_frame_overlap; + stream.main_data = &mad_main_data; + + /* Call mad initialization. Those will zero the arrays frame.overlap, + * frame.sbsample and frame.sbsample_prev. Therefore there is no need to + * zero them here. */ + mad_stream_init(&stream); + mad_frame_init(&frame); + mad_synth_init(&synth); +} + +/* Sync audio stream to a particular frame - see main decoder loop for + * detailed remarks */ +static int audio_sync(struct audio_thread_data *td, + struct str_sync_data *sd) +{ + int retval = STREAM_MATCH; + uint32_t sdtime = TS_TO_TICKS(clip_time(&audio_str, sd->time)); + uint32_t time; + uint32_t duration = 0; + struct stream *str; + struct stream tmp_str; + struct mad_header header; + struct mad_stream stream; + + if (td->ev.id == STREAM_SYNC) + { + /* Actually syncing for playback - use real stream */ + time = 0; + str = &audio_str; + } + else + { + /* Probing - use temp stream */ + time = INVALID_TIMESTAMP; + str = &tmp_str; + str->id = audio_str.id; + } + + str->hdr.pos = sd->sk.pos; + str->hdr.limit = sd->sk.pos + sd->sk.len; + + mad_stream_init(&stream); + mad_header_init(&header); + + while (1) + { + if (audio_buffer(str, STREAM_PM_RANDOM_ACCESS) == STREAM_DATA_END) + { + DEBUGF("audio_sync:STR_DATA_END\n aqu:%ld swl:%ld swr:%ld\n", + (long)audio_queue.used, str->hdr.win_left, str->hdr.win_right); + if (audio_queue.used <= MAD_BUFFER_GUARD) + goto sync_data_end; + } + + stream.error = 0; + mad_stream_buffer(&stream, audio_queue.ptr, audio_queue.used); + + if (stream.sync && mad_stream_sync(&stream) < 0) + { + DEBUGF(" audio: mad_stream_sync failed\n"); + audio_queue_advance_pos(MAX(audio_queue.curr->size - 1, 1)); + continue; + } + + stream.sync = 0; + + if (mad_header_decode(&header, &stream) < 0) + { + DEBUGF(" audio: mad_header_decode failed:%s\n", + mad_stream_errorstr(&stream)); + audio_queue_advance_pos(1); + continue; + } + + duration = 32*MAD_NSBSAMPLES(&header); + time = audio_queue.curr->time; + + DEBUGF(" audio: ft:%u t:%u fe:%u nsamp:%u sampr:%u\n", + (unsigned)TICKS_TO_TS(time), (unsigned)sd->time, + (unsigned)TICKS_TO_TS(time + duration), + (unsigned)duration, header.samplerate); + + audio_queue_advance_pos(stream.this_frame - audio_queue.ptr); + + if (time <= sdtime && sdtime < time + duration) + { + DEBUGF(" audio: ft<=t sdtime) + { + DEBUGF(" audio: ft>t\n"); + break; + } + + audio_queue_advance_pos(stream.next_frame - audio_queue.ptr); + audio_queue.curr->time += duration; + + rb->yield(); + } + +sync_data_end: + if (td->ev.id == STREAM_FIND_END_TIME) + { + if (time != INVALID_TIMESTAMP) + { + time = TICKS_TO_TS(time); + duration = TICKS_TO_TS(duration); + sd->time = time + duration; + retval = STREAM_PERFECT_MATCH; + } + else + { + retval = STREAM_NOT_FOUND; + } + } + + DEBUGF(" audio header: 0x%02X%02X%02X%02X\n", + (unsigned)audio_queue.ptr[0], (unsigned)audio_queue.ptr[1], + (unsigned)audio_queue.ptr[2], (unsigned)audio_queue.ptr[3]); + + return retval; + (void)td; +} + +static void audio_thread_msg(struct audio_thread_data *td) +{ + while (1) + { + intptr_t reply = 0; + + switch (td->ev.id) + { + case STREAM_PLAY: + td->status = STREAM_PLAYING; + + switch (td->state) + { + case TSTATE_INIT: + td->state = TSTATE_DECODE; + case TSTATE_DECODE: + case TSTATE_RENDER_WAIT: + break; + + case TSTATE_EOS: + /* At end of stream - no playback possible so fire the + * completion event */ + stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0); + break; + } + + break; + + case STREAM_PAUSE: + td->status = STREAM_PAUSED; + reply = td->state != TSTATE_EOS; + break; + + case STREAM_STOP: + if (td->state == TSTATE_DATA) + stream_clear_notify(&audio_str, DISK_BUF_DATA_NOTIFY); + + td->status = STREAM_STOPPED; + td->state = TSTATE_EOS; + + reply = true; + break; + + case STREAM_RESET: + if (td->state == TSTATE_DATA) + stream_clear_notify(&audio_str, DISK_BUF_DATA_NOTIFY); + + td->status = STREAM_STOPPED; + td->state = TSTATE_INIT; + td->samplerate = 0; + td->nchannels = 0; + + init_mad(); + td->mad_errors = 0; + + audio_queue_reset(); + + reply = true; + break; + + case STREAM_NEEDS_SYNC: + reply = true; /* Audio always needs to */ + break; + + case STREAM_SYNC: + case STREAM_FIND_END_TIME: + if (td->state != TSTATE_INIT) + break; + + reply = audio_sync(td, (struct str_sync_data *)td->ev.data); + break; + + case DISK_BUF_DATA_NOTIFY: + /* Our bun is done */ + if (td->state != TSTATE_DATA) + break; + + td->state = TSTATE_DECODE; + str_data_notify_received(&audio_str); + break; + + case STREAM_QUIT: + /* Time to go - make thread exit */ + td->state = TSTATE_EOS; + return; + } + + str_reply_msg(&audio_str, reply); + + if (td->status == STREAM_PLAYING) + { + switch (td->state) + { + case TSTATE_DECODE: + case TSTATE_RENDER_WAIT: + /* These return when in playing state */ + return; + } + } + + str_get_msg(&audio_str, &td->ev); + } +} + +static void audio_thread(void) +{ + struct audio_thread_data td; +#ifdef HAVE_PRIORITY_SCHEDULING + /* Up the priority since the core DSP over-yields internally */ + int old_priority = rb->thread_set_priority(rb->thread_self(), + PRIORITY_PLAYBACK-4); +#endif + + rb->memset(&td, 0, sizeof (td)); + td.status = STREAM_STOPPED; + td.state = TSTATE_EOS; + + /* We need this here to init the EMAC for Coldfire targets */ + init_mad(); + + td.dsp = rb->dsp_get_config(CODEC_IDX_AUDIO); + rb->dsp_configure(td.dsp, DSP_SET_OUT_FREQUENCY, CLOCK_RATE); +#ifdef HAVE_PITCHCONTROL + rb->sound_set_pitch(PITCH_SPEED_100); + rb->dsp_set_timestretch(PITCH_SPEED_100); +#endif + rb->dsp_configure(td.dsp, DSP_RESET, 0); + rb->dsp_configure(td.dsp, DSP_FLUSH, 0); + rb->dsp_configure(td.dsp, DSP_SET_SAMPLE_DEPTH, MAD_F_FRACBITS); + + goto message_wait; + + /* This is the decoding loop. */ + while (1) + { + td.state = TSTATE_DECODE; + + /* Check for any pending messages and process them */ + if (str_have_msg(&audio_str)) + { + message_wait: + /* Wait for a message to be queued */ + str_get_msg(&audio_str, &td.ev); + + message_process: + /* Process a message already dequeued */ + audio_thread_msg(&td); + + switch (td.state) + { + /* These states are the only ones that should return */ + case TSTATE_DECODE: goto audio_decode; + case TSTATE_RENDER_WAIT: goto render_wait; + /* Anything else is interpreted as an exit */ + default: + { +#ifdef HAVE_PRIORITY_SCHEDULING + rb->thread_set_priority(rb->thread_self(), old_priority); +#endif + return; + } + } + } + + audio_decode: + + /** Buffering **/ + switch (audio_buffer(&audio_str, STREAM_PM_STREAMING)) + { + case STREAM_DATA_NOT_READY: + { + td.state = TSTATE_DATA; + goto message_wait; + } /* STREAM_DATA_NOT_READY: */ + + case STREAM_DATA_END: + { + if (audio_queue.used > MAD_BUFFER_GUARD) + break; /* Still have frames to decode */ + + /* Used up remainder of compressed audio buffer. Wait for + * samples on PCM buffer to finish playing. */ + audio_queue_reset(); + + while (1) + { + if (pcm_output_empty()) + { + td.state = TSTATE_EOS; + stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0); + break; + } + + pcm_output_drain(); + str_get_msg_w_tmo(&audio_str, &td.ev, 1); + + if (td.ev.id != SYS_TIMEOUT) + break; + } + + goto message_wait; + } /* STREAM_DATA_END: */ + } + + /** Decoding **/ + mad_stream_buffer(&stream, audio_queue.ptr, audio_queue.used); + + int mad_stat = mad_frame_decode(&frame, &stream); + + ssize_t len = stream.next_frame - audio_queue.ptr; + + if (mad_stat != 0) + { + DEBUGF("audio: Stream error: %s\n", + mad_stream_errorstr(&stream)); + + /* If something's goofed - try to perform resync by moving + * at least one byte at a time */ + audio_queue_advance_pos(MAX(len, 1)); + + if (stream.error == MAD_ERROR_BUFLEN) + { + /* This makes the codec support partially corrupted files */ + if (++td.mad_errors <= MPA_MAX_FRAME_SIZE) + { + stream.error = 0; + rb->yield(); + continue; + } + DEBUGF("audio: Too many errors\n"); + } + else if (MAD_RECOVERABLE(stream.error)) + { + /* libmad says it can recover - just keep on decoding */ + rb->yield(); + continue; + } + else + { + /* Some other unrecoverable error */ + DEBUGF("audio: Unrecoverable error\n"); + } + + /* This is too hard - bail out */ + td.state = TSTATE_EOS; + td.status = STREAM_ERROR; + stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0); + + goto message_wait; + } + + /* Adjust sizes by the frame size */ + audio_queue_advance_pos(len); + td.mad_errors = 0; /* Clear errors */ + + /* Generate the pcm samples */ + mad_synth_frame(&synth, &frame); + + /** Output **/ + if (frame.header.samplerate != td.samplerate) + { + td.samplerate = frame.header.samplerate; + rb->dsp_configure(td.dsp, DSP_SET_FREQUENCY, + td.samplerate); + } + + if (MAD_NCHANNELS(&frame.header) != td.nchannels) + { + td.nchannels = MAD_NCHANNELS(&frame.header); + rb->dsp_configure(td.dsp, DSP_SET_STEREO_MODE, + td.nchannels == 1 ? + STEREO_MONO : STEREO_NONINTERLEAVED); + } + + td.src.remcount = synth.pcm.length; + td.src.pin[0] = synth.pcm.samples[0]; + td.src.pin[1] = synth.pcm.samples[1]; + td.src.proc_mask = 0; + + td.state = TSTATE_RENDER_WAIT; + + /* Add a frame of audio to the pcm buffer. Maximum is 1152 samples. */ + render_wait: + rb->yield(); + + while (1) + { + struct dsp_buffer dst; + dst.remcount = 0; + dst.bufcount = MAX(td.src.remcount, 1024); + + ssize_t size = dst.bufcount * 2 * sizeof(int16_t); + + /* Wait for required amount of free buffer space */ + while ((dst.p16out = pcm_output_get_buffer(&size)) == NULL) + { + /* Wait one frame */ + int timeout = dst.bufcount*HZ / td.samplerate; + str_get_msg_w_tmo(&audio_str, &td.ev, MAX(timeout, 1)); + if (td.ev.id != SYS_TIMEOUT) + goto message_process; + } + + dst.bufcount = size / (2 * sizeof (int16_t)); + rb->dsp_process(td.dsp, &td.src, &dst); + + if (dst.remcount > 0) + { + /* Make this data available to DMA */ + pcm_output_commit_data(dst.remcount * 2 * sizeof(int16_t), + audio_queue.curr->time); + + /* As long as we're on this timestamp, the time is just + incremented by the number of samples */ + audio_queue.curr->time += dst.remcount; + } + else if (td.src.remcount <= 0) + { + break; + } + } + } /* end decoding loop */ +} + +/* Initializes the audio thread resources and starts the thread */ +bool audio_thread_init(void) +{ + /* Initialise the encoded audio buffer and its descriptors */ + audio_queue.start = mpeg_malloc(AUDIOBUF_ALLOC_SIZE, + MPEG_ALLOC_AUDIOBUF); + if (audio_queue.start == NULL) + return false; + + /* Start the audio thread */ + audio_str.hdr.q = &audio_str_queue; + rb->queue_init(audio_str.hdr.q, false); + + /* We steal the codec thread for audio */ + rb->codec_thread_do_callback(audio_thread, &audio_str.thread); + + rb->queue_enable_queue_send(audio_str.hdr.q, &audio_str_queue_send, + audio_str.thread); + + /* Wait for thread to initialize */ + str_send_msg(&audio_str, STREAM_NULL, 0); + + return true; +} + +/* Stops the audio thread */ +void audio_thread_exit(void) +{ + if (audio_str.thread != 0) + { + str_post_msg(&audio_str, STREAM_QUIT, 0); + rb->codec_thread_do_callback(NULL, NULL); + audio_str.thread = 0; + } +} diff --git a/apps/plugins/mpegplayer/disk_buf.c b/apps/plugins/mpegplayer/disk_buf.c new file mode 100644 index 0000000000..50c4222192 --- /dev/null +++ b/apps/plugins/mpegplayer/disk_buf.c @@ -0,0 +1,989 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * mpegplayer buffering routines + * + * Copyright (c) 2007 Michael Sevakis + * + * 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 "plugin.h" +#include "mpegplayer.h" +#include + +static struct mutex disk_buf_mtx SHAREDBSS_ATTR; +static struct event_queue disk_buf_queue SHAREDBSS_ATTR; +static struct queue_sender_list disk_buf_queue_send SHAREDBSS_ATTR; +static uint32_t disk_buf_stack[DEFAULT_STACK_SIZE*2/sizeof(uint32_t)]; + +struct disk_buf disk_buf SHAREDBSS_ATTR; +static void *nf_list[MPEGPLAYER_MAX_STREAMS+1]; + +static inline void disk_buf_lock(void) +{ + rb->mutex_lock(&disk_buf_mtx); +} + +static inline void disk_buf_unlock(void) +{ + rb->mutex_unlock(&disk_buf_mtx); +} + +static inline void disk_buf_on_clear_data_notify(struct stream_hdr *sh) +{ + DEBUGF("DISK_BUF_CLEAR_DATA_NOTIFY: 0x%02X (cleared)\n", + STR_FROM_HDR(sh)->id); + list_remove_item(nf_list, sh); +} + +inline bool disk_buf_is_data_ready(struct stream_hdr *sh, + ssize_t margin) +{ + /* Data window available? */ + off_t right = sh->win_right; + + /* Margins past end-of-file can still return true */ + if (right > disk_buf.filesize - margin) + right = disk_buf.filesize - margin; + + return sh->win_left >= disk_buf.win_left && + right + margin <= disk_buf.win_right; +} + +void dbuf_l2_init(struct dbuf_l2_cache *l2_p) +{ + l2_p->addr = OFF_T_MAX; /* Mark as invalid */ +} + +static int disk_buf_on_data_notify(struct stream_hdr *sh) +{ + DEBUGF("DISK_BUF_DATA_NOTIFY: 0x%02X ", STR_FROM_HDR(sh)->id); + + if (sh->win_left <= sh->win_right) + { + /* Check if the data is already ready already */ + if (disk_buf_is_data_ready(sh, 0)) + { + /* It was - don't register */ + DEBUGF("(was ready)\n" + " swl:%lu swr:%lu\n" + " dwl:%lu dwr:%lu\n", + sh->win_left, sh->win_right, + disk_buf.win_left, disk_buf.win_right); + /* Be sure it's not listed though if multiple requests were made */ + list_remove_item(nf_list, sh); + return DISK_BUF_NOTIFY_OK; + } + + switch (disk_buf.state) + { + case TSTATE_DATA: + case TSTATE_BUFFERING: + case TSTATE_INIT: + disk_buf.state = TSTATE_BUFFERING; + list_add_item(nf_list, sh); + DEBUGF("(registered)\n" + " swl:%lu swr:%lu\n" + " dwl:%lu dwr:%lu\n", + sh->win_left, sh->win_right, + disk_buf.win_left, disk_buf.win_right); + return DISK_BUF_NOTIFY_REGISTERED; + } + } + + DEBUGF("(error)\n"); + return DISK_BUF_NOTIFY_ERROR; +} + +static bool check_data_notifies_callback(struct stream_hdr *sh, void *data) +{ + if (disk_buf_is_data_ready(sh, 0)) + { + /* Remove from list then post notification - post because send + * could result in a wait for each thread to finish resulting + * in deadlock */ + list_remove_item(nf_list, sh); + str_post_msg(STR_FROM_HDR(sh), DISK_BUF_DATA_NOTIFY, 0); + DEBUGF("DISK_BUF_DATA_NOTIFY: 0x%02X (notified)\n", + STR_FROM_HDR(sh)->id); + } + + return true; + (void)data; +} + +/* Check registered streams and notify them if their data is available */ +static inline void check_data_notifies(void) +{ + list_enum_items(nf_list, + (list_enum_callback_t)check_data_notifies_callback, + NULL); +} + +/* Clear all registered notifications - do not post them */ +static inline void clear_data_notifies(void) +{ + list_clear_all(nf_list); +} + +/* Background buffering when streaming */ +static inline void disk_buf_buffer(void) +{ + struct stream_window sw; + + switch (disk_buf.state) + { + default: + { + size_t wm; + uint32_t time; + + /* Get remaining minimum data based upon the stream closest to the + * right edge of the window */ + if (!stream_get_window(&sw)) + break; + + time = stream_get_ticks(NULL); + wm = muldiv_uint32(5*CLOCK_RATE, sw.right - disk_buf.pos_last, + time - disk_buf.time_last); + wm = MIN(wm, (size_t)disk_buf.size); + wm = MAX(wm, DISK_BUF_LOW_WATERMARK); + + disk_buf.time_last = time; + disk_buf.pos_last = sw.right; + + /* Fast attack, slow decay */ + disk_buf.low_wm = (wm > (size_t)disk_buf.low_wm) ? + wm : AVERAGE(disk_buf.low_wm, wm, 16); + +#if 0 + rb->splashf(0, "*%10ld %10ld", disk_buf.low_wm, + disk_buf.win_right - sw.right); +#endif + + if (disk_buf.win_right - sw.right > disk_buf.low_wm) + break; + + disk_buf.state = TSTATE_BUFFERING; + } /* default: */ + + /* Fall-through */ + case TSTATE_BUFFERING: + { + ssize_t len, n; + uint32_t tag, *tag_p; + + /* Limit buffering up to the stream with the least progress */ + if (!stream_get_window(&sw)) + { + disk_buf.state = TSTATE_DATA; + rb->storage_sleep(); + break; + } + + /* Wrap pointer */ + if (disk_buf.tail >= disk_buf.end) + disk_buf.tail = disk_buf.start; + + len = disk_buf.size - disk_buf.win_right + sw.left; + + if (len < DISK_BUF_PAGE_SIZE) + { + /* Free space is less than one page */ + disk_buf.state = TSTATE_DATA; + disk_buf.low_wm = DISK_BUF_LOW_WATERMARK; + rb->storage_sleep(); + break; + } + + len = disk_buf.tail - disk_buf.start; + tag = MAP_OFFSET_TO_TAG(disk_buf.win_right); + tag_p = &disk_buf.cache[len >> DISK_BUF_PAGE_SHIFT]; + + if (*tag_p != tag) + { + if (disk_buf.need_seek) + { + rb->lseek(disk_buf.in_file, disk_buf.win_right, SEEK_SET); + disk_buf.need_seek = false; + } + + n = rb->read(disk_buf.in_file, disk_buf.tail, DISK_BUF_PAGE_SIZE); + + if (n <= 0) + { + /* Error or end of stream */ + disk_buf.state = TSTATE_EOS; + rb->storage_sleep(); + break; + } + + if (len < DISK_GUARDBUF_SIZE) + { + /* Autoguard guard-o-rama - maintain guardbuffer coherency */ + rb->memcpy(disk_buf.end + len, disk_buf.tail, + MIN(DISK_GUARDBUF_SIZE - len, n)); + } + + /* Update the cache entry for this page */ + *tag_p = tag; + } + else + { + /* Skipping a read */ + n = MIN(DISK_BUF_PAGE_SIZE, + disk_buf.filesize - disk_buf.win_right); + disk_buf.need_seek = true; + } + + disk_buf.tail += DISK_BUF_PAGE_SIZE; + + /* Keep left edge moving forward */ + + /* Advance right edge in temp variable first, then move + * left edge if overflow would occur. This avoids a stream + * thinking its data might be available when it actually + * may not end up that way after a slide of the window. */ + len = disk_buf.win_right + n; + + if (len - disk_buf.win_left > disk_buf.size) + disk_buf.win_left += n; + + disk_buf.win_right = len; + + /* Continue buffering until filled or file end */ + rb->yield(); + } /* TSTATE_BUFFERING: */ + + case TSTATE_EOS: + break; + } /* end switch */ +} + +static void disk_buf_on_reset(ssize_t pos) +{ + int page; + uint32_t tag; + off_t anchor; + + disk_buf.state = TSTATE_INIT; + disk_buf.status = STREAM_STOPPED; + clear_data_notifies(); + + if (pos >= disk_buf.filesize) + { + /* Anchor on page immediately following the one containing final + * data */ + anchor = disk_buf.file_pages * DISK_BUF_PAGE_SIZE; + disk_buf.win_left = disk_buf.filesize; + } + else + { + anchor = pos & ~DISK_BUF_PAGE_MASK; + disk_buf.win_left = anchor; + } + + /* Collect all valid data already buffered that is contiguous with the + * current position - probe to left, then to right */ + if (anchor > 0) + { + page = MAP_OFFSET_TO_PAGE(anchor); + tag = MAP_OFFSET_TO_TAG(anchor); + + do + { + if (--tag, --page < 0) + page = disk_buf.pgcount - 1; + + if (disk_buf.cache[page] != tag) + break; + + disk_buf.win_left = tag << DISK_BUF_PAGE_SHIFT; + } + while (tag > 0); + } + + if (anchor < disk_buf.filesize) + { + page = MAP_OFFSET_TO_PAGE(anchor); + tag = MAP_OFFSET_TO_TAG(anchor); + + do + { + if (disk_buf.cache[page] != tag) + break; + + if (++tag, ++page >= disk_buf.pgcount) + page = 0; + + anchor += DISK_BUF_PAGE_SIZE; + } + while (anchor < disk_buf.filesize); + } + + if (anchor >= disk_buf.filesize) + { + disk_buf.win_right = disk_buf.filesize; + disk_buf.state = TSTATE_EOS; + } + else + { + disk_buf.win_right = anchor; + } + + disk_buf.tail = disk_buf.start + MAP_OFFSET_TO_BUFFER(anchor); + + DEBUGF("disk buf reset\n" + " dwl:%ld dwr:%ld\n", + disk_buf.win_left, disk_buf.win_right); + + /* Next read position is at right edge */ + rb->lseek(disk_buf.in_file, disk_buf.win_right, SEEK_SET); + disk_buf.need_seek = false; + + disk_buf_reply_msg(disk_buf.win_right - disk_buf.win_left); +} + +static void disk_buf_on_stop(void) +{ + bool was_buffering = disk_buf.state == TSTATE_BUFFERING; + + disk_buf.state = TSTATE_EOS; + disk_buf.status = STREAM_STOPPED; + clear_data_notifies(); + + disk_buf_reply_msg(was_buffering); +} + +static void disk_buf_on_play_pause(bool play, bool forcefill) +{ + struct stream_window sw; + + if (disk_buf.state != TSTATE_EOS) + { + if (forcefill) + { + /* Force buffer filling to top */ + disk_buf.state = TSTATE_BUFFERING; + } + else if (disk_buf.state != TSTATE_BUFFERING) + { + /* If not filling already, simply monitor */ + disk_buf.state = TSTATE_DATA; + } + } + /* else end of stream - no buffering to do */ + + disk_buf.pos_last = stream_get_window(&sw) ? sw.right : 0; + disk_buf.time_last = stream_get_ticks(NULL); + + disk_buf.status = play ? STREAM_PLAYING : STREAM_PAUSED; +} + +static int disk_buf_on_load_range(struct dbuf_range *rng) +{ + uint32_t tag = rng->tag_start; + uint32_t tag_end = rng->tag_end; + int page = rng->pg_start; + + /* Check if a seek is required */ + bool need_seek = rb->lseek(disk_buf.in_file, 0, SEEK_CUR) + != (off_t)(tag << DISK_BUF_PAGE_SHIFT); + + do + { + uint32_t *tag_p = &disk_buf.cache[page]; + + if (*tag_p != tag) + { + /* Page not cached - load it */ + ssize_t o, n; + + if (need_seek) + { + rb->lseek(disk_buf.in_file, tag << DISK_BUF_PAGE_SHIFT, + SEEK_SET); + need_seek = false; + } + + o = page << DISK_BUF_PAGE_SHIFT; + n = rb->read(disk_buf.in_file, disk_buf.start + o, + DISK_BUF_PAGE_SIZE); + + if (n < 0) + { + /* Read error */ + return DISK_BUF_NOTIFY_ERROR; + } + + if (n == 0) + { + /* End of file */ + break; + } + + if (o < DISK_GUARDBUF_SIZE) + { + /* Autoguard guard-o-rama - maintain guardbuffer coherency */ + rb->memcpy(disk_buf.end + o, disk_buf.start + o, + MIN(DISK_GUARDBUF_SIZE - o, n)); + } + + /* Update the cache entry */ + *tag_p = tag; + } + else + { + /* Skipping a disk read - must seek on next one */ + need_seek = true; + } + + if (++page >= disk_buf.pgcount) + page = 0; + } + while (++tag <= tag_end); + + return DISK_BUF_NOTIFY_OK; +} + +static void disk_buf_thread(void) +{ + struct queue_event ev; + + disk_buf.state = TSTATE_EOS; + disk_buf.status = STREAM_STOPPED; + + while (1) + { + if (disk_buf.state != TSTATE_EOS) + { + /* Poll buffer status and messages */ + rb->queue_wait_w_tmo(disk_buf.q, &ev, + disk_buf.state == TSTATE_BUFFERING ? + 0 : HZ/5); + } + else + { + /* Sit idle and wait for commands */ + rb->queue_wait(disk_buf.q, &ev); + } + + switch (ev.id) + { + case SYS_TIMEOUT: + if (disk_buf.state == TSTATE_EOS) + break; + + disk_buf_buffer(); + + /* Check for any due notifications if any are pending */ + if (*nf_list != NULL) + check_data_notifies(); + + /* Still more data left? */ + if (disk_buf.state != TSTATE_EOS) + continue; + + /* Nope - end of stream */ + break; + + case DISK_BUF_CACHE_RANGE: + disk_buf_reply_msg(disk_buf_on_load_range( + (struct dbuf_range *)ev.data)); + break; + + case STREAM_RESET: + disk_buf_on_reset(ev.data); + break; + + case STREAM_STOP: + disk_buf_on_stop(); + break; + + case STREAM_PAUSE: + case STREAM_PLAY: + disk_buf_on_play_pause(ev.id == STREAM_PLAY, ev.data != 0); + disk_buf_reply_msg(1); + break; + + case STREAM_QUIT: + disk_buf.state = TSTATE_EOS; + return; + + case DISK_BUF_DATA_NOTIFY: + disk_buf_reply_msg(disk_buf_on_data_notify( + (struct stream_hdr *)ev.data)); + break; + + case DISK_BUF_CLEAR_DATA_NOTIFY: + disk_buf_on_clear_data_notify((struct stream_hdr *)ev.data); + disk_buf_reply_msg(1); + break; + } + } +} + +/* Caches some data from the current file */ +static ssize_t disk_buf_probe(off_t start, size_t length, void **p) +{ + off_t end; + uint32_t tag, tag_end; + int page; + + /* Can't read past end of file */ + if (length > (size_t)(disk_buf.filesize - start)) + { + length = disk_buf.filesize - start; + } + + /* Can't cache more than the whole buffer size */ + if (length > (size_t)disk_buf.size) + { + length = disk_buf.size; + } + /* Zero-length probes permitted */ + + end = start + length; + + /* Prepare the range probe */ + tag = MAP_OFFSET_TO_TAG(start); + tag_end = MAP_OFFSET_TO_TAG(end); + page = MAP_OFFSET_TO_PAGE(start); + + /* If the end is on a page boundary, check one less or an extra + * one will be probed */ + if (tag_end > tag && (end & DISK_BUF_PAGE_MASK) == 0) + { + tag_end--; + } + + if (p != NULL) + { + *p = disk_buf.start + (page << DISK_BUF_PAGE_SHIFT) + + (start & DISK_BUF_PAGE_MASK); + } + + /* Obtain initial load point. If all data was cached, no message is sent + * otherwise begin on the first page that is not cached. Since we have to + * send the message anyway, the buffering thread will determine what else + * requires loading on its end in order to cache the specified range. */ + do + { + if (disk_buf.cache[page] != tag) + { + static struct dbuf_range rng IBSS_ATTR; + intptr_t result; + + DEBUGF("disk_buf: cache miss\n"); + rng.tag_start = tag; + rng.tag_end = tag_end; + rng.pg_start = page; + + result = rb->queue_send(disk_buf.q, DISK_BUF_CACHE_RANGE, + (intptr_t)&rng); + + return result == DISK_BUF_NOTIFY_OK ? (ssize_t)length : -1; + } + + if (++page >= disk_buf.pgcount) + page = 0; + } + while (++tag <= tag_end); + + return length; +} + +/* Attempt to get a pointer to size bytes on the buffer. Returns real amount of + * data available as well as the size of non-wrapped data after *p. */ +ssize_t _disk_buf_getbuffer(size_t size, void **pp, void **pwrap, + size_t *sizewrap) +{ + disk_buf_lock(); + + size = disk_buf_probe(disk_buf.offset, size, pp); + + if (size != (size_t)-1 && pwrap && sizewrap) + { + uint8_t *p = (uint8_t *)*pp; + + if (p + size > disk_buf.end + DISK_GUARDBUF_SIZE) + { + /* Return pointer to wraparound and the size of same */ + size_t nowrap = (disk_buf.end + DISK_GUARDBUF_SIZE) - p; + *pwrap = disk_buf.start + DISK_GUARDBUF_SIZE; + *sizewrap = size - nowrap; + } + else + { + *pwrap = NULL; + *sizewrap = 0; + } + } + + disk_buf_unlock(); + + return size; +} + +ssize_t _disk_buf_getbuffer_l2(struct dbuf_l2_cache *l2, + size_t size, void **pp) +{ + off_t offs; + off_t l2_addr; + size_t l2_size; + void *l2_p; + + if (l2 == NULL) + { + /* Shouldn't have to check this normally */ + DEBUGF("disk_buf_getbuffer_l2: l2 = NULL!\n"); + } + + if (size > DISK_BUF_L2_CACHE_SIZE) + { + /* Asking for too much; just go through L1 */ + return disk_buf_getbuffer(size, pp, NULL, NULL); + } + + offs = disk_buf.offset; /* Other calls keep this within bounds */ + l2_addr = l2->addr; + + if (offs >= l2_addr && offs < l2_addr + DISK_BUF_L2_CACHE_SIZE) + { + /* Data is in the local buffer */ + offs &= DISK_BUF_L2_CACHE_MASK; + + *pp = l2->data + offs; + if (offs + size > l2->size) + size = l2->size - offs; /* Keep size within file limits */ + + return size; + } + + /* Have to probe main buffer */ + l2_addr = offs & ~DISK_BUF_L2_CACHE_MASK; + l2_size = DISK_BUF_L2_CACHE_SIZE*2; /* 2nd half is a guard buffer */ + + disk_buf_lock(); + + l2_size = disk_buf_probe(l2_addr, l2_size, &l2_p); + + if (l2_size != (size_t)-1) + { + rb->memcpy(l2->data, l2_p, l2_size); + + l2->addr = l2_addr; + l2->size = l2_size; + offs -= l2_addr; + + *pp = l2->data + offs; + if (offs + size > l2->size) + size = l2->size - offs; /* Keep size within file limits */ + } + else + { + size = -1; + } + + disk_buf_unlock(); + + return size; +} + + +/* Read size bytes of data into a buffer - advances the buffer pointer + * and returns the real size read. */ +ssize_t disk_buf_read(void *buffer, size_t size) +{ + uint8_t *p; + + disk_buf_lock(); + + size = disk_buf_probe(disk_buf.offset, size, PUN_PTR(void **, &p)); + + if (size != (size_t)-1) + { + if (p + size > disk_buf.end + DISK_GUARDBUF_SIZE) + { + /* Read wraps */ + size_t nowrap = (disk_buf.end + DISK_GUARDBUF_SIZE) - p; + rb->memcpy(buffer, p, nowrap); + rb->memcpy(buffer + nowrap, disk_buf.start + DISK_GUARDBUF_SIZE, + size - nowrap); + } + else + { + /* Read wasn't wrapped or guardbuffer holds it */ + rb->memcpy(buffer, p, size); + } + + disk_buf.offset += size; + } + + disk_buf_unlock(); + + return size; +} + +ssize_t disk_buf_lseek(off_t offset, int whence) +{ + disk_buf_lock(); + + /* The offset returned is the result of the current thread's action and + * may be invalidated so a local result is returned and not the value + * of disk_buf.offset directly */ + switch (whence) + { + case SEEK_SET: + /* offset is just the offset */ + break; + case SEEK_CUR: + offset += disk_buf.offset; + break; + case SEEK_END: + offset = disk_buf.filesize + offset; + break; + default: + disk_buf_unlock(); + return -2; /* Invalid request */ + } + + if (offset < 0 || offset > disk_buf.filesize) + { + offset = -3; + } + else + { + disk_buf.offset = offset; + } + + disk_buf_unlock(); + + return offset; +} + +/* Prepare the buffer to enter the streaming state. Evaluates the available + * streaming window. */ +ssize_t disk_buf_prepare_streaming(off_t pos, size_t len) +{ + disk_buf_lock(); + + if (pos < 0) + pos = 0; + else if (pos > disk_buf.filesize) + pos = disk_buf.filesize; + + DEBUGF("prepare streaming:\n pos:%ld len:%lu\n", pos, (unsigned long)len); + + pos = disk_buf_lseek(pos, SEEK_SET); + len = disk_buf_probe(pos, len, NULL); + + DEBUGF(" probe done: pos:%ld len:%lu\n", pos, (unsigned long)len); + + len = disk_buf_send_msg(STREAM_RESET, pos); + + disk_buf_unlock(); + + return len; +} + +/* Set the streaming window to an arbitrary position within the file. Makes no + * probes to validate data. Use after calling another function to cause data + * to be cached and correct values are known. */ +ssize_t disk_buf_set_streaming_window(off_t left, off_t right) +{ + ssize_t len; + + disk_buf_lock(); + + if (left < 0) + left = 0; + else if (left > disk_buf.filesize) + left = disk_buf.filesize; + + if (left > right) + right = left; + + if (right > disk_buf.filesize) + right = disk_buf.filesize; + + disk_buf.win_left = left; + disk_buf.win_right = right; + disk_buf.tail = disk_buf.start + ((right + DISK_BUF_PAGE_SIZE-1) & + ~DISK_BUF_PAGE_MASK) % disk_buf.size; + + len = disk_buf.win_right - disk_buf.win_left; + + disk_buf_unlock(); + + return len; +} + +void * disk_buf_offset2ptr(off_t offset) +{ + if (offset < 0) + offset = 0; + else if (offset > disk_buf.filesize) + offset = disk_buf.filesize; + + return disk_buf.start + (offset % disk_buf.size); +} + +void disk_buf_close(void) +{ + disk_buf_lock(); + + if (disk_buf.in_file >= 0) + { + rb->close(disk_buf.in_file); + disk_buf.in_file = -1; + + /* Invalidate entire cache */ + rb->memset(disk_buf.cache, 0xff, + disk_buf.pgcount*sizeof (*disk_buf.cache)); + disk_buf.file_pages = 0; + disk_buf.filesize = 0; + disk_buf.offset = 0; + } + + disk_buf_unlock(); +} + +int disk_buf_open(const char *filename) +{ + int fd; + + disk_buf_lock(); + + disk_buf_close(); + + fd = rb->open(filename, O_RDONLY); + + if (fd >= 0) + { + ssize_t filesize = rb->filesize(fd); + + if (filesize <= 0) + { + rb->close(disk_buf.in_file); + } + else + { + disk_buf.filesize = filesize; + /* Number of file pages rounded up toward +inf */ + disk_buf.file_pages = ((size_t)filesize + DISK_BUF_PAGE_SIZE-1) + / DISK_BUF_PAGE_SIZE; + disk_buf.in_file = fd; + } + } + + disk_buf_unlock(); + + return fd; +} + +intptr_t disk_buf_send_msg(long id, intptr_t data) +{ + return rb->queue_send(disk_buf.q, id, data); +} + +void disk_buf_post_msg(long id, intptr_t data) +{ + rb->queue_post(disk_buf.q, id, data); +} + +void disk_buf_reply_msg(intptr_t retval) +{ + rb->queue_reply(disk_buf.q, retval); +} + +bool disk_buf_init(void) +{ + disk_buf.thread = 0; + + rb->mutex_init(&disk_buf_mtx); + + disk_buf.q = &disk_buf_queue; + rb->queue_init(disk_buf.q, false); + + disk_buf.state = TSTATE_EOS; + disk_buf.status = STREAM_STOPPED; + + disk_buf.in_file = -1; + disk_buf.filesize = 0; + disk_buf.win_left = 0; + disk_buf.win_right = 0; + disk_buf.time_last = 0; + disk_buf.pos_last = 0; + disk_buf.low_wm = DISK_BUF_LOW_WATERMARK; + + disk_buf.start = mpeg_malloc_all((size_t*)&disk_buf.size, MPEG_ALLOC_DISKBUF); + if (disk_buf.start == NULL) + return false; + +#if NUM_CORES > 1 + CACHEALIGN_BUFFER(disk_buf.start, disk_buf.size); + disk_buf.start = UNCACHED_ADDR(disk_buf.start); +#endif + disk_buf.size -= DISK_GUARDBUF_SIZE; + disk_buf.pgcount = disk_buf.size / DISK_BUF_PAGE_SIZE; + + /* Fit it as tightly as possible */ + while (disk_buf.pgcount*(sizeof (*disk_buf.cache) + DISK_BUF_PAGE_SIZE) + > (size_t)disk_buf.size) + { + disk_buf.pgcount--; + } + + disk_buf.cache = (typeof (disk_buf.cache))disk_buf.start; + disk_buf.start += sizeof (*disk_buf.cache)*disk_buf.pgcount; + disk_buf.size = disk_buf.pgcount*DISK_BUF_PAGE_SIZE; + disk_buf.end = disk_buf.start + disk_buf.size; + disk_buf.tail = disk_buf.start; + + DEBUGF("disk_buf info:\n" + " page count: %d\n" + " size: %ld\n", + disk_buf.pgcount, (long)disk_buf.size); + + rb->memset(disk_buf.cache, 0xff, + disk_buf.pgcount*sizeof (*disk_buf.cache)); + + disk_buf.thread = rb->create_thread( + disk_buf_thread, disk_buf_stack, sizeof(disk_buf_stack), 0, + "mpgbuffer" IF_PRIO(, PRIORITY_BUFFERING) IF_COP(, CPU)); + + rb->queue_enable_queue_send(disk_buf.q, &disk_buf_queue_send, + disk_buf.thread); + + if (disk_buf.thread == 0) + return false; + + /* Wait for thread to initialize */ + disk_buf_send_msg(STREAM_NULL, 0); + + return true; +} + +void disk_buf_exit(void) +{ + if (disk_buf.thread != 0) + { + rb->queue_post(disk_buf.q, STREAM_QUIT, 0); + rb->thread_wait(disk_buf.thread); + disk_buf.thread = 0; + } +} diff --git a/apps/plugins/mpegplayer/disk_buf.h b/apps/plugins/mpegplayer/disk_buf.h new file mode 100644 index 0000000000..bc76ab6dc3 --- /dev/null +++ b/apps/plugins/mpegplayer/disk_buf.h @@ -0,0 +1,152 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * AV disk buffer declarations + * + * Copyright (c) 2007 Michael Sevakis + * + * 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. + * + ****************************************************************************/ +#ifndef DISK_BUF_H +#define DISK_BUF_H + +#ifndef OFF_T_MAX +#define OFF_T_MAX (~((off_t)1 << (sizeof (off_t)*8 - 1))) +#endif + +#ifndef OFF_T_MIN +#define OFF_T_MIN ((off_t)1 << (sizeof (off_t)*8 - 1)) +#endif + +#define DISK_BUF_PAGE_SHIFT 15 /* 32KB cache lines */ +#define DISK_BUF_PAGE_SIZE (1 << DISK_BUF_PAGE_SHIFT) +#define DISK_BUF_PAGE_MASK (DISK_BUF_PAGE_SIZE-1) + +enum +{ + DISK_BUF_NOTIFY_ERROR = -1, + DISK_BUF_NOTIFY_NULL = 0, + DISK_BUF_NOTIFY_OK, + DISK_BUF_NOTIFY_TIMEDOUT, + DISK_BUF_NOTIFY_PROCESS_EVENT, + DISK_BUF_NOTIFY_REGISTERED, +}; + +/** Macros to map file offsets to cached data **/ + +/* Returns a cache tag given a file offset */ +#define MAP_OFFSET_TO_TAG(o) \ + ((o) >> DISK_BUF_PAGE_SHIFT) + +/* Returns the cache page number given a file offset */ +#define MAP_OFFSET_TO_PAGE(o) \ + (MAP_OFFSET_TO_TAG(o) % disk_buf.pgcount) + +/* Returns the buffer offset given a file offset */ +#define MAP_OFFSET_TO_BUFFER(o) \ + (MAP_OFFSET_TO_PAGE(o) * DISK_BUF_PAGE_SIZE) + +struct dbuf_range +{ + uint32_t tag_start; + uint32_t tag_end; + int pg_start; +}; + +#define DISK_BUF_L2_CACHE_SHIFT 6 +#define DISK_BUF_L2_CACHE_SIZE (1 << DISK_BUF_L2_CACHE_SHIFT) +#define DISK_BUF_L2_CACHE_MASK (DISK_BUF_L2_CACHE_SIZE-1) + +struct dbuf_l2_cache +{ + off_t addr; /* L2 file offset */ + size_t size; /* Real size */ + uint8_t data[DISK_BUF_L2_CACHE_SIZE*2]; /* Local data and guard */ +}; + +void dbuf_l2_init(struct dbuf_l2_cache *l2_p); + +/* This object is an extension of the stream manager and handles some + * playback events as well as buffering */ +struct disk_buf +{ + unsigned int thread; + struct event_queue *q; + uint8_t *start; /* Start pointer */ + uint8_t *end; /* 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 */ + uint8_t *tail; /* Location of last data + 1 filled into the buffer */ + ssize_t size; /* The buffer length _not_ including the guard space (end-start) */ + int pgcount; /* Total number of available cached pages */ + uint32_t *cache; /* Pointer to cache structure - allocated on buffer */ + int in_file; /* File being read */ + ssize_t filesize; /* Size of file in_file in bytes */ + int file_pages; /* Number of pages in file (rounded up) */ + off_t offset; /* Current position (random access) */ + off_t win_left; /* Left edge of buffer window (streaming) */ + off_t win_right; /* Right edge of buffer window (streaming) */ + uint32_t time_last; /* Last time watermark was checked */ + off_t pos_last; /* Last position at watermark check time */ + ssize_t low_wm; /* The low watermark for automatic rebuffering */ + int status; /* Status as stream */ + int state; /* Current thread state */ + bool need_seek; /* Need to seek because a read was not contiguous */ +}; + +extern struct disk_buf disk_buf SHAREDBSS_ATTR; + +struct stream_hdr; +bool disk_buf_is_data_ready(struct stream_hdr *sh, ssize_t margin); + +bool disk_buf_init(void); +void disk_buf_exit(void); + +static inline int disk_buf_status(void) + { return disk_buf.status; } + +int disk_buf_open(const char *filename); +void disk_buf_close(void); +ssize_t _disk_buf_getbuffer(size_t size, void **pp, void **pwrap, + size_t *sizewrap); +#define disk_buf_getbuffer(size, pp, pwrap, sizewrap) \ + _disk_buf_getbuffer((size), PUN_PTR(void **, (pp)), \ + PUN_PTR(void **, (pwrap)), (sizewrap)) + +ssize_t _disk_buf_getbuffer_l2(struct dbuf_l2_cache *l2, + size_t size, void **pp); +#define disk_buf_getbuffer_l2(l2, size, pp) \ + _disk_buf_getbuffer_l2((l2), (size), PUN_PTR(void **, (pp))) + +ssize_t disk_buf_read(void *buffer, size_t size); +ssize_t disk_buf_lseek(off_t offset, int whence); + +static inline off_t disk_buf_ftell(void) + { return disk_buf.offset; } + +static inline ssize_t disk_buf_filesize(void) + { return disk_buf.filesize; } + +ssize_t disk_buf_prepare_streaming(off_t pos, size_t len); +ssize_t disk_buf_set_streaming_window(off_t left, off_t right); +void * disk_buf_offset2ptr(off_t offset); +int disk_buf_check_streaming_window(off_t left, off_t right); + +intptr_t disk_buf_send_msg(long id, intptr_t data); +void disk_buf_post_msg(long id, intptr_t data); +void disk_buf_reply_msg(intptr_t retval); + +#endif /* DISK_BUF_H */ diff --git a/apps/plugins/mpegplayer/libmpeg2/AUTHORS b/apps/plugins/mpegplayer/libmpeg2/AUTHORS new file mode 100644 index 0000000000..4722897c97 --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/AUTHORS @@ -0,0 +1,33 @@ +Aaron Holtzman started the project and +made the initial working implementation. + +Michel Lespinasse did major changes for speed and +mpeg conformance and is the current maintainer. Most of the current +code was (re)written by him. + +Other contributors include: + Bruno Barreyra - build fixes + Gildas Bazin - mingw32 port + Alexander W. Chin - progressive_seq fix + Stephen Crowley - build fixes + Didier Gautheron - bug fixes + Ryan C. Gordon - SDL support + Peter Gubanov - MMX IDCT scheduling + HÃ¥kan Hjort - Solaris fixes, mlib code + Nicolas Joly - assorted bug fixes + Gerd Knorr - Xv support + David I. Lehn - motion_comp mmx code + Olie Lho - MMX yuv2rgb routine + David S. Miller - sparc VIS optimizations + Rick Niles - build fixes + Real Ouellet - g200 fixes + Bajusz Peter - motion comp fixes + Franck Sicard - x11 fixes + Brion Vibber - x11 fixes + Martin Vogt - reentrancy fixes + Fredrik Vraalsen - general hackage and stuff + +(let me know if I forgot anyone) + +Thanks to David Schleef for creating me an account on his ppc g4 +machine and making it possible for me to work on the altivec code. diff --git a/apps/plugins/mpegplayer/libmpeg2/README b/apps/plugins/mpegplayer/libmpeg2/README new file mode 100644 index 0000000000..2a58846772 --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/README @@ -0,0 +1,204 @@ + + +ABOUT LIBMPEG2 + +libmpeg2 is a free library for decoding mpeg-2 and mpeg-1 video +streams. It is released under the terms of the GPL license. + +The main goals in libmpeg2 development are: + + * Conformance - libmpeg2 is able to decode all mpeg streams that + conform to certain restrictions: "constrained parameters" for + mpeg-1, and "main profile" for mpeg-2. In practice, this is + what most people are using. For streams that follow these + restrictions, we believe libmpeg2 is 100% conformant to the + mpeg standards - and we have a pretty extensive test suite to + check this. + + * Speed - there has been huge efforts there, and we believe + libmpeg2 is the fastest library around for what it + does. Please tell us if you find a faster one ! With typical + video streams as found on DVD's, and doing only decoding with + no display, you should be able to get about 110 fps on a + PIII/666, or 150 fps on an Athlon/950. This is less than 20 + cycles per output pixel. In a real player program, the display + routines will probably take as much time as the actual + decoding ! + + * Portability - most of the code is written in C, and when we + use platform-specific optimizations (typically assembly + routines, currently used for the motion compensation and the + inverse cosine transform stages) we always have a generic C + routine to fall back on. This should be portable to all + architectures - at least we have heard reports from people + running this code on x86, ppc, sparc, arm and + sh4. Assembly-optimized implementations are available on x86 + (MMX) and ppc (altivec) architectures. Ultrasparc (VIS) is + probably the next on the list - we'll see. + + * Reuseability - we do not want libmpeg2 to include any + project-specific code, but it should still include enough + features to be used by very diverse projects. We are only + starting to get there - the best way to help here is to give + us some feedback ! + +The project homepage is at http://libmpeg2.sourceforge.net/ + + +MPEG2DEC + +mpeg2dec is a test program for libmpeg2. It decodes mpeg-1 and mpeg-2 +video streams, and also includes a demultiplexer for mpeg-1 and mpeg-2 +program streams. It is purposely kept simple : it does not include +features like reading files from a DVD, CSS, fullscreen output, +navigation, etc... The main purpose of mpeg2dec is to have a simple +test bed for libmpeg2. + +The libmpeg2 source code is always distributed in the mpeg2dec +package, to make it easier for people to test it. + +The basic usage is to just type "mpeg2dec file" where file is a +demultiplexed mpeg video file. + +The "-s" option must be used for multiplexed (audio and video) mpeg +files using the "program stream" format. These files are usualy found +on the internet or on unencrypted DVDs. + +The "-t" option must be used for multiplexed (audio and video) mpeg +files using the "transport stream" format. These files are usualy +found in digital TV applications. + +The "-o" option is used to select a given output module - for example +to redirect the output to a file. This is also used for performance +testing and conformance testing. + +The "-c" option is used to disable all optimizations. + + +OTHER PROJECTS USING LIBMPEG2 + +libmpeg2 is being used by various other projects, including: + + * xine (http://xine.sourceforge.net/) - started as a simple + mpeg-2 audio and video decoder, but it since became a + full-featured DVD and video media player. + + * VideoLAN (http://www.videolan.org/) - video streaming over an + ethernet network, can also be used as a standalone player. + + * MPlayer (http://www.MPlayerHQ.hu) - another good player, it is + also very robust against damaged streams. + + * movietime (http://movietime.sourceforge.net/) - still quite + young, but it looks very promising ! + + * mpeg2decX (http://homepage1.nifty.com/~toku/software_en.html) - + a graphical interface for mpeg2dec for macintosh osX. + + * TCVP (http://tcvp.sf.net) - video and music player for unix. + + * drip (http://drip.sourceforge.net/) - a DVD to DIVX transcoder. + + * PoMP + (http://www.dmclab.hanyang.ac.kr/research/project/PoDS/PoDS_sw.htm) - + a research player optimized to minimize disk power consumption. + + * OMS (http://www.linuxvideo.org/oms/) + + * XMPS (http://xmps.sourceforge.net/) + + * GStreamer (http://www.gstreamer.net/) - a framework for + streaming media; it has an mpeg2 decoding plugin based on + libmpeg2. + + * mpeglib (http://mpeglib.sourceforge.net/) - a video decoding + library that usess libmpeg2 when decoding mpeg streams. + + * daphne (http://daphne.rulecity.com/) - a laserdisc arcade game + simulator. + + * GOPchop (http://outflux.net/unix/software/GOPchop/) - a + GOP-accurate editor for MPEG2 streams. + +If you use libmpeg2 in another project, let us know ! + + +TASKS + +There are several places where we could easily use some help: + + * Documentation: libmpeg2 still has no documentation. Every + project using it has had to figure things out by looking at + the header files, at the mpeg2dec sample application, and by + asking questions. Writing down a nice documentation would make + the code more easily reuseable. + + * Testing: If you find any stream that does not decode right + with libmpeg2, let us know ! The best thing would be to mail + to the libmpeg2-devel mailing list. Also, it would be nice to + build a stress test so we can make sure libmpeg2 never crashes + on bad streams. + + * Coding: There is a small TODO list in the mpeg2dec package, + you can have a look there ! Most items are pretty terse + though. + + * Porting: If you're porting to a new architecture, you might + want to experiment with the compile flags defined in + configure.in . When you figure out whats fastest on your + platform, send us a patch ! + + * Assembly optimizations: We only have x86 and altivec + optimizations yet, it would be worthwhile writing routines for + other architectures, especially those that have SIMD + instruction set extensions ! Also the yuv2rgb x86 routines + could probably be optimized a lot. + + +CVS SNAPSHOTS + +A daily snapshot is created using "make distcheck" every night and +uploaded to http://libmpeg2.sourceforge.net/files/mpeg2dec-snapshot.tar.gz . +It is easier to use than the CVS repository, because you do not need +to have the right versions of automake, autoconf and libtool +installed. It might be convenient when working on a libmpeg2 port for +example. + + +CVS REPOSITORY + +The latest libmpeg2 and mpeg2dec source code can always be found by +anonymous CVS: + +# export CVSROOT=:pserver:anonymous@cvs.libmpeg2.sourceforge.net:/cvsroot/libmpeg2 +# cvs login (Just press Return when prompted for a password) +# cvs checkout mpeg2dec + +You can also browse the latest changes online at +http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/libmpeg2/mpeg2dec/ + +The other CVS modules are mpeg2dec-streams for the test suite, and +mpeg2dec-livid for the CVS history of the project while it was still +hosted on the linuxvideo.org servers. + + +MAILING LISTS + +See the subscription information at http://libmpeg2.sourceforge.net/lists.html + +libmpeg2-devel + +This is the main mailing list for technical discussion about +libmpeg2. Anyone wanting to work on libmpeg2, or maybe just stay +informed about the development process, should probably subscribe to +this list. + +libmpeg2-checkins + +All libmpeg2 checkins are announced there. This is a good way to keep +track of what goes into CVS. + +libmpeg2-announce + +This is a very low traffic mailing list, only for announcements of new +versions of libmpeg2. Only project administrators can post there. diff --git a/apps/plugins/mpegplayer/libmpeg2/README.rockbox b/apps/plugins/mpegplayer/libmpeg2/README.rockbox new file mode 100644 index 0000000000..95f5a3d4b5 --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/README.rockbox @@ -0,0 +1,44 @@ +Library: libmpeg2 from mpeg2dec-0.4.0b (Released 2004-01-21) +Imported: 2006-08-06 by Dave Chapman + + +This directory contains a local version of libmpeg2 imported into +Rockbox for MPEG video decoding. + + +LICENSING INFORMATION + +mpeg2dec and libmpeg2 are licensed under Version 2 of the GNU General +Public License. + + +IMPORT DETAILS + +The following files were imported from the mpeg2dec-0.4.0b +distribution. Minor changes were made to enable compilation in +Rockbox and TABs were replaced by spaces to comply with the Rockbox +coding guidelines. + +AUTHORS +README +SOURCES +attributes.h +cpu_accel.c +cpu_state.c +decode.c +header.c +idct.c +motion_comp.c +mpeg2.h +mpeg2_internal.h +slice.c +video_out.h +vlc.h + +The following files are new, but based on code in mpeg2dec. + +Makefile +mpegplayer.c +video_out_rockbox.c +mpeg2dec_config.h +alloc.c diff --git a/apps/plugins/mpegplayer/libmpeg2/attributes.h b/apps/plugins/mpegplayer/libmpeg2/attributes.h new file mode 100644 index 0000000000..24b069223b --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/attributes.h @@ -0,0 +1,42 @@ +/* + * attributes.h + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + * libmpeg2 sync history: + * 2008-07-01 - CVS revision 1.5 + */ + +/* use gcc attribs to align critical data structures */ +#ifdef ATTRIBUTE_ALIGNED_MAX +#define ATTR_ALIGN(align) __attribute__ ((__aligned__ ((ATTRIBUTE_ALIGNED_MAX < align) ? ATTRIBUTE_ALIGNED_MAX : align))) +#else +#define ATTR_ALIGN(align) +#endif + +#if defined(LIKELY) && defined (UNLIKELY) +#define likely(x) LIKELY(x) +#define unlikely(x) UNLIKELY(x) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#endif + diff --git a/apps/plugins/mpegplayer/libmpeg2/decode.c b/apps/plugins/mpegplayer/libmpeg2/decode.c new file mode 100644 index 0000000000..9c8081efbe --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/decode.c @@ -0,0 +1,527 @@ +/* + * decode.c + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + * libmpeg2 sync history: + * 2008-07-01 - CVS revision 1.114 + */ + +#include "plugin.h" + +#include "mpeg2dec_config.h" + +#include "mpeg2.h" +#include "attributes.h" +#include "mpeg2_internal.h" + +#define BUFFER_SIZE (1194 * 1024) + +#if defined(CPU_COLDFIRE) || (defined(CPU_ARM) && ARM_ARCH >= 6) +/* twice as large as on other targets because coldfire uses + * a secondary, transposed buffer for optimisation */ +static int16_t static_dct_block[128] IBSS_ATTR ATTR_ALIGN(16); +#define DCT_BLOCKSIZE (128 * sizeof (int16_t)) +#else +static int16_t static_dct_block[64] IBSS_ATTR ATTR_ALIGN(16); +#define DCT_BLOCKSIZE (64 * sizeof (int16_t)) +#endif + +const mpeg2_info_t * mpeg2_info (mpeg2dec_t * mpeg2dec) +{ + return &mpeg2dec->info; +} + +static inline int skip_chunk (mpeg2dec_t * mpeg2dec, int bytes) +{ + uint8_t * current; + uint32_t shift; + uint8_t * limit; + uint8_t byte; + + if (!bytes) + return 0; + + current = mpeg2dec->buf_start; + shift = mpeg2dec->shift; + limit = current + bytes; + + do + { + byte = *current++; + + if (shift == 0x00000100) + { + int skipped; + + mpeg2dec->shift = 0xffffff00; + skipped = current - mpeg2dec->buf_start; + mpeg2dec->buf_start = current; + + return skipped; + } + + shift = (shift | byte) << 8; + } + while (current < limit); + + mpeg2dec->shift = shift; + mpeg2dec->buf_start = current; + + return 0; +} + +static inline int copy_chunk (mpeg2dec_t * mpeg2dec, int bytes) +{ + uint8_t * current; + uint32_t shift; + uint8_t * chunk_ptr; + uint8_t * limit; + uint8_t byte; + + if (!bytes) + return 0; + + current = mpeg2dec->buf_start; + shift = mpeg2dec->shift; + chunk_ptr = mpeg2dec->chunk_ptr; + limit = current + bytes; + + do + { + byte = *current++; + + if (shift == 0x00000100) + { + int copied; + + mpeg2dec->shift = 0xffffff00; + mpeg2dec->chunk_ptr = chunk_ptr + 1; + copied = current - mpeg2dec->buf_start; + mpeg2dec->buf_start = current; + return copied; + } + + shift = (shift | byte) << 8; + *chunk_ptr++ = byte; + } + while (current < limit); + + mpeg2dec->shift = shift; + mpeg2dec->buf_start = current; + return 0; +} + +void mpeg2_buffer (mpeg2dec_t * mpeg2dec, uint8_t * start, uint8_t * end) +{ + mpeg2dec->buf_start = start; + mpeg2dec->buf_end = end; +} + +int mpeg2_getpos (mpeg2dec_t * mpeg2dec) +{ + return mpeg2dec->buf_end - mpeg2dec->buf_start; +} + +static inline mpeg2_state_t seek_chunk (mpeg2dec_t * mpeg2dec) +{ + int size, skipped; + + size = mpeg2dec->buf_end - mpeg2dec->buf_start; + skipped = skip_chunk (mpeg2dec, size); + + if (!skipped) + { + mpeg2dec->bytes_since_tag += size; + return STATE_BUFFER; + } + + mpeg2dec->bytes_since_tag += skipped; + mpeg2dec->code = mpeg2dec->buf_start[-1]; + + return STATE_INTERNAL_NORETURN; +} + +mpeg2_state_t mpeg2_seek_header (mpeg2dec_t * mpeg2dec) +{ + while (!(mpeg2dec->code == 0xb3 || + ((mpeg2dec->code == 0xb7 || mpeg2dec->code == 0xb8 || + !mpeg2dec->code) && mpeg2dec->sequence.width != (unsigned)-1))) + { + if (seek_chunk (mpeg2dec) == STATE_BUFFER) + return STATE_BUFFER; + } + + mpeg2dec->chunk_start = + mpeg2dec->chunk_ptr = mpeg2dec->chunk_buffer; + + mpeg2dec->user_data_len = 0; + + return ((mpeg2dec->code == 0xb7) ? + mpeg2_header_end(mpeg2dec) : mpeg2_parse_header(mpeg2dec)); +} + +#define RECEIVED(code,state) (((state) << 8) + (code)) + +mpeg2_state_t mpeg2_parse (mpeg2dec_t * mpeg2dec) +{ + int size_buffer, size_chunk, copied; + + if (mpeg2dec->action) + { + mpeg2_state_t state; + + state = mpeg2dec->action (mpeg2dec); + + if (state > STATE_INTERNAL_NORETURN) + return state; + } + + while (1) + { + while ((unsigned) (mpeg2dec->code - mpeg2dec->first_decode_slice) < + mpeg2dec->nb_decode_slices) + { + size_buffer = mpeg2dec->buf_end - mpeg2dec->buf_start; + size_chunk = mpeg2dec->chunk_buffer + BUFFER_SIZE - + mpeg2dec->chunk_ptr; + + if (size_buffer <= size_chunk) + { + copied = copy_chunk (mpeg2dec, size_buffer); + + if (!copied) + { + mpeg2dec->bytes_since_tag += size_buffer; + mpeg2dec->chunk_ptr += size_buffer; + return STATE_BUFFER; + } + } + else + { + copied = copy_chunk (mpeg2dec, size_chunk); + + if (!copied) + { + /* filled the chunk buffer without finding a start code */ + mpeg2dec->bytes_since_tag += size_chunk; + mpeg2dec->action = seek_chunk; + return STATE_INVALID; + } + } + + mpeg2dec->bytes_since_tag += copied; + + mpeg2_slice (&mpeg2dec->decoder, mpeg2dec->code, + mpeg2dec->chunk_start); + mpeg2dec->code = mpeg2dec->buf_start[-1]; + mpeg2dec->chunk_ptr = mpeg2dec->chunk_start; + } + + if ((unsigned) (mpeg2dec->code - 1) >= 0xb0 - 1) + break; + + if (seek_chunk (mpeg2dec) == STATE_BUFFER) + return STATE_BUFFER; + } + + mpeg2dec->action = mpeg2_seek_header; + + switch (mpeg2dec->code) + { + case 0x00: + return mpeg2dec->state; + case 0xb3: + case 0xb7: + case 0xb8: + return (mpeg2dec->state == STATE_SLICE) ? STATE_SLICE : STATE_INVALID; + default: + mpeg2dec->action = seek_chunk; + return STATE_INVALID; + } +} + +mpeg2_state_t mpeg2_parse_header (mpeg2dec_t * mpeg2dec) +{ + static int (* const process_header[9]) (mpeg2dec_t *) = + { + mpeg2_header_picture, + mpeg2_header_extension, + mpeg2_header_user_data, + mpeg2_header_sequence, + NULL, + NULL, + NULL, + NULL, + mpeg2_header_gop + }; + + int size_buffer, size_chunk, copied; + + mpeg2dec->action = mpeg2_parse_header; + mpeg2dec->info.user_data = NULL; + mpeg2dec->info.user_data_len = 0; + + while (1) + { + size_buffer = mpeg2dec->buf_end - mpeg2dec->buf_start; + size_chunk = mpeg2dec->chunk_buffer + BUFFER_SIZE - + mpeg2dec->chunk_ptr; + + if (size_buffer <= size_chunk) + { + copied = copy_chunk (mpeg2dec, size_buffer); + + if (!copied) + { + mpeg2dec->bytes_since_tag += size_buffer; + mpeg2dec->chunk_ptr += size_buffer; + return STATE_BUFFER; + } + } + else + { + copied = copy_chunk (mpeg2dec, size_chunk); + + if (!copied) + { + /* filled the chunk buffer without finding a start code */ + mpeg2dec->bytes_since_tag += size_chunk; + mpeg2dec->code = 0xb4; + mpeg2dec->action = mpeg2_seek_header; + return STATE_INVALID; + } + } + + mpeg2dec->bytes_since_tag += copied; + + if (process_header[mpeg2dec->code & 0x0b] (mpeg2dec)) + { + mpeg2dec->code = mpeg2dec->buf_start[-1]; + mpeg2dec->action = mpeg2_seek_header; + return STATE_INVALID; + } + + mpeg2dec->code = mpeg2dec->buf_start[-1]; + + switch (RECEIVED (mpeg2dec->code, mpeg2dec->state)) + { + /* state transition after a sequence header */ + case RECEIVED (0x00, STATE_SEQUENCE): + case RECEIVED (0xb8, STATE_SEQUENCE): + mpeg2_header_sequence_finalize (mpeg2dec); + break; + + /* other legal state transitions */ + case RECEIVED (0x00, STATE_GOP): + mpeg2_header_gop_finalize (mpeg2dec); + break; + case RECEIVED (0x01, STATE_PICTURE): + case RECEIVED (0x01, STATE_PICTURE_2ND): + mpeg2_header_picture_finalize (mpeg2dec); + mpeg2dec->action = mpeg2_header_slice_start; + break; + + /* legal headers within a given state */ + case RECEIVED (0xb2, STATE_SEQUENCE): + case RECEIVED (0xb2, STATE_GOP): + case RECEIVED (0xb2, STATE_PICTURE): + case RECEIVED (0xb2, STATE_PICTURE_2ND): + case RECEIVED (0xb5, STATE_SEQUENCE): + case RECEIVED (0xb5, STATE_PICTURE): + case RECEIVED (0xb5, STATE_PICTURE_2ND): + mpeg2dec->chunk_ptr = mpeg2dec->chunk_start; + continue; + + default: + mpeg2dec->action = mpeg2_seek_header; + return STATE_INVALID; + } + + mpeg2dec->chunk_start = mpeg2dec->chunk_ptr = mpeg2dec->chunk_buffer; + mpeg2dec->user_data_len = 0; + + return mpeg2dec->state; + } +} + +int mpeg2_convert (mpeg2dec_t * mpeg2dec, mpeg2_convert_t convert, void * arg) +{ + mpeg2_convert_init_t convert_init; + int error; + + error = convert (MPEG2_CONVERT_SET, NULL, &mpeg2dec->sequence, 0, + arg, &convert_init); + + if (!error) + { + mpeg2dec->convert = convert; + mpeg2dec->convert_arg = arg; + mpeg2dec->convert_id_size = convert_init.id_size; + mpeg2dec->convert_stride = 0; + } + + return error; +} + +int mpeg2_stride (mpeg2dec_t * mpeg2dec, int stride) +{ + if (!mpeg2dec->convert) + { + if (stride < (int) mpeg2dec->sequence.width) + stride = mpeg2dec->sequence.width; + + mpeg2dec->decoder.stride_frame = stride; + } + else + { + mpeg2_convert_init_t convert_init; + + stride = mpeg2dec->convert(MPEG2_CONVERT_STRIDE, NULL, + &mpeg2dec->sequence, stride, + mpeg2dec->convert_arg, + &convert_init); + + mpeg2dec->convert_id_size = convert_init.id_size; + mpeg2dec->convert_stride = stride; + } + + return stride; +} + +void mpeg2_set_buf (mpeg2dec_t * mpeg2dec, uint8_t * buf[MPEG2_COMPONENTS], void * id) +{ + mpeg2_fbuf_t * fbuf; + + if (mpeg2dec->custom_fbuf) + { + if (mpeg2dec->state == STATE_SEQUENCE) + { + mpeg2dec->fbuf[2] = mpeg2dec->fbuf[1]; + mpeg2dec->fbuf[1] = mpeg2dec->fbuf[0]; + } + + mpeg2_set_fbuf (mpeg2dec, (mpeg2dec->decoder.coding_type == + PIC_FLAG_CODING_TYPE_B)); + + fbuf = mpeg2dec->fbuf[0]; + } + else + { + fbuf = &mpeg2dec->fbuf_alloc[mpeg2dec->alloc_index].fbuf; + mpeg2dec->alloc_index_user = ++mpeg2dec->alloc_index; + } + + fbuf->buf[0] = buf[0]; +#if MPEG2_COLOR + fbuf->buf[1] = buf[1]; + fbuf->buf[2] = buf[2]; +#endif + + fbuf->id = id; +} + +void mpeg2_custom_fbuf (mpeg2dec_t * mpeg2dec, int custom_fbuf) +{ + mpeg2dec->custom_fbuf = custom_fbuf; +} + +void mpeg2_skip (mpeg2dec_t * mpeg2dec, int skip) +{ + mpeg2dec->first_decode_slice = 1; + mpeg2dec->nb_decode_slices = skip ? 0 : (0xb0 - 1); +} + +void mpeg2_slice_region (mpeg2dec_t * mpeg2dec, int start, int end) +{ + start = (start < 1) ? 1 : (start > 0xb0) ? 0xb0 : start; + end = (end < start) ? start : (end > 0xb0) ? 0xb0 : end; + mpeg2dec->first_decode_slice = start; + mpeg2dec->nb_decode_slices = end - start; +} + +void mpeg2_tag_picture (mpeg2dec_t * mpeg2dec, uint32_t tag, uint32_t tag2) +{ + mpeg2dec->tag_previous = mpeg2dec->tag_current; + mpeg2dec->tag2_previous = mpeg2dec->tag2_current; + mpeg2dec->tag_current = tag; + mpeg2dec->tag2_current = tag2; + mpeg2dec->num_tags++; + mpeg2dec->bytes_since_tag = 0; +} + +void mpeg2_reset (mpeg2dec_t * mpeg2dec, int full_reset) +{ + mpeg2dec->buf_start = mpeg2dec->buf_end = NULL; + mpeg2dec->num_tags = 0; + mpeg2dec->shift = 0xffffff00; + mpeg2dec->code = 0xb4; + mpeg2dec->action = mpeg2_seek_header; + mpeg2dec->state = STATE_INVALID; + mpeg2dec->first = 1; + + mpeg2_reset_info(&mpeg2dec->info); + mpeg2dec->info.gop = NULL; + mpeg2dec->info.user_data = NULL; + mpeg2dec->info.user_data_len = 0; + + if (full_reset) + { + mpeg2dec->info.sequence = NULL; + mpeg2_header_state_init (mpeg2dec); + } +} + +mpeg2dec_t * mpeg2_init (void) +{ + mpeg2dec_t * mpeg2dec; + + mpeg2_idct_init (); + + mpeg2dec = (mpeg2dec_t *)mpeg2_bufalloc(sizeof (mpeg2dec_t), + MPEG2_ALLOC_MPEG2DEC); + if (mpeg2dec == NULL) + return NULL; + + mpeg2dec->decoder.DCTblock = static_dct_block; + + rb->memset (mpeg2dec->decoder.DCTblock, 0, DCT_BLOCKSIZE); + + DEBUGF("DCTblock: %p\n", mpeg2dec->decoder.DCTblock); + + mpeg2dec->chunk_buffer = (uint8_t *)mpeg2_bufalloc(BUFFER_SIZE + 4, + MPEG2_ALLOC_CHUNK); + + mpeg2dec->sequence.width = (unsigned)-1; + mpeg2_reset (mpeg2dec, 1); + + return mpeg2dec; +} + +void mpeg2_close (mpeg2dec_t * mpeg2dec) +{ + mpeg2_header_state_init (mpeg2dec); +#if 0 + /* These are dedicated buffers in rockbox */ + mpeg2_free (mpeg2dec->chunk_buffer); + mpeg2_free (mpeg2dec); +#endif +} diff --git a/apps/plugins/mpegplayer/libmpeg2/header.c b/apps/plugins/mpegplayer/libmpeg2/header.c new file mode 100644 index 0000000000..b40193a338 --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/header.c @@ -0,0 +1,1287 @@ +/* + * header.c + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 2003 Regis Duchesne + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + * libmpeg2 sync history: + * 2008-07-01 - CVS revision 1.101 + */ + +#include "plugin.h" + +#include "mpeg2dec_config.h" + +#include "mpeg2.h" +#include "attributes.h" +#include "mpeg2_internal.h" + +#define SEQ_EXT 2 +#define SEQ_DISPLAY_EXT 4 +#define QUANT_MATRIX_EXT 8 +#define COPYRIGHT_EXT 0x10 +#define PIC_DISPLAY_EXT 0x80 +#define PIC_CODING_EXT 0x100 + +/* default intra quant matrix, in zig-zag order */ +static const uint8_t default_intra_quantizer_matrix[64] = +{ + 8, + 16, 16, + 19, 16, 19, + 22, 22, 22, 22, + 22, 22, 26, 24, 26, + 27, 27, 27, 26, 26, 26, + 26, 27, 27, 27, 29, 29, 29, + 34, 34, 34, 29, 29, 29, 27, 27, + 29, 29, 32, 32, 34, 34, 37, + 38, 37, 35, 35, 34, 35, + 38, 38, 40, 40, 40, + 48, 48, 46, 46, + 56, 56, 58, + 69, 69, + 83 +}; + +const uint8_t default_mpeg2_scan_norm[64] = +{ + /* 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, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + +const uint8_t default_mpeg2_scan_alt[64] = +{ + /* 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, + 51, 59, 20, 28, 5, 13, 6, 14, + 21, 29, 36, 44, 52, 60, 37, 45, + 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) + { + mpeg2dec->sequence.width = (unsigned)-1; + mpeg2_mem_reset(); /* Clean the memory slate */ +#if 0 + if (!mpeg2dec->custom_fbuf) + { + int i; + for (i = mpeg2dec->alloc_index_user; + i < mpeg2dec->alloc_index; i++) + { + mpeg2_free(mpeg2dec->fbuf_alloc[i].fbuf.buf[0]); +#if MPEG2_COLOR + mpeg2_free(mpeg2dec->fbuf_alloc[i].fbuf.buf[1]); + mpeg2_free(mpeg2dec->fbuf_alloc[i].fbuf.buf[2]); +#endif + } + } + + if (mpeg2dec->convert_start) + { + int i; + for (i = 0; i < 3; i++) + { + mpeg2_free(mpeg2dec->yuv_buf[i][0]); +#if MPEG2_COLOR + mpeg2_free(mpeg2dec->yuv_buf[i][1]); + mpeg2_free(mpeg2dec->yuv_buf[i][2]); +#endif + } + } + + if (mpeg2dec->decoder.convert_id) + { + mpeg2_free(mpeg2dec->decoder.convert_id); + } +#endif + } + + mpeg2dec->decoder.coding_type = I_TYPE; + mpeg2dec->decoder.convert = NULL; + mpeg2dec->decoder.convert_id = NULL; + + mpeg2dec->picture = mpeg2dec->pictures; + + mpeg2dec->fbuf[0] = &mpeg2dec->fbuf_alloc[0].fbuf; + mpeg2dec->fbuf[1] = &mpeg2dec->fbuf_alloc[1].fbuf; + mpeg2dec->fbuf[2] = &mpeg2dec->fbuf_alloc[2].fbuf; + + mpeg2dec->first = 1; + mpeg2dec->alloc_index = 0; + mpeg2dec->alloc_index_user = 0; + mpeg2dec->first_decode_slice = 1; + mpeg2dec->nb_decode_slices = 0xb0 - 1; + mpeg2dec->convert = NULL; + mpeg2dec->convert_start = NULL; + mpeg2dec->custom_fbuf = 0; + mpeg2dec->yuv_index = 0; +} + +void mpeg2_reset_info (mpeg2_info_t * info) +{ + info->current_picture = + info->current_picture_2nd = NULL; + + info->display_picture = + info->display_picture_2nd = NULL; + + info->current_fbuf = + info->display_fbuf = + info->discard_fbuf = NULL; +} + +static void info_user_data (mpeg2dec_t * mpeg2dec) +{ + if (mpeg2dec->user_data_len) + { + mpeg2dec->info.user_data = mpeg2dec->chunk_buffer; + mpeg2dec->info.user_data_len = mpeg2dec->user_data_len - 3; + } +} + +int mpeg2_header_sequence (mpeg2dec_t * mpeg2dec) +{ + static const unsigned int frame_period[16] = + { + 0, 1126125, 1125000, 1080000, 900900, 900000, 540000, 450450, 450000, + /* unofficial: xing 15 fps */ + 1800000, + /* unofficial: libmpeg3 "Unofficial economy rates" 5/10/12/15 fps */ + 5400000, 2700000, 2250000, 1800000, 0, 0 + }; + + uint8_t * buffer = mpeg2dec->chunk_start; + mpeg2_sequence_t * sequence = &mpeg2dec->new_sequence; + int i; + + if ((buffer[6] & 0x20) != 0x20) /* missing marker_bit */ + return 1; + + i = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2]; + + if (!(sequence->display_width = sequence->picture_width = i >> 12)) + return 1; + + if (!(sequence->display_height = sequence->picture_height = i & 0xfff)) + return 1; + + sequence->width = (sequence->picture_width + 15) & ~15; + sequence->height = (sequence->picture_height + 15) & ~15; + sequence->chroma_width = sequence->width >> 1; + sequence->chroma_height = sequence->height >> 1; + + sequence->flags = SEQ_FLAG_PROGRESSIVE_SEQUENCE | + SEQ_VIDEO_FORMAT_UNSPECIFIED; + + sequence->pixel_width = buffer[3] >> 4; /* aspect ratio */ + sequence->frame_period = frame_period[buffer[3] & 15]; + + sequence->byte_rate = (buffer[4]<<10) | (buffer[5]<<2) | (buffer[6]>>6); + + sequence->vbv_buffer_size = ((buffer[6]<<16) | (buffer[7]<<8)) & 0x1ff800; + + if (buffer[7] & 4) + sequence->flags |= SEQ_FLAG_CONSTRAINED_PARAMETERS; + + mpeg2dec->copy_matrix = 3; + + if (buffer[7] & 2) + { + for (i = 0; i < 64; i++) + { + mpeg2dec->new_quantizer_matrix[0][mpeg2_scan_norm[i]] = + (buffer[i+7] << 7) | (buffer[i+8] >> 1); + } + + buffer += 64; + } + else + { + for (i = 0; i < 64; i++) + { + mpeg2dec->new_quantizer_matrix[0][mpeg2_scan_norm[i]] = + default_intra_quantizer_matrix[i]; + } + } + + if (buffer[7] & 1) + { + for (i = 0; i < 64; i++) + { + mpeg2dec->new_quantizer_matrix[1][mpeg2_scan_norm[i]] = + buffer[i+8]; + } + } + else + { + rb->memset (mpeg2dec->new_quantizer_matrix[1], 16, 64); + } + + sequence->profile_level_id = 0x80; + sequence->colour_primaries = 0; + sequence->transfer_characteristics = 0; + sequence->matrix_coefficients = 0; + + mpeg2dec->ext_state = SEQ_EXT; + mpeg2dec->state = STATE_SEQUENCE; + + mpeg2dec->display_offset_x = + mpeg2dec->display_offset_y = 0; + + return 0; +} + +static int sequence_ext (mpeg2dec_t * mpeg2dec) +{ + uint8_t * buffer = mpeg2dec->chunk_start; + mpeg2_sequence_t * sequence = &mpeg2dec->new_sequence; + uint32_t flags; + + if (!(buffer[3] & 1)) + return 1; + + sequence->profile_level_id = (buffer[0] << 4) | (buffer[1] >> 4); + + sequence->picture_width += ((buffer[1] << 13) | (buffer[2] << 5)) & 0x3000; + sequence->display_width = sequence->picture_width; + + sequence->picture_height += (buffer[2] << 7) & 0x3000; + sequence->display_height = sequence->picture_height; + + sequence->width = (sequence->picture_width + 15) & ~15; + sequence->height = (sequence->picture_height + 15) & ~15; + + flags = sequence->flags | SEQ_FLAG_MPEG2; + + if (!(buffer[1] & 8)) + { + flags &= ~SEQ_FLAG_PROGRESSIVE_SEQUENCE; + sequence->height = (sequence->height + 31) & ~31; + } + + if (buffer[5] & 0x80) + flags |= SEQ_FLAG_LOW_DELAY; + + sequence->flags = flags; + sequence->chroma_width = sequence->width; + sequence->chroma_height = sequence->height; + + switch (buffer[1] & 6) + { + case 0: /* invalid */ + return 1; + case 2: /* 4:2:0 */ + sequence->chroma_height >>= 1; + /* fallthrough */ + case 4: /* 4:2:2 */ + sequence->chroma_width >>= 1; + } + + sequence->byte_rate += ((buffer[2]<<25) | (buffer[3]<<17)) & 0x3ffc0000; + + sequence->vbv_buffer_size |= buffer[4] << 21; + + sequence->frame_period = + sequence->frame_period * ((buffer[5]&31)+1) / (((buffer[5]>>2)&3)+1); + + mpeg2dec->ext_state = SEQ_DISPLAY_EXT; + + return 0; +} + +static int sequence_display_ext (mpeg2dec_t * mpeg2dec) +{ + uint8_t * buffer = mpeg2dec->chunk_start; + mpeg2_sequence_t * sequence = &mpeg2dec->new_sequence; + int x; + + sequence->flags = (sequence->flags & ~SEQ_MASK_VIDEO_FORMAT) | + ((buffer[0] << 4) & SEQ_MASK_VIDEO_FORMAT); + + if (buffer[0] & 1) + { + sequence->flags |= SEQ_FLAG_COLOUR_DESCRIPTION; + sequence->colour_primaries = buffer[1]; + sequence->transfer_characteristics = buffer[2]; + sequence->matrix_coefficients = buffer[3]; + buffer += 3; + } + + if (!(buffer[2] & 2)) /* missing marker_bit */ + return 1; + + x = (buffer[1] << 6) | (buffer[2] >> 2); + if (x) + sequence->display_width = x; + + x = ((buffer[2] & 1) << 13) | (buffer[3] << 5) | (buffer[4] >> 3); + if (x) + sequence->display_height = x; + + return 0; +} + +static inline void simplify (unsigned int * u, unsigned int * v) +{ + unsigned int a, b, tmp; + + a = *u; + b = *v; + + /* find greatest common divisor */ + while (a) + { + tmp = a; + a = b % tmp; + b = tmp; + } + + *u /= b; + *v /= b; +} + +static inline void finalize_sequence (mpeg2_sequence_t * sequence) +{ + int width; + int height; + + sequence->byte_rate *= 50; + + if (sequence->flags & SEQ_FLAG_MPEG2) + { + switch (sequence->pixel_width) + { + case 1: /* square pixels */ + sequence->pixel_width = + sequence->pixel_height = 1; + return; + case 2: /* 4:3 aspect ratio */ + width = 4; + height = 3; + break; + case 3: /* 16:9 aspect ratio */ + width = 16; + height = 9; + break; + case 4: /* 2.21:1 aspect ratio */ + width = 221; + height = 100; + break; + default: /* illegal */ + sequence->pixel_width = + sequence->pixel_height = 0; + return; + } + + width *= sequence->display_height; + height *= sequence->display_width; + } + else + { + if (sequence->byte_rate == 50 * 0x3ffff) + sequence->byte_rate = 0; /* mpeg-1 VBR */ + + switch (sequence->pixel_width) + { + case 0: + case 15: /* illegal */ + sequence->pixel_width = + sequence->pixel_height = 0; + return; + case 1: /* square pixels */ + sequence->pixel_width = + sequence->pixel_height = 1; + return; + case 3: /* 720x576 16:9 */ + sequence->pixel_width = 64; + sequence->pixel_height = 45; + return; + case 6: /* 720x480 16:9 */ + sequence->pixel_width = 32; + sequence->pixel_height = 27; + return; + case 8: /* BT.601 625 lines 4:3 */ + sequence->pixel_width = 59; + sequence->pixel_height = 54; + return; + case 12: /* BT.601 525 lines 4:3 */ + sequence->pixel_width = 10; + sequence->pixel_height = 11; + return; + default: + height = 88 * sequence->pixel_width + 1171; + width = 2000; + } + } + + sequence->pixel_width = width; + sequence->pixel_height = height; + + simplify(&sequence->pixel_width, &sequence->pixel_height); +} + +int mpeg2_guess_aspect (const mpeg2_sequence_t * sequence, + unsigned int * pixel_width, + unsigned int * pixel_height) +{ + static const struct + { + unsigned int width, height; + } video_modes[] = + { + {720, 576}, /* 625 lines, 13.5 MHz (D1, DV, DVB, DVD) */ + {704, 576}, /* 625 lines, 13.5 MHz (1/1 D1, DVB, DVD, 4CIF) */ + {544, 576}, /* 625 lines, 10.125 MHz (DVB, laserdisc) */ + {528, 576}, /* 625 lines, 10.125 MHz (3/4 D1, DVB, laserdisc) */ + {480, 576}, /* 625 lines, 9 MHz (2/3 D1, DVB, SVCD) */ + {352, 576}, /* 625 lines, 6.75 MHz (D2, 1/2 D1, CVD, DVB, DVD) */ + {352, 288}, /* 625 lines, 6.75 MHz, 1 field (D4, VCD, DVB, DVD, CIF) */ + {176, 144}, /* 625 lines, 3.375 MHz, half field (QCIF) */ + {720, 486}, /* 525 lines, 13.5 MHz (D1) */ + {704, 486}, /* 525 lines, 13.5 MHz */ + {720, 480}, /* 525 lines, 13.5 MHz (DV, DSS, DVD) */ + {704, 480}, /* 525 lines, 13.5 MHz (1/1 D1, ATSC, DVD) */ + {544, 480}, /* 525 lines. 10.125 MHz (DSS, laserdisc) */ + {528, 480}, /* 525 lines. 10.125 MHz (3/4 D1, laserdisc) */ + {480, 480}, /* 525 lines, 9 MHz (2/3 D1, SVCD) */ + {352, 480}, /* 525 lines, 6.75 MHz (D2, 1/2 D1, CVD, DVD) */ + {352, 240} /* 525 lines. 6.75 MHz, 1 field (D4, VCD, DSS, DVD) */ + }; + unsigned int width, height, pix_width, pix_height, i, DAR_16_9; + + *pixel_width = sequence->pixel_width; + *pixel_height = sequence->pixel_height; + width = sequence->picture_width; + height = sequence->picture_height; + + for (i = 0; i < sizeof (video_modes) / sizeof (video_modes[0]); i++) + { + if (width == video_modes[i].width && height == video_modes[i].height) + break; + } + + if (i == ARRAYLEN(video_modes) || + (sequence->pixel_width == 1 && sequence->pixel_height == 1) || + width != sequence->display_width || height != sequence->display_height) + { + return 0; + } + + for (pix_height = 1; height * pix_height < 480; pix_height <<= 1); + height *= pix_height; + + for (pix_width = 1; width * pix_width <= 352; pix_width <<= 1); + width *= pix_width; + + if (!(sequence->flags & SEQ_FLAG_MPEG2)) + { + static unsigned int mpeg1_check[2][2] = {{11, 54}, {27, 45}}; + DAR_16_9 = (sequence->pixel_height == 27 || + sequence->pixel_height == 45); + if (width < 704 || + sequence->pixel_height != mpeg1_check[DAR_16_9][height == 576]) + return 0; + } + else + { + DAR_16_9 = (3 * sequence->picture_width * sequence->pixel_width > + 4 * sequence->picture_height * sequence->pixel_height); + switch (width) + { + case 528: + case 544: + pix_width *= 4; + pix_height *= 3; + break; + case 480: + pix_width *= 3; + pix_height *= 2; + break; + } + } + + if (DAR_16_9) + { + pix_width *= 4; + pix_height *= 3; + } + + if (height == 576) + { + pix_width *= 59; + pix_height *= 54; + } + else + { + pix_width *= 10; + pix_height *= 11; + } + + *pixel_width = pix_width; + *pixel_height = pix_height; + + simplify (pixel_width, pixel_height); + + return (height == 576) ? 1 : 2; +} + +static void copy_matrix (mpeg2dec_t * mpeg2dec, int index) +{ + if (rb->memcmp (mpeg2dec->quantizer_matrix[index], + mpeg2dec->new_quantizer_matrix[index], 64)) + { + rb->memcpy (mpeg2dec->quantizer_matrix[index], + mpeg2dec->new_quantizer_matrix[index], 64); + + mpeg2dec->scaled[index] = -1; + } +} + +static void finalize_matrix (mpeg2dec_t * mpeg2dec) +{ + mpeg2_decoder_t *decoder = &mpeg2dec->decoder; + int i; + + for (i = 0; i < 2; i++) + { + if (mpeg2dec->copy_matrix & (1 << i)) + copy_matrix (mpeg2dec, i); + + if ((mpeg2dec->copy_matrix & (4 << i)) && + rb->memcmp(mpeg2dec->quantizer_matrix[i], + mpeg2dec->new_quantizer_matrix[i+2], 64)) + { + copy_matrix (mpeg2dec, i + 2); + decoder->chroma_quantizer[i] = decoder->quantizer_prescale[i+2]; + } + else if (mpeg2dec->copy_matrix & (5 << i)) + { + decoder->chroma_quantizer[i] = decoder->quantizer_prescale[i]; + } + } +} + +static mpeg2_state_t invalid_end_action (mpeg2dec_t * mpeg2dec) +{ + mpeg2_reset_info (&mpeg2dec->info); + + mpeg2dec->info.gop = NULL; + + info_user_data (mpeg2dec); + + mpeg2_header_state_init (mpeg2dec); + + mpeg2dec->sequence = mpeg2dec->new_sequence; + mpeg2dec->action = mpeg2_seek_header; + mpeg2dec->state = STATE_SEQUENCE; + + return STATE_SEQUENCE; +} + +void mpeg2_header_sequence_finalize (mpeg2dec_t * mpeg2dec) +{ + mpeg2_sequence_t * sequence = &mpeg2dec->new_sequence; + mpeg2_decoder_t * decoder = &mpeg2dec->decoder; + + finalize_sequence(sequence); + finalize_matrix(mpeg2dec); + + decoder->mpeg1 = !(sequence->flags & SEQ_FLAG_MPEG2); + decoder->width = sequence->width; + decoder->height = sequence->height; + decoder->vertical_position_extension = sequence->picture_height > 2800; + decoder->chroma_format = (sequence->chroma_width == sequence->width) + + (sequence->chroma_height == sequence->height); + + if (mpeg2dec->sequence.width != (unsigned)-1) + { + /* + * According to 6.1.1.6, repeat sequence headers should be + * identical to the original. However some encoders don't + * respect that and change various fields (including bitrate + * and aspect ratio) in the repeat sequence headers. So we + * choose to be as conservative as possible and only restart + * the decoder if the width, height, chroma_width, + * chroma_height or low_delay flag are modified. + */ + if (sequence->width != mpeg2dec->sequence.width || + sequence->height != mpeg2dec->sequence.height || + sequence->chroma_width != mpeg2dec->sequence.chroma_width || + sequence->chroma_height != mpeg2dec->sequence.chroma_height || + ((sequence->flags ^ mpeg2dec->sequence.flags) & + SEQ_FLAG_LOW_DELAY)) + { + decoder->stride_frame = sequence->width; + mpeg2_header_end (mpeg2dec); + mpeg2dec->action = invalid_end_action; + mpeg2dec->state = STATE_INVALID_END; + return; + } + + mpeg2dec->state = rb->memcmp(&mpeg2dec->sequence, sequence, + sizeof (mpeg2_sequence_t)) ? + STATE_SEQUENCE_MODIFIED : + STATE_SEQUENCE_REPEATED; + } + else + { + decoder->stride_frame = sequence->width; + } + + mpeg2dec->sequence = *sequence; + mpeg2_reset_info(&mpeg2dec->info); + mpeg2dec->info.sequence = &mpeg2dec->sequence; + mpeg2dec->info.gop = NULL; + + info_user_data (mpeg2dec); +} + +int mpeg2_header_gop (mpeg2dec_t * mpeg2dec) +{ + uint8_t * buffer = mpeg2dec->chunk_start; + mpeg2_gop_t * gop = &mpeg2dec->new_gop; + + if (!(buffer[1] & 8)) + return 1; + + gop->hours = (buffer[0] >> 2) & 31; + gop->minutes = ((buffer[0] << 4) | (buffer[1] >> 4)) & 63; + gop->seconds = ((buffer[1] << 3) | (buffer[2] >> 5)) & 63; + gop->pictures = ((buffer[2] << 1) | (buffer[3] >> 7)) & 63; + gop->flags = (buffer[0] >> 7) | ((buffer[3] >> 4) & 6); + + mpeg2dec->state = STATE_GOP; + return 0; +} + +void mpeg2_header_gop_finalize (mpeg2dec_t * mpeg2dec) +{ + mpeg2dec->gop = mpeg2dec->new_gop; + mpeg2_reset_info (&mpeg2dec->info); + mpeg2dec->info.gop = &mpeg2dec->gop; + info_user_data (mpeg2dec); +} + +void mpeg2_set_fbuf (mpeg2dec_t * mpeg2dec, int b_type) +{ + int i; + + for (i = 0; i < 3; i++) + { + if (mpeg2dec->fbuf[1] != &mpeg2dec->fbuf_alloc[i].fbuf && + mpeg2dec->fbuf[2] != &mpeg2dec->fbuf_alloc[i].fbuf) + { + mpeg2dec->fbuf[0] = &mpeg2dec->fbuf_alloc[i].fbuf; + mpeg2dec->info.current_fbuf = mpeg2dec->fbuf[0]; + + if (b_type || (mpeg2dec->sequence.flags & SEQ_FLAG_LOW_DELAY)) + { + if (b_type || mpeg2dec->convert) + mpeg2dec->info.discard_fbuf = mpeg2dec->fbuf[0]; + + mpeg2dec->info.display_fbuf = mpeg2dec->fbuf[0]; + } + + break; + } + } +} + +int mpeg2_header_picture (mpeg2dec_t * mpeg2dec) +{ + uint8_t * buffer = mpeg2dec->chunk_start; + mpeg2_picture_t * picture = &mpeg2dec->new_picture; + mpeg2_decoder_t * decoder = &mpeg2dec->decoder; + int type; + + mpeg2dec->state = (mpeg2dec->state != STATE_SLICE_1ST) ? + STATE_PICTURE : STATE_PICTURE_2ND; + mpeg2dec->ext_state = PIC_CODING_EXT; + + picture->temporal_reference = (buffer[0] << 2) | (buffer[1] >> 6); + + type = (buffer [1] >> 3) & 7; + + if (type == PIC_FLAG_CODING_TYPE_P || type == PIC_FLAG_CODING_TYPE_B) + { + /* forward_f_code and backward_f_code - used in mpeg1 only */ + decoder->f_motion.f_code[1] = (buffer[3] >> 2) & 1; + decoder->f_motion.f_code[0] = + (((buffer[3] << 1) | (buffer[4] >> 7)) & 7) - 1; + decoder->b_motion.f_code[1] = (buffer[4] >> 6) & 1; + decoder->b_motion.f_code[0] = ((buffer[4] >> 3) & 7) - 1; + } + + picture->flags = PIC_FLAG_PROGRESSIVE_FRAME | type; + picture->tag = picture->tag2 = 0; + + if (mpeg2dec->num_tags) + { + if (mpeg2dec->bytes_since_tag >= mpeg2dec->chunk_ptr - buffer + 4) + { + mpeg2dec->num_tags = 0; + picture->tag = mpeg2dec->tag_current; + picture->tag2 = mpeg2dec->tag2_current; + picture->flags |= PIC_FLAG_TAGS; + } + else if (mpeg2dec->num_tags > 1) + { + mpeg2dec->num_tags = 1; + picture->tag = mpeg2dec->tag_previous; + picture->tag2 = mpeg2dec->tag2_previous; + picture->flags |= PIC_FLAG_TAGS; + } + } + + picture->nb_fields = 2; + picture->display_offset[0].x = picture->display_offset[1].x = + picture->display_offset[2].x = mpeg2dec->display_offset_x; + + picture->display_offset[0].y = picture->display_offset[1].y = + picture->display_offset[2].y = mpeg2dec->display_offset_y; + + /* XXXXXX decode extra_information_picture as well */ + + mpeg2dec->q_scale_type = 0; + decoder->intra_dc_precision = 7; + decoder->frame_pred_frame_dct = 1; + decoder->concealment_motion_vectors = 0; + decoder->scan = mpeg2_scan_norm; + decoder->picture_structure = FRAME_PICTURE; + mpeg2dec->copy_matrix = 0; + + return 0; +} + +static int picture_coding_ext (mpeg2dec_t * mpeg2dec) +{ + uint8_t * buffer = mpeg2dec->chunk_start; + mpeg2_picture_t * picture = &mpeg2dec->new_picture; + mpeg2_decoder_t * decoder = &mpeg2dec->decoder; + uint32_t flags; + + /* pre subtract 1 for use later in compute_motion_vector */ + decoder->f_motion.f_code[0] = (buffer[0] & 15) - 1; + decoder->f_motion.f_code[1] = (buffer[1] >> 4) - 1; + decoder->b_motion.f_code[0] = (buffer[1] & 15) - 1; + decoder->b_motion.f_code[1] = (buffer[2] >> 4) - 1; + + flags = picture->flags; + + decoder->intra_dc_precision = 7 - ((buffer[2] >> 2) & 3); + decoder->picture_structure = buffer[2] & 3; + + switch (decoder->picture_structure) + { + case TOP_FIELD: + flags |= PIC_FLAG_TOP_FIELD_FIRST; + case BOTTOM_FIELD: + picture->nb_fields = 1; + break; + case FRAME_PICTURE: + if (!(mpeg2dec->sequence.flags & SEQ_FLAG_PROGRESSIVE_SEQUENCE)) + { + picture->nb_fields = (buffer[3] & 2) ? 3 : 2; + flags |= (buffer[3] & 128) ? PIC_FLAG_TOP_FIELD_FIRST : 0; + } + else + { + picture->nb_fields = (buffer[3]&2) ? ((buffer[3]&128) ? 6 : 4) : 2; + } + break; + default: + return 1; + } + + decoder->top_field_first = buffer[3] >> 7; + decoder->frame_pred_frame_dct = (buffer[3] >> 6) & 1; + decoder->concealment_motion_vectors = (buffer[3] >> 5) & 1; + mpeg2dec->q_scale_type = buffer[3] & 16; + decoder->intra_vlc_format = (buffer[3] >> 3) & 1; + decoder->scan = (buffer[3] & 4) ? mpeg2_scan_alt : mpeg2_scan_norm; + + if (!(buffer[4] & 0x80)) + flags &= ~PIC_FLAG_PROGRESSIVE_FRAME; + + if (buffer[4] & 0x40) + { + flags |= (((buffer[4]<<26) | (buffer[5]<<18) | (buffer[6]<<10)) & + PIC_MASK_COMPOSITE_DISPLAY) | PIC_FLAG_COMPOSITE_DISPLAY; + } + + picture->flags = flags; + + mpeg2dec->ext_state = PIC_DISPLAY_EXT | COPYRIGHT_EXT | QUANT_MATRIX_EXT; + + return 0; +} + +static int picture_display_ext (mpeg2dec_t * mpeg2dec) +{ + uint8_t * buffer = mpeg2dec->chunk_start; + mpeg2_picture_t * picture = &mpeg2dec->new_picture; + int i, nb_pos; + + nb_pos = picture->nb_fields; + + if (mpeg2dec->sequence.flags & SEQ_FLAG_PROGRESSIVE_SEQUENCE) + nb_pos >>= 1; + + for (i = 0; i < nb_pos; i++) + { + int x, y; + + x = ((buffer[4*i] << 24) | (buffer[4*i+1] << 16) | + (buffer[4*i+2] << 8) | buffer[4*i+3]) >> (11-2*i); + + y = ((buffer[4*i+2] << 24) | (buffer[4*i+3] << 16) | + (buffer[4*i+4] << 8) | buffer[4*i+5]) >> (10-2*i); + + if (! (x & y & 1)) + return 1; + + picture->display_offset[i].x = mpeg2dec->display_offset_x = x >> 1; + picture->display_offset[i].y = mpeg2dec->display_offset_y = y >> 1; + } + + for (; i < 3; i++) + { + picture->display_offset[i].x = mpeg2dec->display_offset_x; + picture->display_offset[i].y = mpeg2dec->display_offset_y; + } + + return 0; +} + +void mpeg2_header_picture_finalize (mpeg2dec_t * mpeg2dec) +{ + mpeg2_decoder_t * decoder = &mpeg2dec->decoder; + int old_type_b = decoder->coding_type == B_TYPE; + int low_delay = mpeg2dec->sequence.flags & SEQ_FLAG_LOW_DELAY; + + finalize_matrix (mpeg2dec); + decoder->coding_type = mpeg2dec->new_picture.flags & PIC_MASK_CODING_TYPE; + + if (mpeg2dec->state == STATE_PICTURE) + { + mpeg2_picture_t * picture; + mpeg2_picture_t * other; + + decoder->second_field = 0; + + picture = other = mpeg2dec->pictures; + + if (old_type_b ^ (mpeg2dec->picture < mpeg2dec->pictures + 2)) + picture += 2; + else + other += 2; + + mpeg2dec->picture = picture; + *picture = mpeg2dec->new_picture; + + if (!old_type_b) + { + mpeg2dec->fbuf[2] = mpeg2dec->fbuf[1]; + mpeg2dec->fbuf[1] = mpeg2dec->fbuf[0]; + } + + mpeg2dec->fbuf[0] = NULL; + mpeg2_reset_info (&mpeg2dec->info); + mpeg2dec->info.current_picture = picture; + mpeg2dec->info.display_picture = picture; + + if (decoder->coding_type != B_TYPE) + { + if (!low_delay) + { + if (mpeg2dec->first) + { + mpeg2dec->info.display_picture = NULL; + mpeg2dec->first = 0; + } + else + { + mpeg2dec->info.display_picture = other; + + if (other->nb_fields == 1) + mpeg2dec->info.display_picture_2nd = other + 1; + + mpeg2dec->info.display_fbuf = mpeg2dec->fbuf[1]; + } + } + + if (!low_delay + !mpeg2dec->convert) + { + mpeg2dec->info.discard_fbuf = + mpeg2dec->fbuf[!low_delay + !mpeg2dec->convert]; + } + } + + if (mpeg2dec->convert) + { + mpeg2_convert_init_t convert_init; + + if (!mpeg2dec->convert_start) + { + mpeg2dec->decoder.convert_id = + mpeg2_malloc (mpeg2dec->convert_id_size, + MPEG2_ALLOC_CONVERT_ID); + + mpeg2dec->convert (MPEG2_CONVERT_START, + mpeg2dec->decoder.convert_id, + &mpeg2dec->sequence, + mpeg2dec->convert_stride, + mpeg2dec->convert_arg, &convert_init); + + mpeg2dec->convert_start = convert_init.start; + mpeg2dec->decoder.convert = convert_init.copy; + + int y_size = decoder->stride_frame * mpeg2dec->sequence.height; + + mpeg2dec->yuv_buf[0][0] = + (uint8_t *) mpeg2_malloc(y_size, MPEG2_ALLOC_YUV); +#if MPEG2_COLOR + int uv_size = y_size >> (2 - mpeg2dec->decoder.chroma_format); + + mpeg2dec->yuv_buf[0][1] = + (uint8_t *) mpeg2_malloc(uv_size, MPEG2_ALLOC_YUV); + mpeg2dec->yuv_buf[0][2] = + (uint8_t *) mpeg2_malloc(uv_size, MPEG2_ALLOC_YUV); +#endif + + mpeg2dec->yuv_buf[1][0] = + (uint8_t *) mpeg2_malloc(y_size, MPEG2_ALLOC_YUV); +#if MPEG2_COLOR + mpeg2dec->yuv_buf[1][1] = + (uint8_t *) mpeg2_malloc(uv_size, MPEG2_ALLOC_YUV); + mpeg2dec->yuv_buf[1][2] = + (uint8_t *) mpeg2_malloc(uv_size, MPEG2_ALLOC_YUV); +#endif + y_size = decoder->stride_frame * 32; + + mpeg2dec->yuv_buf[2][0] = + (uint8_t *) mpeg2_malloc(y_size, MPEG2_ALLOC_YUV); +#if MPEG2_COLOR + uv_size = y_size >> (2 - mpeg2dec->decoder.chroma_format); + + mpeg2dec->yuv_buf[2][1] = + (uint8_t *) mpeg2_malloc(uv_size, MPEG2_ALLOC_YUV); + mpeg2dec->yuv_buf[2][2] = + (uint8_t *) mpeg2_malloc(uv_size, MPEG2_ALLOC_YUV); +#endif + } + + if (!mpeg2dec->custom_fbuf) + { + while (mpeg2dec->alloc_index < 3) + { + mpeg2_fbuf_t * fbuf; + + fbuf = &mpeg2dec->fbuf_alloc[mpeg2dec->alloc_index++].fbuf; + fbuf->id = NULL; + + fbuf->buf[0] = + (uint8_t *) mpeg2_malloc (convert_init.buf_size[0], + MPEG2_ALLOC_CONVERTED); +#if MPEG2_COLOR + fbuf->buf[1] = + (uint8_t *) mpeg2_malloc (convert_init.buf_size[1], + MPEG2_ALLOC_CONVERTED); + fbuf->buf[2] = + (uint8_t *) mpeg2_malloc (convert_init.buf_size[2], + MPEG2_ALLOC_CONVERTED); +#endif + } + + mpeg2_set_fbuf (mpeg2dec, (decoder->coding_type == B_TYPE)); + } + } + else if (!mpeg2dec->custom_fbuf) + { + while (mpeg2dec->alloc_index < 3) + { + mpeg2_fbuf_t * fbuf; + + fbuf = &mpeg2dec->fbuf_alloc[mpeg2dec->alloc_index++].fbuf; + + fbuf->id = NULL; + + int y_size = decoder->stride_frame * mpeg2dec->sequence.height; + + fbuf->buf[0] = (uint8_t *) mpeg2_malloc (y_size, + MPEG2_ALLOC_YUV); +#if MPEG2_COLOR + int uv_size = y_size >> (2 - decoder->chroma_format); + + fbuf->buf[1] = (uint8_t *) mpeg2_malloc (uv_size, + MPEG2_ALLOC_YUV); + fbuf->buf[2] = (uint8_t *) mpeg2_malloc (uv_size, + MPEG2_ALLOC_YUV); +#endif + } + + mpeg2_set_fbuf (mpeg2dec, (decoder->coding_type == B_TYPE)); + } + } + else + { + decoder->second_field = 1; + mpeg2dec->picture++; /* second field picture */ + + *(mpeg2dec->picture) = mpeg2dec->new_picture; + + mpeg2dec->info.current_picture_2nd = mpeg2dec->picture; + + if (low_delay || decoder->coding_type == B_TYPE) + mpeg2dec->info.display_picture_2nd = mpeg2dec->picture; + } + + info_user_data (mpeg2dec); +} + +static int copyright_ext (mpeg2dec_t * mpeg2dec) +{ + (void)mpeg2dec; + return 0; +} + +static int quant_matrix_ext (mpeg2dec_t * mpeg2dec) +{ + uint8_t * buffer = mpeg2dec->chunk_start; + int i, j; + + for (i = 0; i < 4; i++) + { + if (buffer[0] & (8 >> i)) + { + for (j = 0; j < 64; j++) + { + mpeg2dec->new_quantizer_matrix[i][mpeg2_scan_norm[j]] = + (buffer[j] << (i+5)) | (buffer[j+1] >> (3-i)); + } + + mpeg2dec->copy_matrix |= 1 << i; + buffer += 64; + } + } + + return 0; +} + +int mpeg2_header_extension (mpeg2dec_t * mpeg2dec) +{ + static int (* const parser[9]) (mpeg2dec_t *) = + { + NULL, + sequence_ext, + sequence_display_ext, + quant_matrix_ext, + copyright_ext, + NULL, + NULL, + picture_display_ext, + picture_coding_ext + }; + + int ext, ext_bit; + + ext = mpeg2dec->chunk_start[0] >> 4; + ext_bit = 1 << ext; + + if (!(mpeg2dec->ext_state & ext_bit)) + return 0; /* ignore illegal extensions */ + + mpeg2dec->ext_state &= ~ext_bit; + + return parser[ext] (mpeg2dec); +} + +int mpeg2_header_user_data (mpeg2dec_t * mpeg2dec) +{ + mpeg2dec->user_data_len += mpeg2dec->chunk_ptr - 1 - mpeg2dec->chunk_start; + mpeg2dec->chunk_start = mpeg2dec->chunk_ptr - 1; + + return 0; +} + +static void prescale (mpeg2dec_t * mpeg2dec, int index) +{ + static const int non_linear_scale[32] = + { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 10, 12, 14, 16, 18, 20, 22, + 24, 28, 32, 36, 40, 44, 48, 52, + 56, 64, 72, 80, 88, 96, 104, 112 + }; + + int i, j, k; + mpeg2_decoder_t * decoder = &mpeg2dec->decoder; + + if (mpeg2dec->scaled[index] != mpeg2dec->q_scale_type) + { + mpeg2dec->scaled[index] = mpeg2dec->q_scale_type; + + for (i = 0; i < 32; i++) + { + k = mpeg2dec->q_scale_type ? non_linear_scale[i] : (i << 1); + + for (j = 0; j < 64; j++) + { + decoder->quantizer_prescale[index][i][j] = + k * mpeg2dec->quantizer_matrix[index][j]; + } + } + } +} + +mpeg2_state_t mpeg2_header_slice_start (mpeg2dec_t * mpeg2dec) +{ + mpeg2_decoder_t * decoder = &mpeg2dec->decoder; + + mpeg2dec->info.user_data = NULL; + mpeg2dec->info.user_data_len = 0; + mpeg2dec->state = (mpeg2dec->picture->nb_fields > 1 || + mpeg2dec->state == STATE_PICTURE_2ND) ? + STATE_SLICE : STATE_SLICE_1ST; + + if (mpeg2dec->decoder.coding_type != D_TYPE) + { + prescale (mpeg2dec, 0); + + if (decoder->chroma_quantizer[0] == decoder->quantizer_prescale[2]) + prescale (mpeg2dec, 2); + + if (mpeg2dec->decoder.coding_type != I_TYPE) + { + prescale (mpeg2dec, 1); + + if (decoder->chroma_quantizer[1] == decoder->quantizer_prescale[3]) + prescale (mpeg2dec, 3); + } + } + + if (!(mpeg2dec->nb_decode_slices)) + { + mpeg2dec->picture->flags |= PIC_FLAG_SKIP; + } + else if (mpeg2dec->convert_start) + { + mpeg2dec->convert_start (decoder->convert_id, mpeg2dec->fbuf[0], + mpeg2dec->picture, mpeg2dec->info.gop); + + if (mpeg2dec->decoder.coding_type == B_TYPE) + { + mpeg2_init_fbuf (&mpeg2dec->decoder, mpeg2dec->yuv_buf[2], + mpeg2dec->yuv_buf[mpeg2dec->yuv_index ^ 1], + mpeg2dec->yuv_buf[mpeg2dec->yuv_index]); + } + else + { + mpeg2_init_fbuf (&mpeg2dec->decoder, + mpeg2dec->yuv_buf[mpeg2dec->yuv_index ^ 1], + mpeg2dec->yuv_buf[mpeg2dec->yuv_index], + mpeg2dec->yuv_buf[mpeg2dec->yuv_index]); + + if (mpeg2dec->state == STATE_SLICE) + mpeg2dec->yuv_index ^= 1; + } + } + else + { + int b_type; + + b_type = (mpeg2dec->decoder.coding_type == B_TYPE); + + mpeg2_init_fbuf (&mpeg2dec->decoder, mpeg2dec->fbuf[0]->buf, + mpeg2dec->fbuf[b_type + 1]->buf, + mpeg2dec->fbuf[b_type]->buf); + } + + mpeg2dec->action = NULL; + + return STATE_INTERNAL_NORETURN; +} + +static mpeg2_state_t seek_sequence (mpeg2dec_t * mpeg2dec) +{ + mpeg2_reset_info (&mpeg2dec->info); + + mpeg2dec->info.sequence = NULL; + mpeg2dec->info.gop = NULL; + + mpeg2_header_state_init (mpeg2dec); + + mpeg2dec->action = mpeg2_seek_header; + + return mpeg2_seek_header (mpeg2dec); +} + +mpeg2_state_t mpeg2_header_end (mpeg2dec_t * mpeg2dec) +{ + mpeg2_picture_t * picture; + int b_type; + + b_type = (mpeg2dec->decoder.coding_type == B_TYPE); + picture = mpeg2dec->pictures; + + if ((mpeg2dec->picture >= picture + 2) ^ b_type) + picture = mpeg2dec->pictures + 2; + + mpeg2_reset_info (&mpeg2dec->info); + + if (!(mpeg2dec->sequence.flags & SEQ_FLAG_LOW_DELAY)) + { + mpeg2dec->info.display_picture = picture; + + if (picture->nb_fields == 1) + mpeg2dec->info.display_picture_2nd = picture + 1; + + mpeg2dec->info.display_fbuf = mpeg2dec->fbuf[b_type]; + + if (!mpeg2dec->convert) + mpeg2dec->info.discard_fbuf = mpeg2dec->fbuf[b_type + 1]; + } + else if (!mpeg2dec->convert) + { + mpeg2dec->info.discard_fbuf = mpeg2dec->fbuf[b_type]; + } + + mpeg2dec->action = seek_sequence; + + return STATE_END; +} diff --git a/apps/plugins/mpegplayer/libmpeg2/idct.c b/apps/plugins/mpegplayer/libmpeg2/idct.c new file mode 100644 index 0000000000..7f0b9a3c12 --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/idct.c @@ -0,0 +1,274 @@ +/* + * idct.c + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + * libmpeg2 sync history: + * 2008-07-01 - CVS revision 1.36 + */ + +#include "plugin.h" + +#include "mpeg2dec_config.h" + +#include "mpeg2.h" +#include "attributes.h" +#include "mpeg2_internal.h" + +#if defined(CPU_COLDFIRE) || defined (CPU_ARM) +#define IDCT_ASM +#endif + +#ifndef IDCT_ASM + +#define W1 2841 /* 2048 * sqrt (2) * cos (1 * pi / 16) */ +#define W2 2676 /* 2048 * sqrt (2) * cos (2 * pi / 16) */ +#define W3 2408 /* 2048 * sqrt (2) * cos (3 * pi / 16) */ +#define W5 1609 /* 2048 * sqrt (2) * cos (5 * pi / 16) */ +#define W6 1108 /* 2048 * sqrt (2) * cos (6 * pi / 16) */ +#define W7 565 /* 2048 * sqrt (2) * cos (7 * pi / 16) */ + +/* + * In legal streams, the IDCT output should be between -384 and +384. + * In corrupted streams, it is possible to force the IDCT output to go + * to +-3826 - this is the worst case for a column IDCT where the + * column inputs are 16-bit values. + */ +#define CLIP(i) \ + ({ typeof (i) _i = (i); \ + if ((_i & 0xff) != _i) \ + _i = ~(_i >> (8*sizeof(_i) - 1)); \ + _i; }) + +#if 0 +#define BUTTERFLY(t0,t1,W0,W1,d0,d1) \ + do { \ + t0 = W0 * d0 + W1 * d1; \ + t1 = W0 * d1 - W1 * d0; \ + } while (0) +#else +#define BUTTERFLY(t0,t1,W0,W1,d0,d1) \ + do { \ + int tmp = W0 * (d0 + d1); \ + t0 = tmp + (W1 - W0) * d1; \ + t1 = tmp - (W1 + W0) * d0; \ + } while (0) +#endif + +static inline void idct_row (int16_t * const block) +{ + int d0, d1, d2, d3; + int a0, a1, a2, a3, b0, b1, b2, b3; + int t0, t1, t2, t3; + + /* shortcut */ + if (likely (!(block[1] | ((int32_t *)block)[1] | ((int32_t *)block)[2] | + ((int32_t *)block)[3]))) + { + uint32_t tmp = (uint16_t) (block[0] >> 1); + tmp |= tmp << 16; + ((int32_t *)block)[0] = tmp; + ((int32_t *)block)[1] = tmp; + ((int32_t *)block)[2] = tmp; + ((int32_t *)block)[3] = tmp; + return; + } + + d0 = (block[0] << 11) + 2048; + d1 = block[1]; + d2 = block[2] << 11; + d3 = block[3]; + t0 = d0 + d2; + t1 = d0 - d2; + BUTTERFLY (t2, t3, W6, W2, d3, d1); + a0 = t0 + t2; + a1 = t1 + t3; + a2 = t1 - t3; + a3 = t0 - t2; + + d0 = block[4]; + d1 = block[5]; + d2 = block[6]; + d3 = block[7]; + BUTTERFLY (t0, t1, W7, W1, d3, d0); + BUTTERFLY (t2, t3, W3, W5, d1, d2); + b0 = t0 + t2; + b3 = t1 + t3; + t0 -= t2; + t1 -= t3; + b1 = ((t0 + t1) >> 8) * 181; + b2 = ((t0 - t1) >> 8) * 181; + + block[0] = (a0 + b0) >> 12; + block[1] = (a1 + b1) >> 12; + block[2] = (a2 + b2) >> 12; + block[3] = (a3 + b3) >> 12; + block[4] = (a3 - b3) >> 12; + block[5] = (a2 - b2) >> 12; + block[6] = (a1 - b1) >> 12; + block[7] = (a0 - b0) >> 12; +} + +static inline void idct_col (int16_t * const block) +{ + int d0, d1, d2, d3; + int a0, a1, a2, a3, b0, b1, b2, b3; + int t0, t1, t2, t3; + + d0 = (block[8*0] << 11) + 65536; + d1 = block[8*1]; + d2 = block[8*2] << 11; + d3 = block[8*3]; + t0 = d0 + d2; + t1 = d0 - d2; + BUTTERFLY (t2, t3, W6, W2, d3, d1); + a0 = t0 + t2; + a1 = t1 + t3; + a2 = t1 - t3; + a3 = t0 - t2; + + d0 = block[8*4]; + d1 = block[8*5]; + d2 = block[8*6]; + d3 = block[8*7]; + BUTTERFLY (t0, t1, W7, W1, d3, d0); + BUTTERFLY (t2, t3, W3, W5, d1, d2); + b0 = t0 + t2; + b3 = t1 + t3; + t0 -= t2; + t1 -= t3; + b1 = ((t0 + t1) >> 8) * 181; + b2 = ((t0 - t1) >> 8) * 181; + + block[8*0] = (a0 + b0) >> 17; + block[8*1] = (a1 + b1) >> 17; + block[8*2] = (a2 + b2) >> 17; + block[8*3] = (a3 + b3) >> 17; + block[8*4] = (a3 - b3) >> 17; + block[8*5] = (a2 - b2) >> 17; + block[8*6] = (a1 - b1) >> 17; + block[8*7] = (a0 - b0) >> 17; +} + +void mpeg2_idct_copy (int16_t * block, uint8_t * dest, + const int stride) +{ + int i; + + for (i = 0; i < 8; i++) + idct_row (block + 8 * i); + + for (i = 0; i < 8; i++) + idct_col (block + i); + + do + { + dest[0] = CLIP (block[0]); + dest[1] = CLIP (block[1]); + dest[2] = CLIP (block[2]); + dest[3] = CLIP (block[3]); + dest[4] = CLIP (block[4]); + dest[5] = CLIP (block[5]); + dest[6] = CLIP (block[6]); + dest[7] = CLIP (block[7]); + + ((int32_t *)block)[0] = 0; + ((int32_t *)block)[1] = 0; + ((int32_t *)block)[2] = 0; + ((int32_t *)block)[3] = 0; + + dest += stride; + block += 8; + } + while (--i); +} + +void mpeg2_idct_add (const int last, int16_t * block, + uint8_t * dest, const int stride) +{ + int i; + + if (last != 129 || (block[0] & (7 << 4)) == (4 << 4)) + { + for (i = 0; i < 8; i++) + idct_row (block + 8 * i); + + for (i = 0; i < 8; i++) + idct_col (block + i); + + do + { + dest[0] = CLIP (block[0] + dest[0]); + dest[1] = CLIP (block[1] + dest[1]); + dest[2] = CLIP (block[2] + dest[2]); + dest[3] = CLIP (block[3] + dest[3]); + dest[4] = CLIP (block[4] + dest[4]); + dest[5] = CLIP (block[5] + dest[5]); + dest[6] = CLIP (block[6] + dest[6]); + dest[7] = CLIP (block[7] + dest[7]); + + ((int32_t *)block)[0] = 0; + ((int32_t *)block)[1] = 0; + ((int32_t *)block)[2] = 0; + ((int32_t *)block)[3] = 0; + + dest += stride; + block += 8; + } + while (--i); + } + else + { + int DC = (block[0] + 64) >> 7; + block[0] = block[63] = 0; + i = 8; + + do + { + dest[0] = CLIP (DC + dest[0]); + dest[1] = CLIP (DC + dest[1]); + dest[2] = CLIP (DC + dest[2]); + dest[3] = CLIP (DC + dest[3]); + dest[4] = CLIP (DC + dest[4]); + dest[5] = CLIP (DC + dest[5]); + dest[6] = CLIP (DC + dest[6]); + dest[7] = CLIP (DC + dest[7]); + dest += stride; + } + while (--i); + } +} + +#endif /* IDCT_ASM */ + +void mpeg2_idct_init (void) +{ + int i, j; + + for (i = 0; i < 64; i++) + { + j = default_mpeg2_scan_norm[i]; + mpeg2_scan_norm[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); + + j = default_mpeg2_scan_alt[i]; + mpeg2_scan_alt[i] = ((j & 0x36) >> 1) | ((j & 0x09) << 2); + } +} diff --git a/apps/plugins/mpegplayer/libmpeg2/idct_arm.S b/apps/plugins/mpegplayer/libmpeg2/idct_arm.S new file mode 100644 index 0000000000..97a87a8b59 --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/idct_arm.S @@ -0,0 +1,443 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 by Michael Sevakis + * + * 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 "config.h" + + .global mpeg2_idct_copy + .type mpeg2_idct_copy, %function + .global mpeg2_idct_add + .type mpeg2_idct_add, %function + + +/* Custom calling convention: + * r0 contains block pointer and is non-volatile + * all non-volatile c context saved and restored on its behalf + */ +.idct: + add r12, r0, #128 +1: + ldrsh r1, [r0, #0] /* d0 */ + ldrsh r2, [r0, #2] /* d1 */ + ldrsh r3, [r0, #4] /* d2 */ + ldrsh r4, [r0, #6] /* d3 */ + ldrsh r5, [r0, #8] /* d0 */ + ldrsh r6, [r0, #10] /* d1 */ + ldrsh r7, [r0, #12] /* d2 */ + ldrsh r8, [r0, #14] /* d3 */ + orrs r9, r2, r3 + orreqs r9, r4, r5 + orreqs r9, r6, r7 + cmpeq r8, #0 + bne 2f + mov r1, r1, asl #15 + bic r1, r1, #0x8000 + orr r1, r1, r1, lsr #16 + str r1, [r0], #4 + str r1, [r0], #4 + str r1, [r0], #4 + str r1, [r0], #4 + cmp r0, r12 + blo 1b + b 3f +2: + mov r1, r1, asl #11 /* r1 = d0 = (block[0] << 11) + 2048 */ + add r1, r1, #2048 + add r1, r1, r3, asl #11 /* r1 = t0 = d0 + (block[2] << 11) */ + sub r3, r1, r3, asl #12 /* r3 = t1 = d0 - (block[2] << 11) */ + + add r9, r2, r4 /* r9 = tmp = (d1+d3)*(1108/4) */ + add r10, r9, r9, asl #2 + add r10, r10, r9, asl #4 + add r9, r10, r9, asl #8 + + add r10, r2, r2, asl #4 /* r2 = t2 = tmp + (d1*(1568/32)*8) */ + add r2, r10, r2, asl #5 + add r2, r9, r2, asl #3 + + add r10, r4, r4, asl #2 /* r4 = t3 = tmp - (d3*(3784/8)*2) */ + rsb r10, r10, r4, asl #6 + add r4, r4, r10, asl #3 + sub r4, r9, r4, asl #1 + /* t2 & t3 are 1/4 final value here */ + add r1, r1, r2, asl #2 /* r1 = a0 = t0 + t2 */ + sub r2, r1, r2, asl #3 /* r2 = a3 = t0 - t2 */ + add r3, r3, r4, asl #2 /* r3 = a1 = t1 + t3 */ + sub r4, r3, r4, asl #3 /* r4 = a2 = t1 - t3 */ + + add r9, r8, r5 /* r9 = tmp = 565*(d3 + d0) */ + add r10, r9, r9, asl #4 + add r10, r10, r10, asl #5 + add r9, r10, r9, asl #2 + + add r10, r5, r5, asl #4 /* r5 = t0 = tmp + (((2276/4)*d0)*4) */ + add r10, r10, r10, asl #5 + add r5, r10, r5, asl #3 + add r5, r9, r5, asl #2 + + add r10, r8, r8, asl #2 /* r8 = t1 = tmp - (((3406/2)*d3)*2) */ + add r10, r10, r10, asl #4 + add r10, r10, r8, asl #7 + rsb r8, r8, r10, asl #3 + sub r8, r9, r8, asl #1 + + add r9, r6, r7 /* r9 = tmp = (2408/8)*(d1 + d2) */ + add r10, r9, r9, asl #3 + add r10, r10, r10, asl #5 + add r9, r10, r9, asl #2 + + add r10, r7, r7, asl #3 /* r7 = t2 = (tmp*8) - 799*d2 */ + add r10, r10, r7, asl #4 + rsb r7, r7, r10, asl #5 + rsb r7, r7, r9, asl #3 + + sub r10, r6, r6, asl #4 /* r6 = t3 = (tmp*8) - 4017*d1 */ + sub r10, r10, r6, asl #6 + add r10, r10, r6, asl #12 + add r6, r10, r6 + rsb r6, r6, r9, asl #3 + /* t0 = r5, t1 = r8, t2 = r7, t3 = r6*/ + add r9, r5, r7 /* r9 = b0 = t0 + t2 */ + add r10, r8, r6 /* r10 = b3 = t1 + t3 */ + sub r5, r5, r7 /* t0 -= t2 */ + sub r8, r8, r6 /* t1 -= t3 */ + add r6, r5, r8 /* r6 = t0 + t1 */ + sub r7, r5, r8 /* r7 = t0 - t1 */ + + add r11, r6, r6, asr #2 /* r6 = b1 = r6*(181/128) */ + add r11, r11, r11, asr #5 + add r6, r11, r6, asr #3 + add r11, r7, r7, asr #2 /* r7 = b2 = r7*(181/128) */ + add r11, r11, r11, asr #5 + add r7, r11, r7, asr #3 + /* r1 = a0, r3 = a1, r4 = a2, r2 = a3 */ + /* r9 = b0, r6 = b1*2, r7 = b2*2, r10 = b3 */ + add r5, r1, r9 /* block[0] = (a0 + b0) >> 12 */ + mov r5, r5, asr #12 + strh r5, [r0], #2 + add r8, r3, r6, asr #1 /* block[1] = (a1 + b1) >> 12 */ + mov r8, r8, asr #12 + strh r8, [r0], #2 + add r5, r4, r7, asr #1 /* block[2] = (a2 + b2) >> 12 */ + mov r5, r5, asr #12 + strh r5, [r0], #2 + add r8, r2, r10 /* block[3] = (a3 + b3) >> 12 */ + mov r8, r8, asr #12 + strh r8, [r0], #2 + sub r5, r2, r10 /* block[4] = (a3 - b3) >> 12 */ + mov r5, r5, asr #12 + strh r5, [r0], #2 + sub r8, r4, r7, asr #1 /* block[5] = (a2 - b2) >> 12 */ + mov r8, r8, asr #12 + strh r8, [r0], #2 + sub r5, r3, r6, asr #1 /* block[6] = (a1 - b1) >> 12 */ + mov r5, r5, asr #12 + strh r5, [r0], #2 + sub r8, r1, r9 /* block[7] = (a0 - b0) >> 12 */ + mov r8, r8, asr #12 + strh r8, [r0], #2 + cmp r0, r12 + blo 1b +3: + sub r0, r0, #128 + add r12, r0, #16 +4: + ldrsh r1, [r0, #0*8] /* d0 */ + ldrsh r2, [r0, #2*8] /* d1 */ + ldrsh r3, [r0, #4*8] /* d2 */ + ldrsh r4, [r0, #6*8] /* d3 */ + ldrsh r5, [r0, #8*8] /* d0 */ + ldrsh r6, [r0, #10*8] /* d1 */ + ldrsh r7, [r0, #12*8] /* d2 */ + ldrsh r8, [r0, #14*8] /* d3 */ + + mov r1, r1, asl #11 /* r1 = d0 = (block[0] << 11) + 2048 */ + add r1, r1, #65536 + add r1, r1, r3, asl #11 /* r1 = t0 = d0 + d2:(block[2] << 11) */ + sub r3, r1, r3, asl #12 /* r3 = t1 = d0 - d2:(block[2] << 11) */ + + add r9, r2, r4 /* r9 = tmp = (d1+d3)*(1108/4) */ + add r10, r9, r9, asl #2 + add r10, r10, r9, asl #4 + add r9, r10, r9, asl #8 + + add r11, r2, r2, asl #4 /* r2 = t2 = tmp + (d1*(1568/32)*8) */ + add r2, r11, r2, asl #5 + add r2, r9, r2, asl #3 + + add r10, r4, r4, asl #2 /* r4 = t3 = tmp - (d3*(3784/8)*2) */ + rsb r10, r10, r4, asl #6 + add r4, r4, r10, asl #3 + sub r4, r9, r4, asl #1 + /* t2 & t3 are 1/4 final value here */ + add r1, r1, r2, asl #2 /* r1 = a0 = t0 + t2 */ + sub r2, r1, r2, asl #3 /* r2 = a3 = t0 - t2 */ + add r3, r3, r4, asl #2 /* r3 = a1 = t1 + t3 */ + sub r4, r3, r4, asl #3 /* r4 = a2 = t1 - t3 */ + + add r9, r8, r5 /* r9 = tmp = 565*(d3 + d0) */ + add r10, r9, r9, asl #4 + add r10, r10, r10, asl #5 + add r9, r10, r9, asl #2 + + add r10, r5, r5, asl #4 /* r5 = t0 = tmp + (((2276/4)*d0)*4) */ + add r10, r10, r10, asl #5 + add r5, r10, r5, asl #3 + add r5, r9, r5, asl #2 + + add r10, r8, r8, asl #2 /* r8 = t1 = tmp - (((3406/2)*d3)*2) */ + add r10, r10, r10, asl #4 + add r10, r10, r8, asl #7 + rsb r8, r8, r10, asl #3 + sub r8, r9, r8, asl #1 + + add r9, r6, r7 /* r9 = tmp = (2408/8)*(d1 + d2) */ + add r10, r9, r9, asl #3 + add r10, r10, r10, asl #5 + add r9, r10, r9, asl #2 + + add r10, r7, r7, asl #3 /* r7 = t2 = (tmp*8) - 799*d2 */ + add r10, r10, r7, asl #4 + rsb r7, r7, r10, asl #5 + rsb r7, r7, r9, asl #3 + + sub r10, r6, r6, asl #4 /* r6 = t3 = (tmp*8) - 4017*d1 */ + sub r10, r10, r6, asl #6 + add r10, r10, r6, asl #12 + add r6, r10, r6 + rsb r6, r6, r9, asl #3 + /* t0=r5, t1=r8, t2=r7, t3=r6*/ + add r9, r5, r7 /* r9 = b0 = t0 + t2 */ + add r10, r8, r6 /* r10 = b3 = t1 + t3 */ + sub r5, r5, r7 /* t0 -= t2 */ + sub r8, r8, r6 /* t1 -= t3 */ + add r6, r5, r8 /* r6 = t0 + t1 */ + sub r7, r5, r8 /* r7 = t0 - t1 */ + + add r11, r6, r6, asr #2 /* r6 = b1 = r5*(181/128) */ + add r11, r11, r11, asr #5 + add r6, r11, r6, asr #3 + add r11, r7, r7, asr #2 /* r7 = b2 = r6*(181/128) */ + add r11, r11, r11, asr #5 + add r7, r11, r7, asr #3 + /* r1 = a0, r3 = a1, r4 = a2, r2 = a3 */ + /* r9 = b0, r6 = b1*2, r7 = b2*2, r10 = b3 */ + add r5, r1, r9 /* block[0] = (a0 + b0) >> 17 */ + mov r5, r5, asr #17 + strh r5, [r0, #0*8] + add r8, r3, r6, asr #1 /* block[1] = (a1 + b1) >> 17 */ + mov r8, r8, asr #17 + strh r8, [r0, #2*8] + add r5, r4, r7, asr #1 /* block[2] = (a2 + b2) >> 17 */ + mov r5, r5, asr #17 + strh r5, [r0, #4*8] + add r8, r2, r10 /* block[3] = (a3 + b3) >> 17 */ + mov r8, r8, asr #17 + strh r8, [r0, #6*8] + sub r5, r2, r10 /* block[4] = (a3 - b3) >> 17 */ + mov r5, r5, asr #17 + strh r5, [r0, #8*8] + sub r8, r4, r7, asr #1 /* block[5] = (a2 - b2) >> 17 */ + mov r8, r8, asr #17 + strh r8, [r0, #10*8] + sub r5, r3, r6, asr #1 /* block[6] = (a1 - b1) >> 17 */ + mov r5, r5, asr #17 + strh r5, [r0, #12*8] + sub r8, r1, r9 /* block[7] = (a0 - b0) >> 17 */ + mov r8, r8, asr #17 + strh r8, [r0, #14*8] + add r0, r0, #2 + cmp r0, r12 + blo 4b + sub r0, r0, #16 + bx lr + +mpeg2_idct_copy: + stmfd sp!, { r1-r2, r4-r11, lr } + bl .idct + ldmfd sp!, { r1-r2 } + mov r11, #0 + add r12, r0, #128 +1: + ldrsh r3, [r0, #0] + ldrsh r4, [r0, #2] + ldrsh r5, [r0, #4] + ldrsh r6, [r0, #6] + ldrsh r7, [r0, #8] + ldrsh r8, [r0, #10] + ldrsh r9, [r0, #12] + ldrsh r10, [r0, #14] + cmp r3, #255 + mvnhi r3, r3, asr #31 + strb r3, [r1, #0] + str r11, [r0], #4 + cmp r4, #255 + mvnhi r4, r4, asr #31 + strb r4, [r1, #1] + cmp r5, #255 + mvnhi r5, r5, asr #31 + strb r5, [r1, #2] + str r11, [r0], #4 + cmp r6, #255 + mvnhi r6, r6, asr #31 + strb r6, [r1, #3] + cmp r7, #255 + mvnhi r7, r7, asr #31 + strb r7, [r1, #4] + str r11, [r0], #4 + cmp r8, #255 + mvnhi r8, r8, asr #31 + strb r8, [r1, #5] + cmp r9, #255 + mvnhi r9, r9, asr #31 + strb r9, [r1, #6] + str r11, [r0], #4 + cmp r10, #255 + mvnhi r10, r10, asr #31 + strb r10, [r1, #7] + add r1, r1, r2 + cmp r0, r12 + blo 1b + ldmpc regs=r4-r11 + +mpeg2_idct_add: + cmp r0, #129 + mov r0, r1 + ldreqsh r1, [r0, #0] + bne 1f + and r1, r1, #0x70 + cmp r1, #0x40 + bne 3f +1: + stmfd sp!, { r2-r11, lr } + bl .idct + ldmfd sp!, { r1-r2 } + mov r11, #0 + add r12, r0, #128 +2: + ldrb r3, [r1, #0] + ldrb r4, [r1, #1] + ldrb r5, [r1, #2] + ldrb r6, [r1, #3] + ldrsh r7, [r0, #0] + ldrsh r8, [r0, #2] + ldrsh r9, [r0, #4] + ldrsh r10, [r0, #6] + add r7, r7, r3 + ldrb r3, [r1, #4] + cmp r7, #255 + mvnhi r7, r7, asr #31 + strb r7, [r1, #0] + ldrsh r7, [r0, #8] + add r8, r8, r4 + ldrb r4, [r1, #5] + cmp r8, #255 + mvnhi r8, r8, asr #31 + strb r8, [r1, #1] + ldrsh r8, [r0, #10] + add r9, r9, r5 + ldrb r5, [r1, #6] + cmp r9, #255 + mvnhi r9, r9, asr #31 + strb r9, [r1, #2] + ldrsh r9, [r0, #12] + add r10, r10, r6 + ldrb r6, [r1, #7] + cmp r10, #255 + mvnhi r10, r10, asr #31 + strb r10, [r1, #3] + ldrsh r10, [r0, #14] + str r11, [r0], #4 + add r7, r7, r3 + cmp r7, #255 + mvnhi r7, r7, asr #31 + strb r7, [r1, #4] + str r11, [r0], #4 + add r8, r8, r4 + cmp r8, #255 + mvnhi r8, r8, asr #31 + strb r8, [r1, #5] + str r11, [r0], #4 + add r9, r9, r5 + cmp r9, #255 + mvnhi r9, r9, asr #31 + strb r9, [r1, #6] + add r10, r10, r6 + cmp r10, #255 + mvnhi r10, r10, asr #31 + strb r10, [r1, #7] + str r11, [r0], #4 + add r1, r1, r2 + cmp r0, r12 + blo 2b + ldmpc regs=r4-r11 +3: + stmfd sp!, { r4-r5, lr } + ldrsh r1, [r0, #0] /* r1 = block[0] */ + mov r4, #0 + strh r4, [r0, #0] /* block[0] = 0 */ + strh r4, [r0, #126] /* block[63] = 0 */ + add r1, r1, #64 /* r1 = DC << 7 */ + add r0, r2, r3, asl #3 +4: + ldrb r4, [r2, #0] + ldrb r5, [r2, #1] + ldrb r12, [r2, #2] + ldrb lr, [r2, #3] + add r4, r4, r1, asr #7 + cmp r4, #255 + mvnhi r4, r4, asr #31 + strb r4, [r2, #0] + add r5, r5, r1, asr #7 + cmp r5, #255 + mvnhi r5, r5, asr #31 + strb r5, [r2, #1] + add r12, r12, r1, asr #7 + cmp r12, #255 + mvnhi r12, r12, asr #31 + strb r12, [r2, #2] + add lr, lr, r1, asr #7 + cmp lr, #255 + mvnhi lr, lr, asr #31 + strb lr, [r2, #3] + ldrb r4, [r2, #4] + ldrb r5, [r2, #5] + ldrb r12, [r2, #6] + ldrb lr, [r2, #7] + add r4, r4, r1, asr #7 + cmp r4, #255 + mvnhi r4, r4, asr #31 + strb r4, [r2, #4] + add r5, r5, r1, asr #7 + cmp r5, #255 + mvnhi r5, r5, asr #31 + strb r5, [r2, #5] + add r12, r12, r1, asr #7 + cmp r12, #255 + mvnhi r12, r12, asr #31 + strb r12, [r2, #6] + add lr, lr, r1, asr #7 + cmp lr, #255 + mvnhi lr, lr, asr #31 + strb lr, [r2, #7] + add r2, r2, r3 + cmp r2, r0 + blo 4b + ldmpc regs=r4-r5 diff --git a/apps/plugins/mpegplayer/libmpeg2/idct_armv6.S b/apps/plugins/mpegplayer/libmpeg2/idct_armv6.S new file mode 100644 index 0000000000..dc53cbd7bd --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/idct_armv6.S @@ -0,0 +1,297 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2009 by Jens Arnold + * + * 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. + * + ****************************************************************************/ + + + .global mpeg2_idct_copy + .type mpeg2_idct_copy, %function + .global mpeg2_idct_add + .type mpeg2_idct_add, %function + +/* Custom calling convention: + * r0 contains block pointer and is non-volatile + * all non-volatile c context saved and restored on its behalf + */ +.idct: + str lr, [sp, #-4]! @ lr is used + add r1, r0, #128 @ secondary, transposed temp buffer + mov r14, #8 @ loop counter + +.row_loop: + ldmia r0!, {r2, r3, r10, r11} @ fetch f0, f2, f4, f6, f1, f3, f5, f7 + ldrd r4, L_W1357 @ load W1, W3, W5, W7 + + smuad r6, r4, r10 @ b0 = W1 * f1 + W3 * f3 + smultt r7, r5, r10 @ -b1 = W7 * f3 + smulbt r8, r4, r10 @ -b2 = W1 * f3 + + smusdx r9, r10, r5 @ b3 = f1 * W7 - f3 * W5 + smlabb r7, r4, r11, r7 @ -b1 += W1 * f5 + rsb r8, r8, #0 @ b2 = -b2 + smlabb r8, r5, r10, r8 @ b2 += W5 * f1 + + smlad r6, r5, r11, r6 @ b0 += W5 * f5 + W7 * f7 + smlabt r7, r5, r11, r7 @ -b1 += W5 * f7 + smlatb r8, r5, r11, r8 @ b2 += W7 * f5 + + smlsdx r9, r11, r4, r9 @ b3 += f5 * W3 - f7 * W1 + rsb r7, r7, #0 @ b1 = -b1 + smlatb r7, r4, r10, r7 @ b1 += W3 * f1 + smlatt r8, r4, r11, r8 @ b2 += W3 * f7 + + ldrd r4, L_W0246 @ load W0, W2, W4, W6 + add r2, r2, #1 @ f0 += 1 + + smulbb r10, r5, r3 @ a0' = W4 * f4 + smultt r12, r5, r3 @ a3' = W6 * f6 + smultt r3, r4, r3 @ -a2' = W2 * f6 + + rsb r11, r10, #0 @ a1' = -W4 * f4 + smlabb r10, r4, r2, r10 @ a0' += W0 * f0 + smlabb r11, r4, r2, r11 @ a1' += W0 * f0 + smlatt r12, r4, r2, r12 @ a3' += W2 * f2 + rsb r3, r3, #0 @ a2' = -a2' + smlatt r3, r5, r2, r3 @ a2' += W6 * f2 + + add r10, r10, r12 @ a0 = a0' + a3' + sub r12, r10, r12, lsl #1 @ a3 = a0 - 2 * a3' + add r11, r11, r3 @ a1 = a1' + a2' + sub r3, r11, r3, lsl #1 @ a2 = a1 - 2 * a2' + + subs r14, r14, #1 @ decrease loop count + + @ Special store order for making the column pass calculate columns in + @ the order 0-2-1-3-4-6-5-7, allowing for uxtab16 use in later stages. + sub r2, r10, r6 @ block[7] = (a0 - b0) + mov r2, r2, asr #12 @ >> 12 + strh r2, [r1, #7*16] + sub r2, r11, r7 @ block[6] = (a1 - b1) + mov r2, r2, asr #12 @ >> 12 + strh r2, [r1, #5*16] + sub r2, r3, r8 @ block[5] = (a2 - b2) + mov r2, r2, asr #12 @ >> 12 + strh r2, [r1, #6*16] + sub r2, r12, r9 @ block[4] = (a3 - b3) + mov r2, r2, asr #12 @ >> 12 + strh r2, [r1, #4*16] + add r2, r12, r9 @ block[3] = (a3 + b3) + mov r2, r2, asr #12 @ >> 12 + strh r2, [r1, #3*16] + add r2, r3, r8 @ block[2] = (a2 + b2) + mov r2, r2, asr #12 @ >> 12 + strh r2, [r1, #1*16] + add r2, r11, r7 @ block[1] = (a1 + b1) + mov r2, r2, asr #12 @ >> 12 + strh r2, [r1, #2*16] + add r2, r10, r6 @ block[0] = (a0 + b0) + mov r2, r2, asr #12 @ >> 12 + strh r2, [r1], #2 @ advance to next temp column + + bne .row_loop + b .col_start + + @placed here because of ldrd's offset limit +L_W1357: + .short 2841 + .short 2408 + .short 1609 + .short 565 + +L_W0246: + .short 2048 + .short 2676 + .short 2048 + .short 1108 + +.col_start: + @ r0 now points to the temp buffer, where we need it. + sub r1, r1, #128+16 @ point r1 back to the input block + mov r14, #8 @ loop counter + +.col_loop: + ldmia r0!, {r2, r3, r10, r11} @ fetch f0, f2, f4, f6, f1, f3, f5, f7 + ldrd r4, L_W1357 @ load W1, W3, W5, W7 + + smuad r6, r4, r10 @ b0 = W1 * f1 + W3 * f3 + smultt r7, r5, r10 @ -b1 = W7 * f3 + smulbt r8, r4, r10 @ -b2 = W1 * f3 + + smusdx r9, r10, r5 @ b3 = f1 * W7 - f3 * W5 + smlabb r7, r4, r11, r7 @ -b1 += W1 * f5 + rsb r8, r8, #0 @ b2 = -b2 + smlabb r8, r5, r10, r8 @ b2 += W5 * f1 + + smlad r6, r5, r11, r6 @ b0 += W5 * f5 + W7 * f7 + smlabt r7, r5, r11, r7 @ -b1 += W5 * f7 + smlatb r8, r5, r11, r8 @ b2 += W7 * f5 + + smlsdx r9, r11, r4, r9 @ b3 += f5 * W3 - f7 * W1 + rsb r7, r7, #0 @ b1 = -b1 + smlatb r7, r4, r10, r7 @ b1 += W3 * f1 + smlatt r8, r4, r11, r8 @ b2 += W3 * f7 + + ldrd r4, L_W0246 @ load W0, W2, W4, W6 + add r2, r2, #32 @ DC offset: 0.5 + + smulbb r10, r5, r3 @ a0' = W4 * f4 + smultt r12, r5, r3 @ a3' = W6 * f6 + smultt r3, r4, r3 @ -a2' = W2 * f6 + + rsb r11, r10, #0 @ a1' = -W4 * f4 + smlabb r10, r4, r2, r10 @ a0' += W0 * f0 + smlabb r11, r4, r2, r11 @ a1' += W0 * f0 + smlatt r12, r4, r2, r12 @ a3' += W2 * f2 + rsb r3, r3, #0 @ a2' = -a2' + smlatt r3, r5, r2, r3 @ a2' += W6 * f2 + + add r10, r10, r12 @ a0 = a0' + a3' + sub r12, r10, r12, lsl #1 @ a3 = a0 - 2 * a3' + add r11, r11, r3 @ a1 = a1' + a2' + sub r3, r11, r3, lsl #1 @ a2 = a1 - 2 * a2' + + subs r14, r14, #1 @ decrease loop count + + sub r2, r10, r6 @ block[7] = (a0 - b0) + mov r2, r2, asr #17 @ >> 17 + strh r2, [r1, #7*16] + sub r2, r11, r7 @ block[6] = (a1 - b1) + mov r2, r2, asr #17 @ >> 17 + strh r2, [r1, #6*16] + sub r2, r3, r8 @ block[5] = (a2 - b2) + mov r2, r2, asr #17 @ >> 17 + strh r2, [r1, #5*16] + sub r2, r12, r9 @ block[4] = (a3 - b3) + mov r2, r2, asr #17 @ >> 17 + strh r2, [r1, #4*16] + add r2, r12, r9 @ block[3] = (a3 + b3) + mov r2, r2, asr #17 @ >> 17 + strh r2, [r1, #3*16] + add r2, r3, r8 @ block[2] = (a2 + b2) + mov r2, r2, asr #17 @ >> 17 + strh r2, [r1, #2*16] + add r2, r11, r7 @ block[1] = (a1 + b1) + mov r2, r2, asr #17 @ >> 17 + strh r2, [r1, #1*16] + add r2, r10, r6 @ block[0] = (a0 + b0) + mov r2, r2, asr #17 @ >> 17 + strh r2, [r1], #2 @ advance to next column + + bne .col_loop + + sub r0, r0, #256 @ point r0 back to the input block + ldr pc, [sp], #4 + + +mpeg2_idct_copy: + stmfd sp!, {r1-r2, r4-r11, lr} + bl .idct + ldmfd sp!, {r1-r2} + + add r3, r0, #128 + mov r8, #0 + mov r9, #0 + mov r10, #0 + mov r11, #0 +1: @ idct data is in order 0-2-1-3-4-6-5-7, + ldmia r0, {r4-r7} @ see above + stmia r0!, {r8-r11} + usat16 r4, #8, r4 + usat16 r5, #8, r5 + orr r4, r4, r5, lsl #8 + usat16 r6, #8, r6 + usat16 r7, #8, r7 + orr r5, r6, r7, lsl #8 + strd r4, [r1] @ r4, r5 + add r1, r1, r2 + cmp r0, r3 + blo 1b + + ldmfd sp!, {r4-r11, pc} + +mpeg2_idct_add: + cmp r0, #129 + mov r0, r1 + ldreqsh r1, [r0, #0] + bne 1f + and r1, r1, #0x70 + cmp r1, #0x40 + bne 3f +1: + stmfd sp!, {r2-r11, lr} + bl .idct + ldmfd sp!, {r1-r2} + + add r3, r0, #128 + mov r10, #0 + mov r11, #0 + mov r12, #0 + mov lr, #0 + ldrd r8, [r1] @ r8, r9 +2: @ idct data is in order 0-2-1-3-4-6-5-7, + ldmia r0, {r4-r7} @ see above + stmia r0!, {r10-r12, lr} + uxtab16 r4, r4, r8 + uxtab16 r5, r5, r8, ror #8 + usat16 r4, #8, r4 + usat16 r5, #8, r5 + orr r4, r4, r5, lsl #8 + uxtab16 r6, r6, r9 + uxtab16 r7, r7, r9, ror #8 + usat16 r6, #8, r6 + usat16 r7, #8, r7 + orr r5, r6, r7, lsl #8 + strd r4, [r1] @ r4, r5 + add r1, r1, r2 + cmp r0, r3 + ldrlod r8, [r1] @ r8, r9 + blo 2b + + ldmfd sp!, {r4-r11, pc} + +3: + stmfd sp!, {r4, lr} + ldrsh r4, [r0, #0] @ r4 = block[0] + mov r12, #0 + strh r12, [r0, #0] @ block[0] = 0 + strh r12, [r0, #126] @ block[63] = 0 + add r4, r4, #64 + mov r4, r4, asr #7 @ r4 = DC + mov r4, r4, lsl #16 @ spread to 2 halfwords + orr r4, r4, r4, lsr #16 + ldrd r0, [r2] @ r0, r1 + add r12, r2, r3, asl #3 +4: + uxtab16 lr, r4, r0, ror #8 + uxtab16 r0, r4, r0 + usat16 lr, #8, lr + usat16 r0, #8, r0 + orr r0, r0, lr, lsl #8 + uxtab16 lr, r4, r1, ror #8 + uxtab16 r1, r4, r1 + usat16 lr, #8, lr + usat16 r1, #8, r1 + orr r1, r1, lr, lsl #8 + strd r0, [r2] @ r0, r1 + add r2, r2, r3 + cmp r2, r12 + ldrlod r0, [r2] @ r0, r1 + blo 4b + + ldmfd sp!, {r4, pc} diff --git a/apps/plugins/mpegplayer/libmpeg2/idct_coldfire.S b/apps/plugins/mpegplayer/libmpeg2/idct_coldfire.S new file mode 100644 index 0000000000..abc54b16cb --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/idct_coldfire.S @@ -0,0 +1,575 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Jens Arnold + * Based on the work of Karim Boucher and Rani Hod + * + * 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. + * + ****************************************************************************/ + + .global mpeg2_idct_copy + .type mpeg2_idct_copy, @function + .global mpeg2_idct_add + .type mpeg2_idct_add, @function + + /* The IDCT itself. + * Input: %a0: block pointer + * Caller must save all registers. */ + .align 2 +.idct: + move.l %a0, %a6 + + move.l #0, %macsr | signed integer mode + + move.l #((2048<<16)+2841), %a0 | W0, W1 + move.l #((2676<<16)+2408), %a1 | W2, W3 + move.l #((2048<<16)+1609), %a2 | W4, W5 + move.l #((1108<<16)+ 565), %a3 | W6, W7 + + lea.l (128,%a6), %a4 | secondary, transposed temp buffer + moveq.l #8, %d3 | loop counter + +.row_loop: + movem.l (%a6), %d0-%d2/%a5 | fetch (f0, f2, f4, f6, f1, f3, f5, f7) + + mac.w %a0l, %d2u, %acc0 | %acc0 = W1 * f1 + mac.w %a1l, %d2l, %acc0 | + W3 * f3 + mac.w %a2l, %a5u, %acc0 | + W5 * f5 + mac.w %a3l, %a5l, %acc0 | + W7 * f7 + + mac.w %a1l, %d2u, %acc1 | %acc1 = W3 * f1 + msac.w %a3l, %d2l, %acc1 | - W7 * f3 + msac.w %a0l, %a5u, %acc1 | - W1 * f5 + msac.w %a2l, %a5l, %acc1 | - W5 * f7 + + mac.w %a2l, %d2u, %acc2 | %acc2 = W5 * f1 + msac.w %a0l, %d2l, %acc2 | - W1 * f3 + mac.w %a3l, %a5u, %acc2 | + W7 * f5 + mac.w %a1l, %a5l, %acc2 | + W3 * f7 + + mac.w %a3l, %d2u, %acc3 | %acc3 = W7 * f1 + msac.w %a2l, %d2l, %acc3 | - W5 * f3 + mac.w %a1l, %a5u, %acc3 | + W3 * f5 + msac.w %a0l, %a5l, %acc3 | - W1 * f7 + + lea.l (16,%a6), %a6 | Advance to next row; put here to fill EMAC latency + add.l #(1<<16), %d0 | f0 += 1; + + movclr.l %acc0, %d4 | b0 + movclr.l %acc1, %d5 | b1 + movclr.l %acc2, %d6 | b2 + movclr.l %acc3, %d7 | b3 + + mac.w %a0u, %d0u, %acc0 | %acc0 = W0 * f0 + mac.w %a2u, %d1u, %acc0 | + W4 * f4 + move.l %acc0, %acc3 + mac.w %a1u, %d0l, %acc0 | + W2 * f2 + mac.w %a3u, %d1l, %acc0 | + W6 * f6 + + mac.w %a0u, %d0u, %acc1 | %acc1 = W0 * f0 + msac.w %a2u, %d1u, %acc1 | - W4 * f4 + move.l %acc1, %acc2 + mac.w %a3u, %d0l, %acc1 | + W6 * f2 + msac.w %a1u, %d1l, %acc1 | - W2 * f6 + + | ^ move.l %acc1, %acc2 %acc2 = W0 * f0 - W4 * f4 + msac.w %a3u, %d0l, %acc2 | - W6 * f2 + mac.w %a1u, %d1l, %acc2 | + W2 * f6 + + | ^ move.l %acc0, %acc3 %acc3 = W0 * f0 + W4 * f4 + msac.w %a1u, %d0l, %acc3 | - W2 * f2 + msac.w %a3u, %d1l, %acc3 | - W6 * f6 + + moveq.l #12, %d1 | shift amount + + move.l %acc0, %d0 | block[7] = (a0 + sub.l %d4,%d0 | - b0) + asr.l %d1, %d0 | >> 12 + move.w %d0, (7*16,%a4) + + move.l %acc1, %d0 | block[6] = (a1 + sub.l %d5,%d0 | - b1) + asr.l %d1, %d0 | >> 12 + move.w %d0, (6*16,%a4) + + move.l %acc2, %d0 | block[5] = (a2 + sub.l %d6,%d0 | - b2) + asr.l %d1, %d0 | >> 12 + move.w %d0, (5*16,%a4) + + move.l %acc3, %d0 | block[4] = (a3 + sub.l %d7,%d0 | - b3) + asr.l %d1, %d0 | >> 12 + move.w %d0, (4*16,%a4) + + movclr.l %acc3, %d0 | block[3] = (a3 + add.l %d7, %d0 | + b3) + asr.l %d1, %d0 | >> 12 + move.w %d0, (3*16,%a4) + + movclr.l %acc2, %d0 | block[2] = (a2 + add.l %d6, %d0 | + b2) + asr.l %d1, %d0 | >> 12 + move.w %d0, (2*16,%a4) + + movclr.l %acc1, %d0 | block[1] = (a1 + add.l %d5, %d0 | + b1) + asr.l %d1, %d0 | >> 12 + move.w %d0, (1*16,%a4) + + movclr.l %acc0, %d0 | block[0] = (a0 + add.l %d4, %d0 | + b0) + asr.l %d1, %d0 | >> 12 + move.w %d0, (%a4)+ | advance to next temp column + + subq.l #1, %d3 | loop 8 times + bne.w .row_loop + + | %a6 now points to the temp buffer, where we need it. + lea.l (-16-128,%a4), %a4 | point %a4 back to the input block + moveq.l #8, %d3 | loop counter + +.col_loop: + movem.l (%a6), %d0-%d2/%a5 | fetch (f0, f2, f4, f6, f1, f3, f5, f7) + + mac.w %a0l, %d2u, %acc0 | %acc0 = W1 * f1 + mac.w %a1l, %d2l, %acc0 | + W3 * f3 + mac.w %a2l, %a5u, %acc0 | + W5 * f5 + mac.w %a3l, %a5l, %acc0 | + W7 * f7 + + mac.w %a1l, %d2u, %acc1 | %acc1 = W3 * f1 + msac.w %a3l, %d2l, %acc1 | - W7 * f3 + msac.w %a0l, %a5u, %acc1 | - W1 * f5 + msac.w %a2l, %a5l, %acc1 | - W5 * f7 + + mac.w %a2l, %d2u, %acc2 | %acc2 = W5 * f1 + msac.w %a0l, %d2l, %acc2 | - W1 * f3 + mac.w %a3l, %a5u, %acc2 | + W7 * f5 + mac.w %a1l, %a5l, %acc2 | + W3 * f7 + + mac.w %a3l, %d2u, %acc3 | %acc3 = W7 * f1 + msac.w %a2l, %d2l, %acc3 | - W5 * f3 + mac.w %a1l, %a5u, %acc3 | + W3 * f5 + msac.w %a0l, %a5l, %acc3 | - W1 * f7 + + lea.l (16,%a6), %a6 | Advance to next row; put here to fill EMAC latency + add.l #(32<<16), %d0 | DC offset: 0.5 + + movclr.l %acc0, %d4 | b0 + movclr.l %acc1, %d5 | b1 + movclr.l %acc2, %d6 | b2 + movclr.l %acc3, %d7 | b3 + + mac.w %a0u, %d0u, %acc0 | %acc0 = W0 * f0 + mac.w %a2u, %d1u, %acc0 | + W4 * f4 + move.l %acc0, %acc3 + mac.w %a1u, %d0l, %acc0 | + W2 * f2 + mac.w %a3u, %d1l, %acc0 | + W6 * f6 + + mac.w %a0u, %d0u, %acc1 | %acc1 = W0 * f0 + msac.w %a2u, %d1u, %acc1 | - W4 * f4 + move.l %acc1, %acc2 + mac.w %a3u, %d0l, %acc1 | + W6 * f2 + msac.w %a1u, %d1l, %acc1 | - W2 * f6 + + | ^ move.l %acc1, %acc2 %acc2 = W0 * f0 - W4 * f4 + msac.w %a3u, %d0l, %acc2 | - W6 * f2 + mac.w %a1u, %d1l, %acc2 | + W2 * f6 + + | ^ move.l %acc0, %acc3 %acc3 = W0 * f0 + W4 * f4 + msac.w %a1u, %d0l, %acc3 | - W2 * f2 + msac.w %a3u, %d1l, %acc3 | - W6 * f6 + + moveq.l #17, %d1 | shift amount + + move.l %acc0, %d0 | block[7] = (a0 + sub.l %d4,%d0 | - b0) + asr.l %d1, %d0 | >> 17 + move.w %d0, (7*16,%a4) + + move.l %acc1, %d0 | block[6] = (a1 + sub.l %d5,%d0 | - b1) + asr.l %d1, %d0 | >> 17 + move.w %d0, (6*16,%a4) + + move.l %acc2, %d0 | block[5] = (a2 + sub.l %d6,%d0 | - b2) + asr.l %d1, %d0 | >> 17 + move.w %d0, (5*16,%a4) + + move.l %acc3, %d0 | block[4] = (a3 + sub.l %d7,%d0 | - b3) + asr.l %d1, %d0 | >> 17 + move.w %d0, (4*16,%a4) + + movclr.l %acc3, %d0 | block[3] = (a3 + add.l %d7, %d0 | + b3) + asr.l %d1, %d0 | >> 17 + move.w %d0, (3*16,%a4) + + movclr.l %acc2, %d0 | block[2] = (a2 + add.l %d6, %d0 | + b2) + asr.l %d1, %d0 | >> 17 + move.w %d0, (2*16,%a4) + + movclr.l %acc1, %d0 | block[1] = (a1 + add.l %d5, %d0 | + b1) + asr.l %d1, %d0 | >> 17 + move.w %d0, (1*16,%a4) + + movclr.l %acc0, %d0 | block[0] = (a0 + add.l %d4, %d0 | + b0) + asr.l %d1, %d0 | >> 17 + move.w %d0, (%a4)+ | advance to next column + + subq.l #1, %d3 | loop 8 times + bne.w .col_loop + + rts + + .align 2 + +mpeg2_idct_copy: + lea.l (-11*4,%sp), %sp + movem.l %d2-%d7/%a2-%a6, (%sp) | save some registers + move.l (11*4+4,%sp), %a0 | %a0 - block pointer for idct + + bsr.w .idct | apply idct to block + movem.l (11*4+4,%sp), %a0-%a2 | %a0 - block pointer + | %a1 - destination pointer + | %a2 - stride + + move.l #255, %d1 | preload constant for clipping + moveq.l #8, %d4 | loop counter + +.copy_clip_loop: + move.w (%a0), %d0 | load block[0] + ext.l %d0 | sign extend + cmp.l %d1, %d0 | overflow? + bls.b 1f + spl.b %d0 | yes: set appropriate limit value in low byte +1: + move.b %d0, %d2 | collect output bytes 0..3 in %d2 + lsl.l #8, %d2 + + move.w (2,%a0), %d0 | load block[1] + ext.l %d0 | sign extend + cmp.l %d1, %d0 | overflow? + bls.b 1f + spl.b %d0 | yes: set appropriate limit value in low byte +1: + move.b %d0, %d2 | collect output bytes 0..3 in %d2 + lsl.l #8, %d2 + clr.l (%a0)+ | clear block[0] and block[1], + | %a0 now pointing to block[2] + move.w (%a0), %d0 | do b2 and b3 + ext.l %d0 + cmp.l %d1, %d0 + bls.b 1f + spl.b %d0 +1: + move.b %d0, %d2 + lsl.l #8, %d2 + + move.w (2,%a0), %d0 + ext.l %d0 + cmp.l %d1, %d0 + bls.b 1f + spl.b %d0 +1: + move.b %d0, %d2 + clr.l (%a0)+ + + move.w (%a0), %d0 | do b4 and b5 + ext.l %d0 + cmp.l %d1, %d0 + bls.b 1f + spl.b %d0 +1: + move.b %d0, %d3 + lsl.l #8, %d3 + + move.w (2,%a0), %d0 + ext.l %d0 + cmp.l %d1, %d0 + bls.b 1f + spl.b %d0 +1: + move.b %d0, %d3 + lsl.l #8, %d3 + clr.l (%a0)+ + + move.w (%a0), %d0 | do b6 and b7 + ext.l %d0 + cmp.l %d1, %d0 + bls.b 1f + spl.b %d0 +1: + move.b %d0, %d3 + lsl.l #8, %d3 + + move.w (2,%a0), %d0 + ext.l %d0 + cmp.l %d1, %d0 + bls.b 1f + spl.b %d0 +1: + move.b %d0, %d3 + clr.l (%a0)+ + + movem.l %d2-%d3, (%a1) | write all 8 output bytes at once + add.l %a2, %a1 | advance output pointer + subq.l #1, %d4 | loop 8 times + bne.w .copy_clip_loop + + movem.l (%sp), %d2-%d7/%a2-%a6 + lea.l (11*4,%sp), %sp + rts + + .align 2 + +mpeg2_idct_add: + lea.l (-11*4,%sp), %sp + movem.l %d2-%d7/%a2-%a6, (%sp) + movem.l (11*4+4,%sp), %d0/%a0-%a2 | %d0 - last value + | %a0 - block pointer + | %a1 - destination pointer + | %a2 - stride + + cmp.l #129, %d0 | last == 129 ? + bne.b .idct_add | no: perform idct + addition + move.w (%a0), %d0 + ext.l %d0 | ((block[0] + asr.l #4, %d0 | >> 4) + and.l #7, %d0 | & 7) + subq.l #4, %d0 | - 4 == 0 ? + bne.w .dc_add | no: just perform addition + +.idct_add: + bsr.w .idct | apply idct + movem.l (11*4+8,%sp), %a0-%a2 | reload arguments %a0..%a2 + + move.l #255, %d2 | preload constant for clipping + clr.l %d3 | used for splitting input words into bytes + moveq.l #8, %d4 | loop counter + +.add_clip_loop: + movem.l (%a1), %d6-%d7 | fetch (b0 b1 b2 b3) (b4 b5 b6 b7) + swap %d6 | (b2 b3 b0 b1) + swap %d7 | (b6 b7 b4 b5) + + move.w (2,%a0), %d0 | load block[1] + ext.l %d0 | sign extend + move.b %d6, %d3 | copy b1 + lsr.l #8, %d6 | prepare 1st buffer for next byte + add.l %d3, %d0 | add b1 + cmp.l %d2, %d0 | overflow ? + bls.b 1f + spl.b %d0 | yes: set appropriate limit value in low byte +1: + move.w (%a0), %d1 | load block[0] + ext.l %d1 | sign extend + move.b %d6, %d3 | copy b0 + lsr.l #8, %d6 | prepare 1st buffer for next byte + add.l %d3, %d1 | add b0 + cmp.l %d2, %d1 | overflow ? + bls.b 1f + spl.b %d1 | yes: set appropriate limit value in low byte +1: + move.b %d1, %d5 | collect output bytes 0..3 in %d5 + lsl.l #8, %d5 + move.b %d0, %d5 + lsl.l #8, %d5 + clr.l (%a0)+ | clear block[0] and block[1] + | %a0 now pointing to block[2] + move.w (2,%a0), %d0 | do b3 and b2 + ext.l %d0 + move.b %d6, %d3 + lsr.l #8, %d6 + add.l %d3, %d0 + cmp.l %d2, %d0 + bls.b 1f + spl.b %d0 +1: + move.w (%a0), %d1 + ext.l %d1 + add.l %d6, %d1 + cmp.l %d2, %d1 + bls.b 1f + spl.b %d1 +1: + move.b %d1, %d5 + lsl.l #8, %d5 + move.b %d0, %d5 + clr.l (%a0)+ + + move.w (2,%a0), %d0 | do b5 and b4 + ext.l %d0 + move.b %d7, %d3 + lsr.l #8, %d7 + add.l %d3, %d0 + cmp.l %d2, %d0 + bls.b 1f + spl.b %d0 +1: + move.w (%a0), %d1 + ext.l %d1 + move.b %d7, %d3 + lsr.l #8, %d7 + add.l %d3, %d1 + cmp.l %d2, %d1 + bls.b 1f + spl.b %d1 +1: + move.b %d1, %d6 + lsl.l #8, %d6 + move.b %d0, %d6 + lsl.l #8, %d6 + clr.l (%a0)+ + + move.w (2,%a0), %d0 | do b7 and b6 + ext.l %d0 + move.b %d7, %d3 + lsr.l #8, %d7 + add.l %d3, %d0 + cmp.l %d2, %d0 + bls.b 1f + spl.b %d0 +1: + move.w (%a0), %d1 + ext.l %d1 + add.l %d7, %d1 + cmp.l %d2, %d1 + bls.b 1f + spl.b %d1 +1: + move.b %d1, %d6 + lsl.l #8, %d6 + move.b %d0, %d6 + clr.l (%a0)+ + + movem.l %d5-%d6, (%a1) | write all 8 output bytes at once + add.l %a2, %a1 | advance output pointer + subq.l #1, %d4 | loop 8 times + bne.w .add_clip_loop + + bra.w .idct_add_end + +.dc_add: + move.w (%a0), %d0 + ext.l %d0 | %d0 = (block[0] + add.l #64, %d0 | + 64) + asr.l #7, %d0 | >> 7 + clr.w (%a0) | clear block[0] + clr.w (63*2,%a0) | and block[63] + move.l %d0, %a0 | DC value in %a0 + + move.l #255, %d2 | preload constant for clipping + clr.l %d3 | for splitting input words into bytes + moveq.l #8, %d4 | loop counter + +.dc_clip_loop: + movem.l (%a1), %d6-%d7 | (b0 b1 b2 b3) (b4 b5 b6 b7) + swap %d6 | (b2 b3 b0 b1) + swap %d7 | (b6 b7 b4 b5) + + move.l %a0, %d0 | copy DC + move.b %d6, %d3 | copy b1 + lsr.l #8, %d6 | prepare 1st buffer for next byte + add.l %d3, %d0 | add b1 + cmp.l %d2, %d0 | overflow ? + bls.b 1f + spl.b %d0 | yes: set appropriate limit value in low byte +1: + move.l %a0, %d1 | copy DC + move.b %d6, %d3 | copy b0 + lsr.l #8, %d6 | prepare 1st buffer for next byte + add.l %d3, %d1 | add b0 + cmp.l %d2, %d1 | overflow ? + bls.b 1f + spl.b %d1 | yes: set appropriate limit value in low byte +1: + move.b %d1, %d5 | collect output bytes 0..3 in %d5 + lsl.l #8, %d5 + move.b %d0, %d5 + lsl.l #8, %d5 + + move.l %a0, %d0 | do b3 and b2 + move.b %d6, %d3 + lsr.l #8, %d6 + add.l %d3, %d0 + cmp.l %d2, %d0 + bls.b 1f + spl.b %d0 +1: + move.l %a0, %d1 + add.l %d6, %d1 + cmp.l %d2, %d1 + bls.b 1f + spl.b %d1 +1: + move.b %d1, %d5 + lsl.l #8, %d5 + move.b %d0, %d5 + + move.l %a0, %d0 | do b5 and b4 + move.b %d7, %d3 + lsr.l #8, %d7 + add.l %d3, %d0 + cmp.l %d2, %d0 + bls.b 1f + spl.b %d0 +1: + move.l %a0, %d1 + move.b %d7, %d3 + lsr.l #8, %d7 + add.l %d3, %d1 + cmp.l %d2, %d1 + bls.b 1f + spl.b %d1 +1: + move.b %d1, %d6 | do b7 and b6 + lsl.l #8, %d6 + move.b %d0, %d6 + lsl.l #8, %d6 + + move.l %a0, %d0 + move.b %d7, %d3 + lsr.l #8, %d7 + add.l %d3, %d0 + cmp.l %d2, %d0 + bls.b 1f + spl.b %d0 +1: + move.l %a0, %d1 + add.l %d7, %d1 + cmp.l %d2, %d1 + bls.b 1f + spl.b %d1 +1: + move.b %d1, %d6 + lsl.l #8, %d6 + move.b %d0, %d6 + + movem.l %d5-%d6, (%a1) | write all 8 output bytes at once + add.l %a2, %a1 | advance output pointer + subq.l #1, %d4 | loop 8 times + bne.w .dc_clip_loop + +.idct_add_end: + movem.l (%sp), %d2-%d7/%a2-%a6 + lea.l (11*4,%sp), %sp + rts diff --git a/apps/plugins/mpegplayer/libmpeg2/motion_comp.c b/apps/plugins/mpegplayer/libmpeg2/motion_comp.c new file mode 100644 index 0000000000..d6968f68ce --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/motion_comp.c @@ -0,0 +1,66 @@ +/* + * motion_comp.c + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + * libmpeg2 sync history: + * 2008-07-01 - CVS revision 1.17 - lost compatibility previously to + * provide simplified and CPU-optimized motion compensation. + */ + +#include "plugin.h" + +#include "mpeg2dec_config.h" + +#include "mpeg2.h" +#include "attributes.h" +#include "mpeg2_internal.h" + +/* These are defined in their respective target files - motion_comp_X.c */ +extern mpeg2_mc_fct MC_put_o_16; +extern mpeg2_mc_fct MC_put_o_8; +extern mpeg2_mc_fct MC_put_x_16; +extern mpeg2_mc_fct MC_put_x_8; +extern mpeg2_mc_fct MC_put_y_16; +extern mpeg2_mc_fct MC_put_y_8; +extern mpeg2_mc_fct MC_put_xy_16; +extern mpeg2_mc_fct MC_put_xy_8; + +extern mpeg2_mc_fct MC_avg_o_16; +extern mpeg2_mc_fct MC_avg_o_8; +extern mpeg2_mc_fct MC_avg_x_16; +extern mpeg2_mc_fct MC_avg_x_8; +extern mpeg2_mc_fct MC_avg_y_16; +extern mpeg2_mc_fct MC_avg_y_8; +extern mpeg2_mc_fct MC_avg_xy_16; +extern mpeg2_mc_fct MC_avg_xy_8; + +const mpeg2_mc_t mpeg2_mc = +{ + { + MC_put_o_16, MC_put_x_16, MC_put_y_16, MC_put_xy_16, + MC_put_o_8, MC_put_x_8, MC_put_y_8, MC_put_xy_8 + }, + { + MC_avg_o_16, MC_avg_x_16, MC_avg_y_16, MC_avg_xy_16, + MC_avg_o_8, MC_avg_x_8, MC_avg_y_8, MC_avg_xy_8 + } +}; diff --git a/apps/plugins/mpegplayer/libmpeg2/motion_comp.h b/apps/plugins/mpegplayer/libmpeg2/motion_comp.h new file mode 100644 index 0000000000..4737e72cab --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/motion_comp.h @@ -0,0 +1,86 @@ +/* + * motion_comp.h + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + + +#define avg2(a,b) ((a+b+1)>>1) +#define avg4(a,b,c,d) ((a+b+c+d+2)>>2) + +#define predict_o(i) (ref[i]) +#define predict_x(i) (avg2 (ref[i], ref[i+1])) +#define predict_y(i) (avg2 (ref[i], (ref+stride)[i])) +#define predict_xy(i) (avg4 (ref[i], ref[i+1], \ + (ref+stride)[i], (ref+stride)[i+1])) + +#define put(predictor,i) dest[i] = predictor (i) +#define avg(predictor,i) dest[i] = avg2 (predictor (i), dest[i]) + +/* mc function template */ +#define MC_FUNC(op, xy) \ + MC_FUNC_16(op, xy) \ + MC_FUNC_8(op, xy) + +#define MC_FUNC_16(op, xy) \ + void MC_##op##_##xy##_16 (uint8_t * dest, const uint8_t * ref, \ + const int stride, int height) \ + { \ + do { \ + op (predict_##xy, 0); \ + op (predict_##xy, 1); \ + op (predict_##xy, 2); \ + op (predict_##xy, 3); \ + op (predict_##xy, 4); \ + op (predict_##xy, 5); \ + op (predict_##xy, 6); \ + op (predict_##xy, 7); \ + op (predict_##xy, 8); \ + op (predict_##xy, 9); \ + op (predict_##xy, 10); \ + op (predict_##xy, 11); \ + op (predict_##xy, 12); \ + op (predict_##xy, 13); \ + op (predict_##xy, 14); \ + op (predict_##xy, 15); \ + ref += stride; \ + dest += stride; \ + } while (--height); \ + } + +#define MC_FUNC_8(op, xy) \ + void MC_##op##_##xy##_8 (uint8_t * dest, const uint8_t * ref, \ + const int stride, int height) \ + { \ + do { \ + op (predict_##xy, 0); \ + op (predict_##xy, 1); \ + op (predict_##xy, 2); \ + op (predict_##xy, 3); \ + op (predict_##xy, 4); \ + op (predict_##xy, 5); \ + op (predict_##xy, 6); \ + op (predict_##xy, 7); \ + ref += stride; \ + dest += stride; \ + } while (--height); \ + } diff --git a/apps/plugins/mpegplayer/libmpeg2/motion_comp_arm_c.c b/apps/plugins/mpegplayer/libmpeg2/motion_comp_arm_c.c new file mode 100644 index 0000000000..dcf1df53e9 --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/motion_comp_arm_c.c @@ -0,0 +1,39 @@ +/* + * motion_comp_arm.c + * Copyright (C) 2004 AGAWA Koji + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ +#include +#include "mpeg2.h" +#include "attributes.h" +#include "mpeg2_internal.h" +#include "motion_comp.h" + +/* definitions of the actual mc functions */ + +/* MC_FUNC (put, o) <= ASM */ +MC_FUNC (avg, o) +/* MC_FUNC (put, x) <= ASM */ +MC_FUNC (avg, x) +MC_FUNC (put, y) +MC_FUNC (avg, y) +MC_FUNC (put, xy) +MC_FUNC (avg, xy) diff --git a/apps/plugins/mpegplayer/libmpeg2/motion_comp_arm_s.S b/apps/plugins/mpegplayer/libmpeg2/motion_comp_arm_s.S new file mode 100644 index 0000000000..1ec1b0686e --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/motion_comp_arm_s.S @@ -0,0 +1,342 @@ +@ motion_comp_arm_s.S +@ Copyright (C) 2004 AGAWA Koji +@ +@ This file is part of mpeg2dec, a free MPEG-2 video stream decoder. +@ See http://libmpeg2.sourceforge.net/ for updates. +@ +@ mpeg2dec 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. +@ +@ mpeg2dec is distributed in the hope that it will be useful, +@ but WITHOUT ANY WARRANTY; without even the implied warranty of +@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +@ GNU General Public License for more details. +@ +@ You should have received a copy of the GNU General Public License +@ along with this program; if not, write to the Free Software +@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +@ +@ $Id$ + +#include "config.h" /* Rockbox: ARM architecture version */ + + .text + +@ ---------------------------------------------------------------- + .align + .global MC_put_o_16 +MC_put_o_16: + @@ void func(uint8_t * dest, const uint8_t * ref, int stride, int height) + @@ pld [r1] + stmfd sp!, {r4-r7, lr} @ R14 is also called LR + and r4, r1, #3 + ldr pc, [pc, r4, lsl #2] + .word 0 + .word MC_put_o_16_align0 + .word MC_put_o_16_align1 + .word MC_put_o_16_align2 + .word MC_put_o_16_align3 + +MC_put_o_16_align0: + ldmia r1, {r4-r7} + add r1, r1, r2 + @@ pld [r1] + stmia r0, {r4-r7} + subs r3, r3, #1 + add r0, r0, r2 + bne MC_put_o_16_align0 + ldmpc regs=r4-r7 @@ update PC with LR content. + +.macro ADJ_ALIGN_QW shift, R0, R1, R2, R3, R4 + mov \R0, \R0, lsr #(\shift) + orr \R0, \R0, \R1, lsl #(32 - \shift) + mov \R1, \R1, lsr #(\shift) + orr \R1, \R1, \R2, lsl #(32 - \shift) + mov \R2, \R2, lsr #(\shift) + orr \R2, \R2, \R3, lsl #(32 - \shift) + mov \R3, \R3, lsr #(\shift) + orr \R3, \R3, \R4, lsl #(32 - \shift) + mov \R4, \R4, lsr #(\shift) +.endm + +MC_put_o_16_align1: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r4-r7, r12} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_QW 8, r4, r5, r6, r7, r12 + stmia r0, {r4-r7} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r4-r7 @@ update PC with LR content. + +MC_put_o_16_align2: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r4-r7, r12} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_QW 16, r4, r5, r6, r7, r12 + stmia r0, {r4-r7} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r4-r7 @@ update PC with LR content. + +MC_put_o_16_align3: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r4-r7, r12} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_QW 24, r4, r5, r6, r7, r12 + stmia r0, {r4-r7} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r4-r7 @@ update PC with LR content. + +@ ---------------------------------------------------------------- + .align + .global MC_put_o_8 +MC_put_o_8: + @@ void func(uint8_t * dest, const uint8_t * ref, int stride, int height) + @@ pld [r1] + stmfd sp!, {r4, r5, lr} @ R14 is also called LR + and r4, r1, #3 + ldr pc, [pc, r4, lsl #2] + .word 0 + .word MC_put_o_8_align0 + .word MC_put_o_8_align1 + .word MC_put_o_8_align2 + .word MC_put_o_8_align3 + +MC_put_o_8_align0: + ldmia r1, {r4, r5} + add r1, r1, r2 + @@ pld [r1] + stmia r0, {r4, r5} + add r0, r0, r2 + subs r3, r3, #1 + bne MC_put_o_8_align0 + ldmpc regs=r4-r5 @@ update PC with LR content. + +.macro ADJ_ALIGN_DW shift, R0, R1, R2 + mov \R0, \R0, lsr #(\shift) + orr \R0, \R0, \R1, lsl #(32 - \shift) + mov \R1, \R1, lsr #(\shift) + orr \R1, \R1, \R2, lsl #(32 - \shift) + mov \R2, \R2, lsr #(\shift) +.endm + +MC_put_o_8_align1: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r4, r5, r12} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_DW 8, r4, r5, r12 + stmia r0, {r4, r5} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r4-r5 @@ update PC with LR content. + +MC_put_o_8_align2: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r4, r5, r12} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_DW 16, r4, r5, r12 + stmia r0, {r4, r5} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r4-r5 @@ update PC with LR content. + +MC_put_o_8_align3: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r4, r5, r12} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_DW 24, r4, r5, r12 + stmia r0, {r4, r5} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r4-r5 @@ update PC with LR content. + +@ ---------------------------------------------------------------- +.macro AVG_PW rW1, rW2 + mov \rW2, \rW2, lsl #24 + orr \rW2, \rW2, \rW1, lsr #8 + eor r9, \rW1, \rW2 +#if ARM_ARCH >= 6 + uhadd8 \rW2, \rW1, \rW2 +#else + and \rW2, \rW1, \rW2 + and r10, r9, r11 + add \rW2, \rW2, r10, lsr #1 +#endif + and r9, r9, r12 + add \rW2, \rW2, r9 +.endm + +#if ARM_ARCH >= 6 +#define HIGHEST_REG r9 +#else +#define HIGHEST_REG r11 +#endif + + .align + .global MC_put_x_16 +MC_put_x_16: + @@ void func(uint8_t * dest, const uint8_t * ref, int stride, int height) + @@ pld [r1] + stmfd sp!, {r4-HIGHEST_REG, lr} @ R14 is also called LR + and r4, r1, #3 + ldr r12, 2f +#if ARM_ARCH < 6 + mvn r11, r12 +#endif + ldr pc, [pc, r4, lsl #2] +2: .word 0x01010101 + .word MC_put_x_16_align0 + .word MC_put_x_16_align1 + .word MC_put_x_16_align2 + .word MC_put_x_16_align3 + +MC_put_x_16_align0: + ldmia r1, {r4-r8} + add r1, r1, r2 + @@ pld [r1] + AVG_PW r7, r8 + AVG_PW r6, r7 + AVG_PW r5, r6 + AVG_PW r4, r5 + stmia r0, {r5-r8} + subs r3, r3, #1 + add r0, r0, r2 + bne MC_put_x_16_align0 + ldmpc regs=r4-HIGHEST_REG @@ update PC with LR content. + +MC_put_x_16_align1: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r4-r8} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_QW 8, r4, r5, r6, r7, r8 + AVG_PW r7, r8 + AVG_PW r6, r7 + AVG_PW r5, r6 + AVG_PW r4, r5 + stmia r0, {r5-r8} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r4-HIGHEST_REG @@ update PC with LR content. + +MC_put_x_16_align2: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r4-r8} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_QW 16, r4, r5, r6, r7, r8 + AVG_PW r7, r8 + AVG_PW r6, r7 + AVG_PW r5, r6 + AVG_PW r4, r5 + stmia r0, {r5-r8} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r4-HIGHEST_REG @@ update PC with LR content. + +MC_put_x_16_align3: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r4-r8} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_QW 24, r4, r5, r6, r7, r8 + AVG_PW r7, r8 + AVG_PW r6, r7 + AVG_PW r5, r6 + AVG_PW r4, r5 + stmia r0, {r5-r8} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r4-HIGHEST_REG @@ update PC with LR content. + +@ ---------------------------------------------------------------- + .align + .global MC_put_x_8 +MC_put_x_8: + @@ void func(uint8_t * dest, const uint8_t * ref, int stride, int height) + @@ pld [r1] + stmfd sp!, {r6-HIGHEST_REG, lr} @ R14 is also called LR + and r6, r1, #3 + ldr r12, 2f +#if ARM_ARCH < 6 + mvn r11, r12 +#endif + ldr pc, [pc, r6, lsl #2] +2: .word 0x01010101 + .word MC_put_x_8_align0 + .word MC_put_x_8_align1 + .word MC_put_x_8_align2 + .word MC_put_x_8_align3 + +MC_put_x_8_align0: + ldmia r1, {r6-r8} + add r1, r1, r2 + @@ pld [r1] + AVG_PW r7, r8 + AVG_PW r6, r7 + stmia r0, {r7-r8} + subs r3, r3, #1 + add r0, r0, r2 + bne MC_put_x_8_align0 + ldmpc regs=r6-HIGHEST_REG @@ update PC with LR content. + +MC_put_x_8_align1: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r6-r8} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_DW 8, r6, r7, r8 + AVG_PW r7, r8 + AVG_PW r6, r7 + stmia r0, {r7-r8} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r6-HIGHEST_REG @@ update PC with LR content. + +MC_put_x_8_align2: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r6-r8} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_DW 16, r6, r7, r8 + AVG_PW r7, r8 + AVG_PW r6, r7 + stmia r0, {r7-r8} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r6-HIGHEST_REG @@ update PC with LR content. + +MC_put_x_8_align3: + and r1, r1, #0xFFFFFFFC +1: ldmia r1, {r6-r8} + add r1, r1, r2 + @@ pld [r1] + ADJ_ALIGN_DW 24, r6, r7, r8 + AVG_PW r7, r8 + AVG_PW r6, r7 + stmia r0, {r7-r8} + subs r3, r3, #1 + add r0, r0, r2 + bne 1b + ldmpc regs=r6-HIGHEST_REG @@ update PC with LR content. diff --git a/apps/plugins/mpegplayer/libmpeg2/motion_comp_c.c b/apps/plugins/mpegplayer/libmpeg2/motion_comp_c.c new file mode 100644 index 0000000000..9a8640e7e6 --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/motion_comp_c.c @@ -0,0 +1,40 @@ +/* + * motion_comp.c + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ +#include +#include "mpeg2.h" +#include "attributes.h" +#include "mpeg2_internal.h" +#include "motion_comp.h" + +/* definitions of the actual mc functions */ + +MC_FUNC (put, o) +MC_FUNC (avg, o) +MC_FUNC (put, x) +MC_FUNC (avg, x) +MC_FUNC (put, y) +MC_FUNC (avg, y) +MC_FUNC (put, xy) +MC_FUNC (avg, xy) diff --git a/apps/plugins/mpegplayer/libmpeg2/motion_comp_coldfire_c.c b/apps/plugins/mpegplayer/libmpeg2/motion_comp_coldfire_c.c new file mode 100644 index 0000000000..b97e3510e7 --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/motion_comp_coldfire_c.c @@ -0,0 +1,38 @@ +/* + * Based on: + * motion_comp_arm.c + * Copyright (C) 2004 AGAWA Koji + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include "mpeg2.h" +#include "attributes.h" +#include "mpeg2_internal.h" +#include "motion_comp.h" + +/* definitions of the actual mc functions */ + +/* MC_FUNC (put, o) <= ASM */ +MC_FUNC (avg, o) +/* MC_FUNC (put, x) <= ASM */ +MC_FUNC (avg, x) +MC_FUNC (put, y) +MC_FUNC (avg, y) +MC_FUNC (put, xy) +MC_FUNC (avg, xy) diff --git a/apps/plugins/mpegplayer/libmpeg2/motion_comp_coldfire_s.S b/apps/plugins/mpegplayer/libmpeg2/motion_comp_coldfire_s.S new file mode 100644 index 0000000000..55d87cb708 --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/motion_comp_coldfire_s.S @@ -0,0 +1,436 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Jens Arnold + * + * 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. + * + ****************************************************************************/ + +.macro LEFT8_PW dW1, dW2 | needs %d0 == 24, clobbers %d2 + lsl.l #8, \dW1 | changes dW1, keeps dW2 + move.l \dW2, %d2 + lsr.l %d0, %d2 + or.l %d2, \dW1 +.endm + +.macro LEFT24_PW dW1, dW2 | needs %d0 == 24, clobbers %d2 + lsl.l %d0, \dW1 | changes dW1, keeps dW2 + move.l \dW2, %d2 + lsr.l #8, %d2 + or.l %d2, \dW1 +.endm + +/*****************************************************************************/ + + .align 2 + .global MC_put_o_8 + .type MC_put_o_8, @function + +MC_put_o_8: + movem.l (4,%sp), %a0-%a1 | dest, source + move.l %a1, %d0 + and.l #3, %d0 + sub.l %d0, %a1 | align source + jmp.l (2, %pc, %d0.l*4) + bra.w .po8_0 + bra.w .po8_1 + bra.w .po8_2 + | last table entry coincides with target + +.po8_3: + lea.l (-5*4,%sp), %sp + movem.l %d2-%d5/%a2, (%sp) | save some registers + move.l (5*4+12,%sp), %a2 | stride + move.l (5*4+16,%sp), %d1 | height + moveq.l #24, %d0 | shift amount +1: + movem.l (%a1), %d3-%d5 + add.l %a2, %a1 + LEFT24_PW %d3, %d4 + lsl.l %d0, %d4 + lsr.l #8, %d5 + or.l %d5, %d4 + movem.l %d3-%d4, (%a0) + add.l %a2, %a0 + subq.l #1, %d1 + bne.s 1b + movem.l (%sp), %d2-%d5/%a2 + lea.l (5*4,%sp), %sp + rts + +.po8_2: + lea.l (-3*4,%sp), %sp + movem.l %d2-%d4, (%sp) | save some registers + movem.l (3*4+12,%sp), %d0-%d1 | stride, height +1: + movem.l (%a1), %d2-%d4 + add.l %d0, %a1 + swap %d2 + swap %d3 + move.w %d3, %d2 + swap %d4 + move.w %d4, %d3 + movem.l %d2-%d3, (%a0) + add.l %d0, %a0 + subq.l #1, %d1 + bne.s 1b + movem.l (%sp), %d2-%d4 + lea.l (3*4,%sp), %sp + rts + +.po8_1: + lea.l (-5*4,%sp), %sp + movem.l %d2-%d5/%a2, (%sp) | save some registers + move.l (5*4+12,%sp), %a2 | stride + move.l (5*4+16,%sp), %d1 | height + moveq.l #24, %d0 | shift amount +1: + movem.l (%a1), %d3-%d5 + add.l %a2, %a1 + LEFT8_PW %d3, %d4 + lsl.l #8, %d4 + lsr.l %d0, %d5 + or.l %d5, %d4 + movem.l %d3-%d4, (%a0) + add.l %a2, %a0 + subq.l #1, %d1 + bne.s 1b + movem.l (%sp), %d2-%d5/%a2 + lea.l (5*4,%sp), %sp + rts + +.po8_0: + movem.l (12,%sp), %d0-%d1 | stride, height + subq.l #4, %d0 | adjust for increment within the loop +1: + move.l (%a1)+, (%a0)+ + move.l (%a1), (%a0) + add.l %d0, %a0 + add.l %d0, %a1 + subq.l #1, %d1 + bne.s 1b + rts + +/*****************************************************************************/ + + .align 2 + .global MC_put_o_16 + .type MC_put_o_16, @function + +MC_put_o_16: + lea.l (-7*4,%sp), %sp + movem.l %d2-%d7/%a2, (%sp) | save some registers + movem.l (7*4+4,%sp), %a0-%a2| dest, source, stride + move.l (7*4+16,%sp), %d1 | height + move.l %a1, %d0 + and.l #3, %d0 + sub.l %d0, %a1 + jmp.l (2, %pc, %d0.l*4) + bra.w .po16_0 + bra.w .po16_1 + bra.w .po16_2 + | last table entry coincides with target + +.po16_3: + moveq.l #24, %d0 | shift amount +1: + movem.l (%a1), %d3-%d7 + add.l %a2, %a1 + LEFT24_PW %d3, %d4 + LEFT24_PW %d4, %d5 + LEFT24_PW %d5, %d6 + lsl.l %d0, %d6 + lsr.l #8, %d7 + or.l %d7, %d6 + movem.l %d3-%d6, (%a0) + add.l %a2, %a0 + subq.l #1, %d1 + bne.s 1b + movem.l (%sp), %d2-%d7/%a2 + lea.l (7*4,%sp), %sp + rts + +.po16_2: +1: + movem.l (%a1), %d3-%d7 + add.l %a2, %a1 + swap %d3 + swap %d4 + move.w %d4, %d3 + swap %d5 + move.w %d5, %d4 + swap %d6 + move.w %d6, %d5 + swap %d7 + move.w %d7, %d6 + movem.l %d3-%d6, (%a0) + add.l %a2, %a0 + subq.l #1, %d1 + bne.s 1b + movem.l (%sp), %d2-%d7/%a2 + lea.l (7*4,%sp), %sp + rts + +.po16_1: + moveq.l #24, %d0 | shift amount +1: + movem.l (%a1), %d3-%d7 + add.l %a2, %a1 + LEFT8_PW %d3, %d4 + LEFT8_PW %d4, %d5 + LEFT8_PW %d5, %d6 + lsl.l #8, %d6 + lsr.l %d0, %d7 + or.l %d7, %d6 + movem.l %d3-%d6, (%a0) + add.l %a2, %a0 + subq.l #1, %d1 + bne.s 1b + movem.l (%sp), %d2-%d7/%a2 + lea.l (7*4,%sp), %sp + rts + +.po16_0: +1: + movem.l (%a1), %d3-%d6 + add.l %a2, %a1 + movem.l %d3-%d6, (%a0) + add.l %a2, %a0 + subq.l #1, %d1 + bne.s 1b + movem.l (%sp), %d2-%d7/%a2 + lea.l (7*4,%sp), %sp + rts + +/*****************************************************************************/ + +.macro AVG_PW dW1, dW2 | needs %d0 == 24, clobbers %d1, %d2, + move.l \dW1, %d1 | changes dW1, keeps dW2 + lsl.l #8, \dW1 + move.l \dW2, %d2 + lsr.l %d0, %d2 + or.l %d2, \dW1 + move.l %d1, %d2 + eor.l \dW1, %d1 + and.l %d2, \dW1 + move.l #0xfefefefe, %d2 + and.l %d1, %d2 + eor.l %d2, %d1 + lsr.l #1, %d2 + add.l %d2, \dW1 + add.l %d1, \dW1 +.endm + +/*****************************************************************************/ + + .align 2 + .global MC_put_x_8 + .type MC_put_x_8, @function + +MC_put_x_8: + lea.l (-6*4,%sp), %sp + movem.l %d2-%d6/%a2, (%sp) | save some registers + movem.l (6*4+4,%sp), %a0-%a2| dest, source, stride + move.l (6*4+16,%sp), %d6 | height + move.l %a1, %d0 + and.l #3, %d0 + sub.l %d0, %a1 + jmp.l (2, %pc, %d0.l*4) + bra.w .px8_0 + bra.w .px8_1 + bra.w .px8_2 + | last table entry coincides with target + +.px8_3: + moveq.l #24, %d0 +1: + movem.l (%a1), %d3-%d5 + add.l %a2, %a1 + LEFT24_PW %d3, %d4 + LEFT24_PW %d4, %d5 + lsl.l %d0, %d5 + AVG_PW %d3, %d4 + AVG_PW %d4, %d5 + movem.l %d3-%d4, (%a0) + add.l %a2, %a0 + subq.l #1, %d6 + bne.s 1b + movem.l (%sp), %d2-%d6/%a2 + lea.l (6*4,%sp), %sp + rts + +.px8_2: + moveq.l #24, %d0 +1: + movem.l (%a1), %d3-%d5 + add.l %a2, %a1 + swap %d3 + swap %d4 + move.w %d4, %d3 + swap %d5 + move.w %d5, %d4 + AVG_PW %d3, %d4 + AVG_PW %d4, %d5 + movem.l %d3-%d4, (%a0) + add.l %a2, %a0 + subq.l #1, %d6 + bne.s 1b + movem.l (%sp), %d2-%d6/%a2 + lea.l (6*4,%sp), %sp + rts + +.px8_1: + moveq.l #24, %d0 +1: + movem.l (%a1), %d3-%d5 + add.l %a2, %a1 + LEFT8_PW %d3, %d4 + LEFT8_PW %d4, %d5 + lsl.l #8, %d5 + AVG_PW %d3, %d4 + AVG_PW %d4, %d5 + movem.l %d3-%d4, (%a0) + add.l %a2, %a0 + subq.l #1, %d6 + bne.s 1b + movem.l (%sp), %d2-%d6/%a2 + lea.l (6*4,%sp), %sp + rts + +.px8_0: + moveq.l #24, %d0 +1: + movem.l (%a1), %d3-%d5 + add.l %a2, %a1 + AVG_PW %d3, %d4 + AVG_PW %d4, %d5 + movem.l %d3-%d4, (%a0) + add.l %a2, %a0 + subq.l #1, %d6 + bne.s 1b + movem.l (%sp), %d2-%d6/%a2 + lea.l (6*4,%sp), %sp + rts + +/*****************************************************************************/ + + .align 2 + .global MC_put_x_16 + .type MC_put_x_16, @function + +MC_put_x_16: + lea.l (-8*4,%sp), %sp + movem.l %d2-%d7/%a2-%a3, (%sp) | save some registers + movem.l (8*4+4,%sp), %a0-%a3 | dest, source, stride, height + move.l %a1, %d0 + and.l #3, %d0 + sub.l %d0, %a1 + jmp.l (2, %pc, %d0.l*4) + bra.w .px16_0 + bra.w .px16_1 + bra.w .px16_2 + | last table entry coincides with target + +.px16_3: + moveq.l #24, %d0 +1: + movem.l (%a1), %d3-%d7 + add.l %a2, %a1 + LEFT24_PW %d3, %d4 + LEFT24_PW %d4, %d5 + LEFT24_PW %d5, %d6 + LEFT24_PW %d6, %d7 + lsl.l %d0, %d7 + AVG_PW %d3, %d4 + AVG_PW %d4, %d5 + AVG_PW %d5, %d6 + AVG_PW %d6, %d7 + movem.l %d3-%d6, (%a0) + add.l %a2, %a0 + subq.l #1, %a3 + tst.l %a3 + bne.w 1b + movem.l (%sp), %d2-%d7/%a2-%a3 + lea.l (8*4,%sp), %sp + rts + +.px16_2: + moveq.l #24, %d0 +1: + movem.l (%a1), %d3-%d7 + add.l %a2, %a1 + swap %d3 + swap %d4 + move.w %d4, %d3 + swap %d5 + move.w %d5, %d4 + swap %d6 + move.w %d6, %d5 + swap %d7 + move.w %d7, %d6 + AVG_PW %d3, %d4 + AVG_PW %d4, %d5 + AVG_PW %d5, %d6 + AVG_PW %d6, %d7 + movem.l %d3-%d6, (%a0) + add.l %a2, %a0 + subq.l #1, %a3 + tst.l %a3 + bne.w 1b + movem.l (%sp), %d2-%d7/%a2-%a3 + lea.l (8*4,%sp), %sp + rts + +.px16_1: + moveq.l #24, %d0 +1: + movem.l (%a1), %d3-%d7 + add.l %a2, %a1 + LEFT8_PW %d3, %d4 + LEFT8_PW %d4, %d5 + LEFT8_PW %d5, %d6 + LEFT8_PW %d6, %d7 + lsl.l #8, %d7 + AVG_PW %d3, %d4 + AVG_PW %d4, %d5 + AVG_PW %d5, %d6 + AVG_PW %d6, %d7 + movem.l %d3-%d6, (%a0) + add.l %a2, %a0 + subq.l #1, %a3 + tst.l %a3 + bne.w 1b + movem.l (%sp), %d2-%d7/%a2-%a3 + lea.l (8*4,%sp), %sp + rts + +.px16_0: + moveq.l #24, %d0 +1: + movem.l (%a1), %d3-%d7 + add.l %a2, %a1 + AVG_PW %d3, %d4 + AVG_PW %d4, %d5 + AVG_PW %d5, %d6 + AVG_PW %d6, %d7 + movem.l %d3-%d6, (%a0) + add.l %a2, %a0 + subq.l #1, %a3 + tst.l %a3 + bne.w 1b + movem.l (%sp), %d2-%d7/%a2-%a3 + lea.l (8*4,%sp), %sp + rts diff --git a/apps/plugins/mpegplayer/libmpeg2/mpeg2.h b/apps/plugins/mpegplayer/libmpeg2/mpeg2.h new file mode 100644 index 0000000000..bd14ead96e --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/mpeg2.h @@ -0,0 +1,223 @@ +/* + * mpeg2.h + * Copyright (C) 2000-2004 Michel Lespinasse + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + * libmpeg2 sync history: + * 2008-07-01 - CVS revision 1.67 + */ + +#ifndef MPEG2_H +#define MPEG2_H + +#include "mpeg2dec_config.h" + +#define MPEG2_VERSION(a,b,c) (((a)<<16)|((b)<<8)|(c)) +#define MPEG2_RELEASE MPEG2_VERSION (0, 5, 0) /* 0.5.0 */ + +#define SEQ_FLAG_MPEG2 1 +#define SEQ_FLAG_CONSTRAINED_PARAMETERS 2 +#define SEQ_FLAG_PROGRESSIVE_SEQUENCE 4 +#define SEQ_FLAG_LOW_DELAY 8 +#define SEQ_FLAG_COLOUR_DESCRIPTION 16 + +#define SEQ_MASK_VIDEO_FORMAT 0xe0 +#define SEQ_VIDEO_FORMAT_COMPONENT 0x00 +#define SEQ_VIDEO_FORMAT_PAL 0x20 +#define SEQ_VIDEO_FORMAT_NTSC 0x40 +#define SEQ_VIDEO_FORMAT_SECAM 0x60 +#define SEQ_VIDEO_FORMAT_MAC 0x80 +#define SEQ_VIDEO_FORMAT_UNSPECIFIED 0xa0 + +typedef struct mpeg2_sequence_s +{ + unsigned int width, height; + unsigned int chroma_width, chroma_height; + unsigned int byte_rate; + unsigned int vbv_buffer_size; + uint32_t flags; + + unsigned int picture_width, picture_height; + unsigned int display_width, display_height; + unsigned int pixel_width, pixel_height; + unsigned int frame_period; + + uint8_t profile_level_id; + uint8_t colour_primaries; + uint8_t transfer_characteristics; + uint8_t matrix_coefficients; +} mpeg2_sequence_t; + +#define GOP_FLAG_DROP_FRAME 1 +#define GOP_FLAG_BROKEN_LINK 2 +#define GOP_FLAG_CLOSED_GOP 4 + +typedef struct mpeg2_gop_s +{ + uint8_t hours; + uint8_t minutes; + uint8_t seconds; + uint8_t pictures; + uint32_t flags; +} mpeg2_gop_t; + +#define PIC_MASK_CODING_TYPE 7 +#define PIC_FLAG_CODING_TYPE_I 1 +#define PIC_FLAG_CODING_TYPE_P 2 +#define PIC_FLAG_CODING_TYPE_B 3 +#define PIC_FLAG_CODING_TYPE_D 4 + +#define PIC_FLAG_TOP_FIELD_FIRST 8 +#define PIC_FLAG_PROGRESSIVE_FRAME 16 +#define PIC_FLAG_COMPOSITE_DISPLAY 32 +#define PIC_FLAG_SKIP 64 +#define PIC_FLAG_TAGS 128 +#define PIC_MASK_COMPOSITE_DISPLAY 0xfffff000 + +typedef struct mpeg2_picture_s +{ + unsigned int temporal_reference; + unsigned int nb_fields; + uint32_t tag, tag2; + uint32_t flags; + struct + { + int x, y; + } display_offset[3]; +} mpeg2_picture_t; + +typedef struct mpeg2_fbuf_s +{ + uint8_t * buf[MPEG2_COMPONENTS]; + void * id; +} mpeg2_fbuf_t; + +typedef struct mpeg2_info_s +{ + const mpeg2_sequence_t * sequence; + const mpeg2_gop_t * gop; + const mpeg2_picture_t * current_picture; + const mpeg2_picture_t * current_picture_2nd; + const mpeg2_fbuf_t * current_fbuf; + const mpeg2_picture_t * display_picture; + const mpeg2_picture_t * display_picture_2nd; + const mpeg2_fbuf_t * display_fbuf; + const mpeg2_fbuf_t * discard_fbuf; + const uint8_t * user_data; + unsigned int user_data_len; +} mpeg2_info_t; + +typedef struct mpeg2dec_s mpeg2dec_t; +typedef struct mpeg2_decoder_s mpeg2_decoder_t; + +typedef enum +{ + STATE_INTERNAL_NORETURN = -1, + STATE_BUFFER = 0, + STATE_SEQUENCE = 1, + STATE_SEQUENCE_REPEATED = 2, + STATE_SEQUENCE_MODIFIED = 3, + STATE_GOP = 4, + STATE_PICTURE = 5, + STATE_SLICE_1ST = 6, + STATE_PICTURE_2ND = 7, + STATE_SLICE = 8, + STATE_END = 9, + STATE_INVALID = 10, + STATE_INVALID_END = 11, +} mpeg2_state_t; + +typedef struct mpeg2_convert_init_s +{ + unsigned int id_size; + unsigned int buf_size[MPEG2_COMPONENTS]; + void (* start)(void * id, const mpeg2_fbuf_t * fbuf, + const mpeg2_picture_t * picture, const mpeg2_gop_t * gop); + void (* copy)(void * id, uint8_t * const * src, unsigned int v_offset); +} mpeg2_convert_init_t; + +typedef enum +{ + MPEG2_CONVERT_SET = 0, + MPEG2_CONVERT_STRIDE = 1, + MPEG2_CONVERT_START = 2 +} mpeg2_convert_stage_t; + +typedef int mpeg2_convert_t (int stage, void * id, + const mpeg2_sequence_t * sequence, int stride, + void * arg, mpeg2_convert_init_t * result); +int mpeg2_convert (mpeg2dec_t * mpeg2dec, mpeg2_convert_t convert, void * arg); +int mpeg2_stride (mpeg2dec_t * mpeg2dec, int stride); +void mpeg2_set_buf (mpeg2dec_t * mpeg2dec, uint8_t * buf[MPEG2_COMPONENTS], + void * id); +void mpeg2_custom_fbuf (mpeg2dec_t * mpeg2dec, int custom_fbuf); + +mpeg2dec_t * mpeg2_init (void); +const mpeg2_info_t * mpeg2_info (mpeg2dec_t * mpeg2dec); +void mpeg2_close (mpeg2dec_t * mpeg2dec); + +void mpeg2_buffer (mpeg2dec_t * mpeg2dec, uint8_t * start, uint8_t * end); +int mpeg2_getpos (mpeg2dec_t * mpeg2dec); +mpeg2_state_t mpeg2_parse (mpeg2dec_t * mpeg2dec); + +void mpeg2_reset (mpeg2dec_t * mpeg2dec, int full_reset); +void mpeg2_skip (mpeg2dec_t * mpeg2dec, int skip); +void mpeg2_slice_region (mpeg2dec_t * mpeg2dec, int start, int end); + +void mpeg2_tag_picture (mpeg2dec_t * mpeg2dec, uint32_t tag, uint32_t tag2); + +void mpeg2_init_fbuf (mpeg2_decoder_t * decoder, + uint8_t * current_fbuf[MPEG2_COMPONENTS], + uint8_t * forward_fbuf[MPEG2_COMPONENTS], + uint8_t * backward_fbuf[MPEG2_COMPONENTS]); +void mpeg2_slice (mpeg2_decoder_t * decoder, int code, const uint8_t * buffer); + +int mpeg2_guess_aspect (const mpeg2_sequence_t * sequence, + unsigned int * pixel_width, + unsigned int * pixel_height); + +typedef enum +{ + MPEG2_ALLOC_MPEG2DEC = 0, + MPEG2_ALLOC_CHUNK = 1, + MPEG2_ALLOC_YUV = 2, + MPEG2_ALLOC_CONVERT_ID = 3, + MPEG2_ALLOC_CONVERTED = 4, + MPEG_ALLOC_CODEC_MALLOC, + MPEG_ALLOC_CODEC_CALLOC, + MPEG_ALLOC_MPEG2_BUFFER, + MPEG_ALLOC_AUDIOBUF, + MPEG_ALLOC_PCMOUT, + MPEG_ALLOC_DISKBUF, + __MPEG_ALLOC_FIRST = -256, +} mpeg2_alloc_t; + +void * mpeg2_malloc (unsigned size, mpeg2_alloc_t reason); +#if 0 +void mpeg2_free (void * buf); +#endif +/* allocates a dedicated buffer and locks all previous allocation in place */ +void * mpeg2_bufalloc(unsigned size, mpeg2_alloc_t reason); +/* clears all non-dedicated buffer space */ +void mpeg2_mem_reset(void); +void mpeg2_alloc_init(unsigned char* buf, int mallocsize); + +#endif /* MPEG2_H */ diff --git a/apps/plugins/mpegplayer/libmpeg2/mpeg2_internal.h b/apps/plugins/mpegplayer/libmpeg2/mpeg2_internal.h new file mode 100644 index 0000000000..e04562e18d --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/mpeg2_internal.h @@ -0,0 +1,274 @@ +/* + * mpeg2_internal.h + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + * libmpeg2 sync history: + * 2008-07-01 - CVS revision 1.89 + */ +#ifndef MPEG2_INTERNAL_H +#define MPEG2_INTERNAL_H + +#include "config.h" /* for Rockbox CPU_ #defines */ + +/* macroblock modes */ +#define MACROBLOCK_INTRA 1 +#define MACROBLOCK_PATTERN 2 +#define MACROBLOCK_MOTION_BACKWARD 4 +#define MACROBLOCK_MOTION_FORWARD 8 +#define MACROBLOCK_QUANT 16 +#define DCT_TYPE_INTERLACED 32 +/* motion_type */ +#define MOTION_TYPE_SHIFT 6 +#define MC_FIELD 1 +#define MC_FRAME 2 +#define MC_16X8 2 +#define MC_DMV 3 + +/* picture structure */ +#define TOP_FIELD 1 +#define BOTTOM_FIELD 2 +#define FRAME_PICTURE 3 + +/* picture coding type */ +#define I_TYPE 1 +#define P_TYPE 2 +#define B_TYPE 3 +#define D_TYPE 4 + +typedef void mpeg2_mc_fct (uint8_t *, const uint8_t *, int, int); + +typedef struct +{ + uint8_t * ref[2][MPEG2_COMPONENTS]; + uint8_t ** ref2[2]; + int pmv[2][2]; + int f_code[2]; +} motion_t; + +typedef void motion_parser_t(mpeg2_decoder_t * decoder, + motion_t * motion, + mpeg2_mc_fct * const * table); + +struct mpeg2_decoder_s +{ + /* first, state that carries information from one macroblock to the */ + /* next inside a slice, and is never used outside of mpeg2_slice() */ + + /* bit parsing stuff */ + uint32_t bitstream_buf; /* current 32 bit working set */ + int bitstream_bits; /* used bits in working set */ + const uint8_t * bitstream_ptr; /* buffer with stream data */ + + uint8_t * dest[MPEG2_COMPONENTS]; + + int offset; + int stride; + int uv_stride; + int slice_stride; + int slice_uv_stride; + int stride_frame; + unsigned int limit_x; + unsigned int limit_y_16; + unsigned int limit_y_8; + unsigned int limit_y; + + /* Motion vectors */ + /* The f_ and b_ correspond to the forward and backward motion */ + /* predictors */ + motion_t b_motion; + motion_t f_motion; + motion_parser_t * motion_parser[5]; + + /* predictor for DC coefficients in intra blocks */ + int16_t dc_dct_pred[MPEG2_COMPONENTS]; + + /* DCT coefficients */ + int16_t * DCTblock; /* put buffer separately to have it in IRAM */ + + uint8_t * picture_dest[MPEG2_COMPONENTS]; + void (* convert) (void * convert_id, uint8_t * const * src, + unsigned int v_offset); + void * convert_id; + + int dmv_offset; + unsigned int v_offset; + + /* now non-slice-specific information */ + + /* sequence header stuff */ + uint16_t * quantizer_matrix[4]; + uint16_t (* chroma_quantizer[2])[64]; + uint16_t quantizer_prescale[4][32][64]; + + /* The width and height of the picture snapped to macroblock units */ + int width; + int height; + int vertical_position_extension; + int chroma_format; + + /* picture header stuff */ + + /* what type of picture this is (I, P, B, D) */ + int coding_type; + + /* picture coding extension stuff */ + + /* quantization factor for intra dc coefficients */ + int intra_dc_precision; + /* top/bottom/both fields */ + int picture_structure; + /* bool to indicate all predictions are frame based */ + int frame_pred_frame_dct; + /* bool to indicate whether intra blocks have motion vectors */ + /* (for concealment) */ + int concealment_motion_vectors; + /* bool to use different vlc tables */ + int intra_vlc_format; + /* used for DMV MC */ + int top_field_first; + + /* stuff derived from bitstream */ + + /* pointer to the zigzag scan we're supposed to be using */ + const uint8_t * scan; + + int second_field; + + int mpeg1; +}; + +typedef struct +{ + mpeg2_fbuf_t fbuf; +} fbuf_alloc_t; + +struct mpeg2dec_s +{ + mpeg2_decoder_t decoder; + + mpeg2_info_t info; + + uint32_t shift; + int is_display_initialized; + mpeg2_state_t (* action) (struct mpeg2dec_s * mpeg2dec); + mpeg2_state_t state; + uint32_t ext_state; + + /* allocated in init - gcc has problems allocating such big structures */ + uint8_t * ATTR_ALIGN(4) chunk_buffer; + /* pointer to start of the current chunk */ + uint8_t * chunk_start; + /* pointer to current position in chunk_buffer */ + uint8_t * chunk_ptr; + /* last start code ? */ + uint8_t code; + + /* picture tags */ + uint32_t tag_current, tag2_current, tag_previous, tag2_previous; + int num_tags; + int bytes_since_tag; + + int first; + int alloc_index_user; + int alloc_index; + uint8_t first_decode_slice; + uint8_t nb_decode_slices; + + unsigned int user_data_len; + + mpeg2_sequence_t new_sequence; + mpeg2_sequence_t sequence; + mpeg2_gop_t new_gop; + mpeg2_gop_t gop; + mpeg2_picture_t new_picture; + mpeg2_picture_t pictures[4]; + mpeg2_picture_t * picture; + /*const*/ mpeg2_fbuf_t * fbuf[3]; /* 0: current fbuf, 1-2: prediction fbufs */ + + fbuf_alloc_t fbuf_alloc[3]; + int custom_fbuf; + + uint8_t * yuv_buf[3][MPEG2_COMPONENTS]; + int yuv_index; + mpeg2_convert_t * convert; + void * convert_arg; + unsigned int convert_id_size; + int convert_stride; + void (* convert_start) (void * id, const mpeg2_fbuf_t * fbuf, + const mpeg2_picture_t * picture, + const mpeg2_gop_t * gop); + + uint8_t * buf_start; + uint8_t * buf_end; + + int16_t display_offset_x, display_offset_y; + + int copy_matrix; + int8_t q_scale_type, scaled[4]; + uint8_t quantizer_matrix[4][64]; + uint8_t new_quantizer_matrix[4][64]; +}; + +/* decode.c */ +mpeg2_state_t mpeg2_seek_header (mpeg2dec_t * mpeg2dec); +mpeg2_state_t mpeg2_parse_header (mpeg2dec_t * mpeg2dec); + +/* header.c */ +void mpeg2_header_state_init (mpeg2dec_t * mpeg2dec); +void mpeg2_reset_info (mpeg2_info_t * info); +int mpeg2_header_sequence (mpeg2dec_t * mpeg2dec); +int mpeg2_header_gop (mpeg2dec_t * mpeg2dec); +int mpeg2_header_picture (mpeg2dec_t * mpeg2dec); +int mpeg2_header_extension (mpeg2dec_t * mpeg2dec); +int mpeg2_header_user_data (mpeg2dec_t * mpeg2dec); +void mpeg2_header_sequence_finalize (mpeg2dec_t * mpeg2dec); +void mpeg2_header_gop_finalize (mpeg2dec_t * mpeg2dec); +void mpeg2_header_picture_finalize (mpeg2dec_t * mpeg2dec); +mpeg2_state_t mpeg2_header_slice_start (mpeg2dec_t * mpeg2dec); +mpeg2_state_t mpeg2_header_end (mpeg2dec_t * mpeg2dec); +void mpeg2_set_fbuf (mpeg2dec_t * mpeg2dec, int b_type); + +/* idct.c */ +void mpeg2_idct_init (void); +void mpeg2_idct_copy(int16_t * block, uint8_t * dest, + const int stride); +void mpeg2_idct_add(const int last, int16_t * block, + uint8_t * dest, const int stride); + +extern const uint8_t default_mpeg2_scan_norm[64]; +extern const uint8_t default_mpeg2_scan_alt[64]; +extern uint8_t mpeg2_scan_norm[64]; +extern uint8_t mpeg2_scan_alt[64]; + +/* motion_comp.c */ +void mpeg2_mc_init (void); + +typedef struct +{ + mpeg2_mc_fct * put [8]; + mpeg2_mc_fct * avg [8]; +} mpeg2_mc_t; + +extern const mpeg2_mc_t mpeg2_mc; + +#endif /* MPEG2_INTERNAL_H */ + diff --git a/apps/plugins/mpegplayer/libmpeg2/mpeg2dec_config.h b/apps/plugins/mpegplayer/libmpeg2/mpeg2dec_config.h new file mode 100644 index 0000000000..c34ee374df --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/mpeg2dec_config.h @@ -0,0 +1,15 @@ +/* $Id$ */ +#ifndef MPEG2DEC_CONFIG_H +#define MPEG2DEC_CONFIG_H + +#define ATTRIBUTE_ALIGNED_MAX 16 + +#ifdef HAVE_LCD_COLOR +#define MPEG2_COLOR 1 +#define MPEG2_COMPONENTS 3 +#else +#define MPEG2_COLOR 0 +#define MPEG2_COMPONENTS 1 +#endif + +#endif /* MPEG2DEC_CONFIG_H */ diff --git a/apps/plugins/mpegplayer/libmpeg2/slice.c b/apps/plugins/mpegplayer/libmpeg2/slice.c new file mode 100644 index 0000000000..926333d5d0 --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/slice.c @@ -0,0 +1,2898 @@ +/* + * slice.c + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 2003 Peter Gubanov + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + * libmpeg2 sync history: + * 2008-07-01 - CVS revision 1.55 + */ + +#include "plugin.h" + +#include "mpeg2dec_config.h" + +#include "mpeg2.h" +#include "attributes.h" +#include "mpeg2_internal.h" + +#include "vlc.h" + +static inline int get_macroblock_modes (mpeg2_decoder_t * const decoder) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + + int macroblock_modes; + const MBtab * tab; + + switch (decoder->coding_type) + { + case I_TYPE: + tab = MB_I + UBITS (bit_buf, 1); + DUMPBITS (bit_buf, bits, tab->len); + macroblock_modes = tab->modes; + + if (!(decoder->frame_pred_frame_dct) && + decoder->picture_structure == FRAME_PICTURE) + { + macroblock_modes |= UBITS (bit_buf, 1) * DCT_TYPE_INTERLACED; + DUMPBITS (bit_buf, bits, 1); + } + + return macroblock_modes; + + case P_TYPE: + tab = MB_P + UBITS (bit_buf, 5); + DUMPBITS (bit_buf, bits, tab->len); + macroblock_modes = tab->modes; + + if (decoder->picture_structure != FRAME_PICTURE) + { + if (macroblock_modes & MACROBLOCK_MOTION_FORWARD) + { + macroblock_modes |= UBITS (bit_buf, 2) << MOTION_TYPE_SHIFT; + DUMPBITS (bit_buf, bits, 2); + } + + return macroblock_modes | MACROBLOCK_MOTION_FORWARD; + } + else if (decoder->frame_pred_frame_dct) + { + if (macroblock_modes & MACROBLOCK_MOTION_FORWARD) + macroblock_modes |= MC_FRAME << MOTION_TYPE_SHIFT; + + return macroblock_modes | MACROBLOCK_MOTION_FORWARD; + } + else + { + if (macroblock_modes & MACROBLOCK_MOTION_FORWARD) + { + macroblock_modes |= UBITS (bit_buf, 2) << MOTION_TYPE_SHIFT; + DUMPBITS (bit_buf, bits, 2); + } + + if (macroblock_modes & (MACROBLOCK_INTRA | MACROBLOCK_PATTERN)) + { + macroblock_modes |= UBITS (bit_buf, 1) * DCT_TYPE_INTERLACED; + DUMPBITS (bit_buf, bits, 1); + } + + return macroblock_modes | MACROBLOCK_MOTION_FORWARD; + } + + case B_TYPE: + tab = MB_B + UBITS (bit_buf, 6); + DUMPBITS (bit_buf, bits, tab->len); + macroblock_modes = tab->modes; + + if (decoder->picture_structure != FRAME_PICTURE) + { + if (! (macroblock_modes & MACROBLOCK_INTRA)) + { + macroblock_modes |= UBITS (bit_buf, 2) << MOTION_TYPE_SHIFT; + DUMPBITS (bit_buf, bits, 2); + } + + return macroblock_modes; + } + else if (decoder->frame_pred_frame_dct) + { + /* if (! (macroblock_modes & MACROBLOCK_INTRA)) */ + macroblock_modes |= MC_FRAME << MOTION_TYPE_SHIFT; + return macroblock_modes; + } + else + { + if (macroblock_modes & MACROBLOCK_INTRA) + goto intra; + + macroblock_modes |= UBITS (bit_buf, 2) << MOTION_TYPE_SHIFT; + DUMPBITS (bit_buf, bits, 2); + + if (macroblock_modes & (MACROBLOCK_INTRA | MACROBLOCK_PATTERN)) + { + intra: + macroblock_modes |= UBITS (bit_buf, 1) * DCT_TYPE_INTERLACED; + DUMPBITS (bit_buf, bits, 1); + } + return macroblock_modes; + } + + case D_TYPE: + DUMPBITS (bit_buf, bits, 1); + return MACROBLOCK_INTRA; + + default: + return 0; + } +#undef bit_buf +#undef bits +#undef bit_ptr +} + +static inline void get_quantizer_scale (mpeg2_decoder_t * const decoder) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + + int quantizer_scale_code; + + quantizer_scale_code = UBITS (bit_buf, 5); + DUMPBITS (bit_buf, bits, 5); + + decoder->quantizer_matrix[0] = + decoder->quantizer_prescale[0][quantizer_scale_code]; + + decoder->quantizer_matrix[1] = + decoder->quantizer_prescale[1][quantizer_scale_code]; + + decoder->quantizer_matrix[2] = + decoder->chroma_quantizer[0][quantizer_scale_code]; + + decoder->quantizer_matrix[3] = + decoder->chroma_quantizer[1][quantizer_scale_code]; +#undef bit_buf +#undef bits +#undef bit_ptr +} + +static inline int get_motion_delta (mpeg2_decoder_t * const decoder, + const int f_code) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + + int delta; + int sign; + const MVtab * tab; + + if (bit_buf & 0x80000000) + { + DUMPBITS (bit_buf, bits, 1); + return 0; + } + else if (bit_buf >= 0x0c000000) + { + tab = MV_4 + UBITS (bit_buf, 4); + delta = (tab->delta << f_code) + 1; + bits += tab->len + f_code + 1; + bit_buf <<= tab->len; + + sign = SBITS (bit_buf, 1); + bit_buf <<= 1; + + if (f_code) + delta += UBITS (bit_buf, f_code); + bit_buf <<= f_code; + + return (delta ^ sign) - sign; + } + else + { + tab = MV_10 + UBITS (bit_buf, 10); + delta = (tab->delta << f_code) + 1; + bits += tab->len + 1; + bit_buf <<= tab->len; + + sign = SBITS (bit_buf, 1); + bit_buf <<= 1; + + if (f_code) + { + NEEDBITS (bit_buf, bits, bit_ptr); + delta += UBITS (bit_buf, f_code); + DUMPBITS (bit_buf, bits, f_code); + } + + return (delta ^ sign) - sign; + + } +#undef bit_buf +#undef bits +#undef bit_ptr +} + +static inline int bound_motion_vector (const int vector, const int f_code) +{ + return ((int32_t)vector << (27 - f_code)) >> (27 - f_code); +} + +static inline int get_dmv (mpeg2_decoder_t * const decoder) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + + const DMVtab * tab; + + tab = DMV_2 + UBITS (bit_buf, 2); + DUMPBITS (bit_buf, bits, tab->len); + return tab->dmv; + +#undef bit_buf +#undef bits +#undef bit_ptr +} + +static inline int get_coded_block_pattern (mpeg2_decoder_t * const decoder) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + + const CBPtab * tab; + + NEEDBITS (bit_buf, bits, bit_ptr); + + if (bit_buf >= 0x20000000) + { + tab = CBP_7 + (UBITS (bit_buf, 7) - 16); + DUMPBITS (bit_buf, bits, tab->len); + return tab->cbp; + } + else + { + tab = CBP_9 + UBITS (bit_buf, 9); + DUMPBITS (bit_buf, bits, tab->len); + return tab->cbp; + } + +#undef bit_buf +#undef bits +#undef bit_ptr +} + +static inline int get_luma_dc_dct_diff (mpeg2_decoder_t * const decoder) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + + const DCtab * tab; + int size; + int dc_diff; + + if (bit_buf < 0xf8000000) + { + tab = DC_lum_5 + UBITS (bit_buf, 5); + size = tab->size; + + if (size) + { + bits += tab->len + size; + bit_buf <<= tab->len; + dc_diff = + UBITS (bit_buf, size) - UBITS (SBITS (~bit_buf, 1), size); + bit_buf <<= size; + return dc_diff << decoder->intra_dc_precision; + } + else + { + DUMPBITS (bit_buf, bits, 3); + return 0; + } + } + else + { + tab = DC_long + (UBITS (bit_buf, 9) - 0x1e0); + size = tab->size; + DUMPBITS (bit_buf, bits, tab->len); + NEEDBITS (bit_buf, bits, bit_ptr); + dc_diff = UBITS (bit_buf, size) - UBITS (SBITS (~bit_buf, 1), size); + DUMPBITS (bit_buf, bits, size); + return dc_diff << decoder->intra_dc_precision; + } + +#undef bit_buf +#undef bits +#undef bit_ptr +} + +#if MPEG2_COLOR +static inline int get_chroma_dc_dct_diff (mpeg2_decoder_t * const decoder) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + + const DCtab * tab; + int size; + int dc_diff; + + if (bit_buf < 0xf8000000) + { + tab = DC_chrom_5 + UBITS (bit_buf, 5); + size = tab->size; + + if (size) + { + bits += tab->len + size; + bit_buf <<= tab->len; + dc_diff = + UBITS (bit_buf, size) - UBITS (SBITS (~bit_buf, 1), size); + bit_buf <<= size; + return dc_diff << decoder->intra_dc_precision; + } + else + { + DUMPBITS (bit_buf, bits, 2); + return 0; + } + } + else + { + tab = DC_long + (UBITS (bit_buf, 10) - 0x3e0); + size = tab->size; + DUMPBITS (bit_buf, bits, tab->len + 1); + NEEDBITS (bit_buf, bits, bit_ptr); + dc_diff = UBITS (bit_buf, size) - UBITS (SBITS (~bit_buf, 1), size); + DUMPBITS (bit_buf, bits, size); + return dc_diff << decoder->intra_dc_precision; + } + +#undef bit_buf +#undef bits +#undef bit_ptr +} +#endif /* MPEG2_COLOR */ + +#define SATURATE(val) \ + do { \ + val <<= 4; \ + if (unlikely (val != (int16_t) val)) \ + val = (SBITS (val, 1) ^ 2047) << 4; \ + } while (0) + +static void get_intra_block_B14 (mpeg2_decoder_t * const decoder, + const uint16_t * const quant_matrix) +{ + uint32_t bit_buf = decoder->bitstream_buf; + int bits = decoder->bitstream_bits; + const uint8_t * bit_ptr = decoder->bitstream_ptr; + const uint8_t * const scan = decoder->scan; + int16_t * const dest = decoder->DCTblock; + int mismatch = ~dest[0]; + int i = 0; + int j; + int val; + const DCTtab * tab; + + NEEDBITS (bit_buf, bits, bit_ptr); + + while (1) + { + if (bit_buf >= 0x28000000) + { + tab = DCT_B14AC_5 + (UBITS (bit_buf, 5) - 5); + + i += tab->run; + if (i >= 64) + break; /* end of block */ + + normal_code: + j = scan[i]; + bit_buf <<= tab->len; + bits += tab->len + 1; + val = (tab->level * quant_matrix[j]) >> 4; + + /* if (bitstream_get (1)) val = -val; */ + val = (val ^ SBITS (bit_buf, 1)) - SBITS (bit_buf, 1); + + SATURATE (val); + dest[j] = val; + mismatch ^= val; + + bit_buf <<= 1; + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + } + else if (bit_buf >= 0x04000000) + { + tab = DCT_B14_8 + (UBITS (bit_buf, 8) - 4); + + i += tab->run; + if (i < 64) + goto normal_code; + + /* escape code */ + + i += UBITS (bit_buf << 6, 6) - 64; + if (i >= 64) + break; /* illegal, check needed to avoid buffer overflow */ + + j = scan[i]; + + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + val = (SBITS (bit_buf, 12) * quant_matrix[j]) / 16; + + SATURATE (val); + dest[j] = val; + mismatch ^= val; + + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + } + else if (bit_buf >= 0x02000000) + { + tab = DCT_B14_10 + (UBITS (bit_buf, 10) - 8); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00800000) + { + tab = DCT_13 + (UBITS (bit_buf, 13) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00200000) + { + tab = DCT_15 + (UBITS (bit_buf, 15) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else + { + tab = DCT_16 + UBITS (bit_buf, 16); + bit_buf <<= 16; + GETWORD (bit_buf, bits + 16, bit_ptr); + i += tab->run; + if (i < 64) + goto normal_code; + } + break; /* illegal, check needed to avoid buffer overflow */ + } + + dest[63] ^= mismatch & 16; + DUMPBITS (bit_buf, bits, tab->len); /* dump end of block code */ + decoder->bitstream_buf = bit_buf; + decoder->bitstream_bits = bits; + decoder->bitstream_ptr = bit_ptr; +} + +static void get_intra_block_B15 (mpeg2_decoder_t * const decoder, + const uint16_t * const quant_matrix) +{ + uint32_t bit_buf = decoder->bitstream_buf; + int bits = decoder->bitstream_bits; + const uint8_t * bit_ptr = decoder->bitstream_ptr; + const uint8_t * const scan = decoder->scan; + int16_t * const dest = decoder->DCTblock; + int mismatch = ~dest[0]; + int i = 0; + int j; + int val; + const DCTtab * tab; + + NEEDBITS (bit_buf, bits, bit_ptr); + + while (1) + { + if (bit_buf >= 0x04000000) + { + tab = DCT_B15_8 + (UBITS (bit_buf, 8) - 4); + + i += tab->run; + + if (i < 64) + { + normal_code: + j = scan[i]; + bit_buf <<= tab->len; + bits += tab->len + 1; + val = (tab->level * quant_matrix[j]) >> 4; + + /* if (bitstream_get (1)) val = -val; */ + val = (val ^ SBITS (bit_buf, 1)) - SBITS (bit_buf, 1); + + SATURATE (val); + dest[j] = val; + mismatch ^= val; + + bit_buf <<= 1; + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + } + else + { + /* end of block. I commented out this code because if we */ + /* dont exit here we will still exit at the later test :) */ + + /* if (i >= 128) break; */ /* end of block */ + + /* escape code */ + + i += UBITS (bit_buf << 6, 6) - 64; + if (i >= 64) + break; /* illegal, check against buffer overflow */ + + j = scan[i]; + + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + val = (SBITS (bit_buf, 12) * quant_matrix[j]) / 16; + + SATURATE (val); + dest[j] = val; + mismatch ^= val; + + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + } + } + else if (bit_buf >= 0x02000000) + { + tab = DCT_B15_10 + (UBITS (bit_buf, 10) - 8); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00800000) + { + tab = DCT_13 + (UBITS (bit_buf, 13) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00200000) + { + tab = DCT_15 + (UBITS (bit_buf, 15) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else + { + tab = DCT_16 + UBITS (bit_buf, 16); + bit_buf <<= 16; + GETWORD (bit_buf, bits + 16, bit_ptr); + i += tab->run; + if (i < 64) + goto normal_code; + } + break; /* illegal, check needed to avoid buffer overflow */ + } + + dest[63] ^= mismatch & 16; + DUMPBITS (bit_buf, bits, tab->len); /* dump end of block code */ + decoder->bitstream_buf = bit_buf; + decoder->bitstream_bits = bits; + decoder->bitstream_ptr = bit_ptr; +} + +static int get_non_intra_block (mpeg2_decoder_t * const decoder, + const uint16_t * const quant_matrix) +{ + uint32_t bit_buf = decoder->bitstream_buf; + int bits = decoder->bitstream_bits; + const uint8_t * bit_ptr = decoder->bitstream_ptr; + const uint8_t * const scan = decoder->scan; + int16_t * const dest = decoder->DCTblock; + int mismatch = -1; + int i = -1; + int j; + int val; + const DCTtab * tab; + + NEEDBITS (bit_buf, bits, bit_ptr); + + if (bit_buf >= 0x28000000) + { + tab = DCT_B14DC_5 + (UBITS (bit_buf, 5) - 5); + goto entry_1; + } + else + { + goto entry_2; + } + + while (1) + { + if (bit_buf >= 0x28000000) + { + tab = DCT_B14AC_5 + (UBITS (bit_buf, 5) - 5); + + entry_1: + i += tab->run; + if (i >= 64) + break; /* end of block */ + + normal_code: + j = scan[i]; + bit_buf <<= tab->len; + bits += tab->len + 1; + val = ((2 * tab->level + 1) * quant_matrix[j]) >> 5; + + /* if (bitstream_get (1)) val = -val; */ + val = (val ^ SBITS (bit_buf, 1)) - SBITS (bit_buf, 1); + + SATURATE (val); + dest[j] = val; + mismatch ^= val; + + bit_buf <<= 1; + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + } + + entry_2: + if (bit_buf >= 0x04000000) + { + tab = DCT_B14_8 + (UBITS (bit_buf, 8) - 4); + + i += tab->run; + if (i < 64) + goto normal_code; + + /* escape code */ + + i += UBITS (bit_buf << 6, 6) - 64; + if (i >= 64) + break; /* illegal, check needed to avoid buffer overflow */ + + j = scan[i]; + + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + val = 2 * (SBITS (bit_buf, 12) + SBITS (bit_buf, 1)) + 1; + val = (val * quant_matrix[j]) / 32; + + SATURATE (val); + dest[j] = val; + mismatch ^= val; + + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + } + else if (bit_buf >= 0x02000000) + { + tab = DCT_B14_10 + (UBITS (bit_buf, 10) - 8); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00800000) + { + tab = DCT_13 + (UBITS (bit_buf, 13) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00200000) + { + tab = DCT_15 + (UBITS (bit_buf, 15) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else + { + tab = DCT_16 + UBITS (bit_buf, 16); + bit_buf <<= 16; + GETWORD (bit_buf, bits + 16, bit_ptr); + i += tab->run; + if (i < 64) + goto normal_code; + } + break; /* illegal, check needed to avoid buffer overflow */ + } + + dest[63] ^= mismatch & 16; + DUMPBITS (bit_buf, bits, tab->len); /* dump end of block code */ + decoder->bitstream_buf = bit_buf; + decoder->bitstream_bits = bits; + decoder->bitstream_ptr = bit_ptr; + return i; +} + +static void get_mpeg1_intra_block (mpeg2_decoder_t * const decoder) +{ + uint32_t bit_buf = decoder->bitstream_buf; + int bits = decoder->bitstream_bits; + const uint8_t * bit_ptr = decoder->bitstream_ptr; + const uint8_t * const scan = decoder->scan; + const uint16_t * const quant_matrix = decoder->quantizer_matrix[0]; + int16_t * const dest = decoder->DCTblock; + int i = 0; + int j; + int val; + const DCTtab * tab; + + NEEDBITS (bit_buf, bits, bit_ptr); + + while (1) + { + if (bit_buf >= 0x28000000) + { + tab = DCT_B14AC_5 + (UBITS (bit_buf, 5) - 5); + + i += tab->run; + if (i >= 64) + break; /* end of block */ + + normal_code: + j = scan[i]; + bit_buf <<= tab->len; + bits += tab->len + 1; + val = (tab->level * quant_matrix[j]) >> 4; + + /* oddification */ + val = (val - 1) | 1; + + /* if (bitstream_get (1)) val = -val; */ + val = (val ^ SBITS (bit_buf, 1)) - SBITS (bit_buf, 1); + + SATURATE (val); + dest[j] = val; + + bit_buf <<= 1; + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + } + else if (bit_buf >= 0x04000000) + { + tab = DCT_B14_8 + (UBITS (bit_buf, 8) - 4); + + i += tab->run; + if (i < 64) + goto normal_code; + + /* escape code */ + + i += UBITS (bit_buf << 6, 6) - 64; + if (i >= 64) + break; /* illegal, check needed to avoid buffer overflow */ + + j = scan[i]; + + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + val = SBITS (bit_buf, 8); + + if (! (val & 0x7f)) + { + DUMPBITS (bit_buf, bits, 8); + val = UBITS (bit_buf, 8) + 2 * val; + } + + val = (val * quant_matrix[j]) / 16; + + /* oddification */ + val = (val + ~SBITS (val, 1)) | 1; + + SATURATE (val); + dest[j] = val; + + DUMPBITS (bit_buf, bits, 8); + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + } + else if (bit_buf >= 0x02000000) + { + tab = DCT_B14_10 + (UBITS (bit_buf, 10) - 8); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00800000) + { + tab = DCT_13 + (UBITS (bit_buf, 13) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00200000) + { + tab = DCT_15 + (UBITS (bit_buf, 15) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else + { + tab = DCT_16 + UBITS (bit_buf, 16); + bit_buf <<= 16; + GETWORD (bit_buf, bits + 16, bit_ptr); + i += tab->run; + if (i < 64) + goto normal_code; + } + break; /* illegal, check needed to avoid buffer overflow */ + } + + DUMPBITS (bit_buf, bits, tab->len); /* dump end of block code */ + decoder->bitstream_buf = bit_buf; + decoder->bitstream_bits = bits; + decoder->bitstream_ptr = bit_ptr; +} + +static int get_mpeg1_non_intra_block (mpeg2_decoder_t * const decoder) +{ + uint32_t bit_buf = decoder->bitstream_buf; + int bits = decoder->bitstream_bits; + const uint8_t * bit_ptr = decoder->bitstream_ptr; + const uint8_t * const scan = decoder->scan; + const uint16_t * const quant_matrix = decoder->quantizer_matrix[1]; + int16_t * const dest = decoder->DCTblock; + int i = -1; + int j; + int val; + const DCTtab * tab; + + NEEDBITS (bit_buf, bits, bit_ptr); + if (bit_buf >= 0x28000000) + { + tab = DCT_B14DC_5 + (UBITS (bit_buf, 5) - 5); + goto entry_1; + } + else + { + goto entry_2; + } + + while (1) + { + if (bit_buf >= 0x28000000) + { + tab = DCT_B14AC_5 + (UBITS (bit_buf, 5) - 5); + + entry_1: + i += tab->run; + if (i >= 64) + break; /* end of block */ + + normal_code: + j = scan[i]; + bit_buf <<= tab->len; + bits += tab->len + 1; + val = ((2 * tab->level + 1) * quant_matrix[j]) >> 5; + + /* oddification */ + val = (val - 1) | 1; + + /* if (bitstream_get (1)) val = -val; */ + val = (val ^ SBITS (bit_buf, 1)) - SBITS (bit_buf, 1); + + SATURATE (val); + dest[j] = val; + + bit_buf <<= 1; + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + } + + entry_2: + if (bit_buf >= 0x04000000) + { + tab = DCT_B14_8 + (UBITS (bit_buf, 8) - 4); + + i += tab->run; + if (i < 64) + goto normal_code; + + /* escape code */ + + i += UBITS (bit_buf << 6, 6) - 64; + if (i >= 64) + break; /* illegal, check needed to avoid buffer overflow */ + + j = scan[i]; + + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + val = SBITS (bit_buf, 8); + + if (! (val & 0x7f)) + { + DUMPBITS (bit_buf, bits, 8); + val = UBITS (bit_buf, 8) + 2 * val; + } + + val = 2 * (val + SBITS (val, 1)) + 1; + val = (val * quant_matrix[j]) / 32; + + /* oddification */ + val = (val + ~SBITS (val, 1)) | 1; + + SATURATE (val); + dest[j] = val; + + DUMPBITS (bit_buf, bits, 8); + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + + } + else if (bit_buf >= 0x02000000) + { + tab = DCT_B14_10 + (UBITS (bit_buf, 10) - 8); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00800000) + { + tab = DCT_13 + (UBITS (bit_buf, 13) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00200000) + { + tab = DCT_15 + (UBITS (bit_buf, 15) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else + { + tab = DCT_16 + UBITS (bit_buf, 16); + bit_buf <<= 16; + GETWORD (bit_buf, bits + 16, bit_ptr); + i += tab->run; + if (i < 64) + goto normal_code; + } + break; /* illegal, check needed to avoid buffer overflow */ + } + + DUMPBITS (bit_buf, bits, tab->len); /* dump end of block code */ + decoder->bitstream_buf = bit_buf; + decoder->bitstream_bits = bits; + decoder->bitstream_ptr = bit_ptr; + return i; +} + +static inline void slice_intra_DCT (mpeg2_decoder_t * const decoder, + const int cc, + uint8_t * const dest, const int stride) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + + NEEDBITS (bit_buf, bits, bit_ptr); + /* Get the intra DC coefficient and inverse quantize it */ + if (cc == 0) + { + decoder->dc_dct_pred[0] += get_luma_dc_dct_diff (decoder); + decoder->DCTblock[0] = decoder->dc_dct_pred[0]; + + } +#if MPEG2_COLOR + else + { + decoder->dc_dct_pred[cc] += get_chroma_dc_dct_diff (decoder); + decoder->DCTblock[0] = decoder->dc_dct_pred[cc]; + } +#endif + + if (decoder->mpeg1) + { + if (decoder->coding_type != D_TYPE) + get_mpeg1_intra_block (decoder); + } + else if (decoder->intra_vlc_format) + { + get_intra_block_B15 (decoder, decoder->quantizer_matrix[cc ? 2 : 0]); + } + else + { + get_intra_block_B14 (decoder, decoder->quantizer_matrix[cc ? 2 : 0]); + } + + mpeg2_idct_copy (decoder->DCTblock, dest, stride); + +#undef bit_buf +#undef bits +#undef bit_ptr +} + +static inline void slice_non_intra_DCT (mpeg2_decoder_t * const decoder, + const int cc, + uint8_t * const dest, const int stride) +{ + int last; + + if (decoder->mpeg1) + { + last = get_mpeg1_non_intra_block (decoder); + } + else + { + last = get_non_intra_block (decoder, + decoder->quantizer_matrix[cc ? 3 : 1]); + } + + mpeg2_idct_add (last, decoder->DCTblock, dest, stride); +} + +#if !MPEG2_COLOR +static void skip_mpeg1_intra_block (mpeg2_decoder_t * const decoder) +{ + uint32_t bit_buf = decoder->bitstream_buf; + int bits = decoder->bitstream_bits; + const uint8_t * bit_ptr = decoder->bitstream_ptr; + int i = 0; + const DCTtab * tab; + + NEEDBITS (bit_buf, bits, bit_ptr); + + while (1) + { + if (bit_buf >= 0x28000000) + { + tab = DCT_B14AC_5 + (UBITS (bit_buf, 5) - 5); + + i += tab->run; + if (i >= 64) + break; /* end of block */ + + normal_code: + bit_buf <<= tab->len + 1; + bits += tab->len + 1; + NEEDBITS (bit_buf, bits, bit_ptr); + continue; + } + else if (bit_buf >= 0x04000000) + { + tab = DCT_B14_8 + (UBITS (bit_buf, 8) - 4); + + i += tab->run; + if (i < 64) + goto normal_code; + + /* escape code */ + + i += UBITS (bit_buf << 6, 6) - 64; + if (i >= 64) + break; /* illegal, check needed to avoid buffer overflow */ + + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + + if (!(SBITS (bit_buf, 8) & 0x7f)) + DUMPBITS (bit_buf, bits, 8); + + DUMPBITS (bit_buf, bits, 8); + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + } + else if (bit_buf >= 0x02000000) + { + tab = DCT_B14_10 + (UBITS (bit_buf, 10) - 8); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00800000) + { + tab = DCT_13 + (UBITS (bit_buf, 13) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00200000) + { + tab = DCT_15 + (UBITS (bit_buf, 15) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else + { + tab = DCT_16 + UBITS (bit_buf, 16); + bit_buf <<= 16; + GETWORD (bit_buf, bits + 16, bit_ptr); + i += tab->run; + if (i < 64) + goto normal_code; + } + break; /* illegal, check needed to avoid buffer overflow */ + } + + DUMPBITS (bit_buf, bits, 2); /* dump end of block code */ + decoder->bitstream_buf = bit_buf; + decoder->bitstream_bits = bits; + decoder->bitstream_ptr = bit_ptr; +} + +static void skip_intra_block_B14 (mpeg2_decoder_t * const decoder) +{ + uint32_t bit_buf = decoder->bitstream_buf; + int bits = decoder->bitstream_bits; + const uint8_t * bit_ptr = decoder->bitstream_ptr; + int i = 0; + const DCTtab * tab; + + NEEDBITS (bit_buf, bits, bit_ptr); + + while (1) + { + if (bit_buf >= 0x28000000) + { + tab = DCT_B14AC_5 + (UBITS (bit_buf, 5) - 5); + + i += tab->run; + if (i >= 64) + break; /* end of block */ + + normal_code: + bit_buf <<= tab->len + 1; + bits += tab->len + 1; + NEEDBITS (bit_buf, bits, bit_ptr); + continue; + } + else if (bit_buf >= 0x04000000) + { + tab = DCT_B14_8 + (UBITS (bit_buf, 8) - 4); + + i += tab->run; + if (i < 64) + goto normal_code; + + /* escape code */ + + i += UBITS (bit_buf << 6, 6) - 64; + if (i >= 64) + break; /* illegal, check needed to avoid buffer overflow */ + + DUMPBITS (bit_buf, bits, 12); /* Can't dump more than 16 atm */ + NEEDBITS (bit_buf, bits, bit_ptr); + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + continue; + } + else if (bit_buf >= 0x02000000) + { + tab = DCT_B14_10 + (UBITS (bit_buf, 10) - 8); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00800000) + { + tab = DCT_13 + (UBITS (bit_buf, 13) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00200000) + { + tab = DCT_15 + (UBITS (bit_buf, 15) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else + { + tab = DCT_16 + UBITS (bit_buf, 16); + bit_buf <<= 16; + GETWORD (bit_buf, bits + 16, bit_ptr); + i += tab->run; + if (i < 64) + goto normal_code; + } + break; /* illegal, check needed to avoid buffer overflow */ + } + + DUMPBITS (bit_buf, bits, 2); /* dump end of block code */ + decoder->bitstream_buf = bit_buf; + decoder->bitstream_bits = bits; + decoder->bitstream_ptr = bit_ptr; +} + +static void skip_intra_block_B15 (mpeg2_decoder_t * const decoder) +{ + uint32_t bit_buf = decoder->bitstream_buf; + int bits = decoder->bitstream_bits; + const uint8_t * bit_ptr = decoder->bitstream_ptr; + int i = 0; + const DCTtab * tab; + + NEEDBITS (bit_buf, bits, bit_ptr); + + while (1) + { + if (bit_buf >= 0x04000000) + { + tab = DCT_B15_8 + (UBITS (bit_buf, 8) - 4); + + i += tab->run; + + if (i < 64) + { + normal_code: + bit_buf <<= tab->len + 1; + bits += tab->len + 1; + NEEDBITS (bit_buf, bits, bit_ptr); + continue; + } + else + { + /* end of block. I commented out this code because if we */ + /* dont exit here we will still exit at the later test :) */ + + /* if (i >= 128) break; */ /* end of block */ + + /* escape code */ + + i += UBITS (bit_buf << 6, 6) - 64; + if (i >= 64) + break; /* illegal, check against buffer overflow */ + + DUMPBITS (bit_buf, bits, 12); /* Can't dump more than 16 atm */ + NEEDBITS (bit_buf, bits, bit_ptr); + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + continue; + } + } + else if (bit_buf >= 0x02000000) + { + tab = DCT_B15_10 + (UBITS (bit_buf, 10) - 8); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00800000) + { + tab = DCT_13 + (UBITS (bit_buf, 13) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00200000) + { + tab = DCT_15 + (UBITS (bit_buf, 15) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else + { + tab = DCT_16 + UBITS (bit_buf, 16); + bit_buf <<= 16; + GETWORD (bit_buf, bits + 16, bit_ptr); + i += tab->run; + if (i < 64) + goto normal_code; + } + break; /* illegal, check needed to avoid buffer overflow */ + } + + DUMPBITS (bit_buf, bits, 4); /* dump end of block code */ + decoder->bitstream_buf = bit_buf; + decoder->bitstream_bits = bits; + decoder->bitstream_ptr = bit_ptr; +} + +static void skip_non_intra_block (mpeg2_decoder_t * const decoder) +{ + uint32_t bit_buf = decoder->bitstream_buf; + int bits = decoder->bitstream_bits; + const uint8_t * bit_ptr = decoder->bitstream_ptr; + int i = -1; + const DCTtab * tab; + + NEEDBITS (bit_buf, bits, bit_ptr); + + if (bit_buf >= 0x28000000) + { + tab = DCT_B14DC_5 + (UBITS (bit_buf, 5) - 5); + goto entry_1; + } + else + { + goto entry_2; + } + + while (1) + { + if (bit_buf >= 0x28000000) + { + tab = DCT_B14AC_5 + (UBITS (bit_buf, 5) - 5); + + entry_1: + i += tab->run; + if (i >= 64) + break; /* end of block */ + + normal_code: + bit_buf <<= tab->len + 1; + bits += tab->len + 1; + NEEDBITS (bit_buf, bits, bit_ptr); + + continue; + } + + entry_2: + if (bit_buf >= 0x04000000) + { + tab = DCT_B14_8 + (UBITS (bit_buf, 8) - 4); + + i += tab->run; + if (i < 64) + goto normal_code; + + /* escape code */ + + i += UBITS (bit_buf << 6, 6) - 64; + if (i >= 64) + break; /* illegal, check needed to avoid buffer overflow */ + + if (decoder->mpeg1) + { + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + + if (!(SBITS (bit_buf, 8) & 0x7f)) + DUMPBITS (bit_buf, bits, 8); + + DUMPBITS (bit_buf, bits, 8); + } + else + { + DUMPBITS (bit_buf, bits, 12); + NEEDBITS (bit_buf, bits, bit_ptr); + DUMPBITS (bit_buf, bits, 12); + } + + NEEDBITS (bit_buf, bits, bit_ptr); + continue; + } + else if (bit_buf >= 0x02000000) + { + tab = DCT_B14_10 + (UBITS (bit_buf, 10) - 8); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00800000) + { + tab = DCT_13 + (UBITS (bit_buf, 13) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else if (bit_buf >= 0x00200000) + { + tab = DCT_15 + (UBITS (bit_buf, 15) - 16); + i += tab->run; + if (i < 64) + goto normal_code; + } + else + { + tab = DCT_16 + UBITS (bit_buf, 16); + bit_buf <<= 16; + GETWORD (bit_buf, bits + 16, bit_ptr); + i += tab->run; + if (i < 64) + goto normal_code; + } + break; /* illegal, check needed to avoid buffer overflow */ + } + + DUMPBITS (bit_buf, bits, 2); /* dump end of block code */ + decoder->bitstream_buf = bit_buf; + decoder->bitstream_bits = bits; + decoder->bitstream_ptr = bit_ptr; +} + +static void skip_chroma_dc_dct_diff (mpeg2_decoder_t * const decoder) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + + const DCtab * tab; + int size; + + if (bit_buf < 0xf8000000) + { + tab = DC_chrom_5 + UBITS (bit_buf, 5); + size = tab->size; + + if (size) + { + bits += tab->len + size; + bit_buf <<= tab->len; + bit_buf <<= size; + } + else + { + DUMPBITS (bit_buf, bits, 2); + } + } + else + { + tab = DC_long + (UBITS (bit_buf, 10) - 0x3e0); + size = tab->size; + DUMPBITS (bit_buf, bits, tab->len + 1); + NEEDBITS (bit_buf, bits, bit_ptr); + DUMPBITS (bit_buf, bits, size); + } + +#undef bit_buf +#undef bits +#undef bit_ptr +} + +static void skip_chroma_non_intra (mpeg2_decoder_t * const decoder, + uint32_t coded_block_pattern) +{ + static const uint32_t cbp_mask[3] = + { + 0x00000030, + 0xc0000030, + 0xfc000030, + }; + + uint32_t cbp = coded_block_pattern & + cbp_mask[MIN((unsigned)decoder->chroma_format, 2u)]; + + while (cbp) + { + skip_non_intra_block (decoder); + cbp &= (cbp - 1); + } +} + +static void skip_chroma_intra (mpeg2_decoder_t * const decoder) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + int i = 2 << decoder->chroma_format; + + if ((unsigned)i > 8) + i = 8; + + while (i-- > 0) + { + NEEDBITS (bit_buf, bits, bit_ptr); + + skip_chroma_dc_dct_diff (decoder); + + if (decoder->mpeg1) + { + if (decoder->coding_type != D_TYPE) + skip_mpeg1_intra_block (decoder); + } + else if (decoder->intra_vlc_format) + { + skip_intra_block_B15 (decoder); + } + else + { + skip_intra_block_B14 (decoder); + } + } + + if (decoder->chroma_format == 0 && decoder->coding_type == D_TYPE) + { + NEEDBITS (bit_buf, bits, bit_ptr); + DUMPBITS (bit_buf, bits, 1); + } + +#undef bit_buf +#undef bits +#undef bit_ptr +} +#endif /* !MPEG2_COLOR */ + +#define MOTION_420(table, ref, motion_x, motion_y, size, y) \ + pos_x = 2 * decoder->offset + motion_x; \ + pos_y = 2 * decoder->v_offset + motion_y + 2 * y; \ + \ + if (unlikely (pos_x > decoder->limit_x)) \ + { \ + pos_x = ((int)pos_x < 0) ? 0 : decoder->limit_x; \ + motion_x = pos_x - 2 * decoder->offset; \ + } \ + \ + if (unlikely (pos_y > decoder->limit_y_ ## size)) \ + { \ + pos_y = ((int)pos_y < 0) ? 0 : decoder->limit_y_ ## size; \ + motion_y = pos_y - 2 * decoder->v_offset - 2 * y; \ + } \ + \ + xy_half = ((pos_y & 1) << 1) | (pos_x & 1); \ + table[xy_half] (decoder->dest[0] + y * decoder->stride + decoder->offset, \ + ref[0] + (pos_x >> 1) + (pos_y >> 1) * decoder->stride, \ + decoder->stride, size); \ + \ + if (MPEG2_COLOR) \ + { \ + motion_x /= 2; \ + motion_y /= 2; \ + xy_half = ((motion_y & 1) << 1) | (motion_x & 1); \ + offset = ((decoder->offset + motion_x) >> 1) + \ + ((((decoder->v_offset + motion_y) >> 1) + y/2) * \ + decoder->uv_stride); \ + \ + table[4+xy_half] (decoder->dest[1] + y/2 * decoder->uv_stride + \ + (decoder->offset >> 1), ref[1] + offset, \ + decoder->uv_stride, size/2); \ + table[4+xy_half] (decoder->dest[2] + y/2 * decoder->uv_stride + \ + (decoder->offset >> 1), ref[2] + offset, \ + decoder->uv_stride, size/2); \ + } + +#define MOTION_FIELD_420(table, ref, motion_x, motion_y, \ + dest_field, op, src_field) \ + pos_x = 2 * decoder->offset + motion_x; \ + pos_y = decoder->v_offset + motion_y; \ + \ + if (unlikely (pos_x > decoder->limit_x)) \ + { \ + pos_x = ((int)pos_x < 0) ? 0 : decoder->limit_x; \ + motion_x = pos_x - 2 * decoder->offset; \ + } \ + \ + if (unlikely (pos_y > decoder->limit_y)) \ + { \ + pos_y = ((int)pos_y < 0) ? 0 : decoder->limit_y; \ + motion_y = pos_y - decoder->v_offset; \ + } \ + \ + xy_half = ((pos_y & 1) << 1) | (pos_x & 1); \ + table[xy_half] (decoder->dest[0] + dest_field * decoder->stride + \ + decoder->offset, \ + (ref[0] + (pos_x >> 1) + \ + ((pos_y op) + src_field) * decoder->stride), \ + 2 * decoder->stride, 8); \ + \ + if (MPEG2_COLOR) \ + { \ + motion_x /= 2; \ + motion_y /= 2; \ + xy_half = ((motion_y & 1) << 1) | (motion_x & 1); \ + offset = ((decoder->offset + motion_x) >> 1) + \ + (((decoder->v_offset >> 1) + (motion_y op) + src_field) * \ + decoder->uv_stride); \ + \ + table[4+xy_half] (decoder->dest[1] + dest_field * decoder->uv_stride + \ + (decoder->offset >> 1), ref[1] + offset, \ + 2 * decoder->uv_stride, 4); \ + table[4+xy_half] (decoder->dest[2] + dest_field * decoder->uv_stride + \ + (decoder->offset >> 1), ref[2] + offset, \ + 2 * decoder->uv_stride, 4); \ + } + +#define MOTION_DMV_420(table, ref, motion_x, motion_y) \ + pos_x = 2 * decoder->offset + motion_x; \ + pos_y = decoder->v_offset + motion_y; \ + \ + if (unlikely (pos_x > decoder->limit_x)) \ + { \ + pos_x = ((int)pos_x < 0) ? 0 : decoder->limit_x; \ + motion_x = pos_x - 2 * decoder->offset; \ + } \ + \ + if (unlikely (pos_y > decoder->limit_y)) \ + { \ + pos_y = ((int)pos_y < 0) ? 0 : decoder->limit_y; \ + motion_y = pos_y - decoder->v_offset; \ + } \ + \ + xy_half = ((pos_y & 1) << 1) | (pos_x & 1); \ + offset = (pos_x >> 1) + (pos_y & ~1) * decoder->stride; \ + table[xy_half] (decoder->dest[0] + decoder->offset, \ + ref[0] + offset, 2 * decoder->stride, 8); \ + table[xy_half] (decoder->dest[0] + decoder->stride + decoder->offset, \ + ref[0] + decoder->stride + offset, \ + 2 * decoder->stride, 8); \ + \ + if (MPEG2_COLOR) \ + { \ + motion_x /= 2; \ + motion_y /= 2; \ + xy_half = ((motion_y & 1) << 1) | (motion_x & 1); \ + offset = ((decoder->offset + motion_x) >> 1) + \ + (((decoder->v_offset >> 1) + (motion_y & ~1)) * \ + decoder->uv_stride); \ + \ + table[4+xy_half] (decoder->dest[1] + (decoder->offset >> 1), \ + ref[1] + offset, 2 * decoder->uv_stride, 4); \ + table[4+xy_half] (decoder->dest[1] + decoder->uv_stride + \ + (decoder->offset >> 1), \ + ref[1] + decoder->uv_stride + offset, \ + 2 * decoder->uv_stride, 4); \ + table[4+xy_half] (decoder->dest[2] + (decoder->offset >> 1), \ + ref[2] + offset, 2 * decoder->uv_stride, 4); \ + table[4+xy_half] (decoder->dest[2] + decoder->uv_stride + \ + (decoder->offset >> 1), \ + ref[2] + decoder->uv_stride + offset, \ + 2 * decoder->uv_stride, 4); \ + } + +#define MOTION_ZERO_420(table, ref) \ + table[0] (decoder->dest[0] + decoder->offset, \ + (ref[0] + decoder->offset + \ + decoder->v_offset * decoder->stride), decoder->stride, 16); \ + \ + if (MPEG2_COLOR) \ + { \ + offset = ((decoder->offset >> 1) + \ + (decoder->v_offset >> 1) * decoder->uv_stride); \ + \ + table[4] (decoder->dest[1] + (decoder->offset >> 1), \ + ref[1] + offset, decoder->uv_stride, 8); \ + table[4] (decoder->dest[2] + (decoder->offset >> 1), \ + ref[2] + offset, decoder->uv_stride, 8); \ + } + +#define MOTION_422(table, ref, motion_x, motion_y, size, y) \ + pos_x = 2 * decoder->offset + motion_x; \ + pos_y = 2 * decoder->v_offset + motion_y + 2 * y; \ + \ + if (unlikely (pos_x > decoder->limit_x)) \ + { \ + pos_x = ((int)pos_x < 0) ? 0 : decoder->limit_x; \ + motion_x = pos_x - 2 * decoder->offset; \ + } \ + \ + if (unlikely (pos_y > decoder->limit_y_ ## size)) \ + { \ + pos_y = ((int)pos_y < 0) ? 0 : decoder->limit_y_ ## size; \ + motion_y = pos_y - 2 * decoder->v_offset - 2 * y; \ + } \ + \ + xy_half = ((pos_y & 1) << 1) | (pos_x & 1); \ + offset = (pos_x >> 1) + (pos_y >> 1) * decoder->stride; \ + \ + table[xy_half] (decoder->dest[0] + y * decoder->stride + decoder->offset, \ + ref[0] + offset, decoder->stride, size); \ + \ + if (MPEG2_COLOR) \ + { \ + offset = (offset + (motion_x & (motion_x < 0))) >> 1; \ + motion_x /= 2; \ + xy_half = ((pos_y & 1) << 1) | (motion_x & 1); \ + \ + table[4+xy_half] (decoder->dest[1] + y * decoder->uv_stride + \ + (decoder->offset >> 1), ref[1] + offset, \ + decoder->uv_stride, size); \ + table[4+xy_half] (decoder->dest[2] + y * decoder->uv_stride + \ + (decoder->offset >> 1), ref[2] + offset, \ + decoder->uv_stride, size); \ + } + +#define MOTION_FIELD_422(table, ref, motion_x, motion_y, \ + dest_field, op, src_field) \ + pos_x = 2 * decoder->offset + motion_x; \ + pos_y = decoder->v_offset + motion_y; \ + \ + if (unlikely (pos_x > decoder->limit_x)) \ + { \ + pos_x = ((int)pos_x < 0) ? 0 : decoder->limit_x; \ + motion_x = pos_x - 2 * decoder->offset; \ + } \ + \ + if (unlikely (pos_y > decoder->limit_y)) \ + { \ + pos_y = ((int)pos_y < 0) ? 0 : decoder->limit_y; \ + motion_y = pos_y - decoder->v_offset; \ + } \ + \ + xy_half = ((pos_y & 1) << 1) | (pos_x & 1); \ + offset = (pos_x >> 1) + ((pos_y op) + src_field) * decoder->stride; \ + \ + table[xy_half] (decoder->dest[0] + dest_field * decoder->stride + \ + decoder->offset, ref[0] + offset, \ + 2 * decoder->stride, 8); \ + \ + if (MPEG2_COLOR) \ + { \ + offset = (offset + (motion_x & (motion_x < 0))) >> 1; \ + motion_x /= 2; \ + xy_half = ((pos_y & 1) << 1) | (motion_x & 1); \ + \ + table[4+xy_half] (decoder->dest[1] + dest_field * decoder->uv_stride + \ + (decoder->offset >> 1), ref[1] + offset, \ + 2 * decoder->uv_stride, 8); \ + table[4+xy_half] (decoder->dest[2] + dest_field * decoder->uv_stride + \ + (decoder->offset >> 1), ref[2] + offset, \ + 2 * decoder->uv_stride, 8); \ + } + +#define MOTION_DMV_422(table, ref, motion_x, motion_y) \ + pos_x = 2 * decoder->offset + motion_x; \ + pos_y = decoder->v_offset + motion_y; \ + \ + if (unlikely (pos_x > decoder->limit_x)) \ + { \ + pos_x = ((int)pos_x < 0) ? 0 : decoder->limit_x; \ + motion_x = pos_x - 2 * decoder->offset; \ + } \ + \ + if (unlikely (pos_y > decoder->limit_y)) \ + { \ + pos_y = ((int)pos_y < 0) ? 0 : decoder->limit_y; \ + motion_y = pos_y - decoder->v_offset; \ + } \ + \ + xy_half = ((pos_y & 1) << 1) | (pos_x & 1); \ + offset = (pos_x >> 1) + (pos_y & ~1) * decoder->stride; \ + \ + table[xy_half] (decoder->dest[0] + decoder->offset, \ + ref[0] + offset, 2 * decoder->stride, 8); \ + table[xy_half] (decoder->dest[0] + decoder->stride + decoder->offset, \ + ref[0] + decoder->stride + offset, \ + 2 * decoder->stride, 8); \ + \ + if (MPEG2_COLOR) \ + { \ + offset = (offset + (motion_x & (motion_x < 0))) >> 1; \ + motion_x /= 2; \ + xy_half = ((pos_y & 1) << 1) | (motion_x & 1); \ + \ + table[4+xy_half] (decoder->dest[1] + (decoder->offset >> 1), \ + ref[1] + offset, 2 * decoder->uv_stride, 8); \ + table[4+xy_half] (decoder->dest[1] + decoder->uv_stride + \ + (decoder->offset >> 1), \ + ref[1] + decoder->uv_stride + offset, \ + 2 * decoder->uv_stride, 8); \ + table[4+xy_half] (decoder->dest[2] + (decoder->offset >> 1), \ + ref[2] + offset, 2 * decoder->uv_stride, 8); \ + table[4+xy_half] (decoder->dest[2] + decoder->uv_stride + \ + (decoder->offset >> 1), \ + ref[2] + decoder->uv_stride + offset, \ + 2 * decoder->uv_stride, 8); \ + } + +#define MOTION_ZERO_422(table, ref) \ + offset = decoder->offset + decoder->v_offset * decoder->stride; \ + table[0] (decoder->dest[0] + decoder->offset, \ + ref[0] + offset, decoder->stride, 16); \ + \ + if (MPEG2_COLOR) \ + { \ + offset >>= 1; \ + table[4] (decoder->dest[1] + (decoder->offset >> 1), \ + ref[1] + offset, decoder->uv_stride, 16); \ + table[4] (decoder->dest[2] + (decoder->offset >> 1), \ + ref[2] + offset, decoder->uv_stride, 16); \ + } + +#define MOTION_444(table, ref, motion_x, motion_y, size, y) \ + pos_x = 2 * decoder->offset + motion_x; \ + pos_y = 2 * decoder->v_offset + motion_y + 2 * y; \ + \ + if (unlikely (pos_x > decoder->limit_x)) \ + { \ + pos_x = ((int)pos_x < 0) ? 0 : decoder->limit_x; \ + motion_x = pos_x - 2 * decoder->offset; \ + } \ + \ + if (unlikely (pos_y > decoder->limit_y_ ## size)) \ + { \ + pos_y = ((int)pos_y < 0) ? 0 : decoder->limit_y_ ## size; \ + motion_y = pos_y - 2 * decoder->v_offset - 2 * y; \ + } \ + \ + xy_half = ((pos_y & 1) << 1) | (pos_x & 1); \ + offset = (pos_x >> 1) + (pos_y >> 1) * decoder->stride; \ + \ + table[xy_half] (decoder->dest[0] + y * decoder->stride + decoder->offset, \ + ref[0] + offset, decoder->stride, size); \ + \ + if (MPEG2_COLOR) \ + { \ + table[xy_half] (decoder->dest[1] + y * decoder->stride + decoder->offset, \ + ref[1] + offset, decoder->stride, size); \ + table[xy_half] (decoder->dest[2] + y * decoder->stride + decoder->offset, \ + ref[2] + offset, decoder->stride, size); \ + } + +#define MOTION_FIELD_444(table, ref, motion_x, motion_y, \ + dest_field, op, src_field) \ + pos_x = 2 * decoder->offset + motion_x; \ + pos_y = decoder->v_offset + motion_y; \ + \ + if (unlikely (pos_x > decoder->limit_x)) \ + { \ + pos_x = ((int)pos_x < 0) ? 0 : decoder->limit_x; \ + motion_x = pos_x - 2 * decoder->offset; \ + } \ + \ + if (unlikely (pos_y > decoder->limit_y)) \ + { \ + pos_y = ((int)pos_y < 0) ? 0 : decoder->limit_y; \ + motion_y = pos_y - decoder->v_offset; \ + } \ + \ + xy_half = ((pos_y & 1) << 1) | (pos_x & 1); \ + offset = (pos_x >> 1) + ((pos_y op) + src_field) * decoder->stride; \ + \ + table[xy_half] (decoder->dest[0] + dest_field * decoder->stride + \ + decoder->offset, ref[0] + offset, \ + 2 * decoder->stride, 8); \ + \ + if (MPEG2_COLOR) \ + { \ + table[xy_half] (decoder->dest[1] + dest_field * decoder->stride + \ + decoder->offset, ref[1] + offset, \ + 2 * decoder->stride, 8); \ + table[xy_half] (decoder->dest[2] + dest_field * decoder->stride + \ + decoder->offset, ref[2] + offset, \ + 2 * decoder->stride, 8); \ + } + +#define MOTION_DMV_444(table, ref, motion_x, motion_y) \ + pos_x = 2 * decoder->offset + motion_x; \ + pos_y = decoder->v_offset + motion_y; \ + \ + if (unlikely (pos_x > decoder->limit_x)) \ + { \ + pos_x = ((int)pos_x < 0) ? 0 : decoder->limit_x; \ + motion_x = pos_x - 2 * decoder->offset; \ + } \ + \ + if (unlikely (pos_y > decoder->limit_y)) \ + { \ + pos_y = ((int)pos_y < 0) ? 0 : decoder->limit_y; \ + motion_y = pos_y - decoder->v_offset; \ + } \ + \ + xy_half = ((pos_y & 1) << 1) | (pos_x & 1); \ + offset = (pos_x >> 1) + (pos_y & ~1) * decoder->stride; \ + \ + table[xy_half] (decoder->dest[0] + decoder->offset, \ + ref[0] + offset, 2 * decoder->stride, 8); \ + table[xy_half] (decoder->dest[0] + decoder->stride + decoder->offset, \ + ref[0] + decoder->stride + offset, \ + 2 * decoder->stride, 8); \ + \ + if (MPEG2_COLOR) \ + { \ + table[xy_half] (decoder->dest[1] + decoder->offset, \ + ref[1] + offset, 2 * decoder->stride, 8); \ + table[xy_half] (decoder->dest[1] + decoder->stride + decoder->offset, \ + ref[1] + decoder->stride + offset, \ + 2 * decoder->stride, 8); \ + table[xy_half] (decoder->dest[2] + decoder->offset, \ + ref[2] + offset, 2 * decoder->stride, 8); \ + table[xy_half] (decoder->dest[2] + decoder->stride + decoder->offset, \ + ref[2] + decoder->stride + offset, \ + 2 * decoder->stride, 8); \ + } + +#define MOTION_ZERO_444(table, ref) \ + offset = decoder->offset + decoder->v_offset * decoder->stride; \ + \ + table[0] (decoder->dest[0] + decoder->offset, \ + ref[0] + offset, decoder->stride, 16); \ + \ + if (MPEG2_COLOR) \ + { \ + table[4] (decoder->dest[1] + decoder->offset, \ + ref[1] + offset, decoder->stride, 16); \ + table[4] (decoder->dest[2] + decoder->offset, \ + ref[2] + offset, decoder->stride, 16); \ + } + +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + +static void motion_mp1 (mpeg2_decoder_t * const decoder, + motion_t * const motion, + mpeg2_mc_fct * const * const table) +{ + int motion_x, motion_y; + unsigned int pos_x, pos_y, xy_half, offset; + + NEEDBITS (bit_buf, bits, bit_ptr); + motion_x = motion->pmv[0][0] + + (get_motion_delta (decoder, + motion->f_code[0]) << motion->f_code[1]); + motion_x = bound_motion_vector (motion_x, + motion->f_code[0] + motion->f_code[1]); + motion->pmv[0][0] = motion_x; + + NEEDBITS (bit_buf, bits, bit_ptr); + motion_y = motion->pmv[0][1] + + (get_motion_delta (decoder, + motion->f_code[0]) << motion->f_code[1]); + motion_y = bound_motion_vector (motion_y, + motion->f_code[0] + motion->f_code[1]); + motion->pmv[0][1] = motion_y; + + MOTION_420 (table, motion->ref[0], motion_x, motion_y, 16, 0); +} + +#define MOTION_FUNCTIONS(FORMAT, MOTION, MOTION_FIELD, \ + MOTION_DMV, MOTION_ZERO) \ + \ +static void motion_fr_frame_##FORMAT (mpeg2_decoder_t * const decoder, \ + motion_t * const motion, \ + mpeg2_mc_fct * const * const table) \ +{ \ + int motion_x, motion_y; \ + unsigned int pos_x, pos_y, xy_half, offset; \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + motion_x = motion->pmv[0][0] + get_motion_delta (decoder, \ + motion->f_code[0]); \ + motion_x = bound_motion_vector (motion_x, motion->f_code[0]); \ + motion->pmv[1][0] = motion->pmv[0][0] = motion_x; \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + motion_y = motion->pmv[0][1] + get_motion_delta (decoder, \ + motion->f_code[1]); \ + motion_y = bound_motion_vector (motion_y, motion->f_code[1]); \ + motion->pmv[1][1] = motion->pmv[0][1] = motion_y; \ + \ + MOTION (table, motion->ref[0], motion_x, motion_y, 16, 0); \ +} \ + \ +static void motion_fr_field_##FORMAT (mpeg2_decoder_t * const decoder, \ + motion_t * const motion, \ + mpeg2_mc_fct * const * const table) \ +{ \ + int motion_x, motion_y, field; \ + unsigned int pos_x, pos_y, xy_half, offset; \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + field = UBITS (bit_buf, 1); \ + DUMPBITS (bit_buf, bits, 1); \ + \ + motion_x = motion->pmv[0][0] + get_motion_delta (decoder, \ + motion->f_code[0]); \ + motion_x = bound_motion_vector (motion_x, motion->f_code[0]); \ + motion->pmv[0][0] = motion_x; \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + motion_y = ((motion->pmv[0][1] >> 1) + \ + get_motion_delta (decoder, motion->f_code[1])); \ + /* motion_y = bound_motion_vector (motion_y, motion->f_code[1]); */ \ + motion->pmv[0][1] = motion_y << 1; \ + \ + MOTION_FIELD (table, motion->ref[0], motion_x, motion_y, 0, & ~1, field); \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + field = UBITS (bit_buf, 1); \ + DUMPBITS (bit_buf, bits, 1); \ + \ + motion_x = motion->pmv[1][0] + get_motion_delta (decoder, \ + motion->f_code[0]); \ + motion_x = bound_motion_vector (motion_x, motion->f_code[0]); \ + motion->pmv[1][0] = motion_x; \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + motion_y = ((motion->pmv[1][1] >> 1) + \ + get_motion_delta (decoder, motion->f_code[1])); \ + /* motion_y = bound_motion_vector (motion_y, motion->f_code[1]); */ \ + motion->pmv[1][1] = motion_y << 1; \ + \ + MOTION_FIELD (table, motion->ref[0], motion_x, motion_y, 1, & ~1, field); \ +} \ + \ +static void motion_fr_dmv_##FORMAT (mpeg2_decoder_t * const decoder, \ + motion_t * const motion, \ + mpeg2_mc_fct * const * const table) \ +{ \ + int motion_x, motion_y, dmv_x, dmv_y, m, other_x, other_y; \ + unsigned int pos_x, pos_y, xy_half, offset; \ + \ + (void)table; \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + motion_x = motion->pmv[0][0] + get_motion_delta (decoder, \ + motion->f_code[0]); \ + motion_x = bound_motion_vector (motion_x, motion->f_code[0]); \ + motion->pmv[1][0] = motion->pmv[0][0] = motion_x; \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + dmv_x = get_dmv (decoder); \ + \ + motion_y = ((motion->pmv[0][1] >> 1) + \ + get_motion_delta (decoder, motion->f_code[1])); \ + /* motion_y = bound_motion_vector (motion_y, motion->f_code[1]); */ \ + motion->pmv[1][1] = motion->pmv[0][1] = motion_y << 1; \ + dmv_y = get_dmv (decoder); \ + \ + m = decoder->top_field_first ? 1 : 3; \ + other_x = ((motion_x * m + (motion_x > 0)) >> 1) + dmv_x; \ + other_y = ((motion_y * m + (motion_y > 0)) >> 1) + dmv_y - 1; \ + MOTION_FIELD (mpeg2_mc.put, motion->ref[0], other_x, other_y, 0, | 1, 0); \ + \ + m = decoder->top_field_first ? 3 : 1; \ + other_x = ((motion_x * m + (motion_x > 0)) >> 1) + dmv_x; \ + other_y = ((motion_y * m + (motion_y > 0)) >> 1) + dmv_y + 1; \ + MOTION_FIELD (mpeg2_mc.put, motion->ref[0], other_x, other_y, 1, & ~1, 0);\ + \ + MOTION_DMV (mpeg2_mc.avg, motion->ref[0], motion_x, motion_y); \ +} \ + \ +static void motion_reuse_##FORMAT (mpeg2_decoder_t * const decoder, \ + motion_t * const motion, \ + mpeg2_mc_fct * const * const table) \ +{ \ + int motion_x, motion_y; \ + unsigned int pos_x, pos_y, xy_half, offset; \ + \ + motion_x = motion->pmv[0][0]; \ + motion_y = motion->pmv[0][1]; \ + \ + MOTION (table, motion->ref[0], motion_x, motion_y, 16, 0); \ +} \ + \ +static void motion_zero_##FORMAT (mpeg2_decoder_t * const decoder, \ + motion_t * const motion, \ + mpeg2_mc_fct * const * const table) \ +{ \ + unsigned int offset; \ + \ + motion->pmv[0][0] = motion->pmv[0][1] = 0; \ + motion->pmv[1][0] = motion->pmv[1][1] = 0; \ + \ + MOTION_ZERO (table, motion->ref[0]); \ +} \ + \ +static void motion_fi_field_##FORMAT (mpeg2_decoder_t * const decoder, \ + motion_t * const motion, \ + mpeg2_mc_fct * const * const table) \ +{ \ + int motion_x, motion_y; \ + uint8_t ** ref_field; \ + unsigned int pos_x, pos_y, xy_half, offset; \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + ref_field = motion->ref2[UBITS (bit_buf, 1)]; \ + DUMPBITS (bit_buf, bits, 1); \ + \ + motion_x = motion->pmv[0][0] + get_motion_delta (decoder, \ + motion->f_code[0]); \ + motion_x = bound_motion_vector (motion_x, motion->f_code[0]); \ + motion->pmv[1][0] = motion->pmv[0][0] = motion_x; \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + motion_y = motion->pmv[0][1] + get_motion_delta (decoder, \ + motion->f_code[1]); \ + motion_y = bound_motion_vector (motion_y, motion->f_code[1]); \ + motion->pmv[1][1] = motion->pmv[0][1] = motion_y; \ + \ + MOTION (table, ref_field, motion_x, motion_y, 16, 0); \ +} \ + \ +static void motion_fi_16x8_##FORMAT (mpeg2_decoder_t * const decoder, \ + motion_t * const motion, \ + mpeg2_mc_fct * const * const table) \ +{ \ + int motion_x, motion_y; \ + uint8_t ** ref_field; \ + unsigned int pos_x, pos_y, xy_half, offset; \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + ref_field = motion->ref2[UBITS (bit_buf, 1)]; \ + DUMPBITS (bit_buf, bits, 1); \ + \ + motion_x = motion->pmv[0][0] + get_motion_delta (decoder, \ + motion->f_code[0]); \ + motion_x = bound_motion_vector (motion_x, motion->f_code[0]); \ + motion->pmv[0][0] = motion_x; \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + motion_y = motion->pmv[0][1] + get_motion_delta (decoder, \ + motion->f_code[1]); \ + motion_y = bound_motion_vector (motion_y, motion->f_code[1]); \ + motion->pmv[0][1] = motion_y; \ + \ + MOTION (table, ref_field, motion_x, motion_y, 8, 0); \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + ref_field = motion->ref2[UBITS (bit_buf, 1)]; \ + DUMPBITS (bit_buf, bits, 1); \ + \ + motion_x = motion->pmv[1][0] + get_motion_delta (decoder, \ + motion->f_code[0]); \ + motion_x = bound_motion_vector (motion_x, motion->f_code[0]); \ + motion->pmv[1][0] = motion_x; \ + \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + motion_y = motion->pmv[1][1] + get_motion_delta (decoder, \ + motion->f_code[1]); \ + motion_y = bound_motion_vector (motion_y, motion->f_code[1]); \ + motion->pmv[1][1] = motion_y; \ + \ + MOTION (table, ref_field, motion_x, motion_y, 8, 8); \ +} \ + \ +static void motion_fi_dmv_##FORMAT (mpeg2_decoder_t * const decoder, \ + motion_t * const motion, \ + mpeg2_mc_fct * const * const table) \ +{ \ + int motion_x, motion_y, other_x, other_y; \ + unsigned int pos_x, pos_y, xy_half, offset; \ + \ + (void)table; \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + motion_x = motion->pmv[0][0] + get_motion_delta (decoder, \ + motion->f_code[0]); \ + motion_x = bound_motion_vector (motion_x, motion->f_code[0]); \ + motion->pmv[1][0] = motion->pmv[0][0] = motion_x; \ + NEEDBITS (bit_buf, bits, bit_ptr); \ + other_x = ((motion_x + (motion_x > 0)) >> 1) + get_dmv (decoder); \ + \ + motion_y = motion->pmv[0][1] + get_motion_delta (decoder, \ + motion->f_code[1]); \ + motion_y = bound_motion_vector (motion_y, motion->f_code[1]); \ + motion->pmv[1][1] = motion->pmv[0][1] = motion_y; \ + other_y = (((motion_y + (motion_y > 0)) >> 1) + get_dmv (decoder) + \ + decoder->dmv_offset); \ + \ + MOTION (mpeg2_mc.put, motion->ref[0], motion_x, motion_y, 16, 0); \ + MOTION (mpeg2_mc.avg, motion->ref[1], other_x, other_y, 16, 0); \ +} \ + +MOTION_FUNCTIONS (420, MOTION_420, MOTION_FIELD_420, MOTION_DMV_420, + MOTION_ZERO_420) +MOTION_FUNCTIONS (422, MOTION_422, MOTION_FIELD_422, MOTION_DMV_422, + MOTION_ZERO_422) +MOTION_FUNCTIONS (444, MOTION_444, MOTION_FIELD_444, MOTION_DMV_444, + MOTION_ZERO_444) + +/* like motion_frame, but parsing without actual motion compensation */ +static void motion_fr_conceal (mpeg2_decoder_t * const decoder) +{ + int tmp; + + NEEDBITS (bit_buf, bits, bit_ptr); + tmp = (decoder->f_motion.pmv[0][0] + + get_motion_delta (decoder, decoder->f_motion.f_code[0])); + tmp = bound_motion_vector (tmp, decoder->f_motion.f_code[0]); + decoder->f_motion.pmv[1][0] = decoder->f_motion.pmv[0][0] = tmp; + + NEEDBITS (bit_buf, bits, bit_ptr); + tmp = (decoder->f_motion.pmv[0][1] + + get_motion_delta (decoder, decoder->f_motion.f_code[1])); + tmp = bound_motion_vector (tmp, decoder->f_motion.f_code[1]); + decoder->f_motion.pmv[1][1] = decoder->f_motion.pmv[0][1] = tmp; + + DUMPBITS (bit_buf, bits, 1); /* remove marker_bit */ +} + +static void motion_fi_conceal (mpeg2_decoder_t * const decoder) +{ + int tmp; + + NEEDBITS (bit_buf, bits, bit_ptr); + DUMPBITS (bit_buf, bits, 1); /* remove field_select */ + + tmp = decoder->f_motion.pmv[0][0] + + get_motion_delta (decoder, decoder->f_motion.f_code[0]); + tmp = bound_motion_vector (tmp, decoder->f_motion.f_code[0]); + + decoder->f_motion.pmv[1][0] = + decoder->f_motion.pmv[0][0] = tmp; + + NEEDBITS (bit_buf, bits, bit_ptr); + + tmp = (decoder->f_motion.pmv[0][1] + + get_motion_delta (decoder, decoder->f_motion.f_code[1])); + tmp = bound_motion_vector (tmp, decoder->f_motion.f_code[1]); + + decoder->f_motion.pmv[1][1] = + decoder->f_motion.pmv[0][1] = tmp; + + DUMPBITS (bit_buf, bits, 1); /* remove marker_bit */ +} + +#undef bit_buf +#undef bits +#undef bit_ptr + +#define MOTION_CALL(routine, direction) \ +do { \ + if ((direction) & MACROBLOCK_MOTION_FORWARD) \ + routine (decoder, &decoder->f_motion, mpeg2_mc.put); \ + \ + if ((direction) & MACROBLOCK_MOTION_BACKWARD) \ + { \ + routine (decoder, &decoder->b_motion, \ + ((direction) & MACROBLOCK_MOTION_FORWARD ? \ + mpeg2_mc.avg : mpeg2_mc.put)); \ + } \ +} while (0) + +#define NEXT_MACROBLOCK \ +do { \ + decoder->offset += 16; \ + \ + if (decoder->offset == decoder->width) \ + { \ + do { /* just so we can use the break statement */ \ + if (decoder->convert) \ + { \ + decoder->convert (decoder->convert_id, decoder->dest, \ + decoder->v_offset); \ + if (decoder->coding_type == B_TYPE) \ + break; \ + } \ + \ + decoder->dest[0] += decoder->slice_stride; \ + if (MPEG2_COLOR) \ + { \ + decoder->dest[1] += decoder->slice_uv_stride; \ + decoder->dest[2] += decoder->slice_uv_stride; \ + } \ + } while (0); \ + \ + decoder->v_offset += 16; \ + \ + if (decoder->v_offset > decoder->limit_y) \ + return; \ + \ + decoder->offset = 0; \ + } \ +} while (0) + +void mpeg2_init_fbuf (mpeg2_decoder_t * decoder, + uint8_t * current_fbuf[MPEG2_COMPONENTS], + uint8_t * forward_fbuf[MPEG2_COMPONENTS], + uint8_t * backward_fbuf[MPEG2_COMPONENTS]) +{ + int offset, stride, height, bottom_field; + + stride = decoder->stride_frame; + bottom_field = (decoder->picture_structure == BOTTOM_FIELD); + offset = bottom_field ? stride : 0; + height = decoder->height; + + decoder->picture_dest[0] = current_fbuf[0] + offset; +#if MPEG2_COLOR + decoder->picture_dest[1] = current_fbuf[1] + (offset >> 1); + decoder->picture_dest[2] = current_fbuf[2] + (offset >> 1); +#endif + + decoder->f_motion.ref[0][0] = forward_fbuf[0] + offset; +#if MPEG2_COLOR + decoder->f_motion.ref[0][1] = forward_fbuf[1] + (offset >> 1); + decoder->f_motion.ref[0][2] = forward_fbuf[2] + (offset >> 1); +#endif + + decoder->b_motion.ref[0][0] = backward_fbuf[0] + offset; +#if MPEG2_COLOR + decoder->b_motion.ref[0][1] = backward_fbuf[1] + (offset >> 1); + decoder->b_motion.ref[0][2] = backward_fbuf[2] + (offset >> 1); +#endif + + if (decoder->picture_structure != FRAME_PICTURE) + { + decoder->dmv_offset = bottom_field ? 1 : -1; + decoder->f_motion.ref2[0] = decoder->f_motion.ref[bottom_field]; + decoder->f_motion.ref2[1] = decoder->f_motion.ref[!bottom_field]; + decoder->b_motion.ref2[0] = decoder->b_motion.ref[bottom_field]; + decoder->b_motion.ref2[1] = decoder->b_motion.ref[!bottom_field]; + offset = stride - offset; + + if (decoder->second_field && (decoder->coding_type != B_TYPE)) + forward_fbuf = current_fbuf; + + decoder->f_motion.ref[1][0] = forward_fbuf[0] + offset; +#if MPEG2_COLOR + decoder->f_motion.ref[1][1] = forward_fbuf[1] + (offset >> 1); + decoder->f_motion.ref[1][2] = forward_fbuf[2] + (offset >> 1); +#endif + decoder->b_motion.ref[1][0] = backward_fbuf[0] + offset; +#if MPEG2_COLOR + decoder->b_motion.ref[1][1] = backward_fbuf[1] + (offset >> 1); + decoder->b_motion.ref[1][2] = backward_fbuf[2] + (offset >> 1); +#endif + stride <<= 1; + height >>= 1; + } + + decoder->stride = stride; + decoder->slice_stride = 16 * stride; +#if MPEG2_COLOR + decoder->uv_stride = stride >> 1; + decoder->slice_uv_stride = + decoder->slice_stride >> (2 - decoder->chroma_format); +#endif + decoder->limit_x = 2 * decoder->width - 32; + decoder->limit_y_16 = 2 * height - 32; + decoder->limit_y_8 = 2 * height - 16; + decoder->limit_y = height - 16; + + if (decoder->mpeg1) + { + decoder->motion_parser[0] = motion_zero_420; + decoder->motion_parser[MC_FRAME] = motion_mp1; + decoder->motion_parser[4] = motion_reuse_420; + } + else if (decoder->picture_structure == FRAME_PICTURE) + { + if (decoder->chroma_format == 0) + { + decoder->motion_parser[0] = motion_zero_420; + decoder->motion_parser[MC_FIELD] = motion_fr_field_420; + decoder->motion_parser[MC_FRAME] = motion_fr_frame_420; + decoder->motion_parser[MC_DMV] = motion_fr_dmv_420; + decoder->motion_parser[4] = motion_reuse_420; + } + else if (decoder->chroma_format == 1) + { + decoder->motion_parser[0] = motion_zero_422; + decoder->motion_parser[MC_FIELD] = motion_fr_field_422; + decoder->motion_parser[MC_FRAME] = motion_fr_frame_422; + decoder->motion_parser[MC_DMV] = motion_fr_dmv_422; + decoder->motion_parser[4] = motion_reuse_422; + } + else + { + decoder->motion_parser[0] = motion_zero_444; + decoder->motion_parser[MC_FIELD] = motion_fr_field_444; + decoder->motion_parser[MC_FRAME] = motion_fr_frame_444; + decoder->motion_parser[MC_DMV] = motion_fr_dmv_444; + decoder->motion_parser[4] = motion_reuse_444; + } + } + else + { + if (decoder->chroma_format == 0) + { + decoder->motion_parser[0] = motion_zero_420; + decoder->motion_parser[MC_FIELD] = motion_fi_field_420; + decoder->motion_parser[MC_16X8] = motion_fi_16x8_420; + decoder->motion_parser[MC_DMV] = motion_fi_dmv_420; + decoder->motion_parser[4] = motion_reuse_420; + } + else if (decoder->chroma_format == 1) + { + decoder->motion_parser[0] = motion_zero_422; + decoder->motion_parser[MC_FIELD] = motion_fi_field_422; + decoder->motion_parser[MC_16X8] = motion_fi_16x8_422; + decoder->motion_parser[MC_DMV] = motion_fi_dmv_422; + decoder->motion_parser[4] = motion_reuse_422; + } + else + { + decoder->motion_parser[0] = motion_zero_444; + decoder->motion_parser[MC_FIELD] = motion_fi_field_444; + decoder->motion_parser[MC_16X8] = motion_fi_16x8_444; + decoder->motion_parser[MC_DMV] = motion_fi_dmv_444; + decoder->motion_parser[4] = motion_reuse_444; + } + } +} + +static inline int slice_init (mpeg2_decoder_t * const decoder, int code) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + + int offset; + const MBAtab * mba; + +#if MPEG2_COLOR + decoder->dc_dct_pred[0] = decoder->dc_dct_pred[1] = + decoder->dc_dct_pred[2] = 16384; +#else + decoder->dc_dct_pred[0] = 16384; +#endif + + decoder->f_motion.pmv[0][0] = decoder->f_motion.pmv[0][1] = 0; + decoder->f_motion.pmv[1][0] = decoder->f_motion.pmv[1][1] = 0; + decoder->b_motion.pmv[0][0] = decoder->b_motion.pmv[0][1] = 0; + decoder->b_motion.pmv[1][0] = decoder->b_motion.pmv[1][1] = 0; + + if (decoder->vertical_position_extension) + { + code += UBITS (bit_buf, 3) << 7; + DUMPBITS (bit_buf, bits, 3); + } + + decoder->v_offset = (code - 1) * 16; + offset = 0; + + if (!(decoder->convert) || decoder->coding_type != B_TYPE) + { + offset = (code - 1) * decoder->slice_stride; + } + + decoder->dest[0] = decoder->picture_dest[0] + offset; +#if MPEG2_COLOR + offset >>= (2 - decoder->chroma_format); + decoder->dest[1] = decoder->picture_dest[1] + offset; + decoder->dest[2] = decoder->picture_dest[2] + offset; +#endif + + get_quantizer_scale (decoder); + + /* ignore intra_slice and all the extra data */ + while (bit_buf & 0x80000000) + { + DUMPBITS (bit_buf, bits, 9); + NEEDBITS (bit_buf, bits, bit_ptr); + } + + /* decode initial macroblock address increment */ + offset = 0; + while (1) + { + if (bit_buf >= 0x08000000) + { + mba = MBA_5 + (UBITS (bit_buf, 6) - 2); + break; + } + else if (bit_buf >= 0x01800000) + { + mba = MBA_11 + (UBITS (bit_buf, 12) - 24); + break; + } + else + { + switch (UBITS (bit_buf, 12)) + { + case 8: /* macroblock_escape */ + offset += 33; + DUMPBITS (bit_buf, bits, 11); + NEEDBITS (bit_buf, bits, bit_ptr); + continue; + case 15: /* macroblock_stuffing (MPEG1 only) */ + bit_buf &= 0xfffff; + DUMPBITS (bit_buf, bits, 11); + NEEDBITS (bit_buf, bits, bit_ptr); + continue; + default: /* error */ + return 1; + } + } + } + + DUMPBITS (bit_buf, bits, mba->len + 1); + decoder->offset = (offset + mba->mba) << 4; + + while (decoder->offset - decoder->width >= 0) + { + decoder->offset -= decoder->width; + + if (!(decoder->convert) || decoder->coding_type != B_TYPE) + { + decoder->dest[0] += decoder->slice_stride; +#if MPEG2_COLOR + decoder->dest[1] += decoder->slice_uv_stride; + decoder->dest[2] += decoder->slice_uv_stride; +#endif + } + + decoder->v_offset += 16; + } + + if (decoder->v_offset > decoder->limit_y) + return 1; + + return 0; + +#undef bit_buf +#undef bits +#undef bit_ptr +} + +void mpeg2_slice (mpeg2_decoder_t * const decoder, const int code, + const uint8_t * const buffer) +{ +#define bit_buf (decoder->bitstream_buf) +#define bits (decoder->bitstream_bits) +#define bit_ptr (decoder->bitstream_ptr) + + bitstream_init (decoder, buffer); + + if (slice_init (decoder, code)) + return; + + while (1) + { + int macroblock_modes; + int mba_inc; + const MBAtab * mba; + + NEEDBITS (bit_buf, bits, bit_ptr); + + macroblock_modes = get_macroblock_modes (decoder); + + /* maybe integrate MACROBLOCK_QUANT test into get_macroblock_modes ? */ + if (macroblock_modes & MACROBLOCK_QUANT) + get_quantizer_scale (decoder); + + if (macroblock_modes & MACROBLOCK_INTRA) + { + int DCT_offset, DCT_stride; + int offset; + uint8_t * dest_y; + + if (decoder->concealment_motion_vectors) + { + if (decoder->picture_structure == FRAME_PICTURE) + motion_fr_conceal (decoder); + else + motion_fi_conceal (decoder); + } + else + { + decoder->f_motion.pmv[0][0] = decoder->f_motion.pmv[0][1] = 0; + decoder->f_motion.pmv[1][0] = decoder->f_motion.pmv[1][1] = 0; + decoder->b_motion.pmv[0][0] = decoder->b_motion.pmv[0][1] = 0; + decoder->b_motion.pmv[1][0] = decoder->b_motion.pmv[1][1] = 0; + } + + if (macroblock_modes & DCT_TYPE_INTERLACED) + { + DCT_offset = decoder->stride; + DCT_stride = decoder->stride * 2; + } + else + { + DCT_offset = decoder->stride * 8; + DCT_stride = decoder->stride; + } + + offset = decoder->offset; + dest_y = decoder->dest[0] + offset; + slice_intra_DCT (decoder, 0, dest_y, DCT_stride); + slice_intra_DCT (decoder, 0, dest_y + 8, DCT_stride); + slice_intra_DCT (decoder, 0, dest_y + DCT_offset, DCT_stride); + slice_intra_DCT (decoder, 0, dest_y + DCT_offset + 8, DCT_stride); + +#if MPEG2_COLOR + if (likely (decoder->chroma_format == 0)) + { + slice_intra_DCT (decoder, 1, decoder->dest[1] + (offset >> 1), + decoder->uv_stride); + slice_intra_DCT (decoder, 2, decoder->dest[2] + (offset >> 1), + decoder->uv_stride); + + if (decoder->coding_type == D_TYPE) + { + NEEDBITS (bit_buf, bits, bit_ptr); + DUMPBITS (bit_buf, bits, 1); + } + } + else if (likely (decoder->chroma_format == 1)) + { + uint8_t * dest_u = decoder->dest[1] + (offset >> 1); + uint8_t * dest_v = decoder->dest[2] + (offset >> 1); + + DCT_stride >>= 1; + DCT_offset >>= 1; + + slice_intra_DCT (decoder, 1, dest_u, DCT_stride); + slice_intra_DCT (decoder, 2, dest_v, DCT_stride); + slice_intra_DCT (decoder, 1, dest_u + DCT_offset, DCT_stride); + slice_intra_DCT (decoder, 2, dest_v + DCT_offset, DCT_stride); + } + else + { + uint8_t * dest_u = decoder->dest[1] + offset; + uint8_t * dest_v = decoder->dest[2] + offset; + + slice_intra_DCT (decoder, 1, dest_u, DCT_stride); + slice_intra_DCT (decoder, 2, dest_v, DCT_stride); + slice_intra_DCT (decoder, 1, dest_u + DCT_offset, DCT_stride); + slice_intra_DCT (decoder, 2, dest_v + DCT_offset, DCT_stride); + slice_intra_DCT (decoder, 1, dest_u + 8, DCT_stride); + slice_intra_DCT (decoder, 2, dest_v + 8, DCT_stride); + slice_intra_DCT (decoder, 1, dest_u + DCT_offset + 8, + DCT_stride); + slice_intra_DCT (decoder, 2, dest_v + DCT_offset + 8, + DCT_stride); + } +#else + skip_chroma_intra(decoder); +#endif /* MPEG2_COLOR */ + } + else + { + motion_parser_t * parser; + + parser = + decoder->motion_parser[macroblock_modes >> MOTION_TYPE_SHIFT]; + MOTION_CALL (parser, macroblock_modes); + + if (macroblock_modes & MACROBLOCK_PATTERN) + { + int coded_block_pattern; + int DCT_offset, DCT_stride; + + if (macroblock_modes & DCT_TYPE_INTERLACED) + { + DCT_offset = decoder->stride; + DCT_stride = decoder->stride * 2; + } + else + { + DCT_offset = decoder->stride * 8; + DCT_stride = decoder->stride; + } + + coded_block_pattern = get_coded_block_pattern (decoder); + + if (likely (decoder->chroma_format == 0)) + { + int offset = decoder->offset; + uint8_t * dest_y = decoder->dest[0] + offset; + + if (coded_block_pattern & 1) + slice_non_intra_DCT (decoder, 0, dest_y, DCT_stride); + + if (coded_block_pattern & 2) + slice_non_intra_DCT (decoder, 0, dest_y + 8, + DCT_stride); + + if (coded_block_pattern & 4) + slice_non_intra_DCT (decoder, 0, dest_y + DCT_offset, + DCT_stride); + + if (coded_block_pattern & 8) + slice_non_intra_DCT (decoder, 0, + dest_y + DCT_offset + 8, + DCT_stride); +#if MPEG2_COLOR + if (coded_block_pattern & 16) + slice_non_intra_DCT (decoder, 1, + decoder->dest[1] + (offset >> 1), + decoder->uv_stride); + + if (coded_block_pattern & 32) + slice_non_intra_DCT (decoder, 2, + decoder->dest[2] + (offset >> 1), + decoder->uv_stride); +#endif /* MPEG2_COLOR */ + } + else if (likely (decoder->chroma_format == 1)) + { + int offset; + uint8_t * dest_y; + + coded_block_pattern |= bit_buf & (3 << 30); + DUMPBITS (bit_buf, bits, 2); + + offset = decoder->offset; + dest_y = decoder->dest[0] + offset; + + if (coded_block_pattern & 1) + slice_non_intra_DCT (decoder, 0, dest_y, DCT_stride); + + if (coded_block_pattern & 2) + slice_non_intra_DCT (decoder, 0, dest_y + 8, + DCT_stride); + + if (coded_block_pattern & 4) + slice_non_intra_DCT (decoder, 0, dest_y + DCT_offset, + DCT_stride); + + if (coded_block_pattern & 8) + slice_non_intra_DCT (decoder, 0, + dest_y + DCT_offset + 8, + DCT_stride); +#if MPEG2_COLOR + DCT_stride >>= 1; + DCT_offset = (DCT_offset + offset) >> 1; + + if (coded_block_pattern & 16) + slice_non_intra_DCT (decoder, 1, + decoder->dest[1] + (offset >> 1), + DCT_stride); + + if (coded_block_pattern & 32) + slice_non_intra_DCT (decoder, 2, + decoder->dest[2] + (offset >> 1), + DCT_stride); + + if (coded_block_pattern & (2 << 30)) + slice_non_intra_DCT (decoder, 1, + decoder->dest[1] + DCT_offset, + DCT_stride); + + if (coded_block_pattern & (1 << 30)) + slice_non_intra_DCT (decoder, 2, + decoder->dest[2] + DCT_offset, + DCT_stride); +#endif /* MPEG2_COLOR */ + } + else + { + int offset = decoder->offset; + uint8_t * dest_y = decoder->dest[0] + offset; +#if MPEG2_COLOR + uint8_t * dest_u = decoder->dest[1] + offset; + uint8_t * dest_v = decoder->dest[2] + offset; +#endif + coded_block_pattern |= bit_buf & (63 << 26); + DUMPBITS (bit_buf, bits, 6); + + if (coded_block_pattern & 1) + slice_non_intra_DCT (decoder, 0, dest_y, DCT_stride); + + if (coded_block_pattern & 2) + slice_non_intra_DCT (decoder, 0, dest_y + 8, + DCT_stride); + + if (coded_block_pattern & 4) + slice_non_intra_DCT (decoder, 0, dest_y + DCT_offset, + DCT_stride); + + if (coded_block_pattern & 8) + slice_non_intra_DCT (decoder, 0, + dest_y + DCT_offset + 8, + DCT_stride); +#if MPEG2_COLOR + if (coded_block_pattern & 16) + slice_non_intra_DCT (decoder, 1, dest_u, DCT_stride); + + if (coded_block_pattern & 32) + slice_non_intra_DCT (decoder, 2, dest_v, DCT_stride); + + if (coded_block_pattern & (32 << 26)) + slice_non_intra_DCT (decoder, 1, dest_u + DCT_offset, + DCT_stride); + + if (coded_block_pattern & (16 << 26)) + slice_non_intra_DCT (decoder, 2, dest_v + DCT_offset, + DCT_stride); + + if (coded_block_pattern & (8 << 26)) + slice_non_intra_DCT (decoder, 1, dest_u + 8, + DCT_stride); + + if (coded_block_pattern & (4 << 26)) + slice_non_intra_DCT (decoder, 2, dest_v + 8, + DCT_stride); + + if (coded_block_pattern & (2 << 26)) + slice_non_intra_DCT (decoder, 1, + dest_u + DCT_offset + 8, + DCT_stride); + + if (coded_block_pattern & (1 << 26)) + slice_non_intra_DCT (decoder, 2, + dest_v + DCT_offset + 8, + DCT_stride); +#endif /* MPEG2_COLOR */ + } +#if !MPEG2_COLOR + skip_chroma_non_intra(decoder, coded_block_pattern); +#endif + } + +#if MPEG2_COLOR + decoder->dc_dct_pred[0] = decoder->dc_dct_pred[1] = + decoder->dc_dct_pred[2] = 16384; +#else + decoder->dc_dct_pred[0] = 16384; +#endif + } + + NEXT_MACROBLOCK; + + NEEDBITS (bit_buf, bits, bit_ptr); + mba_inc = 0; + + while (1) + { + if (bit_buf >= 0x10000000) + { + mba = MBA_5 + (UBITS (bit_buf, 5) - 2); + break; + } + else if (bit_buf >= 0x03000000) + { + mba = MBA_11 + (UBITS (bit_buf, 11) - 24); + break; + } + else + { + switch (UBITS (bit_buf, 11)) + { + case 8: /* macroblock_escape */ + mba_inc += 33; + /* pass through */ + case 15: /* macroblock_stuffing (MPEG1 only) */ + DUMPBITS (bit_buf, bits, 11); + NEEDBITS (bit_buf, bits, bit_ptr); + continue; + default: /* end of slice, or error */ + return; + } + } + } + + DUMPBITS (bit_buf, bits, mba->len); + mba_inc += mba->mba; + + if (mba_inc) + { +#if MPEG2_COLOR + decoder->dc_dct_pred[0] = decoder->dc_dct_pred[1] = + decoder->dc_dct_pred[2] = 16384; +#else + decoder->dc_dct_pred[0] = 16384; +#endif + if (decoder->coding_type == P_TYPE) + { + do + { + MOTION_CALL (decoder->motion_parser[0], + MACROBLOCK_MOTION_FORWARD); + NEXT_MACROBLOCK; + } + while (--mba_inc); + } + else + { + do + { + MOTION_CALL (decoder->motion_parser[4], macroblock_modes); + NEXT_MACROBLOCK; + } + while (--mba_inc); + } + } + } + +#undef bit_buf +#undef bits +#undef bit_ptr +} diff --git a/apps/plugins/mpegplayer/libmpeg2/vlc.h b/apps/plugins/mpegplayer/libmpeg2/vlc.h new file mode 100644 index 0000000000..d1b6a98cde --- /dev/null +++ b/apps/plugins/mpegplayer/libmpeg2/vlc.h @@ -0,0 +1,433 @@ +/* + * vlc.h + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + * libmpeg2 sync history: + * 2008-07-01 - CVS revision 1.12 + */ + +#define GETWORD(bit_buf, shift, bit_ptr) \ +do { \ + bit_buf |= ((bit_ptr[0] << 8) | bit_ptr[1]) << (shift); \ + bit_ptr += 2; \ +} while (0) + +static inline void bitstream_init (mpeg2_decoder_t * decoder, + const uint8_t * start) +{ + decoder->bitstream_buf = + (start[0] << 24) | (start[1] << 16) | (start[2] << 8) | start[3]; + decoder->bitstream_ptr = start + 4; + decoder->bitstream_bits = -16; +} + +/* make sure that there are at least 16 valid bits in bit_buf */ +#define NEEDBITS(bit_buf, bits, bit_ptr) \ +do { \ + if (unlikely (bits > 0)) { \ + GETWORD (bit_buf, bits, bit_ptr); \ + bits -= 16; \ + } \ +} while (0) + +/* remove num valid bits from bit_buf */ +#define DUMPBITS(bit_buf, bits, num) \ +do { \ + bit_buf <<= (num); \ + bits += (num); \ +} while (0) + +/* take num bits from the high part of bit_buf and zero extend them */ +#define UBITS(bit_buf,num) (((uint32_t)(bit_buf)) >> (32 - (num))) + +/* take num bits from the high part of bit_buf and sign extend them */ +#define SBITS(bit_buf,num) (((int32_t)(bit_buf)) >> (32 - (num))) + +typedef struct { + uint8_t modes; + uint8_t len; +} MBtab; + +typedef struct { + uint8_t delta; + uint8_t len; +} MVtab; + +typedef struct { + int8_t dmv; + uint8_t len; +} DMVtab; + +typedef struct { + uint8_t cbp; + uint8_t len; +} CBPtab; + +typedef struct { + uint8_t size; + uint8_t len; +} DCtab; + +typedef struct { + uint8_t run; + uint8_t level; + uint8_t len; +} DCTtab; + +typedef struct { + uint8_t mba; + uint8_t len; +} MBAtab; + + +#define INTRA MACROBLOCK_INTRA +#define QUANT MACROBLOCK_QUANT + +static const MBtab MB_I [] ICONST_ATTR = { + {INTRA|QUANT, 2}, {INTRA, 1} +}; + +#define MC MACROBLOCK_MOTION_FORWARD +#define CODED MACROBLOCK_PATTERN + +static const MBtab MB_P [] ICONST_ATTR = { + {INTRA|QUANT, 6}, {CODED|QUANT, 5}, {MC|CODED|QUANT, 5}, {INTRA, 5}, + {MC, 3}, {MC, 3}, {MC, 3}, {MC, 3}, + {CODED, 2}, {CODED, 2}, {CODED, 2}, {CODED, 2}, + {CODED, 2}, {CODED, 2}, {CODED, 2}, {CODED, 2}, + {MC|CODED, 1}, {MC|CODED, 1}, {MC|CODED, 1}, {MC|CODED, 1}, + {MC|CODED, 1}, {MC|CODED, 1}, {MC|CODED, 1}, {MC|CODED, 1}, + {MC|CODED, 1}, {MC|CODED, 1}, {MC|CODED, 1}, {MC|CODED, 1}, + {MC|CODED, 1}, {MC|CODED, 1}, {MC|CODED, 1}, {MC|CODED, 1} +}; + +#define FWD MACROBLOCK_MOTION_FORWARD +#define BWD MACROBLOCK_MOTION_BACKWARD +#define INTER MACROBLOCK_MOTION_FORWARD|MACROBLOCK_MOTION_BACKWARD + +static const MBtab MB_B [] ICONST_ATTR = { + {0, 6}, {INTRA|QUANT, 6}, + {BWD|CODED|QUANT, 6}, {FWD|CODED|QUANT, 6}, + {INTER|CODED|QUANT, 5}, {INTER|CODED|QUANT, 5}, + {INTRA, 5}, {INTRA, 5}, + {FWD, 4}, {FWD, 4}, {FWD, 4}, {FWD, 4}, + {FWD|CODED, 4}, {FWD|CODED, 4}, {FWD|CODED, 4}, {FWD|CODED, 4}, + {BWD, 3}, {BWD, 3}, {BWD, 3}, {BWD, 3}, + {BWD, 3}, {BWD, 3}, {BWD, 3}, {BWD, 3}, + {BWD|CODED, 3}, {BWD|CODED, 3}, {BWD|CODED, 3}, {BWD|CODED, 3}, + {BWD|CODED, 3}, {BWD|CODED, 3}, {BWD|CODED, 3}, {BWD|CODED, 3}, + {INTER, 2}, {INTER, 2}, {INTER, 2}, {INTER, 2}, + {INTER, 2}, {INTER, 2}, {INTER, 2}, {INTER, 2}, + {INTER, 2}, {INTER, 2}, {INTER, 2}, {INTER, 2}, + {INTER, 2}, {INTER, 2}, {INTER, 2}, {INTER, 2}, + {INTER|CODED, 2}, {INTER|CODED, 2}, {INTER|CODED, 2}, {INTER|CODED, 2}, + {INTER|CODED, 2}, {INTER|CODED, 2}, {INTER|CODED, 2}, {INTER|CODED, 2}, + {INTER|CODED, 2}, {INTER|CODED, 2}, {INTER|CODED, 2}, {INTER|CODED, 2}, + {INTER|CODED, 2}, {INTER|CODED, 2}, {INTER|CODED, 2}, {INTER|CODED, 2} +}; + +#undef INTRA +#undef QUANT +#undef MC +#undef CODED +#undef FWD +#undef BWD +#undef INTER + + +static const MVtab MV_4 [] ICONST_ATTR = { + { 3, 6}, { 2, 4}, { 1, 3}, { 1, 3}, { 0, 2}, { 0, 2}, { 0, 2}, { 0, 2} +}; + +static const MVtab MV_10 [] ICONST_ATTR = { + { 0,10}, { 0,10}, { 0,10}, { 0,10}, { 0,10}, { 0,10}, { 0,10}, { 0,10}, + { 0,10}, { 0,10}, { 0,10}, { 0,10}, {15,10}, {14,10}, {13,10}, {12,10}, + {11,10}, {10,10}, { 9, 9}, { 9, 9}, { 8, 9}, { 8, 9}, { 7, 9}, { 7, 9}, + { 6, 7}, { 6, 7}, { 6, 7}, { 6, 7}, { 6, 7}, { 6, 7}, { 6, 7}, { 6, 7}, + { 5, 7}, { 5, 7}, { 5, 7}, { 5, 7}, { 5, 7}, { 5, 7}, { 5, 7}, { 5, 7}, + { 4, 7}, { 4, 7}, { 4, 7}, { 4, 7}, { 4, 7}, { 4, 7}, { 4, 7}, { 4, 7} +}; + + +static const DMVtab DMV_2 [] ICONST_ATTR = { + { 0, 1}, { 0, 1}, { 1, 2}, {-1, 2} +}; + + +static const CBPtab CBP_7 [] ICONST_ATTR = { + {0x11, 7}, {0x12, 7}, {0x14, 7}, {0x18, 7}, + {0x21, 7}, {0x22, 7}, {0x24, 7}, {0x28, 7}, + {0x3f, 6}, {0x3f, 6}, {0x30, 6}, {0x30, 6}, + {0x09, 6}, {0x09, 6}, {0x06, 6}, {0x06, 6}, + {0x1f, 5}, {0x1f, 5}, {0x1f, 5}, {0x1f, 5}, + {0x10, 5}, {0x10, 5}, {0x10, 5}, {0x10, 5}, + {0x2f, 5}, {0x2f, 5}, {0x2f, 5}, {0x2f, 5}, + {0x20, 5}, {0x20, 5}, {0x20, 5}, {0x20, 5}, + {0x07, 5}, {0x07, 5}, {0x07, 5}, {0x07, 5}, + {0x0b, 5}, {0x0b, 5}, {0x0b, 5}, {0x0b, 5}, + {0x0d, 5}, {0x0d, 5}, {0x0d, 5}, {0x0d, 5}, + {0x0e, 5}, {0x0e, 5}, {0x0e, 5}, {0x0e, 5}, + {0x05, 5}, {0x05, 5}, {0x05, 5}, {0x05, 5}, + {0x0a, 5}, {0x0a, 5}, {0x0a, 5}, {0x0a, 5}, + {0x03, 5}, {0x03, 5}, {0x03, 5}, {0x03, 5}, + {0x0c, 5}, {0x0c, 5}, {0x0c, 5}, {0x0c, 5}, + {0x01, 4}, {0x01, 4}, {0x01, 4}, {0x01, 4}, + {0x01, 4}, {0x01, 4}, {0x01, 4}, {0x01, 4}, + {0x02, 4}, {0x02, 4}, {0x02, 4}, {0x02, 4}, + {0x02, 4}, {0x02, 4}, {0x02, 4}, {0x02, 4}, + {0x04, 4}, {0x04, 4}, {0x04, 4}, {0x04, 4}, + {0x04, 4}, {0x04, 4}, {0x04, 4}, {0x04, 4}, + {0x08, 4}, {0x08, 4}, {0x08, 4}, {0x08, 4}, + {0x08, 4}, {0x08, 4}, {0x08, 4}, {0x08, 4}, + {0x0f, 3}, {0x0f, 3}, {0x0f, 3}, {0x0f, 3}, + {0x0f, 3}, {0x0f, 3}, {0x0f, 3}, {0x0f, 3}, + {0x0f, 3}, {0x0f, 3}, {0x0f, 3}, {0x0f, 3}, + {0x0f, 3}, {0x0f, 3}, {0x0f, 3}, {0x0f, 3} +}; + +static const CBPtab CBP_9 [] ICONST_ATTR = { + {0, 9}, {0x00, 9}, {0x39, 9}, {0x36, 9}, + {0x37, 9}, {0x3b, 9}, {0x3d, 9}, {0x3e, 9}, + {0x17, 8}, {0x17, 8}, {0x1b, 8}, {0x1b, 8}, + {0x1d, 8}, {0x1d, 8}, {0x1e, 8}, {0x1e, 8}, + {0x27, 8}, {0x27, 8}, {0x2b, 8}, {0x2b, 8}, + {0x2d, 8}, {0x2d, 8}, {0x2e, 8}, {0x2e, 8}, + {0x19, 8}, {0x19, 8}, {0x16, 8}, {0x16, 8}, + {0x29, 8}, {0x29, 8}, {0x26, 8}, {0x26, 8}, + {0x35, 8}, {0x35, 8}, {0x3a, 8}, {0x3a, 8}, + {0x33, 8}, {0x33, 8}, {0x3c, 8}, {0x3c, 8}, + {0x15, 8}, {0x15, 8}, {0x1a, 8}, {0x1a, 8}, + {0x13, 8}, {0x13, 8}, {0x1c, 8}, {0x1c, 8}, + {0x25, 8}, {0x25, 8}, {0x2a, 8}, {0x2a, 8}, + {0x23, 8}, {0x23, 8}, {0x2c, 8}, {0x2c, 8}, + {0x31, 8}, {0x31, 8}, {0x32, 8}, {0x32, 8}, + {0x34, 8}, {0x34, 8}, {0x38, 8}, {0x38, 8} +}; + + +static const DCtab DC_lum_5 [] ICONST_ATTR = { + {1, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 2}, + {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, + {0, 3}, {0, 3}, {0, 3}, {0, 3}, {3, 3}, {3, 3}, {3, 3}, {3, 3}, + {4, 3}, {4, 3}, {4, 3}, {4, 3}, {5, 4}, {5, 4}, {6, 5} +}; + +static const DCtab DC_chrom_5 [] ICONST_ATTR = { + {0, 2}, {0, 2}, {0, 2}, {0, 2}, {0, 2}, {0, 2}, {0, 2}, {0, 2}, + {1, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 2}, + {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, + {3, 3}, {3, 3}, {3, 3}, {3, 3}, {4, 4}, {4, 4}, {5, 5} +}; + +static const DCtab DC_long [] ICONST_ATTR = { + {6, 5}, {6, 5}, {6, 5}, {6, 5}, {6, 5}, {6, 5}, { 6, 5}, { 6, 5}, + {6, 5}, {6, 5}, {6, 5}, {6, 5}, {6, 5}, {6, 5}, { 6, 5}, { 6, 5}, + {7, 6}, {7, 6}, {7, 6}, {7, 6}, {7, 6}, {7, 6}, { 7, 6}, { 7, 6}, + {8, 7}, {8, 7}, {8, 7}, {8, 7}, {9, 8}, {9, 8}, {10, 9}, {11, 9} +}; + + +static const DCTtab DCT_16 [] ICONST_ATTR = { + {129, 0, 0}, {129, 0, 0}, {129, 0, 0}, {129, 0, 0}, + {129, 0, 0}, {129, 0, 0}, {129, 0, 0}, {129, 0, 0}, + {129, 0, 0}, {129, 0, 0}, {129, 0, 0}, {129, 0, 0}, + {129, 0, 0}, {129, 0, 0}, {129, 0, 0}, {129, 0, 0}, + { 2,18, 0}, { 2,17, 0}, { 2,16, 0}, { 2,15, 0}, + { 7, 3, 0}, { 17, 2, 0}, { 16, 2, 0}, { 15, 2, 0}, + { 14, 2, 0}, { 13, 2, 0}, { 12, 2, 0}, { 32, 1, 0}, + { 31, 1, 0}, { 30, 1, 0}, { 29, 1, 0}, { 28, 1, 0} +}; + +static const DCTtab DCT_15 [] ICONST_ATTR = { + { 1,40,15}, { 1,39,15}, { 1,38,15}, { 1,37,15}, + { 1,36,15}, { 1,35,15}, { 1,34,15}, { 1,33,15}, + { 1,32,15}, { 2,14,15}, { 2,13,15}, { 2,12,15}, + { 2,11,15}, { 2,10,15}, { 2, 9,15}, { 2, 8,15}, + { 1,31,14}, { 1,31,14}, { 1,30,14}, { 1,30,14}, + { 1,29,14}, { 1,29,14}, { 1,28,14}, { 1,28,14}, + { 1,27,14}, { 1,27,14}, { 1,26,14}, { 1,26,14}, + { 1,25,14}, { 1,25,14}, { 1,24,14}, { 1,24,14}, + { 1,23,14}, { 1,23,14}, { 1,22,14}, { 1,22,14}, + { 1,21,14}, { 1,21,14}, { 1,20,14}, { 1,20,14}, + { 1,19,14}, { 1,19,14}, { 1,18,14}, { 1,18,14}, + { 1,17,14}, { 1,17,14}, { 1,16,14}, { 1,16,14} +}; + +static const DCTtab DCT_13 [] ICONST_ATTR = { + { 11, 2,13}, { 10, 2,13}, { 6, 3,13}, { 4, 4,13}, + { 3, 5,13}, { 2, 7,13}, { 2, 6,13}, { 1,15,13}, + { 1,14,13}, { 1,13,13}, { 1,12,13}, { 27, 1,13}, + { 26, 1,13}, { 25, 1,13}, { 24, 1,13}, { 23, 1,13}, + { 1,11,12}, { 1,11,12}, { 9, 2,12}, { 9, 2,12}, + { 5, 3,12}, { 5, 3,12}, { 1,10,12}, { 1,10,12}, + { 3, 4,12}, { 3, 4,12}, { 8, 2,12}, { 8, 2,12}, + { 22, 1,12}, { 22, 1,12}, { 21, 1,12}, { 21, 1,12}, + { 1, 9,12}, { 1, 9,12}, { 20, 1,12}, { 20, 1,12}, + { 19, 1,12}, { 19, 1,12}, { 2, 5,12}, { 2, 5,12}, + { 4, 3,12}, { 4, 3,12}, { 1, 8,12}, { 1, 8,12}, + { 7, 2,12}, { 7, 2,12}, { 18, 1,12}, { 18, 1,12} +}; + +static const DCTtab DCT_B14_10 [] ICONST_ATTR = { + { 17, 1,10}, { 6, 2,10}, { 1, 7,10}, { 3, 3,10}, + { 2, 4,10}, { 16, 1,10}, { 15, 1,10}, { 5, 2,10} +}; + +static const DCTtab DCT_B14_8 [] ICONST_ATTR = { + { 65, 0, 12}, { 65, 0, 12}, { 65, 0, 12}, { 65, 0, 12}, + { 3, 2, 7}, { 3, 2, 7}, { 10, 1, 7}, { 10, 1, 7}, + { 1, 4, 7}, { 1, 4, 7}, { 9, 1, 7}, { 9, 1, 7}, + { 8, 1, 6}, { 8, 1, 6}, { 8, 1, 6}, { 8, 1, 6}, + { 7, 1, 6}, { 7, 1, 6}, { 7, 1, 6}, { 7, 1, 6}, + { 2, 2, 6}, { 2, 2, 6}, { 2, 2, 6}, { 2, 2, 6}, + { 6, 1, 6}, { 6, 1, 6}, { 6, 1, 6}, { 6, 1, 6}, + { 14, 1, 8}, { 1, 6, 8}, { 13, 1, 8}, { 12, 1, 8}, + { 4, 2, 8}, { 2, 3, 8}, { 1, 5, 8}, { 11, 1, 8} +}; + +static const DCTtab DCT_B14AC_5 [] ICONST_ATTR = { + { 1, 3, 5}, { 5, 1, 5}, { 4, 1, 5}, + { 1, 2, 4}, { 1, 2, 4}, { 3, 1, 4}, { 3, 1, 4}, + { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, + {129, 0, 2}, {129, 0, 2}, {129, 0, 2}, {129, 0, 2}, + {129, 0, 2}, {129, 0, 2}, {129, 0, 2}, {129, 0, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2} +}; + +static const DCTtab DCT_B14DC_5 [] ICONST_ATTR = { + { 1, 3, 5}, { 5, 1, 5}, { 4, 1, 5}, + { 1, 2, 4}, { 1, 2, 4}, { 3, 1, 4}, { 3, 1, 4}, + { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, + { 1, 1, 1}, { 1, 1, 1}, { 1, 1, 1}, { 1, 1, 1}, + { 1, 1, 1}, { 1, 1, 1}, { 1, 1, 1}, { 1, 1, 1}, + { 1, 1, 1}, { 1, 1, 1}, { 1, 1, 1}, { 1, 1, 1}, + { 1, 1, 1}, { 1, 1, 1}, { 1, 1, 1}, { 1, 1, 1} +}; + +static const DCTtab DCT_B15_10 [] ICONST_ATTR = { + { 6, 2, 9}, { 6, 2, 9}, { 15, 1, 9}, { 15, 1, 9}, + { 3, 4,10}, { 17, 1,10}, { 16, 1, 9}, { 16, 1, 9} +}; + +static const DCTtab DCT_B15_8 [] ICONST_ATTR = { + { 65, 0, 12}, { 65, 0, 12}, { 65, 0, 12}, { 65, 0, 12}, + { 8, 1, 7}, { 8, 1, 7}, { 9, 1, 7}, { 9, 1, 7}, + { 7, 1, 7}, { 7, 1, 7}, { 3, 2, 7}, { 3, 2, 7}, + { 1, 7, 6}, { 1, 7, 6}, { 1, 7, 6}, { 1, 7, 6}, + { 1, 6, 6}, { 1, 6, 6}, { 1, 6, 6}, { 1, 6, 6}, + { 5, 1, 6}, { 5, 1, 6}, { 5, 1, 6}, { 5, 1, 6}, + { 6, 1, 6}, { 6, 1, 6}, { 6, 1, 6}, { 6, 1, 6}, + { 2, 5, 8}, { 12, 1, 8}, { 1,11, 8}, { 1,10, 8}, + { 14, 1, 8}, { 13, 1, 8}, { 4, 2, 8}, { 2, 4, 8}, + { 3, 1, 5}, { 3, 1, 5}, { 3, 1, 5}, { 3, 1, 5}, + { 3, 1, 5}, { 3, 1, 5}, { 3, 1, 5}, { 3, 1, 5}, + { 2, 2, 5}, { 2, 2, 5}, { 2, 2, 5}, { 2, 2, 5}, + { 2, 2, 5}, { 2, 2, 5}, { 2, 2, 5}, { 2, 2, 5}, + { 4, 1, 5}, { 4, 1, 5}, { 4, 1, 5}, { 4, 1, 5}, + { 4, 1, 5}, { 4, 1, 5}, { 4, 1, 5}, { 4, 1, 5}, + { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, + { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, + { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, + { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, + { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, + { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, + { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, + { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, { 2, 1, 3}, + {129, 0, 4}, {129, 0, 4}, {129, 0, 4}, {129, 0, 4}, + {129, 0, 4}, {129, 0, 4}, {129, 0, 4}, {129, 0, 4}, + {129, 0, 4}, {129, 0, 4}, {129, 0, 4}, {129, 0, 4}, + {129, 0, 4}, {129, 0, 4}, {129, 0, 4}, {129, 0, 4}, + { 1, 3, 4}, { 1, 3, 4}, { 1, 3, 4}, { 1, 3, 4}, + { 1, 3, 4}, { 1, 3, 4}, { 1, 3, 4}, { 1, 3, 4}, + { 1, 3, 4}, { 1, 3, 4}, { 1, 3, 4}, { 1, 3, 4}, + { 1, 3, 4}, { 1, 3, 4}, { 1, 3, 4}, { 1, 3, 4}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, { 1, 1, 2}, + { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, + { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, + { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, + { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, + { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, + { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, + { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, + { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, { 1, 2, 3}, + { 1, 4, 5}, { 1, 4, 5}, { 1, 4, 5}, { 1, 4, 5}, + { 1, 4, 5}, { 1, 4, 5}, { 1, 4, 5}, { 1, 4, 5}, + { 1, 5, 5}, { 1, 5, 5}, { 1, 5, 5}, { 1, 5, 5}, + { 1, 5, 5}, { 1, 5, 5}, { 1, 5, 5}, { 1, 5, 5}, + { 10, 1, 7}, { 10, 1, 7}, { 2, 3, 7}, { 2, 3, 7}, + { 11, 1, 7}, { 11, 1, 7}, { 1, 8, 7}, { 1, 8, 7}, + { 1, 9, 7}, { 1, 9, 7}, { 1,12, 8}, { 1,13, 8}, + { 3, 3, 8}, { 5, 2, 8}, { 1,14, 8}, { 1,15, 8} +}; + + +static const MBAtab MBA_5 [] ICONST_ATTR = { + {6, 5}, {5, 5}, {4, 4}, {4, 4}, {3, 4}, {3, 4}, + {2, 3}, {2, 3}, {2, 3}, {2, 3}, {1, 3}, {1, 3}, {1, 3}, {1, 3}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, + {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1} +}; + +static const MBAtab MBA_11 [] ICONST_ATTR = { + {32, 11}, {31, 11}, {30, 11}, {29, 11}, + {28, 11}, {27, 11}, {26, 11}, {25, 11}, + {24, 11}, {23, 11}, {22, 11}, {21, 11}, + {20, 10}, {20, 10}, {19, 10}, {19, 10}, + {18, 10}, {18, 10}, {17, 10}, {17, 10}, + {16, 10}, {16, 10}, {15, 10}, {15, 10}, + {14, 8}, {14, 8}, {14, 8}, {14, 8}, + {14, 8}, {14, 8}, {14, 8}, {14, 8}, + {13, 8}, {13, 8}, {13, 8}, {13, 8}, + {13, 8}, {13, 8}, {13, 8}, {13, 8}, + {12, 8}, {12, 8}, {12, 8}, {12, 8}, + {12, 8}, {12, 8}, {12, 8}, {12, 8}, + {11, 8}, {11, 8}, {11, 8}, {11, 8}, + {11, 8}, {11, 8}, {11, 8}, {11, 8}, + {10, 8}, {10, 8}, {10, 8}, {10, 8}, + {10, 8}, {10, 8}, {10, 8}, {10, 8}, + { 9, 8}, { 9, 8}, { 9, 8}, { 9, 8}, + { 9, 8}, { 9, 8}, { 9, 8}, { 9, 8}, + { 8, 7}, { 8, 7}, { 8, 7}, { 8, 7}, + { 8, 7}, { 8, 7}, { 8, 7}, { 8, 7}, + { 8, 7}, { 8, 7}, { 8, 7}, { 8, 7}, + { 8, 7}, { 8, 7}, { 8, 7}, { 8, 7}, + { 7, 7}, { 7, 7}, { 7, 7}, { 7, 7}, + { 7, 7}, { 7, 7}, { 7, 7}, { 7, 7}, + { 7, 7}, { 7, 7}, { 7, 7}, { 7, 7}, + { 7, 7}, { 7, 7}, { 7, 7}, { 7, 7} +}; diff --git a/apps/plugins/mpegplayer/mpeg_alloc.h b/apps/plugins/mpegplayer/mpeg_alloc.h new file mode 100644 index 0000000000..9acfbc5dec --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_alloc.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 by Michael Sevakis + * + * 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. + * + ****************************************************************************/ +#ifndef MPEG_ALLOC_H +#define MPEG_ALLOC_H + +/* returns the remaining mpeg2 buffer and it's size */ +void * mpeg2_get_buf(size_t *size); +void *mpeg_malloc(size_t size, mpeg2_alloc_t reason); +/* Grabs all the buffer available sans margin */ +void *mpeg_malloc_all(size_t *size_out, mpeg2_alloc_t reason); +/* Initializes the malloc buffer with the given base buffer */ +bool mpeg_alloc_init(unsigned char *buf, size_t mallocsize); + +#endif /* MPEG_ALLOC_H */ diff --git a/apps/plugins/mpegplayer/mpeg_misc.c b/apps/plugins/mpegplayer/mpeg_misc.c new file mode 100644 index 0000000000..31f0644212 --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_misc.c @@ -0,0 +1,227 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Miscellaneous helper API definitions + * + * Copyright (c) 2007 Michael Sevakis + * + * 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 "plugin.h" +#include "mpegplayer.h" + +/** Streams **/ + +/* Initializes the cursor */ +void stream_scan_init(struct stream_scan *sk) +{ + dbuf_l2_init(&sk->l2); +} + +/* Ensures direction is -1 or 1 and margin is properly initialized */ +void stream_scan_normalize(struct stream_scan *sk) +{ + if (sk->dir >= 0) + { + sk->dir = SSCAN_FORWARD; + sk->margin = sk->len; + } + else if (sk->dir < 0) + { + sk->dir = SSCAN_REVERSE; + sk->margin = 0; + } +} + +/* Moves a scan cursor. If amount is positive, the increment is in the scan + * direction, otherwise opposite the scan direction */ +void stream_scan_offset(struct stream_scan *sk, off_t by) +{ + off_t bydir = by*sk->dir; + sk->pos += bydir; + sk->margin -= bydir; + sk->len -= by; +} + +/** Time helpers **/ +void ts_to_hms(uint32_t pts, struct hms *hms) +{ + hms->frac = pts % TS_SECOND; + hms->sec = pts / TS_SECOND; + hms->min = hms->sec / 60; + hms->hrs = hms->min / 60; + hms->sec %= 60; + hms->min %= 60; +} + +void hms_format(char *buf, size_t bufsize, struct hms *hms) +{ + /* Only display hours if nonzero */ + if (hms->hrs != 0) + { + rb->snprintf(buf, bufsize, "%u:%02u:%02u", + hms->hrs, hms->min, hms->sec); + } + else + { + rb->snprintf(buf, bufsize, "%u:%02u", + hms->min, hms->sec); + } +} + +/** Maths **/ +uint32_t muldiv_uint32(uint32_t multiplicand, + uint32_t multiplier, + uint32_t divisor) +{ + if (divisor != 0) + { + uint64_t prod = (uint64_t)multiplier*multiplicand + divisor/2; + + if ((uint32_t)(prod >> 32) < divisor) + return (uint32_t)(prod / divisor); + } + else if (multiplicand == 0 || multiplier == 0) + { + return 0; /* 0/0 = 0 : yaya */ + } + /* else (> 0) / 0 = UINT32_MAX */ + + return UINT32_MAX; /* Saturate */ +} + + +/** Lists **/ + +/* Does the list have any members? */ +bool list_is_empty(void **list) +{ + return *list == NULL; +} + +/* Is the item inserted into a particular list? */ +bool list_is_member(void **list, void *item) +{ + return *rb->find_array_ptr(list, item) != NULL; +} + +/* Removes an item from a list - returns true if item was found + * and thus removed. */ +bool list_remove_item(void **list, void *item) +{ + return rb->remove_array_ptr(list, item) != -1; +} + +/* Adds a list item, insert last, if not already present. */ +void list_add_item(void **list, void *item) +{ + void **item_p = rb->find_array_ptr(list, item); + if (*item_p == NULL) + *item_p = item; +} + +/* Clears the entire list. */ +void list_clear_all(void **list) +{ + while (*list != NULL) + *list++ = NULL; +} + +/* Enumerate all items in the array, passing each item in turn to the + * callback as well as the data value. The current item may be safely + * removed. Other changes during enumeration are undefined. The callback + * may return 'false' to stop the enumeration early. */ +void list_enum_items(void **list, + list_enum_callback_t callback, + void* data) +{ + for (;;) + { + void *item = *list; + + if (item == NULL) + break; + + if (callback != NULL && !callback(item, data)) + break; + + if (*list == item) + list++; /* Item still there */ + } +} + + +/** System events **/ +static long mpeg_sysevent_id; + +void mpeg_sysevent_clear(void) +{ + mpeg_sysevent_id = 0; +} + +void mpeg_sysevent_set(void) +{ + /* Nonzero and won't invoke anything in default event handler */ + mpeg_sysevent_id = ACTION_STD_CANCEL; +} + +long mpeg_sysevent(void) +{ + return mpeg_sysevent_id; +} + +int mpeg_sysevent_callback(int btn, + const struct menu_item_ex *menu, + struct gui_synclist *this_list) +{ + (void) this_list; + switch (btn) + { + case SYS_USB_CONNECTED: + case SYS_POWEROFF: + case SYS_REBOOT: + mpeg_sysevent_id = btn; + return ACTION_STD_CANCEL; + } + + return btn; + (void)menu; +} + +void mpeg_sysevent_handle(void) +{ + long id = mpeg_sysevent(); + if (id != 0) + rb->default_event_handler(id); +} + + +/** Buttons **/ + +int mpeg_button_get(int timeout) +{ + int button; + + mpeg_sysevent_clear(); + button = timeout == TIMEOUT_BLOCK ? rb->button_get(true) : + rb->button_get_w_tmo(timeout); + + /* Produce keyclick */ + rb->keyclick_click(true, button); + + return mpeg_sysevent_callback(button, NULL, NULL); +} + diff --git a/apps/plugins/mpegplayer/mpeg_misc.h b/apps/plugins/mpegplayer/mpeg_misc.h new file mode 100644 index 0000000000..e04db0e19d --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_misc.h @@ -0,0 +1,258 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Miscellaneous helper API declarations + * + * Copyright (c) 2007 Michael Sevakis + * + * 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. + * + ****************************************************************************/ +#ifndef MPEG_MISC_H +#define MPEG_MISC_H + +/* Miscellaneous helpers */ +#ifndef ALIGNED_ATTR +#define ALIGNED_ATTR(x) __attribute__((aligned(x))) +#endif + +#include "disk_buf.h" + +/* Generic states for when things are too simple to care about naming them */ +enum state_enum +{ + STATE0 = 0, + STATE1, + STATE2, + STATE3, + STATE4, + STATE5, + STATE6, + STATE7, + STATE8, + STATE9, +}; + +/* Macros for comparing memory bytes to a series of constant bytes in an + efficient manner - evaluate to true if corresponding bytes match */ +#if defined (CPU_ARM) +/* ARM must load 32-bit values at addres % 4 == 0 offsets but this data + isn't aligned nescessarily, so just byte compare */ +#define CMP_3_CONST(_a, _b) \ + ({ int _x; \ + asm volatile ( \ + "ldrb %[x], [%[a], #0] \n" \ + "eors %[x], %[x], %[b0] \n" \ + "ldreqb %[x], [%[a], #1] \n" \ + "eoreqs %[x], %[x], %[b1] \n" \ + "ldreqb %[x], [%[a], #2] \n" \ + "eoreqs %[x], %[x], %[b2] \n" \ + : [x]"=&r"(_x) \ + : [a]"r"(_a), \ + [b0]"i"(((_b) >> 24) & 0xff), \ + [b1]"i"(((_b) >> 16) & 0xff), \ + [b2]"i"(((_b) >> 8) & 0xff) \ + ); \ + _x == 0; }) + +#define CMP_4_CONST(_a, _b) \ + ({ int _x; \ + asm volatile ( \ + "ldrb %[x], [%[a], #0] \n" \ + "eors %[x], %[x], %[b0] \n" \ + "ldreqb %[x], [%[a], #1] \n" \ + "eoreqs %[x], %[x], %[b1] \n" \ + "ldreqb %[x], [%[a], #2] \n" \ + "eoreqs %[x], %[x], %[b2] \n" \ + "ldreqb %[x], [%[a], #3] \n" \ + "eoreqs %[x], %[x], %[b3] \n" \ + : [x]"=&r"(_x) \ + : [a]"r"(_a), \ + [b0]"i"(((_b) >> 24) & 0xff), \ + [b1]"i"(((_b) >> 16) & 0xff), \ + [b2]"i"(((_b) >> 8) & 0xff), \ + [b3]"i"(((_b) ) & 0xff) \ + ); \ + _x == 0; }) + +#elif defined (CPU_COLDFIRE) +/* Coldfire can just load a 32 bit value at any offset but ASM is not the + best way to integrate this with the C code */ +#define CMP_3_CONST(a, b) \ + (((*(uint32_t *)(a) >> 8) == ((uint32_t)(b) >> 8))) + +#define CMP_4_CONST(a, b) \ + ((*(uint32_t *)(a) == (b))) + +#else +/* Don't know what this is - use bytewise comparisons */ +#define CMP_3_CONST(a, b) \ + (( ((a)[0] ^ (((b) >> 24) & 0xff)) | \ + ((a)[1] ^ (((b) >> 16) & 0xff)) | \ + ((a)[2] ^ (((b) >> 8) & 0xff)) ) == 0) + +#define CMP_4_CONST(a, b) \ + (( ((a)[0] ^ (((b) >> 24) & 0xff)) | \ + ((a)[1] ^ (((b) >> 16) & 0xff)) | \ + ((a)[2] ^ (((b) >> 8) & 0xff)) | \ + ((a)[3] ^ (((b) ) & 0xff)) ) == 0) +#endif /* CPU_* */ + + +/** Streams **/ + +/* Convert PTS/DTS ticks to our clock ticks */ +#define TS_TO_TICKS(pts) ((uint64_t)CLOCK_RATE*(pts) / TS_SECOND) +/* Convert our clock ticks to PTS/DTS ticks */ +#define TICKS_TO_TS(ts) ((uint64_t)TS_SECOND*(ts) / CLOCK_RATE) +/* Convert timecode ticks to our clock ticks */ +#define TC_TO_TICKS(stamp) ((uint64_t)CLOCK_RATE*(stamp) / TC_SECOND) +/* Convert our clock ticks to timecode ticks */ +#define TICKS_TO_TC(stamp) ((uint64_t)TC_SECOND*(stamp) / CLOCK_RATE) +/* Convert timecode ticks to timestamp ticks */ +#define TC_TO_TS(stamp) ((stamp) / 600) + +/* + * S = start position, E = end position + * + * pos: + * initialize to search start position (S) + * + * len: + * initialize to = ABS(S-E) + * scanning = remaining bytes in scan direction + * + * dir: + * scan direction; >= 0 == forward, < 0 == reverse + * + * margin: + * amount of data to right of cursor - initialize by stream_scan_normalize + * + * data: + * Extra data used/returned by the function implemented + * + * Forward scan: + * S pos E + * | *<-margin->| dir-> + * | |<--len--->| + * + * Reverse scan: + * E pos S + * |<-len->*<-margin->| <-dir + * | | | + */ +struct stream_scan +{ + off_t pos; /* Initial scan position (file offset) */ + ssize_t len; /* Maximum length of scan */ + off_t dir; /* Direction - >= 0; forward, < 0 backward */ + ssize_t margin; /* Used by function to track margin between position and data end */ + intptr_t data; /* */ + struct dbuf_l2_cache l2; +}; + +#define SSCAN_REVERSE (-1) +#define SSCAN_FORWARD 1 + +/* Initializes the cursor */ +void stream_scan_init(struct stream_scan *sk); + +/* Ensures direction is -1 or 1 and margin is properly initialized */ +void stream_scan_normalize(struct stream_scan *sk); + +/* Moves a scan cursor. If amount is positive, the increment is in the scan + * direction, otherwise opposite the scan direction */ +void stream_scan_offset(struct stream_scan *sk, off_t by); + +/** Time helpers **/ +struct hms +{ + unsigned int hrs; + unsigned int min; + unsigned int sec; + unsigned int frac; +}; + +void ts_to_hms(uint32_t ts, struct hms *hms); +void hms_format(char *buf, size_t bufsize, struct hms *hms); + +/** Maths **/ + +/* Moving average */ +#define AVERAGE(var, x, count) \ + ({ typeof (count) _c = (count); \ + ((var) * (_c-1) + (x)) / (_c); }) + +/* Multiply two unsigned 32-bit integers yielding a 64-bit result and + * divide by another unsigned 32-bit integer to yield a 32-bit result. + * Rounds to nearest with saturation. */ +uint32_t muldiv_uint32(uint32_t multiplicand, + uint32_t multiplier, + uint32_t divisor); + + +/** Lists **/ + +/* Does the list have any members? */ +bool list_is_empty(void **list); + +/* Is the item inserted into a particular list? */ +bool list_is_member(void **list, void *item); + +/* Removes an item from a list - returns true if item was found + * and thus removed. */ +bool list_remove_item(void **list, void *item); + +/* Adds a list item, insert last, if not already present. */ +void list_add_item(void **list, void *item); + +/* Clears the entire list. */ +void list_clear_all(void **list); + +/* Enumerate all items in the array. */ +typedef bool (*list_enum_callback_t)(void *item, void* data); + +void list_enum_items(void **list, + list_enum_callback_t callback, + void *data); + + +/** System events **/ + +/* Clear event */ +void mpeg_sysevent_clear(void); + +/* Set to ACTION_STD_CANCEL */ +void mpeg_sysevent_set(void); + +/* Get event code */ +long mpeg_sysevent(void); + +/* Call with a system event code and used as menu callback */ +int mpeg_sysevent_callback(int btn, const struct menu_item_ex *menu, + struct gui_synclist *this_list); + +/* Handle recorded event */ +void mpeg_sysevent_handle(void); + + +/** Buttons **/ + +/* Get button codes while remembering important events for later + * processing; return of ACTION_STD_CANCEL means plugin should + * abort and handle the event */ +int mpeg_button_get(int timeout); + +#endif /* MPEG_MISC_H */ diff --git a/apps/plugins/mpegplayer/mpeg_parser.c b/apps/plugins/mpegplayer/mpeg_parser.c new file mode 100644 index 0000000000..cc57b0c43c --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_parser.c @@ -0,0 +1,1203 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Parser for MPEG streams + * + * Copyright (c) 2007 Michael Sevakis + * + * 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 "plugin.h" +#include "mpegplayer.h" + +struct stream_parser str_parser SHAREDBSS_ATTR; + +static void parser_init_state(void) +{ + str_parser.last_seek_time = 0; + str_parser.format = STREAM_FMT_UNKNOWN; + str_parser.start_pts = INVALID_TIMESTAMP; + str_parser.end_pts = INVALID_TIMESTAMP; + str_parser.flags = 0; + str_parser.dims.w = 0; + str_parser.dims.h = 0; +} + +/* Place the stream in a state to begin parsing - sync will be performed + * first */ +void str_initialize(struct stream *str, off_t pos) +{ + /* Initial positions start here */ + str->hdr.win_left = str->hdr.win_right = pos; + /* No packet */ + str->curr_packet = NULL; + /* Pick up parsing from this point in the buffer */ + str->curr_packet_end = disk_buf_offset2ptr(pos); + /* No flags */ + str->pkt_flags = 0; + /* Sync first */ + str->state = SSTATE_SYNC; +} + +/* Place the stream in an end of data state */ +void str_end_of_stream(struct stream *str) +{ + /* Offsets that prevent this stream from being included in the + * min left/max right window so that no buffering is triggered on + * its behalf. Set right to the min first so a thread reading the + * overall window gets doesn't see this as valid no matter what the + * file length. */ + str->hdr.win_right = OFF_T_MIN; + str->hdr.win_left = OFF_T_MAX; + /* No packets */ + str->curr_packet = str->curr_packet_end = NULL; + /* No flags */ + str->pkt_flags = 0; + /* Fin */ + str->state = SSTATE_END; +} + +/* Return a timestamp at address p+offset if the marker bits are in tact */ +static inline uint32_t read_pts(uint8_t *p, off_t offset) +{ + return TS_CHECK_MARKERS(p, offset) ? + TS_FROM_HEADER(p, offset) : INVALID_TIMESTAMP; +} + +static inline bool validate_timestamp(uint32_t ts) +{ + return ts >= str_parser.start_pts && ts <= str_parser.end_pts; +} + +/* Find a start code before or after a given position */ +uint8_t * mpeg_parser_scan_start_code(struct stream_scan *sk, uint32_t code) +{ + stream_scan_normalize(sk); + + if (sk->dir < 0) + { + /* Reverse scan - start with at least the min needed */ + stream_scan_offset(sk, 4); + } + + code &= 0xff; /* Only the low byte matters */ + + while (sk->len >= 0 && sk->margin >= 4) + { + uint8_t *p; + off_t pos = disk_buf_lseek(sk->pos, SEEK_SET); + ssize_t len = disk_buf_getbuffer_l2(&sk->l2, 4, &p); + + if (pos < 0 || len < 4) + break; + + if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX) && p[3] == code) + { + return p; + } + + stream_scan_offset(sk, 1); + } + + return NULL; +} + +/* Find a PES packet header for any stream - return stream to which it + * belongs */ +unsigned mpeg_parser_scan_pes(struct stream_scan *sk) +{ + stream_scan_normalize(sk); + + if (sk->dir < 0) + { + /* Reverse scan - start with at least the min needed */ + stream_scan_offset(sk, 4); + } + + while (sk->len >= 0 && sk->margin >= 4) + { + uint8_t *p; + off_t pos = disk_buf_lseek(sk->pos, SEEK_SET); + ssize_t len = disk_buf_getbuffer_l2(&sk->l2, 4, &p); + + if (pos < 0 || len < 4) + break; + + if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX)) + { + unsigned id = p[3]; + if (id >= 0xb9) + return id; /* PES header */ + /* else some video stream element */ + } + + stream_scan_offset(sk, 1); + } + + return -1; +} + +/* Return the first SCR found from the scan direction */ +uint32_t mpeg_parser_scan_scr(struct stream_scan *sk) +{ + uint8_t *p = mpeg_parser_scan_start_code(sk, MPEG_STREAM_PACK_HEADER); + + if (p != NULL && sk->margin >= 9) /* 9 bytes total required */ + { + sk->data = 9; + + if ((p[4] & 0xc0) == 0x40) /* mpeg-2 */ + { + /* Lookhead p+8 */ + if (MPEG2_CHECK_PACK_SCR_MARKERS(p, 4)) + return MPEG2_PACK_HEADER_SCR(p, 4); + } + else if ((p[4] & 0xf0) == 0x20) /* mpeg-1 */ + { + /* Lookahead p+8 */ + if (TS_CHECK_MARKERS(p, 4)) + return TS_FROM_HEADER(p, 4); + } + /* Weird pack header */ + sk->data = 5; + } + + return INVALID_TIMESTAMP; +} + +uint32_t mpeg_parser_scan_pts(struct stream_scan *sk, unsigned id) +{ + stream_scan_normalize(sk); + + if (sk->dir < 0) + { + /* Reverse scan - start with at least the min needed */ + stream_scan_offset(sk, 4); + } + + while (sk->len >= 0 && sk->margin >= 4) + { + uint8_t *p; + off_t pos = disk_buf_lseek(sk->pos, SEEK_SET); + ssize_t len = disk_buf_getbuffer_l2(&sk->l2, 30, &p); + + if (pos < 0 || len < 4) + break; + + if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX) && p[3] == id) + { + uint8_t *h = p; + + if (sk->margin < 7) + { + /* Insufficient data */ + } + else if ((h[6] & 0xc0) == 0x80) /* mpeg2 */ + { + if (sk->margin >= 14 && (h[7] & 0x80) != 0x00) + { + sk->data = 14; + return read_pts(h, 9); + } + } + else /* mpeg1 */ + { + ssize_t l = 6; + ssize_t margin = sk->margin; + + /* Skip stuffing_byte */ + while (margin > 7 && h[l] == 0xff && ++l <= 22) + --margin; + + if (margin >= 7) + { + if ((h[l] & 0xc0) == 0x40) + { + /* Skip STD_buffer_scale and STD_buffer_size */ + margin -= 2; + l += 2; + } + + if (margin >= 5) + { + /* Header points to the mpeg1 pes header */ + h += l; + + if ((h[0] & 0xe0) == 0x20) + { + /* PTS or PTS_DTS indicated */ + sk->data = (h + 5) - p; + return read_pts(h, 0); + } + } + } + } + /* No PTS present - keep searching for a matching PES header with + * one */ + } + + stream_scan_offset(sk, 1); + } + + return INVALID_TIMESTAMP; +} + +static bool init_video_info(void) +{ + DEBUGF("Getting movie size\n"); + + /* The decoder handles this in order to initialize its knowledge of the + * movie parameters making seeking easier */ + str_send_msg(&video_str, STREAM_RESET, 0); + if (str_send_msg(&video_str, VIDEO_GET_SIZE, + (intptr_t)&str_parser.dims) != 0) + { + return true; + } + + DEBUGF(" failed\n"); + return false; +} + +static bool init_times(struct stream *str) +{ + struct stream tmp_str; + const ssize_t filesize = disk_buf_filesize(); + const ssize_t max_probe = MIN(512*1024, filesize); + bool found_stream; + + /* Simply find the first earliest timestamp - this will be the one + * used when streaming anyway */ + DEBUGF("Finding start_pts: 0x%02x\n", str->id); + + found_stream = false; + str->start_pts = INVALID_TIMESTAMP; + str->end_pts = INVALID_TIMESTAMP; + + tmp_str.id = str->id; + tmp_str.hdr.pos = 0; + tmp_str.hdr.limit = max_probe; + + /* Probe for many for the start because some stamps could be anomalous. + * Video also can also have things out of order. Just see what it's got. + */ + while (1) + { + switch (parser_get_next_data(&tmp_str, STREAM_PM_RANDOM_ACCESS)) + { + case STREAM_DATA_END: + break; + case STREAM_OK: + found_stream = true; + if (tmp_str.pkt_flags & PKT_HAS_TS) + { + if (tmp_str.pts < str->start_pts) + str->start_pts = tmp_str.pts; + } + continue; + } + + break; + } + + if (!found_stream) + { + DEBUGF(" stream not found:0x%02x\n", str->id); + return false; + } + + DEBUGF(" start:%u\n", (unsigned)str->start_pts); + + /* Use the decoder thread to perform a synchronized search - no + * decoding should take place but just a simple run through timestamps + * and durations as the decoder would see them. This should give the + * precise time at the end of the last frame for the stream. */ + DEBUGF("Finding end_pts: 0x%02x\n", str->id); + + str_parser.parms.sd.time = MAX_TIMESTAMP; + str_parser.parms.sd.sk.pos = filesize - max_probe; + str_parser.parms.sd.sk.len = max_probe; + str_parser.parms.sd.sk.dir = SSCAN_FORWARD; + + str_send_msg(str, STREAM_RESET, 0); + + if (str_send_msg(str, STREAM_FIND_END_TIME, + (intptr_t)&str_parser.parms.sd) == STREAM_PERFECT_MATCH) + { + str->end_pts = str_parser.parms.sd.time; + DEBUGF(" end:%u\n", (unsigned)str->end_pts); + } + + return true; +} + +static bool check_times(const struct stream *str) +{ + return str->start_pts < str->end_pts && + str->end_pts != INVALID_TIMESTAMP; +} + +/* Return the best-fit file offset of a timestamp in the PES where + * timstamp <= time < next timestamp. Will try to return something reasonably + * valid if best-fit could not be made. */ +static off_t mpeg_parser_seek_PTS(uint32_t time, unsigned id) +{ + ssize_t pos_left = 0; + ssize_t pos_right = disk_buf.filesize; + ssize_t pos, pos_new; + uint32_t time_left = str_parser.start_pts; + uint32_t time_right = str_parser.end_pts; + uint32_t pts = 0; + uint32_t prevpts = 0; + enum state_enum state = STATE0; + struct stream_scan sk; + + stream_scan_init(&sk); + + /* Initial estimate taken from average bitrate - later interpolations are + * taken similarly based on the remaining file interval */ + pos_new = muldiv_uint32(time - time_left, pos_right - pos_left, + time_right - time_left) + pos_left; + + /* return this estimated position if nothing better comes up */ + pos = pos_new; + + DEBUGF("Seeking stream 0x%02x\n", id); + DEBUGF("$$ tl:%u t:%u ct:?? tr:%u\n pl:%ld pn:%ld pr:%ld\n", + (unsigned)time_left, (unsigned)time, (unsigned)time_right, + (long)pos_left, (long)pos_new, (long)pos_right); + + sk.dir = SSCAN_REVERSE; + + while (state < STATE9) + { + uint32_t currpts; + sk.pos = pos_new; + sk.len = (sk.dir < 0) ? pos_new - pos_left : pos_right - pos_new; + + currpts = mpeg_parser_scan_pts(&sk, id); + + if (currpts != INVALID_TIMESTAMP) + { + ssize_t pos_adj; /* Adjustment to over or under-estimate */ + + /* Found a valid timestamp - see were it lies in relation to + * target */ + if (currpts < time) + { + /* Time at current position is before seek time - move + * forward */ + if (currpts > pts) + { + /* This is less than the desired time but greater than + * the currently seeked one; move the position up */ + pts = currpts; + pos = sk.pos; + } + + /* No next timestamp can be sooner */ + pos_left = sk.pos + sk.data; + time_left = currpts; + + if (pos_right <= pos_left) + break; /* If the window disappeared - we're done */ + + pos_new = muldiv_uint32(time - time_left, + pos_right - pos_left, + time_right - time_left); + /* Point is ahead of us - fudge estimate a bit high */ + pos_adj = pos_new / 10; + + if (pos_adj > 512*1024) + pos_adj = 512*1024; + + pos_new += pos_left + pos_adj; + + if (pos_new >= pos_right) + { + /* Estimate could push too far */ + pos_new = pos_right; + } + + state = STATE2; /* Last scan was early */ + sk.dir = SSCAN_REVERSE; + + DEBUGF(">> tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n", + (unsigned)time_left, (unsigned)time, (unsigned)currpts, + (unsigned)time_right, (long)pos_left, (long)pos_new, + (long)pos_right); + } + else if (currpts > time) + { + /* Time at current position is past seek time - move + backward */ + pos_right = sk.pos; + time_right = currpts; + + if (pos_right <= pos_left) + break; /* If the window disappeared - we're done */ + + pos_new = muldiv_uint32(time - time_left, + pos_right - pos_left, + time_right - time_left); + /* Overshot the seek point - fudge estimate a bit low */ + pos_adj = pos_new / 10; + + if (pos_adj > 512*1024) + pos_adj = 512*1024; + + pos_new += pos_left - pos_adj; + + state = STATE3; /* Last scan was late */ + sk.dir = SSCAN_REVERSE; + + DEBUGF("<< tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n", + (unsigned)time_left, (unsigned)time, (unsigned)currpts, + (unsigned)time_right, (long)pos_left, (long)pos_new, + (long)pos_right); + } + else + { + /* Exact match - it happens */ + DEBUGF("|| tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n", + (unsigned)time_left, (unsigned)time, (unsigned)currpts, + (unsigned)time_right, (long)pos_left, (long)pos_new, + (long)pos_right); + pts = currpts; + pos = sk.pos; + state = STATE9; + } + } + else + { + /* Nothing found */ + + switch (state) + { + case STATE1: + /* We already tried the bruteforce scan and failed again - no + * more stamps could possibly exist in the interval */ + DEBUGF("!! no timestamp 2x\n"); + break; + case STATE0: + /* Hardly likely except at very beginning - just do L->R scan + * to find something */ + DEBUGF("!! no timestamp on first probe: %ld\n", sk.pos); + case STATE2: + case STATE3: + /* Could just be missing timestamps because the interval is + * narrowing down. A large block of data from another stream + * may also be in the midst of our chosen points which could + * cluster at either extreme end. If anything is there, this + * will find it. */ + pos_new = pos_left; + sk.dir = SSCAN_FORWARD; + DEBUGF("?? tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n", + (unsigned)time_left, (unsigned)time, (unsigned)currpts, + (unsigned)time_right, (long)pos_left, (long)pos_new, + (long)pos_right); + state = STATE1; + break; + default: + DEBUGF("?? Invalid state: %d\n", state); + } + } + + /* Same timestamp twice = quit */ + if (currpts == prevpts) + { + DEBUGF("!! currpts == prevpts (stop)\n"); + state = STATE9; + } + + prevpts = currpts; + } + +#if defined(DEBUG) || defined(SIMULATOR) + /* The next pts after the seeked-to position should be greater - + * most of the time - frames out of presentation order may muck it + * up a slight bit */ + sk.pos = pos + 1; + sk.len = disk_buf.filesize; + sk.dir = SSCAN_FORWARD; + + uint32_t nextpts = mpeg_parser_scan_pts(&sk, id); + DEBUGF("Seek pos:%ld pts:%u t:%u next pts:%u \n", + (long)pos, (unsigned)pts, (unsigned)time, (unsigned)nextpts); + + if (pts <= time && time < nextpts) + { + /* Smile - it worked */ + DEBUGF(" :) pts<=time time) + { + /* Hmm */ + DEBUGF(" :\\ pts>time\n"); + } + if (pts >= nextpts) + { + /* Weird - probably because of encoded order & tends to be right + * anyway if other criteria are met */ + DEBUGF(" :p pts>=next pts\n"); + } + if (time >= nextpts) + { + /* Ugh */ + DEBUGF(" :( time>=nextpts\n"); + } + } +#endif + + return pos; +} + +static void prepare_audio(uint32_t time) +{ + off_t pos; + + if (!str_send_msg(&audio_str, STREAM_NEEDS_SYNC, time)) + { + DEBUGF("Audio was ready\n"); + return; + } + + pos = mpeg_parser_seek_PTS(time, audio_str.id); + str_send_msg(&audio_str, STREAM_RESET, 0); + + str_parser.parms.sd.time = time; + str_parser.parms.sd.sk.pos = pos; + str_parser.parms.sd.sk.len = 1024*1024; + str_parser.parms.sd.sk.dir = SSCAN_FORWARD; + + str_send_msg(&audio_str, STREAM_SYNC, (intptr_t)&str_parser.parms.sd); +} + +/* This function demuxes the streams and gives the next stream data + * pointer. + * + * STREAM_PM_STREAMING is for operation during playback. If the nescessary + * data and worst-case lookahead margin is not available, the stream is + * registered for notification when the data becomes available. If parsing + * extends beyond the end of the file or the end of stream marker is reached, + * STREAM_DATA_END is returned and the stream state changed to SSTATE_EOS. + * + * STREAM_PM_RANDOM_ACCESS is for operation when not playing such as seeking. + * If the file cache misses for the current position + lookahead, it will be + * loaded from disk. When the specified limit is reached, STREAM_DATA_END is + * returned. + * + * The results from one mode may be used as input to the other. Random access + * requires cooperation amongst threads to avoid evicting another stream's + * data. + */ +static int parse_demux(struct stream *str, enum stream_parse_mode type) +{ + #define INC_BUF(offset) \ + ({ off_t _o = (offset); \ + str->hdr.win_right += _o; \ + if ((p += _o) >= disk_buf.end) \ + p -= disk_buf.size; }) + + static const int mpeg1_skip_table[16] = + { 0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + uint8_t *p = str->curr_packet_end; + + str->pkt_flags = 0; + + while (1) + { + uint8_t *header; + unsigned id; + ssize_t length, bytes; + + switch (type) + { + case STREAM_PM_STREAMING: + /* Has the end been reached already? */ + switch (str->state) + { + case SSTATE_PARSE: /* Expected case first if no jumptable */ + /* Are we at the end of file? */ + if (str->hdr.win_left < disk_buf.filesize) + break; + str_end_of_stream(str); + return STREAM_DATA_END; + + case SSTATE_SYNC: + /* Is sync at the end of file? */ + if (str->hdr.win_right < disk_buf.filesize) + break; + str_end_of_stream(str); + /* Fall-through */ + case SSTATE_END: + return STREAM_DATA_END; + } + + if (!disk_buf_is_data_ready(&str->hdr, MIN_BUFAHEAD)) + { + /* This data range is not buffered yet - register stream to + * be notified when it becomes available. Stream is obliged + * to enter a TSTATE_DATA state if it must wait. */ + int res = str_next_data_not_ready(str); + + if (res != STREAM_OK) + return res; + } + break; + /* STREAM_PM_STREAMING: */ + + case STREAM_PM_RANDOM_ACCESS: + str->hdr.pos = disk_buf_lseek(str->hdr.pos, SEEK_SET); + + if (str->hdr.pos < 0 || str->hdr.pos >= str->hdr.limit || + disk_buf_getbuffer(MIN_BUFAHEAD, &p, NULL, NULL) <= 0) + { + str_end_of_stream(str); + return STREAM_DATA_END; + } + + str->state = SSTATE_SYNC; + str->hdr.win_left = str->hdr.pos; + str->curr_packet = NULL; + str->curr_packet_end = p; + break; + /* STREAM_PM_RANDOM_ACCESS: */ + } + + if (str->state == SSTATE_SYNC) + { + /* Scanning for start code */ + if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX)) + { + INC_BUF(1); + continue; + } + } + + /* Found a start code - enter parse state */ + str->state = SSTATE_PARSE; + + /* Pack header, skip it */ + if (CMP_4_CONST(p, PACK_START_CODE)) + { + /* Max lookahead: 14 */ + if ((p[4] & 0xc0) == 0x40) /* mpeg-2 */ + { + /* Max delta: 14 + 7 = 21 */ + /* Skip pack header and any stuffing bytes*/ + bytes = 14 + (p[13] & 7); + } + else if ((p[4] & 0xf0) == 0x20) /* mpeg-1 */ + { + bytes = 12; + } + else /* unknown - skip it */ + { + DEBUGF("weird pack header!\n"); + bytes = 5; + } + + INC_BUF(bytes); + } + + /* System header, parse and skip it - 6 bytes + size */ + if (CMP_4_CONST(p, SYSTEM_HEADER_START_CODE)) + { + /* Skip start code */ + /* Max Delta = 65535 + 6 = 65541 */ + bytes = 6 + ((p[4] << 8) | p[5]); + INC_BUF(bytes); + } + + /* Packet header, parse it */ + if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX)) + { + /* Problem? Meh...probably not but just a corrupted section. + * Try to resync the parser which will probably succeed. */ + DEBUGF("packet start code prefix not found: 0x%02x\n" + " wl:%lu wr:%lu\n" + " p:%p cp:%p cpe:%p\n" + " dbs:%p dbe:%p dbt:%p\n", + str->id, str->hdr.win_left, str->hdr.win_right, + p, str->curr_packet, str->curr_packet_end, + disk_buf.start, disk_buf.end, disk_buf.tail); + str->state = SSTATE_SYNC; + INC_BUF(1); /* Next byte - this one's no good */ + continue; + } + + /* We retrieve basic infos */ + /* Maximum packet length: 6 + 65535 = 65541 */ + id = p[3]; + length = ((p[4] << 8) | p[5]) + 6; + + if (id != str->id) + { + switch (id) + { + case MPEG_STREAM_PROGRAM_END: + /* end of stream */ + str_end_of_stream(str); + DEBUGF("MPEG program end: 0x%02x\n", str->id); + return STREAM_DATA_END; + case MPEG_STREAM_PACK_HEADER: + case MPEG_STREAM_SYSTEM_HEADER: + /* These shouldn't be here - no increment or resync + * since we'll pick it up above. */ + continue; + default: + /* It's not the packet we're looking for, skip it */ + INC_BUF(length); + continue; + } + } + + /* Ok, it's our packet */ + header = p; + + if ((header[6] & 0xc0) == 0x80) /* mpeg2 */ + { + /* Max Lookahead: 18 */ + /* Min length: 9 */ + /* Max length: 9 + 255 = 264 */ + length = 9 + header[8]; + + /* header points to the mpeg2 pes header */ + if ((header[7] & 0x80) != 0) + { + /* header has a pts */ + uint32_t pts = read_pts(header, 9); + + if (pts != INVALID_TIMESTAMP) + { + str->pts = pts; +#if 0 + /* DTS isn't used for anything since things just get + decoded ASAP but keep the code around */ + if (STREAM_IS_VIDEO(id)) + { + /* Video stream - header may have a dts as well */ + str->dts = pts; + + if (header[7] & 0x40) != 0x00) + { + pts = read_pts(header, 14); + if (pts != INVALID_TIMESTAMP) + str->dts = pts; + } + } +#endif + str->pkt_flags |= PKT_HAS_TS; + } + } + } + else /* mpeg1 */ + { + /* Max lookahead: 24 + 2 + 9 = 35 */ + /* Max len_skip: 24 + 2 = 26 */ + /* Min length: 7 */ + /* Max length: 24 + 2 + 9 = 35 */ + off_t len_skip; + uint8_t * ptsbuf; + + length = 7; + + while (header[length - 1] == 0xff) + { + if (++length > 23) + { + DEBUGF("Too much stuffing" ); + break; + } + } + + if ((header[length - 1] & 0xc0) == 0x40) + length += 2; + + len_skip = length; + length += mpeg1_skip_table[header[length - 1] >> 4]; + + /* Header points to the mpeg1 pes header */ + ptsbuf = header + len_skip; + + if ((ptsbuf[-1] & 0xe0) == 0x20 && TS_CHECK_MARKERS(ptsbuf, -1)) + { + /* header has a pts */ + uint32_t pts = read_pts(ptsbuf, -1); + + if (pts != INVALID_TIMESTAMP) + { + str->pts = pts; +#if 0 + /* DTS isn't used for anything since things just get + decoded ASAP but keep the code around */ + if (STREAM_IS_VIDEO(id)) + { + /* Video stream - header may have a dts as well */ + str->dts = pts; + + if (ptsbuf[-1] & 0xf0) == 0x30) + { + pts = read_pts(ptsbuf, 4); + + if (pts != INVALID_TIMESTAMP) + str->dts = pts; + } + } +#endif + str->pkt_flags |= PKT_HAS_TS; + } + } + } + + p += length; + /* Max bytes: 6 + 65535 - 7 = 65534 */ + bytes = 6 + (header[4] << 8) + header[5] - length; + + str->curr_packet = p; + str->curr_packet_end = p + bytes; + str->hdr.win_left = str->hdr.win_right + length; + str->hdr.win_right = str->hdr.win_left + bytes; + + if (str->hdr.win_right > disk_buf.filesize) + { + /* No packet that exceeds end of file can be valid */ + str_end_of_stream(str); + return STREAM_DATA_END; + } + + return STREAM_OK; + } /* end while */ + + #undef INC_BUF +} + +/* This simply reads data from the file one page at a time and returns a + * pointer to it in the buffer. */ +static int parse_elementary(struct stream *str, enum stream_parse_mode type) +{ + uint8_t *p; + ssize_t len = 0; + + str->pkt_flags = 0; + + switch (type) + { + case STREAM_PM_STREAMING: + /* Has the end been reached already? */ + if (str->state == SSTATE_END) + return STREAM_DATA_END; + + /* Are we at the end of file? */ + if (str->hdr.win_left >= disk_buf.filesize) + { + str_end_of_stream(str); + return STREAM_DATA_END; + } + + if (!disk_buf_is_data_ready(&str->hdr, MIN_BUFAHEAD)) + { + /* This data range is not buffered yet - register stream to + * be notified when it becomes available. Stream is obliged + * to enter a TSTATE_DATA state if it must wait. */ + int res = str_next_data_not_ready(str); + + if (res != STREAM_OK) + return res; + } + + len = DISK_BUF_PAGE_SIZE; + + if ((size_t)(str->hdr.win_right + len) > (size_t)disk_buf.filesize) + len = disk_buf.filesize - str->hdr.win_right; + + if (len <= 0) + { + str_end_of_stream(str); + return STREAM_DATA_END; + } + + p = str->curr_packet_end; + if (p >= disk_buf.end) + p -= disk_buf.size; + break; + /* STREAM_PM_STREAMING: */ + + case STREAM_PM_RANDOM_ACCESS: + str->hdr.pos = disk_buf_lseek(str->hdr.pos, SEEK_SET); + len = disk_buf_getbuffer(DISK_BUF_PAGE_SIZE, &p, NULL, NULL); + + if (len <= 0 || str->hdr.pos < 0 || str->hdr.pos >= str->hdr.limit) + { + str_end_of_stream(str); + return STREAM_DATA_END; + } + break; + /* STREAM_PM_RANDOM_ACCESS: */ + } + + str->state = SSTATE_PARSE; + str->curr_packet = p; + str->curr_packet_end = p + len; + str->hdr.win_left = str->hdr.win_right; + str->hdr.win_right = str->hdr.win_left + len; + + return STREAM_OK; +} + +bool parser_prepare_image(uint32_t time) +{ + struct stream_scan sk; + int tries; + int result; + + stream_scan_init(&sk); + + if (!str_send_msg(&video_str, STREAM_NEEDS_SYNC, time)) + { + DEBUGF("Image was ready\n"); + return true; /* Should already have the image */ + } + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(true); /* No interference with trigger_cpu_boost */ +#endif + + str_send_msg(&video_str, STREAM_RESET, 0); + + sk.pos = parser_can_seek() ? + mpeg_parser_seek_PTS(time, video_str.id) : 0; + sk.len = sk.pos; + sk.dir = SSCAN_REVERSE; + + tries = 1; +try_again: + + if (mpeg_parser_scan_start_code(&sk, MPEG_START_GOP)) + { + DEBUGF("GOP found at: %ld\n", sk.pos); + + unsigned id = mpeg_parser_scan_pes(&sk); + + if (id != video_str.id && sk.pos > 0) + { + /* Not part of our stream */ + DEBUGF(" wrong stream: 0x%02x\n", id); + goto try_again; + } + + /* This will hit the PES header since it's known to be there */ + uint32_t pts = mpeg_parser_scan_pts(&sk, id); + + if (pts == INVALID_TIMESTAMP || pts > time) + { + DEBUGF(" wrong timestamp: %u\n", (unsigned)pts); + goto try_again; + } + } + + str_parser.parms.sd.time = time; + str_parser.parms.sd.sk.pos = MAX(sk.pos, 0); + str_parser.parms.sd.sk.len = 1024*1024; + str_parser.parms.sd.sk.dir = SSCAN_FORWARD; + + DEBUGF("thumb pos:%ld len:%ld\n", str_parser.parms.sd.sk.pos, + (long)str_parser.parms.sd.sk.len); + + result = str_send_msg(&video_str, STREAM_SYNC, + (intptr_t)&str_parser.parms.sd); + + if (result != STREAM_PERFECT_MATCH) + { + /* Two tries should be all that is nescessary to find the exact frame + * if the first GOP actually started later than the timestamp - the + * GOP just prior must then start on or earlier. */ + if (++tries <= 2) + goto try_again; + } + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(false); +#endif + + return result > STREAM_OK; +} + +/* Seek parser to the specified time and return absolute time. + * No actual hard stuff is performed here. That's done when streaming is + * about to begin or something from the current position is requested */ +uint32_t parser_seek_time(uint32_t time) +{ + if (!parser_can_seek()) + time = 0; + else if (time > str_parser.duration) + time = str_parser.duration; + + str_parser.last_seek_time = time + str_parser.start_pts; + return str_parser.last_seek_time; +} + +void parser_prepare_streaming(void) +{ + struct stream_window sw; + + DEBUGF("parser_prepare_streaming\n"); + + /* Prepare initial video frame */ + parser_prepare_image(str_parser.last_seek_time); + + /* Sync audio stream */ + if (audio_str.start_pts != INVALID_TIMESTAMP) + prepare_audio(str_parser.last_seek_time); + + /* Prequeue some data and set buffer window */ + if (!stream_get_window(&sw)) + sw.left = sw.right = disk_buf.filesize; + + DEBUGF(" swl:%ld swr:%ld\n", sw.left, sw.right); + + if (sw.right > disk_buf.filesize - 4*MIN_BUFAHEAD) + sw.right = disk_buf.filesize - 4*MIN_BUFAHEAD; + + disk_buf_prepare_streaming(sw.left, + sw.right - sw.left + 4*MIN_BUFAHEAD); +} + +int parser_init_stream(void) +{ + if (disk_buf.in_file < 0) + return STREAM_ERROR; + + /* TODO: Actually find which streams are available */ + audio_str.id = MPEG_STREAM_AUDIO_FIRST; + video_str.id = MPEG_STREAM_VIDEO_FIRST; + + /* Try to pull a video PES - if not found, try video init anyway which + * should succeed if it really is a video-only stream */ + video_str.hdr.pos = 0; + video_str.hdr.limit = 256*1024; + + if (parse_demux(&video_str, STREAM_PM_RANDOM_ACCESS) == STREAM_OK) + { + /* Found a video packet - assume program stream */ + str_parser.format = STREAM_FMT_MPEG_PS; + str_parser.next_data = parse_demux; + } + else + { + /* No PES element found - assume video elementary stream */ + str_parser.format = STREAM_FMT_MPV; + str_parser.next_data = parse_elementary; + } + + if (!init_video_info()) + { + /* Cannot determine video size, etc. */ + parser_init_state(); + return STREAM_UNSUPPORTED; + } + + if (str_parser.format == STREAM_FMT_MPEG_PS) + { + /* Initalize start_pts and end_pts with the length (in 45kHz units) of + * the movie. INVALID_TIMESTAMP if the time could not be determined */ + if (!init_times(&video_str) || !check_times(&video_str)) + { + /* Must have video at least */ + parser_init_state(); + return STREAM_UNSUPPORTED; + } + + str_parser.flags |= STREAMF_CAN_SEEK; + + if (init_times(&audio_str)) + { + /* Audio will be part of playback pool */ + stream_add_stream(&audio_str); + + if (check_times(&audio_str)) + { + /* Overall duration is maximum span */ + str_parser.start_pts = MIN(audio_str.start_pts, video_str.start_pts); + str_parser.end_pts = MAX(audio_str.end_pts, video_str.end_pts); + } + else + { + /* Bad times on audio - use video times */ + str_parser.start_pts = video_str.start_pts; + str_parser.end_pts = video_str.end_pts; + + /* Questionable: could use bitrate seek and match video to that */ + audio_str.start_pts = video_str.start_pts; + audio_str.end_pts = video_str.end_pts; + } + } + else + { + /* No audio stream - use video only */ + str_parser.start_pts = video_str.start_pts; + str_parser.end_pts = video_str.end_pts; + } + + str_parser.last_seek_time = str_parser.start_pts; + } + else + { + /* There's no way to handle times on this without a full file + * scan */ + audio_str.start_pts = INVALID_TIMESTAMP; + audio_str.end_pts = INVALID_TIMESTAMP; + video_str.start_pts = 0; + video_str.end_pts = INVALID_TIMESTAMP; + str_parser.start_pts = 0; + str_parser.end_pts = INVALID_TIMESTAMP; + } + + /* Add video to playback pool */ + stream_add_stream(&video_str); + + /* Cache duration - it's used very often */ + str_parser.duration = str_parser.end_pts - str_parser.start_pts; + + DEBUGF("Movie info:\n" + " size:%dx%d\n" + " start:%u\n" + " end:%u\n" + " duration:%u\n", + str_parser.dims.w, str_parser.dims.h, + (unsigned)str_parser.start_pts, + (unsigned)str_parser.end_pts, + (unsigned)str_parser.duration); + + return STREAM_OK; +} + +void parser_close_stream(void) +{ + stream_remove_streams(); + parser_init_state(); +} + +bool parser_init(void) +{ + parser_init_state(); + return true; +} diff --git a/apps/plugins/mpegplayer/mpeg_settings.c b/apps/plugins/mpegplayer/mpeg_settings.c new file mode 100644 index 0000000000..c904de466d --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_settings.c @@ -0,0 +1,1454 @@ +#include "plugin.h" +#include "lib/helper.h" +#include "lib/configfile.h" + +#include "mpegplayer.h" +#include "mpeg_settings.h" + +struct mpeg_settings settings; + +#define THUMB_DELAY (75*HZ/100) + +/* button definitions */ +#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H300_PAD) +#define MPEG_START_TIME_SELECT BUTTON_ON +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_OFF + +#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ + (CONFIG_KEYPAD == IPOD_3G_PAD) || \ + (CONFIG_KEYPAD == IPOD_1G2G_PAD) +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_SCROLL_FWD +#define MPEG_START_TIME_DOWN BUTTON_SCROLL_BACK +#define MPEG_START_TIME_EXIT BUTTON_MENU + +#elif CONFIG_KEYPAD == GIGABEAT_PAD +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_VOL_UP +#define MPEG_START_TIME_RIGHT2 BUTTON_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#define MPEG_START_TIME_RC_SELECT (BUTTON_RC_PLAY | BUTTON_REL) +#define MPEG_START_TIME_RC_LEFT BUTTON_RC_REW +#define MPEG_START_TIME_RC_RIGHT BUTTON_RC_FF +#define MPEG_START_TIME_RC_UP BUTTON_RC_VOL_UP +#define MPEG_START_TIME_RC_DOWN BUTTON_RC_VOL_DOWN +#define MPEG_START_TIME_RC_EXIT (BUTTON_RC_PLAY | BUTTON_REPEAT) + +#elif CONFIG_KEYPAD == GIGABEAT_S_PAD +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_VOL_UP +#define MPEG_START_TIME_RIGHT2 BUTTON_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#define MPEG_START_TIME_RC_SELECT (BUTTON_RC_PLAY | BUTTON_REL) +#define MPEG_START_TIME_RC_LEFT BUTTON_RC_REW +#define MPEG_START_TIME_RC_RIGHT BUTTON_RC_FF +#define MPEG_START_TIME_RC_UP BUTTON_RC_VOL_UP +#define MPEG_START_TIME_RC_DOWN BUTTON_RC_VOL_DOWN +#define MPEG_START_TIME_RC_EXIT (BUTTON_RC_PLAY | BUTTON_REPEAT) + +#elif CONFIG_KEYPAD == IRIVER_H10_PAD +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_SCROLL_UP +#define MPEG_START_TIME_DOWN BUTTON_SCROLL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif (CONFIG_KEYPAD == SANSA_E200_PAD) +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_SCROLL_BACK +#define MPEG_START_TIME_RIGHT2 BUTTON_SCROLL_FWD +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD) +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_SCROLL_BACK +#define MPEG_START_TIME_RIGHT2 BUTTON_SCROLL_FWD +#define MPEG_START_TIME_EXIT (BUTTON_HOME|BUTTON_REPEAT) + +#elif (CONFIG_KEYPAD == SANSA_C200_PAD) || \ +(CONFIG_KEYPAD == SANSA_CLIP_PAD) || \ +(CONFIG_KEYPAD == SANSA_M200_PAD) +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_VOL_UP +#define MPEG_START_TIME_RIGHT2 BUTTON_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == MROBE500_PAD +#define MPEG_START_TIME_SELECT BUTTON_RC_HEART +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_RC_PLAY +#define MPEG_START_TIME_DOWN BUTTON_RC_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_RC_VOL_UP +#define MPEG_START_TIME_RIGHT2 BUTTON_RC_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == MROBE100_PAD +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_PLAY +#define MPEG_START_TIME_RIGHT2 BUTTON_MENU +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == IAUDIO_M3_PAD +#define MPEG_START_TIME_SELECT BUTTON_RC_PLAY +#define MPEG_START_TIME_LEFT BUTTON_RC_REW +#define MPEG_START_TIME_RIGHT BUTTON_RC_FF +#define MPEG_START_TIME_UP BUTTON_RC_VOL_UP +#define MPEG_START_TIME_DOWN BUTTON_RC_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_RC_REC + +#elif CONFIG_KEYPAD == COWON_D2_PAD +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == CREATIVEZVM_PAD +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_PLAY +#define MPEG_START_TIME_RIGHT2 BUTTON_MENU +#define MPEG_START_TIME_EXIT BUTTON_BACK + +#elif (CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD) +#define MPEG_START_TIME_SELECT (BUTTON_PLAY|BUTTON_REL) +#define MPEG_START_TIME_LEFT BUTTON_BACK +#define MPEG_START_TIME_RIGHT BUTTON_MENU +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT (BUTTON_PLAY|BUTTON_REPEAT) + +#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_VOL_UP +#define MPEG_START_TIME_RIGHT2 BUTTON_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_VOL_UP +#define MPEG_START_TIME_RIGHT2 BUTTON_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_PREV +#define MPEG_START_TIME_RIGHT BUTTON_NEXT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_VOL_UP +#define MPEG_START_TIME_RIGHT2 BUTTON_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == ONDAVX747_PAD +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == ONDAVX777_PAD +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \ + (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD) +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_REW + +#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_PREV +#define MPEG_START_TIME_RIGHT BUTTON_NEXT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_LEFT2 BUTTON_OK +#define MPEG_START_TIME_RIGHT2 BUTTON_CANCEL +#define MPEG_START_TIME_EXIT BUTTON_REC + +#elif CONFIG_KEYPAD == MPIO_HD200_PAD +#define MPEG_START_TIME_SELECT BUTTON_FUNC +#define MPEG_START_TIME_LEFT BUTTON_REW +#define MPEG_START_TIME_RIGHT BUTTON_FF +#define MPEG_START_TIME_UP BUTTON_VOL_UP +#define MPEG_START_TIME_DOWN BUTTON_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_REC + +#elif CONFIG_KEYPAD == MPIO_HD300_PAD +#define MPEG_START_TIME_SELECT BUTTON_ENTER +#define MPEG_START_TIME_LEFT BUTTON_REW +#define MPEG_START_TIME_RIGHT BUTTON_FF +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_REC + +#elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == SANSA_CONNECT_PAD +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == SAMSUNG_YPR0_PAD +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_BACK + +#elif (CONFIG_KEYPAD == HM60X_PAD) || (CONFIG_KEYPAD == HM801_PAD) +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == SONY_NWZ_PAD +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_BACK + +#elif CONFIG_KEYPAD == CREATIVE_ZEN_PAD +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_BACK + +#elif CONFIG_KEYPAD == DX50_PAD +#define MPEG_START_TIME_EXIT BUTTON_POWER +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_VOL_UP +#define MPEG_START_TIME_DOWN BUTTON_VOL_DOWN + +#elif CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == AGPTEK_ROCKER_PAD +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == XDUOO_X3_PAD +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_PREV +#define MPEG_START_TIME_RIGHT BUTTON_NEXT +#define MPEG_START_TIME_UP BUTTON_HOME +#define MPEG_START_TIME_DOWN BUTTON_OPTION +#define MPEG_START_TIME_LEFT2 BUTTON_VOL_UP +#define MPEG_START_TIME_RIGHT2 BUTTON_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == XDUOO_X3II_PAD || CONFIG_KEYPAD == XDUOO_X20_PAD +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_PREV +#define MPEG_START_TIME_RIGHT BUTTON_NEXT +#define MPEG_START_TIME_UP BUTTON_HOME +#define MPEG_START_TIME_DOWN BUTTON_OPTION +#define MPEG_START_TIME_LEFT2 BUTTON_VOL_UP +#define MPEG_START_TIME_RIGHT2 BUTTON_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == FIIO_M3K_LINUX_PAD +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_PREV +#define MPEG_START_TIME_RIGHT BUTTON_NEXT +#define MPEG_START_TIME_UP BUTTON_HOME +#define MPEG_START_TIME_DOWN BUTTON_OPTION +#define MPEG_START_TIME_LEFT2 BUTTON_VOL_UP +#define MPEG_START_TIME_RIGHT2 BUTTON_VOL_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == IHIFI_770_PAD || CONFIG_KEYPAD == IHIFI_800_PAD +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_HOME +#define MPEG_START_TIME_RIGHT BUTTON_VOL_DOWN +#define MPEG_START_TIME_UP BUTTON_PREV +#define MPEG_START_TIME_DOWN BUTTON_NEXT +#define MPEG_START_TIME_LEFT2 (BUTTON_POWER + BUTTON_HOME) +#define MPEG_START_TIME_RIGHT2 (BUTTON_POWER + BUTTON_VOL_DOWN) +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == EROSQ_PAD +#define MPEG_START_TIME_SELECT BUTTON_PLAY +#define MPEG_START_TIME_LEFT BUTTON_SCROLL_BACK +#define MPEG_START_TIME_RIGHT BUTTON_SCROLL_FWD +#define MPEG_START_TIME_UP BUTTON_PREV +#define MPEG_START_TIME_DOWN BUTTON_NEXT +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == FIIO_M3K_PAD +#define MPEG_START_TIME_SELECT BUTTON_SELECT +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD +#define MPEG_START_TIME_EXIT BUTTON_POWER + +#else +#error No keymap defined! +#endif + +#ifdef HAVE_TOUCHSCREEN +#ifndef MPEG_START_TIME_SELECT +#define MPEG_START_TIME_SELECT BUTTON_CENTER +#endif +#ifndef MPEG_START_TIME_LEFT +#define MPEG_START_TIME_LEFT BUTTON_MIDLEFT +#endif +#ifndef MPEG_START_TIME_RIGHT +#define MPEG_START_TIME_RIGHT BUTTON_MIDRIGHT +#endif +#ifndef MPEG_START_TIME_UP +#define MPEG_START_TIME_UP BUTTON_TOPMIDDLE +#endif +#ifndef MPEG_START_TIME_DOWN +#define MPEG_START_TIME_DOWN BUTTON_BOTTOMMIDDLE +#endif +#ifndef MPEG_START_TIME_LEFT2 +#define MPEG_START_TIME_LEFT2 BUTTON_TOPRIGHT +#endif +#ifndef MPEG_START_TIME_RIGHT2 +#define MPEG_START_TIME_RIGHT2 BUTTON_TOPLEFT +#endif +#ifndef MPEG_START_TIME_EXIT +#define MPEG_START_TIME_EXIT BUTTON_TOPLEFT +#endif +#endif + +static struct configdata config[] = +{ + {TYPE_INT, 0, 2, { .int_p = &settings.showfps }, "Show FPS", NULL}, + {TYPE_INT, 0, 2, { .int_p = &settings.limitfps }, "Limit FPS", NULL}, + {TYPE_INT, 0, 2, { .int_p = &settings.skipframes }, "Skip frames", NULL}, + {TYPE_INT, 0, INT_MAX, { .int_p = &settings.resume_count }, "Resume count", + NULL}, + {TYPE_INT, 0, MPEG_RESUME_NUM_OPTIONS, + { .int_p = &settings.resume_options }, "Resume options", NULL}, +#if MPEG_OPTION_DITHERING_ENABLED + {TYPE_INT, 0, INT_MAX, { .int_p = &settings.displayoptions }, + "Display options", NULL}, +#endif + {TYPE_INT, 0, 2, { .int_p = &settings.tone_controls }, "Tone controls", + NULL}, + {TYPE_INT, 0, 2, { .int_p = &settings.channel_modes }, "Channel modes", + NULL}, + {TYPE_INT, 0, 2, { .int_p = &settings.crossfeed }, "Crossfeed", NULL}, + {TYPE_INT, 0, 2, { .int_p = &settings.equalizer }, "Equalizer", NULL}, + {TYPE_INT, 0, 2, { .int_p = &settings.dithering }, "Dithering", NULL}, + {TYPE_INT, 0, 2, { .int_p = &settings.play_mode }, "Play mode", NULL}, +#ifdef HAVE_BACKLIGHT_BRIGHTNESS + {TYPE_INT, -1, INT_MAX, { .int_p = &settings.backlight_brightness }, + "Backlight brightness", NULL}, +#endif +}; + +static const struct opt_items noyes[2] = { + { STR(LANG_SET_BOOL_NO) }, + { STR(LANG_SET_BOOL_YES) }, +}; + +static const struct opt_items singleall[2] = { + { STR(LANG_SINGLE) }, + { STR(LANG_ALL) }, +}; + +static const struct opt_items globaloff[2] = { + { STR(LANG_OFF) }, + { STR(LANG_USE_SOUND_SETTING) }, +}; + +static void mpeg_settings(void); +static bool mpeg_set_option(const char* string, + void* variable, + enum optiontype type, + const struct opt_items* options, + int numoptions, + void (*function)(int)) +{ + mpeg_sysevent_clear(); + + /* This eats SYS_POWEROFF - :\ */ + bool usb = rb->set_option(string, variable, type, options, numoptions, + function); + + if (usb) + mpeg_sysevent_set(); + + return usb; +} + +#ifdef HAVE_BACKLIGHT_BRIGHTNESS /* Only used for this atm */ +static bool mpeg_set_int(const char *string, const char *unit, + int voice_unit, const int *variable, + void (*function)(int), int step, + int min, + int max, + const char* (*formatter)(char*, size_t, int, const char*), + int32_t (*get_talk_id)(int, int)) +{ + mpeg_sysevent_clear(); + + bool usb = rb->set_int_ex(string, unit, voice_unit, variable, function, + step, min, max, formatter, get_talk_id); + + if (usb) + mpeg_sysevent_set(); + + return usb; +} + +static int32_t backlight_brightness_getlang(int value, int unit) +{ + if (value < 0) + return LANG_USE_COMMON_SETTING; + + return TALK_ID(value + MIN_BRIGHTNESS_SETTING, unit); +} + +void mpeg_backlight_update_brightness(int value) +{ + if (value >= 0) + { + value += MIN_BRIGHTNESS_SETTING; + backlight_brightness_set(value); + } + else + { + backlight_brightness_use_setting(); + } +} + +static void backlight_brightness_function(int value) +{ + mpeg_backlight_update_brightness(value); +} + +static const char* backlight_brightness_formatter(char *buf, size_t length, + int value, const char *input) +{ + (void)input; + + if (value < 0) + return rb->str(LANG_USE_COMMON_SETTING); + else + rb->snprintf(buf, length, "%d", value + MIN_BRIGHTNESS_SETTING); + return buf; +} +#endif /* HAVE_BACKLIGHT_BRIGHTNESS */ + +/* Sync a particular audio setting to global or mpegplayer forced off */ +static void sync_audio_setting(int setting, bool global) +{ + switch (setting) + { + case MPEG_AUDIO_TONE_CONTROLS: + #ifdef AUDIOHW_HAVE_BASS + rb->sound_set(SOUND_BASS, (global || settings.tone_controls) + ? rb->global_settings->bass + : rb->sound_default(SOUND_BASS)); + #endif + #ifdef AUDIOHW_HAVE_TREBLE + rb->sound_set(SOUND_TREBLE, (global || settings.tone_controls) + ? rb->global_settings->treble + : rb->sound_default(SOUND_TREBLE)); + #endif + + #ifdef AUDIOHW_HAVE_EQ + for (int band = 0;; band++) + { + int setting = rb->sound_enum_hw_eq_band_setting(band, AUDIOHW_EQ_GAIN); + + if (setting == -1) + break; + + rb->sound_set(setting, (global || settings.tone_controls) + ? rb->global_settings->hw_eq_bands[band].gain + : rb->sound_default(setting)); + } + #endif /* AUDIOHW_HAVE_EQ */ + break; + + case MPEG_AUDIO_CHANNEL_MODES: + rb->sound_set(SOUND_CHANNELS, (global || settings.channel_modes) + ? rb->global_settings->channel_config + : SOUND_CHAN_STEREO); + break; + + case MPEG_AUDIO_CROSSFEED: + rb->dsp_set_crossfeed_type((global || settings.crossfeed) ? + rb->global_settings->crossfeed : + CROSSFEED_TYPE_NONE); + break; + + case MPEG_AUDIO_EQUALIZER: + rb->dsp_eq_enable((global || settings.equalizer) ? + rb->global_settings->eq_enabled : false); + break; + + case MPEG_AUDIO_DITHERING: + rb->dsp_dither_enable((global || settings.dithering) ? + rb->global_settings->dithering_enabled : false); + break; + } +} + +/* Sync all audio settings to global or mpegplayer forced off */ +static void sync_audio_settings(bool global) +{ + static const int setting_index[] = + { + MPEG_AUDIO_TONE_CONTROLS, + MPEG_AUDIO_CHANNEL_MODES, + MPEG_AUDIO_CROSSFEED, + MPEG_AUDIO_EQUALIZER, + MPEG_AUDIO_DITHERING, + }; + unsigned i; + + for (i = 0; i < ARRAYLEN(setting_index); i++) + { + sync_audio_setting(setting_index[i], global); + } +} + +#ifndef HAVE_LCD_COLOR +/* Cheapo splash implementation for the grey surface */ +static void grey_splash(int ticks, const unsigned char *fmt, ...) +{ + unsigned char buffer[256]; + int x, y, w, h; + int oldfg, oldmode; + + va_list ap; + va_start(ap, fmt); + + rb->vsnprintf(buffer, sizeof (buffer), fmt, ap); + + va_end(ap); + + grey_getstringsize(buffer, &w, &h); + + oldfg = grey_get_foreground(); + oldmode = grey_get_drawmode(); + + grey_set_drawmode(DRMODE_FG); + grey_set_foreground(GREY_LIGHTGRAY); + + x = (LCD_WIDTH - w) / 2; + y = (LCD_HEIGHT - h) / 2; + + grey_fillrect(x - 1, y - 1, w + 2, h + 2); + + grey_set_foreground(GREY_BLACK); + + grey_putsxy(x, y, buffer); + grey_drawrect(x - 2, y - 2, w + 4, h + 4); + + grey_set_foreground(oldfg); + grey_set_drawmode(oldmode); + + grey_update(); + + if (ticks > 0) + rb->sleep(ticks); +} +#endif /* !HAVE_LCD_COLOR */ + +static void show_loading(struct vo_rect *rc) +{ + int oldmode = mylcd_get_drawmode(); + mylcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID); + mylcd_fillrect(rc->l-1, rc->t-1, rc->r - rc->l + 2, rc->b - rc->t + 2); + mylcd_set_drawmode(oldmode); + mylcd_splash(0, "Loading..."); +} + +static void draw_slider(uint32_t range, uint32_t pos, struct vo_rect *rc) +{ + #define SLIDER_WIDTH (LCD_WIDTH-SLIDER_LMARGIN-SLIDER_RMARGIN) + #define SLIDER_X SLIDER_LMARGIN + #define SLIDER_Y (LCD_HEIGHT-SLIDER_HEIGHT-SLIDER_BMARGIN) + #define SLIDER_HEIGHT 8 + #define SLIDER_TEXTMARGIN 1 + #define SLIDER_LMARGIN 1 + #define SLIDER_RMARGIN 1 + #define SLIDER_TMARGIN 1 + #define SLIDER_BMARGIN 1 + #define SCREEN_MARGIN 1 + + struct hms hms; + char str[32]; + int text_w, text_h, text_y; + + /* Put positition on left */ + ts_to_hms(pos, &hms); + hms_format(str, sizeof(str), &hms); + mylcd_getstringsize(str, NULL, &text_h); + text_y = SLIDER_Y - SLIDER_TEXTMARGIN - text_h; + + if (rc == NULL) + { + int oldmode = mylcd_get_drawmode(); + mylcd_set_drawmode(DRMODE_BG | DRMODE_INVERSEVID); + mylcd_fillrect(SLIDER_X, text_y, SLIDER_WIDTH, + LCD_HEIGHT - SLIDER_BMARGIN - text_y + - SLIDER_TMARGIN); + mylcd_set_drawmode(oldmode); + + mylcd_putsxy(SLIDER_X, text_y, str); + + /* Put duration on right */ + ts_to_hms(range, &hms); + hms_format(str, sizeof(str), &hms); + mylcd_getstringsize(str, &text_w, NULL); + + mylcd_putsxy(SLIDER_X + SLIDER_WIDTH - text_w, text_y, str); + + /* Draw slider */ + mylcd_drawrect(SLIDER_X, SLIDER_Y, SLIDER_WIDTH, SLIDER_HEIGHT); + mylcd_fillrect(SLIDER_X, SLIDER_Y, + muldiv_uint32(pos, SLIDER_WIDTH, range), + SLIDER_HEIGHT); + + /* Update screen */ + mylcd_update_rect(SLIDER_X, text_y - SLIDER_TMARGIN, SLIDER_WIDTH, + LCD_HEIGHT - SLIDER_BMARGIN - text_y + SLIDER_TEXTMARGIN); + } + else + { + /* Just return slider rectangle */ + rc->l = SLIDER_X; + rc->t = text_y - SLIDER_TMARGIN; + rc->r = rc->l + SLIDER_WIDTH; + rc->b = rc->t + LCD_HEIGHT - SLIDER_BMARGIN - text_y; + } +} + +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)) + { + /* 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 (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(dkgray); + + mylcd_hline(rc->l-1, rc->r, rc->b); + mylcd_vline(rc->r, rc->t-1, rc->b); + + mylcd_set_foreground(oldcolor); + + mylcd_update_rect(rc->l-1, rc->t-1, rc->r - rc->l + 2, 1); + mylcd_update_rect(rc->l-1, rc->t, 1, rc->b - rc->t); + 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 retval; +} + +/* Add an amount to the specified time - with saturation */ +static uint32_t increment_time(uint32_t val, int32_t amount, uint32_t range) +{ + if (amount < 0) + { + uint32_t off = -amount; + if (range > off && val >= off) + val -= off; + else + val = 0; + } + else if (amount > 0) + { + uint32_t off = amount; + if (range > off && val <= range - off) + val += off; + else + val = range; + } + + return val; +} + +#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) +static void get_start_time_lcd_enable_hook(unsigned short id, void *param) +{ + (void)id; + (void)param; + rb->queue_post(rb->button_queue, LCD_ENABLE_EVENT_0, 0); +} +#endif /* HAVE_LCD_ENABLE */ + +static int get_start_time(uint32_t duration) +{ + int button = 0; + int tmo = TIMEOUT_NOBLOCK; + uint32_t resume_time = settings.resume_time; + struct vo_rect rc_vid, rc_bound; + uint32_t aspect_vid, aspect_bound; + bool sliding = false; + + enum state_enum slider_state = STATE0; + + mylcd_clear_display(); + mylcd_update(); + +#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) + rb->add_event(LCD_EVENT_ACTIVATION, get_start_time_lcd_enable_hook); +#endif + + draw_slider(0, 100, &rc_bound); + rc_bound.b = rc_bound.t - SLIDER_TMARGIN; + rc_bound.t = SCREEN_MARGIN; + + DEBUGF("rc_bound: %d, %d, %d, %d\n", rc_bound.l, rc_bound.t, + rc_bound.r, rc_bound.b); + + rc_vid.l = rc_vid.t = 0; + if (!stream_vo_get_size((struct vo_ext *)&rc_vid.r)) + { + /* Can't get size - fill whole thing */ + rc_vid.r = rc_bound.r - rc_bound.l; + rc_vid.b = rc_bound.b - rc_bound.t; + } + + /* Get aspect ratio of bounding rectangle and video in u16.16 */ + aspect_bound = ((rc_bound.r - rc_bound.l) << 16) / + (rc_bound.b - rc_bound.t); + + DEBUGF("aspect_bound: %u.%02u\n", (unsigned)(aspect_bound >> 16), + (unsigned)(100*(aspect_bound & 0xffff) >> 16)); + + aspect_vid = (rc_vid.r << 16) / rc_vid.b; + + DEBUGF("aspect_vid: %u.%02u\n", (unsigned)(aspect_vid >> 16), + (unsigned)(100*(aspect_vid & 0xffff) >> 16)); + + if (aspect_vid >= aspect_bound) + { + /* Video proportionally wider than or same as bounding rectangle */ + if (rc_vid.r > rc_bound.r - rc_bound.l) + { + rc_vid.r = rc_bound.r - rc_bound.l; + rc_vid.b = (rc_vid.r << 16) / aspect_vid; + } + /* else already fits */ + } + else + { + /* Video proportionally narrower than bounding rectangle */ + if (rc_vid.b > rc_bound.b - rc_bound.t) + { + rc_vid.b = rc_bound.b - rc_bound.t; + rc_vid.r = (aspect_vid * rc_vid.b) >> 16; + } + /* else already fits */ + } + + /* Even width and height >= 2 */ + rc_vid.r = (rc_vid.r < 2) ? 2 : (rc_vid.r & ~1); + rc_vid.b = (rc_vid.b < 2) ? 2 : (rc_vid.b & ~1); + + /* Center display in bounding rectangle */ + rc_vid.l = ((rc_bound.l + rc_bound.r) - rc_vid.r) / 2; + rc_vid.r += rc_vid.l; + + rc_vid.t = ((rc_bound.t + rc_bound.b) - rc_vid.b) / 2; + rc_vid.b += rc_vid.t; + + DEBUGF("rc_vid: %d, %d, %d, %d\n", rc_vid.l, rc_vid.t, + rc_vid.r, rc_vid.b); + +#ifndef HAVE_LCD_COLOR + stream_gray_show(true); +#endif + + while (slider_state < STATE9) + { + button = mpeg_button_get(tmo); + + switch (button) + { + case BUTTON_NONE: + break; + + /* Coarse (1 minute) control */ + case MPEG_START_TIME_DOWN: + case MPEG_START_TIME_DOWN | BUTTON_REPEAT: +#ifdef MPEG_START_TIME_RC_DOWN + case MPEG_START_TIME_RC_DOWN: + case MPEG_START_TIME_RC_DOWN | BUTTON_REPEAT: +#endif + resume_time = increment_time(resume_time, -60*TS_SECOND, duration); + slider_state = STATE0; + break; + + case MPEG_START_TIME_UP: + case MPEG_START_TIME_UP | BUTTON_REPEAT: +#ifdef MPEG_START_TIME_RC_UP + case MPEG_START_TIME_RC_UP: + case MPEG_START_TIME_RC_UP | BUTTON_REPEAT: +#endif + resume_time = increment_time(resume_time, 60*TS_SECOND, duration); + slider_state = STATE0; + break; + + /* Fine (1 second) control */ + case MPEG_START_TIME_LEFT: + case MPEG_START_TIME_LEFT | BUTTON_REPEAT: +#ifdef MPEG_START_TIME_RC_LEFT + case MPEG_START_TIME_RC_LEFT: + case MPEG_START_TIME_RC_LEFT | BUTTON_REPEAT: +#endif +#ifdef MPEG_START_TIME_LEFT2 + case MPEG_START_TIME_LEFT2: + case MPEG_START_TIME_LEFT2 | BUTTON_REPEAT: +#endif + resume_time = increment_time(resume_time, -TS_SECOND, duration); + slider_state = STATE0; + break; + + case MPEG_START_TIME_RIGHT: + case MPEG_START_TIME_RIGHT | BUTTON_REPEAT: +#ifdef MPEG_START_TIME_RC_RIGHT + case MPEG_START_TIME_RC_RIGHT: + case MPEG_START_TIME_RC_RIGHT | BUTTON_REPEAT: +#endif +#ifdef MPEG_START_TIME_RIGHT2 + case MPEG_START_TIME_RIGHT2: + case MPEG_START_TIME_RIGHT2 | BUTTON_REPEAT: +#endif + resume_time = increment_time(resume_time, TS_SECOND, duration); + slider_state = STATE0; + break; + + case MPEG_START_TIME_SELECT: +#ifdef MPEG_START_TIME_RC_SELECT + case MPEG_START_TIME_RC_SELECT: +#endif + settings.resume_time = resume_time; + button = MPEG_START_SEEK; + slider_state = STATE9; + break; + + case MPEG_START_TIME_EXIT: +#ifdef MPEG_START_TIME_RC_EXIT + case MPEG_START_TIME_RC_EXIT: +#endif + button = MPEG_START_EXIT; + slider_state = STATE9; + break; + + case ACTION_STD_CANCEL: + button = MPEG_START_QUIT; + slider_state = STATE9; + break; + +#ifdef HAVE_LCD_ENABLE + case LCD_ENABLE_EVENT_0: + if (slider_state == STATE2) + display_thumb_image(&rc_vid); + continue; +#endif + + default: + rb->default_event_handler(button); + rb->yield(); + continue; + } + + switch (slider_state) + { + case STATE0: + if (!sliding) + { + if (rb->global_settings->talk_menu) + { + rb->talk_disable(true); +#ifdef PLUGIN_USE_IRAM + mpegplayer_iram_restore(); +#endif + } + trigger_cpu_boost(); + sliding = true; + } + stream_seek(resume_time, SEEK_SET); + show_loading(&rc_bound); + draw_slider(duration, resume_time, NULL); + slider_state = STATE1; + tmo = THUMB_DELAY; + break; + case STATE1: + display_thumb_image(&rc_vid); + slider_state = STATE2; + tmo = TIMEOUT_BLOCK; + if (sliding) + { + cancel_cpu_boost(); + if (rb->global_settings->talk_menu) + { +#ifdef PLUGIN_USE_IRAM + mpegplayer_iram_preserve(); +#endif + rb->talk_disable(false); + talk_val(resume_time / TS_SECOND, UNIT_TIME, false); + talk_val(resume_time * 100 / duration, UNIT_PERCENT, true); + } + sliding = false; + } + default: + break; + } + + rb->yield(); + } + +#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) + rb->remove_event(LCD_EVENT_ACTIVATION, get_start_time_lcd_enable_hook); +#endif +#ifndef HAVE_LCD_COLOR + stream_gray_show(false); + grey_clear_display(); + grey_update(); +#endif + + cancel_cpu_boost(); + + return button; +} + +static int show_start_menu(uint32_t duration) +{ + int selected = 0; + int result = 0; + bool menu_quit = false; + + MENUITEM_STRINGLIST(menu, "Mpegplayer Menu", mpeg_sysevent_callback, + ID2P(LANG_RESTART_PLAYBACK), + ID2P(LANG_RESUME_PLAYBACK), + ID2P(LANG_SET_RESUME_TIME), + ID2P(LANG_SETTINGS), + ID2P(LANG_MENU_QUIT)); + + if (rb->global_settings->talk_menu) + { +#ifdef PLUGIN_USE_IRAM + mpegplayer_iram_preserve(); +#endif + rb->talk_disable(false); + } + + rb->button_clear_queue(); + + while (!menu_quit) + { + mpeg_sysevent_clear(); + result = rb->do_menu(&menu, &selected, NULL, false); + + switch (result) + { + case MPEG_START_RESTART: + settings.resume_time = 0; + menu_quit = true; + break; + + case MPEG_START_RESUME: + menu_quit = true; + break; + + case MPEG_START_SEEK: + if (!stream_can_seek()) + { + rb->splash(HZ, ID2P(LANG_UNAVAILABLE)); + break; + } + + result = get_start_time(duration); + + if (result != MPEG_START_EXIT) + menu_quit = true; + break; + + case MPEG_START_SETTINGS: + mpeg_settings(); + break; + + default: + result = MPEG_START_QUIT; + menu_quit = true; + break; + } + + if (mpeg_sysevent() != 0) + { + result = MPEG_START_QUIT; + menu_quit = true; + } + } + + if (rb->global_settings->talk_menu) + { + rb->talk_disable(true); +#ifdef PLUGIN_USE_IRAM + mpegplayer_iram_restore(); +#endif + } + + return result; +} + +/* Return the desired resume action */ +int mpeg_start_menu(uint32_t duration) +{ + mpeg_sysevent_clear(); + + switch (settings.resume_options) + { + case MPEG_RESUME_MENU_IF_INCOMPLETE: + if (!stream_can_seek() || settings.resume_time == 0) + { + case MPEG_RESUME_RESTART: + settings.resume_time = 0; + return MPEG_START_RESTART; + } + default: + case MPEG_RESUME_MENU_ALWAYS: + return show_start_menu(duration); + case MPEG_RESUME_ALWAYS: + return MPEG_START_SEEK; + } +} + +int mpeg_menu(void) +{ + int result; + + MENUITEM_STRINGLIST(menu, "Mpegplayer Menu", mpeg_sysevent_callback, + ID2P(LANG_SETTINGS), + ID2P(LANG_RESUME_PLAYBACK), + ID2P(LANG_MENU_QUIT)); + + if (rb->global_settings->talk_menu) + { +#ifdef PLUGIN_USE_IRAM + mpegplayer_iram_preserve(); +#endif + rb->talk_disable(false); + } + + rb->button_clear_queue(); + + mpeg_sysevent_clear(); + + result = rb->do_menu(&menu, NULL, NULL, false); + + switch (result) + { + case MPEG_MENU_SETTINGS: + mpeg_settings(); + break; + + case MPEG_MENU_RESUME: + break; + + case MPEG_MENU_QUIT: + break; + + default: + break; + } + + if (mpeg_sysevent() != 0) + result = MPEG_MENU_QUIT; + + if (rb->global_settings->talk_menu) + { + rb->talk_disable(true); +#ifdef PLUGIN_USE_IRAM + mpegplayer_iram_restore(); +#endif + } + + return result; +} + +static void display_options(void) +{ + int selected = 0; + int result; + bool menu_quit = false; + + MENUITEM_STRINGLIST(menu, "Display Options", mpeg_sysevent_callback, +#if MPEG_OPTION_DITHERING_ENABLED + ID2P(LANG_DITHERING), +#endif + ID2P(LANG_DISPLAY_FPS), + ID2P(LANG_LIMIT_FPS), + ID2P(LANG_SKIP_FRAMES), +#ifdef HAVE_BACKLIGHT_BRIGHTNESS + ID2P(LANG_BACKLIGHT_BRIGHTNESS), +#endif + ); + + rb->button_clear_queue(); + + while (!menu_quit) + { + mpeg_sysevent_clear(); + result = rb->do_menu(&menu, &selected, NULL, false); + + switch (result) + { +#if MPEG_OPTION_DITHERING_ENABLED + case MPEG_OPTION_DITHERING: + result = (settings.displayoptions & LCD_YUV_DITHER) ? 1 : 0; + mpeg_set_option(rb->str(LANG_DITHERING), &result, INT, noyes, 2, NULL); + settings.displayoptions = + (settings.displayoptions & ~LCD_YUV_DITHER) + | ((result != 0) ? LCD_YUV_DITHER : 0); + rb->lcd_yuv_set_options(settings.displayoptions); + break; +#endif /* MPEG_OPTION_DITHERING_ENABLED */ + + case MPEG_OPTION_DISPLAY_FPS: + mpeg_set_option(rb->str(LANG_DISPLAY_FPS), &settings.showfps, INT, + noyes, 2, NULL); + break; + + case MPEG_OPTION_LIMIT_FPS: + mpeg_set_option(rb->str(LANG_LIMIT_FPS), &settings.limitfps, INT, + noyes, 2, NULL); + break; + + case MPEG_OPTION_SKIP_FRAMES: + mpeg_set_option(rb->str(LANG_SKIP_FRAMES), &settings.skipframes, INT, + noyes, 2, NULL); + break; + +#ifdef HAVE_BACKLIGHT_BRIGHTNESS + case MPEG_OPTION_BACKLIGHT_BRIGHTNESS: + result = settings.backlight_brightness; + mpeg_backlight_update_brightness(result); + mpeg_set_int(rb->str(LANG_BACKLIGHT_BRIGHTNESS), NULL, UNIT_INT, &result, + backlight_brightness_function, 1, -1, + MAX_BRIGHTNESS_SETTING - MIN_BRIGHTNESS_SETTING, + backlight_brightness_formatter, + backlight_brightness_getlang); + settings.backlight_brightness = result; + mpeg_backlight_update_brightness(-1); + break; +#endif /* HAVE_BACKLIGHT_BRIGHTNESS */ + + default: + menu_quit = true; + break; + } + + if (mpeg_sysevent() != 0) + menu_quit = true; + } +} + +static void audio_options(void) +{ + int selected = 0; + int result; + bool menu_quit = false; + + MENUITEM_STRINGLIST(menu, "Audio Options", mpeg_sysevent_callback, + ID2P(LANG_TONE_CONTROLS), + ID2P(LANG_CHANNEL_CONFIGURATION), + ID2P(LANG_CROSSFEED), + ID2P(LANG_EQUALIZER), + ID2P(LANG_DITHERING)); + + rb->button_clear_queue(); + + while (!menu_quit) + { + mpeg_sysevent_clear(); + result = rb->do_menu(&menu, &selected, NULL, false); + + switch (result) + { + case MPEG_AUDIO_TONE_CONTROLS: + mpeg_set_option(rb->str(LANG_TONE_CONTROLS), &settings.tone_controls, INT, + globaloff, 2, NULL); + sync_audio_setting(result, false); + break; + + case MPEG_AUDIO_CHANNEL_MODES: + mpeg_set_option(rb->str(LANG_CHANNEL_CONFIGURATION), &settings.channel_modes, + INT, globaloff, 2, NULL); + sync_audio_setting(result, false); + break; + + case MPEG_AUDIO_CROSSFEED: + mpeg_set_option(rb->str(LANG_CROSSFEED), &settings.crossfeed, INT, + globaloff, 2, NULL); + sync_audio_setting(result, false); + break; + + case MPEG_AUDIO_EQUALIZER: + mpeg_set_option(rb->str(LANG_EQUALIZER), &settings.equalizer, INT, + globaloff, 2, NULL); + sync_audio_setting(result, false); + break; + + case MPEG_AUDIO_DITHERING: + mpeg_set_option(rb->str(LANG_DITHERING), &settings.dithering, INT, + globaloff, 2, NULL); + sync_audio_setting(result, false); + break; + + default: + menu_quit = true; + break; + } + + if (mpeg_sysevent() != 0) + menu_quit = true; + } +} + +static void resume_options(void) +{ + static const struct opt_items items[MPEG_RESUME_NUM_OPTIONS] = { + [MPEG_RESUME_MENU_ALWAYS] = + { STR(LANG_FORCE_START_MENU) }, + [MPEG_RESUME_MENU_IF_INCOMPLETE] = + { STR(LANG_CONDITIONAL_START_MENU) }, + [MPEG_RESUME_ALWAYS] = + { STR(LANG_AUTO_RESUME) }, + [MPEG_RESUME_RESTART] = + { STR(LANG_RESTART_PLAYBACK) }, + }; + + mpeg_set_option(rb->str(LANG_MENU_RESUME_OPTIONS), &settings.resume_options, + INT, items, MPEG_RESUME_NUM_OPTIONS, NULL); +} + +static void clear_resume_count(void) +{ + settings.resume_count = 0; + configfile_save(SETTINGS_FILENAME, config, ARRAYLEN(config), + SETTINGS_VERSION); +} + +static void mpeg_settings(void) +{ + int selected = 0; + int result; + bool menu_quit = false; + + MENUITEM_STRINGLIST(menu, "Settings", mpeg_sysevent_callback, + ID2P(LANG_MENU_DISPLAY_OPTIONS), + ID2P(LANG_MENU_AUDIO_OPTIONS), + ID2P(LANG_MENU_RESUME_OPTIONS), + ID2P(LANG_MENU_PLAY_MODE), + ID2P(LANG_CLEAR_ALL_RESUMES)); + + rb->button_clear_queue(); + + while (!menu_quit) + { + mpeg_sysevent_clear(); + + result = rb->do_menu(&menu, &selected, NULL, false); + + switch (result) + { + case MPEG_SETTING_DISPLAY_SETTINGS: + display_options(); + break; + + case MPEG_SETTING_AUDIO_SETTINGS: + audio_options(); + break; + + case MPEG_SETTING_ENABLE_START_MENU: + resume_options(); + break; + + case MPEG_SETTING_PLAY_MODE: + mpeg_set_option(rb->str(LANG_MENU_PLAY_MODE), &settings.play_mode, + INT, singleall, 2, NULL); + break; + + case MPEG_SETTING_CLEAR_RESUMES: + clear_resume_count(); + break; + + default: + menu_quit = true; + break; + } + + if (mpeg_sysevent() != 0) + menu_quit = true; + } +} + +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.play_mode = 0; /* Play single video */ + settings.resume_options = MPEG_RESUME_MENU_ALWAYS; /* Enable start menu */ + settings.resume_count = 0; +#ifdef HAVE_BACKLIGHT_BRIGHTNESS + settings.backlight_brightness = -1; /* Use default setting */ +#endif +#if MPEG_OPTION_DITHERING_ENABLED + settings.displayoptions = 0; /* No visual effects */ +#endif + settings.tone_controls = false; + settings.channel_modes = false; + settings.crossfeed = false; + settings.equalizer = false; + settings.dithering = false; + + if (configfile_load(SETTINGS_FILENAME, config, ARRAYLEN(config), + SETTINGS_MIN_VERSION) < 0) + { + /* Generate a new config file with default values */ + configfile_save(SETTINGS_FILENAME, config, ARRAYLEN(config), + SETTINGS_VERSION); + } + + rb->strlcpy(settings.resume_filename, filename, MAX_PATH); + + /* get the resume time for the current mpeg if it exists */ + if ((settings.resume_time = configfile_get_value + (SETTINGS_FILENAME, filename)) < 0) + { + settings.resume_time = 0; + } + +#if MPEG_OPTION_DITHERING_ENABLED + rb->lcd_yuv_set_options(settings.displayoptions); +#endif + + /* Set our audio options */ + sync_audio_settings(false); +} + +void save_settings(void) +{ + unsigned i; + for (i = 0; i < ARRAYLEN(config); i++) + { + configfile_update_entry(SETTINGS_FILENAME, config[i].name, + *(config[i].int_p)); + } + + /* 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); + } + + /* Restore audio options */ + sync_audio_settings(true); +} diff --git a/apps/plugins/mpegplayer/mpeg_settings.h b/apps/plugins/mpegplayer/mpeg_settings.h new file mode 100644 index 0000000000..b1704ef707 --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_settings.h @@ -0,0 +1,110 @@ + +#include "plugin.h" + +#define SETTINGS_VERSION 5 +#define SETTINGS_MIN_VERSION 1 +#define SETTINGS_FILENAME "mpegplayer.cfg" + +#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) \ + || defined(IRIVER_H10) || defined(COWON_D2) || defined(PHILIPS_HDD1630) \ + || defined(SANSA_FUZE) || defined(SANSA_E200V2) || defined(SANSA_FUZEV2) \ + || defined(TOSHIBA_GIGABEAT_S) || defined(PHILIPS_SA9200) +#define MPEG_OPTION_DITHERING_ENABLED 1 +#endif + +#ifndef MPEG_OPTION_DITHERING_ENABLED +#define MPEG_OPTION_DITHERING_ENABLED 0 +#endif + +enum mpeg_option_id +{ +#if MPEG_OPTION_DITHERING_ENABLED + MPEG_OPTION_DITHERING, +#endif + MPEG_OPTION_DISPLAY_FPS, + MPEG_OPTION_LIMIT_FPS, + MPEG_OPTION_SKIP_FRAMES, +#ifdef HAVE_BACKLIGHT_BRIGHTNESS + MPEG_OPTION_BACKLIGHT_BRIGHTNESS, +#endif +}; + +enum mpeg_audio_option_id +{ + MPEG_AUDIO_TONE_CONTROLS, + MPEG_AUDIO_CHANNEL_MODES, + MPEG_AUDIO_CROSSFEED, + MPEG_AUDIO_EQUALIZER, + MPEG_AUDIO_DITHERING, +}; + +enum mpeg_resume_id +{ + MPEG_RESUME_MENU_ALWAYS = 0, + MPEG_RESUME_MENU_IF_INCOMPLETE, + MPEG_RESUME_RESTART, + MPEG_RESUME_ALWAYS, + MPEG_RESUME_NUM_OPTIONS, +}; + +enum mpeg_start_id +{ + MPEG_START_RESTART, + MPEG_START_RESUME, + MPEG_START_SEEK, + MPEG_START_SETTINGS, + MPEG_START_QUIT, + MPEG_START_EXIT, +}; + +enum mpeg_setting_id +{ + MPEG_SETTING_DISPLAY_SETTINGS, + MPEG_SETTING_AUDIO_SETTINGS, + MPEG_SETTING_ENABLE_START_MENU, + MPEG_SETTING_PLAY_MODE, + MPEG_SETTING_CLEAR_RESUMES, +}; + +enum mpeg_menu_id +{ + MPEG_MENU_SETTINGS, + MPEG_MENU_RESUME, + MPEG_MENU_QUIT, +}; + +struct mpeg_settings { + int showfps; /* flag to display fps */ + int limitfps; /* flag to limit fps */ + int skipframes; /* flag to skip frames */ + int resume_options; /* type of resume action at start */ + int resume_count; /* total # of resumes in config file */ + int resume_time; /* resume time for current mpeg (in half minutes) */ + char resume_filename[MAX_PATH]; /* filename of current mpeg */ +#if MPEG_OPTION_DITHERING_ENABLED + int displayoptions; +#endif + int play_mode; /* play single file or all files in directory */ + /* Audio options - simple on/off specification */ + int tone_controls; + int channel_modes; + int crossfeed; + int equalizer; + int dithering; + /* Backlight options */ +#ifdef HAVE_BACKLIGHT_BRIGHTNESS + int backlight_brightness; +#endif +}; + +extern struct mpeg_settings settings; + +int mpeg_start_menu(uint32_t duration); +int mpeg_menu(void); + +void init_settings(const char* filename); +void save_settings(void); + +#ifdef HAVE_BACKLIGHT_BRIGHTNESS +void mpeg_backlight_update_brightness(int value); +#endif diff --git a/apps/plugins/mpegplayer/mpeg_stream.h b/apps/plugins/mpegplayer/mpeg_stream.h new file mode 100644 index 0000000000..26fdaf07b4 --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_stream.h @@ -0,0 +1,122 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Stream definitions for MPEG + * + * Copyright (c) 2007 Michael Sevakis + * + * 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. + * + ****************************************************************************/ +#ifndef MPEG_STREAM_H +#define MPEG_STREAM_H + +/* Codes for various header byte sequences - MSB represents lowest memory + address */ +#define PACKET_START_CODE_PREFIX 0x00000100ul +#define END_CODE 0x000001b9ul +#define PACK_START_CODE 0x000001baul +#define SYSTEM_HEADER_START_CODE 0x000001bbul + +/* p = base pointer, b0 - b4 = byte offsets from p */ +/* We only care about the MS 32 bits of the 33 and so the ticks are 45kHz */ +#define TS_FROM_HEADER(p, b0) \ + ((uint32_t)((((p)[(b0)+0] & 0x0e) << 28) | \ + (((p)[(b0)+1] ) << 21) | \ + (((p)[(b0)+2] & 0xfe) << 13) | \ + (((p)[(b0)+3] ) << 6) | \ + (((p)[(b0)+4] ) >> 2))) + +#define TS_CHECK_MARKERS(p, b0) \ + (((((p)[(b0)+0] & 0x01) << 2) | \ + (((p)[(b0)+2] & 0x01) << 1) | \ + (((p)[(b0)+4] & 0x01) )) == 0x07) + +/* Get the SCR in our 45kHz ticks. Ignore the 9-bit extension */ +#define MPEG2_PACK_HEADER_SCR(p, b0) \ + ((uint32_t)((((p)[(b0)+0] & 0x38) << 26) | \ + (((p)[(b0)+0] & 0x03) << 27) | \ + (((p)[(b0)+1] ) << 19) | \ + (((p)[(b0)+2] & 0xf8) << 11) | \ + (((p)[(b0)+2] & 0x03) << 12) | \ + (((p)[(b0)+3] ) << 4) | \ + (((p)[(b0)+4] ) >> 4))) + +#define MPEG2_CHECK_PACK_SCR_MARKERS(ph, b0) \ + (((((ph)[(b0)+0] & 0x04) ) | \ + (((ph)[(b0)+2] & 0x04) >> 1) | \ + (((ph)[(b0)+4] & 0x04) >> 2)) == 0x07) + +#define INVALID_TIMESTAMP (~(uint32_t)0) +#define MAX_TIMESTAMP (INVALID_TIMESTAMP-1) +#define TS_SECOND (45000) /* Timestamp ticks per second */ +#define TC_SECOND (27000000) /* MPEG timecode ticks per second */ + +/* These values immediately follow the start code prefix '00 00 01' */ + +/* Video start codes */ +#define MPEG_START_PICTURE 0x00 +#define MPEG_START_SLICE_FIRST 0x01 +#define MPEG_START_SLICE_LAST 0xaf +#define MPEG_START_RESERVED_1 0xb0 +#define MPEG_START_RESERVED_2 0xb1 +#define MPEG_START_USER_DATA 0xb2 +#define MPEG_START_SEQUENCE_HEADER 0xb3 +#define MPEG_START_SEQUENCE_ERROR 0xb4 +#define MPEG_START_EXTENSION 0xb5 +#define MPEG_START_RESERVED_3 0xb6 +#define MPEG_START_SEQUENCE_END 0xb7 +#define MPEG_START_GOP 0xb8 + +/* Stream IDs */ +#define MPEG_STREAM_PROGRAM_END 0xb9 +#define MPEG_STREAM_PACK_HEADER 0xba +#define MPEG_STREAM_SYSTEM_HEADER 0xbb +#define MPEG_STREAM_PROGRAM_STREAM_MAP 0xbc +#define MPEG_STREAM_PRIVATE_1 0xbd +#define MPEG_STREAM_PADDING 0xbe +#define MPEG_STREAM_PRIVATE_2 0xbf +#define MPEG_STREAM_AUDIO_FIRST 0xc0 +#define MPEG_STREAM_AUDIO_LAST 0xcf +#define MPEG_STREAM_VIDEO_FIRST 0xe0 +#define MPEG_STREAM_VIDEO_LAST 0xef +#define MPEG_STREAM_ECM 0xf0 +#define MPEG_STREAM_EMM 0xf1 +/* ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A or + * ISO/IEC 13818-6_DSMCC_stream */ +#define MPEG_STREAM_MISC_1 0xf2 +/* ISO/IEC_13522_stream */ +#define MPEG_STREAM_MISC_2 0xf3 +/* ITU-T Rec. H.222.1 type A - E */ +#define MPEG_STREAM_MISC_3 0xf4 +#define MPEG_STREAM_MISC_4 0xf5 +#define MPEG_STREAM_MISC_5 0xf6 +#define MPEG_STREAM_MISC_6 0xf7 +#define MPEG_STREAM_MISC_7 0xf8 +#define MPEG_STREAM_ANCILLARY 0xf9 +#define MPEG_STREAM_RESERVED_FIRST 0xfa +#define MPEG_STREAM_RESERVED_LAST 0xfe +/* Program stream directory */ +#define MPEG_STREAM_PROGRAM_DIRECTORY 0xff + +#define STREAM_IS_AUDIO(s) (((s) & 0xf0) == 0xc0) +#define STREAM_IS_VIDEO(s) (((s) & 0xf0) == 0xe0) + +#define MPEG_MAX_PACKET_SIZE (64*1024+16) + +/* Largest MPEG audio frame - MPEG1, Layer II, 384kbps, 32kHz, pad */ +#define MPA_MAX_FRAME_SIZE 1729 + +#endif /* MPEG_STREAM_H */ diff --git a/apps/plugins/mpegplayer/mpegplayer.c b/apps/plugins/mpegplayer/mpegplayer.c new file mode 100644 index 0000000000..e66b4df146 --- /dev/null +++ b/apps/plugins/mpegplayer/mpegplayer.c @@ -0,0 +1,2638 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * mpegplayer main entrypoint and UI implementation + * + * Copyright (c) 2007 Michael Sevakis + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * NOTES: + * + * mpegplayer is structured as follows: + * + * +-->Video Thread-->Video Output-->LCD + * | + * UI-->Stream Manager-->+-->Audio Thread-->PCM buffer--Audio Device + * | | | | (ref. clock) + * | | +-->Buffer Thread | + * Stream Data | | (clock intf./ + * Requests | File Cache drift adj.) + * | Disk I/O + * Stream services + * (timing, etc.) + * + * Thread list: + * 1) The main thread - Handles user input, settings, basic playback control + * and USB connect. + * + * 2) Stream Manager thread - Handles playback state, events from streams + * such as when a stream is finished, stream commands, PCM state. The + * layer in which this thread run also handles arbitration of data + * requests between the streams and the disk buffer. The actual specific + * transport layer code may get moved out to support multiple container + * formats. + * + * 3) Buffer thread - Buffers data in the background, generates notifications + * to streams when their data has been buffered, and watches streams' + * progress to keep data available during playback. Handles synchronous + * random access requests when the file cache is missed. + * + * 4) Video thread (running on the COP for PortalPlayer targets) - Decodes + * the video stream and renders video frames to the LCD. Handles + * miscellaneous video tasks like frame and thumbnail printing. + * + * 5) Audio thread (running on the main CPU to maintain consistency with the + * audio FIQ hander on PP) - Decodes audio frames and places them into + * the PCM buffer for rendering by the audio device. + * + * Streams are neither aware of one another nor care about one another. All + * streams shall have their own thread (unless it is _really_ efficient to + * have a single thread handle a couple minor streams). All coordination of + * the streams is done through the stream manager. The clocking is controlled + * by and exposed by the stream manager to other streams and implemented at + * the PCM level. + * + * Notes about MPEG files: + * + * 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 (1): + * + * FPS | 27Mhz | 100Hz | 44.1KHz | 48KHz + * --------|----------------------------------------------------------- + * 10* | 2700000 | 10 | 4410 | 4800 + * 12* | 2250000 | 8.3333 | 3675 | 4000 + * 15* | 1800000 | 6.6667 | 2940 | 3200 + * 23.9760 | 1126125 | 4.170833333 | 1839.3375 | 2002 + * 24 | 1125000 | 4.166667 | 1837.5 | 2000 + * 25 | 1080000 | 4 | 1764 | 1920 + * 29.9700 | 900900 | 3.336667 | 1471,47 | 1601.6 + * 30 | 900000 | 3.333333 | 1470 | 1600 + * + * *Unofficial framerates + * + * (1) But we don't really care since the audio clock is used anyway and has + * very fine resolution ;-) + *****************************************************************************/ +#include "plugin.h" +#include "mpegplayer.h" +#include "lib/helper.h" +#include "mpeg_settings.h" +#include "video_out.h" +#include "stream_thread.h" +#include "stream_mgr.h" + + +/* 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 +#define MPEG_VOLDOWN BUTTON_DOWN +#define MPEG_VOLUP BUTTON_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \ + (CONFIG_KEYPAD == IPOD_1G2G_PAD) +#define MPEG_MENU BUTTON_MENU +#define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL) +#define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT) +#define MPEG_VOLDOWN BUTTON_SCROLL_BACK +#define MPEG_VOLUP BUTTON_SCROLL_FWD +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD +#define MPEG_MENU (BUTTON_REC | BUTTON_REL) +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_VOLDOWN BUTTON_DOWN +#define MPEG_VOLUP BUTTON_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == GIGABEAT_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_SELECT +#define MPEG_PAUSE2 BUTTON_A +#define MPEG_VOLDOWN BUTTON_LEFT +#define MPEG_VOLUP BUTTON_RIGHT +#define MPEG_VOLDOWN2 BUTTON_VOL_DOWN +#define MPEG_VOLUP2 BUTTON_VOL_UP +#define MPEG_RW BUTTON_UP +#define MPEG_FF BUTTON_DOWN + +#define MPEG_RC_MENU BUTTON_RC_DSP +#define MPEG_RC_STOP (BUTTON_RC_PLAY | BUTTON_REPEAT) +#define MPEG_RC_PAUSE (BUTTON_RC_PLAY | BUTTON_REL) +#define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN +#define MPEG_RC_VOLUP BUTTON_RC_VOL_UP +#define MPEG_RC_RW BUTTON_RC_REW +#define MPEG_RC_FF BUTTON_RC_FF + +#elif CONFIG_KEYPAD == GIGABEAT_S_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_SELECT +#define MPEG_PAUSE2 BUTTON_PLAY +#define MPEG_VOLDOWN BUTTON_LEFT +#define MPEG_VOLUP BUTTON_RIGHT +#define MPEG_VOLDOWN2 BUTTON_VOL_DOWN +#define MPEG_VOLUP2 BUTTON_VOL_UP +#define MPEG_RW BUTTON_UP +#define MPEG_RW2 BUTTON_PREV +#define MPEG_FF BUTTON_DOWN +#define MPEG_FF2 BUTTON_NEXT +#define MPEG_SHOW_OSD BUTTON_BACK + +#define MPEG_RC_MENU BUTTON_RC_DSP +#define MPEG_RC_STOP (BUTTON_RC_PLAY | BUTTON_REPEAT) +#define MPEG_RC_PAUSE (BUTTON_RC_PLAY | BUTTON_REL) +#define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN +#define MPEG_RC_VOLUP BUTTON_RC_VOL_UP +#define MPEG_RC_RW BUTTON_RC_REW +#define MPEG_RC_FF BUTTON_RC_FF + +#elif CONFIG_KEYPAD == IRIVER_H10_PAD +#define MPEG_MENU BUTTON_LEFT +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_VOLDOWN BUTTON_SCROLL_DOWN +#define MPEG_VOLUP BUTTON_SCROLL_UP +#define MPEG_RW BUTTON_REW +#define MPEG_FF BUTTON_FF + +#elif CONFIG_KEYPAD == SANSA_E200_PAD +#define MPEG_MENU BUTTON_SELECT +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_RIGHT +#define MPEG_VOLDOWN BUTTON_SCROLL_BACK +#define MPEG_VOLUP BUTTON_SCROLL_FWD +#define MPEG_RW BUTTON_UP +#define MPEG_FF BUTTON_DOWN + +#elif CONFIG_KEYPAD == SANSA_FUZE_PAD +#define MPEG_MENU BUTTON_SELECT +#define MPEG_STOP (BUTTON_HOME|BUTTON_REPEAT) +#define MPEG_PAUSE BUTTON_UP +#define MPEG_VOLDOWN BUTTON_SCROLL_BACK +#define MPEG_VOLUP BUTTON_SCROLL_FWD +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + + +#elif CONFIG_KEYPAD == SANSA_C200_PAD || \ +CONFIG_KEYPAD == SANSA_CLIP_PAD || \ +CONFIG_KEYPAD == SANSA_M200_PAD +#define MPEG_MENU BUTTON_SELECT +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_UP +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == MROBE500_PAD +#define MPEG_STOP BUTTON_POWER + +#define MPEG_RC_MENU BUTTON_RC_HEART +#define MPEG_RC_STOP BUTTON_RC_DOWN +#define MPEG_RC_PAUSE BUTTON_RC_PLAY +#define MPEG_RC_VOLDOWN BUTTON_RC_VOL_DOWN +#define MPEG_RC_VOLUP BUTTON_RC_VOL_UP +#define MPEG_RC_RW BUTTON_RC_REW +#define MPEG_RC_FF BUTTON_RC_FF + +#elif CONFIG_KEYPAD == MROBE100_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_VOLDOWN BUTTON_DOWN +#define MPEG_VOLUP BUTTON_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == IAUDIO_M3_PAD +#define MPEG_MENU BUTTON_RC_MENU +#define MPEG_STOP BUTTON_RC_REC +#define MPEG_PAUSE BUTTON_RC_PLAY +#define MPEG_VOLDOWN BUTTON_RC_VOL_DOWN +#define MPEG_VOLUP BUTTON_RC_VOL_UP +#define MPEG_RW BUTTON_RC_REW +#define MPEG_FF BUTTON_RC_FF + +#elif CONFIG_KEYPAD == COWON_D2_PAD +#define MPEG_MENU (BUTTON_MENU|BUTTON_REL) +//#define MPEG_STOP BUTTON_POWER +#define MPEG_VOLDOWN BUTTON_MINUS +#define MPEG_VOLUP BUTTON_PLUS + +#elif CONFIG_KEYPAD == CREATIVEZVM_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_STOP BUTTON_BACK +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_VOLDOWN BUTTON_UP +#define MPEG_VOLUP BUTTON_DOWN +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_STOP (BUTTON_PLAY|BUTTON_REPEAT) +#define MPEG_PAUSE (BUTTON_PLAY|BUTTON_REL) +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_DOWN +#define MPEG_FF BUTTON_UP + +#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_SELECT +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_PREV +#define MPEG_FF BUTTON_NEXT + +#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_UP +#define MPEG_FF BUTTON_DOWN + +#elif CONFIG_KEYPAD == ONDAVX747_PAD +#define MPEG_MENU (BUTTON_MENU|BUTTON_REL) +//#define MPEG_STOP BUTTON_POWER +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP + +#elif CONFIG_KEYPAD == ONDAVX777_PAD +#define MPEG_MENU BUTTON_POWER + +#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \ + (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD) +#define MPEG_MENU BUTTON_REW +#define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT) +#define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL) +#define MPEG_VOLDOWN BUTTON_DOWN +#define MPEG_VOLUP BUTTON_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT +#define MPEG_SHOW_OSD BUTTON_FFWD + +#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_STOP BUTTON_REC +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_VOLDOWN BUTTON_DOWN +#define MPEG_VOLUP BUTTON_UP +#define MPEG_RW BUTTON_PREV +#define MPEG_FF BUTTON_NEXT + +#elif CONFIG_KEYPAD == MPIO_HD200_PAD +#define MPEG_MENU BUTTON_FUNC +#define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL) +#define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT) +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_REW +#define MPEG_FF BUTTON_FF + +#elif CONFIG_KEYPAD == MPIO_HD300_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_PAUSE (BUTTON_PLAY | BUTTON_REL) +#define MPEG_STOP (BUTTON_PLAY | BUTTON_REPEAT) +#define MPEG_VOLDOWN BUTTON_DOWN +#define MPEG_VOLUP BUTTON_UP +#define MPEG_RW BUTTON_REW +#define MPEG_FF BUTTON_FF + +#elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD +#define MPEG_MENU BUTTON_POWER +#define MPEG_PAUSE (BUTTON_PLAYPAUSE | BUTTON_REL) +#define MPEG_STOP (BUTTON_PLAYPAUSE | BUTTON_REPEAT) +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == SANSA_CONNECT_PAD +#define MPEG_MENU BUTTON_POWER +#define MPEG_PAUSE (BUTTON_SELECT | BUTTON_REL) +#define MPEG_STOP (BUTTON_SELECT | BUTTON_REPEAT) +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == SAMSUNG_YPR0_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_PAUSE BUTTON_SELECT +#define MPEG_STOP BUTTON_POWER +#define MPEG_VOLDOWN BUTTON_DOWN +#define MPEG_VOLUP BUTTON_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == HM60X_PAD +#define MPEG_MENU BUTTON_POWER +#define MPEG_PAUSE BUTTON_SELECT +#define MPEG_STOP (BUTTON_SELECT | BUTTON_POWER) +#define MPEG_VOLDOWN (BUTTON_POWER | BUTTON_DOWN) +#define MPEG_VOLUP (BUTTON_POWER | BUTTON_UP) +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == HM801_PAD +#define MPEG_MENU BUTTON_POWER +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_STOP (BUTTON_POWER | BUTTON_PLAY) +#define MPEG_VOLDOWN (BUTTON_POWER | BUTTON_DOWN) +#define MPEG_VOLUP (BUTTON_POWER | BUTTON_UP) +#define MPEG_RW BUTTON_PREV +#define MPEG_FF BUTTON_NEXT + +#elif CONFIG_KEYPAD == SONY_NWZ_PAD +#define MPEG_MENU BUTTON_BACK +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_STOP BUTTON_POWER +#define MPEG_VOLDOWN BUTTON_UP +#define MPEG_VOLUP BUTTON_DOWN +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == CREATIVE_ZEN_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_PAUSE BUTTON_PLAYPAUSE +#define MPEG_STOP BUTTON_BACK +#define MPEG_VOLDOWN BUTTON_DOWN +#define MPEG_VOLUP BUTTON_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == DX50_PAD +#define MPEG_MENU BUTTON_POWER +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_STOP (BUTTON_PLAY|BUTTON_REPEAT) + +#elif CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD +#define MPEG_MENU BUTTON_POWER +#define MPEG_PAUSE BUTTON_MENU +#define MPEG_STOP (BUTTON_MENU|BUTTON_REPEAT) + +#elif CONFIG_KEYPAD == AGPTEK_ROCKER_PAD +#define MPEG_MENU BUTTON_POWER +#define MPEG_PAUSE BUTTON_SELECT +#define MPEG_STOP BUTTON_DOWN +#define MPEG_VOLDOWN BUTTON_VOLDOWN +#define MPEG_VOLUP BUTTON_VOLUP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == XDUOO_X3_PAD +#define MPEG_MENU BUTTON_PLAY +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_HOME +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_PREV +#define MPEG_FF BUTTON_NEXT + +#elif CONFIG_KEYPAD == XDUOO_X3II_PAD || CONFIG_KEYPAD == XDUOO_X20_PAD +#define MPEG_MENU BUTTON_PLAY +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_HOME +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_PREV +#define MPEG_FF BUTTON_NEXT + +#elif CONFIG_KEYPAD == FIIO_M3K_LINUX_PAD +#define MPEG_MENU BUTTON_PLAY +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_HOME +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_PREV +#define MPEG_FF BUTTON_NEXT + +#elif CONFIG_KEYPAD == IHIFI_770_PAD || CONFIG_KEYPAD == IHIFI_800_PAD +#define MPEG_MENU BUTTON_PLAY +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_HOME +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_PREV +#define MPEG_FF BUTTON_NEXT + +#elif CONFIG_KEYPAD == EROSQ_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_PREV +#define MPEG_FF BUTTON_NEXT + +#elif CONFIG_KEYPAD == FIIO_M3K_PAD +#define MPEG_MENU BUTTON_MENU +#define MPEG_STOP BUTTON_POWER +#define MPEG_PAUSE BUTTON_PLAY +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD +/* use touchscreen */ + +#else +#error No keymap defined! +#endif + +#ifdef HAVE_TOUCHSCREEN +#ifndef MPEG_MENU +#define MPEG_MENU (BUTTON_TOPRIGHT|BUTTON_REL) +#endif +#ifndef MPEG_STOP +#define MPEG_STOP BUTTON_TOPLEFT +#endif +#ifndef MPEG_PAUSE +#define MPEG_PAUSE BUTTON_CENTER +#endif +#ifndef MPEG_VOLDOWN +#define MPEG_VOLDOWN BUTTON_BOTTOMMIDDLE +#endif +#ifndef MPEG_VOLUP +#define MPEG_VOLUP BUTTON_TOPMIDDLE +#endif +#ifndef MPEG_RW +#define MPEG_RW BUTTON_MIDLEFT +#endif +#ifndef MPEG_FF +#define MPEG_FF BUTTON_MIDRIGHT +#endif +#endif + +/* One thing we can do here for targets with remotes is having a display + * always on the remote instead of always forcing a popup on the main display */ + +#define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */ + /* 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 +{ + VIDEO_STOP = 0, + VIDEO_PREV, + VIDEO_NEXT, + VIDEO_ACTION_MANUAL = 0x8000, /* Flag that says user did it */ +}; + +/* OSD status - same order as icon array */ +enum osd_status_enum +{ + OSD_STATUS_STOPPED = 0, + OSD_STATUS_PAUSED, + OSD_STATUS_PLAYING, + OSD_STATUS_FF, + OSD_STATUS_RW, + OSD_STATUS_COUNT, + OSD_STATUS_MASK = 0x7 +}; + +enum osd_bits +{ + OSD_REFRESH_DEFAULT = 0x0000, /* Only refresh elements when due */ + /* Refresh the... */ + OSD_REFRESH_VOLUME = 0x0001, /* ...volume display */ + OSD_REFRESH_TIME = 0x0002, /* ...time display+progress */ + OSD_REFRESH_STATUS = 0x0004, /* ...playback status icon */ + OSD_REFRESH_BACKGROUND = 0x0008, /* ...background (implies ALL) */ + OSD_REFRESH_VIDEO = 0x0010, /* ...video image upon timeout */ + OSD_REFRESH_RESUME = 0x0020, /* Resume playback upon timeout */ + OSD_NODRAW = 0x8000, /* OR bitflag - don't draw anything */ + OSD_SHOW = 0x4000, /* OR bitflag - show the OSD */ +#ifdef HAVE_HEADPHONE_DETECTION + OSD_HP_PAUSE = 0x2000, /* OR bitflag - headphones caused pause */ +#endif + OSD_HIDE = 0x0000, /* hide the OSD (aid readability) */ + OSD_REFRESH_ALL = 0x000f, /* Only immediate graphical elements */ +}; + +/* Status icons selected according to font height */ +extern const unsigned char mpegplayer_status_icons_8x8x1[]; +extern const unsigned char mpegplayer_status_icons_12x12x1[]; +extern const unsigned char mpegplayer_status_icons_16x16x1[]; + +/* Main border areas that contain OSD elements */ +#define OSD_BDR_L 2 +#define OSD_BDR_T 2 +#define OSD_BDR_R 2 +#define OSD_BDR_B 2 + +struct osd +{ + long hide_tick; + long show_for; + long print_tick; + long print_delay; + long resume_tick; + long resume_delay; + long next_auto_refresh; + int x; + int y; + int width; + int height; + unsigned fgcolor; + unsigned bgcolor; + unsigned prog_fillcolor; + struct vo_rect update_rect; + struct vo_rect prog_rect; + struct vo_rect time_rect; + struct vo_rect dur_rect; + struct vo_rect vol_rect; + const unsigned char *icons; + struct vo_rect stat_rect; + int status; + 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 */ + +#ifdef LCD_PORTRAIT +static fb_data* get_framebuffer(void) +{ + struct viewport *vp_main = *(rb->screens[SCREEN_MAIN]->current_viewport); + return vp_main->buffer->fb_ptr; +} +#endif + +static void osd_show(unsigned show); + +#ifdef LCD_LANDSCAPE + #define __X (x + osd.x) + #define __Y (y + osd.y) + #define __W width + #define __H height +#else + #define __X (LCD_WIDTH - (y + osd.y) - height) + #define __Y (x + osd.x) + #define __W height + #define __H width +#endif + +#ifdef HAVE_LCD_COLOR +/* Blend two colors in 0-100% (0-255) mix of c2 into c1 */ +static unsigned draw_blendcolor(unsigned c1, unsigned c2, unsigned char amount) +{ + int r1 = RGB_UNPACK_RED(c1); + int g1 = RGB_UNPACK_GREEN(c1); + int b1 = RGB_UNPACK_BLUE(c1); + + int r2 = RGB_UNPACK_RED(c2); + int g2 = RGB_UNPACK_GREEN(c2); + int b2 = RGB_UNPACK_BLUE(c2); + + return LCD_RGBPACK(amount*(r2 - r1) / 255 + r1, + amount*(g2 - g1) / 255 + g1, + amount*(b2 - b1) / 255 + b1); +} +#endif + +#ifdef PLUGIN_USE_IRAM +/* IRAM preserving mechanism to enable talking menus */ +static char *iram_saved_copy; +extern char iramstart[], iramend[]; + +static void iram_saving_init(void) +{ +#ifndef SIMULATOR + size_t size; + iram_saved_copy = (char *)rb->plugin_get_buffer(&size); + + if (size >= (size_t)(iramend-iramstart)) + iram_saved_copy += size - (size_t)(iramend - iramstart); + else +#endif + iram_saved_copy = NULL; + + return; +} + +void mpegplayer_iram_preserve(void) +{ + if (iram_saved_copy) + { + rb->memcpy(iram_saved_copy, iramstart, iramend-iramstart); +#ifdef HAVE_CPUCACHE_INVALIDATE + /* make the icache (if it exists) up to date with the new code */ + rb->cpucache_invalidate(); +#endif /* HAVE_CPUCACHE_INVALIDATE */ + } + return; +} + +void mpegplayer_iram_restore(void) +{ + if (iram_saved_copy) + { + rb->audio_hard_stop(); + rb->memcpy(iramstart, iram_saved_copy, iramend-iramstart); +#ifdef HAVE_CPUCACHE_INVALIDATE + /* make the icache (if it exists) up to date with the new code */ + rb->cpucache_invalidate(); +#endif /* HAVE_CPUCACHE_INVALIDATE */ + } + return; +} +#endif + +/* Drawing functions that operate rotated on LCD_PORTRAIT displays - + * most are just wrappers of lcd_* functions with transforms applied. + * The origin is the upper-left corner of the OSD area */ +static void draw_update_rect(int x, int y, int width, int height) +{ + mylcd_update_rect(__X, __Y, __W, __H); +} + +static void draw_clear_area(int x, int y, int width, int height) +{ +#ifdef HAVE_LCD_COLOR + rb->screen_clear_area(rb->screens[SCREEN_MAIN], __X, __Y, __W, __H); +#else + int oldmode = grey_get_drawmode(); + grey_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID); + grey_fillrect(__X, __Y, __W, __H); + grey_set_drawmode(oldmode); +#endif +} + +static void draw_clear_area_rect(const struct vo_rect *rc) +{ + draw_clear_area(rc->l, rc->t, rc->r - rc->l, rc->b - rc->t); +} + +static void draw_fillrect(int x, int y, int width, int height) +{ + mylcd_fillrect(__X, __Y, __W, __H); +} + +static void draw_hline(int x1, int x2, int y) +{ +#ifdef LCD_LANDSCAPE + mylcd_hline(x1 + osd.x, x2 + osd.x, y + osd.y); +#else + y = LCD_WIDTH - (y + osd.y) - 1; + mylcd_vline(y, x1 + osd.x, x2 + osd.x); +#endif +} + +static void draw_vline(int x, int y1, int y2) +{ +#ifdef LCD_LANDSCAPE + mylcd_vline(x + osd.x, y1 + osd.y, y2 + osd.y); +#else + y1 = LCD_WIDTH - (y1 + osd.y) - 1; + y2 = LCD_WIDTH - (y2 + osd.y) - 1; + mylcd_hline(y1, y2, x + osd.x); +#endif +} + +static void draw_scrollbar_draw(int x, int y, int width, int height, + uint32_t min, uint32_t max, uint32_t val) +{ + unsigned oldfg = mylcd_get_foreground(); + + draw_hline(x + 1, x + width - 2, y); + draw_hline(x + 1, x + width - 2, y + height - 1); + draw_vline(x, y + 1, y + height - 2); + draw_vline(x + width - 1, y + 1, y + height - 2); + + val = muldiv_uint32(width - 2, val, max - min); + val = MIN(val, (uint32_t)(width - 2)); + + draw_fillrect(x + 1, y + 1, val, height - 2); + + mylcd_set_foreground(osd.prog_fillcolor); + + draw_fillrect(x + 1 + val, y + 1, width - 2 - val, height - 2); + + mylcd_set_foreground(oldfg); +} + +static void draw_scrollbar_draw_rect(const struct vo_rect *rc, int min, + int max, int val) +{ + draw_scrollbar_draw(rc->l, rc->t, rc->r - rc->l, rc->b - rc->t, + min, max, val); +} + +static void draw_setfont(int font) +{ + osd.font = font; + mylcd_setfont(font); +} + +#ifdef LCD_PORTRAIT +/* Portrait displays need rotated text rendering */ + +/* Limited function that only renders in DRMODE_FG and uses absolute screen + * coordinates */ +static void draw_oriented_mono_bitmap_part(const unsigned char *src, + int src_x, int src_y, + int stride, int x, int y, + int width, int height) +{ + const unsigned char *src_end; + fb_data *dst, *dst_end; + unsigned fg_pattern; + + if (x + width > SCREEN_WIDTH) + width = SCREEN_WIDTH - x; /* Clip right */ + if (x < 0) + width += x, x = 0; /* Clip left */ + if (width <= 0) + return; /* nothing left to do */ + + if (y + height > SCREEN_HEIGHT) + height = SCREEN_HEIGHT - y; /* Clip bottom */ + if (y < 0) + height += y, y = 0; /* Clip top */ + if (height <= 0) + return; /* nothing left to do */ + + fg_pattern = rb->lcd_get_foreground(); + /*bg_pattern =*/ rb->lcd_get_background(); + + src += stride * (src_y >> 3) + src_x; /* move starting point */ + src_y &= 7; + src_end = src + width; + + dst = get_framebuffer() + (LCD_WIDTH - y) + x*LCD_WIDTH; + do + { + const unsigned char *src_col = src++; + unsigned data = *src_col >> src_y; + int numbits = 8 - src_y; + + fb_data *dst_col = dst; + dst_end = dst_col - height; + dst += LCD_WIDTH; + + do + { + dst_col--; + + if (data & 1) + *dst_col = FB_SCALARPACK(fg_pattern); +#if 0 + else + *dst_col = bg_pattern; +#endif + data >>= 1; + if (--numbits == 0) { + src_col += stride; + data = *src_col; + numbits = 8; + } + } + while (dst_col > dst_end); + } + while (src < src_end); +} + + +#define ALPHA_COLOR_FONT_DEPTH 2 +#define ALPHA_COLOR_LOOKUP_SHIFT (1 << ALPHA_COLOR_FONT_DEPTH) +#define ALPHA_COLOR_LOOKUP_SIZE ((1 << ALPHA_COLOR_LOOKUP_SHIFT) - 1) +#define ALPHA_COLOR_PIXEL_PER_BYTE (8 >> ALPHA_COLOR_FONT_DEPTH) +#define ALPHA_COLOR_PIXEL_PER_WORD (32 >> ALPHA_COLOR_FONT_DEPTH) +#ifdef CPU_ARM +#define BLEND_INIT do {} while (0) +#define BLEND_FINISH do {} while(0) +#define BLEND_START(acc, color, alpha) \ + asm volatile("mul %0, %1, %2" : "=&r" (acc) : "r" (color), "r" (alpha)) +#define BLEND_CONT(acc, color, alpha) \ + asm volatile("mla %0, %1, %2, %0" : "+&r" (acc) : "r" (color), "r" (alpha)) +#define BLEND_OUT(acc) do {} while (0) +#elif defined(CPU_COLDFIRE) +#define ALPHA_BITMAP_READ_WORDS +#define BLEND_INIT \ + unsigned long _macsr = coldfire_get_macsr(); \ + coldfire_set_macsr(EMAC_UNSIGNED) +#define BLEND_FINISH \ + coldfire_set_macsr(_macsr) +#define BLEND_START(acc, color, alpha) \ + asm volatile("mac.l %0, %1, %%acc0" :: "%d" (color), "d" (alpha)) +#define BLEND_CONT BLEND_START +#define BLEND_OUT(acc) asm volatile("movclr.l %%acc0, %0" : "=d" (acc)) +#else +#define BLEND_INIT do {} while (0) +#define BLEND_FINISH do {} while(0) +#define BLEND_START(acc, color, alpha) ((acc) = (color) * (alpha)) +#define BLEND_CONT(acc, color, alpha) ((acc) += (color) * (alpha)) +#define BLEND_OUT(acc) do {} while (0) +#endif + +/* Blend the given two colors */ +static inline unsigned blend_two_colors(unsigned c1, unsigned c2, unsigned a) +{ +#if LCD_DEPTH == 16 + a += a >> (ALPHA_COLOR_LOOKUP_SHIFT - 1); +#if (LCD_PIXELFORMAT == RGB565SWAPPED) + c1 = swap16(c1); + c2 = swap16(c2); +#endif + unsigned c1l = (c1 | (c1 << 16)) & 0x07e0f81f; + unsigned c2l = (c2 | (c2 << 16)) & 0x07e0f81f; + unsigned p; + BLEND_START(p, c1l, a); + BLEND_CONT(p, c2l, ALPHA_COLOR_LOOKUP_SIZE + 1 - a); + BLEND_OUT(p); + p = (p >> ALPHA_COLOR_LOOKUP_SHIFT) & 0x07e0f81f; + p |= (p >> 16); +#if (LCD_PIXELFORMAT == RGB565SWAPPED) + return swap16(p); +#else + return p; +#endif + +#else /* LCD_DEPTH == 24 */ + unsigned s = c1; + unsigned d = c2; + unsigned s1 = s & 0xff00ff; + unsigned d1 = d & 0xff00ff; + a += a >> (ALPHA_COLOR_LOOKUP_SHIFT - 1); + d1 = (d1 + ((s1 - d1) * a >> ALPHA_COLOR_LOOKUP_SHIFT)) & 0xff00ff; + s &= 0xff00; + d &= 0xff00; + d = (d + ((s - d) * a >> ALPHA_COLOR_LOOKUP_SHIFT)) & 0xff00; + + return d1 | d; +#endif +} + +static void draw_oriented_alpha_bitmap_part(const unsigned char *src, + int src_x, int src_y, + int stride, int x, int y, + int width, int height) +{ + fb_data *dst, *dst_start; + unsigned fg_pattern; + + if (x + width > SCREEN_WIDTH) + width = SCREEN_WIDTH - x; /* Clip right */ + if (x < 0) + width += x, x = 0; /* Clip left */ + if (width <= 0) + return; /* nothing left to do */ + + if (y + height > SCREEN_HEIGHT) + height = SCREEN_HEIGHT - y; /* Clip bottom */ + if (y < 0) + height += y, y = 0; /* Clip top */ + if (height <= 0) + return; /* nothing left to do */ + + /* initialize blending */ + BLEND_INIT; + + fg_pattern = rb->lcd_get_foreground(); + /*bg_pattern=*/ rb->lcd_get_background(); + + dst_start = get_framebuffer() + (LCD_WIDTH - y - 1) + x*LCD_WIDTH; + int col, row = height; + unsigned data, pixels; + unsigned skip_end = (stride - width); + unsigned skip_start = src_y * stride + src_x; + +#ifdef ALPHA_BITMAP_READ_WORDS + uint32_t *src_w = (uint32_t *)((uintptr_t)src & ~3); + skip_start += ALPHA_COLOR_PIXEL_PER_BYTE * ((uintptr_t)src & 3); + src_w += skip_start / ALPHA_COLOR_PIXEL_PER_WORD; + data = letoh32(*src_w++); +#else + src += skip_start / ALPHA_COLOR_PIXEL_PER_BYTE; + data = *src; +#endif + pixels = skip_start % ALPHA_COLOR_PIXEL_PER_WORD; + data >>= pixels * ALPHA_COLOR_LOOKUP_SHIFT; +#ifdef ALPHA_BITMAP_READ_WORDS + pixels = 8 - pixels; +#endif + + do + { + col = width; + dst = dst_start--; +#ifdef ALPHA_BITMAP_READ_WORDS +#define UPDATE_SRC_ALPHA do { \ + if (--pixels) \ + data >>= ALPHA_COLOR_LOOKUP_SHIFT; \ + else \ + { \ + data = letoh32(*src_w++); \ + pixels = ALPHA_COLOR_PIXEL_PER_WORD; \ + } \ + } while (0) +#elif ALPHA_COLOR_PIXEL_PER_BYTE == 2 +#define UPDATE_SRC_ALPHA do { \ + if (pixels ^= 1) \ + data >>= ALPHA_COLOR_LOOKUP_SHIFT; \ + else \ + data = *(++src); \ + } while (0) +#else +#define UPDATE_SRC_ALPHA do { \ + if (pixels = (++pixels % ALPHA_COLOR_PIXEL_PER_BYTE)) \ + data >>= ALPHA_COLOR_LOOKUP_SHIFT; \ + else \ + data = *(++src); \ + } while (0) +#endif + do + { + unsigned color = blend_two_colors(FB_UNPACK_SCALAR_LCD(*dst), fg_pattern, + data & ALPHA_COLOR_LOOKUP_SIZE ); + *dst= FB_SCALARPACK(color); + dst += LCD_WIDTH; + UPDATE_SRC_ALPHA; + } + while (--col); +#ifdef ALPHA_BITMAP_READ_WORDS + if (skip_end < pixels) + { + pixels -= skip_end; + data >>= skip_end * ALPHA_COLOR_LOOKUP_SHIFT; + } else { + pixels = skip_end - pixels; + src_w += pixels / ALPHA_COLOR_PIXEL_PER_WORD; + pixels %= ALPHA_COLOR_PIXEL_PER_WORD; + data = letoh32(*src_w++); + data >>= pixels * ALPHA_COLOR_LOOKUP_SHIFT; + pixels = 8 - pixels; + } +#else + if (skip_end) + { + pixels += skip_end; + if (pixels >= ALPHA_COLOR_PIXEL_PER_BYTE) + { + src += pixels / ALPHA_COLOR_PIXEL_PER_BYTE; + pixels %= ALPHA_COLOR_PIXEL_PER_BYTE; + data = *src; + data >>= pixels * ALPHA_COLOR_LOOKUP_SHIFT; + } else + data >>= skip_end * ALPHA_COLOR_LOOKUP_SHIFT; + } +#endif + } while (--row); +} + +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(osd.font); + + ucs = rb->bidi_l2v(str, 1); + + x += osd.x; + y += osd.y; + + while ((ch = *ucs++) != 0 && x < SCREEN_WIDTH) + { + int width; + const unsigned char *bits; + + /* get proportional width and glyph bits */ + width = rb->font_get_width(pf, ch); + + if (ofs > width) { + ofs -= width; + continue; + } + + bits = rb->font_get_bits(pf, ch); + + if (pf->depth) + draw_oriented_alpha_bitmap_part(bits, ofs, 0, width, x, y, + width - ofs, pf->height); + else + draw_oriented_mono_bitmap_part(bits, ofs, 0, width, x, y, + width - ofs, pf->height); + + x += width - ofs; + ofs = 0; + } +} +#else +static void draw_oriented_mono_bitmap_part(const unsigned char *src, + int src_x, int src_y, + int stride, int x, int y, + int width, int height) +{ + int mode = mylcd_get_drawmode(); + mylcd_set_drawmode(DRMODE_FG); + mylcd_mono_bitmap_part(src, src_x, src_y, stride, x, y, width, height); + mylcd_set_drawmode(mode); +} + +static void draw_putsxy_oriented(int x, int y, const char *str) +{ + int mode = mylcd_get_drawmode(); + mylcd_set_drawmode(DRMODE_FG); + mylcd_putsxy(x + osd.x, y + osd.y, str); + mylcd_set_drawmode(mode); +} +#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(unsigned short id, void* param) +{ + (void)id; + (void)param; + rb->queue_post(rb->button_queue, LCD_ENABLE_EVENT_1, 0); +} +#endif + +static void osdbacklight_hw_on_video_mode(bool video_on) +{ + if (video_on) { +#ifdef HAVE_BACKLIGHT + /* Turn off backlight timeout */ + backlight_ignore_timeout(); +#endif +#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) + rb->remove_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook); +#endif + } else { +#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) + rb->add_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook); +#endif +#ifdef HAVE_BACKLIGHT + /* Revert to user's backlight settings */ + backlight_use_settings(); +#endif + } +} + +#ifdef HAVE_BACKLIGHT_BRIGHTNESS +static void osd_backlight_brightness_video_mode(bool video_on) +{ + if (settings.backlight_brightness < 0) + return; + + mpeg_backlight_update_brightness( + video_on ? settings.backlight_brightness : -1); +} +#else +#define osd_backlight_brightness_video_mode(video_on) +#endif /* HAVE_BACKLIGHT_BRIGHTNESS */ + +static void osd_text_init(void) +{ + struct hms hms; + char buf[32]; + int phys; + int spc_width; + + draw_setfont(FONT_UI); + + osd.x = 0; + osd.width = SCREEN_WIDTH; + + vo_rect_clear(&osd.time_rect); + vo_rect_clear(&osd.stat_rect); + vo_rect_clear(&osd.prog_rect); + vo_rect_clear(&osd.vol_rect); + + ts_to_hms(stream_get_duration(), &hms); + hms_format(buf, sizeof (buf), &hms); + mylcd_getstringsize(buf, &osd.time_rect.r, &osd.time_rect.b); + + /* Choose well-sized bitmap images relative to font height */ + if (osd.time_rect.b < 12) { + osd.icons = mpegplayer_status_icons_8x8x1; + osd.stat_rect.r = osd.stat_rect.b = 8; + } else if (osd.time_rect.b < 16) { + osd.icons = mpegplayer_status_icons_12x12x1; + osd.stat_rect.r = osd.stat_rect.b = 12; + } else { + osd.icons = mpegplayer_status_icons_16x16x1; + osd.stat_rect.r = osd.stat_rect.b = 16; + } + + if (osd.stat_rect.b < osd.time_rect.b) { + vo_rect_offset(&osd.stat_rect, 0, + (osd.time_rect.b - osd.stat_rect.b) / 2 + OSD_BDR_T); + vo_rect_offset(&osd.time_rect, OSD_BDR_L, OSD_BDR_T); + } else { + vo_rect_offset(&osd.time_rect, OSD_BDR_L, + osd.stat_rect.b - osd.time_rect.b + OSD_BDR_T); + vo_rect_offset(&osd.stat_rect, 0, OSD_BDR_T); + } + + osd.dur_rect = osd.time_rect; + + phys = rb->sound_val2phys(SOUND_VOLUME, rb->sound_min(SOUND_VOLUME)); + rb->snprintf(buf, sizeof(buf), "%d%s", phys, + rb->sound_unit(SOUND_VOLUME)); + + mylcd_getstringsize(" ", &spc_width, NULL); + mylcd_getstringsize(buf, &osd.vol_rect.r, &osd.vol_rect.b); + + osd.prog_rect.r = SCREEN_WIDTH - OSD_BDR_L - spc_width - + osd.vol_rect.r - OSD_BDR_R; + osd.prog_rect.b = 3*osd.stat_rect.b / 4; + vo_rect_offset(&osd.prog_rect, osd.time_rect.l, + osd.time_rect.b); + + vo_rect_offset(&osd.stat_rect, + (osd.prog_rect.r + osd.prog_rect.l - osd.stat_rect.r) / 2, + 0); + + vo_rect_offset(&osd.dur_rect, + osd.prog_rect.r - osd.dur_rect.r, 0); + + vo_rect_offset(&osd.vol_rect, osd.prog_rect.r + spc_width, + (osd.prog_rect.b + osd.prog_rect.t - osd.vol_rect.b) / 2); + + osd.height = OSD_BDR_T + MAX(osd.prog_rect.b, osd.vol_rect.b) - + MIN(osd.time_rect.t, osd.stat_rect.t) + OSD_BDR_B; + +#ifdef HAVE_LCD_COLOR + osd.height = ALIGN_UP(osd.height, 2); +#endif + osd.y = SCREEN_HEIGHT - osd.height; + + draw_setfont(FONT_SYSFIXED); +} + +static void osd_init(void) +{ + osd.flags = 0; + osd.show_for = HZ*4; + osd.print_delay = 75*HZ/100; + osd.resume_delay = HZ/2; +#ifdef HAVE_LCD_COLOR + osd.bgcolor = LCD_RGBPACK(0x73, 0x75, 0xbd); + osd.fgcolor = LCD_WHITE; + osd.prog_fillcolor = LCD_BLACK; +#else + osd.bgcolor = GREY_LIGHTGRAY; + osd.fgcolor = GREY_BLACK; + osd.prog_fillcolor = GREY_WHITE; +#endif + osd.curr_time = 0; + osd.status = OSD_STATUS_STOPPED; + osd.auto_refresh = OSD_REFRESH_TIME; + osd.next_auto_refresh = *rb->current_tick; + osd_text_init(); + fps_init(); +} + +#ifdef HAVE_HEADPHONE_DETECTION +static void osd_set_hp_pause_flag(bool set) +{ + if (set) + osd.flags |= OSD_HP_PAUSE; + else + osd.flags &= ~OSD_HP_PAUSE; +} +#else +#define osd_set_hp_pause_flag(set) +#endif /* HAVE_HEADPHONE_DETECTION */ + +static void osd_schedule_refresh(unsigned refresh) +{ + long tick = *rb->current_tick; + + if (refresh & OSD_REFRESH_VIDEO) + osd.print_tick = tick + osd.print_delay; + + if (refresh & OSD_REFRESH_RESUME) + osd.resume_tick = tick + osd.resume_delay; + + osd.auto_refresh |= refresh; +} + +static void osd_cancel_refresh(unsigned refresh) +{ + osd.auto_refresh &= ~refresh; +} + +/* Refresh the background area */ +static void osd_refresh_background(void) +{ + char buf[32]; + struct hms hms; + + unsigned bg = mylcd_get_background(); + mylcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID); + +#ifdef HAVE_LCD_COLOR + /* Draw a "raised" area for our graphics */ + mylcd_set_background(draw_blendcolor(bg, MYLCD_WHITE, 192)); + draw_hline(0, osd.width, 0); + + mylcd_set_background(draw_blendcolor(bg, MYLCD_WHITE, 80)); + draw_hline(0, osd.width, 1); + + mylcd_set_background(draw_blendcolor(bg, MYLCD_BLACK, 48)); + draw_hline(0, osd.width, osd.height-2); + + mylcd_set_background(draw_blendcolor(bg, MYLCD_BLACK, 128)); + draw_hline(0, osd.width, osd.height-1); + + mylcd_set_background(bg); + draw_clear_area(0, 2, osd.width, osd.height - 4); +#else + /* Give contrast with the main background */ + mylcd_set_background(MYLCD_WHITE); + draw_hline(0, osd.width, 0); + + mylcd_set_background(MYLCD_DARKGRAY); + draw_hline(0, osd.width, osd.height-1); + + mylcd_set_background(bg); + draw_clear_area(0, 1, osd.width, osd.height - 2); +#endif + + vo_rect_set_ext(&osd.update_rect, 0, 0, osd.width, osd.height); + mylcd_set_drawmode(DRMODE_SOLID); + + if (stream_get_duration() != INVALID_TIMESTAMP) { + /* Draw the movie duration */ + ts_to_hms(stream_get_duration(), &hms); + hms_format(buf, sizeof (buf), &hms); + draw_putsxy_oriented(osd.dur_rect.l, osd.dur_rect.t, buf); + } + /* else don't know the duration */ +} + +/* Refresh the current time display + the progress bar */ +static void osd_refresh_time(void) +{ + char buf[32]; + struct hms hms; + + uint32_t duration = stream_get_duration(); + + draw_scrollbar_draw_rect(&osd.prog_rect, 0, duration, + osd.curr_time); + + ts_to_hms(osd.curr_time, &hms); + hms_format(buf, sizeof (buf), &hms); + + draw_clear_area_rect(&osd.time_rect); + draw_putsxy_oriented(osd.time_rect.l, osd.time_rect.t, buf); + + vo_rect_union(&osd.update_rect, &osd.update_rect, + &osd.prog_rect); + vo_rect_union(&osd.update_rect, &osd.update_rect, + &osd.time_rect); +} + +/* Refresh the volume display area */ +static void osd_refresh_volume(void) +{ + char buf[32]; + int width; + + int volume = rb->global_settings->volume; + rb->snprintf(buf, sizeof (buf), "%d%s", + rb->sound_val2phys(SOUND_VOLUME, volume), + rb->sound_unit(SOUND_VOLUME)); + mylcd_getstringsize(buf, &width, NULL); + + /* Right-justified */ + draw_clear_area_rect(&osd.vol_rect); + draw_putsxy_oriented(osd.vol_rect.r - width, osd.vol_rect.t, buf); + + vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.vol_rect); +} + +/* Refresh the status icon */ +static void osd_refresh_status(void) +{ + int icon_size = osd.stat_rect.r - osd.stat_rect.l; + + draw_clear_area_rect(&osd.stat_rect); + +#ifdef HAVE_LCD_COLOR + /* Draw status icon with a drop shadow */ + unsigned oldfg = mylcd_get_foreground(); + int i = 1; + + mylcd_set_foreground(draw_blendcolor(mylcd_get_background(), + MYLCD_BLACK, 96)); + + while (1) + { + draw_oriented_mono_bitmap_part(osd.icons, + icon_size*osd.status, + 0, + icon_size*OSD_STATUS_COUNT, + osd.stat_rect.l + osd.x + i, + osd.stat_rect.t + osd.y + i, + icon_size, icon_size); + + if (--i < 0) + break; + + mylcd_set_foreground(oldfg); + } + + vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.stat_rect); +#else + draw_oriented_mono_bitmap_part(osd.icons, + icon_size*osd.status, + 0, + icon_size*OSD_STATUS_COUNT, + osd.stat_rect.l + osd.x, + osd.stat_rect.t + osd.y, + icon_size, icon_size); + vo_rect_union(&osd.update_rect, &osd.update_rect, &osd.stat_rect); +#endif +} + +/* Update the current status which determines which icon is displayed */ +static bool osd_update_status(void) +{ + int status; + + switch (stream_status()) + { + default: + status = OSD_STATUS_STOPPED; + break; + case STREAM_PAUSED: + /* If paused with a pending resume, coerce it to OSD_STATUS_PLAYING */ + status = (osd.auto_refresh & OSD_REFRESH_RESUME) ? + OSD_STATUS_PLAYING : OSD_STATUS_PAUSED; + break; + case STREAM_PLAYING: + status = OSD_STATUS_PLAYING; + break; + } + + if (status != osd.status) { + /* A refresh is needed */ + osd.status = status; + return true; + } + + return false; +} + +/* Update the current time that will be displayed */ +static void osd_update_time(void) +{ + uint32_t start; + osd.curr_time = stream_get_seek_time(&start); + osd.curr_time -= start; +} + +/* Refresh various parts of the OSD - showing it if it is hidden */ +static void osd_refresh(int hint) +{ + long tick; + unsigned oldbg, oldfg; + + tick = *rb->current_tick; + + if (settings.showfps) + fps_refresh(); + + if (hint == OSD_REFRESH_DEFAULT) { + /* The default which forces no updates */ + + /* Make sure Rockbox doesn't turn off the player because of + too little activity */ + if (osd.status == OSD_STATUS_PLAYING) + rb->reset_poweroff_timer(); + + /* Redraw the current or possibly extract a new video frame */ + if ((osd.auto_refresh & OSD_REFRESH_VIDEO) && + TIME_AFTER(tick, osd.print_tick)) { + osd.auto_refresh &= ~OSD_REFRESH_VIDEO; + stream_draw_frame(false); + } + + /* Restart playback if the timout was reached */ + if ((osd.auto_refresh & OSD_REFRESH_RESUME) && + TIME_AFTER(tick, osd.resume_tick)) { + osd.auto_refresh &= ~(OSD_REFRESH_RESUME | OSD_REFRESH_VIDEO); + stream_resume(); + } + + /* If not visible, return */ + if (!(osd.flags & OSD_SHOW)) + return; + + /* Hide if the visibility duration was reached */ + if (TIME_AFTER(tick, osd.hide_tick)) { + osd_show(OSD_HIDE); + return; + } + } else { + /* A forced update of some region */ + + /* Show if currently invisible */ + if (!(osd.flags & OSD_SHOW)) { + /* Avoid call back into this function - it will be drawn */ + osd_show(OSD_SHOW | OSD_NODRAW); + hint = OSD_REFRESH_ALL; + } + + /* Move back timeouts for frame print and hide */ + osd.print_tick = tick + osd.print_delay; + osd.hide_tick = tick + osd.show_for; + } + + if (TIME_AFTER(tick, osd.next_auto_refresh)) { + /* Refresh whatever graphical elements are due automatically */ + osd.next_auto_refresh = tick + OSD_MIN_UPDATE_INTERVAL; + + if (osd.auto_refresh & OSD_REFRESH_STATUS) { + if (osd_update_status()) + hint |= OSD_REFRESH_STATUS; + } + + if (osd.auto_refresh & OSD_REFRESH_TIME) { + osd_update_time(); + hint |= OSD_REFRESH_TIME; + } + } + + if (hint == 0) + return; /* No drawing needed */ + + /* Set basic drawing params that are used. Elements that perform variations + * will restore them. */ + oldfg = mylcd_get_foreground(); + oldbg = mylcd_get_background(); + + draw_setfont(FONT_UI); + mylcd_set_foreground(osd.fgcolor); + mylcd_set_background(osd.bgcolor); + + vo_rect_clear(&osd.update_rect); + + if (hint & OSD_REFRESH_BACKGROUND) { + osd_refresh_background(); + hint |= OSD_REFRESH_ALL; /* Requires a redraw of everything */ + } + + if (hint & OSD_REFRESH_TIME) { + osd_refresh_time(); + } + + if (hint & OSD_REFRESH_VOLUME) { + osd_refresh_volume(); + } + + if (hint & OSD_REFRESH_STATUS) { + osd_refresh_status(); + } + + /* Go back to defaults */ + draw_setfont(FONT_SYSFIXED); + mylcd_set_foreground(oldfg); + mylcd_set_background(oldbg); + + /* Update the dirty rectangle */ + vo_lock(); + + draw_update_rect(osd.update_rect.l, + osd.update_rect.t, + osd.update_rect.r - osd.update_rect.l, + osd.update_rect.b - osd.update_rect.t); + + vo_unlock(); +} + +/* Show/Hide the OSD */ +static void osd_show(unsigned show) +{ + if (((show ^ osd.flags) & OSD_SHOW) == 0) + { + if (show & OSD_SHOW) { + osd.hide_tick = *rb->current_tick + osd.show_for; + } + return; + } + + if (show & OSD_SHOW) { + /* Clip away the part of video that is covered */ + struct vo_rect rc = { 0, 0, SCREEN_WIDTH, osd.y }; + + osd.flags |= OSD_SHOW; + + if (osd.status != OSD_STATUS_PLAYING) { + /* Not playing - set brightness to mpegplayer setting */ + osd_backlight_brightness_video_mode(true); + } + + stream_vo_set_clip(&rc); + + if (!(show & OSD_NODRAW)) + osd_refresh(OSD_REFRESH_ALL); + } else { + /* Uncover clipped video area and redraw it */ + osd.flags &= ~OSD_SHOW; + + draw_clear_area(0, 0, osd.width, osd.height); + + if (!(show & OSD_NODRAW)) { + vo_lock(); + draw_update_rect(0, 0, osd.width, osd.height); + vo_unlock(); + + stream_vo_set_clip(NULL); + stream_draw_frame(false); + } else { + stream_vo_set_clip(NULL); + } + + if (osd.status != OSD_STATUS_PLAYING) { + /* Not playing - restore backlight brightness */ + osd_backlight_brightness_video_mode(false); + } + } +} + +/* Set the current status - update screen if specified */ +static void osd_set_status(int status) +{ + bool draw = (status & OSD_NODRAW) == 0; + + status &= OSD_STATUS_MASK; + + if (osd.status != status) { + + osd.status = status; + + if (draw) + osd_refresh(OSD_REFRESH_STATUS); + } +} + +/* Get the current status value */ +static int osd_get_status(void) +{ + return osd.status & OSD_STATUS_MASK; +} + +/* Handle Fast-forward/Rewind keys using WPS settings (and some nicked code ;) + * Returns last button code + */ +static int osd_ff_rw(int btn, unsigned refresh, uint32_t *new_time) +{ + unsigned int step = TS_SECOND*rb->global_settings->ff_rewind_min_step; + const long ff_rw_accel = (rb->global_settings->ff_rewind_accel + 3); + uint32_t start; + uint32_t time = stream_get_seek_time(&start); + const uint32_t duration = stream_get_duration(); + unsigned int max_step = 0; + uint32_t ff_rw_count = 0; + unsigned status = osd.status; + int new_btn; + + osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME | + OSD_REFRESH_TIME); + + time -= start; /* Absolute clock => stream-relative */ + + switch (btn) + { + case MPEG_FF: +#ifdef MPEG_FF2 + case MPEG_FF2: +#endif +#ifdef MPEG_RC_FF + case MPEG_RC_FF: +#endif + osd_set_status(OSD_STATUS_FF); + new_btn = btn | BUTTON_REPEAT; /* simplify code below */ + break; + case MPEG_RW: +#ifdef MPEG_RW2 + case MPEG_RW2: +#endif +#ifdef MPEG_RC_RW + case MPEG_RC_RW: +#endif + osd_set_status(OSD_STATUS_RW); + new_btn = btn | BUTTON_REPEAT; /* simplify code below */ + break; + default: + new_btn = BUTTON_NONE; /* Fail tests below but still do proper exit */ + } + + while (1) + { + stream_keep_disk_active(); + + if (new_btn == (btn | BUTTON_REPEAT)) { + if (osd.status == OSD_STATUS_FF) { + /* fast forwarding, calc max step relative to end */ + max_step = muldiv_uint32(duration - (time + ff_rw_count), + FF_REWIND_MAX_PERCENT, 100); + } else { + /* rewinding, calc max step relative to start */ + max_step = muldiv_uint32(time - ff_rw_count, + FF_REWIND_MAX_PERCENT, 100); + } + + max_step = MAX(max_step, MIN_FF_REWIND_STEP); + + if (step > max_step) + step = max_step; + + ff_rw_count += step; + + /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */ + step += step >> ff_rw_accel; + + if (osd.status == OSD_STATUS_FF) { + if (duration - time <= ff_rw_count) + ff_rw_count = duration - time; + + osd.curr_time = time + ff_rw_count; + } else { + if (time <= ff_rw_count) + ff_rw_count = time; + + osd.curr_time = time - ff_rw_count; + } + + osd_refresh(OSD_REFRESH_TIME); + + new_btn = mpeg_button_get(TIMEOUT_BLOCK); + } + else { + if (new_btn == (btn | BUTTON_REL)) { + if (osd.status == OSD_STATUS_FF) + time += ff_rw_count; + else if (osd.status == OSD_STATUS_RW) + time -= ff_rw_count; + } + + *new_time = time; + + osd_schedule_refresh(refresh); + osd_set_status(status); + osd_schedule_refresh(OSD_REFRESH_TIME); + + return new_btn; + } + } +} + +/* Return adjusted STREAM_* status */ +static int osd_stream_status(void) +{ + int status = stream_status(); + + /* Coerce to STREAM_PLAYING if paused with a pending resume */ + if (status == STREAM_PAUSED) { + if (osd.auto_refresh & OSD_REFRESH_RESUME) + status = STREAM_PLAYING; + } + + return status; +} + +/* Change the current audio volume by a specified amount */ +static void osd_set_volume(int delta) +{ + int vol = rb->global_settings->volume; + int limit; + + vol += delta; + + if (delta < 0) { + /* Volume down - clip to lower limit */ + limit = rb->sound_min(SOUND_VOLUME); + if (vol < limit) + vol = limit; + } else { + /* Volume up - clip to upper limit */ + limit = rb->sound_max(SOUND_VOLUME); + if (vol > limit) + vol = limit; + } + + /* Sync the global settings */ + if (vol != rb->global_settings->volume) { + rb->sound_set(SOUND_VOLUME, vol); + rb->global_settings->volume = vol; + } + + /* Update the volume display */ + osd_refresh(OSD_REFRESH_VOLUME); +} + +/* Begin playback at the specified time */ +static int osd_play(uint32_t time) +{ + int retval; + + osd_set_hp_pause_flag(false); + osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME); + + retval = stream_seek(time, SEEK_SET); + + if (retval >= STREAM_OK) { + osdbacklight_hw_on_video_mode(true); + osd_backlight_brightness_video_mode(true); + stream_show_vo(true); + + retval = stream_play(); + + if (retval >= STREAM_OK) + osd_set_status(OSD_STATUS_PLAYING | OSD_NODRAW); + } + + return retval; +} + +/* Halt playback - pause engine and return logical state */ +static int osd_halt(void) +{ + int status = stream_pause(); + + /* Coerce to STREAM_PLAYING if paused with a pending resume */ + if (status == STREAM_PAUSED) { + if (osd_get_status() == OSD_STATUS_PLAYING) + status = STREAM_PLAYING; + } + + /* Cancel some auto refreshes - caller will restart them if desired */ + osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME); + + /* No backlight fiddling here - callers does the right thing */ + + return status; +} + +/* Pause playback if playing */ +static int osd_pause(void) +{ + unsigned refresh = osd.auto_refresh; + int status = osd_halt(); + + osd_set_hp_pause_flag(false); + + if (status == STREAM_PLAYING && (refresh & OSD_REFRESH_RESUME)) { + /* Resume pending - change to a still video frame update */ + osd_schedule_refresh(OSD_REFRESH_VIDEO); + } + + osd_set_status(OSD_STATUS_PAUSED); + + osdbacklight_hw_on_video_mode(false); + /* Leave brightness alone and restore it when OSD is hidden */ + + if (stream_can_seek() && rb->global_settings->pause_rewind) { + stream_seek(-rb->global_settings->pause_rewind*TS_SECOND, + SEEK_CUR); + osd_schedule_refresh(OSD_REFRESH_VIDEO); + /* Update time display now */ + osd_update_time(); + osd_refresh(OSD_REFRESH_TIME); + } + + return status; +} + +/* Resume playback if halted or paused */ +static void osd_resume(void) +{ + /* Cancel video and resume auto refresh - the resyc when starting + * playback will perform those tasks */ + osd_set_hp_pause_flag(false); + osdbacklight_hw_on_video_mode(true); + osd_backlight_brightness_video_mode(true); + osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME); + osd_set_status(OSD_STATUS_PLAYING); + stream_resume(); +} + +/* Stop playback - remember the resume point if not closed */ +static void osd_stop(void) +{ + uint32_t resume_time; + + osd_set_hp_pause_flag(false); + osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME); + osd_set_status(OSD_STATUS_STOPPED | OSD_NODRAW); + osd_show(OSD_HIDE); + + stream_stop(); + + resume_time = stream_get_resume_time(); + + if (resume_time != INVALID_TIMESTAMP) + settings.resume_time = resume_time; + + osdbacklight_hw_on_video_mode(false); + osd_backlight_brightness_video_mode(false); +} + +/* Perform a seek by button if seeking is possible for this stream. + * + * A delay will be inserted before restarting in case the user decides to + * seek again soon after. + * + * Returns last button code + */ +static int osd_seek_btn(int btn) +{ + int status; + unsigned refresh = 0; + uint32_t time; + + if (!stream_can_seek()) + return true; + + /* Halt playback - not strictly necessary but nice when doing + * buttons */ + status = osd_halt(); + + if (status == STREAM_STOPPED) + return true; + + osd_show(OSD_SHOW); + + /* Obtain a new playback point according to the buttons */ + if (status == STREAM_PLAYING) + refresh = OSD_REFRESH_RESUME; /* delay resume if playing */ + else + refresh = OSD_REFRESH_VIDEO; /* refresh if paused */ + + btn = osd_ff_rw(btn, refresh, &time); + + /* Tell engine to resume at that time */ + stream_seek(time, SEEK_SET); + + return btn; +} + +/* Perform a seek by time if seeking is possible for this stream + * + * If playing, the seeking is immediate, otherise a delay is added to showing + * a still if paused in case the user does another seek soon after. + * + * If seeking isn't possible, a time of zero performs a skip to the + * beginning. + */ +static void osd_seek_time(uint32_t time) +{ + int status; + unsigned refresh = 0; + + if (!stream_can_seek() && time != 0) + return; + + stream_wait_status(); + status = osd_stream_status(); + + if (status == STREAM_STOPPED) + return; + + if (status == STREAM_PLAYING) /* merely preserve resume */ + refresh = osd.auto_refresh & OSD_REFRESH_RESUME; + else + refresh = OSD_REFRESH_VIDEO; /* refresh if paused */ + + /* Cancel print or resume if pending */ + osd_cancel_refresh(OSD_REFRESH_VIDEO | OSD_REFRESH_RESUME); + + /* Tell engine to seek to the given time - no state change */ + stream_seek(time, SEEK_SET); + + osd_update_time(); + osd_refresh(OSD_REFRESH_TIME); + osd_schedule_refresh(refresh); +} + +/* Has this file one of the supported extensions? */ +static bool is_videofile(const char* file) +{ + static const char * const extensions[] = + { + /* Should match apps/plugins/viewers.config */ + "mpg", "mpeg", "mpv", "m2v" + }; + + const char* ext = rb->strrchr(file, '.'); + int i; + + if (!ext) + return false; + + for (i = ARRAYLEN(extensions) - 1; i >= 0; i--) + { + if (!rb->strcasecmp(ext + 1, extensions[i])) + break; + } + + return i >= 0; +} + +/* deliver the next/previous video file in the current directory. + returns false if there is none. */ +static bool get_videofile(int direction, char* videofile, size_t bufsize) +{ + struct tree_context *tree = rb->tree_get_context(); + struct entry *dircache = rb->tree_get_entries(tree); + int i, step, end, found = 0; + char *videoname = rb->strrchr(videofile, '/') + 1; + size_t rest = bufsize - (videoname - videofile) - 1; + + if (direction == VIDEO_NEXT) { + i = 0; + step = 1; + end = tree->filesindir; + } else { + i = tree->filesindir-1; + step = -1; + end = -1; + } + for (; i != end; i += step) + { + const char* name = dircache[i].name; + if (!rb->strcmp(name, videoname)) { + found = 1; + continue; + } + if (found && rb->strlen(name) <= rest && + !(dircache[i].attr & ATTR_DIRECTORY) && is_videofile(name)) + { + rb->strcpy(videoname, name); + return true; + } + } + + return false; +} + +#ifdef HAVE_HEADPHONE_DETECTION +/* Handle SYS_PHONE_PLUGGED/UNPLUGGED */ +static void osd_handle_phone_plug(bool inserted) +{ + if (rb->global_settings->unplug_mode == 0) + return; + + /* Wait for any incomplete state transition to complete first */ + stream_wait_status(); + + int status = osd_stream_status(); + + if (inserted) { + if (rb->global_settings->unplug_mode > 1) { + if (status == STREAM_PAUSED && + (osd.flags & OSD_HP_PAUSE)) { + osd_resume(); + } + } + } else { + if (status == STREAM_PLAYING) { + osd_pause(); + + osd_set_hp_pause_flag(true); + } + } +} +#endif + +static int button_loop(void) +{ + int next_action = (settings.play_mode == 0) ? VIDEO_STOP : VIDEO_NEXT; + + rb->lcd_setfont(FONT_SYSFIXED); +#ifdef HAVE_LCD_COLOR + rb->lcd_set_foreground(LCD_WHITE); + rb->lcd_set_background(LCD_BLACK); +#endif + rb->lcd_clear_display(); + rb->lcd_update(); + +#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV) + rb->lcd_set_mode(LCD_MODE_YUV); +#endif + + osd_init(); + + /* Start playback at the specified starting time */ + if (osd_play(settings.resume_time) < STREAM_OK) { + rb->splash(HZ*2, "Playback failed"); + return VIDEO_STOP; + } + + /* Gently poll the video player for EOS and handle UI */ + while (stream_status() != STREAM_STOPPED) + { + int button = mpeg_button_get(OSD_MIN_UPDATE_INTERVAL/2); + + switch (button) + { + case BUTTON_NONE: + { + osd_refresh(OSD_REFRESH_DEFAULT); + continue; + } /* BUTTON_NONE: */ + +#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) + case LCD_ENABLE_EVENT_1: + { + /* Draw the current frame if prepared already */ + stream_draw_frame(true); + break; + } /* LCD_ENABLE_EVENT_1: */ +#endif + + case MPEG_VOLUP: + case MPEG_VOLUP|BUTTON_REPEAT: +#ifdef MPEG_VOLUP2 + case MPEG_VOLUP2: + case MPEG_VOLUP2|BUTTON_REPEAT: +#endif +#ifdef MPEG_RC_VOLUP + case MPEG_RC_VOLUP: + case MPEG_RC_VOLUP|BUTTON_REPEAT: +#endif + { + osd_set_volume(+1); + break; + } /* MPEG_VOLUP*: */ + + case MPEG_VOLDOWN: + case MPEG_VOLDOWN|BUTTON_REPEAT: +#ifdef MPEG_VOLDOWN2 + case MPEG_VOLDOWN2: + case MPEG_VOLDOWN2|BUTTON_REPEAT: +#endif +#ifdef MPEG_RC_VOLDOWN + case MPEG_RC_VOLDOWN: + case MPEG_RC_VOLDOWN|BUTTON_REPEAT: +#endif + { + osd_set_volume(-1); + break; + } /* MPEG_VOLDOWN*: */ + + case MPEG_MENU: +#ifdef MPEG_RC_MENU + case MPEG_RC_MENU: +#endif + { + int state = osd_halt(); /* save previous state */ + int result; + + /* Hide video output */ + osd_show(OSD_HIDE | OSD_NODRAW); + stream_show_vo(false); + osd_backlight_brightness_video_mode(false); + +#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV) + rb->lcd_set_mode(LCD_MODE_RGB565); +#endif + + result = mpeg_menu(); + + 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 + rb->lcd_set_foreground(LCD_WHITE); + rb->lcd_set_background(LCD_BLACK); +#endif + rb->lcd_clear_display(); + rb->lcd_update(); + + switch (result) + { + case MPEG_MENU_QUIT: + next_action = VIDEO_STOP; + osd_stop(); + break; + + default: +#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV) + rb->lcd_set_mode(LCD_MODE_YUV); +#endif + /* If not stopped, show video again */ + if (state != STREAM_STOPPED) { + osd_show(OSD_SHOW); + stream_show_vo(true); + } + + /* If stream was playing, restart it */ + if (state == STREAM_PLAYING) { + osd_resume(); + } + break; + } + break; + } /* MPEG_MENU: */ + +#ifdef MPEG_SHOW_OSD + case MPEG_SHOW_OSD: + case MPEG_SHOW_OSD | BUTTON_REPEAT: + /* Show if not visible */ + osd_show(OSD_SHOW); + /* Make sure it refreshes */ + osd_refresh(OSD_REFRESH_DEFAULT); + break; +#endif + + case MPEG_STOP: +#ifdef MPEG_RC_STOP + case MPEG_RC_STOP: +#endif + case ACTION_STD_CANCEL: + { + cancel_playback: + next_action = VIDEO_STOP; + osd_stop(); + break; + } /* MPEG_STOP: */ + + case MPEG_PAUSE: +#ifdef MPEG_PAUSE2 + case MPEG_PAUSE2: +#endif +#ifdef MPEG_RC_PAUSE + case MPEG_RC_PAUSE: +#endif + { + int status = osd_stream_status(); + + if (status == STREAM_PLAYING) { + /* Playing => Paused */ + osd_pause(); + } + else if (status == STREAM_PAUSED) { + /* Paused => Playing */ + osd_resume(); + } + + break; + } /* MPEG_PAUSE*: */ + + case MPEG_RW: +#ifdef MPEG_RW2 + case MPEG_RW2: +#endif +#ifdef MPEG_RC_RW + case MPEG_RC_RW: +#endif + { + int old_button = button; + + /* If button has been released: skip to next/previous file */ + button = mpeg_button_get(OSD_MIN_UPDATE_INTERVAL); + + if ((old_button | BUTTON_REL) == button) { + /* Check current playback position */ + osd_update_time(); + + if (settings.play_mode == 0 || osd.curr_time >= 3*TS_SECOND) { + /* Start the current video from the beginning */ + osd_seek_time(0*TS_SECOND); + } + else { + /* Release within 3 seconds of start: skip to previous + * file */ + osd_stop(); + next_action = VIDEO_PREV | VIDEO_ACTION_MANUAL; + } + } + else if ((button & ~BUTTON_REPEAT) == old_button) { + button = osd_seek_btn(old_button); + } + + if (button == ACTION_STD_CANCEL) + goto cancel_playback; /* jump to stop handling above */ + + rb->default_event_handler(button); + break; + } /* MPEG_RW: */ + + case MPEG_FF: +#ifdef MPEG_FF2 + case MPEG_FF2: +#endif +#ifdef MPEG_RC_FF + case MPEG_RC_FF: +#endif + { + int old_button = button; + + if (settings.play_mode != 0) + button = mpeg_button_get(OSD_MIN_UPDATE_INTERVAL); + + if ((old_button | BUTTON_REL) == button) { + /* If button has been released: skip to next file */ + osd_stop(); + next_action = VIDEO_NEXT | VIDEO_ACTION_MANUAL; + } + else if ((button & ~BUTTON_REPEAT) == old_button) { + button = osd_seek_btn(old_button); + } + + if (button == ACTION_STD_CANCEL) + goto cancel_playback; /* jump to stop handling above */ + + rb->default_event_handler(button); + break; + } /* MPEG_FF: */ + +#ifdef HAVE_HEADPHONE_DETECTION + case SYS_PHONE_PLUGGED: + case SYS_PHONE_UNPLUGGED: + { + osd_handle_phone_plug(button == SYS_PHONE_PLUGGED); + break; + } /* SYS_PHONE_*: */ +#endif + + default: + { + osd_refresh(OSD_REFRESH_DEFAULT); + rb->default_event_handler(button); + break; + } /* default: */ + } + + rb->yield(); + } /* end while */ + + osd_stop(); + +#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) + /* Be sure hook is removed before exiting since the stop will put it + * back because of the backlight restore. */ + rb->remove_event(LCD_EVENT_ACTIVATION, osd_lcd_enable_hook); +#endif + + rb->lcd_setfont(FONT_UI); + + return next_action; +} + +enum plugin_status plugin_start(const void* parameter) +{ + static char videofile[MAX_PATH]; + int status = PLUGIN_OK; /* assume success */ + bool quit = false; + +#if defined(PLUGIN_USE_IRAM) && !defined(SIMULATOR) + bool preserved_talk_state; +#endif + + if (parameter == NULL) { + /* No file = GTFO */ + rb->splash(HZ*2, "No File"); + return PLUGIN_ERROR; + } + + /* Disable all talking before initializing IRAM */ + rb->talk_disable(true); + +#ifdef PLUGIN_USE_IRAM + iram_saving_init(); + +#ifndef SIMULATOR + preserved_talk_state = rb->global_settings->talk_menu; + if (!iram_saved_copy) + rb->global_settings->talk_menu = false; +#endif +#endif + +#ifdef HAVE_LCD_COLOR + rb->lcd_set_backdrop(NULL); + rb->lcd_set_foreground(LCD_WHITE); + rb->lcd_set_background(LCD_BLACK); +#endif + + rb->lcd_clear_display(); + rb->lcd_update(); + + rb->strcpy(videofile, (const char*) parameter); + + if (stream_init() < STREAM_OK) { + /* Fatal because this should not fail */ + DEBUGF("Could not initialize streams\n"); + status = PLUGIN_ERROR; + } else { + int next_action = VIDEO_STOP; + bool get_videofile_says = true; + + while (!quit) + { + init_settings(videofile); + + int result = stream_open(videofile); + bool manual_skip = false; + + if (result >= STREAM_OK) { + /* start menu */ + rb->lcd_clear_display(); + rb->lcd_update(); + result = mpeg_start_menu(stream_get_duration()); + + next_action = VIDEO_STOP; + if (result != MPEG_START_QUIT) { + /* Enter button loop and process UI */ + next_action = button_loop(); + manual_skip = next_action & VIDEO_ACTION_MANUAL; + next_action &= ~VIDEO_ACTION_MANUAL; + } + + stream_close(); + + rb->lcd_clear_display(); + rb->lcd_update(); + + save_settings(); + } else { + /* Problem with file; display message about it - not + * considered a plugin error */ + long tick; + const char *errstring; + + DEBUGF("Could not open %s\n", videofile); + switch (result) + { + case STREAM_UNSUPPORTED: + errstring = "Unsupported format"; + break; + default: + errstring = "Error opening file: %d"; + } + + tick = *rb->current_tick + HZ*2; + + rb->splashf(0, errstring, result); + + /* Be sure it doesn't get stuck in an unbreakable loop of bad + * files, just in case! Otherwise, keep searching in the + * chosen direction until a good one is found. */ + while (!quit && TIME_BEFORE(*rb->current_tick, tick)) + { + int button = mpeg_button_get(HZ*2); + + switch (button) + { + case MPEG_STOP: + case ACTION_STD_CANCEL: + /* Abort the search and exit */ + next_action = VIDEO_STOP; + quit = true; + break; + + case BUTTON_NONE: + if (settings.play_mode != 0) { + if (next_action == VIDEO_STOP) { + /* Default to next file */ + next_action = VIDEO_NEXT; + } + else if (next_action == VIDEO_PREV && + !get_videofile_says) { + /* Was first file already; avoid endlessly + * retrying it */ + next_action = VIDEO_STOP; + } + } + break; + + default: + rb->default_event_handler(button); + } /* switch */ + } /* while */ + } + + /* return value of button_loop says, what's next */ + switch (next_action) + { + case VIDEO_NEXT: + { + get_videofile_says = get_videofile(VIDEO_NEXT, videofile, + sizeof(videofile)); + /* quit after finished the last videofile */ + quit = !get_videofile_says; + + if (manual_skip) + { + rb->system_sound_play(get_videofile_says ? + SOUND_TRACK_SKIP : SOUND_TRACK_NO_MORE); + } + + break; + } + case VIDEO_PREV: + { + get_videofile_says = get_videofile(VIDEO_PREV, videofile, + sizeof(videofile)); + /* if there is no previous file, play the same videofile */ + + if (manual_skip) + { + rb->system_sound_play(get_videofile_says ? + SOUND_TRACK_SKIP : SOUND_TRACK_NO_MORE); + } + + break; + } + case VIDEO_STOP: + { + quit = true; + break; + } + } + } /* while */ + } + +#if defined(HAVE_LCD_MODES) && (HAVE_LCD_MODES & LCD_MODE_YUV) + rb->lcd_set_mode(LCD_MODE_RGB565); +#endif + + stream_exit(); + +#if defined(PLUGIN_USE_IRAM) && !defined(SIMULATOR) + if (!iram_saved_copy) + rb->global_settings->talk_menu = preserved_talk_state; +#endif + + rb->talk_disable(false); + + /* Actually handle delayed processing of system events of interest + * that were captured in other button loops */ + mpeg_sysevent_handle(); + + return status; +} diff --git a/apps/plugins/mpegplayer/mpegplayer.h b/apps/plugins/mpegplayer/mpegplayer.h new file mode 100644 index 0000000000..51fb9a8f8a --- /dev/null +++ b/apps/plugins/mpegplayer/mpegplayer.h @@ -0,0 +1,95 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Main mpegplayer config header. + * + * Copyright (c) 2007 Michael Sevakis + * + * 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. + * + ****************************************************************************/ +#ifndef MPEGPLAYER_H +#define MPEGPLAYER_H + +#ifdef HAVE_SCHEDULER_BOOSTCTRL +#define trigger_cpu_boost rb->trigger_cpu_boost +#define cancel_cpu_boost rb->cancel_cpu_boost +#endif +/* #else function-like empty macros are defined in the headers */ + +/* Should be enough for now */ +#define MPEGPLAYER_MAX_STREAMS 4 + +/* Memory allotments for various subsystems */ +#define MIN_MEMMARGIN (4*1024) + +/** Video thread **/ +#define LIBMPEG2_ALLOC_SIZE (2*1024*1024) + +/** MPEG audio buffer **/ +#define AUDIOBUF_GUARD_SIZE (MPA_MAX_FRAME_SIZE + 2*MAD_BUFFER_GUARD) +#define AUDIOBUF_SIZE (64*1024) +#define AUDIOBUF_ALLOC_SIZE (AUDIOBUF_SIZE+AUDIOBUF_GUARD_SIZE) + +/** PCM buffer **/ +#define CLOCK_RATE 44100 /* Our clock rate in ticks/second (samplerate) */ + +/* Define this as "1" to have a test tone instead of silence clip */ +#define SILENCE_TEST_TONE 0 + +/* NOTE: Sizes make no frame header allowance when considering duration */ +#define PCMOUT_BUFSIZE (CLOCK_RATE/2*4) /* 1/2s */ +#define PCMOUT_GUARD_SIZE (PCMOUT_BUFSIZE) /* guarantee contiguous sizes */ +#define PCMOUT_ALLOC_SIZE (PCMOUT_BUFSIZE + PCMOUT_GUARD_SIZE) + /* Start pcm playback @ 25% full */ +#define PCMOUT_PLAY_WM (PCMOUT_BUFSIZE/4) +#define PCMOUT_LOW_WM (0) + +/** disk buffer **/ +#define DISK_BUF_LOW_WATERMARK (1024*1024) +/* 65535+6 is required since each PES has a 6 byte header with a 16 bit + * packet length field */ +#define DISK_GUARDBUF_SIZE ALIGN_UP(65535+6, 4) + +#ifdef HAVE_LCD_COLOR +#define mylcd_splash rb->splash +#else +#include "lib/grey.h" +#define mylcd_splash grey_splash +#endif + +#include "lib/mylcd.h" + +#include "libmpeg2/mpeg2.h" +#include "video_out.h" +#include "mpeg_stream.h" +#include "mpeg_misc.h" +#include "mpeg_alloc.h" +#include "stream_thread.h" +#include "parser.h" +#include "pcm_output.h" +#include "disk_buf.h" +#include "stream_mgr.h" + +#define LCD_ENABLE_EVENT_0 MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0) +#define LCD_ENABLE_EVENT_1 MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 1) + +#ifdef PLUGIN_USE_IRAM +/* IRAM preserving mechanism to enable talking menus */ +extern void mpegplayer_iram_preserve(void); +extern void mpegplayer_iram_restore(void); +#endif + +#endif /* MPEGPLAYER_H */ diff --git a/apps/plugins/mpegplayer/mpegplayer.make b/apps/plugins/mpegplayer/mpegplayer.make new file mode 100644 index 0000000000..af2156787e --- /dev/null +++ b/apps/plugins/mpegplayer/mpegplayer.make @@ -0,0 +1,32 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# + +MPEGSRCDIR := $(APPSDIR)/plugins/mpegplayer +MPEGBUILDDIR := $(BUILDDIR)/apps/plugins/mpegplayer + +ROCKS += $(MPEGBUILDDIR)/mpegplayer.rock + +MPEG_SRC := $(call preprocess, $(MPEGSRCDIR)/SOURCES) +MPEG_OBJ := $(call c2obj, $(MPEG_SRC)) + +# add source files to OTHER_SRC to get automatic dependencies +OTHER_SRC += $(MPEG_SRC) + +# Set '-fgnu89-inline' if supported (GCCVER >= 4.1.3, GCCNUM > 401) +ifeq ($(shell expr $(GCCNUM) \> 401),1) + MPEGCFLAGS = $(PLUGINFLAGS) -fgnu89-inline +else + MPEGCFLAGS = $(PLUGINFLAGS) +endif + +$(MPEGBUILDDIR)/mpegplayer.rock: $(MPEG_OBJ) $(CODECDIR)/libmad-mpeg.a + +$(MPEGBUILDDIR)/%.o: $(MPEGSRCDIR)/%.c $(MPEGSRCDIR)/mpegplayer.make + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(MPEGCFLAGS) -c $< -o $@ diff --git a/apps/plugins/mpegplayer/parser.h b/apps/plugins/mpegplayer/parser.h new file mode 100644 index 0000000000..ba2181e98b --- /dev/null +++ b/apps/plugins/mpegplayer/parser.h @@ -0,0 +1,103 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * AV parser inteface declarations + * + * Copyright (c) 2007 Michael Sevakis + * + * 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. + * + ****************************************************************************/ +#ifndef PARSER_H +#define PARSER_H + +enum stream_formats +{ + STREAM_FMT_UNKNOWN = -1, + STREAM_FMT_MPEG_TS, /* MPEG transport stream */ + STREAM_FMT_MPEG_PS, /* MPEG program stream */ + STREAM_FMT_MPV, /* MPEG Video only (1 or 2) */ + STREAM_FMT_MPA, /* MPEG Audio only */ +}; + +/* Structure used by a thread that handles a single demuxed data stream and + * receives commands from the stream manager */ +enum stream_parse_states +{ + /* Stream is... */ + SSTATE_SYNC, /* synchronizing by trying to find a start code */ + SSTATE_PARSE, /* parsing the stream looking for packets */ + SSTATE_END, /* at the end of data */ +}; + +enum stream_parse_mode +{ + STREAM_PM_STREAMING = 0, /* Next packet when streaming */ + STREAM_PM_RANDOM_ACCESS, /* Random-access parsing */ +}; + +enum stream_parser_flags +{ + STREAMF_CAN_SEEK = 0x1, /* Seeking possible for this stream */ +}; + +struct stream_parser +{ + /* Common generic parser data */ + enum stream_formats format; /* Stream format */ + uint32_t start_pts; /* The movie start time as represented by + the first audio PTS tag in the + stream converted to half minutes */ + uint32_t end_pts; /* The movie end time as represented by + the maximum audio PTS tag in the + stream converted to half minutes */ + uint32_t duration; /* Duration in PTS units */ + unsigned flags; /* Various attributes set at init */ + struct vo_ext dims; /* Movie dimensions in pixels */ + uint32_t last_seek_time; + int (*next_data)(struct stream *str, enum stream_parse_mode type); + union /* A place for reusable no-cache parameters */ + { + struct str_sync_data sd; + } parms; +}; + +extern struct stream_parser str_parser; + +/* MPEG parsing */ +uint8_t * mpeg_parser_scan_start_code(struct stream_scan *sk, uint32_t code); +unsigned mpeg_parser_scan_pes(struct stream_scan *sk); +uint32_t mpeg_parser_scan_scr(struct stream_scan *sk); +uint32_t mpeg_parser_scan_pts(struct stream_scan *sk, unsigned id); +off_t mpeg_stream_stream_seek_PTS(uint32_t time, int id); + +/* General parsing */ +bool parser_init(void); +void str_initialize(struct stream *str, off_t pos); +bool parser_prepare_image(uint32_t time); +bool parser_get_video_size(struct vo_ext *sz); +int parser_init_stream(void); +void parser_close_stream(void); +static inline bool parser_can_seek(void) + { return str_parser.flags & STREAMF_CAN_SEEK; } +uint32_t parser_seek_time(uint32_t time); +void parser_prepare_streaming(void); +void str_end_of_stream(struct stream *str); + +static inline int parser_get_next_data(struct stream *str, + enum stream_parse_mode type) + { return str_parser.next_data(str, type); } + +#endif /* PARSER_H */ diff --git a/apps/plugins/mpegplayer/pcm_output.c b/apps/plugins/mpegplayer/pcm_output.c new file mode 100644 index 0000000000..5e95d16316 --- /dev/null +++ b/apps/plugins/mpegplayer/pcm_output.c @@ -0,0 +1,396 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * PCM output buffer definitions + * + * Copyright (c) 2007 Michael Sevakis + * + * 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 "plugin.h" +#include "mpegplayer.h" + +/* PCM channel we're using */ +#define MPEG_PCM_CHANNEL PCM_MIXER_CHAN_PLAYBACK + +/* Pointers */ + +/* Start of buffer */ +static struct pcm_frame_header * ALIGNED_ATTR(4) pcm_buffer; +/* End of buffer (not guard) */ +static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_end; +/* Read pointer */ +static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_head IBSS_ATTR; +/* Write pointer */ +static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR; + +/* Bytes */ +static ssize_t pcmbuf_curr_size IBSS_ATTR; /* Size of currently playing frame */ +static ssize_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */ +static ssize_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */ +static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */ + +/* Clock */ +static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */ +static uint32_t volatile clock_tick IBSS_ATTR; /* Our base clock */ +static uint32_t volatile clock_time IBSS_ATTR; /* Timestamp adjusted */ + +static int pcm_skipped = 0; +static int pcm_underruns = 0; + +static unsigned int old_sampr = 0; + +/* Small silence clip. ~5.80ms @ 44.1kHz */ +static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 }; + +/* Delete all buffer contents */ +static void pcm_reset_buffer(void) +{ + pcmbuf_threshold = PCMOUT_PLAY_WM; + pcmbuf_curr_size = pcmbuf_read = pcmbuf_written = 0; + pcmbuf_head = pcmbuf_tail = pcm_buffer; + pcm_skipped = pcm_underruns = 0; +} + +/* Advance a PCM buffer pointer by size bytes circularly */ +static inline void pcm_advance_buffer(struct pcm_frame_header **p, + size_t size) +{ + *p = SKIPBYTES(*p, size); + if (*p >= pcmbuf_end) + *p = SKIPBYTES(*p, -PCMOUT_BUFSIZE); +} + +/* Return physical space used */ +static inline ssize_t pcm_output_bytes_used(void) +{ + return pcmbuf_written - pcmbuf_read; /* wrap-safe */ +} + +/* Return physical space free */ +static inline ssize_t pcm_output_bytes_free(void) +{ + return PCMOUT_BUFSIZE - pcm_output_bytes_used(); +} + +/* Audio DMA handler */ +static void get_more(const void **start, size_t *size) +{ + ssize_t sz; + + /* Free-up the last frame played frame if any */ + pcmbuf_read += pcmbuf_curr_size; + pcmbuf_curr_size = 0; + + sz = pcm_output_bytes_used(); + + if (sz > pcmbuf_threshold) + { + pcmbuf_threshold = PCMOUT_LOW_WM; + + while (1) + { + uint32_t time = pcmbuf_head->time; + int32_t offset = time - clock_time; + + sz = pcmbuf_head->size; + + if (sz < (ssize_t)(PCM_HDR_SIZE + 4) || + (sz & 3) != 0) + { + /* Just show a warning about this - will never happen + * without a corrupted buffer */ + DEBUGF("get_more: invalid size (%ld)\n", (long)sz); + } + + if (offset < -100*CLOCK_RATE/1000) + { + /* Frame more than 100ms late - drop it */ + pcm_advance_buffer(&pcmbuf_head, sz); + pcmbuf_read += sz; + pcm_skipped++; + if (pcm_output_bytes_used() > 0) + continue; + + /* Ran out so revert to default watermark */ + pcmbuf_threshold = PCMOUT_PLAY_WM; + pcm_underruns++; + } + else if (offset < 100*CLOCK_RATE/1000) + { + /* Frame less than 100ms early - play it */ + struct pcm_frame_header *head = pcmbuf_head; + + pcm_advance_buffer(&pcmbuf_head, sz); + pcmbuf_curr_size = sz; + + sz -= PCM_HDR_SIZE; + + /* Audio is time master - keep clock synchronized */ + clock_time = time + (sz >> 2); + + /* Update base clock */ + clock_tick += sz >> 2; + + *start = head->data; + *size = sz; + return; + } + /* Frame will be dropped - play silence clip */ + break; + } + } + else + { + /* Ran out so revert to default watermark */ + if (pcmbuf_threshold == PCMOUT_LOW_WM) + pcm_underruns++; + + pcmbuf_threshold = PCMOUT_PLAY_WM; + } + + /* Keep clock going at all times */ + clock_time += sizeof (silence) / 4; + clock_tick += sizeof (silence) / 4; + + *start = silence; + *size = sizeof (silence); + + if (sz < 0) + pcmbuf_read = pcmbuf_written; +} + +/** Public interface **/ + +/* Return a buffer pointer if at least size bytes are available and if so, + * give the actual free space */ +void * pcm_output_get_buffer(ssize_t *size) +{ + ssize_t sz = *size; + ssize_t free = pcm_output_bytes_free() - PCM_HDR_SIZE; + + if (sz >= 0 && free >= sz) + { + *size = free; /* return actual free space (- header) */ + return pcmbuf_tail->data; + } + + /* Leave *size alone so caller doesn't have to reinit */ + return NULL; +} + +/* Commit the buffer returned by pcm_ouput_get_buffer; timestamp is PCM + * clock time units, not video format time units */ +bool pcm_output_commit_data(ssize_t size, uint32_t timestamp) +{ + if (size <= 0 || (size & 3)) + return false; /* invalid */ + + size += PCM_HDR_SIZE; + + if (size > pcm_output_bytes_free()) + return false; /* too big */ + + pcmbuf_tail->size = size; + pcmbuf_tail->time = timestamp; + + pcm_advance_buffer(&pcmbuf_tail, size); + pcmbuf_written += size; + + return true; +} + +/* Returns 'true' if the buffer is completely empty */ +bool pcm_output_empty(void) +{ + return pcm_output_bytes_used() <= 0; +} + +/* Flushes the buffer - clock keeps counting */ +void pcm_output_flush(void) +{ + rb->pcm_play_lock(); + + enum channel_status status = rb->mixer_channel_status(MPEG_PCM_CHANNEL); + + /* Stop PCM to clear current buffer */ + if (status != CHANNEL_STOPPED) + rb->mixer_channel_stop(MPEG_PCM_CHANNEL); + + rb->pcm_play_unlock(); + + pcm_reset_buffer(); + + /* Restart if playing state was current */ + if (status == CHANNEL_PLAYING) + rb->mixer_channel_play_data(MPEG_PCM_CHANNEL, + get_more, NULL, 0); +} + +/* Seek the reference clock to the specified time - next audio data ready to + go to DMA should be on the buffer with the same time index or else the PCM + buffer should be empty */ +void pcm_output_set_clock(uint32_t time) +{ + rb->pcm_play_lock(); + + clock_start = time; + clock_tick = time; + clock_time = time; + + rb->pcm_play_unlock(); +} + +/* Return the clock as synchronized by audio frame timestamps */ +uint32_t pcm_output_get_clock(void) +{ + uint32_t time, rem; + + /* Reread if data race detected - rem will be 0 if driver hasn't yet + * updated to the new buffer size. Also be sure pcm state doesn't + * cause indefinite loop. + * + * FYI: NOT scrutinized for rd/wr reordering on different cores. */ + do + { + time = clock_time; + rem = rb->mixer_channel_get_bytes_waiting(MPEG_PCM_CHANNEL) >> 2; + } + while (UNLIKELY(time != clock_time || + (rem == 0 && + rb->mixer_channel_status(MPEG_PCM_CHANNEL) == CHANNEL_PLAYING)) + ); + + return time - rem; + +} + +/* Return the raw clock as counted from the last pcm_output_set_clock + * call */ +uint32_t pcm_output_get_ticks(uint32_t *start) +{ + uint32_t tick, rem; + + /* Same procedure as pcm_output_get_clock */ + do + { + tick = clock_tick; + rem = rb->mixer_channel_get_bytes_waiting(MPEG_PCM_CHANNEL) >> 2; + } + while (UNLIKELY(tick != clock_tick || + (rem == 0 && + rb->mixer_channel_status(MPEG_PCM_CHANNEL) == CHANNEL_PLAYING)) + ); + + if (start) + *start = clock_start; + + return tick - rem; +} + +/* Pauses/Starts pcm playback - and the clock */ +void pcm_output_play_pause(bool play) +{ + rb->pcm_play_lock(); + + if (rb->mixer_channel_status(MPEG_PCM_CHANNEL) != CHANNEL_STOPPED) + { + rb->mixer_channel_play_pause(MPEG_PCM_CHANNEL, play); + rb->pcm_play_unlock(); + } + else + { + rb->pcm_play_unlock(); + + if (play) + { + rb->mixer_channel_set_amplitude(MPEG_PCM_CHANNEL, MIX_AMP_UNITY); + rb->mixer_channel_play_data(MPEG_PCM_CHANNEL, + get_more, NULL, 0); + } + } +} + +/* Stops all playback and resets the clock */ +void pcm_output_stop(void) +{ + rb->pcm_play_lock(); + + if (rb->mixer_channel_status(MPEG_PCM_CHANNEL) != CHANNEL_STOPPED) + rb->mixer_channel_stop(MPEG_PCM_CHANNEL); + + rb->pcm_play_unlock(); + + pcm_output_flush(); + pcm_output_set_clock(0); +} + +/* Drains any data if the start threshold hasn't been reached */ +void pcm_output_drain(void) +{ + rb->pcm_play_lock(); + pcmbuf_threshold = PCMOUT_LOW_WM; + rb->pcm_play_unlock(); +} + +bool pcm_output_init(void) +{ + pcm_buffer = mpeg_malloc(PCMOUT_ALLOC_SIZE, MPEG_ALLOC_PCMOUT); + if (pcm_buffer == NULL) + return false; + + pcmbuf_end = SKIPBYTES(pcm_buffer, PCMOUT_BUFSIZE); + + pcm_reset_buffer(); + +#if INPUT_SRC_CAPS != 0 + /* Select playback */ + rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); + rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); +#endif + +#if SILENCE_TEST_TONE + /* Make the silence clip a square wave */ + const int16_t silence_amp = INT16_MAX / 16; + unsigned i; + + for (i = 0; i < ARRAYLEN(silence); i += 2) + { + if (i < ARRAYLEN(silence)/2) + { + silence[i] = silence_amp; + silence[i+1] = silence_amp; + } + else + { + silence[i] = -silence_amp; + silence[i+1] = -silence_amp; + } + } +#endif + + old_sampr = rb->mixer_get_frequency(); + rb->mixer_set_frequency(CLOCK_RATE); + rb->pcmbuf_fade(false, true); + return true; +} + +void pcm_output_exit(void) +{ + rb->pcmbuf_fade(false, false); + if (old_sampr != 0) + rb->mixer_set_frequency(old_sampr); +} diff --git a/apps/plugins/mpegplayer/pcm_output.h b/apps/plugins/mpegplayer/pcm_output.h new file mode 100644 index 0000000000..bae00cd045 --- /dev/null +++ b/apps/plugins/mpegplayer/pcm_output.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * PCM output buffer declarations + * + * Copyright (c) 2007 Michael Sevakis + * + * 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. + * + ****************************************************************************/ +#ifndef PCM_OUTPUT_H +#define PCM_OUTPUT_H + +#define PCM_HDR_SIZE (sizeof (struct pcm_frame_header)) +struct pcm_frame_header /* Header added to pcm data every time a decoded + audio frame is sent out */ +{ + uint32_t size; /* size of this frame - including header */ + uint32_t time; /* timestamp for this frame in audio ticks */ + unsigned char data[]; /* open array of audio data */ +} ALIGNED_ATTR(4); + +bool pcm_output_init(void); +void pcm_output_exit(void); +void pcm_output_flush(void); +void pcm_output_set_clock(uint32_t time); +uint32_t pcm_output_get_clock(void); +uint32_t pcm_output_get_ticks(uint32_t *start); +void pcm_output_play_pause(bool play); +void pcm_output_stop(void); +void pcm_output_drain(void); +void * pcm_output_get_buffer(ssize_t *size); +bool pcm_output_commit_data(ssize_t size, uint32_t timestamp); +bool pcm_output_empty(void); + +#endif /* PCM_OUTPUT_H */ diff --git a/apps/plugins/mpegplayer/stream_mgr.c b/apps/plugins/mpegplayer/stream_mgr.c new file mode 100644 index 0000000000..3cac8c0f57 --- /dev/null +++ b/apps/plugins/mpegplayer/stream_mgr.c @@ -0,0 +1,1163 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * AV stream manager implementation + * + * Copyright (c) 2007 Michael Sevakis + * + * 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 "plugin.h" +#include "mpegplayer.h" +#include "lib/grey.h" +#include "mpeg_settings.h" + +#ifndef HAVE_LCD_COLOR +GREY_INFO_STRUCT_IRAM +#endif + +static struct event_queue stream_mgr_queue SHAREDBSS_ATTR; +static struct queue_sender_list stream_mgr_queue_send SHAREDBSS_ATTR; +static uint32_t stream_mgr_thread_stack[DEFAULT_STACK_SIZE*2/sizeof(uint32_t)]; + +struct stream_mgr stream_mgr SHAREDBSS_ATTR; + +/* Forward decs */ +static int stream_on_close(void); + +struct str_broadcast_data +{ + long cmd; /* Command to send to stream */ + intptr_t data; /* Data to send with command */ +}; + +static inline void stream_mgr_lock(void) +{ + rb->mutex_lock(&stream_mgr.str_mtx); +} + +static inline void stream_mgr_unlock(void) +{ + rb->mutex_unlock(&stream_mgr.str_mtx); +} + +static inline void actl_lock(void) +{ + rb->mutex_lock(&stream_mgr.actl_mtx); +} + +static inline void actl_unlock(void) +{ + rb->mutex_unlock(&stream_mgr.actl_mtx); +} + +static inline void stream_mgr_post_msg(long id, intptr_t data) +{ + rb->queue_post(stream_mgr.q, id, data); +} + +static inline intptr_t stream_mgr_send_msg(long id, intptr_t data) +{ + return rb->queue_send(stream_mgr.q, id, data); +} + +static inline void stream_mgr_reply_msg(intptr_t retval) +{ + rb->queue_reply(stream_mgr.q, retval); +} + +int str_next_data_not_ready(struct stream *str) +{ + /* Save the current window since it actually might be ready by the time + * the registration is received by buffering. */ + off_t win_right = str->hdr.win_right; + + if (str->hdr.win_right < disk_buf.filesize - MIN_BUFAHEAD && + disk_buf.filesize > MIN_BUFAHEAD) + { + /* Set right edge to where probing left off + the minimum margin */ + str->hdr.win_right += MIN_BUFAHEAD; + } + else + { + /* Request would be passed the end of the file */ + str->hdr.win_right = disk_buf.filesize; + } + + switch (disk_buf_send_msg(DISK_BUF_DATA_NOTIFY, (intptr_t)str)) + { + case DISK_BUF_NOTIFY_OK: + /* Was ready - restore window and process */ + str->hdr.win_right = win_right; + return STREAM_OK; + + case DISK_BUF_NOTIFY_ERROR: + /* Error - quit parsing */ + str_end_of_stream(str); + return STREAM_DATA_END; + + default: + /* Not ready - go wait for notification from buffering. */ + str->pkt_flags = 0; + return STREAM_DATA_NOT_READY; + } +} + +void str_data_notify_received(struct stream *str) +{ + /* Normalize win_right back to the packet length */ + if (str->state == SSTATE_END) + return; + + if (str->curr_packet == NULL) + { + /* Nothing was yet parsed since init */ + str->hdr.win_right = str->hdr.win_left; + } + else + { + /* Restore window based upon current packet */ + str->hdr.win_right = str->hdr.win_left + + (str->curr_packet_end - str->curr_packet); + } +} + +/* Set stream manager to a "no-file" state */ +static void stream_mgr_init_state(void) +{ + stream_mgr.filename = NULL; + stream_mgr.resume_time = INVALID_TIMESTAMP; + stream_mgr.seeked = false; +} + +/* Add a stream to the playback pool */ +void stream_add_stream(struct stream *str) +{ + actl_lock(); + + list_remove_item(stream_mgr.strl, str); + list_add_item(stream_mgr.strl, str); + + actl_unlock(); +} + +/* Callback for various list-moving operations */ +static bool strl_enum_callback(struct stream *str, void *data) +{ + actl_lock(); + + list_remove_item(stream_mgr.strl, str); + + if (*(int*)data == 1) + list_add_item(stream_mgr.actl, str); + + actl_unlock(); + + return true; +} + +/* Clear all streams from active and playback pools */ +void stream_remove_streams(void) +{ + int add_item = 0; + list_enum_items(stream_mgr.strl, + (list_enum_callback_t)strl_enum_callback, (void *)&add_item); +} + +/* Move the playback pool to the active list */ +void move_strl_to_actl(void) +{ + int add_item = 1; + list_enum_items(stream_mgr.strl, + (list_enum_callback_t)strl_enum_callback, (void *)&add_item); +} + +/* Remove a stream from the active list and return it to the pool */ +static bool actl_stream_remove(struct stream *str) +{ + bool retval; + + actl_lock(); + + retval = list_remove_item(stream_mgr.actl, str); + + if (retval) + list_add_item(stream_mgr.strl, str); + + actl_unlock(); + + return retval; +} + +/* Broadcast a message to all active streams */ +static bool actl_stream_broadcast_callback(struct stream *str, + struct str_broadcast_data *sbd) +{ + switch (sbd->cmd) + { + case STREAM_PLAY: + case STREAM_PAUSE: + break; + + case STREAM_STOP: + if (sbd->data != 0) + { + actl_lock(); + + list_remove_item(stream_mgr.actl, str); + list_add_item(stream_mgr.strl, str); + + actl_unlock(); + sbd->data = 0; + } + break; + + default: + return false; + } + + str_send_msg(str, sbd->cmd, sbd->data); + return true; +} + +static void actl_stream_broadcast(int cmd, intptr_t data) +{ + struct str_broadcast_data sbd; + sbd.cmd = cmd; + sbd.data = data; + list_enum_items(stream_mgr.actl, + (list_enum_callback_t)actl_stream_broadcast_callback, + (void*)&sbd); +} + +/* Set the current base clock */ +static void set_stream_clock(uint32_t time) +{ + /* Fudge: Start clock 100ms early to allow for some filling time */ + if (time > 100*TS_SECOND/1000) + time -= 100*TS_SECOND/1000; + else + time = 0; + + pcm_output_set_clock(TS_TO_TICKS(time)); +} + +static void stream_start_playback(uint32_t time, bool fill_buffer) +{ + if (stream_mgr.seeked) + { + /* Clear any seeked status */ + stream_mgr.seeked = false; + + /* Flush old PCM data */ + pcm_output_flush(); + + /* Set the master clock */ + set_stream_clock(time); + + /* Make sure streams are back in active pool */ + move_strl_to_actl(); + + /* Prepare the parser and associated streams */ + parser_prepare_streaming(); + } + + /* Start buffer which optional force fill */ + disk_buf_send_msg(STREAM_PLAY, fill_buffer); + + /* Tell each stream to start - may generate end of stream signals + * now - we'll handle this when finished */ + actl_stream_broadcast(STREAM_PLAY, 0); + + /* Actually start the clock */ + pcm_output_play_pause(true); +} + +/* Return the play time relative to the specified play time */ +static uint32_t time_from_whence(uint32_t time, int whence) +{ + int64_t currtime; + uint32_t start; + + switch (whence) + { + case SEEK_SET: + /* Set the current time (time = unsigned offset from 0) */ + if (time > str_parser.duration) + time = str_parser.duration; + break; + case SEEK_CUR: + /* Seek forward or backward from the current time + * (time = signed offset from current) */ + currtime = stream_get_seek_time(&start); + currtime -= start; + currtime += (int32_t)time; + + if (currtime < 0) + currtime = 0; + else if ((uint64_t)currtime > str_parser.duration) + currtime = str_parser.duration; + + time = (uint32_t)currtime; + break; + case SEEK_END: + /* Seek from the end (time = unsigned offset from end) */ + if (time > str_parser.duration) + time = str_parser.duration; + time = str_parser.duration - time; + break; + } + + return time; +} + +/* Handle seeking details if playing or paused */ +static uint32_t stream_seek_intl(uint32_t time, int whence, + int status, bool *was_buffering) +{ + if (status != STREAM_STOPPED) + { + bool wb; + + /* Place streams in a non-running state - keep them on actl if + * still there */ + actl_stream_broadcast(STREAM_PAUSE, 0); + + /* Stop all buffering or else risk clobbering random-access data */ + wb = disk_buf_send_msg(STREAM_STOP, 0); + + if (was_buffering != NULL) + *was_buffering = wb; + } + + time = time_from_whence(time, whence); + + stream_mgr.seeked = true; + + return parser_seek_time(time); +} + +/* Store the resume time at the last seek/current clock point */ +static void stream_remember_resume_time(void) +{ + /* Assume invalidity */ + stream_mgr.resume_time = 0; + + if (stream_can_seek()) + { + /* Read the current stream time or the last seeked position */ + uint32_t start; + uint32_t time = stream_get_seek_time(&start); + + if (time >= str_parser.start_pts && time <= str_parser.end_pts) + { + /* Save the current stream time */ + stream_mgr.resume_time = time - start; + } + } +} + +/* Handle STREAM_OPEN */ +void stream_on_open(const char *filename) +{ + int err = STREAM_ERROR; + + stream_mgr_lock(); + + trigger_cpu_boost(); + + /* Open the video file */ + if (disk_buf_open(filename) >= 0) + { + /* Initialize the parser */ + err = parser_init_stream(); + + if (err >= STREAM_OK) + { + /* File ok - save the opened filename */ + stream_mgr.filename = filename; + } + } + + /* If error - cleanup */ + if (err < STREAM_OK) + stream_on_close(); + + cancel_cpu_boost(); + + stream_mgr_unlock(); + + stream_mgr_reply_msg(err); +} + +/* Handler STREAM_PLAY */ +static void stream_on_play(void) +{ + int status = stream_mgr.status; + + stream_mgr_lock(); + + if (status == STREAM_STOPPED) + { + uint32_t start; + + /* We just say we're playing now */ + stream_mgr.status = STREAM_PLAYING; + + /* Reply with previous state */ + stream_mgr_reply_msg(status); + + trigger_cpu_boost(); + + /* Seek to initial position and set clock to that time */ + + /* Save the resume time */ + stream_remember_resume_time(); + + /* Prepare seek to start point */ + start = stream_seek_intl(stream_mgr.resume_time, SEEK_SET, + STREAM_STOPPED, NULL); + + /* Sync and start - force buffer fill */ + stream_start_playback(start, true); + } + else + { + /* Reply with previous state */ + stream_mgr_reply_msg(status); + } + + stream_mgr_unlock(); +} + +/* Handle STREAM_PAUSE */ +static void stream_on_pause(void) +{ + int status = stream_mgr.status; + + stream_mgr_lock(); + + /* Reply with previous state */ + stream_mgr_reply_msg(status); + + if (status == STREAM_PLAYING) + { + /* Pause the clock */ + pcm_output_play_pause(false); + + /* Pause each active stream */ + actl_stream_broadcast(STREAM_PAUSE, 0); + + /* Pause the disk buffer - buffer may continue filling */ + disk_buf_send_msg(STREAM_PAUSE, false); + + /* Unboost the CPU */ + cancel_cpu_boost(); + + /* Offically paused */ + stream_mgr.status = STREAM_PAUSED; + } + + stream_mgr_unlock(); +} + +/* Handle STREAM_RESUME */ +static void stream_on_resume(void) +{ + int status = stream_mgr.status; + + stream_mgr_lock(); + + /* Reply with previous state */ + stream_mgr_reply_msg(status); + + if (status == STREAM_PAUSED) + { + /* Boost the CPU */ + trigger_cpu_boost(); + + /* Sync and start - no force buffering */ + stream_start_playback(str_parser.last_seek_time, false); + + /* Officially playing */ + stream_mgr.status = STREAM_PLAYING; + } + + stream_mgr_unlock(); +} + +/* Handle STREAM_STOP */ +static void stream_on_stop(bool reply) +{ + int status = stream_mgr.status; + + stream_mgr_lock(); + + if (reply) + stream_mgr_reply_msg(status); + + if (status != STREAM_STOPPED) + { + /* Pause the clock */ + pcm_output_play_pause(false); + + /* Update the resume time info */ + stream_remember_resume_time(); + + /* Not stopped = paused or playing */ + stream_mgr.seeked = false; + + /* Stop buffering */ + disk_buf_send_msg(STREAM_STOP, 0); + + /* Clear any still-active streams and remove from actl */ + actl_stream_broadcast(STREAM_STOP, 1); + + /* Stop PCM output (and clock) */ + pcm_output_stop(); + + /* Cancel our processor boost */ + cancel_cpu_boost(); + + stream_mgr.status = STREAM_STOPPED; + } + + stream_mgr_unlock(); +} + +/* Handle STREAM_SEEK */ +static void stream_on_seek(struct stream_seek_data *skd) +{ + uint32_t time = skd->time; + int whence = skd->whence; + + switch (whence) + { + case SEEK_SET: + case SEEK_CUR: + case SEEK_END: + if (stream_mgr.filename == NULL) + break; + + /* Keep things spinning if already doing so */ + stream_keep_disk_active(); + + /* Have data - reply in order to acquire lock */ + stream_mgr_reply_msg(STREAM_OK); + + stream_mgr_lock(); + + /* Either seeking must be possible or a full rewind must be done */ + if (stream_can_seek() || time_from_whence(time, whence) == 0) + { + bool buffer = false; + + if (stream_mgr.status == STREAM_PLAYING) + { + /* Keep clock from advancing while seeking */ + pcm_output_play_pause(false); + } + + time = stream_seek_intl(time, whence, stream_mgr.status, &buffer); + stream_remember_resume_time(); + + if (stream_mgr.status == STREAM_PLAYING) + { + /* Sync and restart - no force buffering */ + stream_start_playback(time, buffer); + } + } + + stream_mgr_unlock(); + return; + } + + /* Invalid parameter or no file */ + stream_mgr_reply_msg(STREAM_ERROR); +} + +/* Handle STREAM_CLOSE */ +static int stream_on_close(void) +{ + int status = STREAM_STOPPED; + + stream_mgr_lock(); + + /* Any open file that was accepted for playback? */ + if (stream_mgr.filename != NULL) + { + /* Yes - hide video */ + stream_show_vo(false); + /* Stop any playback */ + status = stream_mgr.status; + stream_on_stop(false); + /* Tell parser file is finished */ + parser_close_stream(); + /* Reinitialize manager */ + stream_mgr_init_state(); + } + + /* Let disk buffer reset itself - file might be open even if no good */ + disk_buf_close(); + + stream_mgr_unlock(); + + return status; +} + +/* Handle STREAM_EV_COMPLETE */ +static void stream_on_ev_complete(struct stream *str) +{ + stream_mgr_lock(); + + /* Stream is active? */ + if (actl_stream_remove(str)) + { + /* No - remove this stream from the active list */ + DEBUGF(" finished: 0x%02x\n", str->id); + if (list_is_empty(stream_mgr.actl)) + { + /* All streams have acked - stop playback */ + stream_on_stop(false); + stream_mgr.resume_time = 0; /* Played to end - no resume */ + } + else + { + /* Stream is done - stop it and place back in pool */ + str_send_msg(str, STREAM_STOP, 1); + } + } + + stream_mgr_unlock(); +} + +/* Callback for stream to notify about events internal to them */ +void stream_generate_event(struct stream *str, long id, intptr_t data) +{ + if (str == NULL) + return; + + switch (id) + { + case STREAM_EV_COMPLETE: + /* The last stream has ended */ + stream_mgr_post_msg(STREAM_EV_COMPLETE, (intptr_t)str); + break; + } + + (void)data; +} + +/* Clear any particular notification for which a stream registered */ +void stream_clear_notify(struct stream *str, int for_msg) +{ + switch (for_msg) + { + case DISK_BUF_DATA_NOTIFY: + disk_buf_send_msg(DISK_BUF_CLEAR_DATA_NOTIFY, (intptr_t)str); + break; + } +} + +/* Special handling for certain messages since they involve multiple + * operations behind the scenes */ +static intptr_t send_video_msg(long id, intptr_t data) +{ + intptr_t retval = 0; + + if (video_str.thread != 0 && disk_buf.in_file >= 0) + { + + switch (id) + { + case VIDEO_DISPLAY_SHOW: + if (data != 0 && disk_buf_status() == STREAM_STOPPED) + { /* Only prepare image if showing and not playing */ + parser_prepare_image(str_parser.last_seek_time); + } + break; + + case VIDEO_PRINT_FRAME: + if (data) + break; + case VIDEO_PRINT_THUMBNAIL: + if (disk_buf_status() != STREAM_STOPPED) + break; /* Prepare image if not playing */ + + /* Ignore return and try video thread anyway */ + parser_prepare_image(str_parser.last_seek_time); + + /* Image ready - pass message to video thread */ + break; + } + + retval = str_send_msg(&video_str, id, data); + } + + return retval; +} + +/* Show/hide the video output */ +bool stream_show_vo(bool show) +{ + bool vis; + stream_mgr_lock(); + + vis = send_video_msg(VIDEO_DISPLAY_SHOW, show); +#ifndef HAVE_LCD_COLOR + grey_show(show); +#endif + stream_mgr_unlock(); + + return vis; +} + +/* Query the visibility of video output */ +bool stream_vo_is_visible(void) +{ + bool vis; + stream_mgr_lock(); + vis = send_video_msg(VIDEO_DISPLAY_IS_VISIBLE, 0); + stream_mgr_unlock(); + return vis; +} + +/* Return the video dimensions */ +bool stream_vo_get_size(struct vo_ext *sz) +{ + bool retval = false; + + stream_mgr_lock(); + + if (str_parser.dims.w > 0 && str_parser.dims.h > 0) + { + *sz = str_parser.dims; + retval = true; + } + + stream_mgr_unlock(); + + return retval; +} + +void stream_vo_set_clip(const struct vo_rect *rc) +{ + stream_mgr_lock(); + + if (rc) + { + stream_mgr.parms.rc = *rc; + rc = &stream_mgr.parms.rc; + } + + send_video_msg(VIDEO_SET_CLIP_RECT, (intptr_t)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) +{ + stream_mgr_lock(); + + grey_show(show); + + stream_mgr_unlock(); +} + +#endif /* !HAVE_LCD_COLOR */ + +/* Display a thumbnail at the last seek point */ +bool stream_display_thumb(const struct vo_rect *rc) +{ + bool retval; + + if (rc == NULL) + return false; + + stream_mgr_lock(); + + stream_mgr.parms.rc = *rc; + retval = send_video_msg(VIDEO_PRINT_THUMBNAIL, + (intptr_t)&stream_mgr.parms.rc); + + stream_mgr_unlock(); + + return retval; +} + +bool stream_draw_frame(bool no_prepare) +{ + bool retval; + stream_mgr_lock(); + + retval = send_video_msg(VIDEO_PRINT_FRAME, no_prepare); + + stream_mgr_unlock(); + + 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) +{ + uint32_t resume_time; + + /* A stop request is async and replies before setting this - must lock */ + stream_mgr_lock(); + + resume_time = stream_mgr.resume_time; + + stream_mgr_unlock(); + + return resume_time; +} + +uint32_t stream_get_seek_time(uint32_t *start) +{ + uint32_t time; + + stream_mgr_lock(); + + if (stream_mgr.seeked) + { + time = str_parser.last_seek_time; + } + else + { + time = TICKS_TO_TS(pcm_output_get_clock()); + + /* Clock can be start early so keep in range */ + if (time < str_parser.start_pts) + time = str_parser.start_pts; + } + + if (start != NULL) + *start = str_parser.start_pts; + + stream_mgr_unlock(); + + return time; +} + +/* Wait for a state transistion to complete */ +void stream_wait_status(void) +{ + stream_mgr_lock(); + stream_mgr_unlock(); +} + +/* Returns the smallest file window that includes all active streams' + * windows */ +static bool stream_get_window_callback(struct stream *str, + struct stream_window *sw) +{ + off_t swl = str->hdr.win_left; + off_t swr = str->hdr.win_right; + + if (swl < sw->left) + sw->left = swl; + + if (swr > sw->right) + sw->right = swr; + + return true; +} + +bool stream_get_window(struct stream_window *sw) +{ + if (sw == NULL) + return false; + + sw->left = LONG_MAX; + sw->right = LONG_MIN; + + actl_lock(); + list_enum_items(stream_mgr.actl, + (list_enum_callback_t)stream_get_window_callback, + (void*)sw); + actl_unlock(); + + return sw->left <= sw->right; +} + +/* Playback control thread */ +static void stream_mgr_thread(void) +{ + struct queue_event ev; + + while (1) + { + rb->queue_wait(stream_mgr.q, &ev); + + switch (ev.id) + { + case STREAM_OPEN: + stream_on_open((const char *)ev.data); + break; + + case STREAM_CLOSE: + stream_on_close(); + break; + + case STREAM_PLAY: + stream_on_play(); + break; + + case STREAM_PAUSE: + if (ev.data) + stream_on_resume(); + else + stream_on_pause(); + break; + + case STREAM_STOP: + stream_on_stop(true); + break; + + case STREAM_SEEK: + stream_on_seek((struct stream_seek_data *)ev.data); + break; + + case STREAM_EV_COMPLETE: + stream_on_ev_complete((struct stream *)ev.data); + break; + + case STREAM_QUIT: + if (stream_mgr.status != STREAM_STOPPED) + stream_on_stop(false); + return; + } + } +} + +/* Stream command interface APIs */ + +/* Opens a new file */ +int stream_open(const char *filename) +{ + if (stream_mgr.thread != 0) + return stream_mgr_send_msg(STREAM_OPEN, (intptr_t)filename); + return STREAM_ERROR; +} + +/* Plays the current file starting at time 'start' */ +int stream_play(void) +{ + if (stream_mgr.thread != 0) + return stream_mgr_send_msg(STREAM_PLAY, 0); + return STREAM_ERROR; +} + +/* Pauses playback if playing */ +int stream_pause(void) +{ + if (stream_mgr.thread != 0) + return stream_mgr_send_msg(STREAM_PAUSE, false); + return STREAM_ERROR; +} + +/* Resumes playback if paused */ +int stream_resume(void) +{ + if (stream_mgr.thread != 0) + return stream_mgr_send_msg(STREAM_PAUSE, true); + return STREAM_ERROR; +} + +/* Stops playback if not stopped */ +int stream_stop(void) +{ + if (stream_mgr.thread != 0) + return stream_mgr_send_msg(STREAM_STOP, 0); + return STREAM_ERROR; +} + +/* Seeks playback time to/by the specified time */ +int stream_seek(uint32_t time, int whence) +{ + int ret; + + if (stream_mgr.thread == 0) + return STREAM_ERROR; + + stream_mgr_lock(); + + stream_mgr.parms.skd.time = time; + stream_mgr.parms.skd.whence = whence; + + ret = stream_mgr_send_msg(STREAM_SEEK, (intptr_t)&stream_mgr.parms.skd); + + stream_mgr_unlock(); + + return ret; +} + +/* Closes the current file */ +int stream_close(void) +{ + if (stream_mgr.thread != 0) + return stream_mgr_send_msg(STREAM_CLOSE, 0); + return STREAM_ERROR; +} + +/* Initializes the playback engine */ +int stream_init(void) +{ + void *mem; + size_t memsize; + + stream_mgr.status = STREAM_STOPPED; + stream_mgr_init_state(); + + /* Initialize our window to the outside world first */ + rb->mutex_init(&stream_mgr.str_mtx); + rb->mutex_init(&stream_mgr.actl_mtx); + + stream_mgr.q = &stream_mgr_queue; + rb->queue_init(stream_mgr.q, false); + + /* sets audiosize and returns buffer pointer */ + mem = rb->plugin_get_audio_buffer(&memsize); + + /* Initialize non-allocator blocks first */ +#ifndef HAVE_LCD_COLOR + long greysize; + + /* Greylib init handles all necessary cache alignment */ + if (!grey_init(mem, memsize, GREY_BUFFERED|GREY_ON_COP, + LCD_WIDTH, LCD_HEIGHT, &greysize)) + { + rb->splash(HZ, "greylib init failed!"); + return STREAM_ERROR; + } + + mem += greysize; + memsize -= greysize; + + grey_clear_display(); +#endif /* !HAVE_LCD_COLOR */ + + stream_mgr.thread = rb->create_thread(stream_mgr_thread, + stream_mgr_thread_stack, sizeof(stream_mgr_thread_stack), + 0, "mpgstream_mgr" IF_PRIO(, PRIORITY_SYSTEM) IF_COP(, CPU)); + + rb->queue_enable_queue_send(stream_mgr.q, &stream_mgr_queue_send, + stream_mgr.thread); + + if (stream_mgr.thread == 0) + { + rb->splash(HZ, "Could not create stream manager thread!"); + return STREAM_ERROR; + } + + /* Wait for thread to initialize */ + stream_mgr_send_msg(STREAM_NULL, 0); + + /* Initialise our malloc buffer */ + if (!mpeg_alloc_init(mem, memsize)) + { + rb->splash(HZ, "Out of memory in stream_init"); + } + /* These inits use the allocator */ + else if (!pcm_output_init()) + { + rb->splash(HZ, "Could not initialize PCM!"); + } + else if (!audio_thread_init()) + { + rb->splash(HZ, "Cannot create audio thread!"); + } + else if (!video_thread_init()) + { + rb->splash(HZ, "Cannot create video thread!"); + } + /* Disk buffer takes max allotment of what's left so it must be last */ + else if (!disk_buf_init()) + { + rb->splash(HZ, "Cannot create buffering thread!"); + } + else if (!parser_init()) + { + rb->splash(HZ, "Parser init failed!"); + } + else + { + return STREAM_OK; + } + + return STREAM_ERROR; +} + +/* Cleans everything up */ +void stream_exit(void) +{ + stream_close(); + + /* Stop the threads and wait for them to terminate */ + video_thread_exit(); + audio_thread_exit(); + disk_buf_exit(); + pcm_output_exit(); + + if (stream_mgr.thread != 0) + { + stream_mgr_post_msg(STREAM_QUIT, 0); + rb->thread_wait(stream_mgr.thread); + stream_mgr.thread = 0; + } + +#ifndef HAVE_LCD_COLOR + grey_release(); +#endif +} diff --git a/apps/plugins/mpegplayer/stream_mgr.h b/apps/plugins/mpegplayer/stream_mgr.h new file mode 100644 index 0000000000..7dba9acc09 --- /dev/null +++ b/apps/plugins/mpegplayer/stream_mgr.h @@ -0,0 +1,168 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * AV stream manager decalarations + * + * Copyright (c) 2007 Michael Sevakis + * + * 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. + * + ****************************************************************************/ +#ifndef STREAM_MGR_H +#define STREAM_MGR_H + +/* Basic media control interface - this handles state changes and stream + * coordination with assistance from the parser */ +struct stream_mgr +{ + unsigned int thread; /* Playback control thread */ + struct event_queue *q; /* event queue for control thread */ + const char *filename; /* Current filename */ + uint32_t resume_time; /* The stream tick where playback was + stopped (or started) */ + bool seeked; /* A seek happened and things must be + resynced */ + int status; /* Current playback status */ + void *strl[MPEGPLAYER_MAX_STREAMS+1]; /* List of available streams */ + void *actl[MPEGPLAYER_MAX_STREAMS+1]; /* List of active streams */ + struct mutex str_mtx; /* Main stream manager mutex */ + struct mutex actl_mtx; /* Lock for current-streams list */ + union /* A place for reusable non-cacheable parameters */ + { + struct vo_rect rc; + struct stream_seek_data skd; + } parms; +}; + +extern struct stream_mgr stream_mgr SHAREDBSS_ATTR; + +struct stream_window +{ + off_t left, right; +}; + +/** Interface for use by streams and other internal objects **/ +bool stream_get_window(struct stream_window *sw); +void stream_clear_notify(struct stream *str, int for_msg); +int str_next_data_not_ready(struct stream *str); +/* Called by a stream to say it got its buffering notification */ +void str_data_notify_received(struct stream *str); +void stream_add_stream(struct stream *str); +void stream_remove_streams(void); + +enum stream_events +{ + __STREAM_EV_FIRST = STREAM_MESSAGE_LAST-1, + STREAM_EV_COMPLETE, +}; + +void stream_generate_event(struct stream *str, long id, intptr_t data); + +/** Main control functions **/ + +/* Initialize the playback engine */ +int stream_init(void); + +/* Close the playback engine */ +void stream_exit(void); + +/* Open a new file */ +int stream_open(const char *filename); + +/* Close the current file */ +int stream_close(void); + +/* Plays from the current seekpoint if stopped */ +int stream_play(void); + +/* Pauses playback if playing */ +int stream_pause(void); + +/* Resumes playback if paused */ +int stream_resume(void); + +/* Stops all streaming activity if playing or paused */ +int stream_stop(void); + +/* Point stream at a particular time. + * whence = one of SEEK_SET, SEEK_CUR, SEEK_END */ +int stream_seek(uint32_t time, int whence); + +/* Show/Hide the video image at the current seekpoint */ +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 + +/* Display thumbnail of the current seekpoint */ +bool stream_display_thumb(const struct vo_rect *rc); + +/* Draw the frame at the current position */ +bool stream_draw_frame(bool no_prepare); + +/* Return video dimensions */ +bool stream_vo_get_size(struct vo_ext *sz); + +/* Returns the resume time in timestamp ticks */ +uint32_t stream_get_resume_time(void); + +/* Returns stream_get_time if no seek is pending or else the + last time give to seek */ +uint32_t stream_get_seek_time(uint32_t *start); + +/* Return the absolute stream time in clock ticks - adjusted by + * master clock stream via audio timestamps */ +static inline uint32_t stream_get_time(void) + { return pcm_output_get_clock(); } + +/* Return the absolute clock time in clock ticks - unadjusted */ +static inline uint32_t stream_get_ticks(uint32_t *start) + { return pcm_output_get_ticks(start); } + +/* Returns the current playback status */ +static inline int stream_status(void) + { return stream_mgr.status; } + +/* Wait for a state transistion to complete */ +void stream_wait_status(void); + +/* Returns the playback length of the stream */ +static inline uint32_t stream_get_duration(void) + { return str_parser.duration; } + +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) +{ +#ifdef HAVE_DISK_STORAGE + rb->storage_spin(); +#endif + } + +#endif /* STREAM_MGR_H */ diff --git a/apps/plugins/mpegplayer/stream_thread.h b/apps/plugins/mpegplayer/stream_thread.h new file mode 100644 index 0000000000..dfa6e8c9a1 --- /dev/null +++ b/apps/plugins/mpegplayer/stream_thread.h @@ -0,0 +1,201 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Declarations for stream-specific threading + * + * Copyright (c) 2007 Michael Sevakis + * + * 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. + * + ****************************************************************************/ +#ifndef STREAM_THREAD_H +#define STREAM_THREAD_H + +#define PKT_HAS_TS 0x1 + +/* Stream header which is the minimum to receive asynchronous buffering + * notifications. + * Layed-out to allow streaming access after random-access parsing */ +struct stream_hdr +{ + struct event_queue *q; /* Communication queue - separate to allow it + to be placed in another section */ + off_t win_left; /* Left position within data stream */ + union + { + off_t win_right; /* Right position within data stream */ + off_t pos; /* Start/current position for random-access read */ + }; + off_t limit; /* Limit for random-access read */ +}; + +struct stream +{ + struct stream_hdr hdr; /* Base stream data */ + unsigned int thread; /* Stream's thread */ + uint8_t* curr_packet; /* Current stream packet beginning */ + uint8_t* curr_packet_end; /* Current stream packet end */ + int state; /* State machine parsing mode */ + uint32_t start_pts; /* First timestamp for stream */ + uint32_t end_pts; /* Last timestamp for stream */ + uint32_t pts; /* Last presentation timestamp */ + uint32_t pkt_flags; /* PKT_* flags */ + unsigned id; /* Stream identifier */ +}; + +#define STR_FROM_HDR(sh) ((struct stream *)(sh)) + +/* Make sure there there is always enough data buffered ahead for + * the worst possible case - regardless of whether a valid stream + * would actually produce that */ +#define MIN_BUFAHEAD (21+65535+6+65535+6) /* 131103 */ + +/* States that a stream's thread assumes internally */ +enum thread_states +{ + /* Stream thread... */ + TSTATE_INIT = 0, /* is initialized and primed */ + TSTATE_DATA, /* is awaiting data to be available */ + TSTATE_BUFFERING, /* is buffering data */ + TSTATE_EOS, /* has hit the end of data */ + TSTATE_DECODE, /* is in a decoding state */ + TSTATE_RENDER, /* is in a rendering state */ + TSTATE_RENDER_WAIT, /* is waiting to render */ +}; + +/* Commands that streams respond to */ +enum stream_message +{ + STREAM_NULL = 0, /* A NULL message for whatever reason - + usually ignored */ + STREAM_PLAY, /* Start playback at current position */ + STREAM_PAUSE, /* Stop playing and await further commands */ + STREAM_RESET, /* Reset the stream for a discontinuity */ + STREAM_STOP, /* Stop stream - requires a reset later */ + STREAM_SEEK, /* Seek the current stream to a new location */ + STREAM_OPEN, /* Open a new file */ + STREAM_CLOSE, /* Close the current file */ + STREAM_QUIT, /* Exit the stream and thread */ + STREAM_NEEDS_SYNC, /* Need to sync before stream decoding? */ + STREAM_SYNC, /* Sync to the specified time from some key point */ + STREAM_FIND_END_TIME, /* Get the exact end time of an elementary + * stream - ie. time just after last frame is finished */ + /* Disk buffer */ + STREAM_DISK_BUF_FIRST, + DISK_BUF_DATA_NOTIFY = STREAM_DISK_BUF_FIRST, + DISK_BUF_CLEAR_DATA_NOTIFY, /* Cancel pending data notification */ + DISK_BUF_CACHE_RANGE, /* Cache a range of the file in the buffer */ + /* Audio stream */ + STREAM_AUDIO_FIRST, + /* Video stream */ + STREAM_VIDEO_FIRST, + VIDEO_DISPLAY_SHOW = STREAM_VIDEO_FIRST, /* Show/hide video output */ + VIDEO_DISPLAY_IS_VISIBLE, /* Is the video output visible? */ + VIDEO_GET_SIZE, /* Get the video dimensions */ + 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, +}; + +/* Data parameter for STREAM_SEEK */ +struct stream_seek_data +{ + uint32_t time; /* Time to seek to/by */ + int whence; /* Specification of relationship to current position/file */ +}; + +/* Data parameter for STREAM_SYNC */ +struct str_sync_data +{ + uint32_t time; /* Time to sync to */ + struct stream_scan sk; /* Specification of start/limits/direction */ +}; + +/* Stream status codes - not eqivalent to thread states */ +enum stream_status +{ + /* Stream status is... */ + STREAM_DATA_END = -4, /* Stream has ended */ + STREAM_DATA_NOT_READY = -3, /* Data was not available yet */ + STREAM_UNSUPPORTED = -2, /* Format is unsupported */ + STREAM_ERROR = -1, /* some kind of error - quit it or reset it */ + STREAM_OK = 0, /* General inequality for success >= is OK, < error */ + STREAM_STOPPED = 0, /* stopped and awaiting commands - send STREAM_INIT */ + STREAM_PLAYING, /* playing and rendering its data */ + STREAM_PAUSED, /* paused and awaiting commands */ + /* Other status codes (> STREAM_OK) */ + STREAM_MATCH, /* A good match was found */ + STREAM_PERFECT_MATCH, /* Exactly what was wanted was found or + no better match is possible */ + STREAM_NOT_FOUND, /* Match not found */ +}; + +/* Clip time to range for a particular stream */ +static inline uint32_t clip_time(struct stream *str, uint32_t time) +{ + if (time < str->start_pts) + time = str->start_pts; + else if (time >= str->end_pts) + time = str->end_pts; + + return time; +} + +extern struct stream video_str IBSS_ATTR; +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 */ +static inline bool str_have_msg(struct stream *str) + { return !rb->queue_empty(str->hdr.q); } + +static inline void str_get_msg(struct stream *str, struct queue_event *ev) + { rb->queue_wait(str->hdr.q, ev); } + +static inline void str_get_msg_w_tmo(struct stream *str, struct queue_event *ev, + int timeout) + { rb->queue_wait_w_tmo(str->hdr.q, ev, timeout); } + +static inline void str_reply_msg(struct stream *str, intptr_t reply) + { rb->queue_reply(str->hdr.q, reply); } + +/* Public use */ +static inline intptr_t str_send_msg(struct stream *str, long id, intptr_t data) + { return rb->queue_send(str->hdr.q, id, data); } + +static inline void str_post_msg(struct stream *str, long id, intptr_t data) + { rb->queue_post(str->hdr.q, id, data); } + +#endif /* STREAM_THREAD_H */ diff --git a/apps/plugins/mpegplayer/video_out.h b/apps/plugins/mpegplayer/video_out.h new file mode 100644 index 0000000000..2a3364c382 --- /dev/null +++ b/apps/plugins/mpegplayer/video_out.h @@ -0,0 +1,102 @@ +/* + * video_out.h + * Copyright (C) 2000-2003 Michel Lespinasse + * Copyright (C) 1999-2000 Aaron Holtzman + * + * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. + * See http://libmpeg2.sourceforge.net/ for updates. + * + * mpeg2dec 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. + * + * mpeg2dec is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + * libmpeg2 sync history: + * 2008-07-01 - CVS revision 1.22 + */ + +#ifndef VIDEO_OUT_H +#define VIDEO_OUT_H + +#if LCD_WIDTH >= LCD_HEIGHT +#define SCREEN_WIDTH LCD_WIDTH +#define SCREEN_HEIGHT LCD_HEIGHT +#define LCD_LANDSCAPE +#else /* Assume the screen is rotated on portrait LCDs */ +#define SCREEN_WIDTH LCD_HEIGHT +#define SCREEN_HEIGHT LCD_WIDTH +#define LCD_PORTRAIT +#endif + +/* Structure to hold width and height values */ +struct vo_ext +{ + int w, h; +}; + +/* Structure that defines a rectangle by its edges */ +struct vo_rect +{ + int l, t, r, b; +}; + +void vo_draw_frame (uint8_t * const * buf); +bool vo_draw_frame_thumb (uint8_t * const * buf, + const struct vo_rect *rc); +bool vo_init (void); +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); +void vo_unlock(void); +#else +static inline void vo_lock(void) {} +static inline void vo_unlock(void) {} +#endif + +/* Sets all coordinates of a vo_rect to 0 */ +void vo_rect_clear(struct vo_rect *rc); +/* Returns true if left >= right or top >= bottom */ +bool vo_rect_empty(const struct vo_rect *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); +/* Query if two rectangles intersect + * If either are empty returns false */ +bool vo_rects_intersect(const struct vo_rect *rc1, + const struct vo_rect *rc2); + +/* Intersect two rectangles + * Resulting rectangle is placed in rc_dst. + * rc_dst is set to empty if they don't intersect. + * Empty source rectangles do not intersect any rectangle. + * rc_dst may be the same structure as rc1 or rc2. + * Returns true if the resulting rectangle is not empty. */ +bool vo_rect_intersect(struct vo_rect *rc_dst, + const struct vo_rect *rc1, + const struct vo_rect *rc2); + +bool vo_rect_union(struct vo_rect *rc_dst, + const struct vo_rect *rc1, + const struct vo_rect *rc2); + +void vo_rect_offset(struct vo_rect *rc, int dx, int dy); + +#endif /* VIDEO_OUT_H */ diff --git a/apps/plugins/mpegplayer/video_out_rockbox.c b/apps/plugins/mpegplayer/video_out_rockbox.c new file mode 100644 index 0000000000..331383843b --- /dev/null +++ b/apps/plugins/mpegplayer/video_out_rockbox.c @@ -0,0 +1,576 @@ +/*************************************************************************** + * __________ __ ___. + * 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 diff --git a/apps/plugins/mpegplayer/video_thread.c b/apps/plugins/mpegplayer/video_thread.c new file mode 100644 index 0000000000..392cc6179f --- /dev/null +++ b/apps/plugins/mpegplayer/video_thread.c @@ -0,0 +1,1059 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * mpegplayer video thread implementation + * + * Copyright (c) 2007 Michael Sevakis + * + * 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 "plugin.h" +#include "mpegplayer.h" +#include "libmpeg2/mpeg2dec_config.h" +#include "lib/grey.h" +#include "video_out.h" +#include "mpeg_settings.h" + +/** Video stream and thread **/ + +/* Video thread data passed around to its various functions */ +struct video_thread_data +{ + /* Stream data */ + mpeg2dec_t *mpeg2dec; /* Our video decoder */ + const mpeg2_info_t *info; /* Info about video stream */ + int state; /* Thread state */ + int status; /* Media status */ + struct queue_event ev;/* Our event queue to receive commands */ + /* Operational info */ + uint32_t stream_time; /* Current time from beginning of stream */ + uint32_t goal_time; /* Scheduled time of current frame */ + int32_t remain_time; /* T-minus value to frame_time (-:early, +:late) */ + int skip_ref_pics; /* Severe skipping - wait for I-frame */ + int skip_level; /* Number of frames still to skip */ + int num_picture; /* Number of picture headers read */ + int num_intra; /* Number of I-picture headers read */ + int group_est; /* Estmated number remaining as of last I */ + long last_render; /* Last time a frame was drawn */ + /* Sync info */ + uint32_t frame_time; /* Current due time of frame (unadjusted) */ + uint32_t frame_period; /* Frame period in clock ticks */ + int num_ref_pics; /* Number of I and P frames since sync/skip */ + 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) +static uint32_t video_stack[VIDEO_STACKSIZE / sizeof(uint32_t)] IBSS_ATTR; +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; + +#define DEFAULT_GOP_SIZE INT_MAX /* no I/P skips until it learns */ +#define DROP_THRESHOLD (100*TS_SECOND/1000) +#define MAX_EARLINESS (120*TS_SECOND/1000) + +#if defined(DEBUG) || defined(SIMULATOR) +static unsigned char pic_coding_type_char(unsigned type) +{ + switch (type) + { + case PIC_FLAG_CODING_TYPE_I: + return 'I'; /* Intra-coded */ + case PIC_FLAG_CODING_TYPE_P: + return 'P'; /* Forward-predicted */ + case PIC_FLAG_CODING_TYPE_B: + return 'B'; /* Bidirectionally-predicted */ + case PIC_FLAG_CODING_TYPE_D: + return 'D'; /* DC-coded */ + default: + return '?'; /* Say what? */ + } +} +#endif /* defined(DEBUG) || defined(SIMULATOR) */ + +/* Multi-use: + * 1) Find the sequence header and initialize video out + * 2) Find the end of the final frame + */ +static int video_str_scan(struct video_thread_data *td, + struct str_sync_data *sd) +{ + int retval = STREAM_ERROR; + uint32_t time = INVALID_TIMESTAMP; + uint32_t period = 0; + struct stream tmp_str; + + tmp_str.id = video_str.id; + tmp_str.hdr.pos = sd->sk.pos; + tmp_str.hdr.limit = sd->sk.pos + sd->sk.len; + + /* Fully reset if obtaining size for a new stream */ + mpeg2_reset(td->mpeg2dec, td->ev.id == VIDEO_GET_SIZE); + mpeg2_skip(td->mpeg2dec, 1); + + while (1) + { + mpeg2_state_t mp2state = mpeg2_parse(td->mpeg2dec); + rb->yield(); + + switch (mp2state) + { + case STATE_BUFFER: + switch (parser_get_next_data(&tmp_str, STREAM_PM_RANDOM_ACCESS)) + { + case STREAM_DATA_END: + DEBUGF("video_stream_scan:STREAM_DATA_END\n"); + goto scan_finished; + + case STREAM_OK: + if (tmp_str.pkt_flags & PKT_HAS_TS) + mpeg2_tag_picture(td->mpeg2dec, tmp_str.pts, 0); + + mpeg2_buffer(td->mpeg2dec, tmp_str.curr_packet, + tmp_str.curr_packet_end); + td->info = mpeg2_info(td->mpeg2dec); + break; + } + break; + + case STATE_SEQUENCE: + DEBUGF("video_stream_scan:STATE_SEQUENCE\n"); + vo_setup(td->info->sequence); + + if (td->ev.id == VIDEO_GET_SIZE) + { + retval = STREAM_OK; + goto scan_finished; + } + break; + + case STATE_SLICE: + case STATE_END: + case STATE_INVALID_END: + { + if (td->info->display_picture == NULL) + break; + + switch (td->ev.id) + { + case STREAM_SYNC: + retval = STREAM_OK; + goto scan_finished; + + case STREAM_FIND_END_TIME: + if (td->info->display_picture->flags & PIC_FLAG_TAGS) + time = td->info->display_picture->tag; + else if (time != INVALID_TIMESTAMP) + time += period; + + period = TC_TO_TS(td->info->sequence->frame_period); + break; + } + + break; + } + + default: + break; + } + } + +scan_finished: + + if (td->ev.id == STREAM_FIND_END_TIME) + { + if (time != INVALID_TIMESTAMP) + { + sd->time = time + period; + retval = STREAM_PERFECT_MATCH; + } + else + { + retval = STREAM_NOT_FOUND; + } + } + + mpeg2_skip(td->mpeg2dec, 0); + return retval; +} + +static bool init_sequence(struct video_thread_data *td) +{ + struct str_sync_data sd; + + sd.time = 0; /* Ignored */ + sd.sk.pos = 0; + sd.sk.len = 1024*1024; + sd.sk.dir = SSCAN_FORWARD; + + return video_str_scan(td, &sd) == STREAM_OK; +} + +static bool check_needs_sync(struct video_thread_data *td, uint32_t time) +{ + uint32_t end_time; + + DEBUGF("check_needs_sync:\n"); + if (td->info == NULL || td->info->display_fbuf == NULL) + { + DEBUGF(" no fbuf\n"); + return true; + } + + if (td->syncf_perfect == 0) + { + DEBUGF(" no frame\n"); + return true; + } + + time = clip_time(&video_str, time); + end_time = td->frame_time + td->frame_period; + + DEBUGF(" sft:%u t:%u sfte:%u\n", (unsigned)td->frame_time, + (unsigned)time, (unsigned)end_time); + + if (time < td->frame_time) + return true; + + if (time >= end_time) + return time < video_str.end_pts || end_time < video_str.end_pts; + + return false; +} + +/* Do any needed decoding/slide up to the specified time */ +static int sync_decoder(struct video_thread_data *td, + struct str_sync_data *sd) +{ + int retval = STREAM_ERROR; + uint32_t time = clip_time(&video_str, sd->time); + + td->syncf_perfect = 0; + td->frame_time = 0; + td->frame_period = 0; + td->num_ref_pics = 0; + + /* Sometimes theres no sequence headers nearby and libmpeg2 may have reset + * fully at some point */ + if ((td->info == NULL || td->info->sequence == NULL) && !init_sequence(td)) + { + DEBUGF("sync_decoder=>init_sequence failed\n"); + goto sync_finished; + } + + video_str.hdr.pos = sd->sk.pos; + video_str.hdr.limit = sd->sk.pos + sd->sk.len; + mpeg2_reset(td->mpeg2dec, false); + mpeg2_skip(td->mpeg2dec, 1); + + while (1) + { + mpeg2_state_t mp2state = mpeg2_parse(td->mpeg2dec); + + switch (mp2state) + { + case STATE_BUFFER: + switch (parser_get_next_data(&video_str, STREAM_PM_RANDOM_ACCESS)) + { + case STREAM_DATA_END: + DEBUGF("sync_decoder:STR_DATA_END\n"); + if (td->info && td->info->display_picture && + !(td->info->display_picture->flags & PIC_FLAG_SKIP)) + { + /* No frame matching the time was found up to the end of + * the stream - consider a perfect match since no better + * can be made */ + retval = STREAM_PERFECT_MATCH; + td->syncf_perfect = 1; + } + goto sync_finished; + + case STREAM_OK: + if (video_str.pkt_flags & PKT_HAS_TS) + mpeg2_tag_picture(td->mpeg2dec, video_str.pts, 0); + mpeg2_buffer(td->mpeg2dec, video_str.curr_packet, + video_str.curr_packet_end); + td->info = mpeg2_info(td->mpeg2dec); + break; + } + break; + + case STATE_SEQUENCE: + DEBUGF(" STATE_SEQUENCE\n"); + vo_setup(td->info->sequence); + break; + + case STATE_GOP: + DEBUGF(" STATE_GOP: (%s)\n", + (td->info->gop->flags & GOP_FLAG_CLOSED_GOP) ? + "closed" : "open"); + break; + + case STATE_PICTURE: + { + int type = td->info->current_picture->flags + & PIC_MASK_CODING_TYPE; + + switch (type) + { + case PIC_FLAG_CODING_TYPE_I: + /* I-frame; start decoding */ + mpeg2_skip(td->mpeg2dec, 0); + td->num_ref_pics++; + break; + + case PIC_FLAG_CODING_TYPE_P: + /* P-frames don't count without I-frames */ + if (td->num_ref_pics > 0) + td->num_ref_pics++; + break; + } + + if (td->info->current_picture->flags & PIC_FLAG_TAGS) + { + DEBUGF(" STATE_PICTURE (%c): %u\n", pic_coding_type_char(type), + (unsigned)td->info->current_picture->tag); + } + else + { + DEBUGF(" STATE_PICTURE (%c): -\n", pic_coding_type_char(type)); + } + + break; + } + + case STATE_SLICE: + case STATE_END: + case STATE_INVALID_END: + { + uint32_t end_time; + + if (td->info->display_picture == NULL) + { + DEBUGF(" td->info->display_picture == NULL\n"); + break; /* No picture */ + } + + int type = td->info->display_picture->flags + & PIC_MASK_CODING_TYPE; + + if (td->info->display_picture->flags & PIC_FLAG_TAGS) + { + td->frame_time = td->info->display_picture->tag; + DEBUGF(" frame tagged:%u (%c%s)\n", (unsigned)td->frame_time, + pic_coding_type_char(type), + (td->info->display_picture->flags & PIC_FLAG_SKIP) ? + " skipped" : ""); + } + else + { + td->frame_time += td->frame_period; + DEBUGF(" add frame_period:%u (%c%s)\n", (unsigned)td->frame_time, + pic_coding_type_char(type), + (td->info->display_picture->flags & PIC_FLAG_SKIP) ? + " skipped" : ""); + } + + td->frame_period = TC_TO_TS(td->info->sequence->frame_period); + end_time = td->frame_time + td->frame_period; + + DEBUGF(" ft:%u t:%u fe:%u (%c%s)", + (unsigned)td->frame_time, + (unsigned)time, + (unsigned)end_time, + pic_coding_type_char(type), + (td->info->display_picture->flags & PIC_FLAG_SKIP) ? + " skipped" : ""); + + if (end_time <= time && end_time < video_str.end_pts) + { + /* Still too early and have not hit at EOS */ + DEBUGF(" too early\n"); + break; + } + else if (!(td->info->display_picture->flags & PIC_FLAG_SKIP)) + { + /* One perfect point if dependent frames were decoded */ + switch (type) + { + case PIC_FLAG_CODING_TYPE_B: + if (td->num_ref_pics > 1) + { + case PIC_FLAG_CODING_TYPE_P: + if (td->num_ref_pics > 0) + { + case PIC_FLAG_CODING_TYPE_I: + td->syncf_perfect = 1; + break; + } + } + } + + if ((td->frame_time <= time && time < end_time) || + end_time >= video_str.end_pts) + { + /* One perfect point for matching time goal */ + DEBUGF(" ft<=tsyncf_perfect++; + } + else + { + DEBUGF(" ft>t\n"); + } + + /* Two or more perfect points = perfect match - yay! */ + retval = (td->syncf_perfect >= 2) ? + STREAM_PERFECT_MATCH : STREAM_MATCH; + } + else + { + /* Too late, no I-Frame yet */ + DEBUGF("\n"); + } + + goto sync_finished; + } + + default: + break; + } + + rb->yield(); + } /* end while */ + +sync_finished: + mpeg2_skip(td->mpeg2dec, 0); + 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) +{ + while (1) + { + intptr_t reply = 0; + + switch (td->ev.id) + { + case STREAM_PLAY: + td->status = STREAM_PLAYING; + + switch (td->state) + { + case TSTATE_INIT: + /* Begin decoding state */ + td->state = TSTATE_DECODE; + /* */ + case TSTATE_DECODE: + if (td->syncf_perfect <= 0) + break; + /* There should be a frame already, just draw it */ + td->goal_time = td->frame_time; + td->state = TSTATE_RENDER_WAIT; + /* */ + case TSTATE_RENDER_WAIT: + /* Settings may have changed to nonlimited - just draw + * what was previously being waited for */ + td->stream_time = TICKS_TO_TS(stream_get_time()); + if (!settings.limitfps) + td->state = TSTATE_RENDER; + /* */ + case TSTATE_RENDER: + break; + + case TSTATE_EOS: + /* At end of stream - no playback possible so fire the + * completion event */ + stream_generate_event(&video_str, STREAM_EV_COMPLETE, 0); + break; + } + + reply = td->state != TSTATE_EOS; + break; + + case STREAM_PAUSE: + td->status = STREAM_PAUSED; + reply = td->state != TSTATE_EOS; + break; + + case STREAM_STOP: + if (td->state == TSTATE_DATA) + stream_clear_notify(&video_str, DISK_BUF_DATA_NOTIFY); + + td->status = STREAM_STOPPED; + td->state = TSTATE_EOS; + reply = true; + break; + + case VIDEO_DISPLAY_IS_VISIBLE: + reply = vo_is_visible(); + break; + + case VIDEO_DISPLAY_SHOW: + /* Show video and draw the last frame we had if any or reveal the + * underlying framebuffer if hiding */ + reply = vo_show(!!td->ev.data); + +#ifdef HAVE_LCD_COLOR + /* Match graylib behavior as much as possible */ + if (!td->ev.data == !reply) + break; + + if (td->ev.data) + { + frame_print_handler(td); + } + else + { + IF_COP(rb->commit_discard_dcache()); + vo_lock(); + rb->lcd_update(); + vo_unlock(); + } +#endif + break; + + case STREAM_RESET: + if (td->state == TSTATE_DATA) + stream_clear_notify(&video_str, DISK_BUF_DATA_NOTIFY); + + td->state = TSTATE_INIT; + td->status = STREAM_STOPPED; + + /* Reset operational info but not sync info */ + td->stream_time = UINT32_MAX; + td->goal_time = 0; + td->remain_time = 0; + td->skip_ref_pics = 0; + td->skip_level = 0; + td->num_picture = 0; + td->num_intra = 0; + td->group_est = DEFAULT_GOP_SIZE; + td->last_render = *rb->current_tick - HZ; + video_num_drawn = 0; + video_num_skipped = 0; + + reply = true; + break; + + case STREAM_NEEDS_SYNC: + reply = check_needs_sync(td, td->ev.data); + break; + + case STREAM_SYNC: + if (td->state == TSTATE_INIT) + reply = sync_decoder(td, (struct str_sync_data *)td->ev.data); + break; + + case DISK_BUF_DATA_NOTIFY: + /* Our bun is done */ + if (td->state != TSTATE_DATA) + break; + + td->state = TSTATE_DECODE; + str_data_notify_received(&video_str); + break; + + case VIDEO_PRINT_FRAME: + case VIDEO_PRINT_THUMBNAIL: + 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_GET_CLIP_RECT: + reply = vo_get_clip_rect((struct vo_rect *)td->ev.data); + break; + + case VIDEO_GET_SIZE: + { + if (td->state != TSTATE_INIT) + break; /* Can only use after a reset was issued */ + + /* This will reset the decoder in full for this particular event */ + if (init_sequence(td)) + { + reply = true; + vo_dimensions((struct vo_ext *)td->ev.data); + } + break; + } + + case STREAM_FIND_END_TIME: + if (td->state != TSTATE_INIT) + { + reply = STREAM_ERROR; + break; + } + + 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; + return; + } + + str_reply_msg(&video_str, reply); + + if (td->status == STREAM_PLAYING) + { + switch (td->state) + { + case TSTATE_DECODE: + case TSTATE_RENDER: + case TSTATE_RENDER_WAIT: + /* These return when in playing state */ + return; + } + } + + str_get_msg(&video_str, &td->ev); + } +} + +static void video_thread(void) +{ + struct video_thread_data td; + + memset(&td, 0, sizeof (td)); + td.mpeg2dec = mpeg2_init(); + td.status = STREAM_STOPPED; + td.state = TSTATE_EOS; + + if (td.mpeg2dec == NULL) + { + td.status = STREAM_ERROR; + /* Loop and wait for quit message */ + while (1) + { + str_get_msg(&video_str, &td.ev); + if (td.ev.id == STREAM_QUIT) + return; + str_reply_msg(&video_str, STREAM_ERROR); + } + } + + vo_init(); + + goto message_wait; + + while (1) + { + mpeg2_state_t mp2state; + td.state = TSTATE_DECODE; + + /* Check for any pending messages and process them */ + if (str_have_msg(&video_str)) + { + message_wait: + /* Wait for a message to be queued */ + str_get_msg(&video_str, &td.ev); + + message_process: + /* Process a message already dequeued */ + video_thread_msg(&td); + + switch (td.state) + { + /* These states are the only ones that should return */ + case TSTATE_DECODE: goto picture_decode; + case TSTATE_RENDER: goto picture_draw; + case TSTATE_RENDER_WAIT: goto picture_wait; + /* Anything else is interpreted as an exit */ + default: goto video_exit; + } + } + + picture_decode: + mp2state = mpeg2_parse (td.mpeg2dec); + + switch (mp2state) + { + case STATE_BUFFER: + /* Request next packet data */ + switch (parser_get_next_data(&video_str, STREAM_PM_STREAMING)) + { + case STREAM_DATA_NOT_READY: + /* Wait for data to be buffered */ + td.state = TSTATE_DATA; + goto message_wait; + + case STREAM_DATA_END: + /* No more data. */ + td.state = TSTATE_EOS; + if (td.status == STREAM_PLAYING) + stream_generate_event(&video_str, STREAM_EV_COMPLETE, 0); + goto message_wait; + + case STREAM_OK: + if (video_str.pkt_flags & PKT_HAS_TS) + mpeg2_tag_picture(td.mpeg2dec, video_str.pts, 0); + + mpeg2_buffer(td.mpeg2dec, video_str.curr_packet, + video_str.curr_packet_end); + td.info = mpeg2_info(td.mpeg2dec); + break; + } + break; + + case STATE_SEQUENCE: + /* New video sequence, inform output of any changes */ + vo_setup(td.info->sequence); + break; + + case STATE_PICTURE: + { + /* This is not in presentation order - do our best anyway */ + int skip = td.skip_ref_pics; + + /* Frame type: I/P/B/D */ + switch (td.info->current_picture->flags & PIC_MASK_CODING_TYPE) + { + case PIC_FLAG_CODING_TYPE_I: + if (++td.num_intra >= 2) + td.group_est = td.num_picture / (td.num_intra - 1); + + /* Things are extremely late and all frames will be + dropped until the next key frame */ + if (td.skip_level > 0 && td.skip_level >= td.group_est) + { + td.skip_level--; /* skip frame */ + skip = td.skip_ref_pics = 1; /* wait for I-frame */ + td.num_ref_pics = 0; + } + else if (skip != 0) + { + skip = td.skip_ref_pics = 0; /* now, decode */ + td.num_ref_pics = 1; + } + break; + + case PIC_FLAG_CODING_TYPE_P: + if (skip == 0) + { + td.num_ref_pics++; + + /* If skip_level at least the estimated number of frames + left in I-I span, skip until next I-frame */ + if (td.group_est > 0 && td.skip_level >= td.group_est) + { + skip = td.skip_ref_pics = 1; /* wait for I-frame */ + td.num_ref_pics = 0; + } + } + + if (skip != 0) + td.skip_level--; + break; + + case PIC_FLAG_CODING_TYPE_B: + /* We want to drop something, so this B-frame won't even be + decoded. Drawing can happen on the next frame if so desired + so long as the B-frames were not dependent upon those from + a previous open GOP where the needed reference frames were + skipped */ + if (td.skip_level > 0 || td.num_ref_pics < 2) + { + skip = 1; + td.skip_level--; + } + break; + + default: + skip = 1; + break; + } + + if (td.num_intra > 0) + td.num_picture++; + + td.group_est--; + + mpeg2_skip(td.mpeg2dec, skip); + break; + } + + case STATE_SLICE: + case STATE_END: + case STATE_INVALID_END: + { + int32_t offset; /* Tick adjustment to keep sync */ + + if (td.info->display_fbuf == NULL) + break; /* No picture */ + + /* Get presentation times in audio samples - quite accurate + enough - add previous frame duration if not stamped */ + if (td.info->display_picture->flags & PIC_FLAG_TAGS) + td.frame_time = td.info->display_picture->tag; + else + td.frame_time += td.frame_period; + + td.frame_period = TC_TO_TS(td.info->sequence->frame_period); + + if (!settings.limitfps) + { + /* No limiting => no dropping or waiting - draw this frame */ + td.remain_time = 0; + td.skip_level = 0; + td.syncf_perfect = 1; /* have frame */ + goto picture_draw; + } + + td.goal_time = td.frame_time; + td.stream_time = TICKS_TO_TS(stream_get_time()); + + /* How early/late are we? > 0 = late, < 0 early */ + offset = td.stream_time - td.goal_time; + + if (offset >= 0) + { + /* Late or on-time */ + if (td.remain_time < 0) + td.remain_time = 0; /* now, late */ + + offset = AVERAGE(td.remain_time, offset, 4); + td.remain_time = offset; + } + else + { + /* Early */ + if (td.remain_time >= 0) + td.remain_time = 0; /* now, early */ + else if (offset > td.remain_time) + td.remain_time = MAX(offset, -MAX_EARLINESS); /* less early */ + else if (td.remain_time != 0) + td.remain_time = AVERAGE(td.remain_time, 0, 8); /* earlier/same */ + /* else there's been no frame drop */ + + offset = -td.remain_time; + } + + /* Skip anything not decoded */ + if (td.info->display_picture->flags & PIC_FLAG_SKIP) + goto picture_skip; + + td.syncf_perfect = 1; /* have frame (assume so from now on) */ + + /* Keep goal_time >= 0 */ + if ((uint32_t)offset > td.goal_time) + offset = td.goal_time; + + td.goal_time -= offset; + + if (!settings.skipframes) + { + /* No skipping - just wait if we're early and correct for + lateness as much as possible. */ + td.skip_level = 0; + goto picture_wait; + } + + /** Possibly skip this frame **/ + + /* Frameskipping has the following order of preference: + * + * Frame Type Who Notes/Rationale + * B decoder arbitrarily drop - no decode or draw + * Any renderer arbitrarily drop - I/P unless B decoded + * P decoder must wait for I-frame + * I decoder must wait for I-frame + * + * If a frame can be drawn and it has been at least 1/2 second, + * the image will be updated no matter how late it is just to + * avoid looking stuck. + */ + if (td.skip_level > 0 && + TIME_BEFORE(*rb->current_tick, td.last_render + HZ/2)) + { + /* Frame skip was set previously but either there wasn't anything + dropped yet or not dropped enough. So we quit at least rendering + the actual frame to avoid further increase of a/v-drift. */ + td.skip_level--; + goto picture_skip; + } + + /* At this point a frame _will_ be drawn - a skip may happen on + the next however */ + + /* Calculate number of frames to drop/skip - allow brief periods + of lateness before producing skips */ + td.skip_level = 0; + if (td.remain_time > 0 && (uint32_t)offset > DROP_THRESHOLD) + { + td.skip_level = (offset - DROP_THRESHOLD + td.frame_period) + / td.frame_period; + } + + picture_wait: + td.state = TSTATE_RENDER_WAIT; + + /* Wait until time catches up */ + while (1) + { + int32_t twait = td.goal_time - td.stream_time; + /* Watch for messages while waiting for the frame time */ + + if (twait <= 0) + break; + + if (twait > TS_SECOND/HZ) + { + /* Several ticks to wait - do some sleeping */ + int timeout = (twait - HZ) / (TS_SECOND/HZ); + str_get_msg_w_tmo(&video_str, &td.ev, MAX(timeout, 1)); + if (td.ev.id != SYS_TIMEOUT) + goto message_process; + } + else + { + /* Just a little left - spin and be accurate */ + rb->yield(); + if (str_have_msg(&video_str)) + goto message_wait; + } + + td.stream_time = TICKS_TO_TS(stream_get_time()); + } + + picture_draw: + /* Record last frame time */ + td.last_render = *rb->current_tick; + + vo_draw_frame(td.info->display_fbuf->buf); + video_num_drawn++; + break; + + picture_skip: + if (td.remain_time <= DROP_THRESHOLD) + { + td.skip_level = 0; + if (td.remain_time <= 0) + td.remain_time = INT32_MIN; + } + + video_num_skipped++; + break; + } + + default: + break; + } + + rb->yield(); + } /* end while */ + +video_exit: + vo_cleanup(); + mpeg2_close(td.mpeg2dec); +} + +/* Initializes the video thread */ +bool video_thread_init(void) +{ + intptr_t rep; + + IF_COP(rb->commit_dcache()); + + video_str.hdr.q = &video_str_queue; + rb->queue_init(video_str.hdr.q, false); + + /* We put the video thread on another processor for multi-core targets. */ + video_str.thread = rb->create_thread( + video_thread, video_stack, VIDEO_STACKSIZE, 0, + "mpgvideo" IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, COP)); + + rb->queue_enable_queue_send(video_str.hdr.q, &video_str_queue_send, + video_str.thread); + + if (video_str.thread == 0) + return false; + + /* Wait for thread to initialize */ + rep = str_send_msg(&video_str, STREAM_NULL, 0); + IF_COP(rb->commit_discard_dcache()); + + return rep == 0; /* Normally STREAM_NULL should be ignored */ +} + +/* Terminates the video thread */ +void video_thread_exit(void) +{ + if (video_str.thread != 0) + { + str_post_msg(&video_str, STREAM_QUIT, 0); + rb->thread_wait(video_str.thread); + IF_COP(rb->commit_discard_dcache()); + 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); +} + diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index 26c1543fdd..8d266966d0 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config @@ -71,6 +71,10 @@ wav,viewers/wavview,10 wav,viewers/wav2wv,- wav,viewers/mp3_encoder,- wav,viewers/test_codec,- +mpg,viewers/mpegplayer,4 +mpeg,viewers/mpegplayer,4 +mpv,viewers/mpegplayer,4 +m2v,viewers/mpegplayer,4 iriver,viewers/iriver_flash,3 tap,viewers/zxbox,12 opx,viewers/open_plugins,- diff --git a/docs/MAINTAINERS b/docs/MAINTAINERS index 2771adb7ad..143e6fa2b4 100644 --- a/docs/MAINTAINERS +++ b/docs/MAINTAINERS @@ -189,6 +189,7 @@ Plugins :minesweeper: Antoine Cellerier :mosaique: :mp3_encoder: +:mpegplayer: Michael Sevakis :nim: :oscilloscope: Jens Arnold :pacbox: Dave Chapman diff --git a/lib/rbcodec/codecs/libmad/libmad.make b/lib/rbcodec/codecs/libmad/libmad.make index 479dd54773..6c50e1d201 100644 --- a/lib/rbcodec/codecs/libmad/libmad.make +++ b/lib/rbcodec/codecs/libmad/libmad.make @@ -7,9 +7,16 @@ # $Id$ # +# we need to build two different mad libraries +# (one for codec, one for mpegplayer) +# so a little trickery is necessary + MADFLAGS = $(CODECFLAGS) -I$(RBCODECLIB_DIR)/codecs/libmad MADFLAGS += -UDEBUG -DNDEBUG -DHAVE_LIMITS_H -DHAVE_ASSERT_H +# MPEGplayer +MPEGMADFLAGS = $(MADFLAGS) -DMPEGPLAYER + # libmad MADLIB := $(CODECDIR)/libmad.a MADLIB_SRC := $(call preprocess, $(RBCODECLIB_DIR)/codecs/libmad/SOURCES) @@ -20,8 +27,26 @@ $(MADLIB): $(MADLIB_OBJ) $(SILENT)$(shell rm -f $@) $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null +# libmad-mpeg +MPEGMADLIB := $(CODECDIR)/libmad-mpeg.a +MPEGMADLIB_SRC := $(call preprocess, $(RBCODECLIB_DIR)/codecs/libmad/SOURCES) +MPEGMADLIB_OBJ := $(addsuffix .o,$(basename $(subst $(RBCODECLIB_DIR)/codecs/libmad,$(RBCODEC_BLD)/codecs/libmad-mpeg,$(MPEGMADLIB_SRC)))) + +$(MPEGMADLIB): $(MPEGMADLIB_OBJ) + $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null + # pattern rules +$(CODECDIR)/libmad-mpeg/%.o : $(RBCODECLIB_DIR)/codecs/libmad/%.c + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<)) \ + $(CC) $(MPEGMADFLAGS) -c $< -o $@ + +$(CODECDIR)/libmad-mpeg/%.o : $(RBCODECLIB_DIR)/codecs/libmad/%.S + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<)) \ + $(CC) $(MPEGMADFLAGS) -c $< -o $@ + $(CODECDIR)/libmad/%.o: $(RBCODECLIB_DIR)/codecs/libmad/%.c $(SILENT)mkdir -p $(dir $@) $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<)) \ diff --git a/lib/rbcodec/codecs/libmad/mad_iram.h b/lib/rbcodec/codecs/libmad/mad_iram.h index 5a315c0381..ac0b64cca9 100644 --- a/lib/rbcodec/codecs/libmad/mad_iram.h +++ b/lib/rbcodec/codecs/libmad/mad_iram.h @@ -35,9 +35,10 @@ #define ICODE_ATTR_MPA_SYNTH #define ICONST_ATTR_MPA_HUFFMAN #else -/* Code performs slower in IRAM on PP502x - S3C2440 doesn't have any IRAM available for codecs */ -#if defined(CPU_PP502x) +/* Code performs slower in IRAM on PP502x and there is no space in + mpegplayer on the PP5002. S3C2440 doesn't have any IRAM available for + codecs */ +#if defined(CPU_PP502x) || (CONFIG_CPU == PP5002 && defined(MPEGPLAYER)) #define ICODE_SECTION_MPA_ARM .text #define ICODE_ATTR_MPA_SYNTH #else diff --git a/lib/rbcodec/codecs/mpa.c b/lib/rbcodec/codecs/mpa.c index db33f17c3b..d6bcc04910 100644 --- a/lib/rbcodec/codecs/mpa.c +++ b/lib/rbcodec/codecs/mpa.c @@ -26,7 +26,7 @@ CODEC_HEADER -#if NUM_CORES > 1 +#if NUM_CORES > 1 && !defined(MPEGPLAYER) #define MPA_SYNTH_ON_COP #endif diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex index 73cc1a2ada..3348b1fce5 100644 --- a/manual/plugins/main.tex +++ b/manual/plugins/main.tex @@ -201,6 +201,8 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} \nopt{lowmem}{\input{plugins/midiplay.tex}} \nopt{lowmem}{\input{plugins/mikmod.tex}} +\nopt{lowmem}{\input{plugins/mpegplayer.tex}} + \input{plugins/mp3_encoder.tex} \opt{iriverh300,iriverh100,SANSA_FUZE_PAD,SANSA_E200_PAD,IPOD_4G_PAD,IPOD_3G_PAD% diff --git a/manual/plugins/mpegplayer.tex b/manual/plugins/mpegplayer.tex new file mode 100644 index 0000000000..9fa438dabe --- /dev/null +++ b/manual/plugins/mpegplayer.tex @@ -0,0 +1,119 @@ +% $Id$ % +\subsection{MPEG Player} +The Mpeg Player is a video player plugin capable of playing back MPEG-1 and +MPEG-2 video streams with MPEG audio multiplexed into \fname{.mpg} files. + +To play a video file, you just select it in the Rockbox \setting{File Browser}. +If your file does not have the \fname{.mpg} extension but is encoded in the +supported format, you will need to use the \setting{Open With...} context menu +option and choose \setting{mpegplayer}. + +\begin{btnmap} + \opt{GIGABEAT_S_PAD}{\ButtonSelect{} or \ButtonPlay} + \opt{GIGABEAT_PAD}{\ButtonSelect{} or \ButtonA} + \nopt{GIGABEAT_S_PAD,GIGABEAT_PAD}{\ActionWpsPlay} + \opt{HAVEREMOTEKEYMAP}{& } + & Pause / Resume\\ + \ActionWpsStop + \opt{HAVEREMOTEKEYMAP}{& } + & Stop\\ + \nopt{GIGABEAT_S_PAD,GIGABEAT_PAD}{\ActionWpsVolUp{} / \ActionWpsVolDown} + \opt{GIGABEAT_S_PAD,GIGABEAT_PAD}{\ButtonLeft{} or \ButtonVolDown{} / + \ButtonRight{} or \ButtonVolUp} + \opt{HAVEREMOTEKEYMAP}{& } + & Adjust volume up / down\\ + \nopt{GIGABEAT_S_PAD,GIGABEAT_PAD,SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD}{% + \ActionWpsSkipPrev{} / \ActionWpsSkipNext} + \opt{GIGABEAT_S_PAD,GIGABEAT_PAD}{\ButtonUp{} / \ButtonDown} + \opt{SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD}{\ButtonLeft{} / \ButtonRight} + \opt{HAVEREMOTEKEYMAP}{& } + & Rewind / Fast Forward\\ + \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonMode} + \opt{IPOD_4G_PAD,IPOD_3G_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD} + {\ButtonMenu} + \opt{IAUDIO_X5_PAD}{\ButtonRec} + \opt{IRIVER_H10_PAD}{\ButtonRew} + \opt{SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD}{\ButtonRew} + \opt{SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,SANSA_CLIP_PAD}{\ButtonSelect} + \opt{HAVEREMOTEKEYMAP}{& } + & Open the MPEG Player menu\\ +\end{btnmap} + +When a video file is selected, the Start Menu will be displayed, unless it is +disabled via the \setting{Resume Options} (see below). In the latter case the video +will start playing immediately. + +Start Menu + +\begin{description} +\item[Play from beginning] Resume information is discarded and the video plays + from the start. +\item[Resume at: mm:ss] Resume video playback at stored resume time mm:ss + (start of the video if no resume time is found). +\item[Set start time] A preview screen is presented consisting of a + thumbnail preview and a progress bar where the user can select a start time + by `seeking' through the video. The video playback is started by pressing + the select button. +\item[Settings] Open \setting{Settings} submenu -- see below. +\item[Quit mpegplayer] Exit the plugin. +\end{description} + +Main Menu + +\begin{description} +\item[Settings] Open \setting{Settings} submenu -- see below. +\item[Resume playback] Return to playback screen. +\item[Quit mpegplayer] Exit the plugin. +\end{description} + +Settings Menu + +\begin{description} +\item[Display Options] Open \setting{Display Options} submenu -- see below. +\item[Audio Options] Open \setting{Audio Options} submenu -- see below. +\item[Resume Options] (default: Start menu) Enable/disable the start menu. +\item[Play Mode] (default: Single) Set to \setting{All} to play multiple + \fname{.mpg} files in the directory continuously. +\item[Clear all resumes: x] Discard all x resume points. +\end{description} + +Display Options Menu + +\begin{description} +\item[Dithering] (default: off) Prevent banding effects in gradients by blending + of colours. (only available on Sansa e200, Sansa c200 and Gigabeat F/X) +\item[Display FPS] (default: off) This option displays (once a second -- if your + video is full-screen this means it will get overwritten by the video and + appear to flash once per second) the average number of frames decoded per + second, the total number of frames skipped (see the Skip Frames option), + the current time (in 100~Hz ticks) and the time the current frame is due to + be displayed. +\item[Limit FPS] (default: on) With this option disabled, mpegplayer will + display the video as fast as it can. Useful for benchmarking. +\item[Skip frames] (default: on) This option causes mpegplayer to attempt to + maintain realtime playback by skipping the display of frames -- but these + frames are still decoded. Disabling this option can cause loss of A/V sync. +\opt{backlight_brightness}{ + \item[Backlight Brightness] (default: Use setting) Choose brightness to use + during video playback. Set to \setting{Use setting} to use the Brightness + setting. +} +\end{description} + +Audio Options Menu + +\begin{description} +\item[Tone Controls] (default: force off) Use the bass and treble control + settings or force them off. +\item[Channel Modes] (default: force off) Use the channel configuration setting + or force Stereo mode. +\item[Crossfeed] (default: force off) Use the Crossfeed setting or force + crossfeed off. +\item[Equalizer] (default: force off) Use the Equalizer setting or force the + equalizer off. +\item[Dithering] (default: force off) Use the Dithering setting or force + audio dithering off. +\end{description} + +See this page in the Rockbox wiki for information on how to encode your videos +to the supported format. \wikilink{PluginMpegplayer} -- cgit v1.2.3