From a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sat, 29 Dec 2007 19:46:35 +0000 Subject: mpegplayer: Make playback engine fully seekable and frame-accurate and split into logical parts. Be sure to have all current features work. Actual UI for seeking will be added soon. Recommended GOP size is about 15-30 frames depending on target or seeking can be slow with really long GOPs (nature of MPEG video). More refined encoding recommendations for a particular player should be posted soon. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15977 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/mpegplayer/SOURCES | 8 + apps/plugins/mpegplayer/alloc.c | 173 +- apps/plugins/mpegplayer/audio_thread.c | 743 ++++++++ apps/plugins/mpegplayer/decode.c | 11 +- apps/plugins/mpegplayer/disk_buf.c | 906 +++++++++ apps/plugins/mpegplayer/disk_buf.h | 132 ++ apps/plugins/mpegplayer/header.c | 8 +- apps/plugins/mpegplayer/mpeg2.h | 6 + apps/plugins/mpegplayer/mpeg_alloc.h | 12 + apps/plugins/mpegplayer/mpeg_linkedlist.c | 149 ++ apps/plugins/mpegplayer/mpeg_linkedlist.h | 69 + apps/plugins/mpegplayer/mpeg_misc.c | 96 + apps/plugins/mpegplayer/mpeg_misc.h | 206 ++ apps/plugins/mpegplayer/mpeg_parser.c | 1182 ++++++++++++ apps/plugins/mpegplayer/mpeg_settings.c | 529 ++++-- apps/plugins/mpegplayer/mpeg_settings.h | 18 +- apps/plugins/mpegplayer/mpeg_stream.h | 120 ++ apps/plugins/mpegplayer/mpegplayer.c | 2681 +++------------------------ apps/plugins/mpegplayer/mpegplayer.h | 120 ++ apps/plugins/mpegplayer/parser.h | 101 + apps/plugins/mpegplayer/pcm_output.c | 278 +++ apps/plugins/mpegplayer/pcm_output.h | 46 + apps/plugins/mpegplayer/stream_mgr.c | 1096 +++++++++++ apps/plugins/mpegplayer/stream_mgr.h | 151 ++ apps/plugins/mpegplayer/stream_thread.h | 192 ++ apps/plugins/mpegplayer/video_out.h | 46 +- apps/plugins/mpegplayer/video_out_rockbox.c | 479 +++-- apps/plugins/mpegplayer/video_thread.c | 1040 +++++++++++ apps/plugins/viewers.config | 2 + 29 files changed, 7819 insertions(+), 2781 deletions(-) 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/mpeg_alloc.h create mode 100644 apps/plugins/mpegplayer/mpeg_linkedlist.c create mode 100644 apps/plugins/mpegplayer/mpeg_linkedlist.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_stream.h create mode 100644 apps/plugins/mpegplayer/mpegplayer.h 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_thread.c (limited to 'apps/plugins') diff --git a/apps/plugins/mpegplayer/SOURCES b/apps/plugins/mpegplayer/SOURCES index e7e2a7a0de..5b3360cc5a 100644 --- a/apps/plugins/mpegplayer/SOURCES +++ b/apps/plugins/mpegplayer/SOURCES @@ -18,5 +18,13 @@ motion_comp_c.c slice.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_linkedlist.c +mpeg_parser.c +mpeg_misc.c diff --git a/apps/plugins/mpegplayer/alloc.c b/apps/plugins/mpegplayer/alloc.c index c79894447b..1ff75d4d64 100644 --- a/apps/plugins/mpegplayer/alloc.c +++ b/apps/plugins/mpegplayer/alloc.c @@ -22,10 +22,7 @@ */ #include "plugin.h" - -#include "mpeg2.h" - -extern struct plugin_api* rb; +#include "mpegplayer.h" /* Main allocator */ static off_t mem_ptr; @@ -33,9 +30,58 @@ static size_t bufsize; static unsigned char* mallocbuf; /* libmpeg2 allocator */ -static off_t mpeg2_mem_ptr; -static size_t mpeg2_bufsize; -static unsigned char *mpeg2_mallocbuf; +static off_t mpeg2_mem_ptr NOCACHEBSS_ATTR; +static size_t mpeg2_bufsize NOCACHEBSS_ATTR; +static unsigned char *mpeg2_mallocbuf NOCACHEBSS_ATTR; +static unsigned char *mpeg2_bufallocbuf NOCACHEBSS_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, @@ -45,12 +91,15 @@ static void * mpeg_malloc_internal (unsigned char *mallocbuf, { void *x; + DEBUGF("mpeg_alloc_internal: bs:%lu s:%u reason:%s (%d)\n", + 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 */ @@ -64,39 +113,46 @@ void *mpeg_malloc(size_t size, mpeg2_alloc_t reason) reason); } -size_t mpeg_alloc_init(unsigned char *buf, size_t mallocsize, - size_t libmpeg2size) +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; - bufsize = mallocsize; - /* Line-align buffer */ - mallocbuf = (char *)(((intptr_t)buf + 15) & ~15); - /* Adjust for real size */ - bufsize -= mallocbuf - buf; + /* Cache-align buffer or 4-byte align */ + mallocbuf = buf; + bufsize = align_buffer(PUN_PTR(void **, &mallocbuf), + mallocsize, CACHEALIGN_UP(4)); /* Separate allocator for video */ - libmpeg2size = (libmpeg2size + 15) & ~15; + 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, libmpeg2size, 0) == NULL) + bufsize, mpeg2_bufsize, + MPEG_ALLOC_MPEG2_BUFFER) == NULL) { - return 0; + return false; } - mpeg2_mallocbuf = mallocbuf; - mpeg2_mem_ptr = 0; - mpeg2_bufsize = libmpeg2size; - -#if NUM_CORES > 1 - flush_icache(); -#endif - - return bufsize - mpeg2_bufsize; + IF_COP(flush_icache()); + return true; } -/* gcc may want to use memcpy before rb is initialised, so here's a trivial +/* gcc may want to use memcpy before rb is initialised, so here's a trivial implementation */ -void *memcpy(void *dest, const void *src, size_t n) { +void *memcpy(void *dest, const void *src, size_t n) +{ size_t i; char* d=(char*)dest; char* s=(char*)src; @@ -107,22 +163,60 @@ void *memcpy(void *dest, const void *src, size_t n) { return dest; } +/* allocate non-dedicated buffer space which mpeg2_mem_reset will free */ void * mpeg2_malloc(unsigned size, mpeg2_alloc_t reason) { - return mpeg_malloc_internal(mpeg2_mallocbuf, &mpeg2_mem_ptr, - mpeg2_bufsize, size, 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; } -void mpeg2_free(void *ptr) +/* allocate dedicated buffer - memory behind buffer pointer becomes dedicated + so order is important */ +void * mpeg2_bufalloc(unsigned size, mpeg2_alloc_t reason) { - mpeg2_mem_ptr = (void *)ptr - (void *)mpeg2_mallocbuf; + 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) { - return mpeg_malloc_internal(mallocbuf, &mem_ptr, - bufsize, size, -3); + 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) @@ -130,17 +224,22 @@ void * codec_calloc(size_t nmemb, size_t size) void* ptr; ptr = mpeg_malloc_internal(mallocbuf, &mem_ptr, - bufsize, nmemb*size, -3); + 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; } void *memmove(void *dest, const void *src, size_t n) diff --git a/apps/plugins/mpegplayer/audio_thread.c b/apps/plugins/mpegplayer/audio_thread.c new file mode 100644 index 0000000000..ee6ff05d2d --- /dev/null +++ b/apps/plugins/mpegplayer/audio_thread.c @@ -0,0 +1,743 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * mpegplayer audio thread implementation + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 */ +}; + +/* The audio stack is stolen from the core codec thread (but not in uisim) */ +/* Used for stealing codec thread's stack */ +static uint32_t* audio_stack; +static size_t audio_stack_size; /* Keep gcc happy and init */ +#define AUDIO_STACKSIZE (9*1024) +#ifndef SIMULATOR +static uint32_t codec_stack_copy[AUDIO_STACKSIZE / sizeof(uint32_t)]; +#endif +static struct event_queue audio_str_queue NOCACHEBSS_ATTR; +static struct queue_sender_list audio_str_queue_send NOCACHEBSS_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; + +/* 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 */ +#ifdef CPU_COLDFIRE +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 tail 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; + audio_queue.curr = audiodesc_queue_head(); + audio_queue.curr->time = 0; + audio_queue.curr->size = 0; +} + +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) +{ + mad_stream_init(&stream); + mad_frame_init(&frame); + mad_synth_init(&synth); + + /* We do this so libmad doesn't try to call codec_calloc() */ + rb->memset(mad_frame_overlap, 0, sizeof(mad_frame_overlap)); + frame.overlap = (void *)mad_frame_overlap; + + rb->memset(mad_main_data, 0, sizeof(mad_main_data)); + stream.main_data = &mad_main_data; +} + +/* 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", + 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: + case TSTATE_RENDER_WAIT_END: + 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; + + 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: + case TSTATE_RENDER_WAIT_END: + /* These return when in playing state */ + return; + } + } + + str_get_msg(&audio_str, &td->ev); + } +} + +static void audio_thread(void) +{ + struct audio_thread_data td; + + td.status = STREAM_STOPPED; + td.state = TSTATE_EOS; + + /* We need this here to init the EMAC for Coldfire targets */ + init_mad(); + + 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; + case TSTATE_RENDER_WAIT_END: goto render_wait_end; + /* Anything else is interpreted as an exit */ + default: 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; + + /* Used up remainder of compressed audio buffer. + * Force any residue to play if audio ended before + * reaching the threshold */ + td.state = TSTATE_RENDER_WAIT_END; + audio_queue_reset(); + + render_wait_end: + pcm_output_drain(); + + while (pcm_output_used() > (ssize_t)PCMOUT_LOW_WM) + { + str_get_msg_w_tmo(&audio_str, &td.ev, 1); + if (td.ev.id != SYS_TIMEOUT) + goto message_process; + } + + td.state = TSTATE_EOS; + if (td.status == STREAM_PLAYING) + stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0); + + rb->yield(); + 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_FLAG_INCOMPLETE + || 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; + + if (td.status == STREAM_PLAYING) + stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0); + + td.status = STREAM_ERROR; + 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 **/ + + /* TODO: Output through core dsp. We'll still use our own PCM buffer + since the core pcm buffer has no timestamping or clock facilities */ + + /* Add a frame of audio to the pcm buffer. Maximum is 1152 samples. */ + render_wait: + if (synth.pcm.length > 0) + { + struct pcm_frame_header *pcm_insert = pcm_output_get_buffer(); + int16_t *audio_data = (int16_t *)pcm_insert->data; + unsigned length = synth.pcm.length; + ssize_t size = sizeof(*pcm_insert) + length*4; + + td.state = TSTATE_RENDER_WAIT; + + /* Wait for required amount of free buffer space */ + while (pcm_output_free() < size) + { + /* Wait one frame */ + int timeout = synth.pcm.length*HZ / synth.pcm.samplerate; + str_get_msg_w_tmo(&audio_str, &td.ev, MAX(timeout, 1)); + if (td.ev.id != SYS_TIMEOUT) + goto message_process; + } + + pcm_insert->time = audio_queue.curr->time; + pcm_insert->size = size; + + /* As long as we're on this timestamp, the time is just incremented + by the number of samples */ + audio_queue.curr->time += length; + + if (MAD_NCHANNELS(&frame.header) == 2) + { + int32_t *left = &synth.pcm.samples[0][0]; + int32_t *right = &synth.pcm.samples[1][0]; + + do + { + /* libmad outputs s3.28 */ + *audio_data++ = clip_sample(*left++ >> 13); + *audio_data++ = clip_sample(*right++ >> 13); + } + while (--length > 0); + } + else /* mono */ + { + int32_t *mono = &synth.pcm.samples[0][0]; + + do + { + int32_t s = clip_sample(*mono++ >> 13); + *audio_data++ = s; + *audio_data++ = s; + } + while (--length > 0); + } + /**/ + + /* Make this data available to DMA */ + pcm_output_add_data(); + } + + rb->yield(); + } /* end decoding loop */ +} + +/* Initializes the audio thread resources and starts the thread */ +bool audio_thread_init(void) +{ + int i; +#ifdef SIMULATOR + /* The simulator thread implementation doesn't have stack buffers, and + these parameters are ignored. */ + (void)i; /* Keep gcc happy */ + audio_stack = NULL; + audio_stack_size = 0; +#else + /* Borrow the codec thread's stack (in IRAM on most targets) */ + audio_stack = NULL; + for (i = 0; i < MAXTHREADS; i++) + { + if (rb->strcmp(rb->threads[i].name, "codec") == 0) + { + /* Wait to ensure the codec thread has blocked */ + while (rb->threads[i].state != STATE_BLOCKED) + rb->yield(); + + /* Now we can steal the stack */ + audio_stack = rb->threads[i].stack; + audio_stack_size = rb->threads[i].stack_size; + + /* Backup the codec thread's stack */ + rb->memcpy(codec_stack_copy, audio_stack, audio_stack_size); + break; + } + } + + if (audio_stack == NULL) + { + /* This shouldn't happen, but deal with it anyway by using + the copy instead */ + audio_stack = codec_stack_copy; + audio_stack_size = AUDIO_STACKSIZE; + } +#endif + + /* 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); + rb->queue_enable_queue_send(audio_str.hdr.q, &audio_str_queue_send); + + audio_str.thread = rb->create_thread( + audio_thread, audio_stack, audio_stack_size, 0, + "mpgaudio" IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, CPU)); + + if (audio_str.thread == NULL) + return false; + + /* 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 != NULL) + { + str_post_msg(&audio_str, STREAM_QUIT, 0); + rb->thread_wait(audio_str.thread); + audio_str.thread = NULL; + } + +#ifndef SIMULATOR + /* Restore the codec thread's stack */ + rb->memcpy(audio_stack, codec_stack_copy, audio_stack_size); +#endif +} diff --git a/apps/plugins/mpegplayer/decode.c b/apps/plugins/mpegplayer/decode.c index fac724c48c..2176cad601 100644 --- a/apps/plugins/mpegplayer/decode.c +++ b/apps/plugins/mpegplayer/decode.c @@ -497,8 +497,8 @@ mpeg2dec_t * mpeg2_init (void) mpeg2_idct_init (); - mpeg2dec = (mpeg2dec_t *)mpeg2_malloc(sizeof (mpeg2dec_t), - MPEG2_ALLOC_MPEG2DEC); + mpeg2dec = (mpeg2dec_t *)mpeg2_bufalloc(sizeof (mpeg2dec_t), + MPEG2_ALLOC_MPEG2DEC); if (mpeg2dec == NULL) return NULL; @@ -509,8 +509,8 @@ mpeg2dec_t * mpeg2_init (void) rb->memset (mpeg2dec->decoder.DCTblock, 0, 64 * sizeof (int16_t)); rb->memset (mpeg2dec->quantizer_matrix, 0, 4 * 64 * sizeof (uint8_t)); - mpeg2dec->chunk_buffer = (uint8_t *)mpeg2_malloc(BUFFER_SIZE + 4, - MPEG2_ALLOC_CHUNK); + mpeg2dec->chunk_buffer = (uint8_t *)mpeg2_bufalloc(BUFFER_SIZE + 4, + MPEG2_ALLOC_CHUNK); mpeg2dec->sequence.width = (unsigned)-1; mpeg2_reset (mpeg2dec, 1); @@ -521,6 +521,9 @@ mpeg2dec_t * mpeg2_init (void) 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/disk_buf.c b/apps/plugins/mpegplayer/disk_buf.c new file mode 100644 index 0000000000..a408b90a67 --- /dev/null +++ b/apps/plugins/mpegplayer/disk_buf.c @@ -0,0 +1,906 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * mpegplayer buffering routines + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "plugin.h" +#include "mpegplayer.h" + +static struct mutex disk_buf_mtx NOCACHEBSS_ATTR; +static struct event_queue disk_buf_queue NOCACHEBSS_ATTR; +static struct queue_sender_list disk_buf_queue_send NOCACHEBSS_ATTR; +static uint32_t disk_buf_stack[DEFAULT_STACK_SIZE*2/sizeof(uint32_t)]; + +struct disk_buf disk_buf NOCACHEBSS_ATTR; +static struct list_item nf_list; + +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_HEADER(sh)->id); + list_remove_item(&sh->nf); +} + +static int disk_buf_on_data_notify(struct stream_hdr *sh) +{ + DEBUGF("DISK_BUF_DATA_NOTIFY: 0x%02X ", STR_FROM_HEADER(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(&sh->nf); + 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->nf); + 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 list_item *item, + intptr_t data) +{ + struct stream_hdr *sh = TYPE_FROM_MEMBER(struct stream_hdr, item, nf); + + 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(item); + str_post_msg(STR_FROM_HEADER(sh), DISK_BUF_DATA_NOTIFY, 0); + DEBUGF("DISK_BUF_DATA_NOTIFY: 0x%02X (notified)\n", + STR_FROM_HEADER(sh)->id); + } + + return true; + (void)data; +} + +/* Check registered streams and notify them if their data is available */ +static void check_data_notifies(void) +{ + list_enum_items(&nf_list, check_data_notifies_callback, 0); +} + +/* 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->splash(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; + 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; + 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; + 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.next != 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 int disk_buf_probe(off_t start, size_t length, + void **p, size_t *outlen) +{ + off_t end; + uint32_t tag, tag_end; + int page; + + /* Can't read past end of file */ + if (length > (size_t)(disk_buf.filesize - disk_buf.offset)) + { + length = disk_buf.filesize - disk_buf.offset; + } + + /* 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); + } + + if (outlen != NULL) + { + *outlen = length; + } + + /* 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 NOCACHEBSS_ATTR; + DEBUGF("disk_buf: cache miss\n"); + rng.tag_start = tag; + rng.tag_end = tag_end; + rng.pg_start = page; + return rb->queue_send(disk_buf.q, DISK_BUF_CACHE_RANGE, + (intptr_t)&rng); + } + + if (++page >= disk_buf.pgcount) + page = 0; + } + while (++tag <= tag_end); + + return DISK_BUF_NOTIFY_OK; +} + +/* 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(); + + if (disk_buf_probe(disk_buf.offset, size, pp, &size) == DISK_BUF_NOTIFY_OK) + { + if (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; + } + } + } + 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(); + + if (disk_buf_probe(disk_buf.offset, size, PUN_PTR(void **, &p), + &size) == DISK_BUF_NOTIFY_OK) + { + 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; + } + else + { + size = -1; + } + + disk_buf_unlock(); + + return size; +} + +off_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, len); + + pos = disk_buf_lseek(pos, SEEK_SET); + disk_buf_probe(pos, len, NULL, &len); + + DEBUGF(" probe done: pos:%ld len:%lu\n", pos, 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 = NULL; + list_initialize(&nf_list); + + rb->mutex_init(&disk_buf_mtx); + + disk_buf.q = &disk_buf_queue; + rb->queue_init(disk_buf.q, false); + rb->queue_enable_queue_send(disk_buf.q, &disk_buf_queue_send); + + 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(&disk_buf.size, MPEG_ALLOC_DISKBUF); + if (disk_buf.start == NULL) + return false; + +#ifdef PROC_NEEDS_CACHEALIGN + disk_buf.size = 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, 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)); + + if (disk_buf.thread == NULL) + 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 != NULL) + { + rb->queue_post(disk_buf.q, STREAM_QUIT, 0); + rb->thread_wait(disk_buf.thread); + disk_buf.thread = NULL; + } +} diff --git a/apps/plugins/mpegplayer/disk_buf.h b/apps/plugins/mpegplayer/disk_buf.h new file mode 100644 index 0000000000..90e72fac2f --- /dev/null +++ b/apps/plugins/mpegplayer/disk_buf.h @@ -0,0 +1,132 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * AV disk buffer declarations + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 + +#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; +}; + +/* This object is an extension of the stream manager and handles some + * playback events as well as buffering */ +struct disk_buf +{ + struct thread_entry *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 NOCACHEBSS_ATTR; + +static 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; +} + + +bool disk_buf_init(void); +void disk_buf_exit(void); + +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_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/header.c b/apps/plugins/mpegplayer/header.c index 52a301f8d8..9e6e6de03d 100644 --- a/apps/plugins/mpegplayer/header.c +++ b/apps/plugins/mpegplayer/header.c @@ -92,12 +92,12 @@ void mpeg2_header_state_init (mpeg2dec_t * mpeg2dec) { if (mpeg2dec->sequence.width != (unsigned)-1) { - int i; - 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++) { @@ -109,6 +109,7 @@ void mpeg2_header_state_init (mpeg2dec_t * mpeg2dec) if (mpeg2dec->convert_start) { + int i; for (i = 0; i < 3; i++) { mpeg2_free(mpeg2dec->yuv_buf[i][0]); @@ -121,6 +122,7 @@ void mpeg2_header_state_init (mpeg2dec_t * mpeg2dec) { mpeg2_free(mpeg2dec->decoder.convert_id); } +#endif } mpeg2dec->decoder.coding_type = I_TYPE; diff --git a/apps/plugins/mpegplayer/mpeg2.h b/apps/plugins/mpegplayer/mpeg2.h index 605a3538b0..824454feab 100644 --- a/apps/plugins/mpegplayer/mpeg2.h +++ b/apps/plugins/mpegplayer/mpeg2.h @@ -189,7 +189,13 @@ typedef enum } 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/mpeg_alloc.h b/apps/plugins/mpegplayer/mpeg_alloc.h new file mode 100644 index 0000000000..9a08fd5308 --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_alloc.h @@ -0,0 +1,12 @@ +#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_linkedlist.c b/apps/plugins/mpegplayer/mpeg_linkedlist.c new file mode 100644 index 0000000000..74cb2cb34a --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_linkedlist.c @@ -0,0 +1,149 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Linked list API definitions + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 "mpeg_linkedlist.h" + +/* Initialize a master list head */ +void list_initialize(struct list_item *master_list_head) +{ + master_list_head->prev = master_list_head->next = NULL; +} + +/* Are there items after the head item? */ +bool list_is_empty(struct list_item *head_item) +{ + return head_item->next == NULL; +} + +/* Does the item belong to a list? */ +bool list_is_item_listed(struct list_item *item) +{ + return item->prev != NULL; +} + +/* Is the item a member in a particular list? */ +bool list_is_member(struct list_item *master_list_head, + struct list_item *item) +{ + if (item != master_list_head && item->prev != NULL) + { + struct list_item *curr = master_list_head->next; + + while (curr != NULL) + { + if (item != curr) + { + curr = curr->next; + continue; + } + + return true; + } + } + + return false; +} + +/* Remove an item from a list - no head item needed */ +void list_remove_item(struct list_item *item) +{ + if (item->prev == NULL) + { + /* Not in a list - no change - could be the master list head + * as well which cannot be removed */ + return; + } + + item->prev->next = item->next; + + if (item->next != NULL) + { + /* Not last item */ + item->next->prev = item->prev; + } + + /* Mark as not in a list */ + item->prev = NULL; +} + +/* Add a list item after the base item */ +void list_add_item(struct list_item *head_item, + struct list_item *item) +{ + if (item->prev != NULL) + { + /* Already in a list - no change */ + DEBUGF("list_add_item: item already in a list\n"); + return; + } + + if (item == head_item) + { + /* Cannot add the item to itself */ + DEBUGF("list_add_item: item == head_item\n"); + return; + } + + /* Insert first */ + item->prev = head_item; + item->next = head_item->next; + + if (head_item->next != NULL) + { + /* Not first item */ + head_item->next->prev = item; + } + + head_item->next = item; +} + +/* Clear list items after the head item */ +void list_clear_all(struct list_item *head_item) +{ + struct list_item *curr = head_item->next; + + while (curr != NULL) + { + list_remove_item(curr); + curr = head_item->next; + } +} + +/* Enumerate all items after the head item - passing each item in turn + * to the callback as well as the data value. The current item may be + * safely removed. Items added after the current position will be enumated + * but not ones added before it. The callback may return false to stop + * the enumeration. */ +void list_enum_items(struct list_item *head_item, + list_enum_callback_t callback, + intptr_t data) +{ + struct list_item *next = head_item->next; + + while (next != NULL) + { + struct list_item *curr = next; + next = curr->next; + if (!callback(curr, data)) + break; + } +} diff --git a/apps/plugins/mpegplayer/mpeg_linkedlist.h b/apps/plugins/mpegplayer/mpeg_linkedlist.h new file mode 100644 index 0000000000..17123cc9ca --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_linkedlist.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Linked list API declarations + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef MPEG_LINKEDLIST_H +#define MPEG_LINKEDLIST_H + +struct list_item +{ + struct list_item *prev; /* previous item in list */ + struct list_item *next; /* next item in list */ +}; + +/* Utility macros to help get the actual structure pointer back */ +#define OFFSETOF(type, membername) ((off_t)&((type *)0)->membername) +#define TYPE_FROM_MEMBER(type, memberptr, membername) \ + ((type *)((intptr_t)(memberptr) - OFFSETOF(type, membername))) + +/* Initialize a master list head */ +void list_initialize(struct list_item *master_list_head); + +/* Are there items after the head item? */ +bool list_is_empty(struct list_item *head_item); + +/* Does the item belong to a list? */ +bool list_is_item_listed(struct list_item *item); + +/* Is the item a member in a particular list? */ +bool list_is_member(struct list_item *master_list_head, + struct list_item *item); + +/* Remove an item from a list - no head item needed */ +void list_remove_item(struct list_item *item); + +/* Add a list item after the base item */ +void list_add_item(struct list_item *head_item, + struct list_item *item); + +/* Clear list items after the head item */ +void list_clear_all(struct list_item *head_item); + +/* Enumerate all items after the head item - passing each item in turn + * to the callback as well as the data value. The current item may be + * safely removed. Items added after the current position will be enumated + * but not ones added before it. The callback may return false to stop + * the enumeration. */ +typedef bool (*list_enum_callback_t)(struct list_item *item, intptr_t data); + +void list_enum_items(struct list_item *head_item, + list_enum_callback_t callback, + intptr_t data); + +#endif /* MPEG_LINKEDLIST_H */ diff --git a/apps/plugins/mpegplayer/mpeg_misc.c b/apps/plugins/mpegplayer/mpeg_misc.c new file mode 100644 index 0000000000..f5ecb6d6c8 --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_misc.c @@ -0,0 +1,96 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Miscellaneous helper API definitions + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 **/ + +/* 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 */ +} diff --git a/apps/plugins/mpegplayer/mpeg_misc.h b/apps/plugins/mpegplayer/mpeg_misc.h new file mode 100644 index 0000000000..2da9c2e313 --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_misc.h @@ -0,0 +1,206 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Miscellaneous helper API declarations + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 + +/* 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; /* */ +}; + +#define SSCAN_REVERSE (-1) +#define SSCAN_FORWARD 1 + +/* 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); + +/** Audio helpers **/ +static inline int32_t clip_sample(int32_t sample) +{ + if ((int16_t)sample != sample) + sample = 0x7fff ^ (sample >> 31); + + return sample; +} + +/** 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); + +#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..c996f9540d --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_parser.c @@ -0,0 +1,1182 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Parser for MPEG streams + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 NOCACHEBSS_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 = LONG_MIN; + str->hdr.win_left = LONG_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(4, &p, NULL, NULL); + + 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(4, &p, NULL, NULL); + + 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(35, &p, NULL, NULL); + + 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 < 6) + { + /* 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 = 7; + ssize_t margin = sk->margin; + + /* Skip stuffing_byte */ + while (h[l - 1] == 0xff && ++l <= 23) + --margin; + + if ((h[l - 1] & 0xc0) == 0x40) + { + /* Skip STD_buffer_scale and STD_buffer_size */ + margin -= 2; + l += 2; + } + + if (margin >= 4) + { + /* header points to the mpeg1 pes header */ + h += l; + + if ((h[-1] & 0xe0) == 0x20) + { + sk->data = (h + 4) - p; + return read_pts(h, -1); + } + } + } + /* 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 void init_times(struct stream *str) +{ + int i; + struct stream tmp_str; + const ssize_t filesize = disk_buf_filesize(); + const ssize_t max_probe = MIN(512*1024, filesize); + + /* Simply find the first earliest timestamp - this will be the one + * used when streaming anyway */ + DEBUGF("Finding start_pts: 0x%02x\n", str->id); + + tmp_str.id = str->id; + tmp_str.hdr.pos = 0; + tmp_str.hdr.limit = max_probe; + + str->start_pts = INVALID_TIMESTAMP; + + /* Probe many for video because of B-frames */ + for (i = STREAM_IS_VIDEO(str->id) ? 5 : 1; i > 0;) + { + switch (parser_get_next_data(&tmp_str, STREAM_PM_RANDOM_ACCESS)) + { + case STREAM_DATA_END: + break; + case STREAM_OK: + if (tmp_str.pkt_flags & PKT_HAS_TS) + { + if (tmp_str.pts < str->start_pts) + str->start_pts = tmp_str.pts; + i--; /* Decrement timestamp counter */ + } + continue; + } + + break; + } + + 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->end_pts = INVALID_TIMESTAMP; + + if (str->start_pts != INVALID_TIMESTAMP) + { + 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); + } + } + + /* End must be greater than start */ + if (str->start_pts >= str->end_pts) + { + str->start_pts = INVALID_TIMESTAMP; + 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; + + /* 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, + pos_left, pos_new, 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) + { + /* 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) + pos_left; + /* Point is ahead of us - fudge estimate a bit high */ + pos_new = muldiv_uint32(11, pos_new - pos_left, 10) + + pos_left; + + 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, pos_left, pos_new, 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) + pos_left; + /* Overshot the seek point - fudge estimate a bit low */ + pos_new = muldiv_uint32(9, pos_new - pos_left, 10) + pos_left; + + 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, pos_left, pos_new, 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, pos_left, pos_new, 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, pos_left, pos_new, 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", + 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 bool prepare_image(uint32_t time) +{ + struct stream_scan sk; + int tries; + int result; + + 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, + 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; +} + +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? */ + 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; + } + 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; + 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; +} + +intptr_t parser_send_video_msg(long id, intptr_t data) +{ + intptr_t retval = 0; + + if (video_str.thread != NULL && disk_buf.in_file >= 0) + { + /* Hook certain messages since they involve multiple operations + * behind the scenes */ + switch (id) + { + case VIDEO_DISPLAY_SHOW: + if (data != 0 && stream_status() != STREAM_PLAYING) + { /* Only prepare image if showing and not playing */ + prepare_image(str_parser.last_seek_time); + } + break; + + case VIDEO_PRINT_FRAME: + case VIDEO_PRINT_THUMBNAIL: + if (stream_status() == STREAM_PLAYING) + break; /* Prepare image if not playing */ + + if (!prepare_image(str_parser.last_seek_time)) + return false; /* Preparation failed */ + + /* Image ready - pass message to video thread */ + break; + } + + retval = str_send_msg(&video_str, id, data); + } + + return retval; +} + +/* 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 */ + 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 transport stream */ + str_parser.format = STREAM_FMT_MPEG_TS; + 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. */ + return STREAM_UNSUPPORTED; + } + + if (str_parser.format == STREAM_FMT_MPEG_TS) + { + /* Initalize start_pts and end_pts with the length (in 45kHz units) of + * the movie. INVALID_TIMESTAMP if the time could not be determined */ + init_times(&audio_str); + init_times(&video_str); + + if (video_str.start_pts == INVALID_TIMESTAMP) + { + /* Must have video at least */ + return STREAM_UNSUPPORTED; + } + + str_parser.flags |= STREAMF_CAN_SEEK; + + if (audio_str.start_pts != INVALID_TIMESTAMP) + { + /* 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); + + /* Audio will be part of playback pool */ + stream_add_stream(&audio_str); + } + else + { + /* No audio stream - use video only */ + str_parser.start_pts = video_str.start_pts; + str_parser.end_pts = video_str.end_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 index 6cd5f7b186..7336507aaa 100644 --- a/apps/plugins/mpegplayer/mpeg_settings.c +++ b/apps/plugins/mpegplayer/mpeg_settings.c @@ -2,27 +2,16 @@ #include "lib/configfile.h" #include "lib/oldmenuapi.h" +#include "mpegplayer.h" #include "mpeg_settings.h" -extern struct plugin_api* rb; - struct mpeg_settings settings; -ssize_t seek_PTS(int in_file, int startTime, int accept_button); -void display_thumb(int in_file); - -#ifndef HAVE_LCD_COLOR -void gray_show(bool enable); -#endif - #define SETTINGS_VERSION 2 #define SETTINGS_MIN_VERSION 1 #define SETTINGS_FILENAME "mpegplayer.cfg" -enum slider_state_t {state0, state1, state2, - state3, state4, state5} slider_state; - -volatile long thumbDelayTimer; +#define THUMB_DELAY (75*HZ/100) /* button definitions */ #if (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ @@ -30,16 +19,16 @@ volatile long thumbDelayTimer; #define MPEG_SELECT BUTTON_ON #define MPEG_RIGHT BUTTON_RIGHT #define MPEG_LEFT BUTTON_LEFT -#define MPEG_SCROLL_DOWN BUTTON_UP -#define MPEG_SCROLL_UP BUTTON_DOWN +#define MPEG_UP BUTTON_UP +#define MPEG_DOWN BUTTON_DOWN #define MPEG_EXIT BUTTON_OFF #elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) #define MPEG_SELECT BUTTON_PLAY #define MPEG_RIGHT BUTTON_RIGHT #define MPEG_LEFT BUTTON_LEFT -#define MPEG_SCROLL_DOWN BUTTON_UP -#define MPEG_SCROLL_UP BUTTON_DOWN +#define MPEG_UP BUTTON_UP +#define MPEG_DOWN BUTTON_DOWN #define MPEG_EXIT BUTTON_POWER #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ @@ -48,8 +37,8 @@ volatile long thumbDelayTimer; #define MPEG_SELECT BUTTON_SELECT #define MPEG_RIGHT BUTTON_RIGHT #define MPEG_LEFT BUTTON_LEFT -#define MPEG_SCROLL_DOWN BUTTON_SCROLL_FWD -#define MPEG_SCROLL_UP BUTTON_SCROLL_BACK +#define MPEG_UP BUTTON_SCROLL_FWD +#define MPEG_DOWN BUTTON_SCROLL_BACK #define MPEG_EXIT BUTTON_MENU #elif CONFIG_KEYPAD == GIGABEAT_PAD @@ -64,10 +53,10 @@ volatile long thumbDelayTimer; #elif CONFIG_KEYPAD == IRIVER_H10_PAD #define MPEG_SELECT BUTTON_PLAY -#define MPEG_SCROLL_UP BUTTON_SCROLL_UP -#define MPEG_SCROLL_DOWN BUTTON_SCROLL_DOWN #define MPEG_LEFT BUTTON_LEFT #define MPEG_RIGHT BUTTON_RIGHT +#define MPEG_UP BUTTON_SCROLL_UP +#define MPEG_DOWN BUTTON_SCROLL_DOWN #define MPEG_EXIT BUTTON_POWER #elif (CONFIG_KEYPAD == SANSA_E200_PAD) @@ -136,10 +125,10 @@ static void display_options(void) int options_quit = 0; static const struct menu_item items[] = { -#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) +#if MPEG_OPTION_DITHERING_ENABLED [MPEG_OPTION_DITHERING] = { "Dithering", NULL }, -#endif /* #ifdef TOSHIBA_GIGABEAT_F */ +#endif [MPEG_OPTION_DISPLAY_FPS] = { "Display FPS", NULL }, [MPEG_OPTION_LIMIT_FPS] = @@ -159,7 +148,7 @@ static void display_options(void) switch (result) { -#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) +#if MPEG_OPTION_DITHERING_ENABLED case MPEG_OPTION_DITHERING: result = (settings.displayoptions & LCD_YUV_DITHER) ? 1 : 0; rb->set_option("Dithering", &result, INT, noyes, 2, NULL); @@ -167,7 +156,7 @@ static void display_options(void) | ((result != 0) ? LCD_YUV_DITHER : 0); rb->lcd_yuv_set_options(settings.displayoptions); break; -#endif /* #ifdef TOSHIBA_GIGABEAT_F */ +#endif /* MPEG_OPTION_DITHERING_ENABLED */ case MPEG_OPTION_DISPLAY_FPS: rb->set_option("Display FPS",&settings.showfps,INT, noyes, 2, NULL); @@ -189,168 +178,343 @@ static void display_options(void) menu_exit(menu_id); } -void draw_slider(int slider_ypos, int max_val, int current_val) +static void show_loading(struct vo_rect *rc) { - int slider_margin = LCD_WIDTH*12/100; /* 12% */ - int slider_width = LCD_WIDTH-(slider_margin*2); - char resume_str[32]; + int oldmode; +#ifndef HAVE_LCD_COLOR + stream_gray_show(false); +#endif + oldmode = rb->lcd_get_drawmode(); + rb->lcd_set_drawmode(DRMODE_BG | DRMODE_INVERSEVID); + rb->lcd_fillrect(rc->l-1, rc->t-1, rc->r - rc->l + 2, rc->b - rc->t + 2); + rb->lcd_set_drawmode(oldmode); + rb->splash(0, "Loading..."); +} + +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); + rb->lcd_getstringsize(str, NULL, &text_h); + text_y = SLIDER_Y - SLIDER_TEXTMARGIN - text_h; + + if (rc == NULL) + { + int oldmode = rb->lcd_get_drawmode(); + rb->lcd_set_drawmode(DRMODE_BG | DRMODE_INVERSEVID); + rb->lcd_fillrect(SLIDER_X, text_y, SLIDER_WIDTH, + LCD_HEIGHT - SLIDER_BMARGIN - text_y + - SLIDER_TMARGIN); + rb->lcd_set_drawmode(oldmode); + + rb->lcd_putsxy(SLIDER_X, text_y, str); + + /* Put duration on right */ + ts_to_hms(range, &hms); + hms_format(str, sizeof(str), &hms); + rb->lcd_getstringsize(str, &text_w, NULL); + + rb->lcd_putsxy(SLIDER_X + SLIDER_WIDTH - text_w, text_y, str); + + /* Draw slider */ + rb->lcd_drawrect(SLIDER_X, SLIDER_Y, SLIDER_WIDTH, SLIDER_HEIGHT); + rb->lcd_fillrect(SLIDER_X, SLIDER_Y, + muldiv_uint32(pos, SLIDER_WIDTH, range), + SLIDER_HEIGHT); + + /* Update screen */ + rb->lcd_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; + } +} + +bool display_thumb_image(const struct vo_rect *rc) +{ + if (!stream_display_thumb(rc)) + { + rb->splash(0, "Frame not available"); + return false; + } + +#ifdef HAVE_LCD_COLOR + /* Draw a raised border around the frame */ + int oldcolor = rb->lcd_get_foreground(); + rb->lcd_set_foreground(LCD_LIGHTGRAY); + + rb->lcd_hline(rc->l-1, rc->r-1, rc->t-1); + rb->lcd_vline(rc->l-1, rc->t, rc->b-1); - /* max_val and current_val are in half minutes - determine value .0 or .5 to display */ - int max_hol = max_val/2; - int max_rem = (max_val-(max_hol*2))*5; - int current_hol = current_val/2; - int current_rem = (current_val-(current_hol*2))*5; - - rb->snprintf(resume_str, sizeof(resume_str), "0.0"); - rb->lcd_putsxy(slider_margin, slider_ypos, resume_str); - - rb->snprintf(resume_str, sizeof(resume_str), "%u.%u", max_hol, max_rem); - rb->lcd_putsxy(LCD_WIDTH-slider_margin-25, slider_ypos, resume_str); - - rb->lcd_drawrect(slider_margin, slider_ypos+17, slider_width, 8); - rb->lcd_fillrect(slider_margin, slider_ypos+17, - current_val*slider_width/max_val, 8); - - rb->snprintf(resume_str, sizeof(resume_str), "%u.%u", current_hol, - current_rem); - rb->lcd_putsxy(slider_margin+(current_val*slider_width/max_val)-16, - slider_ypos+29, resume_str); - - rb->lcd_update_rect(0, slider_ypos, LCD_WIDTH, LCD_HEIGHT-slider_ypos); + rb->lcd_set_foreground(LCD_DARKGRAY); + + rb->lcd_hline(rc->l-1, rc->r, rc->b); + rb->lcd_vline(rc->r, rc->t-1, rc->b); + + rb->lcd_set_foreground(oldcolor); + + rb->lcd_update_rect(rc->l-1, rc->t-1, rc->r - rc->l + 2, 1); + rb->lcd_update_rect(rc->l-1, rc->t, 1, rc->b - rc->t); + rb->lcd_update_rect(rc->l-1, rc->b, rc->r - rc->l + 2, 1); + rb->lcd_update_rect(rc->r, rc->t, 1, rc->b - rc->t); +#else + /* Just show the thumbnail */ + stream_gray_show(true); +#endif + + return true; +} + +/* Add an amount to the specified time - with saturation */ +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; } -int get_start_time(int play_time, int in_file) +int get_start_time(uint32_t duration) { - int seek_quit = 0; int button = 0; - int resume_time = settings.resume_time; - int slider_ypos = LCD_HEIGHT-45; - int seek_return; - - slider_state = state0; - thumbDelayTimer = *(rb->current_tick); + int tmo = TIMEOUT_NOBLOCK; + uint32_t resume_time = settings.resume_time; + struct vo_rect rc_vid, rc_bound; + uint32_t aspect_vid, aspect_bound; + + enum state_enum slider_state = state0; + rb->lcd_clear_display(); rb->lcd_update(); - - while(seek_quit == 0) + + draw_slider(0, 100, &rc_bound); + rc_bound.b = rc_bound.t - SLIDER_TMARGIN; +#ifdef HAVE_LCD_COLOR + rc_bound.t = SCREEN_MARGIN; +#else + rc_bound.t = 0; + rc_bound.l = 0; + rc_bound.r = LCD_WIDTH; +#endif + + 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)) { - button = rb->button_get(false); + /* 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; + } + +#if !defined (HAVE_LCD_COLOR) +#if LCD_PIXELFORMAT == VERTICAL_PACKING + rc_bound.b &= ~7; /* Align bottom edge */ +#endif +#endif + + /* 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: %ld.%02ld\n", aspect_bound >> 16, + 100*(aspect_bound & 0xffff) >> 16); + + aspect_vid = (rc_vid.r << 16) / rc_vid.b; + + DEBUGF("aspect_vid: %ld.%02ld\n", aspect_vid >> 16, + 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 + /* Set gray overlay to the bounding rectangle */ + stream_set_gray_rect(&rc_bound); +#endif + + while(slider_state < state9) + { + button = tmo == TIMEOUT_BLOCK ? + rb->button_get(true) : rb->button_get_w_tmo(tmo); + switch (button) { -#if (CONFIG_KEYPAD == GIGABEAT_PAD) || \ - (CONFIG_KEYPAD == SANSA_E200_PAD) || \ - (CONFIG_KEYPAD == SANSA_C200_PAD) - case MPEG_DOWN: - case MPEG_DOWN | BUTTON_REPEAT: - if ((resume_time -= 20) < 0) - resume_time = 0; - slider_state = state0; - thumbDelayTimer = *(rb->current_tick); - break; - case MPEG_UP: - case MPEG_UP | BUTTON_REPEAT: - if ((resume_time += 20) > play_time) - resume_time = play_time; - slider_state = state0; - thumbDelayTimer = *(rb->current_tick); - break; + case BUTTON_NONE: + break; + + /* Coarse (1 minute) control */ + case MPEG_DOWN: + case MPEG_DOWN | BUTTON_REPEAT: + resume_time = increment_time(resume_time, -60*TS_SECOND, duration); + slider_state = state0; + break; + + case MPEG_UP: + case MPEG_UP | BUTTON_REPEAT: + resume_time = increment_time(resume_time, 60*TS_SECOND, duration); + slider_state = state0; + break; + + /* Fine (1 second) control */ + case MPEG_LEFT: + case MPEG_LEFT | BUTTON_REPEAT: +#ifdef MPEG_SCROLL_UP + case MPEG_SCROLL_UP: + case MPEG_SCROLL_UP | BUTTON_REPEAT: #endif - case MPEG_LEFT: - case MPEG_LEFT | BUTTON_REPEAT: - case MPEG_SCROLL_UP: - case MPEG_SCROLL_UP | BUTTON_REPEAT: - if (--resume_time < 0) - resume_time = 0; - slider_state = state0; - thumbDelayTimer = *(rb->current_tick); - break; - case MPEG_RIGHT: - case MPEG_RIGHT | BUTTON_REPEAT: - case MPEG_SCROLL_DOWN: - case MPEG_SCROLL_DOWN | BUTTON_REPEAT: - if (++resume_time > play_time) - resume_time = play_time; - slider_state = state0; - thumbDelayTimer = *(rb->current_tick); - break; - case MPEG_SELECT: - settings.resume_time = resume_time; - case MPEG_EXIT: - seek_quit = 1; - break; - default: - if (rb->default_event_handler(button) == SYS_USB_CONNECTED) - seek_quit = 1; - break; + resume_time = increment_time(resume_time, -TS_SECOND, duration); + slider_state = state0; + break; + + case MPEG_RIGHT: + case MPEG_RIGHT | BUTTON_REPEAT: +#ifdef MPEG_SCROLL_DOWN + case MPEG_SCROLL_DOWN: + case MPEG_SCROLL_DOWN | BUTTON_REPEAT: +#endif + resume_time = increment_time(resume_time, TS_SECOND, duration); + slider_state = state0; + break; + + case MPEG_SELECT: + settings.resume_time = resume_time; + case MPEG_EXIT: + slider_state = state9; + break; + + case SYS_USB_CONNECTED: + slider_state = state9; +#ifndef HAVE_LCD_COLOR + stream_gray_show(false); +#endif + cancel_cpu_boost(); + default: + rb->default_event_handler(button); + rb->yield(); + continue; } - - rb->yield(); - + switch(slider_state) { - case state0: - rb->lcd_clear_display(); - rb->lcd_update(); -#ifdef HAVE_LCD_COLOR - if (resume_time > 0) - rb->splash(0, "Loading..."); -#endif - slider_state = state1; - break; - case state1: - if (*(rb->current_tick) - thumbDelayTimer > 75) - slider_state = state2; - if (resume_time == 0) - { - seek_return = 0; - slider_state = state5; - } - draw_slider(slider_ypos, play_time, resume_time); - break; - case state2: - if ( (seek_return = seek_PTS(in_file, resume_time, 1)) >= 0) - slider_state = state3; - else if (seek_return == -101) - { - slider_state = state0; - thumbDelayTimer = *(rb->current_tick); - } - else - slider_state = state4; - break; - case state3: - display_thumb(in_file); - draw_slider(slider_ypos, play_time, resume_time); - slider_state = state4; - break; - case state4: - draw_slider(slider_ypos, play_time, resume_time); - slider_state = state5; - break; - case state5: - break; + case state0: + trigger_cpu_boost(); + 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; + case state2: + case state9: + cancel_cpu_boost(); + tmo = TIMEOUT_BLOCK; + default: + break; } } +#ifndef HAVE_LCD_COLOR + /* Restore gray overlay dimensions */ + stream_gray_show(false); + rc_bound.b = LCD_HEIGHT; + stream_set_gray_rect(&rc_bound); +#endif + + cancel_cpu_boost(); + return button; } -enum mpeg_start_id mpeg_start_menu(int play_time, int in_file) +enum mpeg_start_id mpeg_start_menu(uint32_t duration) { int menu_id; int result = 0; int menu_quit = 0; - + /* add the resume time to the menu display */ char resume_str[32]; - int time_hol = (int)(settings.resume_time/2); - int time_rem = ((settings.resume_time%2)==0) ? 0 : 5; + char hms_str[32]; + struct hms hms; + + ts_to_hms(settings.resume_time, &hms); + hms_format(hms_str, sizeof(hms_str), &hms); if (settings.enable_start_menu == 0) { - rb->snprintf(resume_str, sizeof(resume_str), - "Yes (min): %d.%d", time_hol, time_rem); + rb->snprintf(resume_str, sizeof(resume_str), "Yes: %s", hms_str); struct opt_items resume_no_yes[2] = { @@ -369,11 +533,10 @@ enum mpeg_start_id mpeg_start_menu(int play_time, int in_file) return MPEG_START_RESTART; } else - return MPEG_START_RESUME; + return MPEG_START_RESUME; } - rb->snprintf(resume_str, sizeof(resume_str), - "Resume time (min): %d.%d", time_hol, time_rem); + rb->snprintf(resume_str, sizeof(resume_str), "Resume at: %s", hms_str); struct menu_item items[] = { @@ -382,7 +545,7 @@ enum mpeg_start_id mpeg_start_menu(int play_time, int in_file) [MPEG_START_RESUME] = { resume_str, NULL }, [MPEG_START_SEEK] = - { "Set start time (min)", NULL }, + { "Set start time", NULL }, [MPEG_START_QUIT] = { "Quit mpegplayer", NULL }, }; @@ -390,9 +553,9 @@ enum mpeg_start_id mpeg_start_menu(int play_time, int in_file) menu_id = menu_init(rb, items, sizeof(items) / sizeof(*items), NULL, NULL, NULL, NULL); - + rb->button_clear_queue(); - + while(menu_quit == 0) { result = menu_show(menu_id); @@ -407,15 +570,19 @@ enum mpeg_start_id mpeg_start_menu(int play_time, int in_file) menu_quit = 1; break; case MPEG_START_SEEK: -#ifndef HAVE_LCD_COLOR - gray_show(true); -#endif - if (get_start_time(play_time, in_file) == MPEG_SELECT) + { + if (!stream_can_seek()) + { + rb->splash(HZ, "Unavailable"); + break; + } + + bool vis = stream_show_vo(false); + if (get_start_time(duration) == MPEG_SELECT) menu_quit = 1; -#ifndef HAVE_LCD_COLOR - gray_show(false); -#endif + stream_show_vo(vis); break; + } case MPEG_START_QUIT: menu_quit = 1; break; @@ -428,13 +595,15 @@ enum mpeg_start_id mpeg_start_menu(int play_time, int in_file) menu_exit(menu_id); + rb->lcd_clear_display(); + rb->lcd_update(); + return result; } void clear_resume_count(void) { - configfile_save(SETTINGS_FILENAME, config, - sizeof(config)/sizeof(*config), + configfile_save(SETTINGS_FILENAME, config, ARRAYLEN(config), SETTINGS_VERSION); settings.resume_count = 0; @@ -514,7 +683,7 @@ void init_settings(const char* filename) settings.skipframes = 1; /* Skip frames */ settings.enable_start_menu = 1; /* Enable start menu */ settings.resume_count = -1; -#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) +#if MPEG_OPTION_DITHERING_ENABLED settings.displayoptions = 0; /* No visual effects */ #endif @@ -530,7 +699,7 @@ void init_settings(const char* filename) SETTINGS_VERSION); } -#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) +#if MPEG_OPTION_DITHERING_ENABLED if ((settings.displayoptions = configfile_get_value(SETTINGS_FILENAME, "Display options")) < 0) { @@ -546,7 +715,7 @@ void init_settings(const char* filename) configfile_update_entry(SETTINGS_FILENAME, "Resume count", 0); } - rb->strcpy(settings.resume_filename, filename); + rb->snprintf(settings.resume_filename, MAX_PATH, "%s", filename); /* get the resume time for the current mpeg if it exist */ if ((settings.resume_time = configfile_get_value @@ -558,11 +727,11 @@ void init_settings(const char* filename) void save_settings(void) { - configfile_update_entry(SETTINGS_FILENAME, "Show FPS", + configfile_update_entry(SETTINGS_FILENAME, "Show FPS", settings.showfps); - configfile_update_entry(SETTINGS_FILENAME, "Limit FPS", + configfile_update_entry(SETTINGS_FILENAME, "Limit FPS", settings.limitfps); - configfile_update_entry(SETTINGS_FILENAME, "Skip frames", + configfile_update_entry(SETTINGS_FILENAME, "Skip frames", settings.skipframes); configfile_update_entry(SETTINGS_FILENAME, "Enable start menu", settings.enable_start_menu); @@ -575,8 +744,8 @@ void save_settings(void) ++settings.resume_count); } -#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) - configfile_update_entry(SETTINGS_FILENAME, "Display options", +#if MPEG_OPTION_DITHERING_ENABLED + configfile_update_entry(SETTINGS_FILENAME, "Display options", settings.displayoptions); #endif } diff --git a/apps/plugins/mpegplayer/mpeg_settings.h b/apps/plugins/mpegplayer/mpeg_settings.h index 613a345307..3186c73f2c 100644 --- a/apps/plugins/mpegplayer/mpeg_settings.h +++ b/apps/plugins/mpegplayer/mpeg_settings.h @@ -1,9 +1,17 @@ #include "plugin.h" +#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) +#define MPEG_OPTION_DITHERING_ENABLED 1 +#endif + +#ifndef MPEG_OPTION_DITHERING_ENABLED +#define MPEG_OPTION_DITHERING_ENABLED 0 +#endif + enum mpeg_option_id { -#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) +#if MPEG_OPTION_DITHERING_ENABLED MPEG_OPTION_DITHERING, #endif MPEG_OPTION_DISPLAY_FPS, @@ -34,16 +42,16 @@ struct mpeg_settings { int enable_start_menu; /* flag to enable/disable start menu */ int resume_count; /* total # of resumes in config file */ int resume_time; /* resume time for current mpeg (in half minutes) */ - char resume_filename[128]; /* filename of current mpeg */ -#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) + char resume_filename[MAX_PATH]; /* filename of current mpeg */ +#if MPEG_OPTION_DITHERING_ENABLED int displayoptions; #endif }; extern struct mpeg_settings settings; -int get_start_time(int play_time, int in_file); -enum mpeg_start_id mpeg_start_menu(int play_time, int in_file); +int get_start_time(uint32_t duration); +enum mpeg_start_id mpeg_start_menu(uint32_t duration); enum mpeg_menu_id mpeg_menu(void); void init_settings(const char* filename); void save_settings(void); diff --git a/apps/plugins/mpegplayer/mpeg_stream.h b/apps/plugins/mpegplayer/mpeg_stream.h new file mode 100644 index 0000000000..0c850f072d --- /dev/null +++ b/apps/plugins/mpegplayer/mpeg_stream.h @@ -0,0 +1,120 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Stream definitions for MPEG + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 index eb904ed3c1..03ec5ba821 100644 --- a/apps/plugins/mpegplayer/mpegplayer.c +++ b/apps/plugins/mpegplayer/mpegplayer.c @@ -1,110 +1,110 @@ -/* - * mpegplayer.c - based on : - * - mpeg2dec.c - * - m2psd.c (http://www.brouhaha.com/~eric/software/m2psd/) +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ * - * Copyright (C) 2000-2003 Michel Lespinasse - * Copyright (C) 1999-2000 Aaron Holtzman + * mpegplayer main entrypoint and UI implementation * - * m2psd: MPEG 2 Program Stream Demultiplexer - * Copyright (C) 2003 Eric Smith + * Copyright (c) 2007 Michael Sevakis * - * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. - * See http://libmpeg2.sourceforge.net/ for updates. + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. * - * 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. + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. * - * 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 - */ - -/* - -NOTES: - -mpegplayer is structured as follows: - -1) Video thread (running on the COP for PortalPlayer targets). -2) Audio thread (running on the main CPU to maintain consistency with - the audio FIQ hander on PP). -3) The main thread which takes care of buffering. - -Using the main thread for buffering wastes the 8KB main stack which is -in IRAM. However, 8KB is not enough for the audio thread to run (it -needs somewhere between 8KB and 9KB), so we create a new thread in -order to`give it a larger stack and steal the core codec thread's -stack (9KB of precious IRAM). - -The button loop (and hence pause/resume, main menu and, in the future, -seeking) is placed in the audio thread. This keeps it on the main CPU -in PP targets and also allows buffering to continue in the background -whilst the main thread is filling the buffer. - -A/V sync is not yet implemented but is planned to be achieved by -syncing the master clock with the audio, and then (as is currently -implemented), syncing video with the master clock. This can happen in -the audio thread, along with resyncing after pause. - -Seeking should probably happen in the main thread, as that's where the -buffering happens. - -On PortalPlayer targets, the main CPU is not being fully utilised - -the bottleneck is the video decoding on the COP. One way to improve -that might be to move the rendering of the frames (i.e. the -lcd_yuv_blit() call) from the COP back to the main CPU. Ideas and -patches for that are welcome! - -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: - -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 - -*/ - - -#include "mpeg2dec_config.h" - + ****************************************************************************/ + +/**************************************************************************** + * 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 "gray.h" +#include "mpegplayer.h" #include "helper.h" - -#include "mpeg2.h" #include "mpeg_settings.h" +#include "mpeg2.h" #include "video_out.h" -#include "../../codecs/libmad/mad.h" +#include "stream_thread.h" +#include "stream_mgr.h" PLUGIN_HEADER PLUGIN_IRAM_DECLARE @@ -177,2390 +177,233 @@ PLUGIN_IRAM_DECLARE struct plugin_api* rb; CACHE_FUNCTION_WRAPPERS(rb); +ALIGN_BUFFER_WRAPPER(rb); -extern void *mpeg_malloc(size_t size, mpeg2_alloc_t reason); -extern size_t mpeg_alloc_init(unsigned char *buf, size_t mallocsize, - size_t libmpeg2size); - -static mpeg2dec_t * mpeg2dec NOCACHEBSS_ATTR; -static int total_offset NOCACHEBSS_ATTR = 0; -static int num_drawn NOCACHEBSS_ATTR = 0; -static int count_start NOCACHEBSS_ATTR = 0; - -/* Streams */ -typedef struct -{ - struct thread_entry *thread; /* Stream's thread */ - int status; /* Current stream status */ - struct queue_event ev; /* Event sent to steam */ - int have_msg; /* 1=event pending */ - int replied; /* 1=replied to last event */ - int reply; /* reply value */ - struct mutex msg_lock; /* serialization for event senders */ - uint8_t* curr_packet; /* Current stream packet beginning */ - uint8_t* curr_packet_end; /* Current stream packet end */ - - uint8_t* prev_packet; /* Previous stream packet beginning */ - size_t prev_packet_length; /* Lenth of previous packet */ - size_t buffer_remaining; /* How much data is left in the buffer */ - uint32_t curr_pts; /* Current presentation timestamp */ - uint32_t curr_time; /* Current time in samples */ - uint32_t tagged; /* curr_pts is valid */ - - int id; -} Stream; - -static Stream audio_str IBSS_ATTR; -static Stream video_str IBSS_ATTR; - -/* Messages */ -enum -{ - STREAM_PLAY, - STREAM_PAUSE, - STREAM_QUIT -}; - -/* Status */ -enum -{ - STREAM_ERROR = -4, - STREAM_STOPPED = -3, - STREAM_TERMINATED = -2, - STREAM_DONE = -1, - STREAM_PLAYING = 0, - STREAM_PAUSED, - STREAM_BUFFERING -}; - -/* Returns true if a message is waiting */ -static inline bool str_have_msg(Stream *str) +static bool button_loop(void) { - return str->have_msg != 0; -} + bool ret = true; -/* Waits until a message is sent */ -static void str_wait_msg(Stream *str) -{ - int spin_count = 0; + rb->lcd_setfont(FONT_SYSFIXED); + rb->lcd_clear_display(); + rb->lcd_update(); - while (str->have_msg == 0) + /* Start playback at the specified starting time */ + if (stream_seek(settings.resume_time, SEEK_SET) < STREAM_OK || + (stream_show_vo(true), stream_play()) < STREAM_OK) { - if (spin_count < 100) - { - rb->yield(); - spin_count++; - continue; - } - - rb->sleep(0); - } -} - -/* Returns a message waiting or blocks until one is available - removes the - event */ -static void str_get_msg(Stream *str, struct queue_event *ev) -{ - str_wait_msg(str); - ev->id = str->ev.id; - ev->data = str->ev.data; - str->have_msg = 0; -} - -/* Peeks at the current message without blocking, returns the data but - does not remove the event */ -static bool str_look_msg(Stream *str, struct queue_event *ev) -{ - if (!str_have_msg(str)) + rb->splash(HZ*2, "Playback failed"); return false; + } - ev->id = str->ev.id; - ev->data = str->ev.data; - return true; -} - -/* Replies to the last message pulled - has no effect if last message has not - been pulled or already replied */ -static void str_reply_msg(Stream *str, int reply) -{ - if (str->replied == 1 || str->have_msg != 0) - return; - - str->reply = reply; - str->replied = 1; -} - -/* Sends a message to a stream and waits for a reply */ -static intptr_t str_send_msg(Stream *str, int id, intptr_t data) -{ - int spin_count = 0; - intptr_t reply; - -#if 0 - if (str->thread == rb->thread_get_current()) - return str->dispatch_fn(str, msg); -#endif - - /* Only one thread at a time, please */ - rb->mutex_lock(&str->msg_lock); - - str->ev.id = id; - str->ev.data = data; - str->reply = 0; - str->replied = 0; - str->have_msg = 1; - - while (str->replied == 0 && str->status != STREAM_TERMINATED) + /* Gently poll the video player for EOS and handle UI */ + while (stream_status() != STREAM_STOPPED) { - if (spin_count < 100) + int button = rb->button_get_w_tmo(HZ/2); + + switch (button) { - rb->yield(); - spin_count++; + case BUTTON_NONE: continue; - } - - rb->sleep(0); - } - - reply = str->reply; - - rb->mutex_unlock(&str->msg_lock); - - return reply; -} - -/* NOTE: Putting the following variables in IRAM cause audio corruption - on the ipod (reason unknown) -*/ -static uint8_t *disk_buf_start IBSS_ATTR; /* Start pointer */ -static uint8_t *disk_buf_end IBSS_ATTR; /* 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 */ -static uint8_t *disk_buf_tail IBSS_ATTR; /* Location of last data + 1 - filled into the buffer */ -static size_t disk_buf_size IBSS_ATTR; /* The total buffer length - including the guard - space */ -static size_t file_remaining IBSS_ATTR; - -#if NUM_CORES > 1 -/* Some stream variables are shared between cores */ -struct mutex stream_lock IBSS_ATTR; -static inline void init_stream_lock(void) - { rb->mutex_init(&stream_lock); } -static inline void lock_stream(void) - { rb->mutex_lock(&stream_lock); } -static inline void unlock_stream(void) - { rb->mutex_unlock(&stream_lock); } -#else -/* No RMW issue here */ -static inline void init_stream_lock(void) - { } -static inline void lock_stream(void) - { } -static inline void unlock_stream(void) - { } -#endif - -static int audio_sync_start IBSS_ATTR; /* If 0, the audio thread - yields waiting on the video - thread to synchronize with - the stream */ -static uint32_t audio_sync_time IBSS_ATTR; /* The time that the video - thread has reached after - synchronizing. The - audio thread now needs - to advance to this - time */ -static int video_sync_start IBSS_ATTR; /* While 0, the video thread - yields until the audio - thread has reached the - audio_sync_time */ -static int video_thumb_print IBSS_ATTR; /* If 1, the video thread is - only decoding one frame for - use in the menu. If 0, - normal operation */ -static int end_pts_time IBSS_ATTR; /* The movie end time as represented by - the maximum audio PTS tag in the - stream converted to half minutes */ -static int start_pts_time IBSS_ATTR; /* The movie start time as represented by - the first audio PTS tag in the - stream converted to half minutes */ -char *filename; /* hack for resume time storage */ - - -/* Various buffers */ -/* TODO: Can we reduce the PCM buffer size? */ -#define PCMBUFFER_SIZE ((512*1024)-PCMBUFFER_GUARD_SIZE) -#define PCMBUFFER_GUARD_SIZE (1152*4 + sizeof (struct pcm_frame_header)) -#define MPA_MAX_FRAME_SIZE 1729 /* Largest frame - MPEG1, Layer II, 384kbps, 32kHz, pad */ -#define MPABUF_SIZE (64*1024 + ALIGN_UP(MPA_MAX_FRAME_SIZE + 2*MAD_BUFFER_GUARD, 4)) -#define LIBMPEG2BUFFER_SIZE (2*1024*1024) - -/* 65536+6 is required since each PES has a 6 byte header with a 16 bit packet length field */ -#define MPEG_GUARDBUF_SIZE (65*1024) /* Keep a bit extra - excessive for now */ -#define MPEG_LOW_WATERMARK (1024*1024) - -static void pcm_playback_play_pause(bool play); - -/* libmad related functions/definitions */ -#define INPUT_CHUNK_SIZE 8192 - -struct mad_stream stream IBSS_ATTR; -struct mad_frame frame IBSS_ATTR; -struct mad_synth synth IBSS_ATTR; - -unsigned char mad_main_data[MAD_BUFFER_MDLEN]; /* 2567 bytes */ - -/* There isn't enough room for this in IRAM on PortalPlayer, but there - is for Coldfire. */ - -#ifdef CPU_COLDFIRE -static mad_fixed_t mad_frame_overlap[2][32][18] IBSS_ATTR; /* 4608 bytes */ -#else -static mad_fixed_t mad_frame_overlap[2][32][18] __attribute__((aligned(16))); /* 4608 bytes */ -#endif - -static void init_mad(void* mad_frame_overlap) -{ - rb->memset(&stream, 0, sizeof(struct mad_stream)); - rb->memset(&frame, 0, sizeof(struct mad_frame)); - rb->memset(&synth, 0, sizeof(struct mad_synth)); - - mad_stream_init(&stream); - mad_frame_init(&frame); - - /* We do this so libmad doesn't try to call codec_calloc() */ - frame.overlap = mad_frame_overlap; - - rb->memset(mad_main_data, 0, sizeof(mad_main_data)); - stream.main_data = &mad_main_data; -} - -/* MPEG related headers */ -/* 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] \r\n" \ - "eors %[x], %[x], %[b0] \r\n" \ - "ldreqb %[x], [%[a], #1] \r\n" \ - "eoreqs %[x], %[x], %[b1] \r\n" \ - "ldreqb %[x], [%[a], #2] \r\n" \ - "eoreqs %[x], %[x], %[b2] \r\n" \ - : [x]"=&r"(_x) \ - : [a]"r"(_a), \ - [b0]"i"((_b) >> 24), \ - [b1]"i"((_b) << 8 >> 24), \ - [b2]"i"((_b) << 16 >> 24) \ - ); \ - _x == 0; \ - }) -#define CMP_4_CONST(_a, _b) \ - ({ \ - int _x; \ - asm volatile ( \ - "ldrb %[x], [%[a], #0] \r\n" \ - "eors %[x], %[x], %[b0] \r\n" \ - "ldreqb %[x], [%[a], #1] \r\n" \ - "eoreqs %[x], %[x], %[b1] \r\n" \ - "ldreqb %[x], [%[a], #2] \r\n" \ - "eoreqs %[x], %[x], %[b2] \r\n" \ - "ldreqb %[x], [%[a], #3] \r\n" \ - "eoreqs %[x], %[x], %[b3] \r\n" \ - : [x]"=&r"(_x) \ - : [a]"r"(_a), \ - [b0]"i"((_b) >> 24), \ - [b1]"i"((_b) << 8 >> 24), \ - [b2]"i"((_b) << 16 >> 24), \ - [b3]"i"((_b) << 24 >> 24) \ - ); \ - _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)) == 0) -#define CMP_4_CONST(a, b) \ - ((*(uint32_t *)(a) ^ (b)) == 0) -#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) + case MPEG_VOLUP: + case MPEG_VOLUP|BUTTON_REPEAT: +#ifdef MPEG_VOLUP2 + case MPEG_VOLUP2: + case MPEG_VOLUP2|BUTTON_REPEAT: #endif - -/* 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, b1, b2, b3, b4) \ - ((uint32_t)(((p)[b0] >> 1 << 29) | \ - ((p)[b1] << 21) | \ - ((p)[b2] >> 1 << 14) | \ - ((p)[b3] << 6) | \ - ((p)[b4] >> 2 ))) - -/* This function synchronizes the mpeg stream. The function returns - true on error */ -bool sync_data_stream(uint8_t **p) -{ - for (;;) - { - while ( !CMP_4_CONST(*p, PACK_START_CODE) && (*p) < disk_buf_tail ) - (*p)++; - if ( (*p) >= disk_buf_tail ) - break; - uint8_t *p_save = (*p); - if ( ((*p)[4] & 0xc0) == 0x40 ) /* mpeg-2 */ - (*p) += 14 + ((*p)[13] & 7); - else if ( ((*p)[4] & 0xf0) == 0x20 ) /* mpeg-1 */ - (*p) += 12; - else - (*p) += 5; - if ( (*p) >= disk_buf_tail ) - break; - if ( CMP_3_CONST(*p, PACKET_START_CODE_PREFIX) ) - { - (*p) = p_save; - break; - } - else - (*p) = p_save+1; - } - - if ( (*p) >= disk_buf_tail ) - return true; - else - return false; -} - -/* This function demuxes the streams and gives the next stream data - pointer. Type 0 is normal operation. Type 1 and 2 have been added - for rapid seeks into the data stream. Type 1 and 2 ignore the - video_sync_start state (a signal to yield for refilling the - buffer). Type 1 will append more data to the buffer tail (minumal - bufer size reads that are increased only as needed). */ -static int get_next_data( Stream* str, uint8_t type ) -{ - uint8_t *p; - uint8_t *header; - int stream; - - static int mpeg1_skip_table[16] = - { 0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - - if ( (p=str->curr_packet_end) == NULL) - p = disk_buf_start; - - while (1) - { - int length, bytes; - - /* Yield for buffer filling */ - if ( (type == 0) && (str->buffer_remaining < 120*1024) && (file_remaining > 0) ) - while ( (str->buffer_remaining < 512*1024) && (file_remaining > 0) ) - rb->yield(); - - /* The packet start position (plus an arbitrary header length) - has exceeded the amount of data in the buffer */ - if ( type == 1 && (p+50) >= disk_buf_tail ) { - DEBUGF("disk buffer overflow\n"); - return 1; - } + int vol = rb->global_settings->volume; + int maxvol = rb->sound_max(SOUND_VOLUME); - /* are we at the end of file? */ - { - size_t tmp_length; - if (p < str->prev_packet) - tmp_length = (disk_buf_end - str->prev_packet) + - (p - disk_buf_start); - else - tmp_length = (p - str->prev_packet); - if (0 == str->buffer_remaining-tmp_length-str->prev_packet_length) - { - str->curr_packet_end = str->curr_packet = NULL; - break; + if (vol < maxvol) { + vol++; + rb->sound_set(SOUND_VOLUME, vol); + rb->global_settings->volume = vol; } - } - - /* wrap the disk buffer */ - if (p >= disk_buf_end) - p = disk_buf_start + (p - disk_buf_end); - - /* wrap packet header if needed */ - if ( (p+50) >= disk_buf_end ) - rb->memcpy(disk_buf_end, disk_buf_start, 50); + break; + } /* MPEG_VOLUP*: */ - /* Pack header, skip it */ - if (CMP_4_CONST(p, PACK_START_CODE)) + case MPEG_VOLDOWN: + case MPEG_VOLDOWN|BUTTON_REPEAT: +#ifdef MPEG_VOLDOWN2 + case MPEG_VOLDOWN2: + case MPEG_VOLDOWN2|BUTTON_REPEAT: +#endif { - if ((p[4] & 0xc0) == 0x40) /* mpeg-2 */ - { - p += 14 + (p[13] & 7); - } - else if ((p[4] & 0xf0) == 0x20) /* mpeg-1 */ - { - p += 12; - } - else - { - rb->splash( 30, "Weird Pack header!" ); - p += 5; + int vol = rb->global_settings->volume; + int minvol = rb->sound_min(SOUND_VOLUME); + + if (vol > minvol) { + vol--; + rb->sound_set(SOUND_VOLUME, vol); + rb->global_settings->volume = vol; } - } + break; + } /* MPEG_VOLDOWN*: */ - /* System header, parse and skip it - four bytes */ - if (CMP_4_CONST(p, SYSTEM_HEADER_START_CODE)) + case MPEG_MENU: { - int header_length; - - p += 4; /*skip start code*/ - header_length = *p++ << 8; - header_length += *p++; + int state = stream_pause(); /* save previous state */ + int result; - p += header_length; - - if ( p >= disk_buf_end ) - p = disk_buf_start + (p - disk_buf_end); - } - - /* Packet header, parse it */ - if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX)) - { - /* Problem */ - rb->splash( HZ*3, "missing packet start code prefix : %X%X at %lX", - *p, *(p+2), (long unsigned int)(p-disk_buf_start) ); + /* Hide video output */ + stream_show_vo(false); + backlight_use_settings(rb); - /* not 64bit safe - DEBUGF("end diff: %X,%X,%X,%X,%X,%X\n",(int)str->curr_packet_end, - (int)audio_str.curr_packet_end,(int)video_str.curr_packet_end, - (int)disk_buf_start,(int)disk_buf_end,(int)disk_buf_tail); - */ - - str->curr_packet_end = str->curr_packet = NULL; - break; - } + result = mpeg_menu(); - /* We retrieve basic infos */ - stream = p[3]; - length = (p[4] << 8) | p[5]; + /* The menu can change the font, so restore */ + rb->lcd_setfont(FONT_SYSFIXED); - if (stream != str->id) - { - /* End of stream ? */ - if (stream == 0xB9) + switch (result) { - str->curr_packet_end = str->curr_packet = NULL; + case MPEG_MENU_QUIT: + stream_stop(); + break; + default: + /* If not stopped, show video again */ + if (state != STREAM_STOPPED) + stream_show_vo(true); + + /* If stream was playing, restart it */ + if (state == STREAM_PLAYING) { + backlight_force_on(rb); + stream_resume(); + } break; } + break; + } /* MPEG_MENU: */ - /* It's not the packet we're looking for, skip it */ - p += length + 6; - continue; - } - - /* Ok, it's our packet */ - str->curr_packet_end = p + length+6; - header = p; - - if ((header[6] & 0xc0) == 0x80) /* mpeg2 */ + case MPEG_STOP: { - length = 9 + header[8]; - - /* header points to the mpeg2 pes header */ - if (header[7] & 0x80) - { - /* header has a pts */ - uint32_t pts = TS_FROM_HEADER(header, 9, 10, 11, 12, 13); - - if (stream >= 0xe0) - { - /* video stream - header may have a dts as well */ - uint32_t dts = (header[7] & 0x40) == 0 ? - pts : TS_FROM_HEADER(header, 14, 15, 16, 17, 18); + stream_stop(); + break; + } /* MPEG_STOP: */ - mpeg2_tag_picture (mpeg2dec, pts, dts); - } - else - { - str->curr_pts = pts; - str->tagged = 1; - } - } - } - else /* mpeg1 */ + case MPEG_PAUSE: +#ifdef MPEG_PAUSE2 + case MPEG_PAUSE2: +#endif { - int len_skip; - uint8_t * ptsbuf; - - length = 7; - - while (header[length - 1] == 0xff) - { - length++; - if (length > 23) - { - rb->splash( 30, "Too much stuffing" ); - 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) - { - /* header has a pts */ - uint32_t pts = TS_FROM_HEADER(ptsbuf, -1, 0, 1, 2, 3); - - if (stream >= 0xe0) - { - /* video stream - header may have a dts as well */ - uint32_t dts = (ptsbuf[-1] & 0xf0) != 0x30 ? - pts : TS_FROM_HEADER(ptsbuf, 4, 5, 6, 7, 18); - - mpeg2_tag_picture (mpeg2dec, pts, dts); - } - else - { - str->curr_pts = pts; - str->tagged = 1; - } + if (stream_status() == STREAM_PLAYING) { + /* Playing => Paused */ + stream_pause(); + backlight_use_settings(rb); } - } - - p += length; - bytes = 6 + (header[4] << 8) + header[5] - length; - - if (bytes > 0) - { - str->curr_packet_end = p + bytes; - - if (str->curr_packet != NULL) - { - lock_stream(); - - str->buffer_remaining -= str->prev_packet_length; - if (str->curr_packet < str->prev_packet) - str->prev_packet_length = (disk_buf_end - str->prev_packet) + - (str->curr_packet - disk_buf_start); - else - str->prev_packet_length = (str->curr_packet - str->prev_packet); - - unlock_stream(); - - str->prev_packet = str->curr_packet; + else if (stream_status() == STREAM_PAUSED) { + /* Paused => Playing */ + backlight_force_on(rb); + stream_resume(); } - str->curr_packet = p; - - if (str->curr_packet_end > disk_buf_end) - rb->memcpy(disk_buf_end, disk_buf_start, - str->curr_packet_end - disk_buf_end ); + break; + } /* MPEG_PAUSE*: */ + + case SYS_POWEROFF: + case SYS_USB_CONNECTED: + /* Stop and get the resume time before closing the file early */ + stream_stop(); + settings.resume_time = stream_get_resume_time(); + stream_close(); + ret = false; + /* Fall-through */ + default: + rb->default_event_handler(button); + break; } - break; + rb->yield(); } /* end while */ - return 0; -} -/* Our clock rate in ticks/second - this won't be a constant for long */ -#define CLOCK_RATE 44100 + rb->lcd_setfont(FONT_UI); -/* For simple lowpass filtering of sync variables */ -#define AVERAGE(var, x, count) (((var) * (count-1) + (x)) / (count)) -/* Convert 45kHz PTS/DTS ticks to our clock ticks */ -#define TS_TO_TICKS(pts) ((uint64_t)CLOCK_RATE*(pts) / 45000) -/* Convert 27MHz ticks to our clock ticks */ -#define TIME_TO_TICKS(stamp) ((uint64_t)CLOCK_RATE*(stamp) / 27000000) - -/** MPEG audio stream buffer */ -uint8_t* mpa_buffer NOCACHEBSS_ATTR; - -static bool init_mpabuf(void) -{ - mpa_buffer = mpeg_malloc(MPABUF_SIZE,-2); - return mpa_buffer != NULL; + return ret; } -#define PTS_QUEUE_LEN (1 << 5) /* 32 should be way more than sufficient - - if not, the case is handled */ -#define PTS_QUEUE_MASK (PTS_QUEUE_LEN-1) -struct pts_queue_slot +enum plugin_status plugin_start(struct plugin_api* api, void* parameter) { - uint32_t pts; /* Time stamp for packet */ - ssize_t size; /* Number of bytes left in packet */ -} pts_queue[PTS_QUEUE_LEN] __attribute__((aligned(16))); + int status = PLUGIN_ERROR; /* assume failure */ + int result; + int err; + const char *errstring; - /* This starts out wr == rd but will never be emptied to zero during - streaming again in order to support initializing the first packet's - pts value without a special case */ -static unsigned pts_queue_rd NOCACHEBSS_ATTR; -static unsigned pts_queue_wr NOCACHEBSS_ATTR; + if (parameter == NULL) { + /* No file = GTFO */ + api->splash(HZ*2, "No File"); + return PLUGIN_ERROR; + } -/* Increments the queue head postion - should be used to preincrement */ -static bool pts_queue_add_head(void) -{ - if (pts_queue_wr - pts_queue_rd >= PTS_QUEUE_LEN-1) - return false; + /* Disable all talking before initializing IRAM */ + api->talk_disable(true); - pts_queue_wr++; - return true; -} + /* Initialize IRAM - stops audio and voice as well */ + PLUGIN_IRAM_INIT(api) -/* Increments the queue tail position - leaves one slot as current */ -static bool pts_queue_remove_tail(void) -{ - if (pts_queue_wr - pts_queue_rd <= 1u) - return false; + rb = api; - pts_queue_rd++; - return true; -} +#ifdef HAVE_LCD_COLOR + rb->lcd_set_backdrop(NULL); + rb->lcd_set_foreground(LCD_WHITE); + rb->lcd_set_background(LCD_BLACK); +#endif -/* Returns the "head" at the index just behind the write index */ -static struct pts_queue_slot * pts_queue_head(void) -{ - return &pts_queue[(pts_queue_wr - 1) & PTS_QUEUE_MASK]; -} + rb->lcd_clear_display(); + rb->lcd_update(); -/* Returns a pointer to the current tail */ -static struct pts_queue_slot * pts_queue_tail(void) -{ - return &pts_queue[pts_queue_rd & PTS_QUEUE_MASK]; -} + if (stream_init() < STREAM_OK) { + DEBUGF("Could not initialize streams\n"); + } else { + rb->splash(0, "Loading..."); -/* Resets the pts queue - call when starting and seeking */ -static void pts_queue_reset(void) -{ - struct pts_queue_slot *pts; - pts_queue_rd = pts_queue_wr; - pts = pts_queue_tail(); - pts->pts = 0; - pts->size = 0; -} + init_settings((char*)parameter); -struct pcm_frame_header /* Header added to pcm data every time a decoded - mpa frame is sent out */ -{ - uint32_t size; /* size of this frame - including header */ - uint32_t time; /* timestamp for this frame - derived from PTS */ - unsigned char data[]; /* open array of audio data */ -}; + err = stream_open((char *)parameter); -#define PCMBUF_PLAY_ALL 1l /* Forces buffer to play back all data */ -#define PCMBUF_PLAY_NONE LONG_MAX /* Keeps buffer from playing any data */ -static volatile uint64_t pcmbuf_read IBSS_ATTR; -static volatile uint64_t pcmbuf_written IBSS_ATTR; -static volatile ssize_t pcmbuf_threshold IBSS_ATTR; -static struct pcm_frame_header *pcm_buffer IBSS_ATTR; -static struct pcm_frame_header *pcmbuf_end IBSS_ATTR; -static struct pcm_frame_header * volatile pcmbuf_head IBSS_ATTR; -static struct pcm_frame_header * volatile pcmbuf_tail IBSS_ATTR; + if (err >= STREAM_OK) { + /* start menu */ + rb->lcd_clear_display(); + rb->lcd_update(); + result = mpeg_start_menu(stream_get_duration()); -static volatile uint32_t samplesplayed IBSS_ATTR; /* Our base clock */ -static volatile uint32_t samplestart IBSS_ATTR; /* Clock at playback start */ -static volatile int32_t sampleadjust IBSS_ATTR; /* Clock drift adjustment */ + if (result != MPEG_START_QUIT) { + /* Turn off backlight timeout */ + /* backlight control in lib/helper.c */ + backlight_force_on(rb); -static ssize_t pcmbuf_used(void) -{ - return (ssize_t)(pcmbuf_written - pcmbuf_read); -} + /* Enter button loop and process UI */ + if (button_loop()) { + settings.resume_time = stream_get_resume_time(); + } -static bool init_pcmbuf(void) -{ - pcm_buffer = mpeg_malloc(PCMBUFFER_SIZE + PCMBUFFER_GUARD_SIZE, -2); + /* Turn on backlight timeout (revert to settings) */ + backlight_use_settings(rb); + } - if (pcm_buffer == NULL) - return false; + stream_close(); - pcmbuf_head = pcm_buffer; - pcmbuf_tail = pcm_buffer; - pcmbuf_end = SKIPBYTES(pcm_buffer, PCMBUFFER_SIZE); - pcmbuf_read = 0; - pcmbuf_written = 0; + rb->lcd_clear_display(); + rb->lcd_update(); - return true; -} + save_settings(); /* Save settings (if they have changed) */ + status = PLUGIN_OK; + } else { + DEBUGF("Could not open %s\n", (char*)parameter); + switch (err) + { + case STREAM_UNSUPPORTED: + errstring = "Unsupported format"; + break; + default: + errstring = "Error opening file: %d"; + } -/* Advance a PCM buffer pointer by size bytes circularly */ -static inline void pcm_advance_buffer(struct pcm_frame_header * volatile *p, - size_t size) -{ - *p = SKIPBYTES(*p, size); - if (*p >= pcmbuf_end) - *p = pcm_buffer; -} - -static void get_more(unsigned char** start, size_t* size) -{ - /* 25ms @ 44.1kHz */ - static unsigned char silence[4412] __attribute__((aligned (4))) = { 0 }; - size_t sz; - - if (pcmbuf_used() >= pcmbuf_threshold) - { - uint32_t time = pcmbuf_tail->time; - sz = pcmbuf_tail->size; - - *start = (unsigned char *)pcmbuf_tail->data; - - pcm_advance_buffer(&pcmbuf_tail, sz); - - pcmbuf_read += sz; - - sz -= sizeof (*pcmbuf_tail); - - *size = sz; - - /* Drift the clock towards the audio timestamp values */ - sampleadjust = AVERAGE(sampleadjust, (int32_t)(time - samplesplayed), 8); - - /* Update master clock */ - samplesplayed += sz >> 2; - return; - } - - /* Keep clock going at all times */ - sz = sizeof (silence); - *start = silence; - *size = sz; - - samplesplayed += sz >> 2; - - if (pcmbuf_read > pcmbuf_written) - pcmbuf_read = pcmbuf_written; -} - -/* Flushes the buffer - clock keeps counting */ -static void pcm_playback_flush(void) -{ - bool was_playing = rb->pcm_is_playing(); - - if (was_playing) - rb->pcm_play_stop(); - - pcmbuf_read = 0; - pcmbuf_written = 0; - pcmbuf_head = pcmbuf_tail; - - if (was_playing) - rb->pcm_play_data(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 */ -static void pcm_playback_seek_time(uint32_t time) -{ - bool was_playing = rb->pcm_is_playing(); - - if (was_playing) - rb->pcm_play_stop(); - - samplesplayed = time; - samplestart = time; - sampleadjust = 0; - - if (was_playing) - rb->pcm_play_data(get_more, NULL, 0); -} - -/* Start pcm playback with the reference clock set to the specified time */ -static void pcm_playback_play(uint32_t time) -{ - pcm_playback_seek_time(time); - - if (!rb->pcm_is_playing()) - rb->pcm_play_data(get_more, NULL, 0); -} - -/* Pauses playback - and the clock */ -static void pcm_playback_play_pause(bool play) -{ - rb->pcm_play_pause(play); -} - -/* Stops all playback and resets the clock */ -static void pcm_playback_stop(void) -{ - if (rb->pcm_is_playing()) - rb->pcm_play_stop(); - - pcm_playback_flush(); - - sampleadjust = - samplestart = - samplesplayed = 0; -} - -static uint32_t get_stream_time(void) -{ - return samplesplayed + sampleadjust - (rb->pcm_get_bytes_waiting() >> 2); -} - -static uint32_t get_playback_time(void) -{ - return samplesplayed + sampleadjust - - samplestart - (rb->pcm_get_bytes_waiting() >> 2); -} - -static inline int32_t clip_sample(int32_t sample) -{ - if ((int16_t)sample != sample) - sample = 0x7fff ^ (sample >> 31); - - return sample; -} - -static int button_loop(void) -{ - int result; - int vol, minvol, maxvol; - int button; - - if (video_sync_start==1) { - - if (str_have_msg(&audio_str)) - { - struct queue_event ev; - str_get_msg(&audio_str, &ev); - - if (ev.id == STREAM_QUIT) - { - audio_str.status = STREAM_STOPPED; - goto quit; - } - else - { - str_reply_msg(&audio_str, 0); - } - } - - button = rb->button_get(false); - - switch (button) - { - case MPEG_VOLUP: - case MPEG_VOLUP|BUTTON_REPEAT: -#ifdef MPEG_VOLUP2 - case MPEG_VOLUP2: - case MPEG_VOLUP2|BUTTON_REPEAT: -#endif - vol = rb->global_settings->volume; - maxvol = rb->sound_max(SOUND_VOLUME); - - if (vol < maxvol) { - vol++; - rb->sound_set(SOUND_VOLUME, vol); - rb->global_settings->volume = vol; - } - break; - - case MPEG_VOLDOWN: - case MPEG_VOLDOWN|BUTTON_REPEAT: -#ifdef MPEG_VOLDOWN2 - case MPEG_VOLDOWN2: - case MPEG_VOLDOWN2|BUTTON_REPEAT: -#endif - vol = rb->global_settings->volume; - minvol = rb->sound_min(SOUND_VOLUME); - - if (vol > minvol) { - vol--; - rb->sound_set(SOUND_VOLUME, vol); - rb->global_settings->volume = vol; - } - break; - - case MPEG_MENU: - pcm_playback_play_pause(false); - audio_str.status = STREAM_PAUSED; - str_send_msg(&video_str, STREAM_PAUSE, 0); -#ifndef HAVE_LCD_COLOR - gray_show(false); -#endif - result = mpeg_menu(); - count_start = get_playback_time(); - num_drawn = 0; - -#ifndef HAVE_LCD_COLOR - gray_show(true); -#endif - - /* The menu can change the font, so restore */ - rb->lcd_setfont(FONT_SYSFIXED); - - switch (result) - { - case MPEG_MENU_QUIT: - settings.resume_time = (int)(get_stream_time()/CLOCK_RATE/ - 30-start_pts_time); - str_send_msg(&video_str, STREAM_QUIT, 0); - audio_str.status = STREAM_STOPPED; - break; - default: - audio_str.status = STREAM_PLAYING; - str_send_msg(&video_str, STREAM_PLAY, 0); - pcm_playback_play_pause(true); - break; - } - break; - - case MPEG_STOP: - settings.resume_time = (int)(get_stream_time()/CLOCK_RATE/ - 30-start_pts_time); - str_send_msg(&video_str, STREAM_QUIT, 0); - audio_str.status = STREAM_STOPPED; - break; - - case MPEG_PAUSE: -#ifdef MPEG_PAUSE2 - case MPEG_PAUSE2: -#endif - settings.resume_time = (int)(get_stream_time()/CLOCK_RATE/ - 30-start_pts_time); - save_settings(); - str_send_msg(&video_str, STREAM_PAUSE, 0); - audio_str.status = STREAM_PAUSED; - pcm_playback_play_pause(false); - - button = BUTTON_NONE; -#ifdef HAVE_ADJUSTABLE_CPU_FREQ - rb->cpu_boost(false); -#endif - do { - button = rb->button_get(true); - if (button == MPEG_STOP) { - str_send_msg(&video_str, STREAM_QUIT, 0); - audio_str.status = STREAM_STOPPED; - goto quit; - } -#ifndef MPEG_PAUSE2 - } while (button != MPEG_PAUSE); -#else - } while (button != MPEG_PAUSE && button != MPEG_PAUSE2); -#endif - - str_send_msg(&video_str, STREAM_PLAY, 0); - audio_str.status = STREAM_PLAYING; - pcm_playback_play_pause(true); -#ifdef HAVE_ADJUSTABLE_CPU_FREQ - rb->cpu_boost(true); -#endif - break; - - default: - if(rb->default_event_handler(button) == SYS_USB_CONNECTED) { - str_send_msg(&video_str, STREAM_QUIT, 0); - audio_str.status = STREAM_STOPPED; - } - } - } -quit: - return audio_str.status; -} - -static void audio_thread(void) -{ - uint8_t *mpabuf = mpa_buffer; - ssize_t mpabuf_used = 0; - int mad_errors = 0; /* A count of the errors in each frame */ - struct pts_queue_slot *pts; - - /* We need this here to init the EMAC for Coldfire targets */ - mad_synth_init(&synth); - - /* Init pts queue */ - pts_queue_reset(); - pts = pts_queue_tail(); - - /* Keep buffer from playing */ - pcmbuf_threshold = PCMBUF_PLAY_NONE; - - /* Start clock */ - pcm_playback_play(0); - - /* Get first packet */ - get_next_data(&audio_str, 0 ); - - /* skip audio packets here */ - while (audio_sync_start==0) - { - audio_str.status = STREAM_PLAYING; - rb->yield(); - } - - if (audio_sync_time>10000) - { - while (TS_TO_TICKS(audio_str.curr_pts) < audio_sync_time - 10000) - { - get_next_data(&audio_str, 0 ); - rb->priority_yield(); - } - } - - if (audio_str.curr_packet == NULL) - goto done; - - /* This is the decoding loop. */ - while (1) - { - int mad_stat; - size_t len; - - if (button_loop() == STREAM_STOPPED) - goto audio_thread_quit; - - if (pts->size <= 0) - { - /* Carry any overshoot to the next size since we're technically - -pts->size bytes into it already. If size is negative an audio - frame was split accross packets. Old has to be saved before - moving the tail. */ - if (pts_queue_remove_tail()) - { - struct pts_queue_slot *old = pts; - pts = pts_queue_tail(); - pts->size += old->size; - old->size = 0; - } - } - - /** Buffering **/ - if (mpabuf_used >= MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD) - { - /* Above low watermark - do nothing */ - } - else if (audio_str.curr_packet != NULL) - { - do - { - /* Get data from next audio packet */ - len = audio_str.curr_packet_end - audio_str.curr_packet; - - if (audio_str.tagged) - { - struct pts_queue_slot *stamp = pts; - - if (pts_queue_add_head()) - { - stamp = pts_queue_head(); - stamp->pts = TS_TO_TICKS(audio_str.curr_pts); - /* pts->size should have been zeroed when slot was - freed */ - } - /* else queue full - just count up from the last to make - it look like more data in the same packet */ - stamp->size += len; - audio_str.tagged = 0; - } - else - { - /* Add to the one just behind the head - this may be the - tail or the previouly added head - whether or not we'll - ever reach this is quite in question since audio always - seems to have every packet timestamped */ - pts_queue_head()->size += len; - } - - /* Slide any remainder over to beginning - avoid function - call overhead if no data remaining as well */ - if (mpabuf > mpa_buffer && mpabuf_used > 0) - rb->memmove(mpa_buffer, mpabuf, mpabuf_used); - - /* Splice this packet onto any remainder */ - rb->memcpy(mpa_buffer + mpabuf_used, audio_str.curr_packet, - len); - - mpabuf_used += len; - mpabuf = mpa_buffer; - - /* Get data from next audio packet */ - get_next_data(&audio_str, 0 ); - } - while (audio_str.curr_packet != NULL && - mpabuf_used < MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD); - } - else if (mpabuf_used <= 0) - { - /* Used up remainder of mpa buffer so quit */ - break; - } - - /** Decoding **/ - mad_stream_buffer(&stream, mpabuf, mpabuf_used); - - mad_stat = mad_frame_decode(&frame, &stream); - - if (stream.next_frame == NULL) - { - /* What to do here? (This really is fatal) */ - DEBUGF("/* What to do here? */\n"); - break; - } - - /* Next mad stream buffer is the next frame postion */ - mpabuf = (uint8_t *)stream.next_frame; - - /* Adjust sizes by the frame size */ - len = stream.next_frame - stream.this_frame; - mpabuf_used -= len; - pts->size -= len; - - if (mad_stat != 0) - { - if (stream.error == MAD_FLAG_INCOMPLETE - || stream.error == MAD_ERROR_BUFLEN) - { - /* This makes the codec support partially corrupted files */ - if (++mad_errors > 30) - break; - - stream.error = 0; - rb->priority_yield(); - continue; - } - else if (MAD_RECOVERABLE(stream.error)) - { - stream.error = 0; - rb->priority_yield(); - continue; - } - else - { - /* Some other unrecoverable error */ - DEBUGF("Unrecoverable error\n"); - } - - break; - } - - mad_errors = 0; /* Clear errors */ - - /* Generate the pcm samples */ - mad_synth_frame(&synth, &frame); - - /** Output **/ - - /* TODO: Output through core dsp. We'll still use our own PCM buffer - since the core pcm buffer has no timestamping or clock facilities */ - - /* Add a frame of audio to the pcm buffer. Maximum is 1152 samples. */ - if (synth.pcm.length > 0) - { - int16_t *audio_data = (int16_t *)pcmbuf_head->data; - size_t size = sizeof (*pcmbuf_head) + synth.pcm.length*4; - size_t wait_for = size + 32*1024; - - /* Leave at least 32KB free (this will be the currently - playing chunk) */ - while (pcmbuf_used() + wait_for > PCMBUFFER_SIZE) - { - if (str_have_msg(&audio_str)) - { - struct queue_event ev; - str_look_msg(&audio_str, &ev); - - if (ev.id == STREAM_QUIT) - goto audio_thread_quit; - } - - rb->priority_yield(); - } - - if (video_sync_start == 0 && - pts->pts+(uint32_t)synth.pcm.lengthyield(); - } - - /* TODO: This part will be replaced with dsp calls soon */ - if (MAD_NCHANNELS(&frame.header) == 2) - { - int32_t *left = &synth.pcm.samples[0][0]; - int32_t *right = &synth.pcm.samples[1][0]; - int i = synth.pcm.length; - - do - { - /* libmad outputs s3.28 */ - *audio_data++ = clip_sample(*left++ >> 13); - *audio_data++ = clip_sample(*right++ >> 13); - } - while (--i > 0); - } - else /* mono */ - { - int32_t *mono = &synth.pcm.samples[0][0]; - int i = synth.pcm.length; - - do - { - int32_t s = clip_sample(*mono++ >> 13); - *audio_data++ = s; - *audio_data++ = s; - } - while (--i > 0); - } - /**/ - - pcmbuf_head->time = pts->pts; - pcmbuf_head->size = size; - - /* As long as we're on this timestamp, the time is just incremented - by the number of samples */ - pts->pts += synth.pcm.length; - - pcm_advance_buffer(&pcmbuf_head, size); - - if (pcmbuf_threshold != PCMBUF_PLAY_ALL && pcmbuf_used() >= 64*1024) - { - /* We've reached our size treshold so start playing back the - audio in the buffer and set the buffer to play all data */ - audio_str.status = STREAM_PLAYING; - pcmbuf_threshold = PCMBUF_PLAY_ALL; - pcm_playback_seek_time(pcmbuf_tail->time); - video_sync_start = 1; - } - - /* Make this data available to DMA */ - pcmbuf_written += size; - } - - rb->yield(); - } /* end decoding loop */ - -done: - if (audio_str.status == STREAM_STOPPED) - goto audio_thread_quit; - - /* Force any residue to play if audio ended before reaching the - threshold */ - if (pcmbuf_threshold != PCMBUF_PLAY_ALL && pcmbuf_used() > 0) - { - pcm_playback_play(pcmbuf_tail->time); - pcmbuf_threshold = PCMBUF_PLAY_ALL; - } - - if (rb->pcm_is_playing() && !rb->pcm_is_paused()) - { - /* Wait for audio to finish */ - while (pcmbuf_used() > 0) - { - if (button_loop() == STREAM_STOPPED) - goto audio_thread_quit; - rb->sleep(HZ/10); - } - } - - audio_str.status = STREAM_DONE; - - /* Process events until finished */ - while (button_loop() != STREAM_STOPPED) - rb->sleep(HZ/4); - -audio_thread_quit: - pcm_playback_stop(); - - audio_str.status = STREAM_TERMINATED; -} - -/* End of libmad stuff */ - -/* The audio stack is stolen from the core codec thread (but not in uisim) */ -#define AUDIO_STACKSIZE (9*1024) -uint32_t* audio_stack; - -#ifndef SIMULATOR -static uint32_t codec_stack_copy[AUDIO_STACKSIZE / sizeof(uint32_t)]; -#endif - -/* 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 void video_thread(void) -{ - struct queue_event ev; - const mpeg2_info_t * info; - mpeg2_state_t state; - char str[80]; - uint32_t curr_time = 0; - uint32_t period = 0; /* Frame period in clock ticks */ - uint32_t eta_audio = UINT_MAX, eta_video = 0; - int32_t eta_early = 0, eta_late = 0; - int frame_drop_level = 0; - int skip_level = 0; - int num_skipped = 0; - /* Used to decide when to display FPS */ - unsigned long last_showfps = *rb->current_tick - HZ; - /* Used to decide whether or not to force a frame update */ - unsigned long last_render = last_showfps; - - mpeg2dec = mpeg2_init(); - if (mpeg2dec == NULL) - { - rb->splash(0, "mpeg2_init failed"); - /* Commit suicide */ - video_str.status = STREAM_TERMINATED; - return; - } - - /* Clear the display - this is mainly just to indicate that the - video thread has started successfully. */ - if (!video_thumb_print) - { - rb->lcd_clear_display(); - rb->lcd_update(); - } - - /* Request the first packet data */ - get_next_data( &video_str, 0 ); - - if (video_str.curr_packet == NULL) - goto video_thread_quit; - - mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); - total_offset += video_str.curr_packet_end - video_str.curr_packet; - - info = mpeg2_info (mpeg2dec); - - while (1) - { - /* quickly check mailbox first */ - if (video_thumb_print) - { - if (video_str.status == STREAM_STOPPED) - break; - } - else if (str_have_msg(&video_str)) - { - while (1) - { - str_get_msg(&video_str, &ev); - - switch (ev.id) - { - case STREAM_QUIT: - video_str.status = STREAM_STOPPED; - goto video_thread_quit; - case STREAM_PAUSE: - #if NUM_CORES > 1 - flush_icache(); - #endif - video_str.status = STREAM_PAUSED; - str_reply_msg(&video_str, 1); - continue; - } - - break; - } - - video_str.status = STREAM_PLAYING; - str_reply_msg(&video_str, 1); - } - - state = mpeg2_parse (mpeg2dec); - rb->yield(); - - /* Prevent idle poweroff */ - rb->reset_poweroff_timer(); - - switch (state) - { - case STATE_BUFFER: - /* Request next packet data */ - get_next_data( &video_str, 0 ); - - mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); - total_offset += video_str.curr_packet_end - video_str.curr_packet; - info = mpeg2_info (mpeg2dec); - - if (video_str.curr_packet == NULL) - { - /* No more data. */ - goto video_thread_quit; - } - continue; - - case STATE_SEQUENCE: - /* New GOP, inform output of any changes */ - vo_setup(info->sequence); - break; - - case STATE_PICTURE: - { - int skip = 0; /* Assume no skip */ - - if (frame_drop_level >= 1 || skip_level > 0) - { - /* A frame will be dropped in the decoder */ - - /* Frame type: I/P/B/D */ - int type = info->current_picture->flags & PIC_MASK_CODING_TYPE; - - switch (type) - { - case PIC_FLAG_CODING_TYPE_I: - case PIC_FLAG_CODING_TYPE_D: - /* Level 5: Things are extremely late and all frames will be - dropped until the next key frame */ - if (frame_drop_level >= 1) - frame_drop_level = 0; /* Key frame - reset drop level */ - if (skip_level >= 5) - { - frame_drop_level = 1; - skip_level = 0; /* reset */ - } - break; - case PIC_FLAG_CODING_TYPE_P: - /* Level 4: Things are very late and all frames will be - dropped until the next key frame */ - if (skip_level >= 4) - { - frame_drop_level = 1; - skip_level = 0; /* reset */ - } - 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. Bring the level down as skips are done. */ - skip = 1; - if (skip_level > 0) - skip_level--; - } - - skip |= frame_drop_level; - } - - mpeg2_skip(mpeg2dec, skip); - break; - } - - case STATE_SLICE: - case STATE_END: - case STATE_INVALID_END: - { - int32_t offset; /* Tick adjustment to keep sync */ - - /* draw current picture */ - if (!info->display_fbuf) - break; - - /* No limiting => no dropping - draw this frame */ - if (!settings.limitfps && (video_thumb_print == 0)) - { - audio_sync_start = 1; - video_sync_start = 1; - goto picture_draw; - } - - /* Get presentation times in audio samples - quite accurate - enough - add previous frame duration if not stamped */ - curr_time = (info->display_picture->flags & PIC_FLAG_TAGS) ? - TS_TO_TICKS(info->display_picture->tag) : (curr_time + period); - - period = TIME_TO_TICKS(info->sequence->frame_period); - - if ( (video_thumb_print == 1 || video_sync_start == 0) && - ((int)(info->current_picture->flags & PIC_MASK_CODING_TYPE) - == PIC_FLAG_CODING_TYPE_B)) - break; - - eta_video = curr_time; - - audio_sync_time = eta_video; - audio_sync_start = 1; - - while (video_sync_start == 0) - rb->yield(); - - eta_audio = get_stream_time(); - - /* How early/late are we? > 0 = late, < 0 early */ - offset = eta_audio - eta_video; - - if (!settings.skipframes) - { - /* Make no effort to determine whether this frame should be - drawn or not since no action can be taken to correct the - situation. We'll just wait if we're early and correct for - lateness as much as possible. */ - if (offset < 0) - offset = 0; - - eta_late = AVERAGE(eta_late, offset, 4); - offset = eta_late; - - if ((uint32_t)offset > eta_video) - offset = eta_video; - - eta_video -= offset; - 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 - will be I/D/P - * P decoder must wait for I/D-frame - choppy - * I/D decoder must wait for I/D-frame - choppy - * - * 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 we're late, set the eta to play the frame early so - we may catch up. If early, especially because of a drop, - mitigate a "snap" by moving back gradually. */ - if (offset >= 0) /* late or on time */ - { - eta_early = 0; /* Not early now :( */ - - eta_late = AVERAGE(eta_late, offset, 4); - offset = eta_late; - - if ((uint32_t)offset > eta_video) - offset = eta_video; - - eta_video -= offset; - } - else - { - eta_late = 0; /* Not late now :) */ - - if (offset > eta_early) - { - /* Just dropped a frame and we're now early or we're - coming back from being early */ - eta_early = offset; - if ((uint32_t)-offset > eta_video) - offset = -eta_video; - - eta_video += offset; - } - else - { - /* Just early with an offset, do exponential drift back */ - if (eta_early != 0) - { - eta_early = AVERAGE(eta_early, 0, 8); - eta_video = ((uint32_t)-eta_early > eta_video) ? - 0 : (eta_video + eta_early); - } - - offset = eta_early; - } - } - - if (info->display_picture->flags & PIC_FLAG_SKIP) - { - /* This frame was set to skip so skip it after having updated - timing information */ - num_skipped++; - eta_early = INT32_MIN; - goto picture_skip; - } - - if (skip_level == 3 && TIME_BEFORE(*rb->current_tick, last_render + HZ/2)) - { - /* Render drop was set previously but nothing was dropped in the - decoder or it's been to long since drawing the last frame. */ - skip_level = 0; - num_skipped++; - eta_early = INT32_MIN; - goto picture_skip; - } - - /* At this point a frame _will_ be drawn - a skip may happen on - the next however */ - skip_level = 0; - - if (offset > CLOCK_RATE*110/1000) - { - /* Decide which skip level is needed in order to catch up */ - - /* TODO: Calculate this rather than if...else - this is rather - exponential though */ - if (offset > CLOCK_RATE*367/1000) - skip_level = 5; /* Decoder skip: I/D */ - if (offset > CLOCK_RATE*233/1000) - skip_level = 4; /* Decoder skip: P */ - else if (offset > CLOCK_RATE*167/1000) - skip_level = 3; /* Render skip */ - else if (offset > CLOCK_RATE*133/1000) - skip_level = 2; /* Decoder skip: B */ - else - skip_level = 1; /* Decoder skip: B */ - } - - picture_wait: - /* Wait until audio catches up */ - if (video_thumb_print) - video_str.status = STREAM_STOPPED; - else - while (eta_video > eta_audio) - { - rb->priority_yield(); - - /* Make sure not to get stuck waiting here forever */ - if (str_have_msg(&video_str)) - { - str_look_msg(&video_str, &ev); - - /* If not to play, process up top */ - if (ev.id != STREAM_PLAY) - goto rendering_finished; - - /* Told to play but already playing */ - str_get_msg(&video_str, &ev); - str_reply_msg(&video_str, 1); - } - - eta_audio = get_stream_time(); - } - - picture_draw: - /* Record last frame time */ - last_render = *rb->current_tick; - - if (video_thumb_print) - vo_draw_frame_thumb(info->display_fbuf->buf); - else - vo_draw_frame(info->display_fbuf->buf); - - num_drawn++; - - picture_skip: - if (!settings.showfps) - break; - - /* Calculate and display fps */ - if (TIME_AFTER(*rb->current_tick, last_showfps + HZ)) - { - uint32_t clock_ticks = get_playback_time() - count_start; - int fps = 0; - - if (clock_ticks != 0) - fps = num_drawn*CLOCK_RATE*10ll / clock_ticks; - - rb->snprintf(str, sizeof(str), "%d.%d %d %d ", - fps / 10, fps % 10, num_skipped, - info->display_picture->temporal_reference); - rb->lcd_putsxy(0, 0, str); - rb->lcd_update_rect(0, 0, LCD_WIDTH, 8); - - last_showfps = *rb->current_tick; - } - break; - } - - default: - break; - } - rendering_finished: - - rb->yield(); - } - - video_thread_quit: - /* if video ends before time sync'd, - besure the audio thread is closed */ - if (video_sync_start == 0) - { - audio_str.status = STREAM_STOPPED; - audio_sync_start = 1; - } - - #if NUM_CORES > 1 - flush_icache(); - #endif - - mpeg2_close (mpeg2dec); - - /* Commit suicide */ - video_str.status = STREAM_TERMINATED; -} - -void initialize_stream( Stream *str, uint8_t *buffer_start, size_t disk_buf_len, int id ) -{ - str->curr_packet_end = str->curr_packet = NULL; - str->prev_packet_length = 0; - str->prev_packet = str->curr_packet_end = buffer_start; - str->buffer_remaining = disk_buf_len; - str->id = id; -} - -void display_thumb(int in_file) -{ - size_t disk_buf_len; - - video_thumb_print = 1; - audio_sync_start = 1; - video_sync_start = 1; - - disk_buf_len = rb->read (in_file, disk_buf_start, disk_buf_size - MPEG_GUARDBUF_SIZE); - disk_buf_tail = disk_buf_start + disk_buf_len; - file_remaining = 0; - initialize_stream(&video_str,disk_buf_start,disk_buf_len,0xe0); - - video_str.status = STREAM_PLAYING; - - if ((video_str.thread = rb->create_thread(video_thread, - (uint8_t*)video_stack,VIDEO_STACKSIZE, 0,"mpgvideo" - IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, COP))) == NULL) - { - rb->splash(HZ, "Cannot create video thread!"); - } - else - { - rb->thread_wait(video_str.thread); - } - - if ( video_str.curr_packet_end == video_str.curr_packet) - rb->splash(0, "frame not available"); -} - -int find_start_pts( int in_file ) -{ - uint8_t *p; - size_t read_length = 60*1024; - size_t disk_buf_len; - - start_pts_time = 0; - - /* temporary read buffer size cannot exceed buffer size */ - if ( read_length > disk_buf_size ) - read_length = disk_buf_size; - - /* read tail of file */ - rb->lseek( in_file, 0, SEEK_SET ); - disk_buf_len = rb->read( in_file, disk_buf_start, read_length ); - disk_buf_tail = disk_buf_start + disk_buf_len; - - /* sync reader to this segment of the stream */ - p=disk_buf_start; - if (sync_data_stream(&p)) - { - DEBUGF("Could not sync stream\n"); - return PLUGIN_ERROR; - } - - /* find first PTS in audio stream. if the PTS can not be determined, - set start_pts_time to 0 */ - audio_sync_start = 0; - audio_sync_time = 0; - video_sync_start = 0; - { - Stream tmp; - initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); - int count=0; - do - { - count++; - get_next_data(&tmp, 2); - } - while (tmp.tagged != 1 && count < 30); - if (tmp.tagged == 1) - start_pts_time = (int)((tmp.curr_pts/45000)/30); - } - return 0; -} - -int find_end_pts( int in_file ) -{ - uint8_t *p; - size_t read_length = 60*1024; - size_t disk_buf_len; - - end_pts_time = 0; - - /* temporary read buffer size cannot exceed buffer size */ - if ( read_length > disk_buf_size ) - read_length = disk_buf_size; - - /* read tail of file */ - rb->lseek( in_file, -1*read_length, SEEK_END ); - disk_buf_len = rb->read( in_file, disk_buf_start, read_length ); - disk_buf_tail = disk_buf_start + disk_buf_len; - - /* sync reader to this segment of the stream */ - p=disk_buf_start; - if (sync_data_stream(&p)) - { - DEBUGF("Could not sync stream\n"); - return PLUGIN_ERROR; - } - - /* find last PTS in audio stream; will movie always have audio? if - the play time can not be determined, set end_pts_time to 0 */ - audio_sync_start = 0; - audio_sync_time = 0; - video_sync_start = 0; - { - Stream tmp; - initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); - - do - { - get_next_data(&tmp, 2); - if (tmp.tagged == 1) - /* 10 sec less to insure the video frame exist */ - end_pts_time = (int)((tmp.curr_pts/45000-10)/30); - } - while (tmp.curr_packet_end != NULL); - } - return 0; -} - -ssize_t seek_PTS( int in_file, int start_time, int accept_button ) -{ - static ssize_t last_seek_pos = 0; - static int last_start_time = 0; - ssize_t seek_pos; - size_t disk_buf_len; - uint8_t *p; - size_t read_length = 60*1024; - - /* temporary read buffer size cannot exceed buffer size */ - if ( read_length > disk_buf_size ) - read_length = disk_buf_size; - - if ( start_time == last_start_time ) - { - seek_pos = last_seek_pos; - rb->lseek(in_file,seek_pos,SEEK_SET); - } - else if ( start_time != 0 ) - { - seek_pos = rb->filesize(in_file)*start_time/ - (end_pts_time-start_pts_time); - int seek_pos_sec_inc = rb->filesize(in_file)/ - (end_pts_time-start_pts_time)/30; - - if (seek_pos<0) - seek_pos=0; - if ((size_t)seek_pos > rb->filesize(in_file) - read_length) - seek_pos = rb->filesize(in_file) - read_length; - rb->lseek( in_file, seek_pos, SEEK_SET ); - disk_buf_len = rb->read( in_file, disk_buf_start, read_length ); - disk_buf_tail = disk_buf_start + disk_buf_len; - - /* sync reader to this segment of the stream */ - p=disk_buf_start; - if (sync_data_stream(&p)) - { - DEBUGF("Could not sync stream\n"); - return PLUGIN_ERROR; - } - - /* find PTS >= start_time */ - audio_sync_start = 0; - audio_sync_time = 0; - video_sync_start = 0; - { - Stream tmp; - initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); - int cont_seek_loop = 1; - int coarse_seek = 1; - do - { - if ( accept_button ) - { - rb->yield(); - if (rb->button_queue_count()) - return -101; - } - - while ( get_next_data(&tmp, 1) == 1 ) - { - if ( tmp.curr_packet_end == disk_buf_start ) - seek_pos += disk_buf_tail - disk_buf_start; - else - seek_pos += tmp.curr_packet_end - disk_buf_start; - if ((size_t)seek_pos > rb->filesize(in_file) - read_length) - seek_pos = rb->filesize(in_file) - read_length; - rb->lseek( in_file, seek_pos, SEEK_SET ); - disk_buf_len = rb->read ( in_file, disk_buf_start, read_length ); - disk_buf_tail = disk_buf_start + disk_buf_len; - - /* sync reader to this segment of the stream */ - p=disk_buf_start; - initialize_stream(&tmp,p,disk_buf_len,0xc0); - } - - /* are we after start_time in the stream? */ - if ( coarse_seek && (int)(tmp.curr_pts/45000) >= - (start_time+start_pts_time)*30 ) - { - int time_to_backup = (int)(tmp.curr_pts/45000) - - (start_time+start_pts_time)*30; - if (time_to_backup == 0) - time_to_backup++; - seek_pos -= seek_pos_sec_inc * time_to_backup; - seek_pos_sec_inc -= seek_pos_sec_inc/20; /* for stability */ - if (seek_pos<0) - seek_pos=0; - if ((size_t)seek_pos > rb->filesize(in_file) - read_length) - seek_pos = rb->filesize(in_file) - read_length; - rb->lseek( in_file, seek_pos, SEEK_SET ); - disk_buf_len = rb->read( in_file, disk_buf_start, read_length ); - disk_buf_tail = disk_buf_start + disk_buf_len; - - /* sync reader to this segment of the stream */ - p=disk_buf_start; - if (sync_data_stream(&p)) - { - DEBUGF("Could not sync stream\n"); - return PLUGIN_ERROR; - } - initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); - continue; - } - - /* are we well before start_time in the stream? */ - if ( coarse_seek && (start_time+start_pts_time)*30 - - (int)(tmp.curr_pts/45000) > 2 ) - { - int time_to_advance = (start_time+start_pts_time)*30 - - (int)(tmp.curr_pts/45000) - 2; - if (time_to_advance <= 0) - time_to_advance = 1; - seek_pos += seek_pos_sec_inc * time_to_advance; - if (seek_pos<0) - seek_pos=0; - if ((size_t)seek_pos > rb->filesize(in_file) - read_length) - seek_pos = rb->filesize(in_file) - read_length; - rb->lseek( in_file, seek_pos, SEEK_SET ); - disk_buf_len = rb->read ( in_file, disk_buf_start, read_length ); - disk_buf_tail = disk_buf_start + disk_buf_len; - - /* sync reader to this segment of the stream */ - p=disk_buf_start; - if (sync_data_stream(&p)) - { - DEBUGF("Could not sync stream\n"); - return PLUGIN_ERROR; - } - initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0); - continue; - } - - coarse_seek = 0; - - /* are we at start_time in the stream? */ - if ( (int)(tmp.curr_pts/45000) >= (start_time+start_pts_time)* - 30 ) - cont_seek_loop = 0; - - } - while ( cont_seek_loop ); - - - DEBUGF("start diff: %u %u\n",(unsigned int)(tmp.curr_pts/45000), - (start_time+start_pts_time)*30); - seek_pos+=tmp.curr_packet_end-disk_buf_start; - - last_seek_pos = seek_pos; - last_start_time = start_time; - - rb->lseek(in_file,seek_pos,SEEK_SET); - } - } - else - { - seek_pos = 0; - rb->lseek(in_file,0,SEEK_SET); - last_seek_pos = seek_pos; - last_start_time = start_time; - } - return seek_pos; -} - -enum plugin_status plugin_start(struct plugin_api* api, void* parameter) -{ - int status = PLUGIN_ERROR; /* assume failure */ - int result; - int start_time = -1; - void* audiobuf; - ssize_t audiosize; - int in_file; - size_t disk_buf_len; - ssize_t seek_pos; - size_t audio_stack_size = 0; /* Keep gcc happy and init */ - int i; -#ifndef HAVE_LCD_COLOR - long graysize; - int grayscales; -#endif - - audio_sync_start = 0; - audio_sync_time = 0; - video_sync_start = 0; - - if (parameter == NULL) - { - api->splash(HZ*2, "No File"); - return PLUGIN_ERROR; - } - api->talk_disable(true); - - /* Initialize IRAM - stops audio and voice as well */ - PLUGIN_IRAM_INIT(api) - - rb = api; - rb->splash(0, "Loading..."); - - /* sets audiosize and returns buffer pointer */ - audiobuf = rb->plugin_get_audio_buffer(&audiosize); - -#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 - - rb->pcm_set_frequency(SAMPR_44); - -#ifndef HAVE_LCD_COLOR - /* initialize the grayscale buffer: 32 bitplanes for 33 shades of gray. */ - grayscales = gray_init(rb, audiobuf, audiosize, false, LCD_WIDTH, LCD_HEIGHT, - 32, 2<<8, &graysize) + 1; - audiobuf += graysize; - audiosize -= graysize; - if (grayscales < 33 || audiosize <= 0) - { - rb->talk_disable(false); - rb->splash(HZ, "gray buf error"); - return PLUGIN_ERROR; - } -#endif - - /* Initialise our malloc buffer */ - audiosize = mpeg_alloc_init(audiobuf,audiosize, LIBMPEG2BUFFER_SIZE); - if (audiosize == 0) - { - rb->talk_disable(false); - return PLUGIN_ERROR; - } - - /* Set disk pointers to NULL */ - disk_buf_end = disk_buf_start = NULL; - - /* Grab most of the buffer for the compressed video - leave some for - PCM audio data and some for libmpeg2 malloc use. */ - disk_buf_size = audiosize - (PCMBUFFER_SIZE+PCMBUFFER_GUARD_SIZE+ - MPABUF_SIZE); - - DEBUGF("audiosize=%ld, disk_buf_size=%ld\n",audiosize,disk_buf_size); - disk_buf_start = mpeg_malloc(disk_buf_size,-1); - - if (disk_buf_start == NULL) - { - rb->talk_disable(false); - return PLUGIN_ERROR; - } - - if (!init_mpabuf()) - { - rb->talk_disable(false); - return PLUGIN_ERROR; - } - if (!init_pcmbuf()) - { - rb->talk_disable(false); - return PLUGIN_ERROR; - } - - /* The remaining buffer is for use by libmpeg2 */ - - /* Open the video file */ - in_file = rb->open((char*)parameter,O_RDONLY); - - if (in_file < 0){ - DEBUGF("Could not open %s\n",(char*)parameter); - rb->talk_disable(false); - return PLUGIN_ERROR; - } - filename = (char*)parameter; - -#ifdef HAVE_LCD_COLOR - rb->lcd_set_backdrop(NULL); - rb->lcd_set_foreground(LCD_WHITE); - rb->lcd_set_background(LCD_BLACK); -#endif - - init_settings((char*)parameter); - - /* Initialise libmad */ - rb->memset(mad_frame_overlap, 0, sizeof(mad_frame_overlap)); - init_mad(mad_frame_overlap); - - disk_buf_end = disk_buf_start + disk_buf_size-MPEG_GUARDBUF_SIZE; - - /* initalize start_pts_time and end_pts_time with the length (in half - minutes) of the movie. zero if the time could not be determined */ - find_start_pts( in_file ); - find_end_pts( in_file ); - - - /* start menu */ - rb->lcd_clear_display(); - rb->lcd_update(); - result = mpeg_start_menu(end_pts_time-start_pts_time, in_file); - - switch (result) - { - case MPEG_START_QUIT: - rb->talk_disable(false); - return 0; - default: - start_time = settings.resume_time; - break; - } - - /* basic time checks */ - if ( start_time < 0 ) - start_time = 0; - else if ( start_time > (end_pts_time-start_pts_time) ) - start_time = (end_pts_time-start_pts_time); - - /* Turn off backlight timeout */ - backlight_force_on(rb); /* backlight control in lib/helper.c */ - -#ifdef HAVE_ADJUSTABLE_CPU_FREQ - rb->cpu_boost(true); -#endif - - /* From this point on we've altered settings, colors, cpu_boost, etc. and - cannot just return PLUGIN_ERROR - instead drop though to cleanup code - */ - -#ifdef SIMULATOR - /* The simulator thread implementation doesn't have stack buffers, and - these parameters are ignored. */ - (void)i; /* Keep gcc happy */ - audio_stack = NULL; - audio_stack_size = 0; -#else - /* Borrow the codec thread's stack (in IRAM on most targets) */ - audio_stack = NULL; - for (i = 0; i < MAXTHREADS; i++) - { - if (rb->strcmp(rb->threads[i].name,"codec")==0) - { - /* Wait to ensure the codec thread has blocked */ - while (rb->threads[i].state!=STATE_BLOCKED) - rb->yield(); - - /* Now we can steal the stack */ - audio_stack = rb->threads[i].stack; - audio_stack_size = rb->threads[i].stack_size; - - /* Backup the codec thread's stack */ - rb->memcpy(codec_stack_copy,audio_stack,audio_stack_size); - - break; - } - } - - if (audio_stack == NULL) - { - /* This shouldn't happen, but deal with it anyway by using - the copy instead */ - audio_stack = codec_stack_copy; - audio_stack_size = AUDIO_STACKSIZE; - } -#endif - - rb->splash(0, "Loading..."); - - /* seek start time */ - seek_pos = seek_PTS( in_file, start_time, 0 ); - - video_thumb_print = 0; - audio_sync_start = 0; - audio_sync_time = 0; - video_sync_start = 0; - - /* Read some stream data */ - disk_buf_len = rb->read (in_file, disk_buf_start, disk_buf_size - MPEG_GUARDBUF_SIZE); - - disk_buf_tail = disk_buf_start + disk_buf_len; - file_remaining = rb->filesize(in_file); - file_remaining -= disk_buf_len + seek_pos; - - initialize_stream( &video_str, disk_buf_start, disk_buf_len, 0xe0 ); - initialize_stream( &audio_str, disk_buf_start, disk_buf_len, 0xc0 ); - - rb->mutex_init(&audio_str.msg_lock); - rb->mutex_init(&video_str.msg_lock); - - audio_str.status = STREAM_BUFFERING; - video_str.status = STREAM_PLAYING; - -#ifndef HAVE_LCD_COLOR - gray_show(true); -#endif - - init_stream_lock(); - -#if NUM_CORES > 1 - flush_icache(); -#endif - - /* We put the video thread on the second processor for multi-core targets. */ - if ((video_str.thread = rb->create_thread(video_thread, - (uint8_t*)video_stack, VIDEO_STACKSIZE, 0, - "mpgvideo" IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, COP))) == NULL) - { - rb->splash(HZ, "Cannot create video thread!"); - } - else if ((audio_str.thread = rb->create_thread(audio_thread, - (uint8_t*)audio_stack,audio_stack_size, 0,"mpgaudio" - IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, CPU))) == NULL) - { - rb->splash(HZ, "Cannot create audio thread!"); - } - else - { - rb->lcd_setfont(FONT_SYSFIXED); - - /* Wait until both threads have finished their work */ - while ((audio_str.status >= 0) || (video_str.status >= 0)) - { - size_t audio_remaining = audio_str.buffer_remaining; - size_t video_remaining = video_str.buffer_remaining; - - if (MIN(audio_remaining,video_remaining) < MPEG_LOW_WATERMARK) - { - - size_t bytes_to_read = disk_buf_size - MPEG_GUARDBUF_SIZE - - MAX(audio_remaining,video_remaining); - - bytes_to_read = MIN(bytes_to_read,(size_t)(disk_buf_end-disk_buf_tail)); - - while (( bytes_to_read > 0) && (file_remaining > 0) && - ((audio_str.status != STREAM_DONE) || (video_str.status != STREAM_DONE))) - { - - size_t n; - if ( video_sync_start != 0 ) - n = rb->read(in_file, disk_buf_tail, MIN(32*1024,bytes_to_read)); - else - { - n = rb->read(in_file, disk_buf_tail,bytes_to_read); - if (n==0) - rb->splash(30,"buffer fill error"); - } - - bytes_to_read -= n; - file_remaining -= n; - - lock_stream(); - audio_str.buffer_remaining += n; - video_str.buffer_remaining += n; - unlock_stream(); - - disk_buf_tail += n; - - rb->yield(); - } - - if (disk_buf_tail == disk_buf_end) - disk_buf_tail = disk_buf_start; - } - - rb->sleep(HZ/10); + rb->splash(HZ*2, errstring, err); } - - rb->lcd_setfont(FONT_UI); - status = PLUGIN_OK; - } - - /* Stop the threads and wait for them to terminate */ - if (video_str.thread != NULL) - { - str_send_msg(&video_str, STREAM_QUIT, 0); - rb->thread_wait(video_str.thread); - } - - if (audio_str.thread != NULL) - { - str_send_msg(&audio_str, STREAM_QUIT, 0); - rb->thread_wait(audio_str.thread); } -#if NUM_CORES > 1 - invalidate_icache(); -#endif - - vo_cleanup(); - -#ifndef HAVE_LCD_COLOR - gray_release(); -#endif - - rb->lcd_clear_display(); - rb->lcd_update(); - - mpeg2_close (mpeg2dec); - - rb->close (in_file); - -#ifndef SIMULATOR - /* Restore the codec thread's stack */ - rb->memcpy(audio_stack, codec_stack_copy, audio_stack_size); -#endif - -#ifdef HAVE_ADJUSTABLE_CPU_FREQ - rb->cpu_boost(false); -#endif - - save_settings(); /* Save settings (if they have changed) */ - - rb->pcm_set_frequency(HW_SAMPR_DEFAULT); + stream_exit(); - /* Turn on backlight timeout (revert to settings) */ - backlight_use_settings(rb); /* backlight control in lib/helper.c */ rb->talk_disable(false); return status; } diff --git a/apps/plugins/mpegplayer/mpegplayer.h b/apps/plugins/mpegplayer/mpegplayer.h new file mode 100644 index 0000000000..ae1234d39d --- /dev/null +++ b/apps/plugins/mpegplayer/mpegplayer.h @@ -0,0 +1,120 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Main mpegplayer config header. + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef MPEGPLAYER_H +#define MPEGPLAYER_H + +/* Global API pointer */ +extern struct plugin_api* rb; + +#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 */ + +/* Memory allotments for various subsystems */ +#define MIN_MEMMARGIN (4*1024) + +enum mpeg_malloc_reason_t +{ + __MPEG_ALLOC_FIRST = -256, + MPEG_ALLOC_CODEC_MALLOC, + MPEG_ALLOC_CODEC_CALLOC, + MPEG_ALLOC_MPEG2_BUFFER, + MPEG_ALLOC_AUDIOBUF, + MPEG_ALLOC_PCMOUT, + MPEG_ALLOC_DISKBUF, +}; + +/** 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 + +#define PCMOUT_BUFSIZE (CLOCK_RATE) /* 1s */ +#define PCMOUT_GUARD_SIZE (1152*4 + sizeof (struct pcm_frame_header)) +#define PCMOUT_ALLOC_SIZE (PCMOUT_BUFSIZE + PCMOUT_GUARD_SIZE) + /* Start pcm playback @ 25% full */ +#define PCMOUT_PLAY_WM (PCMOUT_BUFSIZE/4) + /* No valid audio frame is smaller */ +#define PCMOUT_LOW_WM (sizeof (struct pcm_frame_header)) + +/** 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 DRAW_BLACK LCD_BLACK +#define DRAW_DARKGRAY LCD_DARKGRAY +#define DRAW_LIGHTGRAY LCD_LIGHTGRAY +#define DRAW_WHITE LCD_WHITE +#define lcd_(fn) rb->lcd_##fn +#define lcd_splash splash + +#define GRAY_FLUSH_ICACHE() +#define GRAY_INVALIDATE_ICACHE() +#define GRAY_VIDEO_FLUSH_ICACHE() +#define GRAY_VIDEO_INVALIDATE_ICACHE() +#else +#include "gray.h" +#define DRAW_BLACK GRAY_BLACK +#define DRAW_DARKGRAY GRAY_DARKGRAY +#define DRAW_LIGHTGRAY GRAY_LIGHTGRAY +#define DRAW_WHITE GRAY_WHITE +#define lcd_(fn) gray_##fn + +#define GRAY_FLUSH_ICACHE() \ + IF_COP(flush_icache()) +#define GRAY_INVALIDATE_ICACHE() \ + IF_COP(invalidate_icache()) +#define GRAY_VIDEO_FLUSH_ICACHE() \ + IF_COP(parser_send_video_msg(VIDEO_GRAY_CACHEOP, 0)) +#define GRAY_VIDEO_INVALIDATE_ICACHE() \ + IF_COP(parser_send_video_msg(VIDEO_GRAY_CACHEOP, 1)) +#if NUM_CORES > 1 +#define GRAY_CACHE_MAINT +#endif +#endif + +#include "mpeg2.h" +#include "video_out.h" +#include "mpeg_stream.h" +#include "mpeg_linkedlist.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" + +#endif /* MPEGPLAYER_H */ diff --git a/apps/plugins/mpegplayer/parser.h b/apps/plugins/mpegplayer/parser.h new file mode 100644 index 0000000000..892a8a14d2 --- /dev/null +++ b/apps/plugins/mpegplayer/parser.h @@ -0,0 +1,101 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * AV parser inteface declarations + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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); +intptr_t parser_send_video_msg(long id, intptr_t data); +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..281f7ddb72 --- /dev/null +++ b/apps/plugins/mpegplayer/pcm_output.c @@ -0,0 +1,278 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * PCM output buffer definitions + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "plugin.h" +#include "mpegplayer.h" + +/* 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 uint64_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */ +static uint64_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_base IBSS_ATTR; /* Our base clock */ +static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */ +static int32_t clock_adjust IBSS_ATTR; /* Clock drift adjustment */ + +/* Small silence clip. ~5.80ms @ 44.1kHz */ +static int16_t silence[256*2] ALIGNED_ATTR(4) = { 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 = pcm_buffer; +} + +/* Inline internally but not externally */ +inline ssize_t pcm_output_used(void) +{ + return (ssize_t)(pcmbuf_written - pcmbuf_read); +} + +inline ssize_t pcm_output_free(void) +{ + return (ssize_t)(PCMOUT_BUFSIZE - pcmbuf_written + pcmbuf_read); +} + +/* Audio DMA handler */ +static void get_more(unsigned char **start, size_t *size) +{ + ssize_t sz = pcm_output_used(); + + if (sz > pcmbuf_threshold) + { + pcmbuf_threshold = PCMOUT_LOW_WM; + + while (1) + { + uint32_t time = pcmbuf_head->time; + int32_t offset = time - (clock_base + clock_adjust); + + sz = pcmbuf_head->size; + + if (sz < (ssize_t)(sizeof(pcmbuf_head) + 4) || + (sz & 3) != 0) + { + /* Just show a warning about this - will never happen + * without a bug in the audio thread code or a clobbered + * buffer */ + DEBUGF("get_more: invalid size (%ld)\n", sz); + } + + if (offset < -100*CLOCK_RATE/1000) + { + /* Frame more than 100ms late - drop it */ + pcm_advance_buffer(&pcmbuf_head, sz); + pcmbuf_read += sz; + if (pcmbuf_read < pcmbuf_written) + continue; + } + else if (offset < 100*CLOCK_RATE/1000) + { + /* Frame less than 100ms early - play it */ + *start = (unsigned char *)pcmbuf_head->data; + + pcm_advance_buffer(&pcmbuf_head, sz); + pcmbuf_read += sz; + + sz -= sizeof (struct pcm_frame_header); + + *size = sz; + + /* Audio is time master - keep clock synchronized */ + clock_adjust = time - clock_base; + + /* Update base clock */ + clock_base += sz >> 2; + return; + } + /* Frame will be dropped - play silence clip */ + break; + } + } + else + { + /* Ran out so revert to default watermark */ + pcmbuf_threshold = PCMOUT_PLAY_WM; + } + + /* Keep clock going at all times */ + *start = (unsigned char *)silence; + *size = sizeof (silence); + + clock_base += sizeof (silence) / 4; + + if (pcmbuf_read > pcmbuf_written) + pcmbuf_read = pcmbuf_written; +} + +struct pcm_frame_header * pcm_output_get_buffer(void) +{ + return pcmbuf_tail; +} + +void pcm_output_add_data(void) +{ + size_t size = pcmbuf_tail->size; + pcm_advance_buffer(&pcmbuf_tail, size); + pcmbuf_written += size; +} + +/* Flushes the buffer - clock keeps counting */ +void pcm_output_flush(void) +{ + rb->pcm_play_lock(); + + pcmbuf_threshold = PCMOUT_PLAY_WM; + pcmbuf_read = pcmbuf_written = 0; + pcmbuf_head = pcmbuf_tail = pcm_buffer; + + rb->pcm_play_unlock(); +} + +/* 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_base = time; + clock_start = time; + clock_adjust = 0; + + rb->pcm_play_unlock(); +} + +uint32_t pcm_output_get_clock(void) +{ + return clock_base + clock_adjust + - (rb->pcm_get_bytes_waiting() >> 2); +} + +uint32_t pcm_output_get_ticks(uint32_t *start) +{ + if (start) + *start = clock_start; + + return clock_base - (rb->pcm_get_bytes_waiting() >> 2); +} + +/* Pauses/Starts pcm playback - and the clock */ +void pcm_output_play_pause(bool play) +{ + rb->pcm_play_lock(); + + if (rb->pcm_is_playing()) + { + rb->pcm_play_pause(play); + } + else if (play) + { + rb->pcm_play_data(get_more, NULL, 0); + } + + rb->pcm_play_unlock(); +} + +/* Stops all playback and resets the clock */ +void pcm_output_stop(void) +{ + rb->pcm_play_lock(); + + if (rb->pcm_is_playing()) + rb->pcm_play_stop(); + + pcm_output_flush(); + pcm_output_set_clock(0); + + rb->pcm_play_unlock(); +} + +/* 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_threshold = PCMOUT_PLAY_WM; + pcmbuf_head = pcm_buffer; + pcmbuf_tail = pcm_buffer; + pcmbuf_end = SKIPBYTES(pcm_buffer, PCMOUT_BUFSIZE); + pcmbuf_read = 0; + pcmbuf_written = 0; + + rb->pcm_set_frequency(SAMPR_44); + +#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 = 32767 / 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 + + return true; +} + +void pcm_output_exit(void) +{ + rb->pcm_set_frequency(HW_SAMPR_DEFAULT); +} diff --git a/apps/plugins/mpegplayer/pcm_output.h b/apps/plugins/mpegplayer/pcm_output.h new file mode 100644 index 0000000000..8a230b87a5 --- /dev/null +++ b/apps/plugins/mpegplayer/pcm_output.h @@ -0,0 +1,46 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * PCM output buffer declarations + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 + +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); +struct pcm_frame_header * pcm_output_get_buffer(void); +void pcm_output_add_data(void); +ssize_t pcm_output_used(void); +ssize_t pcm_output_free(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..00b96173b6 --- /dev/null +++ b/apps/plugins/mpegplayer/stream_mgr.c @@ -0,0 +1,1096 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * AV stream manager implementation + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 "gray.h" +#include "mpeg_settings.h" + +static struct event_queue stream_mgr_queue NOCACHEBSS_ATTR; +static struct queue_sender_list stream_mgr_queue_send NOCACHEBSS_ATTR; +static uint32_t stream_mgr_thread_stack[DEFAULT_STACK_SIZE*2/sizeof(uint32_t)]; + +struct stream_mgr stream_mgr NOCACHEBSS_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 = 0; + stream_mgr.seeked = false; +} + +/* Add a stream to the playback pool */ +void stream_add_stream(struct stream *str) +{ + actl_lock(); + + list_remove_item(&str->l); + list_add_item(&stream_mgr.strl, &str->l); + + actl_unlock(); +} + +/* Callback for various list-moving operations */ +static bool strl_enum_callback(struct list_item *item, intptr_t data) +{ + actl_lock(); + + list_remove_item(item); + + if (data == 1) + list_add_item(&stream_mgr.actl, item); + + actl_unlock(); + + return true; +} + +/* Clear all streams from active and playback pools */ +void stream_remove_streams(void) +{ + list_enum_items(&stream_mgr.strl, strl_enum_callback, 0); +} + +/* Move the playback pool to the active list */ +void move_strl_to_actl(void) +{ + list_enum_items(&stream_mgr.strl, strl_enum_callback, 1); +} + +/* Remove a stream from the active list and return it to the pool */ +static bool actl_stream_remove(struct stream *str) +{ + if (list_is_member(&stream_mgr.actl, &str->l)) + { + actl_lock(); + + list_remove_item(&str->l); + list_add_item(&stream_mgr.strl, &str->l); + + actl_unlock(); + return true; + } + + return false; +} + +/* Broadcast a message to all active streams */ +static bool actl_stream_broadcast_callback(struct list_item *item, + struct str_broadcast_data *sbd) +{ + struct stream *str = TYPE_FROM_MEMBER(struct stream, item, l); + + switch (sbd->cmd) + { + case STREAM_PLAY: + case STREAM_PAUSE: + break; + + case STREAM_STOP: + if (sbd->data != 0) + { + actl_lock(); + + list_remove_item(item); + list_add_item(&stream_mgr.strl, item); + + 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, + (intptr_t)&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)); +} + +/* Return the play time relative to the specified play time */ +static uint32_t time_from_whence(uint32_t time, int whence) +{ + int64_t currtime; + + 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) */ + if (stream_mgr.seeked) + currtime = str_parser.last_seek_time; + else + currtime = TICKS_TO_TS(pcm_output_get_clock()); + + currtime -= str_parser.start_pts; + 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) +{ + /* seek start time */ + bool was_buffering; + + if (status == STREAM_PLAYING) + { + /* Keep clock from advancing while seeking */ + pcm_output_play_pause(false); + } + + /* Place streams in a non-running state - keep them on actl */ + actl_stream_broadcast(STREAM_STOP, 0); + + /* Stop all buffering or else risk clobbering random-access data */ + was_buffering = disk_buf_send_msg(STREAM_STOP, 0); + + time = time_from_whence(time, whence); + time = parser_seek_time(time); + + if (status == STREAM_PLAYING) + { + /* Restart streams if currently playing */ + + /* Clear any seeked status */ + stream_mgr.seeked = false; + + /* Flush old PCM data */ + pcm_output_flush(); + + /* Set the master clock */ + set_stream_clock(time); + + /* Prepare the parser and associated streams */ + parser_prepare_streaming(); + + /* Start buffer using previous buffering status */ + disk_buf_send_msg(STREAM_PLAY, was_buffering); + + /* 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); + } + else + { + /* Performed the seek - leave it at that until restarted */ + stream_mgr.seeked = true; + } + + return time; +} + +/* 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 */ + start = str_parser.last_seek_time - str_parser.start_pts; + stream_mgr.resume_time = start; + + start = stream_seek_intl(start, SEEK_SET, STREAM_STOPPED); + + /* Fill list of all streams that will be playing */ + move_strl_to_actl(); + + /* Clear any seeked status */ + stream_mgr.seeked = false; + + /* Set the master clock */ + set_stream_clock(start); + + /* Prepare the parser and associated streams */ + parser_prepare_streaming(); + + /* Force buffering */ + disk_buf_send_msg(STREAM_PLAY, true); + + /* 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); + } + 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(); + + if (stream_mgr.seeked) + { + /* Have to give the parser notice to sync up streams */ + stream_mgr.seeked = false; + + /* Flush old PCM data */ + pcm_output_flush(); + + /* Set the master clock */ + set_stream_clock(str_parser.last_seek_time); + + /* Prepare the parser and associated streams */ + parser_prepare_streaming(); + } + + /* Don't force buffering */ + disk_buf_send_msg(STREAM_PLAY, false); + + /* 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); + + /* 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) + { + /* Not stopped = paused or playing */ + stream_mgr.seeked = false; + + /* Pause the clock */ + pcm_output_play_pause(false); + + if (stream_can_seek()) + { + /* Read the current stream time */ + uint32_t time = TICKS_TO_TS(pcm_output_get_clock()); + + /* Assume invalidity */ + stream_mgr.resume_time = 0; + + if (time >= str_parser.start_pts && time < str_parser.end_pts) + { + /* Save the current stream time */ + stream_mgr.resume_time = time - str_parser.start_pts; + } + } + + /* 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; + + stream_mgr_reply_msg(STREAM_OK); + + stream_keep_disk_active(); + + stream_mgr_lock(); + + if (!stream_can_seek()) + { + } + else if (stream_mgr.status != STREAM_STOPPED) + { + if (stream_mgr.status != STREAM_PLAYING) + { + trigger_cpu_boost(); + } + + stream_seek_intl(time, whence, stream_mgr.status); + + if (stream_mgr.status != STREAM_PLAYING) + { + cancel_cpu_boost(); + } + } + else + { + stream_mgr.seeked = true; + time = time_from_whence(time, whence); + parser_seek_time(time); + } + + stream_mgr_unlock(); + return; + } + + 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? */ + 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(); + /* Close file */ + disk_buf_close(); + /* Reinitialize manager */ + stream_mgr_init_state(); + } + + 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; + } +} + +/* Show/hide the video output */ +bool stream_show_vo(bool show) +{ + bool vis; + stream_mgr_lock(); + + vis = parser_send_video_msg(VIDEO_DISPLAY_SHOW, show); +#ifndef HAVE_LCD_COLOR + GRAY_VIDEO_FLUSH_ICACHE(); + GRAY_INVALIDATE_ICACHE(); + gray_show(show); + GRAY_FLUSH_ICACHE(); +#endif + stream_mgr_unlock(); + + return vis; +} + +/* Query the visibility of video output */ +bool stream_vo_is_visible(void) +{ + bool vis; + stream_mgr_lock(); + vis = parser_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; +} + +#ifndef HAVE_LCD_COLOR +/* Set the rectangle for the gray video overlay - clipped to screen */ +bool stream_set_gray_rect(const struct vo_rect *rc) +{ + bool retval = false; + struct vo_rect rc_gray; + + stream_mgr_lock(); + + vo_rect_set_ext(&rc_gray, 0, 0, LCD_WIDTH, LCD_HEIGHT); + + if (vo_rect_intersect(&rc_gray, &rc_gray, rc)) + { + bool vo_vis = stream_show_vo(false); + + GRAY_VIDEO_FLUSH_ICACHE(); + GRAY_INVALIDATE_ICACHE(); + + gray_init(rb, stream_mgr.graymem, stream_mgr.graysize, + false, rc_gray.r - rc_gray.l, rc_gray.b - rc_gray.t, + 32, 2<<8, NULL); + + gray_set_position(rc_gray.l, rc_gray.t); + GRAY_FLUSH_ICACHE(); + + if (vo_vis) + { + stream_show_vo(true); + } + } + + stream_mgr_unlock(); + + return retval; +} + +/* Show/hide the gray video overlay (independently of vo visibility). */ +void stream_gray_show(bool show) +{ + stream_mgr_lock(); + + GRAY_VIDEO_FLUSH_ICACHE(); + GRAY_INVALIDATE_ICACHE(); + gray_show(show); + GRAY_FLUSH_ICACHE(); + + stream_mgr_unlock(); +} +#endif + +/* 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 = parser_send_video_msg(VIDEO_PRINT_THUMBNAIL, + (intptr_t)&stream_mgr.parms.rc); + + 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; +} + +/* Returns the smallest file window that includes all active streams' + * windows */ +static bool stream_get_window_callback(struct list_item *item, + struct stream_window *sw) +{ + struct stream *str = TYPE_FROM_MEMBER(struct stream, item, l); + 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, + (intptr_t)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 != NULL) + 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 != NULL) + return stream_mgr_send_msg(STREAM_PLAY, 0); + return STREAM_ERROR; +} + +/* Pauses playback if playing */ +int stream_pause(void) +{ + if (stream_mgr.thread != NULL) + return stream_mgr_send_msg(STREAM_PAUSE, false); + return STREAM_ERROR; +} + +/* Resumes playback if paused */ +int stream_resume(void) +{ + if (stream_mgr.thread != NULL) + return stream_mgr_send_msg(STREAM_PAUSE, true); + return STREAM_ERROR; +} + +/* Stops playback if not stopped */ +int stream_stop(void) +{ + if (stream_mgr.thread != NULL) + 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 == NULL) + 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 != NULL) + 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(); + list_initialize(&stream_mgr.actl); + + /* 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); + rb->queue_enable_queue_send(stream_mgr.q, &stream_mgr_queue_send); + + /* sets audiosize and returns buffer pointer */ + mem = rb->plugin_get_audio_buffer(&memsize); + + /* Initialize non-allocator blocks first */ +#ifndef HAVE_LCD_COLOR + int grayscales; + + /* This can run on another processor - align data */ + memsize = CACHEALIGN_BUFFER(&mem, memsize); + stream_mgr.graymem = mem; + + /* initialize the grayscale buffer: 32 bitplanes for 33 shades of gray. */ + grayscales = gray_init(rb, mem, memsize, false, + LCD_WIDTH, LCD_HEIGHT, + 32, 2<<8, &stream_mgr.graysize) + 1; + + /* This can run on another processor - align size */ + stream_mgr.graysize = CACHEALIGN_UP(stream_mgr.graysize); + + mem += stream_mgr.graysize; + memsize -= stream_mgr.graysize; + + if (grayscales < 33 || (ssize_t)memsize <= 0) + { + rb->splash(HZ, "graylib init failed!"); + return STREAM_ERROR; + } +#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)); + + if (stream_mgr.thread == NULL) + { + 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 != NULL) + { + stream_mgr_post_msg(STREAM_QUIT, 0); + rb->thread_wait(stream_mgr.thread); + stream_mgr.thread = NULL; + } +} diff --git a/apps/plugins/mpegplayer/stream_mgr.h b/apps/plugins/mpegplayer/stream_mgr.h new file mode 100644 index 0000000000..63452ecbc0 --- /dev/null +++ b/apps/plugins/mpegplayer/stream_mgr.h @@ -0,0 +1,151 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * AV stream manager decalarations + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 +{ + struct thread_entry *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 */ + struct list_item strl; /* List of available streams */ + struct list_item actl; /* List of active streams */ + struct mutex str_mtx; /* Main stream manager mutex */ + struct mutex actl_mtx; /* Lock for current-streams list */ +#ifndef HAVE_LCD_COLOR + void *graymem; + size_t graysize; +#endif + union /* A place for reusable non-cacheable parameters */ + { + struct vo_rect rc; + struct stream_seek_data skd; + } parms; +}; + +extern struct stream_mgr stream_mgr NOCACHEBSS_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); + +#ifndef HAVE_LCD_COLOR +/* Set the gray overlay rectangle */ +bool stream_set_gray_rect(const struct vo_rect *rc); +void stream_gray_show(bool show); +#endif + +/* Display thumbnail of the current seekpoint */ +bool stream_display_thumb(const struct vo_rect *rc); + +/* 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); + +/* 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; } + +/* 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(); } + +/* Keep the disk spinning (for seeking and browsing) */ +static inline void stream_keep_disk_active(void) +{ +#ifndef HAVE_FLASH_STORAGE + rb->ata_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..1962a66878 --- /dev/null +++ b/apps/plugins/mpegplayer/stream_thread.h @@ -0,0 +1,192 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Declarations for stream-specific threading + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 list_item nf; /* List for data notification */ +}; + +struct stream +{ + struct stream_hdr hdr; /* Base stream data */ + struct thread_entry *thread; /* Stream's thread */ + uint8_t* curr_packet; /* Current stream packet beginning */ + uint8_t* curr_packet_end; /* Current stream packet end */ + struct list_item l; /* List of streams - either reserve pool + or active pool */ + 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 */ +}; + +/* 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 */ + TSTATE_RENDER_WAIT_END, /* is waiting on remaining data */ +}; + +/* 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 */ +#ifdef GRAY_CACHE_MAINT + VIDEO_GRAY_CACHEOP, +#endif + 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 */ +}; + +#define STR_FROM_HEADER(sh) ((struct stream *)(sh)) + +/* 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); +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 index ec3f7c65d3..08cd7aa848 100644 --- a/apps/plugins/mpegplayer/video_out.h +++ b/apps/plugins/mpegplayer/video_out.h @@ -21,7 +21,51 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#ifndef VIDEO_OUT_H +#define VIDEO_OUT_H + +/* 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); -void vo_draw_frame_thumb (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_dimensions(struct vo_ext *sz); void vo_cleanup (void); + +/* 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); + +#endif /* VIDEO_OUT_H */ diff --git a/apps/plugins/mpegplayer/video_out_rockbox.c b/apps/plugins/mpegplayer/video_out_rockbox.c index 9dd8d6a467..d5e927e9f1 100644 --- a/apps/plugins/mpegplayer/video_out_rockbox.c +++ b/apps/plugins/mpegplayer/video_out_rockbox.c @@ -1,189 +1,404 @@ -/* - * video_out_null.c - * Copyright (C) 2000-2003 Michel Lespinasse - * Copyright (C) 1999-2000 Aaron Holtzman +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ * - * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. - * See http://libmpeg2.sourceforge.net/ for updates. + * mpegplayer video output routines * - * 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. + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. * - * 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. + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. * - * 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 "mpeg2dec_config.h" #include "plugin.h" -#include "gray.h" +#include "mpegplayer.h" + +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; + bool visible; + bool thumb_mode; + void *last; +}; + +#ifdef PROC_NEEDS_CACHEALIGN +/* Cache aligned and padded to avoid clobbering other processors' cacheable + * data */ +static uint8_t __vo_data[CACHEALIGN_UP(sizeof(struct vo_data))] + CACHEALIGN_ATTR; +#define vo (*((struct vo_data *)__vo_data)) +#else +static struct vo_data vo; +#endif + +/* Draw a black rectangle if no video frame is available */ +static void vo_draw_black(void) +{ + int foreground = lcd_(get_foreground)(); -extern struct plugin_api* rb; + lcd_(set_foreground)(DRAW_BLACK); -#include "mpeg2.h" -#include "video_out.h" + lcd_(fillrect)(vo.output_x, vo.output_y, vo.output_width, + vo.output_height); + lcd_(update_rect)(vo.output_x, vo.output_y, vo.output_width, + vo.output_height); -static int image_width; -static int image_height; -static int image_chroma_x; -static int image_chroma_y; -static int output_x; -static int output_y; -static int output_width; -static int output_height; + lcd_(set_foreground)(foreground); +} -void vo_draw_frame (uint8_t * const * buf) +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) { #ifdef HAVE_LCD_COLOR - rb->lcd_yuv_blit(buf, 0,0,image_width, - output_x,output_y,output_width,output_height); + rb->lcd_yuv_blit(buf, src_x, src_y, stride, x, y , width, height); #else - gray_ub_gray_bitmap_part(buf[0],0,0,image_width, - output_x,output_y,output_width,output_height); + gray_ub_gray_bitmap_part(buf[0], src_x, src_y, stride, x, y, width, height); #endif } +void vo_draw_frame(uint8_t * const * buf) +{ + if (!vo.visible) + { + /* Frame is hidden - copout */ + DEBUGF("vo hidden\n"); + return; + } + else if (buf == NULL) + { + /* No frame exists - draw black */ + vo_draw_black(); + DEBUGF("vo no frame\n"); + return; + } + + yuv_blit(buf, 0, 0, vo.image_width, + vo.output_x, vo.output_y, vo.output_width, + vo.output_height); +} + #if LCD_WIDTH >= LCD_HEIGHT #define SCREEN_WIDTH LCD_WIDTH #define SCREEN_HEIGHT LCD_HEIGHT -#else /* Assume the screen is rotates on portraid LCDs */ +#else /* Assume the screen is rotated on portrait LCDs */ #define SCREEN_WIDTH LCD_HEIGHT #define SCREEN_HEIGHT LCD_WIDTH #endif -uint8_t* tmpbufa = 0; -uint8_t* tmpbufb = 0; -uint8_t* tmpbufc = 0; -uint8_t* tmpbuf[3]; +static inline void vo_rect_clear_inl(struct vo_rect *rc) +{ + rc->l = rc->t = rc->r = rc->b = 0; +} -void vo_draw_frame_thumb (uint8_t * const * buf) +static inline bool vo_rect_empty_inl(const struct vo_rect *rc) { - int r,c; + return rc == NULL || rc->l >= rc->r || rc->t >= rc->b; +} -#if LCD_WIDTH >= LCD_HEIGHT - for (r=0;rl < 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) { - *(tmpbuf[1]+c*image_width/4+r) = - *(buf[1]+c*image_width+2*r); - *(tmpbuf[2]+c*image_width/4+r) = - *(buf[2]+c*image_width+2*r); + 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; +} + +/* 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 - for (r=0;r= 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; -rb->lcd_clear_display(); -rb->lcd_update(); + if (dst >= dst_line_end) + { + dw = dst_w; + break; + } -#ifdef HAVE_LCD_COLOR -#ifdef SIMULATOR #if LCD_WIDTH >= LCD_HEIGHT - rb->lcd_yuv_blit(tmpbuf,0,0,image_width/2, - (LCD_WIDTH-1-image_width/2)/2, - LCD_HEIGHT-50-(image_height/2), - output_width/2,output_height/2); - + s += qw; #else - rb->lcd_yuv_blit(tmpbuf,0,0,image_height/2, - LCD_HEIGHT-50-(image_height/2), - (LCD_WIDTH-1-image_width/2)/2, - output_height/2,output_width/2); + s += qw*stride; #endif -#else + dw += rw; + + if (dw >= dst_w2) + { + dw -= dst_w2; #if LCD_WIDTH >= LCD_HEIGHT - rb->lcd_yuv_blit(tmpbuf,0,0,image_width/2, - (LCD_WIDTH-1-image_width/2)/2, - LCD_HEIGHT-50-(image_height/2), - output_width/2,output_height/2); + s++; #else - rb->lcd_yuv_blit(tmpbuf,0,0,image_height/2, - LCD_HEIGHT-50-(image_height/2), - (LCD_WIDTH-1-image_width/2)/2, - output_height/2,output_width/2); -#endif + 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 - gray_ub_gray_bitmap_part(tmpbuf[0],0,0,image_width/2, - (LCD_WIDTH-1-image_width/2)/2, - LCD_HEIGHT-50-(image_height/2), - output_width/2,output_height/2); + src += stride; #else - gray_ub_gray_bitmap_part(tmpbuf[0],0,0,image_height/2, - LCD_HEIGHT-50-(image_height/2), - (LCD_WIDTH-1-image_width/2)/2, - output_height/2,output_width/2); + src--; +#endif + } + } +} + +bool vo_draw_frame_thumb(uint8_t * const * buf, const struct vo_rect *rc) +{ + void *mem; + size_t bufsize; + uint8_t *yuv[3]; + struct vo_rect thumb_rc; + int thumb_width, thumb_height; + int thumb_uv_width, thumb_uv_height; + + if (buf == NULL) + return false; + + /* Obtain rectangle as clipped to the screen */ + vo_rect_set_ext(&thumb_rc, 0, 0, LCD_WIDTH, LCD_HEIGHT); + if (!vo_rect_intersect(&thumb_rc, rc, &thumb_rc)) + return true; + + DEBUGF("thumb_rc: %d, %d, %d, %d\n", thumb_rc.l, thumb_rc.t, + thumb_rc.r, thumb_rc.b); + + thumb_width = rc->r - rc->l; + thumb_height = rc->b - rc->t; + thumb_uv_width = thumb_width / 2; + thumb_uv_height = thumb_height / 2; + + DEBUGF("thumb: w: %d h: %d uvw: %d uvh: %d\n", thumb_width, + thumb_height, thumb_uv_width, thumb_uv_height); + + /* Use remaining mpeg2 buffer as temp space */ + mem = mpeg2_get_buf(&bufsize); + + if (bufsize < (size_t)(thumb_width*thumb_height) +#ifdef HAVE_LCD_COLOR + + 2u*(thumb_uv_width * thumb_uv_height) #endif + ) + { + DEBUGF("thumb: insufficient buffer\n"); + return false; + } + + yuv[0] = mem; + stretch_image_plane(buf[0], yuv[0], vo.image_width, + vo.display_width, vo.display_height, + thumb_width, thumb_height); + +#ifdef HAVE_LCD_COLOR + yuv[1] = yuv[0] + thumb_width*thumb_height; + yuv[2] = yuv[1] + thumb_uv_width*thumb_uv_height; + + stretch_image_plane(buf[1], yuv[1], vo.image_width / 2, + vo.display_width / 2, vo.display_height / 2, + thumb_uv_width, thumb_uv_height); + + stretch_image_plane(buf[2], yuv[2], vo.image_width / 2, + vo.display_width / 2, vo.display_height / 2, + thumb_uv_width, thumb_uv_height); #endif + +#if LCD_WIDTH >= LCD_HEIGHT + yuv_blit(yuv, 0, 0, thumb_width, + thumb_rc.l, thumb_rc.t, + thumb_rc.r - thumb_rc.l, + thumb_rc.b - thumb_rc.t); +#else + yuv_blit(yuv, 0, 0, thumb_height, + thumb_rc.t, thumb_rc.l, + thumb_rc.b - thumb_rc.t, + thumb_rc.r - thumb_rc.l); +#endif /* LCD_WIDTH >= LCD_HEIGHT */ + + return true; } void vo_setup(const mpeg2_sequence_t * sequence) { - image_width=sequence->width; - image_height=sequence->height; - - tmpbufa = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width* - image_height/4, -2); - tmpbufb = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width* - image_height/16, -2); - tmpbufc = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width* - image_height/16, -2); - tmpbuf[0] = tmpbufa; - tmpbuf[1] = tmpbufb; - tmpbuf[2] = tmpbufc; - - image_chroma_x=image_width/sequence->chroma_width; - image_chroma_y=image_height/sequence->chroma_height; - - if (sequence->display_width >= SCREEN_WIDTH) { - output_width = SCREEN_WIDTH; - output_x = 0; - } else { - output_width = sequence->display_width; - output_x = (SCREEN_WIDTH-sequence->display_width)/2; + 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.output_width = SCREEN_WIDTH; + vo.output_x = 0; + } + else + { + vo.output_width = sequence->display_width; + vo.output_x = (SCREEN_WIDTH - sequence->display_width) / 2; } - if (sequence->display_height >= SCREEN_HEIGHT) { - output_height = SCREEN_HEIGHT; - output_y = 0; - } else { - output_height = sequence->display_height; - output_y = (SCREEN_HEIGHT-sequence->display_height)/2; + if (sequence->display_height >= SCREEN_HEIGHT) + { + vo.output_height = SCREEN_HEIGHT; + vo.output_y = 0; + } + else + { + vo.output_height = sequence->display_height; + vo.output_y = (SCREEN_HEIGHT - sequence->display_height) / 2; } } +void vo_dimensions(struct vo_ext *sz) +{ + sz->w = vo.display_width; + sz->h = vo.display_height; +} + +bool vo_init(void) +{ + vo.visible = false; + return true; +} + +bool vo_show(bool show) +{ + bool vis = vo.visible; + vo.visible = show; + return vis; +} + +bool vo_is_visible(void) +{ + return vo.visible; +} + void vo_cleanup(void) { - if (tmpbufc) - mpeg2_free(tmpbufc); - if (tmpbufb) - mpeg2_free(tmpbufb); - if (tmpbufa) - mpeg2_free(tmpbufa); + vo.visible = false; +#ifndef HAVE_LCD_COLOR + gray_release(); +#endif } diff --git a/apps/plugins/mpegplayer/video_thread.c b/apps/plugins/mpegplayer/video_thread.c new file mode 100644 index 0000000000..e69089d734 --- /dev/null +++ b/apps/plugins/mpegplayer/video_thread.c @@ -0,0 +1,1040 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * mpegplayer video thread implementation + * + * Copyright (c) 2007 Michael Sevakis + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * 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 "mpeg2dec_config.h" +#include "gray.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 +{ + 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 */ + int num_drawn; /* Number of frames drawn since reset */ + int num_skipped; /* Number of frames skipped since reset */ + uint32_t curr_time; /* Current due time of frame */ + uint32_t period; /* Frame period in clock ticks */ + uint32_t eta_stream; /* Current time of stream */ + uint32_t eta_video; /* Time that frame has been scheduled for */ + int32_t eta_early; /* How early has the frame been decoded? */ + int32_t eta_late; /* How late has the frame been decoded? */ + int frame_drop_level; /* Drop severity */ + int skip_level; /* Skip severity */ + long last_showfps; /* Last time the FPS display was updated */ + long last_render; /* Last time a frame was drawn */ + int syncf_perfect; /* Last sync fit result */ + uint32_t syncf_time; /* PTS of last synced frame */ + uint32_t syncf_period; /* TS duration of last synced frame */ +}; + +/* 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 NOCACHEBSS_ATTR; +static struct queue_sender_list video_str_queue_send NOCACHEBSS_ATTR; +struct stream video_str IBSS_ATTR; + +static void draw_fps(struct video_thread_data *td) +{ + uint32_t start; + uint32_t clock_ticks = stream_get_ticks(&start); + int fps = 0; + char str[80]; + + clock_ticks -= start; + if (clock_ticks != 0) + fps = muldiv_uint32(CLOCK_RATE*100, td->num_drawn, clock_ticks); + + rb->snprintf(str, sizeof(str), "%d.%02d %d %d ", + fps / 100, fps % 100, td->num_skipped, + td->info->display_picture->temporal_reference); + rb->lcd_putsxy(0, 0, str); + rb->lcd_update_rect(0, 0, LCD_WIDTH, 8); + + td->last_showfps = *rb->current_tick; +} + +#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; + + mpeg2_reset(td->mpeg2dec, false); + 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 syncf_end; + + 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); + syncf_end = td->syncf_time + td->syncf_period; + + DEBUGF(" sft:%u t:%u sfte:%u\n", (unsigned)td->syncf_time, + (unsigned)time, (unsigned)syncf_end); + + if (time < td->syncf_time) + return true; + + if (time >= syncf_end) + return time < video_str.end_pts || syncf_end < 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; + int ipic = 0, ppic = 0; + uint32_t time = clip_time(&video_str, sd->time); + + td->syncf_perfect = 0; + td->syncf_time = 0; + td->syncf_period = 0; + td->curr_time = 0; + td->period = 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); + rb->yield(); + + 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); + ipic = 1; + break; + case PIC_FLAG_CODING_TYPE_P: + /* P-frames don't count without I-frames */ + ppic = ipic; + 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 syncf_end; + + 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->syncf_time = td->info->display_picture->tag; + DEBUGF(" frame tagged:%u (%c%s)\n", (unsigned)td->syncf_time, + pic_coding_type_char(type), + (td->info->display_picture->flags & PIC_FLAG_SKIP) ? + " skipped" : ""); + } + else + { + td->syncf_time += td->syncf_period; + DEBUGF(" add period:%u (%c%s)\n", (unsigned)td->syncf_time, + pic_coding_type_char(type), + (td->info->display_picture->flags & PIC_FLAG_SKIP) ? + " skipped" : ""); + } + + td->syncf_period = TC_TO_TS(td->info->sequence->frame_period); + syncf_end = td->syncf_time + td->syncf_period; + + DEBUGF(" ft:%u t:%u fe:%u (%c%s)", + (unsigned)td->syncf_time, + (unsigned)time, + (unsigned)(td->syncf_time + td->syncf_period), + pic_coding_type_char(type), + (td->info->display_picture->flags & PIC_FLAG_SKIP) ? + " skipped" : ""); + + td->curr_time = TS_TO_TICKS(td->syncf_time); + td->period = TS_TO_TICKS(td->syncf_period); + + if (syncf_end <= time && syncf_end < 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 */ + td->syncf_perfect = ipic; + + if (type == PIC_FLAG_CODING_TYPE_B) + td->syncf_perfect &= ppic; + + if ((td->syncf_time <= time && time < syncf_end) || + syncf_end >= 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; +} + +/* 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_RENDER_WAIT: + /* Settings may have changed to nonlimited - just draw + * what was previously being waited for */ + if (!settings.limitfps) + td->state = TSTATE_RENDER; + case TSTATE_DECODE: + case TSTATE_RENDER: + break; + + case TSTATE_INIT: + /* Begin decoding state */ + td->state = TSTATE_DECODE; + 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(&audio_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) + { + if (td->info != NULL && td->info->display_fbuf != NULL) + vo_draw_frame(td->info->display_fbuf->buf); + } + else + { + IF_COP(invalidate_icache()); + rb->lcd_update(); + } +#else + GRAY_FLUSH_ICACHE(); +#endif + break; + + case STREAM_RESET: + if (td->state == TSTATE_DATA) + stream_clear_notify(&audio_str, DISK_BUF_DATA_NOTIFY); + + td->state = TSTATE_INIT; + td->status = STREAM_STOPPED; + + /* Reset operational info but not sync info */ + td->eta_stream = UINT32_MAX; + td->eta_video = 0; + td->eta_early = 0; + td->eta_late = 0; + td->frame_drop_level = 0; + td->skip_level = 0; + td->num_drawn = 0; + td->num_skipped = 0; + td->last_showfps = *rb->current_tick - HZ; + td->last_render = td->last_showfps; + + 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_THUMBNAIL: + /* Print a thumbnail of whatever was last decoded - scale and + * position to fill the specified rectangle */ + if (td->info != NULL && td->info->display_fbuf != NULL) + { + vo_draw_frame_thumb(td->info->display_fbuf->buf, + (struct vo_rect *)td->ev.data); + reply = true; + } + break; + + case VIDEO_PRINT_FRAME: + /* Print the last frame decoded */ + if (td->info != NULL && td->info->display_fbuf != NULL) + { + vo_draw_frame(td->info->display_fbuf->buf); + reply = true; + } + break; + + case VIDEO_GET_SIZE: + { + if (td->state != TSTATE_INIT) + break; + + 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; + +#ifdef GRAY_CACHE_MAINT + case VIDEO_GRAY_CACHEOP: + td->ev.data ? + GRAY_INVALIDATE_ICACHE() : + GRAY_FLUSH_ICACHE(); + break; +#endif + + 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; + + td.status = STREAM_STOPPED; + td.state = TSTATE_EOS; + td.mpeg2dec = mpeg2_init(); + td.info = NULL; + td.syncf_perfect = 0; + td.syncf_time = 0; + td.syncf_period = 0; + + 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: + vo_cleanup(); + mpeg2_close(td.mpeg2dec); + return; + } + } + + picture_decode: + mp2state = mpeg2_parse (td.mpeg2dec); + rb->yield(); + + 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: + { + int skip = 0; /* Assume no skip */ + + if (td.frame_drop_level >= 1 || td.skip_level > 0) + { + /* A frame will be dropped in the decoder */ + + /* Frame type: I/P/B/D */ + int type = td.info->current_picture->flags + & PIC_MASK_CODING_TYPE; + + switch (type) + { + case PIC_FLAG_CODING_TYPE_I: + case PIC_FLAG_CODING_TYPE_D: + /* Level 5: Things are extremely late and all frames will + be dropped until the next key frame */ + if (td.frame_drop_level >= 1) + td.frame_drop_level = 0; /* Key frame - reset drop level */ + if (td.skip_level >= 5) + { + td.frame_drop_level = 1; + td.skip_level = 0; /* reset */ + } + break; + case PIC_FLAG_CODING_TYPE_P: + /* Level 4: Things are very late and all frames will be + dropped until the next key frame */ + if (td.skip_level >= 4) + { + td.frame_drop_level = 1; + td.skip_level = 0; /* reset */ + } + 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. Bring the level down as skips are done. */ + skip = 1; + if (td.skip_level > 0) + td.skip_level--; + } + + skip |= td.frame_drop_level; + } + + mpeg2_skip(td.mpeg2dec, skip); + break; + } + + case STATE_SLICE: + case STATE_END: + case STATE_INVALID_END: + { + int32_t offset; /* Tick adjustment to keep sync */ + + /* draw current picture */ + 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 */ + td.curr_time = (td.info->display_picture->flags & PIC_FLAG_TAGS) ? + TS_TO_TICKS(td.info->display_picture->tag) : + (td.curr_time + td.period); + + td.period = TC_TO_TICKS(td.info->sequence->frame_period); + + /* No limiting => no dropping - draw this frame */ + if (!settings.limitfps) + { + goto picture_draw; + } + + td.eta_video = td.curr_time; + td.eta_stream = stream_get_time(); + + /* How early/late are we? > 0 = late, < 0 early */ + offset = td.eta_stream - td.eta_video; + + if (!settings.skipframes) + { + /* Make no effort to determine whether this frame should be + drawn or not since no action can be taken to correct the + situation. We'll just wait if we're early and correct for + lateness as much as possible. */ + if (offset < 0) + offset = 0; + + td.eta_late = AVERAGE(td.eta_late, offset, 4); + offset = td.eta_late; + + if ((uint32_t)offset > td.eta_video) + offset = td.eta_video; + + td.eta_video -= offset; + 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 - will be I/D/P + * P decoder must wait for I/D-frame - choppy + * I/D decoder must wait for I/D-frame - choppy + * + * 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 we're late, set the eta to play the frame early so + we may catch up. If early, especially because of a drop, + mitigate a "snap" by moving back gradually. */ + if (offset >= 0) /* late or on time */ + { + td.eta_early = 0; /* Not early now :( */ + + td.eta_late = AVERAGE(td.eta_late, offset, 4); + offset = td.eta_late; + + if ((uint32_t)offset > td.eta_video) + offset = td.eta_video; + + td.eta_video -= offset; + } + else + { + td.eta_late = 0; /* Not late now :) */ + + if (offset > td.eta_early) + { + /* Just dropped a frame and we're now early or we're + coming back from being early */ + td.eta_early = offset; + if ((uint32_t)-offset > td.eta_video) + offset = -td.eta_video; + + td.eta_video += offset; + } + else + { + /* Just early with an offset, do exponential drift back */ + if (td.eta_early != 0) + { + td.eta_early = AVERAGE(td.eta_early, 0, 8); + td.eta_video = ((uint32_t)-td.eta_early > td.eta_video) ? + 0 : (td.eta_video + td.eta_early); + } + + offset = td.eta_early; + } + } + + if (td.info->display_picture->flags & PIC_FLAG_SKIP) + { + /* This frame was set to skip so skip it after having updated + timing information */ + td.num_skipped++; + td.eta_early = INT32_MIN; + goto picture_skip; + } + + if (td.skip_level == 3 && + TIME_BEFORE(*rb->current_tick, td.last_render + HZ/2)) + { + /* Render drop was set previously but nothing was dropped in the + decoder or it's been to long since drawing the last frame. */ + td.skip_level = 0; + td.num_skipped++; + td.eta_early = INT32_MIN; + goto picture_skip; + } + + /* At this point a frame _will_ be drawn - a skip may happen on + the next however */ + td.skip_level = 0; + + if (offset > CLOCK_RATE*110/1000) + { + /* Decide which skip level is needed in order to catch up */ + + /* TODO: Calculate this rather than if...else - this is rather + exponential though */ + if (offset > CLOCK_RATE*367/1000) + td.skip_level = 5; /* Decoder skip: I/D */ + if (offset > CLOCK_RATE*233/1000) + td.skip_level = 4; /* Decoder skip: P */ + else if (offset > CLOCK_RATE*167/1000) + td.skip_level = 3; /* Render skip */ + else if (offset > CLOCK_RATE*133/1000) + td.skip_level = 2; /* Decoder skip: B */ + else + td.skip_level = 1; /* Decoder skip: B */ + } + + picture_wait: + td.state = TSTATE_RENDER_WAIT; + + /* Wait until time catches up */ + while (td.eta_video > td.eta_stream) + { + /* Watch for messages while waiting for the frame time */ + int32_t eta_remaining = td.eta_video - td.eta_stream; + if (eta_remaining > CLOCK_RATE/HZ) + { + /* Several ticks to wait - do some sleeping */ + int timeout = (eta_remaining - HZ) / (CLOCK_RATE/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->priority_yield(); + if (str_have_msg(&video_str)) + goto message_wait; + } + + td.eta_stream = stream_get_time(); + } + + picture_draw: + /* Record last frame time */ + td.last_render = *rb->current_tick; + vo_draw_frame(td.info->display_fbuf->buf); + td.num_drawn++; + + picture_skip: + if (!settings.showfps) + break; + + if (TIME_BEFORE(*rb->current_tick, td.last_showfps + HZ)) + break; + + /* Calculate and display fps */ + draw_fps(&td); + break; + } + + default: + break; + } + + rb->yield(); + } /* end while */ +} + +/* Initializes the video thread */ +bool video_thread_init(void) +{ + intptr_t rep; + + IF_COP(flush_icache()); + + video_str.hdr.q = &video_str_queue; + rb->queue_init(video_str.hdr.q, false); + rb->queue_enable_queue_send(video_str.hdr.q, &video_str_queue_send); + + /* 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)); + + if (video_str.thread == NULL) + return false; + + /* Wait for thread to initialize */ + rep = str_send_msg(&video_str, STREAM_NULL, 0); + IF_COP(invalidate_icache()); + + return rep == 0; /* Normally STREAM_NULL should be ignored */ +} + +/* Terminates the video thread */ +void video_thread_exit(void) +{ + if (video_str.thread != NULL) + { + str_post_msg(&video_str, STREAM_QUIT, 0); + rb->thread_wait(video_str.thread); + IF_COP(invalidate_icache()); + video_str.thread = NULL; + } + else + { + /* Some things were done before thread creation */ +#ifndef HAVE_LCD_COLOR + gray_release(); +#endif + } +} diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index 27fae18b4c..375128e7c4 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config @@ -28,6 +28,8 @@ wav,viewers/test_codec,- bmp,apps/rockpaint,11 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 sna,viewers/zxbox,12 -- cgit v1.2.3