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:
parent
1d0f6b90ff
commit
a222f27c4a
34 changed files with 7850 additions and 2764 deletions
|
@ -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)
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
743
apps/plugins/mpegplayer/audio_thread.c
Normal file
743
apps/plugins/mpegplayer/audio_thread.c
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
906
apps/plugins/mpegplayer/disk_buf.c
Normal file
906
apps/plugins/mpegplayer/disk_buf.c
Normal 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;
|
||||
}
|
||||
}
|
132
apps/plugins/mpegplayer/disk_buf.h
Normal file
132
apps/plugins/mpegplayer/disk_buf.h
Normal 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 */
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
12
apps/plugins/mpegplayer/mpeg_alloc.h
Normal file
12
apps/plugins/mpegplayer/mpeg_alloc.h
Normal 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 */
|
149
apps/plugins/mpegplayer/mpeg_linkedlist.c
Normal file
149
apps/plugins/mpegplayer/mpeg_linkedlist.c
Normal 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;
|
||||
}
|
||||
}
|
69
apps/plugins/mpegplayer/mpeg_linkedlist.h
Normal file
69
apps/plugins/mpegplayer/mpeg_linkedlist.h
Normal 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 */
|
96
apps/plugins/mpegplayer/mpeg_misc.c
Normal file
96
apps/plugins/mpegplayer/mpeg_misc.c
Normal 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 */
|
||||
}
|
206
apps/plugins/mpegplayer/mpeg_misc.h
Normal file
206
apps/plugins/mpegplayer/mpeg_misc.h
Normal 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 */
|
1182
apps/plugins/mpegplayer/mpeg_parser.c
Normal file
1182
apps/plugins/mpegplayer/mpeg_parser.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
120
apps/plugins/mpegplayer/mpeg_stream.h
Normal file
120
apps/plugins/mpegplayer/mpeg_stream.h
Normal 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
120
apps/plugins/mpegplayer/mpegplayer.h
Normal file
120
apps/plugins/mpegplayer/mpegplayer.h
Normal 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 */
|
101
apps/plugins/mpegplayer/parser.h
Normal file
101
apps/plugins/mpegplayer/parser.h
Normal 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 */
|
278
apps/plugins/mpegplayer/pcm_output.c
Normal file
278
apps/plugins/mpegplayer/pcm_output.c
Normal 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);
|
||||
}
|
46
apps/plugins/mpegplayer/pcm_output.h
Normal file
46
apps/plugins/mpegplayer/pcm_output.h
Normal 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 */
|
1096
apps/plugins/mpegplayer/stream_mgr.c
Normal file
1096
apps/plugins/mpegplayer/stream_mgr.c
Normal file
File diff suppressed because it is too large
Load diff
151
apps/plugins/mpegplayer/stream_mgr.h
Normal file
151
apps/plugins/mpegplayer/stream_mgr.h
Normal 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 */
|
192
apps/plugins/mpegplayer/stream_thread.h
Normal file
192
apps/plugins/mpegplayer/stream_thread.h
Normal 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 */
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
1040
apps/plugins/mpegplayer/video_thread.c
Normal file
1040
apps/plugins/mpegplayer/video_thread.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in a new issue