summaryrefslogtreecommitdiff
path: root/apps/plugins/mpegplayer
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2007-12-29 19:46:35 +0000
committerMichael Sevakis <jethead71@rockbox.org>2007-12-29 19:46:35 +0000
commita222f27c4a17ed8f9809cda7861fe5f23d4cc0c1 (patch)
treed393a23d83549f99772bb156e59ffb88725148b6 /apps/plugins/mpegplayer
parent1d0f6b90ff43776e55b4b9f062c9bea3f99aa768 (diff)
downloadrockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.zip
rockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.tar.gz
rockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.tar.bz2
rockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.tar.xz
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
Diffstat (limited to 'apps/plugins/mpegplayer')
-rw-r--r--apps/plugins/mpegplayer/SOURCES8
-rw-r--r--apps/plugins/mpegplayer/alloc.c173
-rw-r--r--apps/plugins/mpegplayer/audio_thread.c743
-rw-r--r--apps/plugins/mpegplayer/decode.c11
-rw-r--r--apps/plugins/mpegplayer/disk_buf.c906
-rw-r--r--apps/plugins/mpegplayer/disk_buf.h132
-rw-r--r--apps/plugins/mpegplayer/header.c8
-rw-r--r--apps/plugins/mpegplayer/mpeg2.h6
-rw-r--r--apps/plugins/mpegplayer/mpeg_alloc.h12
-rw-r--r--apps/plugins/mpegplayer/mpeg_linkedlist.c149
-rw-r--r--apps/plugins/mpegplayer/mpeg_linkedlist.h69
-rw-r--r--apps/plugins/mpegplayer/mpeg_misc.c96
-rw-r--r--apps/plugins/mpegplayer/mpeg_misc.h206
-rw-r--r--apps/plugins/mpegplayer/mpeg_parser.c1182
-rw-r--r--apps/plugins/mpegplayer/mpeg_settings.c529
-rw-r--r--apps/plugins/mpegplayer/mpeg_settings.h18
-rw-r--r--apps/plugins/mpegplayer/mpeg_stream.h120
-rw-r--r--apps/plugins/mpegplayer/mpegplayer.c2583
-rw-r--r--apps/plugins/mpegplayer/mpegplayer.h120
-rw-r--r--apps/plugins/mpegplayer/parser.h101
-rw-r--r--apps/plugins/mpegplayer/pcm_output.c278
-rw-r--r--apps/plugins/mpegplayer/pcm_output.h46
-rw-r--r--apps/plugins/mpegplayer/stream_mgr.c1096
-rw-r--r--apps/plugins/mpegplayer/stream_mgr.h151
-rw-r--r--apps/plugins/mpegplayer/stream_thread.h192
-rw-r--r--apps/plugins/mpegplayer/video_out.h46
-rw-r--r--apps/plugins/mpegplayer/video_out_rockbox.c479
-rw-r--r--apps/plugins/mpegplayer/video_thread.c1040
28 files changed, 7768 insertions, 2732 deletions
diff --git a/apps/plugins/mpegplayer/SOURCES b/apps/plugins/mpegplayer/SOURCES
index e7e2a7a..5b3360c 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 c798944..1ff75d4 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 0000000..ee6ff05
--- /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<fe\n");
+ retval = STREAM_PERFECT_MATCH;
+ break;
+ }
+ else if (time > 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 fac724c..2176cad 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 0000000..a408b90
--- /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 0000000..90e72fa
--- /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 52a301f..9e6e6de 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 605a353..824454f 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 0000000..9a08fd5
--- /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 0000000..74cb2cb
--- /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 0000000..17123cc
--- /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 0000000..f5ecb6d
--- /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 0000000..2da9c2e
--- /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 0000000..c996f95
--- /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<next pts\n");
+ }
+ else
+ {
+ /* See where things ended up */
+ if (pts > 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 6cd5f7b..7336507 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 613a345..3186c73 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 0000000..0c850f0
--- /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 eb904ed..03ec5ba 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 <walken@zoy.org>
- * Copyright (C) 1999-2000 Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
+ * mpegplayer main entrypoint and UI implementation
*
- * m2psd: MPEG 2 Program Stream Demultiplexer
- * Copyright (C) 2003 Eric Smith <eric@brouhaha.com>
+ * 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,908 +177,43 @@ 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)
- {
- if (spin_count < 100)
- {
- rb->yield();
- spin_count++;
- 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)
-#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)
+ /* Gently poll the video player for EOS and handle UI */
+ while (stream_status() != STREAM_STOPPED)
{
- 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;
- }
-
- /* 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;
- }
- }
+ int button = rb->button_get_w_tmo(HZ/2);
- /* 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);
-
- /* Pack header, skip it */
- if (CMP_4_CONST(p, PACK_START_CODE))
+ switch (button)
{
- 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;
- }
- }
-
- /* System header, parse and skip it - four bytes */
- if (CMP_4_CONST(p, SYSTEM_HEADER_START_CODE))
- {
- int header_length;
-
- p += 4; /*skip start code*/
- header_length = *p++ << 8;
- header_length += *p++;
-
- 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) );
-
- /* 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;
- }
-
- /* We retrieve basic infos */
- stream = p[3];
- length = (p[4] << 8) | p[5];
-
- if (stream != str->id)
- {
- /* End of stream ? */
- if (stream == 0xB9)
- {
- str->curr_packet_end = str->curr_packet = NULL;
- break;
- }
-
- /* It's not the packet we're looking for, skip it */
- p += length + 6;
+ case BUTTON_NONE:
continue;
- }
-
- /* Ok, it's our packet */
- str->curr_packet_end = p + length+6;
- header = p;
-
- if ((header[6] & 0xc0) == 0x80) /* mpeg2 */
- {
- 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);
-
- mpeg2_tag_picture (mpeg2dec, pts, dts);
- }
- else
- {
- str->curr_pts = pts;
- str->tagged = 1;
- }
- }
- }
- else /* mpeg1 */
- {
- 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;
- }
- }
- }
-
- 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;
- }
-
- 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;
- } /* end while */
- return 0;
-}
-
-/* Our clock rate in ticks/second - this won't be a constant for long */
-#define CLOCK_RATE 44100
-
-/* 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;
-}
-
-#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
-{
- uint32_t pts; /* Time stamp for packet */
- ssize_t size; /* Number of bytes left in packet */
-} pts_queue[PTS_QUEUE_LEN] __attribute__((aligned(16)));
-
- /* 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;
-
-/* 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;
-
- pts_queue_wr++;
- return true;
-}
-
-/* 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;
-
- pts_queue_rd++;
- return true;
-}
-
-/* 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];
-}
-
-/* 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];
-}
-
-/* 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;
-}
-
-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 */
-};
-
-#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;
-
-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 */
-
-static ssize_t pcmbuf_used(void)
-{
- return (ssize_t)(pcmbuf_written - pcmbuf_read);
-}
-
-static bool init_pcmbuf(void)
-{
- pcm_buffer = mpeg_malloc(PCMBUFFER_SIZE + PCMBUFFER_GUARD_SIZE, -2);
-
- if (pcm_buffer == NULL)
- return false;
-
- pcmbuf_head = pcm_buffer;
- pcmbuf_tail = pcm_buffer;
- pcmbuf_end = SKIPBYTES(pcm_buffer, PCMBUFFER_SIZE);
- pcmbuf_read = 0;
- pcmbuf_written = 0;
-
- return true;
-}
-
-/* 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);
+ {
+ int vol = rb->global_settings->volume;
+ int maxvol = rb->sound_max(SOUND_VOLUME);
if (vol < maxvol) {
vol++;
@@ -1086,6 +221,7 @@ static int button_loop(void)
rb->global_settings->volume = vol;
}
break;
+ } /* MPEG_VOLUP*: */
case MPEG_VOLDOWN:
case MPEG_VOLDOWN|BUTTON_REPEAT:
@@ -1093,8 +229,9 @@ static int button_loop(void)
case MPEG_VOLDOWN2:
case MPEG_VOLDOWN2|BUTTON_REPEAT:
#endif
- vol = rb->global_settings->volume;
- minvol = rb->sound_min(SOUND_VOLUME);
+ {
+ int vol = rb->global_settings->volume;
+ int minvol = rb->sound_min(SOUND_VOLUME);
if (vol > minvol) {
vol--;
@@ -1102,1225 +239,108 @@ static int button_loop(void)
rb->global_settings->volume = vol;
}
break;
+ } /* MPEG_VOLDOWN*: */
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;
+ {
+ int state = stream_pause(); /* save previous state */
+ int result;
-#ifndef HAVE_LCD_COLOR
- gray_show(true);
-#endif
+ /* Hide video output */
+ stream_show_vo(false);
+ backlight_use_settings(rb);
+
+ result = mpeg_menu();
/* 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;
+ 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: */
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;
+ {
+ stream_stop();
break;
+ } /* MPEG_STOP: */
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;
+ if (stream_status() == STREAM_PLAYING) {
+ /* Playing => Paused */
+ stream_pause();
+ backlight_use_settings(rb);
}
- }
-
- /** 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.length<audio_sync_time) {
- synth.pcm.length = 0;
- size = 0;
- rb->yield();
- }
-
- /* 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;
+ else if (stream_status() == STREAM_PAUSED) {
+ /* Paused => Playing */
+ backlight_force_on(rb);
+ stream_resume();
}
- 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;
- }
-
+ } /* 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;
}
- 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);
- }
+ } /* end while */
- if ( video_str.curr_packet_end == video_str.curr_packet)
- rb->splash(0, "frame not available");
-}
+ rb->lcd_setfont(FONT_UI);
-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;
+ return ret;
}
-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;
+ int err;
+ const char *errstring;
- if (parameter == NULL)
- {
+ if (parameter == NULL) {
+ /* No file = GTFO */
api->splash(HZ*2, "No File");
- return PLUGIN_ERROR;
+ return PLUGIN_ERROR;
}
+
+ /* Disable all talking before initializing IRAM */
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);
@@ -2328,239 +348,62 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
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);
+ if (stream_init() < STREAM_OK) {
+ DEBUGF("Could not initialize streams\n");
+ } else {
+ rb->splash(0, "Loading...");
- disk_buf_tail = disk_buf_start + disk_buf_len;
- file_remaining = rb->filesize(in_file);
- file_remaining -= disk_buf_len + seek_pos;
+ init_settings((char*)parameter);
- initialize_stream( &video_str, disk_buf_start, disk_buf_len, 0xe0 );
- initialize_stream( &audio_str, disk_buf_start, disk_buf_len, 0xc0 );
+ err = stream_open((char *)parameter);
- rb->mutex_init(&audio_str.msg_lock);
- rb->mutex_init(&video_str.msg_lock);
+ if (err >= STREAM_OK) {
+ /* start menu */
+ rb->lcd_clear_display();
+ rb->lcd_update();
+ result = mpeg_start_menu(stream_get_duration());
- audio_str.status = STREAM_BUFFERING;
- video_str.status = STREAM_PLAYING;
+ if (result != MPEG_START_QUIT) {
+ /* Turn off backlight timeout */
+ /* backlight control in lib/helper.c */
+ backlight_force_on(rb);
-#ifndef HAVE_LCD_COLOR
- gray_show(true);
-#endif
+ /* Enter button loop and process UI */
+ if (button_loop()) {
+ settings.resume_time = stream_get_resume_time();
+ }
- init_stream_lock();
+ /* Turn on backlight timeout (revert to settings) */
+ backlight_use_settings(rb);
+ }
-#if NUM_CORES > 1
- flush_icache();
-#endif
+ stream_close();
- /* 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;
+ rb->lcd_clear_display();
+ rb->lcd_update();
- if (MIN(audio_remaining,video_remaining) < MPEG_LOW_WATERMARK)
+ save_settings(); /* Save settings (if they have changed) */
+ status = PLUGIN_OK;
+ } else {
+ DEBUGF("Could not open %s\n", (char*)parameter);
+ switch (err)
{
-
- 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;
+ case STREAM_UNSUPPORTED:
+ errstring = "Unsupported format";
+ break;
+ default:
+ errstring = "Error opening file: %d";
}
- 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 0000000..ae1234d
--- /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 0000000..892a8a1
--- /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 0000000..281f7dd
--- /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 0000000..8a230b8
--- /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 0000000..00b9617
--- /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 0000000..63452ec
--- /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 0000000..1962a66
--- /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 ec3f7c6..08cd7aa 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 9dd8d6a..d5e927e 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 <walken@zoy.org>
- * Copyright (C) 1999-2000 Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
+/***************************************************************************
+ * __________ __ ___.
+ * 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;r<image_width/2;r++)
- for (c=0;c<image_height/2;c++)
- *(tmpbuf[0]+c*image_width/2+r) =
- *(buf[0]+2*c*image_width+2*r);
-
- for (r=0;r<image_width/4;r++)
- for (c=0;c<image_height/4;c++)
+static inline bool vo_rects_intersect_inl(const struct vo_rect *rc1,
+ const struct vo_rect *rc2)
+{
+ return !vo_rect_empty_inl(rc1) &&
+ !vo_rect_empty_inl(rc2) &&
+ rc1->l < rc2->r && rc1->r > rc2->l &&
+ rc1->t < rc2->b && rc1->b > rc2->t;
+}
+
+/* Sets all coordinates of a vo_rect to 0 */
+void vo_rect_clear(struct vo_rect *rc)
+{
+ vo_rect_clear_inl(rc);
+}
+
+/* Returns true if left >= right or top >= bottom */
+bool vo_rect_empty(const struct vo_rect *rc)
+{
+ return vo_rect_empty_inl(rc);
+}
+
+/* Initializes a vo_rect using upper-left corner and extents */
+void vo_rect_set_ext(struct vo_rect *rc, int x, int y,
+ int width, int height)
+{
+ rc->l = x;
+ rc->t = y;
+ rc->r = x + width;
+ rc->b = y + height;
+}
+
+/* Query if two rectangles intersect */
+bool vo_rects_intersect(const struct vo_rect *rc1,
+ const struct vo_rect *rc2)
+{
+ return vo_rects_intersect_inl(rc1, rc2);
+}
+
+/* Intersect two rectangles, placing the result in rc_dst */
+bool vo_rect_intersect(struct vo_rect *rc_dst,
+ const struct vo_rect *rc1,
+ const struct vo_rect *rc2)
+{
+ if (rc_dst != NULL)
{
- *(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<image_width/2;r++)
- for (c=0;c<image_height/2;c++)
- *(tmpbuf[0]+(image_width/2-1-r)*image_height/2+c) =
- *(buf[0]+2*c*image_width+2*r);
-
- for (r=0;r<image_width/4;r++)
- for (c=0;c<image_height/4;c++)
+ int src_w2 = src_w*2;
+ int dst_w2 = dst_h*2;
+ int src_h2 = src_h*2;
+ int dst_h2 = dst_w*2;
+ int qw = src_h2 / dst_w2;
+ int rw = src_h2 - qw*dst_w2;
+ int qh = src_w2 / dst_h2;
+ int rh = src_w2 - qh*dst_h2;
+ int dw = dst_h;
+ int dh = dst_w;
+
+ src += src_w - 1;
+#endif
+
+ while (1)
{
- *(tmpbuf[1]+(image_width/4-1-r)*image_height/4+c) =
- *(buf[1]+c*image_width+2*r);
- *(tmpbuf[2]+(image_width/4-1-r)*image_height/4+c) =
- *(buf[2]+c*image_width+2*r);
- }
+ const uint8_t *s = src;
+#if LCD_WIDTH >= LCD_HEIGHT
+ uint8_t * const dst_line_end = dst + dst_w;
+#else
+ uint8_t * const dst_line_end = dst + dst_h;
#endif
+ while (1)
+ {
+ *dst++ = *s;
-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 0000000..e69089d
--- /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<=t<fe\n");
+ td->syncf_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
+ }
+}