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
This commit is contained in:
Michael Sevakis 2007-12-29 19:46:35 +00:00
parent 1d0f6b90ff
commit a222f27c4a
34 changed files with 7850 additions and 2764 deletions

View file

@ -519,7 +519,7 @@ static const struct plugin_api rockbox_api = {
thread_wait,
#ifdef PROC_NEEDS_CACHEALIGN
#if (CONFIG_CODEC == SWCODEC)
align_buffer,
#endif
@ -564,6 +564,23 @@ static const struct plugin_api rockbox_api = {
find_albumart,
search_albumart_files,
#endif
#if CONFIG_CODEC == SWCODEC
pcm_play_lock,
pcm_play_unlock,
queue_enable_queue_send,
queue_empty,
queue_wait,
queue_send,
queue_reply,
#ifndef HAVE_FLASH_STORAGE
ata_spin,
#endif
#ifdef HAVE_SCHEDULER_BOOSTCTRL
trigger_cpu_boost,
cancel_cpu_boost,
#endif
#endif /* CONFIG_CODEC == SWCODEC */
};
int plugin_load(const char* plugin, void* parameter)

View file

@ -119,7 +119,7 @@
#define PLUGIN_MAGIC 0x526F634B /* RocK */
/* increase this every time the api struct changes */
#define PLUGIN_API_VERSION 91
#define PLUGIN_API_VERSION 92
/* update this to latest version if a change to the api struct breaks
backwards compatibility (and please take the opportunity to sort in any
@ -648,7 +648,7 @@ struct plugin_api {
void (*thread_wait)(struct thread_entry *thread);
#ifdef PROC_NEEDS_CACHEALIGN
#if (CONFIG_CODEC == SWCODEC)
size_t (*align_buffer)(void **start, size_t size, size_t align);
#endif
@ -697,6 +697,25 @@ struct plugin_api {
bool (*search_albumart_files)(const struct mp3entry *id3, const char *size_string,
char *buf, int buflen);
#endif
#if CONFIG_CODEC == SWCODEC
void (*pcm_play_lock)(void);
void (*pcm_play_unlock)(void);
void (*queue_enable_queue_send)(struct event_queue *q,
struct queue_sender_list *send);
bool (*queue_empty)(const struct event_queue *q);
void (*queue_wait)(struct event_queue *q, struct queue_event *ev);
intptr_t (*queue_send)(struct event_queue *q, long id,
intptr_t data);
void (*queue_reply)(struct event_queue *q, intptr_t retval);
#ifndef HAVE_FLASH_STORAGE
void (*ata_spin)(void);
#endif
#ifdef HAVE_SCHEDULER_BOOSTCTRL
void (*trigger_cpu_boost)(void);
void (*cancel_cpu_boost)(void);
#endif
#endif /* CONFIG_CODEC == SWCODEC */
};
/* plugin header */

View file

@ -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

View file

@ -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)
{
mem_ptr = 0;
bufsize = mallocsize;
/* Line-align buffer */
mallocbuf = (char *)(((intptr_t)buf + 15) & ~15);
/* Adjust for real size */
bufsize -= mallocbuf - buf;
/* Can steal all but MIN_MEMMARGIN */
if (bufsize - mem_ptr < MIN_MEMMARGIN)
return NULL;
/* Separate allocator for video */
libmpeg2size = (libmpeg2size + 15) & ~15;
if (mpeg_malloc_internal(mallocbuf, &mem_ptr,
bufsize, libmpeg2size, 0) == NULL)
{
return 0;
}
mpeg2_mallocbuf = mallocbuf;
mpeg2_mem_ptr = 0;
mpeg2_bufsize = libmpeg2size;
#if NUM_CORES > 1
flush_icache();
#endif
return bufsize - mpeg2_bufsize;
*size_out = bufsize - mem_ptr - MIN_MEMMARGIN;
return mpeg_malloc(*size_out, reason);
}
/* gcc may want to use memcpy before rb is initialised, so here's a trivial
bool mpeg_alloc_init(unsigned char *buf, size_t mallocsize)
{
mem_ptr = 0;
/* Cache-align buffer or 4-byte align */
mallocbuf = buf;
bufsize = align_buffer(PUN_PTR(void **, &mallocbuf),
mallocsize, CACHEALIGN_UP(4));
/* Separate allocator for video */
mpeg2_mem_ptr = 0;
mpeg2_mallocbuf = mallocbuf;
mpeg2_bufallocbuf = mallocbuf;
mpeg2_bufsize = CACHEALIGN_UP(LIBMPEG2_ALLOC_SIZE);
if (mpeg_malloc_internal(mallocbuf, &mem_ptr,
bufsize, mpeg2_bufsize,
MPEG_ALLOC_MPEG2_BUFFER) == NULL)
{
return false;
}
IF_COP(flush_icache());
return true;
}
/* 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)

View file

@ -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
}

View file

@ -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
}

View file

@ -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;
}
}

View file

@ -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 */

View file

@ -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;

View file

@ -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 */

View file

@ -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 */

View file

@ -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;
}
}

View file

@ -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 */

View file

@ -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 */
}

View file

@ -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 */

File diff suppressed because it is too large Load diff

View file

@ -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];
/* 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);
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...");
}
int get_start_time(int play_time, int in_file)
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);
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(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
}

View file

@ -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);

View file

@ -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 */

File diff suppressed because it is too large Load diff

View file

@ -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 */

View file

@ -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 */

View file

@ -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);
}

View file

@ -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 */

File diff suppressed because it is too large Load diff

View file

@ -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 */

View file

@ -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 */

View file

@ -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 */

View file

@ -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"
extern struct plugin_api* rb;
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;
};
#include "mpeg2.h"
#include "video_out.h"
#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
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;
/* Draw a black rectangle if no video frame is available */
static void vo_draw_black(void)
{
int foreground = lcd_(get_foreground)();
void vo_draw_frame (uint8_t * const * buf)
lcd_(set_foreground)(DRAW_BLACK);
lcd_(fillrect)(vo.output_x, vo.output_y, vo.output_width,
vo.output_height);
lcd_(update_rect)(vo.output_x, vo.output_y, vo.output_width,
vo.output_height);
lcd_(set_foreground)(foreground);
}
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];
void vo_draw_frame_thumb (uint8_t * const * buf)
static inline void vo_rect_clear_inl(struct vo_rect *rc)
{
int r,c;
rc->l = rc->t = rc->r = rc->b = 0;
}
static inline bool vo_rect_empty_inl(const struct vo_rect *rc)
{
return rc == NULL || rc->l >= rc->r || rc->t >= rc->b;
}
static inline bool vo_rects_intersect_inl(const struct vo_rect *rc1,
const struct vo_rect *rc2)
{
return !vo_rect_empty_inl(rc1) &&
!vo_rect_empty_inl(rc2) &&
rc1->l < rc2->r && rc1->r > rc2->l &&
rc1->t < rc2->b && rc1->b > rc2->t;
}
/* Sets all coordinates of a vo_rect to 0 */
void vo_rect_clear(struct vo_rect *rc)
{
vo_rect_clear_inl(rc);
}
/* Returns true if left >= right or top >= bottom */
bool vo_rect_empty(const struct vo_rect *rc)
{
return vo_rect_empty_inl(rc);
}
/* Initializes a vo_rect using upper-left corner and extents */
void vo_rect_set_ext(struct vo_rect *rc, int x, int y,
int width, int height)
{
rc->l = x;
rc->t = y;
rc->r = x + width;
rc->b = y + height;
}
/* Query if two rectangles intersect */
bool vo_rects_intersect(const struct vo_rect *rc1,
const struct vo_rect *rc2)
{
return vo_rects_intersect_inl(rc1, rc2);
}
/* Intersect two rectangles, placing the result in rc_dst */
bool vo_rect_intersect(struct vo_rect *rc_dst,
const struct vo_rect *rc1,
const struct vo_rect *rc2)
{
if (rc_dst != NULL)
{
if (vo_rects_intersect_inl(rc1, rc2))
{
rc_dst->l = MAX(rc1->l, rc2->l);
rc_dst->r = MIN(rc1->r, rc2->r);
rc_dst->t = MAX(rc1->t, rc2->t);
rc_dst->b = MIN(rc1->b, rc2->b);
return true;
}
vo_rect_clear_inl(rc_dst);
}
return false;
}
/* 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
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++)
{
*(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);
}
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++)
{
*(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);
}
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
rb->lcd_clear_display();
rb->lcd_update();
while (1)
{
const uint8_t *s = src;
#if LCD_WIDTH >= LCD_HEIGHT
uint8_t * const dst_line_end = dst + dst_w;
#else
uint8_t * const dst_line_end = dst + dst_h;
#endif
while (1)
{
*dst++ = *s;
if (dst >= dst_line_end)
{
dw = dst_w;
break;
}
#if LCD_WIDTH >= LCD_HEIGHT
s += qw;
#else
s += qw*stride;
#endif
dw += rw;
if (dw >= dst_w2)
{
dw -= dst_w2;
#if LCD_WIDTH >= LCD_HEIGHT
s++;
#else
s += stride;
#endif
}
}
if (dst >= dst_end)
break;
#if LCD_WIDTH >= LCD_HEIGHT
src += qh*stride;
#else
src -= qh;
#endif
dh += rh;
if (dh >= dst_h2)
{
dh -= dst_h2;
#if LCD_WIDTH >= LCD_HEIGHT
src += stride;
#else
src--;
#endif
}
}
}
bool vo_draw_frame_thumb(uint8_t * const * buf, const struct vo_rect *rc)
{
void *mem;
size_t bufsize;
uint8_t *yuv[3];
struct vo_rect thumb_rc;
int thumb_width, thumb_height;
int thumb_uv_width, thumb_uv_height;
if (buf == NULL)
return false;
/* Obtain rectangle as clipped to the screen */
vo_rect_set_ext(&thumb_rc, 0, 0, LCD_WIDTH, LCD_HEIGHT);
if (!vo_rect_intersect(&thumb_rc, rc, &thumb_rc))
return true;
DEBUGF("thumb_rc: %d, %d, %d, %d\n", thumb_rc.l, thumb_rc.t,
thumb_rc.r, thumb_rc.b);
thumb_width = rc->r - rc->l;
thumb_height = rc->b - rc->t;
thumb_uv_width = thumb_width / 2;
thumb_uv_height = thumb_height / 2;
DEBUGF("thumb: w: %d h: %d uvw: %d uvh: %d\n", thumb_width,
thumb_height, thumb_uv_width, thumb_uv_height);
/* Use remaining mpeg2 buffer as temp space */
mem = mpeg2_get_buf(&bufsize);
if (bufsize < (size_t)(thumb_width*thumb_height)
#ifdef HAVE_LCD_COLOR
+ 2u*(thumb_uv_width * thumb_uv_height)
#endif
)
{
DEBUGF("thumb: insufficient buffer\n");
return false;
}
yuv[0] = mem;
stretch_image_plane(buf[0], yuv[0], vo.image_width,
vo.display_width, vo.display_height,
thumb_width, thumb_height);
#ifdef HAVE_LCD_COLOR
#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);
yuv[1] = yuv[0] + thumb_width*thumb_height;
yuv[2] = yuv[1] + thumb_uv_width*thumb_uv_height;
#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);
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
#else
#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);
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
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
#endif
#else
#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);
#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);
#endif
#endif
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;
vo.image_width = sequence->width;
vo.image_height = sequence->height;
vo.display_width = sequence->display_width;
vo.display_height = sequence->display_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;
DEBUGF("vo_setup - w:%d h:%d\n", vo.display_width, vo.display_height);
image_chroma_x=image_width/sequence->chroma_width;
image_chroma_y=image_height/sequence->chroma_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) {
output_width = SCREEN_WIDTH;
output_x = 0;
} else {
output_width = sequence->display_width;
output_x = (SCREEN_WIDTH-sequence->display_width)/2;
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
}

File diff suppressed because it is too large Load diff

View file

@ -28,6 +28,8 @@ wav,viewers/test_codec,-
bmp,apps/rockpaint,11
mpg,viewers/mpegplayer,4
mpeg,viewers/mpegplayer,4
mpv,viewers/mpegplayer,4
m2v,viewers/mpegplayer,4
iriver,viewers/iriver_flash,3
tap,viewers/zxbox,12
sna,viewers/zxbox,12

View file

@ -243,16 +243,18 @@ static inline uint32_t swap_odd_even32(uint32_t value)
#define CACHEALIGN_DOWN(x) \
((typeof (x))ALIGN_DOWN_P2((uintptr_t)(x), CACHEALIGN_BITS))
/* Aligns at least to the greater of size x or CACHEALIGN_SIZE */
#define CACHEALIGN_AT_LEAST_ATTR(x) __attribute__((aligned(CACHEALIGN_UP(x))))
#define CACHEALIGN_AT_LEAST_ATTR(x) \
__attribute__((aligned(CACHEALIGN_UP(x))))
/* Aligns a buffer pointer and size to proper boundaries */
#define CACHEALIGN_BUFFER(start, size) \
({ align_buffer((start), (size), CACHEALIGN_SIZE); })
({ align_buffer(PUN_PTR(void **, (start)), (size), CACHEALIGN_SIZE); })
#else /* ndef PROC_NEEDS_CACHEALIGN */
/* Cache alignment attributes and sizes are not enabled */
#define CACHEALIGN_ATTR
#define CACHEALIGN_AT_LEAST_ATTR(x) __attribute__((aligned(x)))
#define CACHEALIGN_AT_LEAST_ATTR(x) \
__attribute__((aligned(x)))
#define CACHEALIGN_UP(x) (x)
#define CACHEALIGN_DOWN(x) (x)
/* Make no adjustments */
@ -261,4 +263,8 @@ static inline uint32_t swap_odd_even32(uint32_t value)
#endif /* PROC_NEEDS_CACHEALIGN */
/* Double-cast to avoid 'dereferencing type-punned pointer will
* break strict aliasing rules' B.S. */
#define PUN_PTR(type, p) ((type)(intptr_t)(p))
#endif /* __SYSTEM_H__ */

View file

@ -42,11 +42,20 @@
#define PRIORITY_SYSTEM 6 /* All other firmware threads */
#define PRIORITY_BACKGROUND 8 /* Normal application threads */
/* TODO: Only a minor tweak to create_thread would be needed to let
* thread slots be caller allocated - no essential threading functionality
* depends upon an array */
#if CONFIG_CODEC == SWCODEC
#ifdef HAVE_RECORDING
#define MAXTHREADS 18
#else
#define MAXTHREADS 17
#endif
#else
#define MAXTHREADS 11
#endif
#endif /* CONFIG_CODE == * */
#define DEFAULT_STACK_SIZE 0x400 /* Bytes */

View file

@ -77,8 +77,6 @@ int make_list_from_caps32(unsigned long src_mask,
return count;
} /* make_list_from_caps32 */
/* Only needed for cache aligning atm */
#ifdef PROC_NEEDS_CACHEALIGN
/* Align a buffer and size to a size boundary while remaining within
* the original boundaries */
size_t align_buffer(void **start, size_t size, size_t align)
@ -98,4 +96,3 @@ size_t align_buffer(void **start, size_t size, size_t align)
*start = newstart;
return newend - newstart;
}
#endif /* PROC_NEEDS_CACHEALIGN */