diff --git a/apps/plugin.c b/apps/plugin.c index 9739df4661..2fbfe346b3 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -486,6 +486,7 @@ static const struct plugin_api rockbox_api = { sound_default, pcm_record_more, #endif + create_thread_on_core, }; int plugin_load(const char* plugin, void* parameter) diff --git a/apps/plugin.h b/apps/plugin.h index 6fad78edd7..6e9a1316d5 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -110,7 +110,7 @@ #define PLUGIN_MAGIC 0x526F634B /* RocK */ /* increase this every time the api struct changes */ -#define PLUGIN_API_VERSION 39 +#define PLUGIN_API_VERSION 40 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any @@ -602,6 +602,11 @@ struct plugin_api { int (*sound_default)(int setting); void (*pcm_record_more)(void *start, size_t size); #endif + + struct thread_entry*(*create_thread_on_core)( + unsigned int core, void (*function)(void), + void* stack, int stack_size, + const char *name IF_PRIO(, int priority)); }; /* plugin header */ diff --git a/apps/plugins/mpegplayer/Makefile b/apps/plugins/mpegplayer/Makefile index fd19a6da9c..f31f00f26c 100644 --- a/apps/plugins/mpegplayer/Makefile +++ b/apps/plugins/mpegplayer/Makefile @@ -34,7 +34,7 @@ all: $(OUTPUT) ifndef SIMVER $(OBJDIR)/mpegplayer.elf: $(OBJS) $(LINKFILE) - $(call PRINTS,LD $(@F))$(CC) $(GCCOPTS) -O -nostdlib -o $@ $(OBJS) -L$(BUILDDIR) -lplugin -lgcc \ + $(call PRINTS,LD $(@F))$(CC) $(GCCOPTS) -O -nostdlib -o $@ $(OBJS) -L$(BUILDDIR) -lplugin -lmad -lgcc\ -T$(LINKFILE) -Wl,-Map,$(OBJDIR)/mpegplayer.map $(OUTPUT): $(OBJDIR)/mpegplayer.elf @@ -46,7 +46,7 @@ ifeq ($(SIMVER), x11) # This is the X11 simulator version $(OUTPUT): $(OBJS) - $(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(OBJS) -L$(BUILDDIR) -lplugin -o $@ + $(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(OBJS) -L$(BUILDDIR) -lplugin -lmad -o $@ ifeq ($(findstring CYGWIN,$(UNAME)),CYGWIN) # 'x' must be kept or you'll have "Win32 error 5" # $ fgrep 5 /usr/include/w32api/winerror.h | head -1 @@ -61,7 +61,7 @@ ifeq ($(SIMVER), sdl) # This is the SDL simulator version $(OUTPUT): $(OBJS) - $(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(OBJS) -L$(BUILDDIR) -lplugin -o $@ + $(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(OBJS) -L$(BUILDDIR) -lplugin -lmad -o $@ ifeq ($(findstring CYGWIN,$(UNAME)),CYGWIN) # 'x' must be kept or you'll have "Win32 error 5" # $ fgrep 5 /usr/include/w32api/winerror.h | head -1 diff --git a/apps/plugins/mpegplayer/alloc.c b/apps/plugins/mpegplayer/alloc.c index 0a3568ae5b..d406947a58 100644 --- a/apps/plugins/mpegplayer/alloc.c +++ b/apps/plugins/mpegplayer/alloc.c @@ -46,6 +46,7 @@ void * mpeg2_malloc (unsigned size, mpeg2_alloc_t reason) (void)reason; + DEBUGF("mpeg2_malloc(%d,%d)\n",size,reason); if (mem_ptr + (long)size > bufsize) { DEBUGF("OUT OF MEMORY\n"); return NULL; @@ -74,3 +75,45 @@ void *memcpy(void *dest, const void *src, size_t n) { return dest; } + + +/* The following are expected by libmad */ +void* codec_malloc(size_t size) +{ + return mpeg2_malloc(size,-3); +} + +void* codec_calloc(size_t nmemb, size_t size) +{ + void* ptr; + + ptr = mpeg2_malloc(nmemb*size,-3); + + if (ptr) + rb->memset(ptr,0,size); + + return ptr; +} + +void codec_free(void* ptr) { + (void)ptr; +} + +void *memmove(void *dest, const void *src, size_t n) +{ + return rb->memmove(dest,src,n); +} + +void *memset(void *s, int c, size_t n) +{ + return rb->memset(s,c,n); +} + +void abort(void) +{ + rb->lcd_putsxy(0,0,"ABORT!"); + rb->lcd_update(); + + while (1); + /* Let's hope this is never called */ +} diff --git a/apps/plugins/mpegplayer/mpegplayer.c b/apps/plugins/mpegplayer/mpegplayer.c index 8a839eb17b..d046e1e01a 100644 --- a/apps/plugins/mpegplayer/mpegplayer.c +++ b/apps/plugins/mpegplayer/mpegplayer.c @@ -1,8 +1,13 @@ - /* - * mpegplayer.c - based on mpeg2dec.c +/* + * mpegplayer.c - based on : + * - mpeg2dec.c + * - m2psd.c (http://www.brouhaha.com/~eric/software/m2psd/) * * Copyright (C) 2000-2003 Michel Lespinasse * Copyright (C) 1999-2000 Aaron Holtzman + * + * m2psd: MPEG 2 Program Stream Demultiplexer + * Copyright (C) 2003 Eric Smith * * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. * See http://libmpeg2.sourceforge.net/ for updates. @@ -22,6 +27,79 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +/* + +NOTES: + +mpegplayer is structured as follows: + +1) Video thread (running on the COP for PortalPlayer targets). +2) Audio thread (running on the main CPU to maintain consistency with + the audio FIQ hander on PP). +3) The main thread which takes care of buffering. + +Using the main thread for buffering wastes the 8KB main stack which is +in IRAM. However, 8KB is not enough for the audio thread to run (it +needs somewhere between 8KB and 9KB), so we create a new thread in +order to`give it a larger stack. + +We use 4.5KB of the main stack for a libmad buffer (making use of +otherwise unused IRAM). There is also the possiblity of stealing the +main Rockbox codec thread's 9KB of IRAM stack and using that for +mpegplayer's audio thread - but we should only implement that if we +can put the IRAM to good use. + +The button loop (and hence pause/resume, main menu and, in the future, +seeking) is placed in the audio thread. This keeps it on the main CPU +in PP targets and also allows buffering to continue in the background +whilst the main thread is filling the buffer. + +A/V sync is not yet implemented but is planned to be achieved by +syncing the master clock with the audio, and then (as is currently +implemented), syncing video with the master clock. This can happen in +the audio thread, along with resyncing after pause. + +Seeking should probably happen in the main thread, as that's where the +buffering happens. + +On PortalPlayer targets, the main CPU is not being fully utilised - +the bottleneck is the video decoding on the COP. One way to improve +that might be to move the rendering of the frames (i.e. the +lcd_yuv_blit() call) from the COP back to the main CPU. Ideas and +patches for that are welcome! + +Notes about MPEG files: + +MPEG System Clock is 27MHz - i.e. 27000000 ticks/second. + +FPS is represented in terms of a frame period - this is always an +integer number of 27MHz ticks. + +e.g. 29.97fps (30000/1001) NTSC video has an exact frame period of +900900 27MHz ticks. + +In libmpeg2, info->sequence->frame_period contains the frame_period. + +Working with Rockbox's 100Hz tick, the common frame rates would need +to be as follows: + +FPS | 27Mhz | 100Hz +--------|---------------- +10* | 2700000 | 10 +12* | 2250000 | 8.3333 +15* | 1800000 | 6.6667 +23.9760 | 1126125 | 4.170833333 +24 | 1125000 | 4.166667 +25 | 1080000 | 4 +29.9700 | 900900 | 3.336667 +30 | 900000 | 3.333333 + + +*Unofficial framerates + +*/ + + #include "mpeg2dec_config.h" #include "plugin.h" @@ -29,15 +107,11 @@ #include "mpeg2.h" #include "mpeg_settings.h" #include "video_out.h" +#include "../../codecs/libmad/mad.h" PLUGIN_HEADER PLUGIN_IRAM_DECLARE -struct plugin_api* rb; - -static mpeg2dec_t * mpeg2dec; -static int total_offset = 0; - /* button definitions */ #if (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD) #define MPEG_MENU BUTTON_MODE @@ -73,35 +147,81 @@ static int total_offset = 0; #error MPEGPLAYER: Unsupported keypad #endif -static int tick_enabled = 0; +struct plugin_api* rb; + +static mpeg2dec_t * mpeg2dec; +static int total_offset = 0; + +/* Streams */ +typedef struct +{ + uint8_t* curr_packet; /* Current stream packet beginning */ + uint8_t* curr_packet_end; /* Current stream packet end */ + + uint8_t* next_packet; /* Next stream packet beginning */ + + int id; +} Stream; + +static Stream audio_str, video_str; + +static uint8_t *disk_buf, *disk_buf_end; + +/* Events */ +static struct event_queue msg_queue IBSS_ATTR; + +#define MSG_BUFFER_NEARLY_EMPTY 1 +#define MSG_EXIT_REQUESTED 2 + +/* Threads */ +static struct thread_entry* audiothread_id; +static struct thread_entry* videothread_id; + +/* Status */ +#define STREAM_PLAYING 0 +#define STREAM_DONE 1 +#define STREAM_PAUSING 2 +#define PLEASE_STOP 3 +#define PLEASE_PAUSE 4 + +int audiostatus IBSS_ATTR; +int videostatus IBSS_ATTR; + +/* Various buffers */ +/* TODO: Can we reduce the PCM buffer size? */ +#define PCMBUFFER_SIZE (512*1024) +#define AUDIOBUFFER_SIZE (32*1024) +#define LIBMPEG2BUFFER_SIZE (2*1024*1024) + +static int tick_enabled IBSS_ATTR = 0; #define MPEG_CURRENT_TICK ((unsigned int)((*rb->current_tick - tick_offset))) /* The value to subtract from current_tick to get the current mpeg tick */ -static int tick_offset; +static int tick_offset IBSS_ATTR; /* The last tick - i.e. the time to reset the tick_offset to when unpausing */ -static int last_tick; +static int last_tick IBSS_ATTR; -void start_timer(void) +static void start_timer(void) { last_tick = 0; tick_offset = *rb->current_tick; } -void unpause_timer(void) +static void unpause_timer(void) { tick_offset = *rb->current_tick - last_tick; } -void pause_timer(void) +static void pause_timer(void) { /* Save the current MPEG tick */ last_tick = *rb->current_tick - tick_offset; } -static bool button_loop(void) +static void button_loop(void) { bool result; int button = rb->button_get(false); @@ -109,28 +229,53 @@ static bool button_loop(void) switch (button) { case MPEG_MENU: + videostatus=PLEASE_PAUSE; + rb->pcm_play_pause(false); pause_timer(); + /* Wait for video thread to stop */ + while (videostatus == PLEASE_PAUSE) { rb->sleep(HZ/25); } result = mpeg_menu(); + /* The menu can change the font, so restore */ + rb->lcd_setfont(FONT_SYSFIXED); + unpause_timer(); - return result; + if (result) { + audiostatus = PLEASE_STOP; + videostatus = PLEASE_STOP; + } else { + videostatus = STREAM_PLAYING; + rb->pcm_play_pause(true); + } + break; case MPEG_STOP: - return true; + audiostatus = PLEASE_STOP; + videostatus = PLEASE_STOP; + break; case MPEG_PAUSE: + videostatus=PLEASE_PAUSE; + rb->pcm_play_pause(false); pause_timer(); /* Freeze time */ + button = BUTTON_NONE; #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(false); #endif do { button = rb->button_get(true); - if (button == MPEG_STOP) - return true; + if (button == MPEG_STOP) { + audiostatus = PLEASE_STOP; + videostatus = PLEASE_STOP; + return; + } } while (button != MPEG_PAUSE); + + videostatus = STREAM_PLAYING; + rb->pcm_play_pause(true); #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(true); #endif @@ -138,59 +283,414 @@ static bool button_loop(void) break; default: - if(rb->default_event_handler(button) == SYS_USB_CONNECTED) - return true; + if(rb->default_event_handler(button) == SYS_USB_CONNECTED) { + audiostatus = PLEASE_STOP; + videostatus = PLEASE_STOP; + } } - return false; } -/* +/* libmad related functions/definitions */ +#define INPUT_CHUNK_SIZE 8192 -NOTES: +struct mad_stream stream IBSS_ATTR; +struct mad_frame frame IBSS_ATTR; +struct mad_synth synth IBSS_ATTR; -MPEG System Clock is 27MHz - i.e. 27000000 ticks/second. +unsigned char mad_main_data[MAD_BUFFER_MDLEN]; /* 2567 bytes */ -FPS is represented in terms of a frame period - this is always an -integer number of 27MHz ticks. +static void init_mad(void* mad_frame_overlap) +{ + rb->memset(&stream, 0, sizeof(struct mad_stream)); + rb->memset(&frame, 0, sizeof(struct mad_frame)); + rb->memset(&synth, 0, sizeof(struct mad_synth)); -e.g. 29.97fps (30000/1001) NTSC video has an exact frame period of -900900 27MHz ticks. + mad_stream_init(&stream); + mad_frame_init(&frame); + mad_synth_init(&synth); -In libmpeg2, info->sequence->frame_period contains the frame_period. + /* We do this so libmad doesn't try to call codec_calloc() */ + frame.overlap = mad_frame_overlap; -Working with Rockbox's 100Hz tick, the common frame rates would need -to be as follows: + rb->memset(mad_main_data, 0, sizeof(mad_main_data)); + stream.main_data = &mad_main_data; +} -FPS | 27Mhz | 100Hz ---------|---------------- -10* | 2700000 | 10 -12* | 2250000 | 8.3333 -15* | 1800000 | 6.6667 -23.9760 | 1126125 | 4.170833333 -24 | 1125000 | 4.166667 -25 | 1080000 | 4 -29.9700 | 900900 | 3.336667 -30 | 900000 | 3.333333 +/* MPEG related headers */ +uint8_t packet_start_code_prefix [3] = { 0x00, 0x00, 0x01 }; +uint8_t end_code [4] = { 0x00, 0x00, 0x01, 0xb9 }; +uint8_t pack_start_code [4] = { 0x00, 0x00, 0x01, 0xba }; +uint8_t system_header_start_code [4] = { 0x00, 0x00, 0x01, 0xbb }; +/* This function demux the streams and give the next stream data pointer */ +static void get_next_data( Stream* str ) +{ + uint8_t *p; + uint8_t *header; + int stream; + + static int mpeg1_skip_table[16] = { + 0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + if( str->curr_packet_end == NULL ) + { + while( (p = disk_buf) == NULL ) + { + rb->sleep(100); + } + }else{ + p = str->curr_packet_end; + } + + for( ;; ) + { + int length, bytes; + + /* Pack header, skip it */ + if( rb->memcmp (p, pack_start_code, sizeof (pack_start_code)) == 0 ) + { + if ((p[4] & 0xc0) == 0x40) { /* mpeg-2 */ + p += 14 + (p[13] & 7); + } else if ((p[4] & 0xf0) == 0x20) { /* mpeg-1 */ + p += 12; + } else { + rb->splash( 30, true, "Weird Pack header!" ); + p += 5; + } + /*rb->splash( 30, true, "Pack header" );*/ + } -*Unofficial framerates + /* System header, parse and skip it */ + if( rb->memcmp (p, system_header_start_code, sizeof (system_header_start_code)) == 0 ) + { + int header_length; -*/ + p += 4; /*skip start code*/ + header_length = (*(p++)) << 8; + header_length += *(p++); -static uint64_t eta; + p += header_length; + /*rb->splash( 30, true, "System header" );*/ + } + + /* Packet header, parse it */ + if( rb->memcmp (p, packet_start_code_prefix, sizeof (packet_start_code_prefix)) != 0 ) + { + /* Problem */ + //rb->splash( HZ*3, true, "missing packet start code prefix : %X%X at %X", *p, *(p+2), p-disk_buf ); + str->curr_packet_end = str->curr_packet = NULL; + return; + //++p; + break; + } + + /* We retrieve basic infos */ + stream = *(p+3); + length = (*(p+4)) << 8; + length += *(p+5); + + /*rb->splash( 100, true, "Stream : %X", stream );*/ + if (stream != str->id) + { + /* End of stream ? */ + if( stream == 0xB9 ) + { + str->curr_packet_end = str->curr_packet = NULL; + return; + } + + /* It's not the packet we're looking for, skip it */ + p += length+6; + continue; + } + + /* Ok, it's our packet */ + str->curr_packet_end = p + length+6; + header = p; + if ((header[6] & 0xc0) == 0x80) { /* mpeg2 */ + + length = 9 + header[8]; + /* header points to the mpeg2 pes header */ + if (header[7] & 0x80) { + uint32_t pts, dts; -static bool decode_mpeg2 (uint8_t * current, uint8_t * end) + pts = (((header[9] >> 1) << 30) | + (header[10] << 22) | ((header[11] >> 1) << 15) | + (header[12] << 7) | (header[13] >> 1)); + dts = (!(header[7] & 0x40) ? pts : + ((uint32_t)(((header[14] >> 1) << 30) | + (header[15] << 22) | + ((header[16] >> 1) << 15) | + (header[17] << 7) | (header[18] >> 1)))); + + if( stream >= 0xe0 ) + mpeg2_tag_picture (mpeg2dec, pts, dts); + } + } else { /* mpeg1 */ + int len_skip; + uint8_t * ptsbuf; + + length = 7; + while (header[length - 1] == 0xff) + { + length++; + if (length > 23) + { + rb->splash( 30, true, "Too much stuffing" ); + break; + } + } + if ((header[length - 1] & 0xc0) == 0x40) + { + length += 2; + } + len_skip = length; + length += mpeg1_skip_table[header[length - 1] >> 4]; + + /* header points to the mpeg1 pes header */ + ptsbuf = header + len_skip; + if ((ptsbuf[-1] & 0xe0) == 0x20) + { + uint32_t pts, dts; + + pts = (((ptsbuf[-1] >> 1) << 30) | + (ptsbuf[0] << 22) | ((ptsbuf[1] >> 1) << 15) | + (ptsbuf[2] << 7) | (ptsbuf[3] >> 1)); + dts = (((ptsbuf[-1] & 0xf0) != 0x30) ? pts : + ((uint32_t)(((ptsbuf[4] >> 1) << 30) | + (ptsbuf[5] << 22) | ((ptsbuf[6] >> 1) << 15) | + (ptsbuf[7] << 7) | (ptsbuf[18] >> 1)))); + if( stream >= 0xe0 ) + mpeg2_tag_picture (mpeg2dec, pts, dts); + } + } + p += length; + bytes = 6 + (header[4] << 8) + header[5] - length; + if (bytes > 0) { + /*str->curr_packet_end = p+bytes;*/ + str->curr_packet = p; + return; + } + + if( str->curr_packet_end > disk_buf_end ) + { + /* We should ask for buffering here */ + str->curr_packet_end = str->curr_packet = NULL; + return; + } + + break; + } +} + +uint8_t* mpa_buffer; +size_t mpa_buffer_size; + +static volatile int madpcm_playing; +static volatile int16_t* pcm_buffer; +static volatile size_t pcm_buffer_size; + +static volatile size_t pcmbuf_len; +static volatile int16_t* pcmbuf_end; +static volatile int16_t* pcmbuf_head; +static volatile int16_t* pcmbuf_tail; + +static void init_pcmbuf(void) +{ + pcmbuf_head = pcm_buffer; + pcmbuf_len = 0; + pcmbuf_tail = pcm_buffer; + pcmbuf_end = pcm_buffer + pcm_buffer_size / sizeof(int16_t); + madpcm_playing = 0; +} + +static void get_more(unsigned char** start, size_t* size) +{ + if (pcmbuf_len < 32*1024) { + *start = NULL; + *size = 0; + madpcm_playing = 0; + } else { + *start = (unsigned char*)(pcmbuf_tail); + *size = 32*1024; + pcmbuf_tail += (32*1024)/sizeof(int16_t); + pcmbuf_len -= 32*1024; + if (pcmbuf_tail >= pcmbuf_end) { pcmbuf_tail = pcm_buffer; } + } +} + +int line; + +static void mad_decode(void) +{ + int32_t* left; + int32_t* right; + int i; + size_t n = 0; + size_t len; + int file_end = 0; /* A count of the errors in each frame */ + int framelength; + + init_pcmbuf(); + + /* This is the decoding loop. */ + for (;;) { + button_loop(); + + if (audiostatus == PLEASE_STOP) { + goto done; + } + + if (n < 1000) { /* TODO: What is the maximum size of an MPEG audio frame? */ + get_next_data( &audio_str ); + if (audio_str.curr_packet == NULL) { + goto done; + } + len = audio_str.curr_packet_end - audio_str.curr_packet; + if (n + len > mpa_buffer_size) { + rb->splash( 30, true, "Audio buffer overflow" ); + audiostatus=STREAM_DONE; + /* Wait to be killed */ + for (;;) { rb->sleep(HZ); } + } + rb->memcpy(mpa_buffer+n,audio_str.curr_packet,len); + n += len; + } + + /* Lock buffers */ + if (stream.error == 0) { + mad_stream_buffer(&stream, mpa_buffer, n); + } + + if (mad_frame_decode(&frame, &stream)) { + if (stream.error == MAD_FLAG_INCOMPLETE + || stream.error == MAD_ERROR_BUFLEN) { + /* This makes the codec support partially corrupted files */ + if (file_end == 30) + break; + +#if 0 + /* The mpa.c version: */ + if (stream.next_frame) + inputbuffer = stream.next_frame; + else + inputbuffer++; +#endif + + stream.error = 0; + file_end++; + continue; + } else if (MAD_RECOVERABLE(stream.error)) { + continue; + } else { + /* Some other unrecoverable error */ + break; + } + break; + } + + file_end = 0; + + mad_synth_frame(&synth, &frame); + + /* TODO: Don't memmove so much... */ + if (stream.next_frame) { + len = stream.next_frame - mpa_buffer; + rb->memmove(mpa_buffer,stream.next_frame,n-len); + n -= len; + } else { + /* What to do here? */ + goto done; + } +#if 0 + /* The mpa.c version: */ + if (stream.next_frame) + inputbuffer = stream.next_frame; + else + inputbuffer = inputbuffer_end; +#endif + + framelength = synth.pcm.length; + + if (framelength > 0) { + /* Leave at least 32KB free (this will be the currently playing chunk) */ + while (pcmbuf_len + framelength*4 + 32*1024 > pcm_buffer_size) { rb->yield(); } + + if (MAD_NCHANNELS(&frame.header) == 2) { + left = &synth.pcm.samples[0][0]; + right = &synth.pcm.samples[1][0]; + for (i = 0 ; i < framelength; i++) { + *(pcmbuf_head++) = *(left++) >> 13; + *(pcmbuf_head++) = *(right++) >> 13; + if (pcmbuf_head >= pcmbuf_end) { pcmbuf_head = pcm_buffer; } + } + } else { /* mono */ + int16_t sample; + left = &synth.pcm.samples[0][0]; + for (i = 0 ; i < framelength; i++) { + sample = *(left++) >> 13; + *(pcmbuf_head++) = sample; + *(pcmbuf_head++) = sample; + if (pcmbuf_head >= pcmbuf_end) { pcmbuf_head = pcm_buffer; } + } + } + + /* TODO: Disable interrupts for Coldfire? */ + + /* pcmbuf_len is also modified by the FIQ handler (in + get_more), so we disable the FIQ TODO: Add sempahore so we + don't change whilst get_more is running. */ + +#ifdef CPU_ARM + disable_fiq(); +#endif + pcmbuf_len += framelength*4; +#ifdef CPU_ARM + enable_fiq(); +#endif + if ((!madpcm_playing) && (pcmbuf_len > 64*1024)) { + madpcm_playing = 1; + rb->pcm_play_data(get_more,NULL,0); + } + } + rb->yield(); + } + +done: + rb->pcm_play_stop(); + audiostatus=STREAM_DONE; + + for (;;) { rb->sleep(HZ); } +} + +/* End of libmad stuff */ + +static uint64_t eta IBSS_ATTR; + +/* TODO: Running in the main thread, libmad needs 8.25KB of stack. + The codec thread uses a 9KB stack. So we can probable reduce this a + little, but leave at 9KB for now to be safe. */ +#define AUDIO_STACKSIZE (9*1024) +uint32_t audio_stack[AUDIO_STACKSIZE / sizeof(uint32_t)] IBSS_ATTR; + +/* TODO: Check if 4KB is appropriate - it works for my test streams, + so maybe we can reduce it. */ +#define VIDEO_STACKSIZE (4*1024) +static uint32_t video_stack[VIDEO_STACKSIZE / sizeof(uint32_t)] IBSS_ATTR; + +static void decode_mpeg2 (void) { const mpeg2_info_t * info; mpeg2_state_t state; char str[80]; static int skipped = 0; + int skipcount = 0; static int frame = 0; static int starttick = 0; static int lasttick; unsigned int eta2; unsigned int x; - int fps; if (starttick == 0) { @@ -198,18 +698,35 @@ static bool decode_mpeg2 (uint8_t * current, uint8_t * end) lasttick=starttick; } - mpeg2_buffer (mpeg2dec, current, end); - total_offset += end - current; + /* Request the first packet data */ + get_next_data( &video_str ); + mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); + total_offset += video_str.curr_packet_end - video_str.curr_packet; info = mpeg2_info (mpeg2dec); while (1) { - if (button_loop()) - return true; + if (videostatus == PLEASE_STOP) { + goto done; + } else if (videostatus == PLEASE_PAUSE) { + videostatus = STREAM_PAUSING; + while (videostatus == STREAM_PAUSING) { rb->sleep(HZ/10); } + } state = mpeg2_parse (mpeg2dec); + rb->yield(); switch (state) { case STATE_BUFFER: - return false; + /* Request next packet data */ + get_next_data( &video_str ); + mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end); + total_offset += video_str.curr_packet_end - video_str.curr_packet; + info = mpeg2_info (mpeg2dec); + if (video_str.curr_packet == NULL) { + /* No more data. */ + goto done; + } + continue; + case STATE_SEQUENCE: vo_setup(info->sequence->width, info->sequence->height, @@ -244,10 +761,12 @@ static bool decode_mpeg2 (uint8_t * current, uint8_t * end) /* If we are more than 1/20 second behind schedule (and more than 1/20 second into the decoding), skip frame */ if (settings.skipframes && (x > HZ/20) && - (eta2 < (x - (HZ/20)))) { + (eta2 < (x - (HZ/20))) && (skipcount < 10)) { skipped++; + skipcount++; } else { vo_draw_frame(info->display_fbuf->buf); + skipcount = 0; } /* Calculate fps */ @@ -269,53 +788,68 @@ static bool decode_mpeg2 (uint8_t * current, uint8_t * end) rb->yield(); } -} -static void es_loop (int in_file, uint8_t* buffer, size_t buffer_size) -{ - uint8_t * end; - - if (buffer==NULL) - return; - - eta = 0; - do { - rb->splash(0,true,"Buffering..."); - save_settings(); /* Save settings (if they have changed) */ - end = buffer + rb->read (in_file, buffer, buffer_size); - if (decode_mpeg2 (buffer, end)) - break; - } while (end == buffer + buffer_size); +done: + videostatus = STREAM_DONE; + for (;;) { rb->sleep(HZ); } } enum plugin_status plugin_start(struct plugin_api* api, void* parameter) { - (void)parameter; void* audiobuf; int audiosize; int in_file; uint8_t* buffer; size_t buffer_size; + /* We define this here so it is on the main stack (in IRAM) */ + mad_fixed_t mad_frame_overlap[2][32][18]; /* 4608 bytes */ + /* This also stops audio playback - so we do it before using IRAM */ audiobuf = api->plugin_get_audio_buffer(&audiosize); PLUGIN_IRAM_INIT(api) rb = api; + /* Set disk pointers to NULL */ + disk_buf_end = disk_buf = NULL; + + /* Stream construction */ + /* We take the first stream of each (audio and video) */ + /* TODO : Search for these in the file first */ + audio_str.curr_packet_end = audio_str.curr_packet = audio_str.next_packet = NULL; + video_str = audio_str; + video_str.id = 0xe0; + audio_str.id = 0xc0; + /* Initialise our malloc buffer */ mpeg2_alloc_init(audiobuf,audiosize); - /* Grab most of the buffer for the compressed video - leave 2MB */ - buffer_size = audiosize - 2*1024*1024; - buffer = mpeg2_malloc(buffer_size,0); + /* Grab most of the buffer for the compressed video - leave 4MB for mpeg audio, and 512KB for PCM audio data */ + buffer_size = audiosize - (PCMBUFFER_SIZE+AUDIOBUFFER_SIZE+LIBMPEG2BUFFER_SIZE); + DEBUGF("audiosize=%d, buffer_size=%d\n",audiosize,buffer_size); + buffer = mpeg2_malloc(buffer_size,-1); if (buffer == NULL) return PLUGIN_ERROR; - rb->lcd_set_backdrop(NULL); + mpa_buffer_size = AUDIOBUFFER_SIZE; + mpa_buffer = mpeg2_malloc(mpa_buffer_size,-2); + + if (mpa_buffer == NULL) + return PLUGIN_ERROR; + + /* Use 0.5MB of the remaining 4MB for audio data */ + pcm_buffer_size = PCMBUFFER_SIZE; + pcm_buffer = mpeg2_malloc(pcm_buffer_size,-2); + + if (pcm_buffer == NULL) + return PLUGIN_ERROR; + + /* The remaining buffer is for use by libmpeg2 */ #ifdef HAVE_LCD_COLOR + rb->lcd_set_backdrop(NULL); rb->lcd_set_foreground(LCD_WHITE); rb->lcd_set_background(LCD_BLACK); #endif @@ -326,6 +860,7 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) return PLUGIN_ERROR; } + /* Open the video file */ in_file = rb->open((char*)parameter,O_RDONLY); if (in_file < 0) { @@ -337,6 +872,8 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) mpeg2dec = mpeg2_init (); + rb->queue_init( &msg_queue, false ); /* Msg queue init */ + if (mpeg2dec == NULL) return PLUGIN_ERROR; @@ -353,10 +890,57 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) rb->cpu_boost(true); #endif - es_loop (in_file, buffer, buffer_size); + /* Initialise libmad */ + rb->memset(mad_frame_overlap, 0, sizeof(mad_frame_overlap)); + init_mad(mad_frame_overlap); + + eta = 0; + + rb->splash(0,true,"Buffering..."); + + disk_buf_end = buffer + rb->read (in_file, buffer, buffer_size); + disk_buf = buffer; + + rb->lcd_setfont(FONT_SYSFIXED); + + audiostatus = STREAM_PLAYING; + videostatus = STREAM_PLAYING; + + /* We put the video thread on the second processor for multi-core targets. */ +#if NUM_CORES > 1 + if ((videothread_id = rb->create_thread_on_core(COP,decode_mpeg2, +#else + if ((videothread_id = rb->create_thread(decode_mpeg2, +#endif + (uint8_t*)video_stack,VIDEO_STACKSIZE,"mpgvideo" IF_PRIO(,PRIORITY_PLAYBACK))) == NULL) + { + rb->splash(HZ,true,"Cannot create video thread!"); + return PLUGIN_ERROR; + } + + if ((audiothread_id = rb->create_thread(mad_decode, + (uint8_t*)audio_stack,AUDIO_STACKSIZE,"mpgaudio" IF_PRIO(,PRIORITY_PLAYBACK))) == NULL) + { + rb->splash(HZ,true,"Cannot create audio thread!"); + return PLUGIN_ERROR; + } + + /* Wait until both threads have finished their work */ + while ((audiostatus != STREAM_DONE) || (videostatus != STREAM_DONE)) { + rb->sleep(HZ/10); + } + + rb->remove_thread(audiothread_id); + rb->remove_thread(videothread_id); + rb->yield(); /* Is this needed? */ + + rb->lcd_clear_display(); + rb->lcd_update(); mpeg2_close (mpeg2dec); + rb->queue_delete( &msg_queue ); + rb->close (in_file); #ifdef HAVE_ADJUSTABLE_CPU_FREQ @@ -365,6 +949,8 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter) save_settings(); /* Save settings (if they have changed) */ + rb->lcd_setfont(FONT_UI); + #ifdef CONFIG_BACKLIGHT /* reset backlight settings */ rb->backlight_set_timeout(rb->global_settings->backlight_timeout); diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index 35ad7088e4..345b1653d7 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config @@ -21,7 +21,7 @@ wav,viewers/wav2wv, 00 00 00 00 00 00 wav,viewers/mp3_encoder, 00 00 00 00 00 00 wav,viewers/wavplay,60 7F 05 35 3F 00 bmp,rocks/rockpaint, 01 10 01 10 01 10 -m2v,viewers/mpegplayer,5D 7F 5D 7F 5D 7F +mpg,viewers/mpegplayer,5D 7F 5D 7F 5D 7F iriver,viewers/iriver_flash,2A 7F 41 41 7F 2A tap,viewers/zxbox,66 52 4A 66 52 4A sna,viewers/zxbox,66 52 4A 66 52 4A diff --git a/docs/CREDITS b/docs/CREDITS index 43a53bd36c..df511b57f4 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -260,3 +260,4 @@ Chris Taylor Tobias Langhoff Steve Gotthardt Greg White +Mattieu Favréaux