summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2007-12-29 19:46:35 +0000
committerMichael Sevakis <jethead71@rockbox.org>2007-12-29 19:46:35 +0000
commita222f27c4a17ed8f9809cda7861fe5f23d4cc0c1 (patch)
treed393a23d83549f99772bb156e59ffb88725148b6 /apps
parent1d0f6b90ff43776e55b4b9f062c9bea3f99aa768 (diff)
downloadrockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.tar.gz
rockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.zip
mpegplayer: Make playback engine fully seekable and frame-accurate and split into logical parts. Be sure to have all current features work. Actual UI for seeking will be added soon. Recommended GOP size is about 15-30 frames depending on target or seeking can be slow with really long GOPs (nature of MPEG video). More refined encoding recommendations for a particular player should be posted soon.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15977 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps')
-rw-r--r--apps/plugin.c19
-rw-r--r--apps/plugin.h23
-rw-r--r--apps/plugins/mpegplayer/SOURCES8
-rw-r--r--apps/plugins/mpegplayer/alloc.c173
-rw-r--r--apps/plugins/mpegplayer/audio_thread.c743
-rw-r--r--apps/plugins/mpegplayer/decode.c11
-rw-r--r--apps/plugins/mpegplayer/disk_buf.c906
-rw-r--r--apps/plugins/mpegplayer/disk_buf.h132
-rw-r--r--apps/plugins/mpegplayer/header.c8
-rw-r--r--apps/plugins/mpegplayer/mpeg2.h6
-rw-r--r--apps/plugins/mpegplayer/mpeg_alloc.h12
-rw-r--r--apps/plugins/mpegplayer/mpeg_linkedlist.c149
-rw-r--r--apps/plugins/mpegplayer/mpeg_linkedlist.h69
-rw-r--r--apps/plugins/mpegplayer/mpeg_misc.c96
-rw-r--r--apps/plugins/mpegplayer/mpeg_misc.h206
-rw-r--r--apps/plugins/mpegplayer/mpeg_parser.c1182
-rw-r--r--apps/plugins/mpegplayer/mpeg_settings.c529
-rw-r--r--apps/plugins/mpegplayer/mpeg_settings.h18
-rw-r--r--apps/plugins/mpegplayer/mpeg_stream.h120
-rw-r--r--apps/plugins/mpegplayer/mpegplayer.c2583
-rw-r--r--apps/plugins/mpegplayer/mpegplayer.h120
-rw-r--r--apps/plugins/mpegplayer/parser.h101
-rw-r--r--apps/plugins/mpegplayer/pcm_output.c278
-rw-r--r--apps/plugins/mpegplayer/pcm_output.h46
-rw-r--r--apps/plugins/mpegplayer/stream_mgr.c1096
-rw-r--r--apps/plugins/mpegplayer/stream_mgr.h151
-rw-r--r--apps/plugins/mpegplayer/stream_thread.h192
-rw-r--r--apps/plugins/mpegplayer/video_out.h46
-rw-r--r--apps/plugins/mpegplayer/video_out_rockbox.c479
-rw-r--r--apps/plugins/mpegplayer/video_thread.c1040
-rw-r--r--apps/plugins/viewers.config2
31 files changed, 7809 insertions, 2735 deletions
diff --git a/apps/plugin.c b/apps/plugin.c
index 10391f9f35..0192590361 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -519,7 +519,7 @@ static const struct plugin_api rockbox_api = {
519 519
520 thread_wait, 520 thread_wait,
521 521
522#ifdef PROC_NEEDS_CACHEALIGN 522#if (CONFIG_CODEC == SWCODEC)
523 align_buffer, 523 align_buffer,
524#endif 524#endif
525 525
@@ -564,6 +564,23 @@ static const struct plugin_api rockbox_api = {
564 find_albumart, 564 find_albumart,
565 search_albumart_files, 565 search_albumart_files,
566#endif 566#endif
567
568#if CONFIG_CODEC == SWCODEC
569 pcm_play_lock,
570 pcm_play_unlock,
571 queue_enable_queue_send,
572 queue_empty,
573 queue_wait,
574 queue_send,
575 queue_reply,
576#ifndef HAVE_FLASH_STORAGE
577 ata_spin,
578#endif
579#ifdef HAVE_SCHEDULER_BOOSTCTRL
580 trigger_cpu_boost,
581 cancel_cpu_boost,
582#endif
583#endif /* CONFIG_CODEC == SWCODEC */
567}; 584};
568 585
569int plugin_load(const char* plugin, void* parameter) 586int plugin_load(const char* plugin, void* parameter)
diff --git a/apps/plugin.h b/apps/plugin.h
index 1b12011c9a..5ac4aa7cc2 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -119,7 +119,7 @@
119#define PLUGIN_MAGIC 0x526F634B /* RocK */ 119#define PLUGIN_MAGIC 0x526F634B /* RocK */
120 120
121/* increase this every time the api struct changes */ 121/* increase this every time the api struct changes */
122#define PLUGIN_API_VERSION 91 122#define PLUGIN_API_VERSION 92
123 123
124/* update this to latest version if a change to the api struct breaks 124/* update this to latest version if a change to the api struct breaks
125 backwards compatibility (and please take the opportunity to sort in any 125 backwards compatibility (and please take the opportunity to sort in any
@@ -648,7 +648,7 @@ struct plugin_api {
648 648
649 void (*thread_wait)(struct thread_entry *thread); 649 void (*thread_wait)(struct thread_entry *thread);
650 650
651#ifdef PROC_NEEDS_CACHEALIGN 651#if (CONFIG_CODEC == SWCODEC)
652 size_t (*align_buffer)(void **start, size_t size, size_t align); 652 size_t (*align_buffer)(void **start, size_t size, size_t align);
653#endif 653#endif
654 654
@@ -697,6 +697,25 @@ struct plugin_api {
697 bool (*search_albumart_files)(const struct mp3entry *id3, const char *size_string, 697 bool (*search_albumart_files)(const struct mp3entry *id3, const char *size_string,
698 char *buf, int buflen); 698 char *buf, int buflen);
699#endif 699#endif
700
701#if CONFIG_CODEC == SWCODEC
702 void (*pcm_play_lock)(void);
703 void (*pcm_play_unlock)(void);
704 void (*queue_enable_queue_send)(struct event_queue *q,
705 struct queue_sender_list *send);
706 bool (*queue_empty)(const struct event_queue *q);
707 void (*queue_wait)(struct event_queue *q, struct queue_event *ev);
708 intptr_t (*queue_send)(struct event_queue *q, long id,
709 intptr_t data);
710 void (*queue_reply)(struct event_queue *q, intptr_t retval);
711#ifndef HAVE_FLASH_STORAGE
712 void (*ata_spin)(void);
713#endif
714#ifdef HAVE_SCHEDULER_BOOSTCTRL
715 void (*trigger_cpu_boost)(void);
716 void (*cancel_cpu_boost)(void);
717#endif
718#endif /* CONFIG_CODEC == SWCODEC */
700}; 719};
701 720
702/* plugin header */ 721/* plugin header */
diff --git a/apps/plugins/mpegplayer/SOURCES b/apps/plugins/mpegplayer/SOURCES
index e7e2a7a0de..5b3360cc5a 100644
--- a/apps/plugins/mpegplayer/SOURCES
+++ b/apps/plugins/mpegplayer/SOURCES
@@ -18,5 +18,13 @@ motion_comp_c.c
18 18
19slice.c 19slice.c
20video_out_rockbox.c 20video_out_rockbox.c
21video_thread.c
22pcm_output.c
23audio_thread.c
24disk_buf.c
21mpeg_settings.c 25mpeg_settings.c
26stream_mgr.c
22mpegplayer.c 27mpegplayer.c
28mpeg_linkedlist.c
29mpeg_parser.c
30mpeg_misc.c
diff --git a/apps/plugins/mpegplayer/alloc.c b/apps/plugins/mpegplayer/alloc.c
index c79894447b..1ff75d4d64 100644
--- a/apps/plugins/mpegplayer/alloc.c
+++ b/apps/plugins/mpegplayer/alloc.c
@@ -22,10 +22,7 @@
22 */ 22 */
23 23
24#include "plugin.h" 24#include "plugin.h"
25 25#include "mpegplayer.h"
26#include "mpeg2.h"
27
28extern struct plugin_api* rb;
29 26
30/* Main allocator */ 27/* Main allocator */
31static off_t mem_ptr; 28static off_t mem_ptr;
@@ -33,9 +30,58 @@ static size_t bufsize;
33static unsigned char* mallocbuf; 30static unsigned char* mallocbuf;
34 31
35/* libmpeg2 allocator */ 32/* libmpeg2 allocator */
36static off_t mpeg2_mem_ptr; 33static off_t mpeg2_mem_ptr NOCACHEBSS_ATTR;
37static size_t mpeg2_bufsize; 34static size_t mpeg2_bufsize NOCACHEBSS_ATTR;
38static unsigned char *mpeg2_mallocbuf; 35static unsigned char *mpeg2_mallocbuf NOCACHEBSS_ATTR;
36static unsigned char *mpeg2_bufallocbuf NOCACHEBSS_ATTR;
37
38#if defined(DEBUG) || defined(SIMULATOR)
39const char * mpeg_get_reason_str(int reason)
40{
41 const char *str;
42
43 switch (reason)
44 {
45 case MPEG2_ALLOC_MPEG2DEC:
46 str = "MPEG2_ALLOC_MPEG2DEC";
47 break;
48 case MPEG2_ALLOC_CHUNK:
49 str = "MPEG2_ALLOC_CHUNK";
50 break;
51 case MPEG2_ALLOC_YUV:
52 str = "MPEG2_ALLOC_YUV";
53 break;
54 case MPEG2_ALLOC_CONVERT_ID:
55 str = "MPEG2_ALLOC_CONVERT_ID";
56 break;
57 case MPEG2_ALLOC_CONVERTED:
58 str = "MPEG2_ALLOC_CONVERTED";
59 break;
60 case MPEG_ALLOC_MPEG2_BUFFER:
61 str = "MPEG_ALLOC_MPEG2_BUFFER";
62 break;
63 case MPEG_ALLOC_AUDIOBUF:
64 str = "MPEG_ALLOC_AUDIOBUF";
65 break;
66 case MPEG_ALLOC_PCMOUT:
67 str = "MPEG_ALLOC_PCMOUT";
68 break;
69 case MPEG_ALLOC_DISKBUF:
70 str = "MPEG_ALLOC_DISKBUF";
71 break;
72 case MPEG_ALLOC_CODEC_MALLOC:
73 str = "MPEG_ALLOC_CODEC_MALLOC";
74 break;
75 case MPEG_ALLOC_CODEC_CALLOC:
76 str = "MPEG_ALLOC_CODEC_CALLOC";
77 break;
78 default:
79 str = "Unknown";
80 }
81
82 return str;
83}
84#endif
39 85
40static void * mpeg_malloc_internal (unsigned char *mallocbuf, 86static void * mpeg_malloc_internal (unsigned char *mallocbuf,
41 off_t *mem_ptr, 87 off_t *mem_ptr,
@@ -45,12 +91,15 @@ static void * mpeg_malloc_internal (unsigned char *mallocbuf,
45{ 91{
46 void *x; 92 void *x;
47 93
94 DEBUGF("mpeg_alloc_internal: bs:%lu s:%u reason:%s (%d)\n",
95 bufsize, size, mpeg_get_reason_str(reason), reason);
96
48 if ((size_t) (*mem_ptr + size) > bufsize) 97 if ((size_t) (*mem_ptr + size) > bufsize)
49 { 98 {
50 DEBUGF("OUT OF MEMORY\n"); 99 DEBUGF("OUT OF MEMORY\n");
51 return NULL; 100 return NULL;
52 } 101 }
53 102
54 x = &mallocbuf[*mem_ptr]; 103 x = &mallocbuf[*mem_ptr];
55 *mem_ptr += (size + 3) & ~3; /* Keep memory 32-bit aligned */ 104 *mem_ptr += (size + 3) & ~3; /* Keep memory 32-bit aligned */
56 105
@@ -64,39 +113,46 @@ void *mpeg_malloc(size_t size, mpeg2_alloc_t reason)
64 reason); 113 reason);
65} 114}
66 115
67size_t mpeg_alloc_init(unsigned char *buf, size_t mallocsize, 116void *mpeg_malloc_all(size_t *size_out, mpeg2_alloc_t reason)
68 size_t libmpeg2size) 117{
118 /* Can steal all but MIN_MEMMARGIN */
119 if (bufsize - mem_ptr < MIN_MEMMARGIN)
120 return NULL;
121
122 *size_out = bufsize - mem_ptr - MIN_MEMMARGIN;
123 return mpeg_malloc(*size_out, reason);
124}
125
126bool mpeg_alloc_init(unsigned char *buf, size_t mallocsize)
69{ 127{
70 mem_ptr = 0; 128 mem_ptr = 0;
71 bufsize = mallocsize; 129 /* Cache-align buffer or 4-byte align */
72 /* Line-align buffer */ 130 mallocbuf = buf;
73 mallocbuf = (char *)(((intptr_t)buf + 15) & ~15); 131 bufsize = align_buffer(PUN_PTR(void **, &mallocbuf),
74 /* Adjust for real size */ 132 mallocsize, CACHEALIGN_UP(4));
75 bufsize -= mallocbuf - buf;
76 133
77 /* Separate allocator for video */ 134 /* Separate allocator for video */
78 libmpeg2size = (libmpeg2size + 15) & ~15; 135 mpeg2_mem_ptr = 0;
136 mpeg2_mallocbuf = mallocbuf;
137 mpeg2_bufallocbuf = mallocbuf;
138 mpeg2_bufsize = CACHEALIGN_UP(LIBMPEG2_ALLOC_SIZE);
139
79 if (mpeg_malloc_internal(mallocbuf, &mem_ptr, 140 if (mpeg_malloc_internal(mallocbuf, &mem_ptr,
80 bufsize, libmpeg2size, 0) == NULL) 141 bufsize, mpeg2_bufsize,
142 MPEG_ALLOC_MPEG2_BUFFER) == NULL)
81 { 143 {
82 return 0; 144 return false;
83 } 145 }
84 146
85 mpeg2_mallocbuf = mallocbuf; 147 IF_COP(flush_icache());
86 mpeg2_mem_ptr = 0; 148 return true;
87 mpeg2_bufsize = libmpeg2size;
88
89#if NUM_CORES > 1
90 flush_icache();
91#endif
92
93 return bufsize - mpeg2_bufsize;
94} 149}
95 150
96/* gcc may want to use memcpy before rb is initialised, so here's a trivial 151/* gcc may want to use memcpy before rb is initialised, so here's a trivial
97 implementation */ 152 implementation */
98 153
99void *memcpy(void *dest, const void *src, size_t n) { 154void *memcpy(void *dest, const void *src, size_t n)
155{
100 size_t i; 156 size_t i;
101 char* d=(char*)dest; 157 char* d=(char*)dest;
102 char* s=(char*)src; 158 char* s=(char*)src;
@@ -107,22 +163,60 @@ void *memcpy(void *dest, const void *src, size_t n) {
107 return dest; 163 return dest;
108} 164}
109 165
166/* allocate non-dedicated buffer space which mpeg2_mem_reset will free */
110void * mpeg2_malloc(unsigned size, mpeg2_alloc_t reason) 167void * mpeg2_malloc(unsigned size, mpeg2_alloc_t reason)
111{ 168{
112 return mpeg_malloc_internal(mpeg2_mallocbuf, &mpeg2_mem_ptr, 169 void *ptr = mpeg_malloc_internal(mpeg2_mallocbuf, &mpeg2_mem_ptr,
113 mpeg2_bufsize, size, reason); 170 mpeg2_bufsize, size, reason);
171 /* libmpeg2 expects zero-initialized allocations */
172 if (ptr)
173 rb->memset(ptr, 0, size);
174
175 return ptr;
114} 176}
115 177
116void mpeg2_free(void *ptr) 178/* allocate dedicated buffer - memory behind buffer pointer becomes dedicated
179 so order is important */
180void * mpeg2_bufalloc(unsigned size, mpeg2_alloc_t reason)
117{ 181{
118 mpeg2_mem_ptr = (void *)ptr - (void *)mpeg2_mallocbuf; 182 void *buf = mpeg2_malloc(size, reason);
183
184 if (buf == NULL)
185 return NULL;
186
187 mpeg2_bufallocbuf = &mpeg2_mallocbuf[mpeg2_mem_ptr];
188 return buf;
189}
190
191/* return unused buffer portion and size */
192void * mpeg2_get_buf(size_t *size)
193{
194 if ((size_t)mpeg2_mem_ptr + 32 >= mpeg2_bufsize)
195 return NULL;
196
197 *size = mpeg2_bufsize - mpeg2_mem_ptr;
198 return &mpeg2_mallocbuf[mpeg2_mem_ptr];
199}
200
201/* de-allocate all non-dedicated buffer space */
202void mpeg2_mem_reset(void)
203{
204 DEBUGF("mpeg2_mem_reset\n");
205 mpeg2_mem_ptr = mpeg2_bufallocbuf - mpeg2_mallocbuf;
119} 206}
120 207
121/* The following are expected by libmad */ 208/* The following are expected by libmad */
122void * codec_malloc(size_t size) 209void * codec_malloc(size_t size)
123{ 210{
124 return mpeg_malloc_internal(mallocbuf, &mem_ptr, 211 void* ptr;
125 bufsize, size, -3); 212
213 ptr = mpeg_malloc_internal(mallocbuf, &mem_ptr,
214 bufsize, size, MPEG_ALLOC_CODEC_MALLOC);
215
216 if (ptr)
217 rb->memset(ptr,0,size);
218
219 return ptr;
126} 220}
127 221
128void * codec_calloc(size_t nmemb, size_t size) 222void * codec_calloc(size_t nmemb, size_t size)
@@ -130,17 +224,22 @@ void * codec_calloc(size_t nmemb, size_t size)
130 void* ptr; 224 void* ptr;
131 225
132 ptr = mpeg_malloc_internal(mallocbuf, &mem_ptr, 226 ptr = mpeg_malloc_internal(mallocbuf, &mem_ptr,
133 bufsize, nmemb*size, -3); 227 bufsize, nmemb*size,
228 MPEG_ALLOC_CODEC_CALLOC);
134 229
135 if (ptr) 230 if (ptr)
136 rb->memset(ptr,0,size); 231 rb->memset(ptr,0,size);
137 232
138 return ptr; 233 return ptr;
139} 234}
140 235
141void codec_free(void* ptr) 236void codec_free(void* ptr)
142{ 237{
238 DEBUGF("codec_free - %p\n", ptr);
239#if 0
143 mem_ptr = (void *)ptr - (void *)mallocbuf; 240 mem_ptr = (void *)ptr - (void *)mallocbuf;
241#endif
242 (void)ptr;
144} 243}
145 244
146void *memmove(void *dest, const void *src, size_t n) 245void *memmove(void *dest, const void *src, size_t n)
diff --git a/apps/plugins/mpegplayer/audio_thread.c b/apps/plugins/mpegplayer/audio_thread.c
new file mode 100644
index 0000000000..ee6ff05d2d
--- /dev/null
+++ b/apps/plugins/mpegplayer/audio_thread.c
@@ -0,0 +1,743 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * mpegplayer audio thread implementation
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "mpegplayer.h"
23#include "../../codecs/libmad/bit.h"
24#include "../../codecs/libmad/mad.h"
25
26/** Audio stream and thread **/
27struct pts_queue_slot;
28struct audio_thread_data
29{
30 struct queue_event ev; /* Our event queue to receive commands */
31 int state; /* Thread state */
32 int status; /* Media status (STREAM_PLAYING, etc.) */
33 int mad_errors; /* A count of the errors in each frame */
34};
35
36/* The audio stack is stolen from the core codec thread (but not in uisim) */
37/* Used for stealing codec thread's stack */
38static uint32_t* audio_stack;
39static size_t audio_stack_size; /* Keep gcc happy and init */
40#define AUDIO_STACKSIZE (9*1024)
41#ifndef SIMULATOR
42static uint32_t codec_stack_copy[AUDIO_STACKSIZE / sizeof(uint32_t)];
43#endif
44static struct event_queue audio_str_queue NOCACHEBSS_ATTR;
45static struct queue_sender_list audio_str_queue_send NOCACHEBSS_ATTR;
46struct stream audio_str IBSS_ATTR;
47
48/* libmad related definitions */
49static struct mad_stream stream IBSS_ATTR;
50static struct mad_frame frame IBSS_ATTR;
51static struct mad_synth synth IBSS_ATTR;
52
53/* 2567 bytes */
54static unsigned char mad_main_data[MAD_BUFFER_MDLEN];
55
56/* There isn't enough room for this in IRAM on PortalPlayer, but there
57 is for Coldfire. */
58
59/* 4608 bytes */
60#ifdef CPU_COLDFIRE
61static mad_fixed_t mad_frame_overlap[2][32][18] IBSS_ATTR;
62#else
63static mad_fixed_t mad_frame_overlap[2][32][18];
64#endif
65
66/** A queue for saving needed information about MPEG audio packets **/
67#define AUDIODESC_QUEUE_LEN (1 << 5) /* 32 should be way more than sufficient -
68 if not, the case is handled */
69#define AUDIODESC_QUEUE_MASK (AUDIODESC_QUEUE_LEN-1)
70struct audio_frame_desc
71{
72 uint32_t time; /* Time stamp for packet in audio ticks */
73 ssize_t size; /* Number of unprocessed bytes left in packet */
74};
75
76 /* This starts out wr == rd but will never be emptied to zero during
77 streaming again in order to support initializing the first packet's
78 timestamp without a special case */
79struct
80{
81 /* Compressed audio data */
82 uint8_t *start; /* Start of encoded audio buffer */
83 uint8_t *ptr; /* Pointer to next encoded audio data */
84 ssize_t used; /* Number of bytes in MPEG audio buffer */
85 /* Compressed audio data descriptors */
86 unsigned read, write;
87 struct audio_frame_desc *curr; /* Current slot */
88 struct audio_frame_desc descs[AUDIODESC_QUEUE_LEN];
89} audio_queue;
90
91static inline int audiodesc_queue_count(void)
92{
93 return audio_queue.write - audio_queue.read;
94}
95
96static inline bool audiodesc_queue_full(void)
97{
98 return audio_queue.used >= MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD ||
99 audiodesc_queue_count() >= AUDIODESC_QUEUE_LEN;
100}
101
102/* Increments the queue tail postion - should be used to preincrement */
103static inline void audiodesc_queue_add_tail(void)
104{
105 if (audiodesc_queue_full())
106 {
107 DEBUGF("audiodesc_queue_add_tail: audiodesc queue full!\n");
108 return;
109 }
110
111 audio_queue.write++;
112}
113
114/* Increments the queue tail position - leaves one slot as current */
115static inline bool audiodesc_queue_remove_head(void)
116{
117 if (audio_queue.write == audio_queue.read)
118 return false;
119
120 audio_queue.read++;
121 return true;
122}
123
124/* Returns the "tail" at the index just behind the write index */
125static inline struct audio_frame_desc * audiodesc_queue_tail(void)
126{
127 return &audio_queue.descs[(audio_queue.write - 1) & AUDIODESC_QUEUE_MASK];
128}
129
130/* Returns a pointer to the current head */
131static inline struct audio_frame_desc * audiodesc_queue_head(void)
132{
133 return &audio_queue.descs[audio_queue.read & AUDIODESC_QUEUE_MASK];
134}
135
136/* Resets the pts queue - call when starting and seeking */
137static void audio_queue_reset(void)
138{
139 audio_queue.ptr = audio_queue.start;
140 audio_queue.used = 0;
141 audio_queue.read = 0;
142 audio_queue.write = 0;
143 audio_queue.curr = audiodesc_queue_head();
144 audio_queue.curr->time = 0;
145 audio_queue.curr->size = 0;
146}
147
148static void audio_queue_advance_pos(ssize_t len)
149{
150 audio_queue.ptr += len;
151 audio_queue.used -= len;
152 audio_queue.curr->size -= len;
153}
154
155static int audio_buffer(struct stream *str, enum stream_parse_mode type)
156{
157 int ret = STREAM_OK;
158
159 /* Carry any overshoot to the next size since we're technically
160 -size bytes into it already. If size is negative an audio
161 frame was split across packets. Old has to be saved before
162 moving the head. */
163 if (audio_queue.curr->size <= 0 && audiodesc_queue_remove_head())
164 {
165 struct audio_frame_desc *old = audio_queue.curr;
166 audio_queue.curr = audiodesc_queue_head();
167 audio_queue.curr->size += old->size;
168 old->size = 0;
169 }
170
171 /* Add packets to compressed audio buffer until it's full or the
172 * timestamp queue is full - whichever happens first */
173 while (!audiodesc_queue_full())
174 {
175 ret = parser_get_next_data(str, type);
176 struct audio_frame_desc *curr;
177 ssize_t len;
178
179 if (ret != STREAM_OK)
180 break;
181
182 /* Get data from next audio packet */
183 len = str->curr_packet_end - str->curr_packet;
184
185 if (str->pkt_flags & PKT_HAS_TS)
186 {
187 audiodesc_queue_add_tail();
188 curr = audiodesc_queue_tail();
189 curr->time = TS_TO_TICKS(str->pts);
190 /* pts->size should have been zeroed when slot was
191 freed */
192 }
193 else
194 {
195 /* Add to the one just behind the tail - this may be
196 * the head or the previouly added tail - whether or
197 * not we'll ever reach this is quite in question
198 * since audio always seems to have every packet
199 * timestamped */
200 curr = audiodesc_queue_tail();
201 }
202
203 curr->size += len;
204
205 /* Slide any remainder over to beginning */
206 if (audio_queue.ptr > audio_queue.start && audio_queue.used > 0)
207 {
208 rb->memmove(audio_queue.start, audio_queue.ptr,
209 audio_queue.used);
210 }
211
212 /* Splice this packet onto any remainder */
213 rb->memcpy(audio_queue.start + audio_queue.used,
214 str->curr_packet, len);
215
216 audio_queue.used += len;
217 audio_queue.ptr = audio_queue.start;
218
219 rb->yield();
220 }
221
222 return ret;
223}
224
225/* Initialise libmad */
226static void init_mad(void)
227{
228 mad_stream_init(&stream);
229 mad_frame_init(&frame);
230 mad_synth_init(&synth);
231
232 /* We do this so libmad doesn't try to call codec_calloc() */
233 rb->memset(mad_frame_overlap, 0, sizeof(mad_frame_overlap));
234 frame.overlap = (void *)mad_frame_overlap;
235
236 rb->memset(mad_main_data, 0, sizeof(mad_main_data));
237 stream.main_data = &mad_main_data;
238}
239
240/* Sync audio stream to a particular frame - see main decoder loop for
241 * detailed remarks */
242static int audio_sync(struct audio_thread_data *td,
243 struct str_sync_data *sd)
244{
245 int retval = STREAM_MATCH;
246 uint32_t sdtime = TS_TO_TICKS(clip_time(&audio_str, sd->time));
247 uint32_t time;
248 uint32_t duration = 0;
249 struct stream *str;
250 struct stream tmp_str;
251 struct mad_header header;
252 struct mad_stream stream;
253
254 if (td->ev.id == STREAM_SYNC)
255 {
256 /* Actually syncing for playback - use real stream */
257 time = 0;
258 str = &audio_str;
259 }
260 else
261 {
262 /* Probing - use temp stream */
263 time = INVALID_TIMESTAMP;
264 str = &tmp_str;
265 str->id = audio_str.id;
266 }
267
268 str->hdr.pos = sd->sk.pos;
269 str->hdr.limit = sd->sk.pos + sd->sk.len;
270
271 mad_stream_init(&stream);
272 mad_header_init(&header);
273
274 while (1)
275 {
276 if (audio_buffer(str, STREAM_PM_RANDOM_ACCESS) == STREAM_DATA_END)
277 {
278 DEBUGF("audio_sync:STR_DATA_END\n aqu:%ld swl:%ld swr:%ld\n",
279 audio_queue.used, str->hdr.win_left, str->hdr.win_right);
280 if (audio_queue.used <= MAD_BUFFER_GUARD)
281 goto sync_data_end;
282 }
283
284 stream.error = 0;
285 mad_stream_buffer(&stream, audio_queue.ptr, audio_queue.used);
286
287 if (stream.sync && mad_stream_sync(&stream) < 0)
288 {
289 DEBUGF(" audio: mad_stream_sync failed\n");
290 audio_queue_advance_pos(MAX(audio_queue.curr->size - 1, 1));
291 continue;
292 }
293
294 stream.sync = 0;
295
296 if (mad_header_decode(&header, &stream) < 0)
297 {
298 DEBUGF(" audio: mad_header_decode failed:%s\n",
299 mad_stream_errorstr(&stream));
300 audio_queue_advance_pos(1);
301 continue;
302 }
303
304 duration = 32*MAD_NSBSAMPLES(&header);
305 time = audio_queue.curr->time;
306
307 DEBUGF(" audio: ft:%u t:%u fe:%u nsamp:%u sampr:%u\n",
308 (unsigned)TICKS_TO_TS(time), (unsigned)sd->time,
309 (unsigned)TICKS_TO_TS(time + duration),
310 (unsigned)duration, header.samplerate);
311
312 audio_queue_advance_pos(stream.this_frame - audio_queue.ptr);
313
314 if (time <= sdtime && sdtime < time + duration)
315 {
316 DEBUGF(" audio: ft<=t<fe\n");
317 retval = STREAM_PERFECT_MATCH;
318 break;
319 }
320 else if (time > sdtime)
321 {
322 DEBUGF(" audio: ft>t\n");
323 break;
324 }
325
326 audio_queue_advance_pos(stream.next_frame - audio_queue.ptr);
327 audio_queue.curr->time += duration;
328
329 rb->yield();
330 }
331
332sync_data_end:
333 if (td->ev.id == STREAM_FIND_END_TIME)
334 {
335 if (time != INVALID_TIMESTAMP)
336 {
337 time = TICKS_TO_TS(time);
338 duration = TICKS_TO_TS(duration);
339 sd->time = time + duration;
340 retval = STREAM_PERFECT_MATCH;
341 }
342 else
343 {
344 retval = STREAM_NOT_FOUND;
345 }
346 }
347
348 DEBUGF(" audio header: 0x%02X%02X%02X%02X\n",
349 (unsigned)audio_queue.ptr[0], (unsigned)audio_queue.ptr[1],
350 (unsigned)audio_queue.ptr[2], (unsigned)audio_queue.ptr[3]);
351
352 return retval;
353 (void)td;
354}
355
356static void audio_thread_msg(struct audio_thread_data *td)
357{
358 while (1)
359 {
360 intptr_t reply = 0;
361
362 switch (td->ev.id)
363 {
364 case STREAM_PLAY:
365 td->status = STREAM_PLAYING;
366
367 switch (td->state)
368 {
369 case TSTATE_INIT:
370 td->state = TSTATE_DECODE;
371 case TSTATE_DECODE:
372 case TSTATE_RENDER_WAIT:
373 case TSTATE_RENDER_WAIT_END:
374 break;
375
376 case TSTATE_EOS:
377 /* At end of stream - no playback possible so fire the
378 * completion event */
379 stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0);
380 break;
381 }
382
383 break;
384
385 case STREAM_PAUSE:
386 td->status = STREAM_PAUSED;
387 reply = td->state != TSTATE_EOS;
388 break;
389
390 case STREAM_STOP:
391 if (td->state == TSTATE_DATA)
392 stream_clear_notify(&audio_str, DISK_BUF_DATA_NOTIFY);
393
394 td->status = STREAM_STOPPED;
395 td->state = TSTATE_EOS;
396
397 reply = true;
398 break;
399
400 case STREAM_RESET:
401 if (td->state == TSTATE_DATA)
402 stream_clear_notify(&audio_str, DISK_BUF_DATA_NOTIFY);
403
404 td->status = STREAM_STOPPED;
405 td->state = TSTATE_INIT;
406
407 init_mad();
408 td->mad_errors = 0;
409
410 audio_queue_reset();
411
412 reply = true;
413 break;
414
415 case STREAM_NEEDS_SYNC:
416 reply = true; /* Audio always needs to */
417 break;
418
419 case STREAM_SYNC:
420 case STREAM_FIND_END_TIME:
421 if (td->state != TSTATE_INIT)
422 break;
423
424 reply = audio_sync(td, (struct str_sync_data *)td->ev.data);
425 break;
426
427 case DISK_BUF_DATA_NOTIFY:
428 /* Our bun is done */
429 if (td->state != TSTATE_DATA)
430 break;
431
432 td->state = TSTATE_DECODE;
433 str_data_notify_received(&audio_str);
434 break;
435
436 case STREAM_QUIT:
437 /* Time to go - make thread exit */
438 td->state = TSTATE_EOS;
439 return;
440 }
441
442 str_reply_msg(&audio_str, reply);
443
444 if (td->status == STREAM_PLAYING)
445 {
446 switch (td->state)
447 {
448 case TSTATE_DECODE:
449 case TSTATE_RENDER_WAIT:
450 case TSTATE_RENDER_WAIT_END:
451 /* These return when in playing state */
452 return;
453 }
454 }
455
456 str_get_msg(&audio_str, &td->ev);
457 }
458}
459
460static void audio_thread(void)
461{
462 struct audio_thread_data td;
463
464 td.status = STREAM_STOPPED;
465 td.state = TSTATE_EOS;
466
467 /* We need this here to init the EMAC for Coldfire targets */
468 init_mad();
469
470 goto message_wait;
471
472 /* This is the decoding loop. */
473 while (1)
474 {
475 td.state = TSTATE_DECODE;
476
477 /* Check for any pending messages and process them */
478 if (str_have_msg(&audio_str))
479 {
480 message_wait:
481 /* Wait for a message to be queued */
482 str_get_msg(&audio_str, &td.ev);
483
484 message_process:
485 /* Process a message already dequeued */
486 audio_thread_msg(&td);
487
488 switch (td.state)
489 {
490 /* These states are the only ones that should return */
491 case TSTATE_DECODE: goto audio_decode;
492 case TSTATE_RENDER_WAIT: goto render_wait;
493 case TSTATE_RENDER_WAIT_END: goto render_wait_end;
494 /* Anything else is interpreted as an exit */
495 default: return;
496 }
497 }
498
499 audio_decode:
500
501 /** Buffering **/
502 switch (audio_buffer(&audio_str, STREAM_PM_STREAMING))
503 {
504 case STREAM_DATA_NOT_READY:
505 {
506 td.state = TSTATE_DATA;
507 goto message_wait;
508 } /* STREAM_DATA_NOT_READY: */
509
510 case STREAM_DATA_END:
511 {
512 if (audio_queue.used > MAD_BUFFER_GUARD)
513 break;
514
515 /* Used up remainder of compressed audio buffer.
516 * Force any residue to play if audio ended before
517 * reaching the threshold */
518 td.state = TSTATE_RENDER_WAIT_END;
519 audio_queue_reset();
520
521 render_wait_end:
522 pcm_output_drain();
523
524 while (pcm_output_used() > (ssize_t)PCMOUT_LOW_WM)
525 {
526 str_get_msg_w_tmo(&audio_str, &td.ev, 1);
527 if (td.ev.id != SYS_TIMEOUT)
528 goto message_process;
529 }
530
531 td.state = TSTATE_EOS;
532 if (td.status == STREAM_PLAYING)
533 stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0);
534
535 rb->yield();
536 goto message_wait;
537 } /* STREAM_DATA_END: */
538 }
539
540 /** Decoding **/
541 mad_stream_buffer(&stream, audio_queue.ptr, audio_queue.used);
542
543 int mad_stat = mad_frame_decode(&frame, &stream);
544
545 ssize_t len = stream.next_frame - audio_queue.ptr;
546
547 if (mad_stat != 0)
548 {
549 DEBUGF("audio: Stream error: %s\n",
550 mad_stream_errorstr(&stream));
551
552 /* If something's goofed - try to perform resync by moving
553 * at least one byte at a time */
554 audio_queue_advance_pos(MAX(len, 1));
555
556 if (stream.error == MAD_FLAG_INCOMPLETE
557 || stream.error == MAD_ERROR_BUFLEN)
558 {
559 /* This makes the codec support partially corrupted files */
560 if (++td.mad_errors <= MPA_MAX_FRAME_SIZE)
561 {
562 stream.error = 0;
563 rb->yield();
564 continue;
565 }
566 DEBUGF("audio: Too many errors\n");
567 }
568 else if (MAD_RECOVERABLE(stream.error))
569 {
570 /* libmad says it can recover - just keep on decoding */
571 rb->yield();
572 continue;
573 }
574 else
575 {
576 /* Some other unrecoverable error */
577 DEBUGF("audio: Unrecoverable error\n");
578 }
579
580 /* This is too hard - bail out */
581 td.state = TSTATE_EOS;
582
583 if (td.status == STREAM_PLAYING)
584 stream_generate_event(&audio_str, STREAM_EV_COMPLETE, 0);
585
586 td.status = STREAM_ERROR;
587 goto message_wait;
588 }
589
590 /* Adjust sizes by the frame size */
591 audio_queue_advance_pos(len);
592 td.mad_errors = 0; /* Clear errors */
593
594 /* Generate the pcm samples */
595 mad_synth_frame(&synth, &frame);
596
597 /** Output **/
598
599 /* TODO: Output through core dsp. We'll still use our own PCM buffer
600 since the core pcm buffer has no timestamping or clock facilities */
601
602 /* Add a frame of audio to the pcm buffer. Maximum is 1152 samples. */
603 render_wait:
604 if (synth.pcm.length > 0)
605 {
606 struct pcm_frame_header *pcm_insert = pcm_output_get_buffer();
607 int16_t *audio_data = (int16_t *)pcm_insert->data;
608 unsigned length = synth.pcm.length;
609 ssize_t size = sizeof(*pcm_insert) + length*4;
610
611 td.state = TSTATE_RENDER_WAIT;
612
613 /* Wait for required amount of free buffer space */
614 while (pcm_output_free() < size)
615 {
616 /* Wait one frame */
617 int timeout = synth.pcm.length*HZ / synth.pcm.samplerate;
618 str_get_msg_w_tmo(&audio_str, &td.ev, MAX(timeout, 1));
619 if (td.ev.id != SYS_TIMEOUT)
620 goto message_process;
621 }
622
623 pcm_insert->time = audio_queue.curr->time;
624 pcm_insert->size = size;
625
626 /* As long as we're on this timestamp, the time is just incremented
627 by the number of samples */
628 audio_queue.curr->time += length;
629
630 if (MAD_NCHANNELS(&frame.header) == 2)
631 {
632 int32_t *left = &synth.pcm.samples[0][0];
633 int32_t *right = &synth.pcm.samples[1][0];
634
635 do
636 {
637 /* libmad outputs s3.28 */
638 *audio_data++ = clip_sample(*left++ >> 13);
639 *audio_data++ = clip_sample(*right++ >> 13);
640 }
641 while (--length > 0);
642 }
643 else /* mono */
644 {
645 int32_t *mono = &synth.pcm.samples[0][0];
646
647 do
648 {
649 int32_t s = clip_sample(*mono++ >> 13);
650 *audio_data++ = s;
651 *audio_data++ = s;
652 }
653 while (--length > 0);
654 }
655 /**/
656
657 /* Make this data available to DMA */
658 pcm_output_add_data();
659 }
660
661 rb->yield();
662 } /* end decoding loop */
663}
664
665/* Initializes the audio thread resources and starts the thread */
666bool audio_thread_init(void)
667{
668 int i;
669#ifdef SIMULATOR
670 /* The simulator thread implementation doesn't have stack buffers, and
671 these parameters are ignored. */
672 (void)i; /* Keep gcc happy */
673 audio_stack = NULL;
674 audio_stack_size = 0;
675#else
676 /* Borrow the codec thread's stack (in IRAM on most targets) */
677 audio_stack = NULL;
678 for (i = 0; i < MAXTHREADS; i++)
679 {
680 if (rb->strcmp(rb->threads[i].name, "codec") == 0)
681 {
682 /* Wait to ensure the codec thread has blocked */
683 while (rb->threads[i].state != STATE_BLOCKED)
684 rb->yield();
685
686 /* Now we can steal the stack */
687 audio_stack = rb->threads[i].stack;
688 audio_stack_size = rb->threads[i].stack_size;
689
690 /* Backup the codec thread's stack */
691 rb->memcpy(codec_stack_copy, audio_stack, audio_stack_size);
692 break;
693 }
694 }
695
696 if (audio_stack == NULL)
697 {
698 /* This shouldn't happen, but deal with it anyway by using
699 the copy instead */
700 audio_stack = codec_stack_copy;
701 audio_stack_size = AUDIO_STACKSIZE;
702 }
703#endif
704
705 /* Initialise the encoded audio buffer and its descriptors */
706 audio_queue.start = mpeg_malloc(AUDIOBUF_ALLOC_SIZE,
707 MPEG_ALLOC_AUDIOBUF);
708 if (audio_queue.start == NULL)
709 return false;
710
711 /* Start the audio thread */
712 audio_str.hdr.q = &audio_str_queue;
713 rb->queue_init(audio_str.hdr.q, false);
714 rb->queue_enable_queue_send(audio_str.hdr.q, &audio_str_queue_send);
715
716 audio_str.thread = rb->create_thread(
717 audio_thread, audio_stack, audio_stack_size, 0,
718 "mpgaudio" IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, CPU));
719
720 if (audio_str.thread == NULL)
721 return false;
722
723 /* Wait for thread to initialize */
724 str_send_msg(&audio_str, STREAM_NULL, 0);
725
726 return true;
727}
728
729/* Stops the audio thread */
730void audio_thread_exit(void)
731{
732 if (audio_str.thread != NULL)
733 {
734 str_post_msg(&audio_str, STREAM_QUIT, 0);
735 rb->thread_wait(audio_str.thread);
736 audio_str.thread = NULL;
737 }
738
739#ifndef SIMULATOR
740 /* Restore the codec thread's stack */
741 rb->memcpy(audio_stack, codec_stack_copy, audio_stack_size);
742#endif
743}
diff --git a/apps/plugins/mpegplayer/decode.c b/apps/plugins/mpegplayer/decode.c
index fac724c48c..2176cad601 100644
--- a/apps/plugins/mpegplayer/decode.c
+++ b/apps/plugins/mpegplayer/decode.c
@@ -497,8 +497,8 @@ mpeg2dec_t * mpeg2_init (void)
497 497
498 mpeg2_idct_init (); 498 mpeg2_idct_init ();
499 499
500 mpeg2dec = (mpeg2dec_t *)mpeg2_malloc(sizeof (mpeg2dec_t), 500 mpeg2dec = (mpeg2dec_t *)mpeg2_bufalloc(sizeof (mpeg2dec_t),
501 MPEG2_ALLOC_MPEG2DEC); 501 MPEG2_ALLOC_MPEG2DEC);
502 if (mpeg2dec == NULL) 502 if (mpeg2dec == NULL)
503 return NULL; 503 return NULL;
504 504
@@ -509,8 +509,8 @@ mpeg2dec_t * mpeg2_init (void)
509 rb->memset (mpeg2dec->decoder.DCTblock, 0, 64 * sizeof (int16_t)); 509 rb->memset (mpeg2dec->decoder.DCTblock, 0, 64 * sizeof (int16_t));
510 rb->memset (mpeg2dec->quantizer_matrix, 0, 4 * 64 * sizeof (uint8_t)); 510 rb->memset (mpeg2dec->quantizer_matrix, 0, 4 * 64 * sizeof (uint8_t));
511 511
512 mpeg2dec->chunk_buffer = (uint8_t *)mpeg2_malloc(BUFFER_SIZE + 4, 512 mpeg2dec->chunk_buffer = (uint8_t *)mpeg2_bufalloc(BUFFER_SIZE + 4,
513 MPEG2_ALLOC_CHUNK); 513 MPEG2_ALLOC_CHUNK);
514 514
515 mpeg2dec->sequence.width = (unsigned)-1; 515 mpeg2dec->sequence.width = (unsigned)-1;
516 mpeg2_reset (mpeg2dec, 1); 516 mpeg2_reset (mpeg2dec, 1);
@@ -521,6 +521,9 @@ mpeg2dec_t * mpeg2_init (void)
521void mpeg2_close (mpeg2dec_t * mpeg2dec) 521void mpeg2_close (mpeg2dec_t * mpeg2dec)
522{ 522{
523 mpeg2_header_state_init (mpeg2dec); 523 mpeg2_header_state_init (mpeg2dec);
524#if 0
525 /* These are dedicated buffers in rockbox */
524 mpeg2_free (mpeg2dec->chunk_buffer); 526 mpeg2_free (mpeg2dec->chunk_buffer);
525 mpeg2_free (mpeg2dec); 527 mpeg2_free (mpeg2dec);
528#endif
526} 529}
diff --git a/apps/plugins/mpegplayer/disk_buf.c b/apps/plugins/mpegplayer/disk_buf.c
new file mode 100644
index 0000000000..a408b90a67
--- /dev/null
+++ b/apps/plugins/mpegplayer/disk_buf.c
@@ -0,0 +1,906 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * mpegplayer buffering routines
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "mpegplayer.h"
23
24static struct mutex disk_buf_mtx NOCACHEBSS_ATTR;
25static struct event_queue disk_buf_queue NOCACHEBSS_ATTR;
26static struct queue_sender_list disk_buf_queue_send NOCACHEBSS_ATTR;
27static uint32_t disk_buf_stack[DEFAULT_STACK_SIZE*2/sizeof(uint32_t)];
28
29struct disk_buf disk_buf NOCACHEBSS_ATTR;
30static struct list_item nf_list;
31
32static inline void disk_buf_lock(void)
33{
34 rb->mutex_lock(&disk_buf_mtx);
35}
36
37static inline void disk_buf_unlock(void)
38{
39 rb->mutex_unlock(&disk_buf_mtx);
40}
41
42static inline void disk_buf_on_clear_data_notify(struct stream_hdr *sh)
43{
44 DEBUGF("DISK_BUF_CLEAR_DATA_NOTIFY: 0x%02X (cleared)\n",
45 STR_FROM_HEADER(sh)->id);
46 list_remove_item(&sh->nf);
47}
48
49static int disk_buf_on_data_notify(struct stream_hdr *sh)
50{
51 DEBUGF("DISK_BUF_DATA_NOTIFY: 0x%02X ", STR_FROM_HEADER(sh)->id);
52
53 if (sh->win_left <= sh->win_right)
54 {
55 /* Check if the data is already ready already */
56 if (disk_buf_is_data_ready(sh, 0))
57 {
58 /* It was - don't register */
59 DEBUGF("(was ready)\n"
60 " swl:%lu swr:%lu\n"
61 " dwl:%lu dwr:%lu\n",
62 sh->win_left, sh->win_right,
63 disk_buf.win_left, disk_buf.win_right);
64 /* Be sure it's not listed though if multiple requests were made */
65 list_remove_item(&sh->nf);
66 return DISK_BUF_NOTIFY_OK;
67 }
68
69 switch (disk_buf.state)
70 {
71 case TSTATE_DATA:
72 case TSTATE_BUFFERING:
73 case TSTATE_INIT:
74 disk_buf.state = TSTATE_BUFFERING;
75 list_add_item(&nf_list, &sh->nf);
76 DEBUGF("(registered)\n"
77 " swl:%lu swr:%lu\n"
78 " dwl:%lu dwr:%lu\n",
79 sh->win_left, sh->win_right,
80 disk_buf.win_left, disk_buf.win_right);
81 return DISK_BUF_NOTIFY_REGISTERED;
82 }
83 }
84
85 DEBUGF("(error)\n");
86 return DISK_BUF_NOTIFY_ERROR;
87}
88
89static bool check_data_notifies_callback(struct list_item *item,
90 intptr_t data)
91{
92 struct stream_hdr *sh = TYPE_FROM_MEMBER(struct stream_hdr, item, nf);
93
94 if (disk_buf_is_data_ready(sh, 0))
95 {
96 /* Remove from list then post notification - post because send
97 * could result in a wait for each thread to finish resulting
98 * in deadlock */
99 list_remove_item(item);
100 str_post_msg(STR_FROM_HEADER(sh), DISK_BUF_DATA_NOTIFY, 0);
101 DEBUGF("DISK_BUF_DATA_NOTIFY: 0x%02X (notified)\n",
102 STR_FROM_HEADER(sh)->id);
103 }
104
105 return true;
106 (void)data;
107}
108
109/* Check registered streams and notify them if their data is available */
110static void check_data_notifies(void)
111{
112 list_enum_items(&nf_list, check_data_notifies_callback, 0);
113}
114
115/* Clear all registered notifications - do not post them */
116static inline void clear_data_notifies(void)
117{
118 list_clear_all(&nf_list);
119}
120
121/* Background buffering when streaming */
122static inline void disk_buf_buffer(void)
123{
124 struct stream_window sw;
125
126 switch (disk_buf.state)
127 {
128 default:
129 {
130 size_t wm;
131 uint32_t time;
132
133 /* Get remaining minimum data based upon the stream closest to the
134 * right edge of the window */
135 if (!stream_get_window(&sw))
136 break;
137
138 time = stream_get_ticks(NULL);
139 wm = muldiv_uint32(5*CLOCK_RATE, sw.right - disk_buf.pos_last,
140 time - disk_buf.time_last);
141 wm = MIN(wm, (size_t)disk_buf.size);
142 wm = MAX(wm, DISK_BUF_LOW_WATERMARK);
143
144 disk_buf.time_last = time;
145 disk_buf.pos_last = sw.right;
146
147 /* Fast attack, slow decay */
148 disk_buf.low_wm = (wm > (size_t)disk_buf.low_wm) ?
149 wm : AVERAGE(disk_buf.low_wm, wm, 16);
150
151#if 0
152 rb->splash(0, "*%10ld %10ld", disk_buf.low_wm,
153 disk_buf.win_right - sw.right);
154#endif
155
156 if (disk_buf.win_right - sw.right > disk_buf.low_wm)
157 break;
158
159 disk_buf.state = TSTATE_BUFFERING;
160 } /* default: */
161
162 /* Fall-through */
163 case TSTATE_BUFFERING:
164 {
165 ssize_t len, n;
166 uint32_t tag, *tag_p;
167
168 /* Limit buffering up to the stream with the least progress */
169 if (!stream_get_window(&sw))
170 {
171 disk_buf.state = TSTATE_DATA;
172 break;
173 }
174
175 /* Wrap pointer */
176 if (disk_buf.tail >= disk_buf.end)
177 disk_buf.tail = disk_buf.start;
178
179 len = disk_buf.size - disk_buf.win_right + sw.left;
180
181 if (len < DISK_BUF_PAGE_SIZE)
182 {
183 /* Free space is less than one page */
184 disk_buf.state = TSTATE_DATA;
185 disk_buf.low_wm = DISK_BUF_LOW_WATERMARK;
186 break;
187 }
188
189 len = disk_buf.tail - disk_buf.start;
190 tag = MAP_OFFSET_TO_TAG(disk_buf.win_right);
191 tag_p = &disk_buf.cache[len >> DISK_BUF_PAGE_SHIFT];
192
193 if (*tag_p != tag)
194 {
195 if (disk_buf.need_seek)
196 {
197 rb->lseek(disk_buf.in_file, disk_buf.win_right, SEEK_SET);
198 disk_buf.need_seek = false;
199 }
200
201 n = rb->read(disk_buf.in_file, disk_buf.tail, DISK_BUF_PAGE_SIZE);
202
203 if (n <= 0)
204 {
205 /* Error or end of stream */
206 disk_buf.state = TSTATE_EOS;
207 break;
208 }
209
210 if (len < DISK_GUARDBUF_SIZE)
211 {
212 /* Autoguard guard-o-rama - maintain guardbuffer coherency */
213 rb->memcpy(disk_buf.end + len, disk_buf.tail,
214 MIN(DISK_GUARDBUF_SIZE - len, n));
215 }
216
217 /* Update the cache entry for this page */
218 *tag_p = tag;
219 }
220 else
221 {
222 /* Skipping a read */
223 n = MIN(DISK_BUF_PAGE_SIZE,
224 disk_buf.filesize - disk_buf.win_right);
225 disk_buf.need_seek = true;
226 }
227
228 disk_buf.tail += DISK_BUF_PAGE_SIZE;
229
230 /* Keep left edge moving forward */
231
232 /* Advance right edge in temp variable first, then move
233 * left edge if overflow would occur. This avoids a stream
234 * thinking its data might be available when it actually
235 * may not end up that way after a slide of the window. */
236 len = disk_buf.win_right + n;
237
238 if (len - disk_buf.win_left > disk_buf.size)
239 disk_buf.win_left += n;
240
241 disk_buf.win_right = len;
242
243 /* Continue buffering until filled or file end */
244 rb->yield();
245 } /* TSTATE_BUFFERING: */
246
247 case TSTATE_EOS:
248 break;
249 } /* end switch */
250}
251
252static void disk_buf_on_reset(ssize_t pos)
253{
254 int page;
255 uint32_t tag;
256 off_t anchor;
257
258 disk_buf.state = TSTATE_INIT;
259 disk_buf.status = STREAM_STOPPED;
260 clear_data_notifies();
261
262 if (pos >= disk_buf.filesize)
263 {
264 /* Anchor on page immediately following the one containing final
265 * data */
266 anchor = disk_buf.file_pages * DISK_BUF_PAGE_SIZE;
267 disk_buf.win_left = disk_buf.filesize;
268 }
269 else
270 {
271 anchor = pos & ~DISK_BUF_PAGE_MASK;
272 disk_buf.win_left = anchor;
273 }
274
275 /* Collect all valid data already buffered that is contiguous with the
276 * current position - probe to left, then to right */
277 if (anchor > 0)
278 {
279 page = MAP_OFFSET_TO_PAGE(anchor);
280 tag = MAP_OFFSET_TO_TAG(anchor);
281
282 do
283 {
284 if (--tag, --page < 0)
285 page = disk_buf.pgcount - 1;
286
287 if (disk_buf.cache[page] != tag)
288 break;
289
290 disk_buf.win_left = tag << DISK_BUF_PAGE_SHIFT;
291 }
292 while (tag > 0);
293 }
294
295 if (anchor < disk_buf.filesize)
296 {
297 page = MAP_OFFSET_TO_PAGE(anchor);
298 tag = MAP_OFFSET_TO_TAG(anchor);
299
300 do
301 {
302 if (disk_buf.cache[page] != tag)
303 break;
304
305 if (++tag, ++page >= disk_buf.pgcount)
306 page = 0;
307
308 anchor += DISK_BUF_PAGE_SIZE;
309 }
310 while (anchor < disk_buf.filesize);
311 }
312
313 if (anchor >= disk_buf.filesize)
314 {
315 disk_buf.win_right = disk_buf.filesize;
316 disk_buf.state = TSTATE_EOS;
317 }
318 else
319 {
320 disk_buf.win_right = anchor;
321 }
322
323 disk_buf.tail = disk_buf.start + MAP_OFFSET_TO_BUFFER(anchor);
324
325 DEBUGF("disk buf reset\n"
326 " dwl:%ld dwr:%ld\n",
327 disk_buf.win_left, disk_buf.win_right);
328
329 /* Next read position is at right edge */
330 rb->lseek(disk_buf.in_file, disk_buf.win_right, SEEK_SET);
331 disk_buf.need_seek = false;
332
333 disk_buf_reply_msg(disk_buf.win_right - disk_buf.win_left);
334}
335
336static void disk_buf_on_stop(void)
337{
338 bool was_buffering = disk_buf.state == TSTATE_BUFFERING;
339
340 disk_buf.state = TSTATE_EOS;
341 disk_buf.status = STREAM_STOPPED;
342 clear_data_notifies();
343
344 disk_buf_reply_msg(was_buffering);
345}
346
347static void disk_buf_on_play_pause(bool play, bool forcefill)
348{
349 struct stream_window sw;
350
351 if (disk_buf.state != TSTATE_EOS)
352 {
353 if (forcefill)
354 {
355 /* Force buffer filling to top */
356 disk_buf.state = TSTATE_BUFFERING;
357 }
358 else if (disk_buf.state != TSTATE_BUFFERING)
359 {
360 /* If not filling already, simply monitor */
361 disk_buf.state = TSTATE_DATA;
362 }
363 }
364 /* else end of stream - no buffering to do */
365
366 disk_buf.pos_last = stream_get_window(&sw) ? sw.right : 0;
367 disk_buf.time_last = stream_get_ticks(NULL);
368
369 disk_buf.status = play ? STREAM_PLAYING : STREAM_PAUSED;
370}
371
372static int disk_buf_on_load_range(struct dbuf_range *rng)
373{
374 uint32_t tag = rng->tag_start;
375 uint32_t tag_end = rng->tag_end;
376 int page = rng->pg_start;
377
378 /* Check if a seek is required */
379 bool need_seek = rb->lseek(disk_buf.in_file, 0, SEEK_CUR)
380 != (off_t)(tag << DISK_BUF_PAGE_SHIFT);
381
382 do
383 {
384 uint32_t *tag_p = &disk_buf.cache[page];
385
386 if (*tag_p != tag)
387 {
388 /* Page not cached - load it */
389 ssize_t o, n;
390
391 if (need_seek)
392 {
393 rb->lseek(disk_buf.in_file, tag << DISK_BUF_PAGE_SHIFT,
394 SEEK_SET);
395 need_seek = false;
396 }
397
398 o = page << DISK_BUF_PAGE_SHIFT;
399 n = rb->read(disk_buf.in_file, disk_buf.start + o,
400 DISK_BUF_PAGE_SIZE);
401
402 if (n < 0)
403 {
404 /* Read error */
405 return DISK_BUF_NOTIFY_ERROR;
406 }
407
408 if (n == 0)
409 {
410 /* End of file */
411 break;
412 }
413
414 if (o < DISK_GUARDBUF_SIZE)
415 {
416 /* Autoguard guard-o-rama - maintain guardbuffer coherency */
417 rb->memcpy(disk_buf.end + o, disk_buf.start + o,
418 MIN(DISK_GUARDBUF_SIZE - o, n));
419 }
420
421 /* Update the cache entry */
422 *tag_p = tag;
423 }
424 else
425 {
426 /* Skipping a disk read - must seek on next one */
427 need_seek = true;
428 }
429
430 if (++page >= disk_buf.pgcount)
431 page = 0;
432 }
433 while (++tag <= tag_end);
434
435 return DISK_BUF_NOTIFY_OK;
436}
437
438static void disk_buf_thread(void)
439{
440 struct queue_event ev;
441
442 disk_buf.state = TSTATE_EOS;
443 disk_buf.status = STREAM_STOPPED;
444
445 while (1)
446 {
447 if (disk_buf.state != TSTATE_EOS)
448 {
449 /* Poll buffer status and messages */
450 rb->queue_wait_w_tmo(disk_buf.q, &ev,
451 disk_buf.state == TSTATE_BUFFERING ?
452 0 : HZ/5);
453 }
454 else
455 {
456 /* Sit idle and wait for commands */
457 rb->queue_wait(disk_buf.q, &ev);
458 }
459
460 switch (ev.id)
461 {
462 case SYS_TIMEOUT:
463 if (disk_buf.state == TSTATE_EOS)
464 break;
465
466 disk_buf_buffer();
467
468 /* Check for any due notifications if any are pending */
469 if (nf_list.next != NULL)
470 check_data_notifies();
471
472 /* Still more data left? */
473 if (disk_buf.state != TSTATE_EOS)
474 continue;
475
476 /* Nope - end of stream */
477 break;
478
479 case DISK_BUF_CACHE_RANGE:
480 disk_buf_reply_msg(disk_buf_on_load_range(
481 (struct dbuf_range *)ev.data));
482 break;
483
484 case STREAM_RESET:
485 disk_buf_on_reset(ev.data);
486 break;
487
488 case STREAM_STOP:
489 disk_buf_on_stop();
490 break;
491
492 case STREAM_PAUSE:
493 case STREAM_PLAY:
494 disk_buf_on_play_pause(ev.id == STREAM_PLAY, ev.data != 0);
495 disk_buf_reply_msg(1);
496 break;
497
498 case STREAM_QUIT:
499 disk_buf.state = TSTATE_EOS;
500 return;
501
502 case DISK_BUF_DATA_NOTIFY:
503 disk_buf_reply_msg(disk_buf_on_data_notify(
504 (struct stream_hdr *)ev.data));
505 break;
506
507 case DISK_BUF_CLEAR_DATA_NOTIFY:
508 disk_buf_on_clear_data_notify((struct stream_hdr *)ev.data);
509 disk_buf_reply_msg(1);
510 break;
511 }
512 }
513}
514
515/* Caches some data from the current file */
516static int disk_buf_probe(off_t start, size_t length,
517 void **p, size_t *outlen)
518{
519 off_t end;
520 uint32_t tag, tag_end;
521 int page;
522
523 /* Can't read past end of file */
524 if (length > (size_t)(disk_buf.filesize - disk_buf.offset))
525 {
526 length = disk_buf.filesize - disk_buf.offset;
527 }
528
529 /* Can't cache more than the whole buffer size */
530 if (length > (size_t)disk_buf.size)
531 {
532 length = disk_buf.size;
533 }
534 /* Zero-length probes permitted */
535
536 end = start + length;
537
538 /* Prepare the range probe */
539 tag = MAP_OFFSET_TO_TAG(start);
540 tag_end = MAP_OFFSET_TO_TAG(end);
541 page = MAP_OFFSET_TO_PAGE(start);
542
543 /* If the end is on a page boundary, check one less or an extra
544 * one will be probed */
545 if (tag_end > tag && (end & DISK_BUF_PAGE_MASK) == 0)
546 {
547 tag_end--;
548 }
549
550 if (p != NULL)
551 {
552 *p = disk_buf.start + (page << DISK_BUF_PAGE_SHIFT)
553 + (start & DISK_BUF_PAGE_MASK);
554 }
555
556 if (outlen != NULL)
557 {
558 *outlen = length;
559 }
560
561 /* Obtain initial load point. If all data was cached, no message is sent
562 * otherwise begin on the first page that is not cached. Since we have to
563 * send the message anyway, the buffering thread will determine what else
564 * requires loading on its end in order to cache the specified range. */
565 do
566 {
567 if (disk_buf.cache[page] != tag)
568 {
569 static struct dbuf_range rng NOCACHEBSS_ATTR;
570 DEBUGF("disk_buf: cache miss\n");
571 rng.tag_start = tag;
572 rng.tag_end = tag_end;
573 rng.pg_start = page;
574 return rb->queue_send(disk_buf.q, DISK_BUF_CACHE_RANGE,
575 (intptr_t)&rng);
576 }
577
578 if (++page >= disk_buf.pgcount)
579 page = 0;
580 }
581 while (++tag <= tag_end);
582
583 return DISK_BUF_NOTIFY_OK;
584}
585
586/* Attempt to get a pointer to size bytes on the buffer. Returns real amount of
587 * data available as well as the size of non-wrapped data after *p. */
588ssize_t _disk_buf_getbuffer(size_t size, void **pp, void **pwrap, size_t *sizewrap)
589{
590 disk_buf_lock();
591
592 if (disk_buf_probe(disk_buf.offset, size, pp, &size) == DISK_BUF_NOTIFY_OK)
593 {
594 if (pwrap && sizewrap)
595 {
596 uint8_t *p = (uint8_t *)*pp;
597
598 if (p + size > disk_buf.end + DISK_GUARDBUF_SIZE)
599 {
600 /* Return pointer to wraparound and the size of same */
601 size_t nowrap = (disk_buf.end + DISK_GUARDBUF_SIZE) - p;
602 *pwrap = disk_buf.start + DISK_GUARDBUF_SIZE;
603 *sizewrap = size - nowrap;
604 }
605 else
606 {
607 *pwrap = NULL;
608 *sizewrap = 0;
609 }
610 }
611 }
612 else
613 {
614 size = -1;
615 }
616
617 disk_buf_unlock();
618
619 return size;
620}
621
622/* Read size bytes of data into a buffer - advances the buffer pointer
623 * and returns the real size read. */
624ssize_t disk_buf_read(void *buffer, size_t size)
625{
626 uint8_t *p;
627
628 disk_buf_lock();
629
630 if (disk_buf_probe(disk_buf.offset, size, PUN_PTR(void **, &p),
631 &size) == DISK_BUF_NOTIFY_OK)
632 {
633 if (p + size > disk_buf.end + DISK_GUARDBUF_SIZE)
634 {
635 /* Read wraps */
636 size_t nowrap = (disk_buf.end + DISK_GUARDBUF_SIZE) - p;
637 rb->memcpy(buffer, p, nowrap);
638 rb->memcpy(buffer + nowrap, disk_buf.start + DISK_GUARDBUF_SIZE,
639 size - nowrap);
640 }
641 else
642 {
643 /* Read wasn't wrapped or guardbuffer holds it */
644 rb->memcpy(buffer, p, size);
645 }
646
647 disk_buf.offset += size;
648 }
649 else
650 {
651 size = -1;
652 }
653
654 disk_buf_unlock();
655
656 return size;
657}
658
659off_t disk_buf_lseek(off_t offset, int whence)
660{
661 disk_buf_lock();
662
663 /* The offset returned is the result of the current thread's action and
664 * may be invalidated so a local result is returned and not the value
665 * of disk_buf.offset directly */
666 switch (whence)
667 {
668 case SEEK_SET:
669 /* offset is just the offset */
670 break;
671 case SEEK_CUR:
672 offset += disk_buf.offset;
673 break;
674 case SEEK_END:
675 offset = disk_buf.filesize + offset;
676 break;
677 default:
678 disk_buf_unlock();
679 return -2; /* Invalid request */
680 }
681
682 if (offset < 0 || offset > disk_buf.filesize)
683 {
684 offset = -3;
685 }
686 else
687 {
688 disk_buf.offset = offset;
689 }
690
691 disk_buf_unlock();
692
693 return offset;
694}
695
696/* Prepare the buffer to enter the streaming state. Evaluates the available
697 * streaming window. */
698ssize_t disk_buf_prepare_streaming(off_t pos, size_t len)
699{
700 disk_buf_lock();
701
702 if (pos < 0)
703 pos = 0;
704 else if (pos > disk_buf.filesize)
705 pos = disk_buf.filesize;
706
707 DEBUGF("prepare streaming:\n pos:%ld len:%lu\n", pos, len);
708
709 pos = disk_buf_lseek(pos, SEEK_SET);
710 disk_buf_probe(pos, len, NULL, &len);
711
712 DEBUGF(" probe done: pos:%ld len:%lu\n", pos, len);
713
714 len = disk_buf_send_msg(STREAM_RESET, pos);
715
716 disk_buf_unlock();
717
718 return len;
719}
720
721/* Set the streaming window to an arbitrary position within the file. Makes no
722 * probes to validate data. Use after calling another function to cause data
723 * to be cached and correct values are known. */
724ssize_t disk_buf_set_streaming_window(off_t left, off_t right)
725{
726 ssize_t len;
727
728 disk_buf_lock();
729
730 if (left < 0)
731 left = 0;
732 else if (left > disk_buf.filesize)
733 left = disk_buf.filesize;
734
735 if (left > right)
736 right = left;
737
738 if (right > disk_buf.filesize)
739 right = disk_buf.filesize;
740
741 disk_buf.win_left = left;
742 disk_buf.win_right = right;
743 disk_buf.tail = disk_buf.start + ((right + DISK_BUF_PAGE_SIZE-1) &
744 ~DISK_BUF_PAGE_MASK) % disk_buf.size;
745
746 len = disk_buf.win_right - disk_buf.win_left;
747
748 disk_buf_unlock();
749
750 return len;
751}
752
753void * disk_buf_offset2ptr(off_t offset)
754{
755 if (offset < 0)
756 offset = 0;
757 else if (offset > disk_buf.filesize)
758 offset = disk_buf.filesize;
759
760 return disk_buf.start + (offset % disk_buf.size);
761}
762
763void disk_buf_close(void)
764{
765 disk_buf_lock();
766
767 if (disk_buf.in_file >= 0)
768 {
769 rb->close(disk_buf.in_file);
770 disk_buf.in_file = -1;
771
772 /* Invalidate entire cache */
773 rb->memset(disk_buf.cache, 0xff,
774 disk_buf.pgcount*sizeof (*disk_buf.cache));
775 disk_buf.file_pages = 0;
776 disk_buf.filesize = 0;
777 disk_buf.offset = 0;
778 }
779
780 disk_buf_unlock();
781}
782
783int disk_buf_open(const char *filename)
784{
785 int fd;
786
787 disk_buf_lock();
788
789 disk_buf_close();
790
791 fd = rb->open(filename, O_RDONLY);
792
793 if (fd >= 0)
794 {
795 ssize_t filesize = rb->filesize(fd);
796
797 if (filesize <= 0)
798 {
799 rb->close(disk_buf.in_file);
800 }
801 else
802 {
803 disk_buf.filesize = filesize;
804 /* Number of file pages rounded up toward +inf */
805 disk_buf.file_pages = ((size_t)filesize + DISK_BUF_PAGE_SIZE-1)
806 / DISK_BUF_PAGE_SIZE;
807 disk_buf.in_file = fd;
808 }
809 }
810
811 disk_buf_unlock();
812
813 return fd;
814}
815
816intptr_t disk_buf_send_msg(long id, intptr_t data)
817{
818 return rb->queue_send(disk_buf.q, id, data);
819}
820
821void disk_buf_post_msg(long id, intptr_t data)
822{
823 rb->queue_post(disk_buf.q, id, data);
824}
825
826void disk_buf_reply_msg(intptr_t retval)
827{
828 rb->queue_reply(disk_buf.q, retval);
829}
830
831bool disk_buf_init(void)
832{
833 disk_buf.thread = NULL;
834 list_initialize(&nf_list);
835
836 rb->mutex_init(&disk_buf_mtx);
837
838 disk_buf.q = &disk_buf_queue;
839 rb->queue_init(disk_buf.q, false);
840 rb->queue_enable_queue_send(disk_buf.q, &disk_buf_queue_send);
841
842 disk_buf.state = TSTATE_EOS;
843 disk_buf.status = STREAM_STOPPED;
844
845 disk_buf.in_file = -1;
846 disk_buf.filesize = 0;
847 disk_buf.win_left = 0;
848 disk_buf.win_right = 0;
849 disk_buf.time_last = 0;
850 disk_buf.pos_last = 0;
851 disk_buf.low_wm = DISK_BUF_LOW_WATERMARK;
852
853 disk_buf.start = mpeg_malloc_all(&disk_buf.size, MPEG_ALLOC_DISKBUF);
854 if (disk_buf.start == NULL)
855 return false;
856
857#ifdef PROC_NEEDS_CACHEALIGN
858 disk_buf.size = CACHEALIGN_BUFFER(&disk_buf.start, disk_buf.size);
859 disk_buf.start = UNCACHED_ADDR(disk_buf.start);
860#endif
861 disk_buf.size -= DISK_GUARDBUF_SIZE;
862 disk_buf.pgcount = disk_buf.size / DISK_BUF_PAGE_SIZE;
863
864 /* Fit it as tightly as possible */
865 while (disk_buf.pgcount*(sizeof (*disk_buf.cache) + DISK_BUF_PAGE_SIZE)
866 > (size_t)disk_buf.size)
867 {
868 disk_buf.pgcount--;
869 }
870
871 disk_buf.cache = (typeof (disk_buf.cache))disk_buf.start;
872 disk_buf.start += sizeof (*disk_buf.cache)*disk_buf.pgcount;
873 disk_buf.size = disk_buf.pgcount*DISK_BUF_PAGE_SIZE;
874 disk_buf.end = disk_buf.start + disk_buf.size;
875 disk_buf.tail = disk_buf.start;
876
877 DEBUGF("disk_buf info:\n"
878 " page count: %d\n"
879 " size: %ld\n",
880 disk_buf.pgcount, disk_buf.size);
881
882 rb->memset(disk_buf.cache, 0xff,
883 disk_buf.pgcount*sizeof (*disk_buf.cache));
884
885 disk_buf.thread = rb->create_thread(
886 disk_buf_thread, disk_buf_stack, sizeof(disk_buf_stack), 0,
887 "mpgbuffer" IF_PRIO(, PRIORITY_BUFFERING) IF_COP(, CPU));
888
889 if (disk_buf.thread == NULL)
890 return false;
891
892 /* Wait for thread to initialize */
893 disk_buf_send_msg(STREAM_NULL, 0);
894
895 return true;
896}
897
898void disk_buf_exit(void)
899{
900 if (disk_buf.thread != NULL)
901 {
902 rb->queue_post(disk_buf.q, STREAM_QUIT, 0);
903 rb->thread_wait(disk_buf.thread);
904 disk_buf.thread = NULL;
905 }
906}
diff --git a/apps/plugins/mpegplayer/disk_buf.h b/apps/plugins/mpegplayer/disk_buf.h
new file mode 100644
index 0000000000..90e72fac2f
--- /dev/null
+++ b/apps/plugins/mpegplayer/disk_buf.h
@@ -0,0 +1,132 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * AV disk buffer declarations
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef DISK_BUF_H
22#define DISK_BUF_H
23
24#define DISK_BUF_PAGE_SHIFT 15 /* 32KB cache lines */
25#define DISK_BUF_PAGE_SIZE (1 << DISK_BUF_PAGE_SHIFT)
26#define DISK_BUF_PAGE_MASK (DISK_BUF_PAGE_SIZE-1)
27
28enum
29{
30 DISK_BUF_NOTIFY_ERROR = -1,
31 DISK_BUF_NOTIFY_NULL = 0,
32 DISK_BUF_NOTIFY_OK,
33 DISK_BUF_NOTIFY_TIMEDOUT,
34 DISK_BUF_NOTIFY_PROCESS_EVENT,
35 DISK_BUF_NOTIFY_REGISTERED,
36};
37
38/** Macros to map file offsets to cached data **/
39
40/* Returns a cache tag given a file offset */
41#define MAP_OFFSET_TO_TAG(o) \
42 ((o) >> DISK_BUF_PAGE_SHIFT)
43
44/* Returns the cache page number given a file offset */
45#define MAP_OFFSET_TO_PAGE(o) \
46 (MAP_OFFSET_TO_TAG(o) % disk_buf.pgcount)
47
48/* Returns the buffer offset given a file offset */
49#define MAP_OFFSET_TO_BUFFER(o) \
50 (MAP_OFFSET_TO_PAGE(o) * DISK_BUF_PAGE_SIZE)
51
52struct dbuf_range
53{
54 uint32_t tag_start;
55 uint32_t tag_end;
56 int pg_start;
57};
58
59/* This object is an extension of the stream manager and handles some
60 * playback events as well as buffering */
61struct disk_buf
62{
63 struct thread_entry *thread;
64 struct event_queue *q;
65 uint8_t *start; /* Start pointer */
66 uint8_t *end; /* End of buffer pointer less MPEG_GUARDBUF_SIZE. The
67 guard space is used to wrap data at the buffer start to
68 pass continuous data packets */
69 uint8_t *tail; /* Location of last data + 1 filled into the buffer */
70 ssize_t size; /* The buffer length _not_ including the guard space (end-start) */
71 int pgcount; /* Total number of available cached pages */
72 uint32_t *cache; /* Pointer to cache structure - allocated on buffer */
73 int in_file; /* File being read */
74 ssize_t filesize; /* Size of file in_file in bytes */
75 int file_pages; /* Number of pages in file (rounded up) */
76 off_t offset; /* Current position (random access) */
77 off_t win_left; /* Left edge of buffer window (streaming) */
78 off_t win_right; /* Right edge of buffer window (streaming) */
79 uint32_t time_last; /* Last time watermark was checked */
80 off_t pos_last; /* Last position at watermark check time */
81 ssize_t low_wm; /* The low watermark for automatic rebuffering */
82 int status; /* Status as stream */
83 int state; /* Current thread state */
84 bool need_seek; /* Need to seek because a read was not contiguous */
85};
86
87extern struct disk_buf disk_buf NOCACHEBSS_ATTR;
88
89static inline bool disk_buf_is_data_ready(struct stream_hdr *sh,
90 ssize_t margin)
91{
92 /* Data window available? */
93 off_t right = sh->win_right;
94
95 /* Margins past end-of-file can still return true */
96 if (right > disk_buf.filesize - margin)
97 right = disk_buf.filesize - margin;
98
99 return sh->win_left >= disk_buf.win_left &&
100 right + margin <= disk_buf.win_right;
101}
102
103
104bool disk_buf_init(void);
105void disk_buf_exit(void);
106
107int disk_buf_open(const char *filename);
108void disk_buf_close(void);
109ssize_t _disk_buf_getbuffer(size_t size, void **pp, void **pwrap,
110 size_t *sizewrap);
111#define disk_buf_getbuffer(size, pp, pwrap, sizewrap) \
112 _disk_buf_getbuffer((size), PUN_PTR(void **, (pp)), \
113 PUN_PTR(void **, (pwrap)), (sizewrap))
114ssize_t disk_buf_read(void *buffer, size_t size);
115ssize_t disk_buf_lseek(off_t offset, int whence);
116
117static inline off_t disk_buf_ftell(void)
118 { return disk_buf.offset; }
119
120static inline ssize_t disk_buf_filesize(void)
121 { return disk_buf.filesize; }
122
123ssize_t disk_buf_prepare_streaming(off_t pos, size_t len);
124ssize_t disk_buf_set_streaming_window(off_t left, off_t right);
125void * disk_buf_offset2ptr(off_t offset);
126int disk_buf_check_streaming_window(off_t left, off_t right);
127
128intptr_t disk_buf_send_msg(long id, intptr_t data);
129void disk_buf_post_msg(long id, intptr_t data);
130void disk_buf_reply_msg(intptr_t retval);
131
132#endif /* DISK_BUF_H */
diff --git a/apps/plugins/mpegplayer/header.c b/apps/plugins/mpegplayer/header.c
index 52a301f8d8..9e6e6de03d 100644
--- a/apps/plugins/mpegplayer/header.c
+++ b/apps/plugins/mpegplayer/header.c
@@ -92,12 +92,12 @@ void mpeg2_header_state_init (mpeg2dec_t * mpeg2dec)
92{ 92{
93 if (mpeg2dec->sequence.width != (unsigned)-1) 93 if (mpeg2dec->sequence.width != (unsigned)-1)
94 { 94 {
95 int i;
96
97 mpeg2dec->sequence.width = (unsigned)-1; 95 mpeg2dec->sequence.width = (unsigned)-1;
98 96 mpeg2_mem_reset(); /* Clean the memory slate */
97#if 0
99 if (!mpeg2dec->custom_fbuf) 98 if (!mpeg2dec->custom_fbuf)
100 { 99 {
100 int i;
101 for (i = mpeg2dec->alloc_index_user; 101 for (i = mpeg2dec->alloc_index_user;
102 i < mpeg2dec->alloc_index; i++) 102 i < mpeg2dec->alloc_index; i++)
103 { 103 {
@@ -109,6 +109,7 @@ void mpeg2_header_state_init (mpeg2dec_t * mpeg2dec)
109 109
110 if (mpeg2dec->convert_start) 110 if (mpeg2dec->convert_start)
111 { 111 {
112 int i;
112 for (i = 0; i < 3; i++) 113 for (i = 0; i < 3; i++)
113 { 114 {
114 mpeg2_free(mpeg2dec->yuv_buf[i][0]); 115 mpeg2_free(mpeg2dec->yuv_buf[i][0]);
@@ -121,6 +122,7 @@ void mpeg2_header_state_init (mpeg2dec_t * mpeg2dec)
121 { 122 {
122 mpeg2_free(mpeg2dec->decoder.convert_id); 123 mpeg2_free(mpeg2dec->decoder.convert_id);
123 } 124 }
125#endif
124 } 126 }
125 127
126 mpeg2dec->decoder.coding_type = I_TYPE; 128 mpeg2dec->decoder.coding_type = I_TYPE;
diff --git a/apps/plugins/mpegplayer/mpeg2.h b/apps/plugins/mpegplayer/mpeg2.h
index 605a3538b0..824454feab 100644
--- a/apps/plugins/mpegplayer/mpeg2.h
+++ b/apps/plugins/mpegplayer/mpeg2.h
@@ -189,7 +189,13 @@ typedef enum
189} mpeg2_alloc_t; 189} mpeg2_alloc_t;
190 190
191void * mpeg2_malloc (unsigned size, mpeg2_alloc_t reason); 191void * mpeg2_malloc (unsigned size, mpeg2_alloc_t reason);
192#if 0
192void mpeg2_free (void * buf); 193void mpeg2_free (void * buf);
194#endif
195/* allocates a dedicated buffer and locks all previous allocation in place */
196void * mpeg2_bufalloc(unsigned size, mpeg2_alloc_t reason);
197/* clears all non-dedicated buffer space */
198void mpeg2_mem_reset(void);
193void mpeg2_alloc_init(unsigned char* buf, int mallocsize); 199void mpeg2_alloc_init(unsigned char* buf, int mallocsize);
194 200
195#endif /* MPEG2_H */ 201#endif /* MPEG2_H */
diff --git a/apps/plugins/mpegplayer/mpeg_alloc.h b/apps/plugins/mpegplayer/mpeg_alloc.h
new file mode 100644
index 0000000000..9a08fd5308
--- /dev/null
+++ b/apps/plugins/mpegplayer/mpeg_alloc.h
@@ -0,0 +1,12 @@
1#ifndef MPEG_ALLOC_H
2#define MPEG_ALLOC_H
3
4/* returns the remaining mpeg2 buffer and it's size */
5void * mpeg2_get_buf(size_t *size);
6void *mpeg_malloc(size_t size, mpeg2_alloc_t reason);
7/* Grabs all the buffer available sans margin */
8void *mpeg_malloc_all(size_t *size_out, mpeg2_alloc_t reason);
9/* Initializes the malloc buffer with the given base buffer */
10bool mpeg_alloc_init(unsigned char *buf, size_t mallocsize);
11
12#endif /* MPEG_ALLOC_H */
diff --git a/apps/plugins/mpegplayer/mpeg_linkedlist.c b/apps/plugins/mpegplayer/mpeg_linkedlist.c
new file mode 100644
index 0000000000..74cb2cb34a
--- /dev/null
+++ b/apps/plugins/mpegplayer/mpeg_linkedlist.c
@@ -0,0 +1,149 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Linked list API definitions
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "mpegplayer.h"
23#include "mpeg_linkedlist.h"
24
25/* Initialize a master list head */
26void list_initialize(struct list_item *master_list_head)
27{
28 master_list_head->prev = master_list_head->next = NULL;
29}
30
31/* Are there items after the head item? */
32bool list_is_empty(struct list_item *head_item)
33{
34 return head_item->next == NULL;
35}
36
37/* Does the item belong to a list? */
38bool list_is_item_listed(struct list_item *item)
39{
40 return item->prev != NULL;
41}
42
43/* Is the item a member in a particular list? */
44bool list_is_member(struct list_item *master_list_head,
45 struct list_item *item)
46{
47 if (item != master_list_head && item->prev != NULL)
48 {
49 struct list_item *curr = master_list_head->next;
50
51 while (curr != NULL)
52 {
53 if (item != curr)
54 {
55 curr = curr->next;
56 continue;
57 }
58
59 return true;
60 }
61 }
62
63 return false;
64}
65
66/* Remove an item from a list - no head item needed */
67void list_remove_item(struct list_item *item)
68{
69 if (item->prev == NULL)
70 {
71 /* Not in a list - no change - could be the master list head
72 * as well which cannot be removed */
73 return;
74 }
75
76 item->prev->next = item->next;
77
78 if (item->next != NULL)
79 {
80 /* Not last item */
81 item->next->prev = item->prev;
82 }
83
84 /* Mark as not in a list */
85 item->prev = NULL;
86}
87
88/* Add a list item after the base item */
89void list_add_item(struct list_item *head_item,
90 struct list_item *item)
91{
92 if (item->prev != NULL)
93 {
94 /* Already in a list - no change */
95 DEBUGF("list_add_item: item already in a list\n");
96 return;
97 }
98
99 if (item == head_item)
100 {
101 /* Cannot add the item to itself */
102 DEBUGF("list_add_item: item == head_item\n");
103 return;
104 }
105
106 /* Insert first */
107 item->prev = head_item;
108 item->next = head_item->next;
109
110 if (head_item->next != NULL)
111 {
112 /* Not first item */
113 head_item->next->prev = item;
114 }
115
116 head_item->next = item;
117}
118
119/* Clear list items after the head item */
120void list_clear_all(struct list_item *head_item)
121{
122 struct list_item *curr = head_item->next;
123
124 while (curr != NULL)
125 {
126 list_remove_item(curr);
127 curr = head_item->next;
128 }
129}
130
131/* Enumerate all items after the head item - passing each item in turn
132 * to the callback as well as the data value. The current item may be
133 * safely removed. Items added after the current position will be enumated
134 * but not ones added before it. The callback may return false to stop
135 * the enumeration. */
136void list_enum_items(struct list_item *head_item,
137 list_enum_callback_t callback,
138 intptr_t data)
139{
140 struct list_item *next = head_item->next;
141
142 while (next != NULL)
143 {
144 struct list_item *curr = next;
145 next = curr->next;
146 if (!callback(curr, data))
147 break;
148 }
149}
diff --git a/apps/plugins/mpegplayer/mpeg_linkedlist.h b/apps/plugins/mpegplayer/mpeg_linkedlist.h
new file mode 100644
index 0000000000..17123cc9ca
--- /dev/null
+++ b/apps/plugins/mpegplayer/mpeg_linkedlist.h
@@ -0,0 +1,69 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Linked list API declarations
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef MPEG_LINKEDLIST_H
22#define MPEG_LINKEDLIST_H
23
24struct list_item
25{
26 struct list_item *prev; /* previous item in list */
27 struct list_item *next; /* next item in list */
28};
29
30/* Utility macros to help get the actual structure pointer back */
31#define OFFSETOF(type, membername) ((off_t)&((type *)0)->membername)
32#define TYPE_FROM_MEMBER(type, memberptr, membername) \
33 ((type *)((intptr_t)(memberptr) - OFFSETOF(type, membername)))
34
35/* Initialize a master list head */
36void list_initialize(struct list_item *master_list_head);
37
38/* Are there items after the head item? */
39bool list_is_empty(struct list_item *head_item);
40
41/* Does the item belong to a list? */
42bool list_is_item_listed(struct list_item *item);
43
44/* Is the item a member in a particular list? */
45bool list_is_member(struct list_item *master_list_head,
46 struct list_item *item);
47
48/* Remove an item from a list - no head item needed */
49void list_remove_item(struct list_item *item);
50
51/* Add a list item after the base item */
52void list_add_item(struct list_item *head_item,
53 struct list_item *item);
54
55/* Clear list items after the head item */
56void list_clear_all(struct list_item *head_item);
57
58/* Enumerate all items after the head item - passing each item in turn
59 * to the callback as well as the data value. The current item may be
60 * safely removed. Items added after the current position will be enumated
61 * but not ones added before it. The callback may return false to stop
62 * the enumeration. */
63typedef bool (*list_enum_callback_t)(struct list_item *item, intptr_t data);
64
65void list_enum_items(struct list_item *head_item,
66 list_enum_callback_t callback,
67 intptr_t data);
68
69#endif /* MPEG_LINKEDLIST_H */
diff --git a/apps/plugins/mpegplayer/mpeg_misc.c b/apps/plugins/mpegplayer/mpeg_misc.c
new file mode 100644
index 0000000000..f5ecb6d6c8
--- /dev/null
+++ b/apps/plugins/mpegplayer/mpeg_misc.c
@@ -0,0 +1,96 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Miscellaneous helper API definitions
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "mpegplayer.h"
23
24/** Streams **/
25
26/* Ensures direction is -1 or 1 and margin is properly initialized */
27void stream_scan_normalize(struct stream_scan *sk)
28{
29 if (sk->dir >= 0)
30 {
31 sk->dir = SSCAN_FORWARD;
32 sk->margin = sk->len;
33 }
34 else if (sk->dir < 0)
35 {
36 sk->dir = SSCAN_REVERSE;
37 sk->margin = 0;
38 }
39}
40
41/* Moves a scan cursor. If amount is positive, the increment is in the scan
42 * direction, otherwise opposite the scan direction */
43void stream_scan_offset(struct stream_scan *sk, off_t by)
44{
45 off_t bydir = by*sk->dir;
46 sk->pos += bydir;
47 sk->margin -= bydir;
48 sk->len -= by;
49}
50
51/** Time helpers **/
52void ts_to_hms(uint32_t pts, struct hms *hms)
53{
54 hms->frac = pts % TS_SECOND;
55 hms->sec = pts / TS_SECOND;
56 hms->min = hms->sec / 60;
57 hms->hrs = hms->min / 60;
58 hms->sec %= 60;
59 hms->min %= 60;
60}
61
62void hms_format(char *buf, size_t bufsize, struct hms *hms)
63{
64 /* Only display hours if nonzero */
65 if (hms->hrs != 0)
66 {
67 rb->snprintf(buf, bufsize, "%u:%02u:%02u",
68 hms->hrs, hms->min, hms->sec);
69 }
70 else
71 {
72 rb->snprintf(buf, bufsize, "%u:%02u",
73 hms->min, hms->sec);
74 }
75}
76
77/** Maths **/
78uint32_t muldiv_uint32(uint32_t multiplicand,
79 uint32_t multiplier,
80 uint32_t divisor)
81{
82 if (divisor != 0)
83 {
84 uint64_t prod = (uint64_t)multiplier*multiplicand + divisor/2;
85
86 if ((uint32_t)(prod >> 32) < divisor)
87 return (uint32_t)(prod / divisor);
88 }
89 else if (multiplicand == 0 || multiplier == 0)
90 {
91 return 0; /* 0/0 = 0 : yaya */
92 }
93 /* else (> 0) / 0 = UINT32_MAX */
94
95 return UINT32_MAX; /* Saturate */
96}
diff --git a/apps/plugins/mpegplayer/mpeg_misc.h b/apps/plugins/mpegplayer/mpeg_misc.h
new file mode 100644
index 0000000000..2da9c2e313
--- /dev/null
+++ b/apps/plugins/mpegplayer/mpeg_misc.h
@@ -0,0 +1,206 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Miscellaneous helper API declarations
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef MPEG_MISC_H
22#define MPEG_MISC_H
23
24/* Miscellaneous helpers */
25#ifndef ALIGNED_ATTR
26#define ALIGNED_ATTR(x) __attribute__((aligned(x)))
27#endif
28
29/* Generic states for when things are too simple to care about naming them */
30enum state_enum
31{
32 state0 = 0,
33 state1,
34 state2,
35 state3,
36 state4,
37 state5,
38 state6,
39 state7,
40 state8,
41 state9,
42};
43
44/* Macros for comparing memory bytes to a series of constant bytes in an
45 efficient manner - evaluate to true if corresponding bytes match */
46#if defined (CPU_ARM)
47/* ARM must load 32-bit values at addres % 4 == 0 offsets but this data
48 isn't aligned nescessarily, so just byte compare */
49#define CMP_3_CONST(_a, _b) \
50 ({ int _x; \
51 asm volatile ( \
52 "ldrb %[x], [%[a], #0] \n" \
53 "eors %[x], %[x], %[b0] \n" \
54 "ldreqb %[x], [%[a], #1] \n" \
55 "eoreqs %[x], %[x], %[b1] \n" \
56 "ldreqb %[x], [%[a], #2] \n" \
57 "eoreqs %[x], %[x], %[b2] \n" \
58 : [x]"=&r"(_x) \
59 : [a]"r"(_a), \
60 [b0]"i"(((_b) >> 24) & 0xff), \
61 [b1]"i"(((_b) >> 16) & 0xff), \
62 [b2]"i"(((_b) >> 8) & 0xff) \
63 ); \
64 _x == 0; })
65
66#define CMP_4_CONST(_a, _b) \
67 ({ int _x; \
68 asm volatile ( \
69 "ldrb %[x], [%[a], #0] \n" \
70 "eors %[x], %[x], %[b0] \n" \
71 "ldreqb %[x], [%[a], #1] \n" \
72 "eoreqs %[x], %[x], %[b1] \n" \
73 "ldreqb %[x], [%[a], #2] \n" \
74 "eoreqs %[x], %[x], %[b2] \n" \
75 "ldreqb %[x], [%[a], #3] \n" \
76 "eoreqs %[x], %[x], %[b3] \n" \
77 : [x]"=&r"(_x) \
78 : [a]"r"(_a), \
79 [b0]"i"(((_b) >> 24) & 0xff), \
80 [b1]"i"(((_b) >> 16) & 0xff), \
81 [b2]"i"(((_b) >> 8) & 0xff), \
82 [b3]"i"(((_b) ) & 0xff) \
83 ); \
84 _x == 0; })
85
86#elif defined (CPU_COLDFIRE)
87/* Coldfire can just load a 32 bit value at any offset but ASM is not the
88 best way to integrate this with the C code */
89#define CMP_3_CONST(a, b) \
90 (((*(uint32_t *)(a) >> 8) == ((uint32_t)(b) >> 8)))
91
92#define CMP_4_CONST(a, b) \
93 ((*(uint32_t *)(a) == (b)))
94
95#else
96/* Don't know what this is - use bytewise comparisons */
97#define CMP_3_CONST(a, b) \
98 (( ((a)[0] ^ (((b) >> 24) & 0xff)) | \
99 ((a)[1] ^ (((b) >> 16) & 0xff)) | \
100 ((a)[2] ^ (((b) >> 8) & 0xff)) ) == 0)
101
102#define CMP_4_CONST(a, b) \
103 (( ((a)[0] ^ (((b) >> 24) & 0xff)) | \
104 ((a)[1] ^ (((b) >> 16) & 0xff)) | \
105 ((a)[2] ^ (((b) >> 8) & 0xff)) | \
106 ((a)[3] ^ (((b) ) & 0xff)) ) == 0)
107#endif /* CPU_* */
108
109
110/** Streams **/
111
112/* Convert PTS/DTS ticks to our clock ticks */
113#define TS_TO_TICKS(pts) ((uint64_t)CLOCK_RATE*(pts) / TS_SECOND)
114/* Convert our clock ticks to PTS/DTS ticks */
115#define TICKS_TO_TS(ts) ((uint64_t)TS_SECOND*(ts) / CLOCK_RATE)
116/* Convert timecode ticks to our clock ticks */
117#define TC_TO_TICKS(stamp) ((uint64_t)CLOCK_RATE*(stamp) / TC_SECOND)
118/* Convert our clock ticks to timecode ticks */
119#define TICKS_TO_TC(stamp) ((uint64_t)TC_SECOND*(stamp) / CLOCK_RATE)
120/* Convert timecode ticks to timestamp ticks */
121#define TC_TO_TS(stamp) ((stamp) / 600)
122
123/*
124 * S = start position, E = end position
125 *
126 * pos:
127 * initialize to search start position (S)
128 *
129 * len:
130 * initialize to = ABS(S-E)
131 * scanning = remaining bytes in scan direction
132 *
133 * dir:
134 * scan direction; >= 0 == forward, < 0 == reverse
135 *
136 * margin:
137 * amount of data to right of cursor - initialize by stream_scan_normalize
138 *
139 * data:
140 * Extra data used/returned by the function implemented
141 *
142 * Forward scan:
143 * S pos E
144 * | *<-margin->| dir->
145 * | |<--len--->|
146 *
147 * Reverse scan:
148 * E pos S
149 * |<-len->*<-margin->| <-dir
150 * | | |
151 */
152struct stream_scan
153{
154 off_t pos; /* Initial scan position (file offset) */
155 ssize_t len; /* Maximum length of scan */
156 off_t dir; /* Direction - >= 0; forward, < 0 backward */
157 ssize_t margin; /* Used by function to track margin between position and data end */
158 intptr_t data; /* */
159};
160
161#define SSCAN_REVERSE (-1)
162#define SSCAN_FORWARD 1
163
164/* Ensures direction is -1 or 1 and margin is properly initialized */
165void stream_scan_normalize(struct stream_scan *sk);
166
167/* Moves a scan cursor. If amount is positive, the increment is in the scan
168 * direction, otherwise opposite the scan direction */
169void stream_scan_offset(struct stream_scan *sk, off_t by);
170
171/** Audio helpers **/
172static inline int32_t clip_sample(int32_t sample)
173{
174 if ((int16_t)sample != sample)
175 sample = 0x7fff ^ (sample >> 31);
176
177 return sample;
178}
179
180/** Time helpers **/
181struct hms
182{
183 unsigned int hrs;
184 unsigned int min;
185 unsigned int sec;
186 unsigned int frac;
187};
188
189void ts_to_hms(uint32_t ts, struct hms *hms);
190void hms_format(char *buf, size_t bufsize, struct hms *hms);
191
192/** Maths **/
193
194/* Moving average */
195#define AVERAGE(var, x, count) \
196 ({ typeof (count) _c = (count); \
197 ((var) * (_c-1) + (x)) / (_c); })
198
199/* Multiply two unsigned 32-bit integers yielding a 64-bit result and
200 * divide by another unsigned 32-bit integer to yield a 32-bit result.
201 * Rounds to nearest with saturation. */
202uint32_t muldiv_uint32(uint32_t multiplicand,
203 uint32_t multiplier,
204 uint32_t divisor);
205
206#endif /* MPEG_MISC_H */
diff --git a/apps/plugins/mpegplayer/mpeg_parser.c b/apps/plugins/mpegplayer/mpeg_parser.c
new file mode 100644
index 0000000000..c996f9540d
--- /dev/null
+++ b/apps/plugins/mpegplayer/mpeg_parser.c
@@ -0,0 +1,1182 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Parser for MPEG streams
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "mpegplayer.h"
23
24struct stream_parser str_parser NOCACHEBSS_ATTR;
25
26static void parser_init_state(void)
27{
28 str_parser.last_seek_time = 0;
29 str_parser.format = STREAM_FMT_UNKNOWN;
30 str_parser.start_pts = INVALID_TIMESTAMP;
31 str_parser.end_pts = INVALID_TIMESTAMP;
32 str_parser.flags = 0;
33 str_parser.dims.w = 0;
34 str_parser.dims.h = 0;
35}
36
37/* Place the stream in a state to begin parsing - sync will be performed
38 * first */
39void str_initialize(struct stream *str, off_t pos)
40{
41 /* Initial positions start here */
42 str->hdr.win_left = str->hdr.win_right = pos;
43 /* No packet */
44 str->curr_packet = NULL;
45 /* Pick up parsing from this point in the buffer */
46 str->curr_packet_end = disk_buf_offset2ptr(pos);
47 /* No flags */
48 str->pkt_flags = 0;
49 /* Sync first */
50 str->state = SSTATE_SYNC;
51}
52
53/* Place the stream in an end of data state */
54void str_end_of_stream(struct stream *str)
55{
56 /* Offsets that prevent this stream from being included in the
57 * min left/max right window so that no buffering is triggered on
58 * its behalf. Set right to the min first so a thread reading the
59 * overall window gets doesn't see this as valid no matter what the
60 * file length. */
61 str->hdr.win_right = LONG_MIN;
62 str->hdr.win_left = LONG_MAX;
63 /* No packets */
64 str->curr_packet = str->curr_packet_end = NULL;
65 /* No flags */
66 str->pkt_flags = 0;
67 /* Fin */
68 str->state = SSTATE_END;
69}
70
71/* Return a timestamp at address p+offset if the marker bits are in tact */
72static inline uint32_t read_pts(uint8_t *p, off_t offset)
73{
74 return TS_CHECK_MARKERS(p, offset) ?
75 TS_FROM_HEADER(p, offset) : INVALID_TIMESTAMP;
76}
77
78static inline bool validate_timestamp(uint32_t ts)
79{
80 return ts >= str_parser.start_pts && ts <= str_parser.end_pts;
81}
82
83/* Find a start code before or after a given position */
84uint8_t * mpeg_parser_scan_start_code(struct stream_scan *sk, uint32_t code)
85{
86 stream_scan_normalize(sk);
87
88 if (sk->dir < 0)
89 {
90 /* Reverse scan - start with at least the min needed */
91 stream_scan_offset(sk, 4);
92 }
93
94 code &= 0xff; /* Only the low byte matters */
95
96 while (sk->len >= 0 && sk->margin >= 4)
97 {
98 uint8_t *p;
99 off_t pos = disk_buf_lseek(sk->pos, SEEK_SET);
100 ssize_t len = disk_buf_getbuffer(4, &p, NULL, NULL);
101
102 if (pos < 0 || len < 4)
103 break;
104
105 if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX) && p[3] == code)
106 {
107 return p;
108 }
109
110 stream_scan_offset(sk, 1);
111 }
112
113 return NULL;
114}
115
116/* Find a PES packet header for any stream - return stream to which it
117 * belongs */
118unsigned mpeg_parser_scan_pes(struct stream_scan *sk)
119{
120 stream_scan_normalize(sk);
121
122 if (sk->dir < 0)
123 {
124 /* Reverse scan - start with at least the min needed */
125 stream_scan_offset(sk, 4);
126 }
127
128 while (sk->len >= 0 && sk->margin >= 4)
129 {
130 uint8_t *p;
131 off_t pos = disk_buf_lseek(sk->pos, SEEK_SET);
132 ssize_t len = disk_buf_getbuffer(4, &p, NULL, NULL);
133
134 if (pos < 0 || len < 4)
135 break;
136
137 if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX))
138 {
139 unsigned id = p[3];
140 if (id >= 0xb9)
141 return id; /* PES header */
142 /* else some video stream element */
143 }
144
145 stream_scan_offset(sk, 1);
146 }
147
148 return -1;
149}
150
151/* Return the first SCR found from the scan direction */
152uint32_t mpeg_parser_scan_scr(struct stream_scan *sk)
153{
154 uint8_t *p = mpeg_parser_scan_start_code(sk, MPEG_STREAM_PACK_HEADER);
155
156 if (p != NULL && sk->margin >= 9) /* 9 bytes total required */
157 {
158 sk->data = 9;
159
160 if ((p[4] & 0xc0) == 0x40) /* mpeg-2 */
161 {
162 /* Lookhead p+8 */
163 if (MPEG2_CHECK_PACK_SCR_MARKERS(p, 4))
164 return MPEG2_PACK_HEADER_SCR(p, 4);
165 }
166 else if ((p[4] & 0xf0) == 0x20) /* mpeg-1 */
167 {
168 /* Lookahead p+8 */
169 if (TS_CHECK_MARKERS(p, 4))
170 return TS_FROM_HEADER(p, 4);
171 }
172 /* Weird pack header */
173 sk->data = 5;
174 }
175
176 return INVALID_TIMESTAMP;
177}
178
179uint32_t mpeg_parser_scan_pts(struct stream_scan *sk, unsigned id)
180{
181 stream_scan_normalize(sk);
182
183 if (sk->dir < 0)
184 {
185 /* Reverse scan - start with at least the min needed */
186 stream_scan_offset(sk, 4);
187 }
188
189 while (sk->len >= 0 && sk->margin >= 4)
190 {
191 uint8_t *p;
192 off_t pos = disk_buf_lseek(sk->pos, SEEK_SET);
193 ssize_t len = disk_buf_getbuffer(35, &p, NULL, NULL);
194
195 if (pos < 0 || len < 4)
196 break;
197
198 if (CMP_3_CONST(p, PACKET_START_CODE_PREFIX) && p[3] == id)
199 {
200 uint8_t *h = p;
201
202 if (sk->margin < 6)
203 {
204 /* Insufficient data */
205 }
206 else if ((h[6] & 0xc0) == 0x80) /* mpeg2 */
207 {
208 if (sk->margin >= 14 && (h[7] & 0x80) != 0x00)
209 {
210 sk->data = 14;
211 return read_pts(h, 9);
212 }
213 }
214 else /* mpeg1 */
215 {
216 ssize_t l = 7;
217 ssize_t margin = sk->margin;
218
219 /* Skip stuffing_byte */
220 while (h[l - 1] == 0xff && ++l <= 23)
221 --margin;
222
223 if ((h[l - 1] & 0xc0) == 0x40)
224 {
225 /* Skip STD_buffer_scale and STD_buffer_size */
226 margin -= 2;
227 l += 2;
228 }
229
230 if (margin >= 4)
231 {
232 /* header points to the mpeg1 pes header */
233 h += l;
234
235 if ((h[-1] & 0xe0) == 0x20)
236 {
237 sk->data = (h + 4) - p;
238 return read_pts(h, -1);
239 }
240 }
241 }
242 /* No PTS present - keep searching for a matching PES header with
243 * one */
244 }
245
246 stream_scan_offset(sk, 1);
247 }
248
249 return INVALID_TIMESTAMP;
250}
251
252static bool init_video_info(void)
253{
254 DEBUGF("Getting movie size\n");
255
256 /* The decoder handles this in order to initialize its knowledge of the
257 * movie parameters making seeking easier */
258 str_send_msg(&video_str, STREAM_RESET, 0);
259 if (str_send_msg(&video_str, VIDEO_GET_SIZE,
260 (intptr_t)&str_parser.dims) != 0)
261 {
262 return true;
263 }
264
265 DEBUGF(" failed\n");
266 return false;
267}
268
269static void init_times(struct stream *str)
270{
271 int i;
272 struct stream tmp_str;
273 const ssize_t filesize = disk_buf_filesize();
274 const ssize_t max_probe = MIN(512*1024, filesize);
275
276 /* Simply find the first earliest timestamp - this will be the one
277 * used when streaming anyway */
278 DEBUGF("Finding start_pts: 0x%02x\n", str->id);
279
280 tmp_str.id = str->id;
281 tmp_str.hdr.pos = 0;
282 tmp_str.hdr.limit = max_probe;
283
284 str->start_pts = INVALID_TIMESTAMP;
285
286 /* Probe many for video because of B-frames */
287 for (i = STREAM_IS_VIDEO(str->id) ? 5 : 1; i > 0;)
288 {
289 switch (parser_get_next_data(&tmp_str, STREAM_PM_RANDOM_ACCESS))
290 {
291 case STREAM_DATA_END:
292 break;
293 case STREAM_OK:
294 if (tmp_str.pkt_flags & PKT_HAS_TS)
295 {
296 if (tmp_str.pts < str->start_pts)
297 str->start_pts = tmp_str.pts;
298 i--; /* Decrement timestamp counter */
299 }
300 continue;
301 }
302
303 break;
304 }
305
306 DEBUGF(" start:%u\n", (unsigned)str->start_pts);
307
308 /* Use the decoder thread to perform a synchronized search - no
309 * decoding should take place but just a simple run through timestamps
310 * and durations as the decoder would see them. This should give the
311 * precise time at the end of the last frame for the stream. */
312 DEBUGF("Finding end_pts: 0x%02x\n", str->id);
313
314 str->end_pts = INVALID_TIMESTAMP;
315
316 if (str->start_pts != INVALID_TIMESTAMP)
317 {
318 str_parser.parms.sd.time = MAX_TIMESTAMP;
319 str_parser.parms.sd.sk.pos = filesize - max_probe;
320 str_parser.parms.sd.sk.len = max_probe;
321 str_parser.parms.sd.sk.dir = SSCAN_FORWARD;
322
323 str_send_msg(str, STREAM_RESET, 0);
324
325 if (str_send_msg(str, STREAM_FIND_END_TIME,
326 (intptr_t)&str_parser.parms.sd) == STREAM_PERFECT_MATCH)
327 {
328 str->end_pts = str_parser.parms.sd.time;
329 DEBUGF(" end:%u\n", (unsigned)str->end_pts);
330 }
331 }
332
333 /* End must be greater than start */
334 if (str->start_pts >= str->end_pts)
335 {
336 str->start_pts = INVALID_TIMESTAMP;
337 str->end_pts = INVALID_TIMESTAMP;
338 }
339}
340
341/* Return the best-fit file offset of a timestamp in the PES where
342 * timstamp <= time < next timestamp. Will try to return something reasonably
343 * valid if best-fit could not be made. */
344static off_t mpeg_parser_seek_PTS(uint32_t time, unsigned id)
345{
346 ssize_t pos_left = 0;
347 ssize_t pos_right = disk_buf.filesize;
348 ssize_t pos, pos_new;
349 uint32_t time_left = str_parser.start_pts;
350 uint32_t time_right = str_parser.end_pts;
351 uint32_t pts = 0;
352 uint32_t prevpts = 0;
353 enum state_enum state = state0;
354 struct stream_scan sk;
355
356 /* Initial estimate taken from average bitrate - later interpolations are
357 * taken similarly based on the remaining file interval */
358 pos_new = muldiv_uint32(time - time_left, pos_right - pos_left,
359 time_right - time_left) + pos_left;
360
361 /* return this estimated position if nothing better comes up */
362 pos = pos_new;
363
364 DEBUGF("Seeking stream 0x%02x\n", id);
365 DEBUGF("$$ tl:%u t:%u ct:?? tr:%u\n pl:%ld pn:%ld pr:%ld\n",
366 (unsigned)time_left, (unsigned)time, (unsigned)time_right,
367 pos_left, pos_new, pos_right);
368
369 sk.dir = SSCAN_REVERSE;
370
371 while (state < state9)
372 {
373 uint32_t currpts;
374 sk.pos = pos_new;
375 sk.len = (sk.dir < 0) ? pos_new - pos_left : pos_right - pos_new;
376
377 currpts = mpeg_parser_scan_pts(&sk, id);
378
379 if (currpts != INVALID_TIMESTAMP)
380 {
381 /* Found a valid timestamp - see were it lies in relation to
382 * target */
383 if (currpts < time)
384 {
385 /* Time at current position is before seek time - move
386 * forward */
387 if (currpts > pts)
388 {
389 /* This is less than the desired time but greater than
390 * the currently seeked one; move the position up */
391 pts = currpts;
392 pos = sk.pos;
393 }
394
395 /* No next timestamp can be sooner */
396 pos_left = sk.pos + sk.data;
397 time_left = currpts;
398
399 if (pos_right <= pos_left)
400 break; /* If the window disappeared - we're done */
401
402 pos_new = muldiv_uint32(time - time_left,
403 pos_right - pos_left,
404 time_right - time_left) + pos_left;
405 /* Point is ahead of us - fudge estimate a bit high */
406 pos_new = muldiv_uint32(11, pos_new - pos_left, 10)
407 + pos_left;
408
409 if (pos_new >= pos_right)
410 {
411 /* Estimate could push too far */
412 pos_new = pos_right;
413 }
414
415 state = state2; /* Last scan was early */
416 sk.dir = SSCAN_REVERSE;
417
418 DEBUGF(">> tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n",
419 (unsigned)time_left, (unsigned)time, (unsigned)currpts,
420 (unsigned)time_right, pos_left, pos_new, pos_right);
421 }
422 else if (currpts > time)
423 {
424 /* Time at current position is past seek time - move
425 backward */
426 pos_right = sk.pos;
427 time_right = currpts;
428
429 if (pos_right <= pos_left)
430 break; /* If the window disappeared - we're done */
431
432 pos_new = muldiv_uint32(time - time_left,
433 pos_right - pos_left,
434 time_right - time_left) + pos_left;
435 /* Overshot the seek point - fudge estimate a bit low */
436 pos_new = muldiv_uint32(9, pos_new - pos_left, 10) + pos_left;
437
438 state = state3; /* Last scan was late */
439 sk.dir = SSCAN_REVERSE;
440
441 DEBUGF("<< tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n",
442 (unsigned)time_left, (unsigned)time, (unsigned)currpts,
443 (unsigned)time_right, pos_left, pos_new, pos_right);
444 }
445 else
446 {
447 /* Exact match - it happens */
448 DEBUGF("|| tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n",
449 (unsigned)time_left, (unsigned)time, (unsigned)currpts,
450 (unsigned)time_right, pos_left, pos_new, pos_right);
451 pts = currpts;
452 pos = sk.pos;
453 state = state9;
454 }
455 }
456 else
457 {
458 /* Nothing found */
459
460 switch (state)
461 {
462 case state1:
463 /* We already tried the bruteforce scan and failed again - no
464 * more stamps could possibly exist in the interval */
465 DEBUGF("!! no timestamp 2x\n");
466 break;
467 case state0:
468 /* Hardly likely except at very beginning - just do L->R scan
469 * to find something */
470 DEBUGF("!! no timestamp on first probe: %ld\n", sk.pos);
471 case state2:
472 case state3:
473 /* Could just be missing timestamps because the interval is
474 * narrowing down. A large block of data from another stream
475 * may also be in the midst of our chosen points which could
476 * cluster at either extreme end. If anything is there, this
477 * will find it. */
478 pos_new = pos_left;
479 sk.dir = SSCAN_FORWARD;
480 DEBUGF("?? tl:%u t:%u ct:%u tr:%u\n pl:%ld pn:%ld pr:%ld\n",
481 (unsigned)time_left, (unsigned)time, (unsigned)currpts,
482 (unsigned)time_right, pos_left, pos_new, pos_right);
483 state = state1;
484 break;
485 default:
486 DEBUGF("?? Invalid state: %d\n", state);
487 }
488 }
489
490 /* Same timestamp twice = quit */
491 if (currpts == prevpts)
492 {
493 DEBUGF("!! currpts == prevpts (stop)\n");
494 state = state9;
495 }
496
497 prevpts = currpts;
498 }
499
500#if defined(DEBUG) || defined(SIMULATOR)
501 /* The next pts after the seeked-to position should be greater -
502 * most of the time - frames out of presentation order may muck it
503 * up a slight bit */
504 sk.pos = pos + 1;
505 sk.len = disk_buf.filesize;
506 sk.dir = SSCAN_FORWARD;
507
508 uint32_t nextpts = mpeg_parser_scan_pts(&sk, id);
509 DEBUGF("Seek pos:%ld pts:%u t:%u next pts:%u \n",
510 pos, (unsigned)pts, (unsigned)time, (unsigned)nextpts);
511
512 if (pts <= time && time < nextpts)
513 {
514 /* Smile - it worked */
515 DEBUGF(" :) pts<=time<next pts\n");
516 }
517 else
518 {
519 /* See where things ended up */
520 if (pts > time)
521 {
522 /* Hmm */
523 DEBUGF(" :\\ pts>time\n");
524 }
525 if (pts >= nextpts)
526 {
527 /* Weird - probably because of encoded order & tends to be right
528 * anyway if other criteria are met */
529 DEBUGF(" :p pts>=next pts\n");
530 }
531 if (time >= nextpts)
532 {
533 /* Ugh */
534 DEBUGF(" :( time>=nextpts\n");
535 }
536 }
537#endif
538
539 return pos;
540}
541
542static bool prepare_image(uint32_t time)
543{
544 struct stream_scan sk;
545 int tries;
546 int result;
547
548 if (!str_send_msg(&video_str, STREAM_NEEDS_SYNC, time))
549 {
550 DEBUGF("Image was ready\n");
551 return true; /* Should already have the image */
552 }
553
554#ifdef HAVE_ADJUSTABLE_CPU_FREQ
555 rb->cpu_boost(true); /* No interference with trigger_cpu_boost */
556#endif
557
558 str_send_msg(&video_str, STREAM_RESET, 0);
559
560 sk.pos = parser_can_seek() ?
561 mpeg_parser_seek_PTS(time, video_str.id) : 0;
562 sk.len = sk.pos;
563 sk.dir = SSCAN_REVERSE;
564
565 tries = 1;
566try_again:
567
568 if (mpeg_parser_scan_start_code(&sk, MPEG_START_GOP))
569 {
570 DEBUGF("GOP found at: %ld\n", sk.pos);
571
572 unsigned id = mpeg_parser_scan_pes(&sk);
573
574 if (id != video_str.id && sk.pos > 0)
575 {
576 /* Not part of our stream */
577 DEBUGF(" wrong stream: 0x%02x\n", id);
578 goto try_again;
579 }
580
581 /* This will hit the PES header since it's known to be there */
582 uint32_t pts = mpeg_parser_scan_pts(&sk, id);
583
584 if (pts == INVALID_TIMESTAMP || pts > time)
585 {
586 DEBUGF(" wrong timestamp: %u\n", (unsigned)pts);
587 goto try_again;
588 }
589 }
590
591 str_parser.parms.sd.time = time;
592 str_parser.parms.sd.sk.pos = MAX(sk.pos, 0);
593 str_parser.parms.sd.sk.len = 1024*1024;
594 str_parser.parms.sd.sk.dir = SSCAN_FORWARD;
595
596 DEBUGF("thumb pos:%ld len:%ld\n", str_parser.parms.sd.sk.pos,
597 str_parser.parms.sd.sk.len);
598
599 result = str_send_msg(&video_str, STREAM_SYNC,
600 (intptr_t)&str_parser.parms.sd);
601
602 if (result != STREAM_PERFECT_MATCH)
603 {
604 /* Two tries should be all that is nescessary to find the exact frame
605 * if the first GOP actually started later than the timestamp - the
606 * GOP just prior must then start on or earlier. */
607 if (++tries <= 2)
608 goto try_again;
609 }
610
611#ifdef HAVE_ADJUSTABLE_CPU_FREQ
612 rb->cpu_boost(false);
613#endif
614
615 return result > STREAM_OK;
616}
617
618static void prepare_audio(uint32_t time)
619{
620 off_t pos;
621
622 if (!str_send_msg(&audio_str, STREAM_NEEDS_SYNC, time))
623 {
624 DEBUGF("Audio was ready\n");
625 return;
626 }
627
628 pos = mpeg_parser_seek_PTS(time, audio_str.id);
629 str_send_msg(&audio_str, STREAM_RESET, 0);
630
631 str_parser.parms.sd.time = time;
632 str_parser.parms.sd.sk.pos = pos;
633 str_parser.parms.sd.sk.len = 1024*1024;
634 str_parser.parms.sd.sk.dir = SSCAN_FORWARD;
635
636 str_send_msg(&audio_str, STREAM_SYNC, (intptr_t)&str_parser.parms.sd);
637}
638
639/* This function demuxes the streams and gives the next stream data
640 * pointer.
641 *
642 * STREAM_PM_STREAMING is for operation during playback. If the nescessary
643 * data and worst-case lookahead margin is not available, the stream is
644 * registered for notification when the data becomes available. If parsing
645 * extends beyond the end of the file or the end of stream marker is reached,
646 * STREAM_DATA_END is returned and the stream state changed to SSTATE_EOS.
647 *
648 * STREAM_PM_RANDOM_ACCESS is for operation when not playing such as seeking.
649 * If the file cache misses for the current position + lookahead, it will be
650 * loaded from disk. When the specified limit is reached, STREAM_DATA_END is
651 * returned.
652 *
653 * The results from one mode may be used as input to the other. Random access
654 * requires cooperation amongst threads to avoid evicting another stream's
655 * data.
656 */
657static int parse_demux(struct stream *str, enum stream_parse_mode type)
658{
659 #define INC_BUF(offset) \
660 ({ off_t _o = (offset); \
661 str->hdr.win_right += _o; \
662 if ((p += _o) >= disk_buf.end) \
663 p -= disk_buf.size; })
664
665 static const int mpeg1_skip_table[16] =
666 { 0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
667
668 uint8_t *p = str->curr_packet_end;
669
670 str->pkt_flags = 0;
671
672 while (1)
673 {
674 uint8_t *header;
675 unsigned id;
676 ssize_t length, bytes;
677
678 switch (type)
679 {
680 case STREAM_PM_STREAMING:
681 /* Has the end been reached already? */
682 if (str->state == SSTATE_END)
683 return STREAM_DATA_END;
684
685 /* Are we at the end of file? */
686 if (str->hdr.win_left >= disk_buf.filesize)
687 {
688 str_end_of_stream(str);
689 return STREAM_DATA_END;
690 }
691
692 if (!disk_buf_is_data_ready(&str->hdr, MIN_BUFAHEAD))
693 {
694 /* This data range is not buffered yet - register stream to
695 * be notified when it becomes available. Stream is obliged
696 * to enter a TSTATE_DATA state if it must wait. */
697 int res = str_next_data_not_ready(str);
698
699 if (res != STREAM_OK)
700 return res;
701 }
702 break;
703 /* STREAM_PM_STREAMING: */
704
705 case STREAM_PM_RANDOM_ACCESS:
706 str->hdr.pos = disk_buf_lseek(str->hdr.pos, SEEK_SET);
707
708 if (str->hdr.pos < 0 || str->hdr.pos >= str->hdr.limit ||
709 disk_buf_getbuffer(MIN_BUFAHEAD, &p, NULL, NULL) <= 0)
710 {
711 str_end_of_stream(str);
712 return STREAM_DATA_END;
713 }
714
715 str->state = SSTATE_SYNC;
716 str->hdr.win_left = str->hdr.pos;
717 str->curr_packet = NULL;
718 str->curr_packet_end = p;
719 break;
720 /* STREAM_PM_RANDOM_ACCESS: */
721 }
722
723 if (str->state == SSTATE_SYNC)
724 {
725 /* Scanning for start code */
726 if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX))
727 {
728 INC_BUF(1);
729 continue;
730 }
731 }
732
733 /* Found a start code - enter parse state */
734 str->state = SSTATE_PARSE;
735
736 /* Pack header, skip it */
737 if (CMP_4_CONST(p, PACK_START_CODE))
738 {
739 /* Max lookahead: 14 */
740 if ((p[4] & 0xc0) == 0x40) /* mpeg-2 */
741 {
742 /* Max delta: 14 + 7 = 21 */
743 /* Skip pack header and any stuffing bytes*/
744 bytes = 14 + (p[13] & 7);
745 }
746 else if ((p[4] & 0xf0) == 0x20) /* mpeg-1 */
747 {
748 bytes = 12;
749 }
750 else /* unknown - skip it */
751 {
752 DEBUGF("weird pack header!\n");
753 bytes = 5;
754 }
755
756 INC_BUF(bytes);
757 }
758
759 /* System header, parse and skip it - 6 bytes + size */
760 if (CMP_4_CONST(p, SYSTEM_HEADER_START_CODE))
761 {
762 /* Skip start code */
763 /* Max Delta = 65535 + 6 = 65541 */
764 bytes = 6 + ((p[4] << 8) | p[5]);
765 INC_BUF(bytes);
766 }
767
768 /* Packet header, parse it */
769 if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX))
770 {
771 /* Problem? Meh...probably not but just a corrupted section.
772 * Try to resync the parser which will probably succeed. */
773 DEBUGF("packet start code prefix not found: 0x%02x\n"
774 " wl:%lu wr:%lu\n"
775 " p:%p cp:%p cpe:%p\n"
776 " dbs:%p dbe:%p dbt:%p\n",
777 str->id, str->hdr.win_left, str->hdr.win_right,
778 p, str->curr_packet, str->curr_packet_end,
779 disk_buf.start, disk_buf.end, disk_buf.tail);
780 str->state = SSTATE_SYNC;
781 INC_BUF(1); /* Next byte - this one's no good */
782 continue;
783 }
784
785 /* We retrieve basic infos */
786 /* Maximum packet length: 6 + 65535 = 65541 */
787 id = p[3];
788 length = ((p[4] << 8) | p[5]) + 6;
789
790 if (id != str->id)
791 {
792 switch (id)
793 {
794 case MPEG_STREAM_PROGRAM_END:
795 /* end of stream */
796 str_end_of_stream(str);
797 DEBUGF("MPEG program end: 0x%02x\n", str->id);
798 return STREAM_DATA_END;
799 case MPEG_STREAM_PACK_HEADER:
800 case MPEG_STREAM_SYSTEM_HEADER:
801 /* These shouldn't be here - no increment or resync
802 * since we'll pick it up above. */
803 continue;
804 default:
805 /* It's not the packet we're looking for, skip it */
806 INC_BUF(length);
807 continue;
808 }
809 }
810
811 /* Ok, it's our packet */
812 header = p;
813
814 if ((header[6] & 0xc0) == 0x80) /* mpeg2 */
815 {
816 /* Max Lookahead: 18 */
817 /* Min length: 9 */
818 /* Max length: 9 + 255 = 264 */
819 length = 9 + header[8];
820
821 /* header points to the mpeg2 pes header */
822 if ((header[7] & 0x80) != 0)
823 {
824 /* header has a pts */
825 uint32_t pts = read_pts(header, 9);
826
827 if (pts != INVALID_TIMESTAMP)
828 {
829 str->pts = pts;
830#if 0
831 /* DTS isn't used for anything since things just get
832 decoded ASAP but keep the code around */
833 if (STREAM_IS_VIDEO(id))
834 {
835 /* Video stream - header may have a dts as well */
836 str->dts = pts;
837
838 if (header[7] & 0x40) != 0x00)
839 {
840 pts = read_pts(header, 14);
841 if (pts != INVALID_TIMESTAMP)
842 str->dts = pts;
843 }
844 }
845#endif
846 str->pkt_flags |= PKT_HAS_TS;
847 }
848 }
849 }
850 else /* mpeg1 */
851 {
852 /* Max lookahead: 24 + 2 + 9 = 35 */
853 /* Max len_skip: 24 + 2 = 26 */
854 /* Min length: 7 */
855 /* Max length: 24 + 2 + 9 = 35 */
856 off_t len_skip;
857 uint8_t * ptsbuf;
858
859 length = 7;
860
861 while (header[length - 1] == 0xff)
862 {
863 if (++length > 23)
864 {
865 DEBUGF("Too much stuffing" );
866 break;
867 }
868 }
869
870 if ((header[length - 1] & 0xc0) == 0x40)
871 length += 2;
872
873 len_skip = length;
874 length += mpeg1_skip_table[header[length - 1] >> 4];
875
876 /* Header points to the mpeg1 pes header */
877 ptsbuf = header + len_skip;
878
879 if ((ptsbuf[-1] & 0xe0) == 0x20 && TS_CHECK_MARKERS(ptsbuf, -1))
880 {
881 /* header has a pts */
882 uint32_t pts = read_pts(ptsbuf, -1);
883
884 if (pts != INVALID_TIMESTAMP)
885 {
886 str->pts = pts;
887#if 0
888 /* DTS isn't used for anything since things just get
889 decoded ASAP but keep the code around */
890 if (STREAM_IS_VIDEO(id))
891 {
892 /* Video stream - header may have a dts as well */
893 str->dts = pts;
894
895 if (ptsbuf[-1] & 0xf0) == 0x30)
896 {
897 pts = read_pts(ptsbuf, 4);
898
899 if (pts != INVALID_TIMESTAMP)
900 str->dts = pts;
901 }
902 }
903#endif
904 str->pkt_flags |= PKT_HAS_TS;
905 }
906 }
907 }
908
909 p += length;
910 /* Max bytes: 6 + 65535 - 7 = 65534 */
911 bytes = 6 + (header[4] << 8) + header[5] - length;
912
913 str->curr_packet = p;
914 str->curr_packet_end = p + bytes;
915 str->hdr.win_left = str->hdr.win_right + length;
916 str->hdr.win_right = str->hdr.win_left + bytes;
917
918 if (str->hdr.win_right > disk_buf.filesize)
919 {
920 /* No packet that exceeds end of file can be valid */
921 str_end_of_stream(str);
922 return STREAM_DATA_END;
923 }
924
925 return STREAM_OK;
926 } /* end while */
927
928 #undef INC_BUF
929}
930
931/* This simply reads data from the file one page at a time and returns a
932 * pointer to it in the buffer. */
933static int parse_elementary(struct stream *str, enum stream_parse_mode type)
934{
935 uint8_t *p;
936 ssize_t len = 0;
937
938 str->pkt_flags = 0;
939
940 switch (type)
941 {
942 case STREAM_PM_STREAMING:
943 /* Has the end been reached already? */
944 if (str->state == SSTATE_END)
945 return STREAM_DATA_END;
946
947 /* Are we at the end of file? */
948 if (str->hdr.win_left >= disk_buf.filesize)
949 {
950 str_end_of_stream(str);
951 return STREAM_DATA_END;
952 }
953
954 if (!disk_buf_is_data_ready(&str->hdr, MIN_BUFAHEAD))
955 {
956 /* This data range is not buffered yet - register stream to
957 * be notified when it becomes available. Stream is obliged
958 * to enter a TSTATE_DATA state if it must wait. */
959 int res = str_next_data_not_ready(str);
960
961 if (res != STREAM_OK)
962 return res;
963 }
964
965 len = DISK_BUF_PAGE_SIZE;
966
967 if ((size_t)(str->hdr.win_right + len) > (size_t)disk_buf.filesize)
968 len = disk_buf.filesize - str->hdr.win_right;
969
970 if (len <= 0)
971 {
972 str_end_of_stream(str);
973 return STREAM_DATA_END;
974 }
975
976 p = str->curr_packet_end;
977 break;
978 /* STREAM_PM_STREAMING: */
979
980 case STREAM_PM_RANDOM_ACCESS:
981 str->hdr.pos = disk_buf_lseek(str->hdr.pos, SEEK_SET);
982 len = disk_buf_getbuffer(DISK_BUF_PAGE_SIZE, &p, NULL, NULL);
983
984 if (len <= 0 || str->hdr.pos < 0 || str->hdr.pos >= str->hdr.limit)
985 {
986 str_end_of_stream(str);
987 return STREAM_DATA_END;
988 }
989 break;
990 /* STREAM_PM_RANDOM_ACCESS: */
991 }
992
993 str->state = SSTATE_PARSE;
994 str->curr_packet = p;
995 str->curr_packet_end = p + len;
996 str->hdr.win_left = str->hdr.win_right;
997 str->hdr.win_right = str->hdr.win_left + len;
998
999 return STREAM_OK;
1000}
1001
1002intptr_t parser_send_video_msg(long id, intptr_t data)
1003{
1004 intptr_t retval = 0;
1005
1006 if (video_str.thread != NULL && disk_buf.in_file >= 0)
1007 {
1008 /* Hook certain messages since they involve multiple operations
1009 * behind the scenes */
1010 switch (id)
1011 {
1012 case VIDEO_DISPLAY_SHOW:
1013 if (data != 0 && stream_status() != STREAM_PLAYING)
1014 { /* Only prepare image if showing and not playing */
1015 prepare_image(str_parser.last_seek_time);
1016 }
1017 break;
1018
1019 case VIDEO_PRINT_FRAME:
1020 case VIDEO_PRINT_THUMBNAIL:
1021 if (stream_status() == STREAM_PLAYING)
1022 break; /* Prepare image if not playing */
1023
1024 if (!prepare_image(str_parser.last_seek_time))
1025 return false; /* Preparation failed */
1026
1027 /* Image ready - pass message to video thread */
1028 break;
1029 }
1030
1031 retval = str_send_msg(&video_str, id, data);
1032 }
1033
1034 return retval;
1035}
1036
1037/* Seek parser to the specified time and return absolute time.
1038 * No actual hard stuff is performed here. That's done when streaming is
1039 * about to begin or something from the current position is requested */
1040uint32_t parser_seek_time(uint32_t time)
1041{
1042 if (!parser_can_seek())
1043 time = 0;
1044 else if (time > str_parser.duration)
1045 time = str_parser.duration;
1046
1047 str_parser.last_seek_time = time + str_parser.start_pts;
1048 return str_parser.last_seek_time;
1049}
1050
1051void parser_prepare_streaming(void)
1052{
1053 struct stream_window sw;
1054
1055 DEBUGF("parser_prepare_streaming\n");
1056
1057 /* Prepare initial video frame */
1058 prepare_image(str_parser.last_seek_time);
1059
1060 /* Sync audio stream */
1061 if (audio_str.start_pts != INVALID_TIMESTAMP)
1062 prepare_audio(str_parser.last_seek_time);
1063
1064 /* Prequeue some data and set buffer window */
1065 if (!stream_get_window(&sw))
1066 sw.left = sw.right = disk_buf.filesize;
1067
1068 DEBUGF(" swl:%ld swr:%ld\n", sw.left, sw.right);
1069
1070 if (sw.right > disk_buf.filesize - 4*MIN_BUFAHEAD)
1071 sw.right = disk_buf.filesize - 4*MIN_BUFAHEAD;
1072
1073 disk_buf_prepare_streaming(sw.left,
1074 sw.right - sw.left + 4*MIN_BUFAHEAD);
1075}
1076
1077int parser_init_stream(void)
1078{
1079 if (disk_buf.in_file < 0)
1080 return STREAM_ERROR;
1081
1082 /* TODO: Actually find which streams are available */
1083 audio_str.id = MPEG_STREAM_AUDIO_FIRST;
1084 video_str.id = MPEG_STREAM_VIDEO_FIRST;
1085
1086 /* Try to pull a video PES - if not found, try video init anyway which
1087 * should succeed if it really is a video-only stream */
1088 video_str.hdr.pos = 0;
1089 video_str.hdr.limit = 256*1024;
1090
1091 if (parse_demux(&video_str, STREAM_PM_RANDOM_ACCESS) == STREAM_OK)
1092 {
1093 /* Found a video packet - assume transport stream */
1094 str_parser.format = STREAM_FMT_MPEG_TS;
1095 str_parser.next_data = parse_demux;
1096 }
1097 else
1098 {
1099 /* No PES element found - assume video elementary stream */
1100 str_parser.format = STREAM_FMT_MPV;
1101 str_parser.next_data = parse_elementary;
1102 }
1103
1104 if (!init_video_info())
1105 {
1106 /* Cannot determine video size, etc. */
1107 return STREAM_UNSUPPORTED;
1108 }
1109
1110 if (str_parser.format == STREAM_FMT_MPEG_TS)
1111 {
1112 /* Initalize start_pts and end_pts with the length (in 45kHz units) of
1113 * the movie. INVALID_TIMESTAMP if the time could not be determined */
1114 init_times(&audio_str);
1115 init_times(&video_str);
1116
1117 if (video_str.start_pts == INVALID_TIMESTAMP)
1118 {
1119 /* Must have video at least */
1120 return STREAM_UNSUPPORTED;
1121 }
1122
1123 str_parser.flags |= STREAMF_CAN_SEEK;
1124
1125 if (audio_str.start_pts != INVALID_TIMESTAMP)
1126 {
1127 /* Overall duration is maximum span */
1128 str_parser.start_pts = MIN(audio_str.start_pts, video_str.start_pts);
1129 str_parser.end_pts = MAX(audio_str.end_pts, video_str.end_pts);
1130
1131 /* Audio will be part of playback pool */
1132 stream_add_stream(&audio_str);
1133 }
1134 else
1135 {
1136 /* No audio stream - use video only */
1137 str_parser.start_pts = video_str.start_pts;
1138 str_parser.end_pts = video_str.end_pts;
1139 }
1140 }
1141 else
1142 {
1143 /* There's no way to handle times on this without a full file
1144 * scan */
1145 audio_str.start_pts = INVALID_TIMESTAMP;
1146 audio_str.end_pts = INVALID_TIMESTAMP;
1147 video_str.start_pts = 0;
1148 video_str.end_pts = INVALID_TIMESTAMP;
1149 str_parser.start_pts = 0;
1150 str_parser.end_pts = INVALID_TIMESTAMP;
1151 }
1152
1153 /* Add video to playback pool */
1154 stream_add_stream(&video_str);
1155
1156 /* Cache duration - it's used very often */
1157 str_parser.duration = str_parser.end_pts - str_parser.start_pts;
1158
1159 DEBUGF("Movie info:\n"
1160 " size:%dx%d\n"
1161 " start:%u\n"
1162 " end:%u\n"
1163 " duration:%u\n",
1164 str_parser.dims.w, str_parser.dims.h,
1165 (unsigned)str_parser.start_pts,
1166 (unsigned)str_parser.end_pts,
1167 (unsigned)str_parser.duration);
1168
1169 return STREAM_OK;
1170}
1171
1172void parser_close_stream(void)
1173{
1174 stream_remove_streams();
1175 parser_init_state();
1176}
1177
1178bool parser_init(void)
1179{
1180 parser_init_state();
1181 return true;
1182}
diff --git a/apps/plugins/mpegplayer/mpeg_settings.c b/apps/plugins/mpegplayer/mpeg_settings.c
index 6cd5f7b186..7336507aaa 100644
--- a/apps/plugins/mpegplayer/mpeg_settings.c
+++ b/apps/plugins/mpegplayer/mpeg_settings.c
@@ -2,27 +2,16 @@
2#include "lib/configfile.h" 2#include "lib/configfile.h"
3#include "lib/oldmenuapi.h" 3#include "lib/oldmenuapi.h"
4 4
5#include "mpegplayer.h"
5#include "mpeg_settings.h" 6#include "mpeg_settings.h"
6 7
7extern struct plugin_api* rb;
8
9struct mpeg_settings settings; 8struct mpeg_settings settings;
10 9
11ssize_t seek_PTS(int in_file, int startTime, int accept_button);
12void display_thumb(int in_file);
13
14#ifndef HAVE_LCD_COLOR
15void gray_show(bool enable);
16#endif
17
18#define SETTINGS_VERSION 2 10#define SETTINGS_VERSION 2
19#define SETTINGS_MIN_VERSION 1 11#define SETTINGS_MIN_VERSION 1
20#define SETTINGS_FILENAME "mpegplayer.cfg" 12#define SETTINGS_FILENAME "mpegplayer.cfg"
21 13
22enum slider_state_t {state0, state1, state2, 14#define THUMB_DELAY (75*HZ/100)
23 state3, state4, state5} slider_state;
24
25volatile long thumbDelayTimer;
26 15
27/* button definitions */ 16/* button definitions */
28#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ 17#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
@@ -30,16 +19,16 @@ volatile long thumbDelayTimer;
30#define MPEG_SELECT BUTTON_ON 19#define MPEG_SELECT BUTTON_ON
31#define MPEG_RIGHT BUTTON_RIGHT 20#define MPEG_RIGHT BUTTON_RIGHT
32#define MPEG_LEFT BUTTON_LEFT 21#define MPEG_LEFT BUTTON_LEFT
33#define MPEG_SCROLL_DOWN BUTTON_UP 22#define MPEG_UP BUTTON_UP
34#define MPEG_SCROLL_UP BUTTON_DOWN 23#define MPEG_DOWN BUTTON_DOWN
35#define MPEG_EXIT BUTTON_OFF 24#define MPEG_EXIT BUTTON_OFF
36 25
37#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) 26#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
38#define MPEG_SELECT BUTTON_PLAY 27#define MPEG_SELECT BUTTON_PLAY
39#define MPEG_RIGHT BUTTON_RIGHT 28#define MPEG_RIGHT BUTTON_RIGHT
40#define MPEG_LEFT BUTTON_LEFT 29#define MPEG_LEFT BUTTON_LEFT
41#define MPEG_SCROLL_DOWN BUTTON_UP 30#define MPEG_UP BUTTON_UP
42#define MPEG_SCROLL_UP BUTTON_DOWN 31#define MPEG_DOWN BUTTON_DOWN
43#define MPEG_EXIT BUTTON_POWER 32#define MPEG_EXIT BUTTON_POWER
44 33
45#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ 34#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
@@ -48,8 +37,8 @@ volatile long thumbDelayTimer;
48#define MPEG_SELECT BUTTON_SELECT 37#define MPEG_SELECT BUTTON_SELECT
49#define MPEG_RIGHT BUTTON_RIGHT 38#define MPEG_RIGHT BUTTON_RIGHT
50#define MPEG_LEFT BUTTON_LEFT 39#define MPEG_LEFT BUTTON_LEFT
51#define MPEG_SCROLL_DOWN BUTTON_SCROLL_FWD 40#define MPEG_UP BUTTON_SCROLL_FWD
52#define MPEG_SCROLL_UP BUTTON_SCROLL_BACK 41#define MPEG_DOWN BUTTON_SCROLL_BACK
53#define MPEG_EXIT BUTTON_MENU 42#define MPEG_EXIT BUTTON_MENU
54 43
55#elif CONFIG_KEYPAD == GIGABEAT_PAD 44#elif CONFIG_KEYPAD == GIGABEAT_PAD
@@ -64,10 +53,10 @@ volatile long thumbDelayTimer;
64 53
65#elif CONFIG_KEYPAD == IRIVER_H10_PAD 54#elif CONFIG_KEYPAD == IRIVER_H10_PAD
66#define MPEG_SELECT BUTTON_PLAY 55#define MPEG_SELECT BUTTON_PLAY
67#define MPEG_SCROLL_UP BUTTON_SCROLL_UP
68#define MPEG_SCROLL_DOWN BUTTON_SCROLL_DOWN
69#define MPEG_LEFT BUTTON_LEFT 56#define MPEG_LEFT BUTTON_LEFT
70#define MPEG_RIGHT BUTTON_RIGHT 57#define MPEG_RIGHT BUTTON_RIGHT
58#define MPEG_UP BUTTON_SCROLL_UP
59#define MPEG_DOWN BUTTON_SCROLL_DOWN
71#define MPEG_EXIT BUTTON_POWER 60#define MPEG_EXIT BUTTON_POWER
72 61
73#elif (CONFIG_KEYPAD == SANSA_E200_PAD) 62#elif (CONFIG_KEYPAD == SANSA_E200_PAD)
@@ -136,10 +125,10 @@ static void display_options(void)
136 int options_quit = 0; 125 int options_quit = 0;
137 126
138 static const struct menu_item items[] = { 127 static const struct menu_item items[] = {
139#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) 128#if MPEG_OPTION_DITHERING_ENABLED
140 [MPEG_OPTION_DITHERING] = 129 [MPEG_OPTION_DITHERING] =
141 { "Dithering", NULL }, 130 { "Dithering", NULL },
142#endif /* #ifdef TOSHIBA_GIGABEAT_F */ 131#endif
143 [MPEG_OPTION_DISPLAY_FPS] = 132 [MPEG_OPTION_DISPLAY_FPS] =
144 { "Display FPS", NULL }, 133 { "Display FPS", NULL },
145 [MPEG_OPTION_LIMIT_FPS] = 134 [MPEG_OPTION_LIMIT_FPS] =
@@ -159,7 +148,7 @@ static void display_options(void)
159 148
160 switch (result) 149 switch (result)
161 { 150 {
162#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) 151#if MPEG_OPTION_DITHERING_ENABLED
163 case MPEG_OPTION_DITHERING: 152 case MPEG_OPTION_DITHERING:
164 result = (settings.displayoptions & LCD_YUV_DITHER) ? 1 : 0; 153 result = (settings.displayoptions & LCD_YUV_DITHER) ? 1 : 0;
165 rb->set_option("Dithering", &result, INT, noyes, 2, NULL); 154 rb->set_option("Dithering", &result, INT, noyes, 2, NULL);
@@ -167,7 +156,7 @@ static void display_options(void)
167 | ((result != 0) ? LCD_YUV_DITHER : 0); 156 | ((result != 0) ? LCD_YUV_DITHER : 0);
168 rb->lcd_yuv_set_options(settings.displayoptions); 157 rb->lcd_yuv_set_options(settings.displayoptions);
169 break; 158 break;
170#endif /* #ifdef TOSHIBA_GIGABEAT_F */ 159#endif /* MPEG_OPTION_DITHERING_ENABLED */
171 case MPEG_OPTION_DISPLAY_FPS: 160 case MPEG_OPTION_DISPLAY_FPS:
172 rb->set_option("Display FPS",&settings.showfps,INT, 161 rb->set_option("Display FPS",&settings.showfps,INT,
173 noyes, 2, NULL); 162 noyes, 2, NULL);
@@ -189,168 +178,343 @@ static void display_options(void)
189 menu_exit(menu_id); 178 menu_exit(menu_id);
190} 179}
191 180
192void draw_slider(int slider_ypos, int max_val, int current_val) 181static void show_loading(struct vo_rect *rc)
193{ 182{
194 int slider_margin = LCD_WIDTH*12/100; /* 12% */ 183 int oldmode;
195 int slider_width = LCD_WIDTH-(slider_margin*2); 184#ifndef HAVE_LCD_COLOR
196 char resume_str[32]; 185 stream_gray_show(false);
186#endif
187 oldmode = rb->lcd_get_drawmode();
188 rb->lcd_set_drawmode(DRMODE_BG | DRMODE_INVERSEVID);
189 rb->lcd_fillrect(rc->l-1, rc->t-1, rc->r - rc->l + 2, rc->b - rc->t + 2);
190 rb->lcd_set_drawmode(oldmode);
191 rb->splash(0, "Loading...");
192}
193
194void draw_slider(uint32_t range, uint32_t pos, struct vo_rect *rc)
195{
196 #define SLIDER_WIDTH (LCD_WIDTH-SLIDER_LMARGIN-SLIDER_RMARGIN)
197 #define SLIDER_X SLIDER_LMARGIN
198 #define SLIDER_Y (LCD_HEIGHT-SLIDER_HEIGHT-SLIDER_BMARGIN)
199 #define SLIDER_HEIGHT 8
200 #define SLIDER_TEXTMARGIN 1
201 #define SLIDER_LMARGIN 1
202 #define SLIDER_RMARGIN 1
203 #define SLIDER_TMARGIN 1
204 #define SLIDER_BMARGIN 1
205 #define SCREEN_MARGIN 1
206
207 struct hms hms;
208 char str[32];
209 int text_w, text_h, text_y;
210
211 /* Put positition on left */
212 ts_to_hms(pos, &hms);
213 hms_format(str, sizeof(str), &hms);
214 rb->lcd_getstringsize(str, NULL, &text_h);
215 text_y = SLIDER_Y - SLIDER_TEXTMARGIN - text_h;
216
217 if (rc == NULL)
218 {
219 int oldmode = rb->lcd_get_drawmode();
220 rb->lcd_set_drawmode(DRMODE_BG | DRMODE_INVERSEVID);
221 rb->lcd_fillrect(SLIDER_X, text_y, SLIDER_WIDTH,
222 LCD_HEIGHT - SLIDER_BMARGIN - text_y
223 - SLIDER_TMARGIN);
224 rb->lcd_set_drawmode(oldmode);
225
226 rb->lcd_putsxy(SLIDER_X, text_y, str);
227
228 /* Put duration on right */
229 ts_to_hms(range, &hms);
230 hms_format(str, sizeof(str), &hms);
231 rb->lcd_getstringsize(str, &text_w, NULL);
232
233 rb->lcd_putsxy(SLIDER_X + SLIDER_WIDTH - text_w, text_y, str);
234
235 /* Draw slider */
236 rb->lcd_drawrect(SLIDER_X, SLIDER_Y, SLIDER_WIDTH, SLIDER_HEIGHT);
237 rb->lcd_fillrect(SLIDER_X, SLIDER_Y,
238 muldiv_uint32(pos, SLIDER_WIDTH, range),
239 SLIDER_HEIGHT);
240
241 /* Update screen */
242 rb->lcd_update_rect(SLIDER_X, text_y - SLIDER_TMARGIN, SLIDER_WIDTH,
243 LCD_HEIGHT - SLIDER_BMARGIN - text_y + SLIDER_TEXTMARGIN);
244 }
245 else
246 {
247 /* Just return slider rectangle */
248 rc->l = SLIDER_X;
249 rc->t = text_y - SLIDER_TMARGIN;
250 rc->r = rc->l + SLIDER_WIDTH;
251 rc->b = rc->t + LCD_HEIGHT - SLIDER_BMARGIN - text_y;
252 }
253}
254
255bool display_thumb_image(const struct vo_rect *rc)
256{
257 if (!stream_display_thumb(rc))
258 {
259 rb->splash(0, "Frame not available");
260 return false;
261 }
262
263#ifdef HAVE_LCD_COLOR
264 /* Draw a raised border around the frame */
265 int oldcolor = rb->lcd_get_foreground();
266 rb->lcd_set_foreground(LCD_LIGHTGRAY);
267
268 rb->lcd_hline(rc->l-1, rc->r-1, rc->t-1);
269 rb->lcd_vline(rc->l-1, rc->t, rc->b-1);
197 270
198 /* max_val and current_val are in half minutes 271 rb->lcd_set_foreground(LCD_DARKGRAY);
199 determine value .0 or .5 to display */ 272
200 int max_hol = max_val/2; 273 rb->lcd_hline(rc->l-1, rc->r, rc->b);
201 int max_rem = (max_val-(max_hol*2))*5; 274 rb->lcd_vline(rc->r, rc->t-1, rc->b);
202 int current_hol = current_val/2; 275
203 int current_rem = (current_val-(current_hol*2))*5; 276 rb->lcd_set_foreground(oldcolor);
204 277
205 rb->snprintf(resume_str, sizeof(resume_str), "0.0"); 278 rb->lcd_update_rect(rc->l-1, rc->t-1, rc->r - rc->l + 2, 1);
206 rb->lcd_putsxy(slider_margin, slider_ypos, resume_str); 279 rb->lcd_update_rect(rc->l-1, rc->t, 1, rc->b - rc->t);
207 280 rb->lcd_update_rect(rc->l-1, rc->b, rc->r - rc->l + 2, 1);
208 rb->snprintf(resume_str, sizeof(resume_str), "%u.%u", max_hol, max_rem); 281 rb->lcd_update_rect(rc->r, rc->t, 1, rc->b - rc->t);
209 rb->lcd_putsxy(LCD_WIDTH-slider_margin-25, slider_ypos, resume_str); 282#else
210 283 /* Just show the thumbnail */
211 rb->lcd_drawrect(slider_margin, slider_ypos+17, slider_width, 8); 284 stream_gray_show(true);
212 rb->lcd_fillrect(slider_margin, slider_ypos+17, 285#endif
213 current_val*slider_width/max_val, 8); 286
214 287 return true;
215 rb->snprintf(resume_str, sizeof(resume_str), "%u.%u", current_hol, 288}
216 current_rem); 289
217 rb->lcd_putsxy(slider_margin+(current_val*slider_width/max_val)-16, 290/* Add an amount to the specified time - with saturation */
218 slider_ypos+29, resume_str); 291uint32_t increment_time(uint32_t val, int32_t amount, uint32_t range)
219 292{
220 rb->lcd_update_rect(0, slider_ypos, LCD_WIDTH, LCD_HEIGHT-slider_ypos); 293 if (amount < 0)
294 {
295 uint32_t off = -amount;
296 if (range > off && val >= off)
297 val -= off;
298 else
299 val = 0;
300 }
301 else if (amount > 0)
302 {
303 uint32_t off = amount;
304 if (range > off && val <= range - off)
305 val += off;
306 else
307 val = range;
308 }
309
310 return val;
221} 311}
222 312
223int get_start_time(int play_time, int in_file) 313int get_start_time(uint32_t duration)
224{ 314{
225 int seek_quit = 0;
226 int button = 0; 315 int button = 0;
227 int resume_time = settings.resume_time; 316 int tmo = TIMEOUT_NOBLOCK;
228 int slider_ypos = LCD_HEIGHT-45; 317 uint32_t resume_time = settings.resume_time;
229 int seek_return; 318 struct vo_rect rc_vid, rc_bound;
230 319 uint32_t aspect_vid, aspect_bound;
231 slider_state = state0; 320
232 thumbDelayTimer = *(rb->current_tick); 321 enum state_enum slider_state = state0;
322
233 rb->lcd_clear_display(); 323 rb->lcd_clear_display();
234 rb->lcd_update(); 324 rb->lcd_update();
235 325
236 while(seek_quit == 0) 326 draw_slider(0, 100, &rc_bound);
327 rc_bound.b = rc_bound.t - SLIDER_TMARGIN;
328#ifdef HAVE_LCD_COLOR
329 rc_bound.t = SCREEN_MARGIN;
330#else
331 rc_bound.t = 0;
332 rc_bound.l = 0;
333 rc_bound.r = LCD_WIDTH;
334#endif
335
336 DEBUGF("rc_bound: %d, %d, %d, %d\n", rc_bound.l, rc_bound.t,
337 rc_bound.r, rc_bound.b);
338
339 rc_vid.l = rc_vid.t = 0;
340 if (!stream_vo_get_size((struct vo_ext *)&rc_vid.r))
237 { 341 {
238 button = rb->button_get(false); 342 /* Can't get size - fill whole thing */
343 rc_vid.r = rc_bound.r - rc_bound.l;
344 rc_vid.b = rc_bound.b - rc_bound.t;
345 }
346
347#if !defined (HAVE_LCD_COLOR)
348#if LCD_PIXELFORMAT == VERTICAL_PACKING
349 rc_bound.b &= ~7; /* Align bottom edge */
350#endif
351#endif
352
353 /* Get aspect ratio of bounding rectangle and video in u16.16 */
354 aspect_bound = ((rc_bound.r - rc_bound.l) << 16) /
355 (rc_bound.b - rc_bound.t);
356
357 DEBUGF("aspect_bound: %ld.%02ld\n", aspect_bound >> 16,
358 100*(aspect_bound & 0xffff) >> 16);
359
360 aspect_vid = (rc_vid.r << 16) / rc_vid.b;
361
362 DEBUGF("aspect_vid: %ld.%02ld\n", aspect_vid >> 16,
363 100*(aspect_vid & 0xffff) >> 16);
364
365 if (aspect_vid >= aspect_bound)
366 {
367 /* Video proportionally wider than or same as bounding rectangle */
368 if (rc_vid.r > rc_bound.r - rc_bound.l)
369 {
370 rc_vid.r = rc_bound.r - rc_bound.l;
371 rc_vid.b = (rc_vid.r << 16) / aspect_vid;
372 }
373 /* else already fits */
374 }
375 else
376 {
377 /* Video proportionally narrower than bounding rectangle */
378 if (rc_vid.b > rc_bound.b - rc_bound.t)
379 {
380 rc_vid.b = rc_bound.b - rc_bound.t;
381 rc_vid.r = (aspect_vid * rc_vid.b) >> 16;
382 }
383 /* else already fits */
384 }
385
386 /* Even width and height >= 2 */
387 rc_vid.r = (rc_vid.r < 2) ? 2 : (rc_vid.r & ~1);
388 rc_vid.b = (rc_vid.b < 2) ? 2 : (rc_vid.b & ~1);
389
390 /* Center display in bounding rectangle */
391 rc_vid.l = ((rc_bound.l + rc_bound.r) - rc_vid.r) / 2;
392 rc_vid.r += rc_vid.l;
393
394 rc_vid.t = ((rc_bound.t + rc_bound.b) - rc_vid.b) / 2;
395 rc_vid.b += rc_vid.t;
396
397 DEBUGF("rc_vid: %d, %d, %d, %d\n", rc_vid.l, rc_vid.t,
398 rc_vid.r, rc_vid.b);
399
400#ifndef HAVE_LCD_COLOR
401 /* Set gray overlay to the bounding rectangle */
402 stream_set_gray_rect(&rc_bound);
403#endif
404
405 while(slider_state < state9)
406 {
407 button = tmo == TIMEOUT_BLOCK ?
408 rb->button_get(true) : rb->button_get_w_tmo(tmo);
409
239 switch (button) 410 switch (button)
240 { 411 {
241#if (CONFIG_KEYPAD == GIGABEAT_PAD) || \ 412 case BUTTON_NONE:
242 (CONFIG_KEYPAD == SANSA_E200_PAD) || \ 413 break;
243 (CONFIG_KEYPAD == SANSA_C200_PAD) 414
244 case MPEG_DOWN: 415 /* Coarse (1 minute) control */
245 case MPEG_DOWN | BUTTON_REPEAT: 416 case MPEG_DOWN:
246 if ((resume_time -= 20) < 0) 417 case MPEG_DOWN | BUTTON_REPEAT:
247 resume_time = 0; 418 resume_time = increment_time(resume_time, -60*TS_SECOND, duration);
248 slider_state = state0; 419 slider_state = state0;
249 thumbDelayTimer = *(rb->current_tick); 420 break;
250 break; 421
251 case MPEG_UP: 422 case MPEG_UP:
252 case MPEG_UP | BUTTON_REPEAT: 423 case MPEG_UP | BUTTON_REPEAT:
253 if ((resume_time += 20) > play_time) 424 resume_time = increment_time(resume_time, 60*TS_SECOND, duration);
254 resume_time = play_time; 425 slider_state = state0;
255 slider_state = state0; 426 break;
256 thumbDelayTimer = *(rb->current_tick); 427
257 break; 428 /* Fine (1 second) control */
429 case MPEG_LEFT:
430 case MPEG_LEFT | BUTTON_REPEAT:
431#ifdef MPEG_SCROLL_UP
432 case MPEG_SCROLL_UP:
433 case MPEG_SCROLL_UP | BUTTON_REPEAT:
258#endif 434#endif
259 case MPEG_LEFT: 435 resume_time = increment_time(resume_time, -TS_SECOND, duration);
260 case MPEG_LEFT | BUTTON_REPEAT: 436 slider_state = state0;
261 case MPEG_SCROLL_UP: 437 break;
262 case MPEG_SCROLL_UP | BUTTON_REPEAT: 438
263 if (--resume_time < 0) 439 case MPEG_RIGHT:
264 resume_time = 0; 440 case MPEG_RIGHT | BUTTON_REPEAT:
265 slider_state = state0; 441#ifdef MPEG_SCROLL_DOWN
266 thumbDelayTimer = *(rb->current_tick); 442 case MPEG_SCROLL_DOWN:
267 break; 443 case MPEG_SCROLL_DOWN | BUTTON_REPEAT:
268 case MPEG_RIGHT: 444#endif
269 case MPEG_RIGHT | BUTTON_REPEAT: 445 resume_time = increment_time(resume_time, TS_SECOND, duration);
270 case MPEG_SCROLL_DOWN: 446 slider_state = state0;
271 case MPEG_SCROLL_DOWN | BUTTON_REPEAT: 447 break;
272 if (++resume_time > play_time) 448
273 resume_time = play_time; 449 case MPEG_SELECT:
274 slider_state = state0; 450 settings.resume_time = resume_time;
275 thumbDelayTimer = *(rb->current_tick); 451 case MPEG_EXIT:
276 break; 452 slider_state = state9;
277 case MPEG_SELECT: 453 break;
278 settings.resume_time = resume_time; 454
279 case MPEG_EXIT: 455 case SYS_USB_CONNECTED:
280 seek_quit = 1; 456 slider_state = state9;
281 break; 457#ifndef HAVE_LCD_COLOR
282 default: 458 stream_gray_show(false);
283 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) 459#endif
284 seek_quit = 1; 460 cancel_cpu_boost();
285 break; 461 default:
462 rb->default_event_handler(button);
463 rb->yield();
464 continue;
286 } 465 }
287 466
288 rb->yield();
289
290 switch(slider_state) 467 switch(slider_state)
291 { 468 {
292 case state0: 469 case state0:
293 rb->lcd_clear_display(); 470 trigger_cpu_boost();
294 rb->lcd_update(); 471 stream_seek(resume_time, SEEK_SET);
295#ifdef HAVE_LCD_COLOR 472 show_loading(&rc_bound);
296 if (resume_time > 0) 473 draw_slider(duration, resume_time, NULL);
297 rb->splash(0, "Loading..."); 474 slider_state = state1;
298#endif 475 tmo = THUMB_DELAY;
299 slider_state = state1; 476 break;
300 break; 477 case state1:
301 case state1: 478 display_thumb_image(&rc_vid);
302 if (*(rb->current_tick) - thumbDelayTimer > 75) 479 slider_state = state2;
303 slider_state = state2; 480 case state2:
304 if (resume_time == 0) 481 case state9:
305 { 482 cancel_cpu_boost();
306 seek_return = 0; 483 tmo = TIMEOUT_BLOCK;
307 slider_state = state5; 484 default:
308 } 485 break;
309 draw_slider(slider_ypos, play_time, resume_time);
310 break;
311 case state2:
312 if ( (seek_return = seek_PTS(in_file, resume_time, 1)) >= 0)
313 slider_state = state3;
314 else if (seek_return == -101)
315 {
316 slider_state = state0;
317 thumbDelayTimer = *(rb->current_tick);
318 }
319 else
320 slider_state = state4;
321 break;
322 case state3:
323 display_thumb(in_file);
324 draw_slider(slider_ypos, play_time, resume_time);
325 slider_state = state4;
326 break;
327 case state4:
328 draw_slider(slider_ypos, play_time, resume_time);
329 slider_state = state5;
330 break;
331 case state5:
332 break;
333 } 486 }
334 } 487 }
335 488
489#ifndef HAVE_LCD_COLOR
490 /* Restore gray overlay dimensions */
491 stream_gray_show(false);
492 rc_bound.b = LCD_HEIGHT;
493 stream_set_gray_rect(&rc_bound);
494#endif
495
496 cancel_cpu_boost();
497
336 return button; 498 return button;
337} 499}
338 500
339enum mpeg_start_id mpeg_start_menu(int play_time, int in_file) 501enum mpeg_start_id mpeg_start_menu(uint32_t duration)
340{ 502{
341 int menu_id; 503 int menu_id;
342 int result = 0; 504 int result = 0;
343 int menu_quit = 0; 505 int menu_quit = 0;
344 506
345 /* add the resume time to the menu display */ 507 /* add the resume time to the menu display */
346 char resume_str[32]; 508 char resume_str[32];
347 int time_hol = (int)(settings.resume_time/2); 509 char hms_str[32];
348 int time_rem = ((settings.resume_time%2)==0) ? 0 : 5; 510 struct hms hms;
511
512 ts_to_hms(settings.resume_time, &hms);
513 hms_format(hms_str, sizeof(hms_str), &hms);
349 514
350 if (settings.enable_start_menu == 0) 515 if (settings.enable_start_menu == 0)
351 { 516 {
352 rb->snprintf(resume_str, sizeof(resume_str), 517 rb->snprintf(resume_str, sizeof(resume_str), "Yes: %s", hms_str);
353 "Yes (min): %d.%d", time_hol, time_rem);
354 518
355 struct opt_items resume_no_yes[2] = 519 struct opt_items resume_no_yes[2] =
356 { 520 {
@@ -369,11 +533,10 @@ enum mpeg_start_id mpeg_start_menu(int play_time, int in_file)
369 return MPEG_START_RESTART; 533 return MPEG_START_RESTART;
370 } 534 }
371 else 535 else
372 return MPEG_START_RESUME; 536 return MPEG_START_RESUME;
373 } 537 }
374 538
375 rb->snprintf(resume_str, sizeof(resume_str), 539 rb->snprintf(resume_str, sizeof(resume_str), "Resume at: %s", hms_str);
376 "Resume time (min): %d.%d", time_hol, time_rem);
377 540
378 struct menu_item items[] = 541 struct menu_item items[] =
379 { 542 {
@@ -382,7 +545,7 @@ enum mpeg_start_id mpeg_start_menu(int play_time, int in_file)
382 [MPEG_START_RESUME] = 545 [MPEG_START_RESUME] =
383 { resume_str, NULL }, 546 { resume_str, NULL },
384 [MPEG_START_SEEK] = 547 [MPEG_START_SEEK] =
385 { "Set start time (min)", NULL }, 548 { "Set start time", NULL },
386 [MPEG_START_QUIT] = 549 [MPEG_START_QUIT] =
387 { "Quit mpegplayer", NULL }, 550 { "Quit mpegplayer", NULL },
388 }; 551 };
@@ -390,9 +553,9 @@ enum mpeg_start_id mpeg_start_menu(int play_time, int in_file)
390 553
391 menu_id = menu_init(rb, items, sizeof(items) / sizeof(*items), 554 menu_id = menu_init(rb, items, sizeof(items) / sizeof(*items),
392 NULL, NULL, NULL, NULL); 555 NULL, NULL, NULL, NULL);
393 556
394 rb->button_clear_queue(); 557 rb->button_clear_queue();
395 558
396 while(menu_quit == 0) 559 while(menu_quit == 0)
397 { 560 {
398 result = menu_show(menu_id); 561 result = menu_show(menu_id);
@@ -407,15 +570,19 @@ enum mpeg_start_id mpeg_start_menu(int play_time, int in_file)
407 menu_quit = 1; 570 menu_quit = 1;
408 break; 571 break;
409 case MPEG_START_SEEK: 572 case MPEG_START_SEEK:
410#ifndef HAVE_LCD_COLOR 573 {
411 gray_show(true); 574 if (!stream_can_seek())
412#endif 575 {
413 if (get_start_time(play_time, in_file) == MPEG_SELECT) 576 rb->splash(HZ, "Unavailable");
577 break;
578 }
579
580 bool vis = stream_show_vo(false);
581 if (get_start_time(duration) == MPEG_SELECT)
414 menu_quit = 1; 582 menu_quit = 1;
415#ifndef HAVE_LCD_COLOR 583 stream_show_vo(vis);
416 gray_show(false);
417#endif
418 break; 584 break;
585 }
419 case MPEG_START_QUIT: 586 case MPEG_START_QUIT:
420 menu_quit = 1; 587 menu_quit = 1;
421 break; 588 break;
@@ -428,13 +595,15 @@ enum mpeg_start_id mpeg_start_menu(int play_time, int in_file)
428 595
429 menu_exit(menu_id); 596 menu_exit(menu_id);
430 597
598 rb->lcd_clear_display();
599 rb->lcd_update();
600
431 return result; 601 return result;
432} 602}
433 603
434void clear_resume_count(void) 604void clear_resume_count(void)
435{ 605{
436 configfile_save(SETTINGS_FILENAME, config, 606 configfile_save(SETTINGS_FILENAME, config, ARRAYLEN(config),
437 sizeof(config)/sizeof(*config),
438 SETTINGS_VERSION); 607 SETTINGS_VERSION);
439 608
440 settings.resume_count = 0; 609 settings.resume_count = 0;
@@ -514,7 +683,7 @@ void init_settings(const char* filename)
514 settings.skipframes = 1; /* Skip frames */ 683 settings.skipframes = 1; /* Skip frames */
515 settings.enable_start_menu = 1; /* Enable start menu */ 684 settings.enable_start_menu = 1; /* Enable start menu */
516 settings.resume_count = -1; 685 settings.resume_count = -1;
517#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) 686#if MPEG_OPTION_DITHERING_ENABLED
518 settings.displayoptions = 0; /* No visual effects */ 687 settings.displayoptions = 0; /* No visual effects */
519#endif 688#endif
520 689
@@ -530,7 +699,7 @@ void init_settings(const char* filename)
530 SETTINGS_VERSION); 699 SETTINGS_VERSION);
531 } 700 }
532 701
533#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) 702#if MPEG_OPTION_DITHERING_ENABLED
534 if ((settings.displayoptions = 703 if ((settings.displayoptions =
535 configfile_get_value(SETTINGS_FILENAME, "Display options")) < 0) 704 configfile_get_value(SETTINGS_FILENAME, "Display options")) < 0)
536 { 705 {
@@ -546,7 +715,7 @@ void init_settings(const char* filename)
546 configfile_update_entry(SETTINGS_FILENAME, "Resume count", 0); 715 configfile_update_entry(SETTINGS_FILENAME, "Resume count", 0);
547 } 716 }
548 717
549 rb->strcpy(settings.resume_filename, filename); 718 rb->snprintf(settings.resume_filename, MAX_PATH, "%s", filename);
550 719
551 /* get the resume time for the current mpeg if it exist */ 720 /* get the resume time for the current mpeg if it exist */
552 if ((settings.resume_time = configfile_get_value 721 if ((settings.resume_time = configfile_get_value
@@ -558,11 +727,11 @@ void init_settings(const char* filename)
558 727
559void save_settings(void) 728void save_settings(void)
560{ 729{
561 configfile_update_entry(SETTINGS_FILENAME, "Show FPS", 730 configfile_update_entry(SETTINGS_FILENAME, "Show FPS",
562 settings.showfps); 731 settings.showfps);
563 configfile_update_entry(SETTINGS_FILENAME, "Limit FPS", 732 configfile_update_entry(SETTINGS_FILENAME, "Limit FPS",
564 settings.limitfps); 733 settings.limitfps);
565 configfile_update_entry(SETTINGS_FILENAME, "Skip frames", 734 configfile_update_entry(SETTINGS_FILENAME, "Skip frames",
566 settings.skipframes); 735 settings.skipframes);
567 configfile_update_entry(SETTINGS_FILENAME, "Enable start menu", 736 configfile_update_entry(SETTINGS_FILENAME, "Enable start menu",
568 settings.enable_start_menu); 737 settings.enable_start_menu);
@@ -575,8 +744,8 @@ void save_settings(void)
575 ++settings.resume_count); 744 ++settings.resume_count);
576 } 745 }
577 746
578#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) 747#if MPEG_OPTION_DITHERING_ENABLED
579 configfile_update_entry(SETTINGS_FILENAME, "Display options", 748 configfile_update_entry(SETTINGS_FILENAME, "Display options",
580 settings.displayoptions); 749 settings.displayoptions);
581#endif 750#endif
582} 751}
diff --git a/apps/plugins/mpegplayer/mpeg_settings.h b/apps/plugins/mpegplayer/mpeg_settings.h
index 613a345307..3186c73f2c 100644
--- a/apps/plugins/mpegplayer/mpeg_settings.h
+++ b/apps/plugins/mpegplayer/mpeg_settings.h
@@ -1,9 +1,17 @@
1 1
2#include "plugin.h" 2#include "plugin.h"
3 3
4#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200)
5#define MPEG_OPTION_DITHERING_ENABLED 1
6#endif
7
8#ifndef MPEG_OPTION_DITHERING_ENABLED
9#define MPEG_OPTION_DITHERING_ENABLED 0
10#endif
11
4enum mpeg_option_id 12enum mpeg_option_id
5{ 13{
6#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) 14#if MPEG_OPTION_DITHERING_ENABLED
7 MPEG_OPTION_DITHERING, 15 MPEG_OPTION_DITHERING,
8#endif 16#endif
9 MPEG_OPTION_DISPLAY_FPS, 17 MPEG_OPTION_DISPLAY_FPS,
@@ -34,16 +42,16 @@ struct mpeg_settings {
34 int enable_start_menu; /* flag to enable/disable start menu */ 42 int enable_start_menu; /* flag to enable/disable start menu */
35 int resume_count; /* total # of resumes in config file */ 43 int resume_count; /* total # of resumes in config file */
36 int resume_time; /* resume time for current mpeg (in half minutes) */ 44 int resume_time; /* resume time for current mpeg (in half minutes) */
37 char resume_filename[128]; /* filename of current mpeg */ 45 char resume_filename[MAX_PATH]; /* filename of current mpeg */
38#if defined(TOSHIBA_GIGABEAT_F) || defined(SANSA_E200) || defined(SANSA_C200) 46#if MPEG_OPTION_DITHERING_ENABLED
39 int displayoptions; 47 int displayoptions;
40#endif 48#endif
41}; 49};
42 50
43extern struct mpeg_settings settings; 51extern struct mpeg_settings settings;
44 52
45int get_start_time(int play_time, int in_file); 53int get_start_time(uint32_t duration);
46enum mpeg_start_id mpeg_start_menu(int play_time, int in_file); 54enum mpeg_start_id mpeg_start_menu(uint32_t duration);
47enum mpeg_menu_id mpeg_menu(void); 55enum mpeg_menu_id mpeg_menu(void);
48void init_settings(const char* filename); 56void init_settings(const char* filename);
49void save_settings(void); 57void save_settings(void);
diff --git a/apps/plugins/mpegplayer/mpeg_stream.h b/apps/plugins/mpegplayer/mpeg_stream.h
new file mode 100644
index 0000000000..0c850f072d
--- /dev/null
+++ b/apps/plugins/mpegplayer/mpeg_stream.h
@@ -0,0 +1,120 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Stream definitions for MPEG
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef MPEG_STREAM_H
22#define MPEG_STREAM_H
23
24/* Codes for various header byte sequences - MSB represents lowest memory
25 address */
26#define PACKET_START_CODE_PREFIX 0x00000100ul
27#define END_CODE 0x000001b9ul
28#define PACK_START_CODE 0x000001baul
29#define SYSTEM_HEADER_START_CODE 0x000001bbul
30
31/* p = base pointer, b0 - b4 = byte offsets from p */
32/* We only care about the MS 32 bits of the 33 and so the ticks are 45kHz */
33#define TS_FROM_HEADER(p, b0) \
34 ((uint32_t)((((p)[(b0)+0] & 0x0e) << 28) | \
35 (((p)[(b0)+1] ) << 21) | \
36 (((p)[(b0)+2] & 0xfe) << 13) | \
37 (((p)[(b0)+3] ) << 6) | \
38 (((p)[(b0)+4] ) >> 2)))
39
40#define TS_CHECK_MARKERS(p, b0) \
41 (((((p)[(b0)+0] & 0x01) << 2) | \
42 (((p)[(b0)+2] & 0x01) << 1) | \
43 (((p)[(b0)+4] & 0x01) )) == 0x07)
44
45/* Get the SCR in our 45kHz ticks. Ignore the 9-bit extension */
46#define MPEG2_PACK_HEADER_SCR(p, b0) \
47 ((uint32_t)((((p)[(b0)+0] & 0x38) << 26) | \
48 (((p)[(b0)+0] & 0x03) << 27) | \
49 (((p)[(b0)+1] ) << 19) | \
50 (((p)[(b0)+2] & 0xf8) << 11) | \
51 (((p)[(b0)+2] & 0x03) << 12) | \
52 (((p)[(b0)+3] ) << 4) | \
53 (((p)[(b0)+4] ) >> 4)))
54
55#define MPEG2_CHECK_PACK_SCR_MARKERS(ph, b0) \
56 (((((ph)[(b0)+0] & 0x04) ) | \
57 (((ph)[(b0)+2] & 0x04) >> 1) | \
58 (((ph)[(b0)+4] & 0x04) >> 2)) == 0x07)
59
60#define INVALID_TIMESTAMP (~(uint32_t)0)
61#define MAX_TIMESTAMP (INVALID_TIMESTAMP-1)
62#define TS_SECOND (45000) /* Timestamp ticks per second */
63#define TC_SECOND (27000000) /* MPEG timecode ticks per second */
64
65/* These values immediately follow the start code prefix '00 00 01' */
66
67/* Video start codes */
68#define MPEG_START_PICTURE 0x00
69#define MPEG_START_SLICE_FIRST 0x01
70#define MPEG_START_SLICE_LAST 0xaf
71#define MPEG_START_RESERVED_1 0xb0
72#define MPEG_START_RESERVED_2 0xb1
73#define MPEG_START_USER_DATA 0xb2
74#define MPEG_START_SEQUENCE_HEADER 0xb3
75#define MPEG_START_SEQUENCE_ERROR 0xb4
76#define MPEG_START_EXTENSION 0xb5
77#define MPEG_START_RESERVED_3 0xb6
78#define MPEG_START_SEQUENCE_END 0xb7
79#define MPEG_START_GOP 0xb8
80
81/* Stream IDs */
82#define MPEG_STREAM_PROGRAM_END 0xb9
83#define MPEG_STREAM_PACK_HEADER 0xba
84#define MPEG_STREAM_SYSTEM_HEADER 0xbb
85#define MPEG_STREAM_PROGRAM_STREAM_MAP 0xbc
86#define MPEG_STREAM_PRIVATE_1 0xbd
87#define MPEG_STREAM_PADDING 0xbe
88#define MPEG_STREAM_PRIVATE_2 0xbf
89#define MPEG_STREAM_AUDIO_FIRST 0xc0
90#define MPEG_STREAM_AUDIO_LAST 0xcf
91#define MPEG_STREAM_VIDEO_FIRST 0xe0
92#define MPEG_STREAM_VIDEO_LAST 0xef
93#define MPEG_STREAM_ECM 0xf0
94#define MPEG_STREAM_EMM 0xf1
95/* ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A or
96 * ISO/IEC 13818-6_DSMCC_stream */
97#define MPEG_STREAM_MISC_1 0xf2
98/* ISO/IEC_13522_stream */
99#define MPEG_STREAM_MISC_2 0xf3
100/* ITU-T Rec. H.222.1 type A - E */
101#define MPEG_STREAM_MISC_3 0xf4
102#define MPEG_STREAM_MISC_4 0xf5
103#define MPEG_STREAM_MISC_5 0xf6
104#define MPEG_STREAM_MISC_6 0xf7
105#define MPEG_STREAM_MISC_7 0xf8
106#define MPEG_STREAM_ANCILLARY 0xf9
107#define MPEG_STREAM_RESERVED_FIRST 0xfa
108#define MPEG_STREAM_RESERVED_LAST 0xfe
109/* Program stream directory */
110#define MPEG_STREAM_PROGRAM_DIRECTORY 0xff
111
112#define STREAM_IS_AUDIO(s) (((s) & 0xf0) == 0xc0)
113#define STREAM_IS_VIDEO(s) (((s) & 0xf0) == 0xe0)
114
115#define MPEG_MAX_PACKET_SIZE (64*1024+16)
116
117/* Largest MPEG audio frame - MPEG1, Layer II, 384kbps, 32kHz, pad */
118#define MPA_MAX_FRAME_SIZE 1729
119
120#endif /* MPEG_STREAM_H */
diff --git a/apps/plugins/mpegplayer/mpegplayer.c b/apps/plugins/mpegplayer/mpegplayer.c
index eb904ed3c1..03ec5ba821 100644
--- a/apps/plugins/mpegplayer/mpegplayer.c
+++ b/apps/plugins/mpegplayer/mpegplayer.c
@@ -1,110 +1,110 @@
1/* 1/***************************************************************************
2 * mpegplayer.c - based on : 2 * __________ __ ___.
3 * - mpeg2dec.c 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * - m2psd.c (http://www.brouhaha.com/~eric/software/m2psd/) 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
5 * 9 *
6 * Copyright (C) 2000-2003 Michel Lespinasse <walken@zoy.org> 10 * mpegplayer main entrypoint and UI implementation
7 * Copyright (C) 1999-2000 Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
8 * 11 *
9 * m2psd: MPEG 2 Program Stream Demultiplexer 12 * Copyright (c) 2007 Michael Sevakis
10 * Copyright (C) 2003 Eric Smith <eric@brouhaha.com>
11 * 13 *
12 * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. 14 * All files in this archive are subject to the GNU General Public License.
13 * See http://libmpeg2.sourceforge.net/ for updates. 15 * See the file COPYING in the source tree root for full license agreement.
14 * 16 *
15 * mpeg2dec is free software; you can redistribute it and/or modify 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * it under the terms of the GNU General Public License as published by 18 * KIND, either express or implied.
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 * 19 *
20 * mpeg2dec is distributed in the hope that it will be useful, 20 ****************************************************************************/
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of 21
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22/****************************************************************************
23 * GNU General Public License for more details. 23 * NOTES:
24 * 24 *
25 * You should have received a copy of the GNU General Public License 25 * mpegplayer is structured as follows:
26 * along with this program; if not, write to the Free Software 26 *
27 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 27 * +-->Video Thread-->Video Output-->LCD
28 */ 28 * |
29 29 * UI-->Stream Manager-->+-->Audio Thread-->PCM buffer--Audio Device
30/* 30 * | | | | (ref. clock)
31 31 * | | +-->Buffer Thread |
32NOTES: 32 * Stream Data | | (clock intf./
33 33 * Requests | File Cache drift adj.)
34mpegplayer is structured as follows: 34 * | Disk I/O
35 35 * Stream services
361) Video thread (running on the COP for PortalPlayer targets). 36 * (timing, etc.)
372) Audio thread (running on the main CPU to maintain consistency with 37 *
38 the audio FIQ hander on PP). 38 * Thread list:
393) The main thread which takes care of buffering. 39 * 1) The main thread - Handles user input, settings, basic playback control
40 40 * and USB connect.
41Using the main thread for buffering wastes the 8KB main stack which is 41 *
42in IRAM. However, 8KB is not enough for the audio thread to run (it 42 * 2) Stream Manager thread - Handles playback state, events from streams
43needs somewhere between 8KB and 9KB), so we create a new thread in 43 * such as when a stream is finished, stream commands, PCM state. The
44order to`give it a larger stack and steal the core codec thread's 44 * layer in which this thread run also handles arbitration of data
45stack (9KB of precious IRAM). 45 * requests between the streams and the disk buffer. The actual specific
46 46 * transport layer code may get moved out to support multiple container
47The button loop (and hence pause/resume, main menu and, in the future, 47 * formats.
48seeking) is placed in the audio thread. This keeps it on the main CPU 48 *
49in PP targets and also allows buffering to continue in the background 49 * 3) Buffer thread - Buffers data in the background, generates notifications
50whilst the main thread is filling the buffer. 50 * to streams when their data has been buffered, and watches streams'
51 51 * progress to keep data available during playback. Handles synchronous
52A/V sync is not yet implemented but is planned to be achieved by 52 * random access requests when the file cache is missed.
53syncing the master clock with the audio, and then (as is currently 53 *
54implemented), syncing video with the master clock. This can happen in 54 * 4) Video thread (running on the COP for PortalPlayer targets) - Decodes
55the audio thread, along with resyncing after pause. 55 * the video stream and renders video frames to the LCD. Handles
56 56 * miscellaneous video tasks like frame and thumbnail printing.
57Seeking should probably happen in the main thread, as that's where the 57 *
58buffering happens. 58 * 5) Audio thread (running on the main CPU to maintain consistency with the
59 59 * audio FIQ hander on PP) - Decodes audio frames and places them into
60On PortalPlayer targets, the main CPU is not being fully utilised - 60 * the PCM buffer for rendering by the audio device.
61the bottleneck is the video decoding on the COP. One way to improve 61 *
62that might be to move the rendering of the frames (i.e. the 62 * Streams are neither aware of one another nor care about one another. All
63lcd_yuv_blit() call) from the COP back to the main CPU. Ideas and 63 * streams shall have their own thread (unless it is _really_ efficient to
64patches for that are welcome! 64 * have a single thread handle a couple minor streams). All coordination of
65 65 * the streams is done through the stream manager. The clocking is controlled
66Notes about MPEG files: 66 * by and exposed by the stream manager to other streams and implemented at
67 67 * the PCM level.
68MPEG System Clock is 27MHz - i.e. 27000000 ticks/second. 68 *
69 69 * Notes about MPEG files:
70FPS is represented in terms of a frame period - this is always an 70 *
71integer number of 27MHz ticks. 71 * MPEG System Clock is 27MHz - i.e. 27000000 ticks/second.
72 72 *
73e.g. 29.97fps (30000/1001) NTSC video has an exact frame period of 73 * FPS is represented in terms of a frame period - this is always an
74900900 27MHz ticks. 74 * integer number of 27MHz ticks.
75 75 *
76In libmpeg2, info->sequence->frame_period contains the frame_period. 76 * e.g. 29.97fps (30000/1001) NTSC video has an exact frame period of
77 77 * 900900 27MHz ticks.
78Working with Rockbox's 100Hz tick, the common frame rates would need 78 *
79to be as follows: 79 * In libmpeg2, info->sequence->frame_period contains the frame_period.
80 80 *
81FPS | 27Mhz | 100Hz | 44.1KHz | 48KHz 81 * Working with Rockbox's 100Hz tick, the common frame rates would need
82--------|----------------------------------------------------------- 82 * to be as follows (1):
8310* | 2700000 | 10 | 4410 | 4800 83 *
8412* | 2250000 | 8.3333 | 3675 | 4000 84 * FPS | 27Mhz | 100Hz | 44.1KHz | 48KHz
8515* | 1800000 | 6.6667 | 2940 | 3200 85 * --------|-----------------------------------------------------------
8623.9760 | 1126125 | 4.170833333 | 1839.3375 | 2002 86 * 10* | 2700000 | 10 | 4410 | 4800
8724 | 1125000 | 4.166667 | 1837.5 | 2000 87 * 12* | 2250000 | 8.3333 | 3675 | 4000
8825 | 1080000 | 4 | 1764 | 1920 88 * 15* | 1800000 | 6.6667 | 2940 | 3200
8929.9700 | 900900 | 3.336667 | 1471,47 | 1601.6 89 * 23.9760 | 1126125 | 4.170833333 | 1839.3375 | 2002
9030 | 900000 | 3.333333 | 1470 | 1600 90 * 24 | 1125000 | 4.166667 | 1837.5 | 2000
91 91 * 25 | 1080000 | 4 | 1764 | 1920
92 92 * 29.9700 | 900900 | 3.336667 | 1471,47 | 1601.6
93*Unofficial framerates 93 * 30 | 900000 | 3.333333 | 1470 | 1600
94 94 *
95*/ 95 * *Unofficial framerates
96 96 *
97 97 * (1) But we don't really care since the audio clock is used anyway and has
98#include "mpeg2dec_config.h" 98 * very fine resolution ;-)
99 99 *****************************************************************************/
100#include "plugin.h" 100#include "plugin.h"
101#include "gray.h" 101#include "mpegplayer.h"
102#include "helper.h" 102#include "helper.h"
103
104#include "mpeg2.h"
105#include "mpeg_settings.h" 103#include "mpeg_settings.h"
104#include "mpeg2.h"
106#include "video_out.h" 105#include "video_out.h"
107#include "../../codecs/libmad/mad.h" 106#include "stream_thread.h"
107#include "stream_mgr.h"
108 108
109PLUGIN_HEADER 109PLUGIN_HEADER
110PLUGIN_IRAM_DECLARE 110PLUGIN_IRAM_DECLARE
@@ -177,908 +177,43 @@ PLUGIN_IRAM_DECLARE
177struct plugin_api* rb; 177struct plugin_api* rb;
178 178
179CACHE_FUNCTION_WRAPPERS(rb); 179CACHE_FUNCTION_WRAPPERS(rb);
180ALIGN_BUFFER_WRAPPER(rb);
180 181
181extern void *mpeg_malloc(size_t size, mpeg2_alloc_t reason); 182static bool button_loop(void)
182extern size_t mpeg_alloc_init(unsigned char *buf, size_t mallocsize,
183 size_t libmpeg2size);
184
185static mpeg2dec_t * mpeg2dec NOCACHEBSS_ATTR;
186static int total_offset NOCACHEBSS_ATTR = 0;
187static int num_drawn NOCACHEBSS_ATTR = 0;
188static int count_start NOCACHEBSS_ATTR = 0;
189
190/* Streams */
191typedef struct
192{
193 struct thread_entry *thread; /* Stream's thread */
194 int status; /* Current stream status */
195 struct queue_event ev; /* Event sent to steam */
196 int have_msg; /* 1=event pending */
197 int replied; /* 1=replied to last event */
198 int reply; /* reply value */
199 struct mutex msg_lock; /* serialization for event senders */
200 uint8_t* curr_packet; /* Current stream packet beginning */
201 uint8_t* curr_packet_end; /* Current stream packet end */
202
203 uint8_t* prev_packet; /* Previous stream packet beginning */
204 size_t prev_packet_length; /* Lenth of previous packet */
205 size_t buffer_remaining; /* How much data is left in the buffer */
206 uint32_t curr_pts; /* Current presentation timestamp */
207 uint32_t curr_time; /* Current time in samples */
208 uint32_t tagged; /* curr_pts is valid */
209
210 int id;
211} Stream;
212
213static Stream audio_str IBSS_ATTR;
214static Stream video_str IBSS_ATTR;
215
216/* Messages */
217enum
218{
219 STREAM_PLAY,
220 STREAM_PAUSE,
221 STREAM_QUIT
222};
223
224/* Status */
225enum
226{
227 STREAM_ERROR = -4,
228 STREAM_STOPPED = -3,
229 STREAM_TERMINATED = -2,
230 STREAM_DONE = -1,
231 STREAM_PLAYING = 0,
232 STREAM_PAUSED,
233 STREAM_BUFFERING
234};
235
236/* Returns true if a message is waiting */
237static inline bool str_have_msg(Stream *str)
238{ 183{
239 return str->have_msg != 0; 184 bool ret = true;
240}
241 185
242/* Waits until a message is sent */ 186 rb->lcd_setfont(FONT_SYSFIXED);
243static void str_wait_msg(Stream *str) 187 rb->lcd_clear_display();
244{ 188 rb->lcd_update();
245 int spin_count = 0;
246 189
247 while (str->have_msg == 0) 190 /* Start playback at the specified starting time */
191 if (stream_seek(settings.resume_time, SEEK_SET) < STREAM_OK ||
192 (stream_show_vo(true), stream_play()) < STREAM_OK)
248 { 193 {
249 if (spin_count < 100) 194 rb->splash(HZ*2, "Playback failed");
250 {
251 rb->yield();
252 spin_count++;
253 continue;
254 }
255
256 rb->sleep(0);
257 }
258}
259
260/* Returns a message waiting or blocks until one is available - removes the
261 event */
262static void str_get_msg(Stream *str, struct queue_event *ev)
263{
264 str_wait_msg(str);
265 ev->id = str->ev.id;
266 ev->data = str->ev.data;
267 str->have_msg = 0;
268}
269
270/* Peeks at the current message without blocking, returns the data but
271 does not remove the event */
272static bool str_look_msg(Stream *str, struct queue_event *ev)
273{
274 if (!str_have_msg(str))
275 return false; 195 return false;
276
277 ev->id = str->ev.id;
278 ev->data = str->ev.data;
279 return true;
280}
281
282/* Replies to the last message pulled - has no effect if last message has not
283 been pulled or already replied */
284static void str_reply_msg(Stream *str, int reply)
285{
286 if (str->replied == 1 || str->have_msg != 0)
287 return;
288
289 str->reply = reply;
290 str->replied = 1;
291}
292
293/* Sends a message to a stream and waits for a reply */
294static intptr_t str_send_msg(Stream *str, int id, intptr_t data)
295{
296 int spin_count = 0;
297 intptr_t reply;
298
299#if 0
300 if (str->thread == rb->thread_get_current())
301 return str->dispatch_fn(str, msg);
302#endif
303
304 /* Only one thread at a time, please */
305 rb->mutex_lock(&str->msg_lock);
306
307 str->ev.id = id;
308 str->ev.data = data;
309 str->reply = 0;
310 str->replied = 0;
311 str->have_msg = 1;
312
313 while (str->replied == 0 && str->status != STREAM_TERMINATED)
314 {
315 if (spin_count < 100)
316 {
317 rb->yield();
318 spin_count++;
319 continue;
320 }
321
322 rb->sleep(0);
323 }
324
325 reply = str->reply;
326
327 rb->mutex_unlock(&str->msg_lock);
328
329 return reply;
330}
331
332/* NOTE: Putting the following variables in IRAM cause audio corruption
333 on the ipod (reason unknown)
334*/
335static uint8_t *disk_buf_start IBSS_ATTR; /* Start pointer */
336static uint8_t *disk_buf_end IBSS_ATTR; /* End of buffer pointer less
337 MPEG_GUARDBUF_SIZE. The
338 guard space is used to wrap
339 data at the buffer start to
340 pass continuous data
341 packets */
342static uint8_t *disk_buf_tail IBSS_ATTR; /* Location of last data + 1
343 filled into the buffer */
344static size_t disk_buf_size IBSS_ATTR; /* The total buffer length
345 including the guard
346 space */
347static size_t file_remaining IBSS_ATTR;
348
349#if NUM_CORES > 1
350/* Some stream variables are shared between cores */
351struct mutex stream_lock IBSS_ATTR;
352static inline void init_stream_lock(void)
353 { rb->mutex_init(&stream_lock); }
354static inline void lock_stream(void)
355 { rb->mutex_lock(&stream_lock); }
356static inline void unlock_stream(void)
357 { rb->mutex_unlock(&stream_lock); }
358#else
359/* No RMW issue here */
360static inline void init_stream_lock(void)
361 { }
362static inline void lock_stream(void)
363 { }
364static inline void unlock_stream(void)
365 { }
366#endif
367
368static int audio_sync_start IBSS_ATTR; /* If 0, the audio thread
369 yields waiting on the video
370 thread to synchronize with
371 the stream */
372static uint32_t audio_sync_time IBSS_ATTR; /* The time that the video
373 thread has reached after
374 synchronizing. The
375 audio thread now needs
376 to advance to this
377 time */
378static int video_sync_start IBSS_ATTR; /* While 0, the video thread
379 yields until the audio
380 thread has reached the
381 audio_sync_time */
382static int video_thumb_print IBSS_ATTR; /* If 1, the video thread is
383 only decoding one frame for
384 use in the menu. If 0,
385 normal operation */
386static int end_pts_time IBSS_ATTR; /* The movie end time as represented by
387 the maximum audio PTS tag in the
388 stream converted to half minutes */
389static int start_pts_time IBSS_ATTR; /* The movie start time as represented by
390 the first audio PTS tag in the
391 stream converted to half minutes */
392char *filename; /* hack for resume time storage */
393
394
395/* Various buffers */
396/* TODO: Can we reduce the PCM buffer size? */
397#define PCMBUFFER_SIZE ((512*1024)-PCMBUFFER_GUARD_SIZE)
398#define PCMBUFFER_GUARD_SIZE (1152*4 + sizeof (struct pcm_frame_header))
399#define MPA_MAX_FRAME_SIZE 1729 /* Largest frame - MPEG1, Layer II, 384kbps, 32kHz, pad */
400#define MPABUF_SIZE (64*1024 + ALIGN_UP(MPA_MAX_FRAME_SIZE + 2*MAD_BUFFER_GUARD, 4))
401#define LIBMPEG2BUFFER_SIZE (2*1024*1024)
402
403/* 65536+6 is required since each PES has a 6 byte header with a 16 bit packet length field */
404#define MPEG_GUARDBUF_SIZE (65*1024) /* Keep a bit extra - excessive for now */
405#define MPEG_LOW_WATERMARK (1024*1024)
406
407static void pcm_playback_play_pause(bool play);
408
409/* libmad related functions/definitions */
410#define INPUT_CHUNK_SIZE 8192
411
412struct mad_stream stream IBSS_ATTR;
413struct mad_frame frame IBSS_ATTR;
414struct mad_synth synth IBSS_ATTR;
415
416unsigned char mad_main_data[MAD_BUFFER_MDLEN]; /* 2567 bytes */
417
418/* There isn't enough room for this in IRAM on PortalPlayer, but there
419 is for Coldfire. */
420
421#ifdef CPU_COLDFIRE
422static mad_fixed_t mad_frame_overlap[2][32][18] IBSS_ATTR; /* 4608 bytes */
423#else
424static mad_fixed_t mad_frame_overlap[2][32][18] __attribute__((aligned(16))); /* 4608 bytes */
425#endif
426
427static void init_mad(void* mad_frame_overlap)
428{
429 rb->memset(&stream, 0, sizeof(struct mad_stream));
430 rb->memset(&frame, 0, sizeof(struct mad_frame));
431 rb->memset(&synth, 0, sizeof(struct mad_synth));
432
433 mad_stream_init(&stream);
434 mad_frame_init(&frame);
435
436 /* We do this so libmad doesn't try to call codec_calloc() */
437 frame.overlap = mad_frame_overlap;
438
439 rb->memset(mad_main_data, 0, sizeof(mad_main_data));
440 stream.main_data = &mad_main_data;
441}
442
443/* MPEG related headers */
444
445/* Macros for comparing memory bytes to a series of constant bytes in an
446 efficient manner - evaluate to true if corresponding bytes match */
447#if defined (CPU_ARM)
448/* ARM must load 32-bit values at addres % 4 == 0 offsets but this data
449 isn't aligned nescessarily, so just byte compare */
450#define CMP_3_CONST(_a, _b) \
451 ({ \
452 int _x; \
453 asm volatile ( \
454 "ldrb %[x], [%[a], #0] \r\n" \
455 "eors %[x], %[x], %[b0] \r\n" \
456 "ldreqb %[x], [%[a], #1] \r\n" \
457 "eoreqs %[x], %[x], %[b1] \r\n" \
458 "ldreqb %[x], [%[a], #2] \r\n" \
459 "eoreqs %[x], %[x], %[b2] \r\n" \
460 : [x]"=&r"(_x) \
461 : [a]"r"(_a), \
462 [b0]"i"((_b) >> 24), \
463 [b1]"i"((_b) << 8 >> 24), \
464 [b2]"i"((_b) << 16 >> 24) \
465 ); \
466 _x == 0; \
467 })
468#define CMP_4_CONST(_a, _b) \
469 ({ \
470 int _x; \
471 asm volatile ( \
472 "ldrb %[x], [%[a], #0] \r\n" \
473 "eors %[x], %[x], %[b0] \r\n" \
474 "ldreqb %[x], [%[a], #1] \r\n" \
475 "eoreqs %[x], %[x], %[b1] \r\n" \
476 "ldreqb %[x], [%[a], #2] \r\n" \
477 "eoreqs %[x], %[x], %[b2] \r\n" \
478 "ldreqb %[x], [%[a], #3] \r\n" \
479 "eoreqs %[x], %[x], %[b3] \r\n" \
480 : [x]"=&r"(_x) \
481 : [a]"r"(_a), \
482 [b0]"i"((_b) >> 24), \
483 [b1]"i"((_b) << 8 >> 24), \
484 [b2]"i"((_b) << 16 >> 24), \
485 [b3]"i"((_b) << 24 >> 24) \
486 ); \
487 _x == 0; \
488 })
489#elif defined (CPU_COLDFIRE)
490/* Coldfire can just load a 32 bit value at any offset but ASM is not the best way
491 to integrate this with the C code */
492#define CMP_3_CONST(a, b) \
493 (((*(uint32_t *)(a) >> 8) ^ ((uint32_t)(b) >> 8)) == 0)
494#define CMP_4_CONST(a, b) \
495 ((*(uint32_t *)(a) ^ (b)) == 0)
496#else
497/* Don't know what this is - use bytewise comparisons */
498#define CMP_3_CONST(a, b) \
499 (( ((a)[0] ^ (((b) >> 24) & 0xff)) | \
500 ((a)[1] ^ (((b) >> 16) & 0xff)) | \
501 ((a)[2] ^ (((b) >> 8) & 0xff)) ) == 0)
502#define CMP_4_CONST(a, b) \
503 (( ((a)[0] ^ (((b) >> 24) & 0xff)) | \
504 ((a)[1] ^ (((b) >> 16) & 0xff)) | \
505 ((a)[2] ^ (((b) >> 8) & 0xff)) | \
506 ((a)[3] ^ ((b) & 0xff)) ) == 0)
507#endif
508
509/* Codes for various header byte sequences - MSB represents lowest memory
510 address */
511#define PACKET_START_CODE_PREFIX 0x00000100ul
512#define END_CODE 0x000001b9ul
513#define PACK_START_CODE 0x000001baul
514#define SYSTEM_HEADER_START_CODE 0x000001bbul
515
516/* p = base pointer, b0 - b4 = byte offsets from p */
517/* We only care about the MS 32 bits of the 33 and so the ticks are 45kHz */
518#define TS_FROM_HEADER(p, b0, b1, b2, b3, b4) \
519 ((uint32_t)(((p)[b0] >> 1 << 29) | \
520 ((p)[b1] << 21) | \
521 ((p)[b2] >> 1 << 14) | \
522 ((p)[b3] << 6) | \
523 ((p)[b4] >> 2 )))
524
525/* This function synchronizes the mpeg stream. The function returns
526 true on error */
527bool sync_data_stream(uint8_t **p)
528{
529 for (;;)
530 {
531 while ( !CMP_4_CONST(*p, PACK_START_CODE) && (*p) < disk_buf_tail )
532 (*p)++;
533 if ( (*p) >= disk_buf_tail )
534 break;
535 uint8_t *p_save = (*p);
536 if ( ((*p)[4] & 0xc0) == 0x40 ) /* mpeg-2 */
537 (*p) += 14 + ((*p)[13] & 7);
538 else if ( ((*p)[4] & 0xf0) == 0x20 ) /* mpeg-1 */
539 (*p) += 12;
540 else
541 (*p) += 5;
542 if ( (*p) >= disk_buf_tail )
543 break;
544 if ( CMP_3_CONST(*p, PACKET_START_CODE_PREFIX) )
545 {
546 (*p) = p_save;
547 break;
548 }
549 else
550 (*p) = p_save+1;
551 } 196 }
552 197
553 if ( (*p) >= disk_buf_tail ) 198 /* Gently poll the video player for EOS and handle UI */
554 return true; 199 while (stream_status() != STREAM_STOPPED)
555 else
556 return false;
557}
558
559/* This function demuxes the streams and gives the next stream data
560 pointer. Type 0 is normal operation. Type 1 and 2 have been added
561 for rapid seeks into the data stream. Type 1 and 2 ignore the
562 video_sync_start state (a signal to yield for refilling the
563 buffer). Type 1 will append more data to the buffer tail (minumal
564 bufer size reads that are increased only as needed). */
565static int get_next_data( Stream* str, uint8_t type )
566{
567 uint8_t *p;
568 uint8_t *header;
569 int stream;
570
571 static int mpeg1_skip_table[16] =
572 { 0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
573
574 if ( (p=str->curr_packet_end) == NULL)
575 p = disk_buf_start;
576
577 while (1)
578 { 200 {
579 int length, bytes; 201 int button = rb->button_get_w_tmo(HZ/2);
580
581 /* Yield for buffer filling */
582 if ( (type == 0) && (str->buffer_remaining < 120*1024) && (file_remaining > 0) )
583 while ( (str->buffer_remaining < 512*1024) && (file_remaining > 0) )
584 rb->yield();
585
586 /* The packet start position (plus an arbitrary header length)
587 has exceeded the amount of data in the buffer */
588 if ( type == 1 && (p+50) >= disk_buf_tail )
589 {
590 DEBUGF("disk buffer overflow\n");
591 return 1;
592 }
593
594 /* are we at the end of file? */
595 {
596 size_t tmp_length;
597 if (p < str->prev_packet)
598 tmp_length = (disk_buf_end - str->prev_packet) +
599 (p - disk_buf_start);
600 else
601 tmp_length = (p - str->prev_packet);
602 if (0 == str->buffer_remaining-tmp_length-str->prev_packet_length)
603 {
604 str->curr_packet_end = str->curr_packet = NULL;
605 break;
606 }
607 }
608 202
609 /* wrap the disk buffer */ 203 switch (button)
610 if (p >= disk_buf_end)
611 p = disk_buf_start + (p - disk_buf_end);
612
613 /* wrap packet header if needed */
614 if ( (p+50) >= disk_buf_end )
615 rb->memcpy(disk_buf_end, disk_buf_start, 50);
616
617 /* Pack header, skip it */
618 if (CMP_4_CONST(p, PACK_START_CODE))
619 { 204 {
620 if ((p[4] & 0xc0) == 0x40) /* mpeg-2 */ 205 case BUTTON_NONE:
621 {
622 p += 14 + (p[13] & 7);
623 }
624 else if ((p[4] & 0xf0) == 0x20) /* mpeg-1 */
625 {
626 p += 12;
627 }
628 else
629 {
630 rb->splash( 30, "Weird Pack header!" );
631 p += 5;
632 }
633 }
634
635 /* System header, parse and skip it - four bytes */
636 if (CMP_4_CONST(p, SYSTEM_HEADER_START_CODE))
637 {
638 int header_length;
639
640 p += 4; /*skip start code*/
641 header_length = *p++ << 8;
642 header_length += *p++;
643
644 p += header_length;
645
646 if ( p >= disk_buf_end )
647 p = disk_buf_start + (p - disk_buf_end);
648 }
649
650 /* Packet header, parse it */
651 if (!CMP_3_CONST(p, PACKET_START_CODE_PREFIX))
652 {
653 /* Problem */
654 rb->splash( HZ*3, "missing packet start code prefix : %X%X at %lX",
655 *p, *(p+2), (long unsigned int)(p-disk_buf_start) );
656
657 /* not 64bit safe
658 DEBUGF("end diff: %X,%X,%X,%X,%X,%X\n",(int)str->curr_packet_end,
659 (int)audio_str.curr_packet_end,(int)video_str.curr_packet_end,
660 (int)disk_buf_start,(int)disk_buf_end,(int)disk_buf_tail);
661 */
662
663 str->curr_packet_end = str->curr_packet = NULL;
664 break;
665 }
666
667 /* We retrieve basic infos */
668 stream = p[3];
669 length = (p[4] << 8) | p[5];
670
671 if (stream != str->id)
672 {
673 /* End of stream ? */
674 if (stream == 0xB9)
675 {
676 str->curr_packet_end = str->curr_packet = NULL;
677 break;
678 }
679
680 /* It's not the packet we're looking for, skip it */
681 p += length + 6;
682 continue; 206 continue;
683 }
684
685 /* Ok, it's our packet */
686 str->curr_packet_end = p + length+6;
687 header = p;
688
689 if ((header[6] & 0xc0) == 0x80) /* mpeg2 */
690 {
691 length = 9 + header[8];
692
693 /* header points to the mpeg2 pes header */
694 if (header[7] & 0x80)
695 {
696 /* header has a pts */
697 uint32_t pts = TS_FROM_HEADER(header, 9, 10, 11, 12, 13);
698
699 if (stream >= 0xe0)
700 {
701 /* video stream - header may have a dts as well */
702 uint32_t dts = (header[7] & 0x40) == 0 ?
703 pts : TS_FROM_HEADER(header, 14, 15, 16, 17, 18);
704
705 mpeg2_tag_picture (mpeg2dec, pts, dts);
706 }
707 else
708 {
709 str->curr_pts = pts;
710 str->tagged = 1;
711 }
712 }
713 }
714 else /* mpeg1 */
715 {
716 int len_skip;
717 uint8_t * ptsbuf;
718
719 length = 7;
720
721 while (header[length - 1] == 0xff)
722 {
723 length++;
724 if (length > 23)
725 {
726 rb->splash( 30, "Too much stuffing" );
727 DEBUGF("Too much stuffing" );
728 break;
729 }
730 }
731
732 if ( (header[length - 1] & 0xc0) == 0x40 )
733 length += 2;
734
735 len_skip = length;
736 length += mpeg1_skip_table[header[length - 1] >> 4];
737
738 /* header points to the mpeg1 pes header */
739 ptsbuf = header + len_skip;
740
741 if ((ptsbuf[-1] & 0xe0) == 0x20)
742 {
743 /* header has a pts */
744 uint32_t pts = TS_FROM_HEADER(ptsbuf, -1, 0, 1, 2, 3);
745
746 if (stream >= 0xe0)
747 {
748 /* video stream - header may have a dts as well */
749 uint32_t dts = (ptsbuf[-1] & 0xf0) != 0x30 ?
750 pts : TS_FROM_HEADER(ptsbuf, 4, 5, 6, 7, 18);
751
752 mpeg2_tag_picture (mpeg2dec, pts, dts);
753 }
754 else
755 {
756 str->curr_pts = pts;
757 str->tagged = 1;
758 }
759 }
760 }
761
762 p += length;
763 bytes = 6 + (header[4] << 8) + header[5] - length;
764
765 if (bytes > 0)
766 {
767 str->curr_packet_end = p + bytes;
768
769 if (str->curr_packet != NULL)
770 {
771 lock_stream();
772
773 str->buffer_remaining -= str->prev_packet_length;
774 if (str->curr_packet < str->prev_packet)
775 str->prev_packet_length = (disk_buf_end - str->prev_packet) +
776 (str->curr_packet - disk_buf_start);
777 else
778 str->prev_packet_length = (str->curr_packet - str->prev_packet);
779
780 unlock_stream();
781
782 str->prev_packet = str->curr_packet;
783 }
784
785 str->curr_packet = p;
786
787 if (str->curr_packet_end > disk_buf_end)
788 rb->memcpy(disk_buf_end, disk_buf_start,
789 str->curr_packet_end - disk_buf_end );
790 }
791
792 break;
793 } /* end while */
794 return 0;
795}
796
797/* Our clock rate in ticks/second - this won't be a constant for long */
798#define CLOCK_RATE 44100
799
800/* For simple lowpass filtering of sync variables */
801#define AVERAGE(var, x, count) (((var) * (count-1) + (x)) / (count))
802/* Convert 45kHz PTS/DTS ticks to our clock ticks */
803#define TS_TO_TICKS(pts) ((uint64_t)CLOCK_RATE*(pts) / 45000)
804/* Convert 27MHz ticks to our clock ticks */
805#define TIME_TO_TICKS(stamp) ((uint64_t)CLOCK_RATE*(stamp) / 27000000)
806
807/** MPEG audio stream buffer */
808uint8_t* mpa_buffer NOCACHEBSS_ATTR;
809
810static bool init_mpabuf(void)
811{
812 mpa_buffer = mpeg_malloc(MPABUF_SIZE,-2);
813 return mpa_buffer != NULL;
814}
815
816#define PTS_QUEUE_LEN (1 << 5) /* 32 should be way more than sufficient -
817 if not, the case is handled */
818#define PTS_QUEUE_MASK (PTS_QUEUE_LEN-1)
819struct pts_queue_slot
820{
821 uint32_t pts; /* Time stamp for packet */
822 ssize_t size; /* Number of bytes left in packet */
823} pts_queue[PTS_QUEUE_LEN] __attribute__((aligned(16)));
824
825 /* This starts out wr == rd but will never be emptied to zero during
826 streaming again in order to support initializing the first packet's
827 pts value without a special case */
828static unsigned pts_queue_rd NOCACHEBSS_ATTR;
829static unsigned pts_queue_wr NOCACHEBSS_ATTR;
830
831/* Increments the queue head postion - should be used to preincrement */
832static bool pts_queue_add_head(void)
833{
834 if (pts_queue_wr - pts_queue_rd >= PTS_QUEUE_LEN-1)
835 return false;
836
837 pts_queue_wr++;
838 return true;
839}
840
841/* Increments the queue tail position - leaves one slot as current */
842static bool pts_queue_remove_tail(void)
843{
844 if (pts_queue_wr - pts_queue_rd <= 1u)
845 return false;
846
847 pts_queue_rd++;
848 return true;
849}
850
851/* Returns the "head" at the index just behind the write index */
852static struct pts_queue_slot * pts_queue_head(void)
853{
854 return &pts_queue[(pts_queue_wr - 1) & PTS_QUEUE_MASK];
855}
856
857/* Returns a pointer to the current tail */
858static struct pts_queue_slot * pts_queue_tail(void)
859{
860 return &pts_queue[pts_queue_rd & PTS_QUEUE_MASK];
861}
862
863/* Resets the pts queue - call when starting and seeking */
864static void pts_queue_reset(void)
865{
866 struct pts_queue_slot *pts;
867 pts_queue_rd = pts_queue_wr;
868 pts = pts_queue_tail();
869 pts->pts = 0;
870 pts->size = 0;
871}
872
873struct pcm_frame_header /* Header added to pcm data every time a decoded
874 mpa frame is sent out */
875{
876 uint32_t size; /* size of this frame - including header */
877 uint32_t time; /* timestamp for this frame - derived from PTS */
878 unsigned char data[]; /* open array of audio data */
879};
880
881#define PCMBUF_PLAY_ALL 1l /* Forces buffer to play back all data */
882#define PCMBUF_PLAY_NONE LONG_MAX /* Keeps buffer from playing any data */
883static volatile uint64_t pcmbuf_read IBSS_ATTR;
884static volatile uint64_t pcmbuf_written IBSS_ATTR;
885static volatile ssize_t pcmbuf_threshold IBSS_ATTR;
886static struct pcm_frame_header *pcm_buffer IBSS_ATTR;
887static struct pcm_frame_header *pcmbuf_end IBSS_ATTR;
888static struct pcm_frame_header * volatile pcmbuf_head IBSS_ATTR;
889static struct pcm_frame_header * volatile pcmbuf_tail IBSS_ATTR;
890
891static volatile uint32_t samplesplayed IBSS_ATTR; /* Our base clock */
892static volatile uint32_t samplestart IBSS_ATTR; /* Clock at playback start */
893static volatile int32_t sampleadjust IBSS_ATTR; /* Clock drift adjustment */
894
895static ssize_t pcmbuf_used(void)
896{
897 return (ssize_t)(pcmbuf_written - pcmbuf_read);
898}
899
900static bool init_pcmbuf(void)
901{
902 pcm_buffer = mpeg_malloc(PCMBUFFER_SIZE + PCMBUFFER_GUARD_SIZE, -2);
903
904 if (pcm_buffer == NULL)
905 return false;
906
907 pcmbuf_head = pcm_buffer;
908 pcmbuf_tail = pcm_buffer;
909 pcmbuf_end = SKIPBYTES(pcm_buffer, PCMBUFFER_SIZE);
910 pcmbuf_read = 0;
911 pcmbuf_written = 0;
912
913 return true;
914}
915
916/* Advance a PCM buffer pointer by size bytes circularly */
917static inline void pcm_advance_buffer(struct pcm_frame_header * volatile *p,
918 size_t size)
919{
920 *p = SKIPBYTES(*p, size);
921 if (*p >= pcmbuf_end)
922 *p = pcm_buffer;
923}
924
925static void get_more(unsigned char** start, size_t* size)
926{
927 /* 25ms @ 44.1kHz */
928 static unsigned char silence[4412] __attribute__((aligned (4))) = { 0 };
929 size_t sz;
930
931 if (pcmbuf_used() >= pcmbuf_threshold)
932 {
933 uint32_t time = pcmbuf_tail->time;
934 sz = pcmbuf_tail->size;
935
936 *start = (unsigned char *)pcmbuf_tail->data;
937
938 pcm_advance_buffer(&pcmbuf_tail, sz);
939
940 pcmbuf_read += sz;
941
942 sz -= sizeof (*pcmbuf_tail);
943
944 *size = sz;
945
946 /* Drift the clock towards the audio timestamp values */
947 sampleadjust = AVERAGE(sampleadjust, (int32_t)(time - samplesplayed), 8);
948
949 /* Update master clock */
950 samplesplayed += sz >> 2;
951 return;
952 }
953
954 /* Keep clock going at all times */
955 sz = sizeof (silence);
956 *start = silence;
957 *size = sz;
958
959 samplesplayed += sz >> 2;
960
961 if (pcmbuf_read > pcmbuf_written)
962 pcmbuf_read = pcmbuf_written;
963}
964
965/* Flushes the buffer - clock keeps counting */
966static void pcm_playback_flush(void)
967{
968 bool was_playing = rb->pcm_is_playing();
969
970 if (was_playing)
971 rb->pcm_play_stop();
972
973 pcmbuf_read = 0;
974 pcmbuf_written = 0;
975 pcmbuf_head = pcmbuf_tail;
976
977 if (was_playing)
978 rb->pcm_play_data(get_more, NULL, 0);
979}
980
981/* Seek the reference clock to the specified time - next audio data ready to
982 go to DMA should be on the buffer with the same time index or else the PCM
983 buffer should be empty */
984static void pcm_playback_seek_time(uint32_t time)
985{
986 bool was_playing = rb->pcm_is_playing();
987
988 if (was_playing)
989 rb->pcm_play_stop();
990
991 samplesplayed = time;
992 samplestart = time;
993 sampleadjust = 0;
994
995 if (was_playing)
996 rb->pcm_play_data(get_more, NULL, 0);
997}
998
999/* Start pcm playback with the reference clock set to the specified time */
1000static void pcm_playback_play(uint32_t time)
1001{
1002 pcm_playback_seek_time(time);
1003
1004 if (!rb->pcm_is_playing())
1005 rb->pcm_play_data(get_more, NULL, 0);
1006}
1007
1008/* Pauses playback - and the clock */
1009static void pcm_playback_play_pause(bool play)
1010{
1011 rb->pcm_play_pause(play);
1012}
1013
1014/* Stops all playback and resets the clock */
1015static void pcm_playback_stop(void)
1016{
1017 if (rb->pcm_is_playing())
1018 rb->pcm_play_stop();
1019
1020 pcm_playback_flush();
1021
1022 sampleadjust =
1023 samplestart =
1024 samplesplayed = 0;
1025}
1026
1027static uint32_t get_stream_time(void)
1028{
1029 return samplesplayed + sampleadjust - (rb->pcm_get_bytes_waiting() >> 2);
1030}
1031
1032static uint32_t get_playback_time(void)
1033{
1034 return samplesplayed + sampleadjust -
1035 samplestart - (rb->pcm_get_bytes_waiting() >> 2);
1036}
1037
1038static inline int32_t clip_sample(int32_t sample)
1039{
1040 if ((int16_t)sample != sample)
1041 sample = 0x7fff ^ (sample >> 31);
1042
1043 return sample;
1044}
1045 207
1046static int button_loop(void)
1047{
1048 int result;
1049 int vol, minvol, maxvol;
1050 int button;
1051
1052 if (video_sync_start==1) {
1053
1054 if (str_have_msg(&audio_str))
1055 {
1056 struct queue_event ev;
1057 str_get_msg(&audio_str, &ev);
1058
1059 if (ev.id == STREAM_QUIT)
1060 {
1061 audio_str.status = STREAM_STOPPED;
1062 goto quit;
1063 }
1064 else
1065 {
1066 str_reply_msg(&audio_str, 0);
1067 }
1068 }
1069
1070 button = rb->button_get(false);
1071
1072 switch (button)
1073 {
1074 case MPEG_VOLUP: 208 case MPEG_VOLUP:
1075 case MPEG_VOLUP|BUTTON_REPEAT: 209 case MPEG_VOLUP|BUTTON_REPEAT:
1076#ifdef MPEG_VOLUP2 210#ifdef MPEG_VOLUP2
1077 case MPEG_VOLUP2: 211 case MPEG_VOLUP2:
1078 case MPEG_VOLUP2|BUTTON_REPEAT: 212 case MPEG_VOLUP2|BUTTON_REPEAT:
1079#endif 213#endif
1080 vol = rb->global_settings->volume; 214 {
1081 maxvol = rb->sound_max(SOUND_VOLUME); 215 int vol = rb->global_settings->volume;
216 int maxvol = rb->sound_max(SOUND_VOLUME);
1082 217
1083 if (vol < maxvol) { 218 if (vol < maxvol) {
1084 vol++; 219 vol++;
@@ -1086,6 +221,7 @@ static int button_loop(void)
1086 rb->global_settings->volume = vol; 221 rb->global_settings->volume = vol;
1087 } 222 }
1088 break; 223 break;
224 } /* MPEG_VOLUP*: */
1089 225
1090 case MPEG_VOLDOWN: 226 case MPEG_VOLDOWN:
1091 case MPEG_VOLDOWN|BUTTON_REPEAT: 227 case MPEG_VOLDOWN|BUTTON_REPEAT:
@@ -1093,8 +229,9 @@ static int button_loop(void)
1093 case MPEG_VOLDOWN2: 229 case MPEG_VOLDOWN2:
1094 case MPEG_VOLDOWN2|BUTTON_REPEAT: 230 case MPEG_VOLDOWN2|BUTTON_REPEAT:
1095#endif 231#endif
1096 vol = rb->global_settings->volume; 232 {
1097 minvol = rb->sound_min(SOUND_VOLUME); 233 int vol = rb->global_settings->volume;
234 int minvol = rb->sound_min(SOUND_VOLUME);
1098 235
1099 if (vol > minvol) { 236 if (vol > minvol) {
1100 vol--; 237 vol--;
@@ -1102,1225 +239,108 @@ static int button_loop(void)
1102 rb->global_settings->volume = vol; 239 rb->global_settings->volume = vol;
1103 } 240 }
1104 break; 241 break;
242 } /* MPEG_VOLDOWN*: */
1105 243
1106 case MPEG_MENU: 244 case MPEG_MENU:
1107 pcm_playback_play_pause(false); 245 {
1108 audio_str.status = STREAM_PAUSED; 246 int state = stream_pause(); /* save previous state */
1109 str_send_msg(&video_str, STREAM_PAUSE, 0); 247 int result;
1110#ifndef HAVE_LCD_COLOR
1111 gray_show(false);
1112#endif
1113 result = mpeg_menu();
1114 count_start = get_playback_time();
1115 num_drawn = 0;
1116 248
1117#ifndef HAVE_LCD_COLOR 249 /* Hide video output */
1118 gray_show(true); 250 stream_show_vo(false);
1119#endif 251 backlight_use_settings(rb);
252
253 result = mpeg_menu();
1120 254
1121 /* The menu can change the font, so restore */ 255 /* The menu can change the font, so restore */
1122 rb->lcd_setfont(FONT_SYSFIXED); 256 rb->lcd_setfont(FONT_SYSFIXED);
1123 257
1124 switch (result) 258 switch (result)
1125 { 259 {
1126 case MPEG_MENU_QUIT: 260 case MPEG_MENU_QUIT:
1127 settings.resume_time = (int)(get_stream_time()/CLOCK_RATE/ 261 stream_stop();
1128 30-start_pts_time); 262 break;
1129 str_send_msg(&video_str, STREAM_QUIT, 0); 263 default:
1130 audio_str.status = STREAM_STOPPED; 264 /* If not stopped, show video again */
1131 break; 265 if (state != STREAM_STOPPED)
1132 default: 266 stream_show_vo(true);
1133 audio_str.status = STREAM_PLAYING; 267
1134 str_send_msg(&video_str, STREAM_PLAY, 0); 268 /* If stream was playing, restart it */
1135 pcm_playback_play_pause(true); 269 if (state == STREAM_PLAYING) {
1136 break; 270 backlight_force_on(rb);
271 stream_resume();
272 }
273 break;
1137 } 274 }
1138 break; 275 break;
276 } /* MPEG_MENU: */
1139 277
1140 case MPEG_STOP: 278 case MPEG_STOP:
1141 settings.resume_time = (int)(get_stream_time()/CLOCK_RATE/ 279 {
1142 30-start_pts_time); 280 stream_stop();
1143 str_send_msg(&video_str, STREAM_QUIT, 0);
1144 audio_str.status = STREAM_STOPPED;
1145 break; 281 break;
282 } /* MPEG_STOP: */
1146 283
1147 case MPEG_PAUSE: 284 case MPEG_PAUSE:
1148#ifdef MPEG_PAUSE2 285#ifdef MPEG_PAUSE2
1149 case MPEG_PAUSE2: 286 case MPEG_PAUSE2:
1150#endif 287#endif
1151 settings.resume_time = (int)(get_stream_time()/CLOCK_RATE/
1152 30-start_pts_time);
1153 save_settings();
1154 str_send_msg(&video_str, STREAM_PAUSE, 0);
1155 audio_str.status = STREAM_PAUSED;
1156 pcm_playback_play_pause(false);
1157
1158 button = BUTTON_NONE;
1159#ifdef HAVE_ADJUSTABLE_CPU_FREQ
1160 rb->cpu_boost(false);
1161#endif
1162 do {
1163 button = rb->button_get(true);
1164 if (button == MPEG_STOP) {
1165 str_send_msg(&video_str, STREAM_QUIT, 0);
1166 audio_str.status = STREAM_STOPPED;
1167 goto quit;
1168 }
1169#ifndef MPEG_PAUSE2
1170 } while (button != MPEG_PAUSE);
1171#else
1172 } while (button != MPEG_PAUSE && button != MPEG_PAUSE2);
1173#endif
1174
1175 str_send_msg(&video_str, STREAM_PLAY, 0);
1176 audio_str.status = STREAM_PLAYING;
1177 pcm_playback_play_pause(true);
1178#ifdef HAVE_ADJUSTABLE_CPU_FREQ
1179 rb->cpu_boost(true);
1180#endif
1181 break;
1182
1183 default:
1184 if(rb->default_event_handler(button) == SYS_USB_CONNECTED) {
1185 str_send_msg(&video_str, STREAM_QUIT, 0);
1186 audio_str.status = STREAM_STOPPED;
1187 }
1188 }
1189 }
1190quit:
1191 return audio_str.status;
1192}
1193
1194static void audio_thread(void)
1195{
1196 uint8_t *mpabuf = mpa_buffer;
1197 ssize_t mpabuf_used = 0;
1198 int mad_errors = 0; /* A count of the errors in each frame */
1199 struct pts_queue_slot *pts;
1200
1201 /* We need this here to init the EMAC for Coldfire targets */
1202 mad_synth_init(&synth);
1203
1204 /* Init pts queue */
1205 pts_queue_reset();
1206 pts = pts_queue_tail();
1207
1208 /* Keep buffer from playing */
1209 pcmbuf_threshold = PCMBUF_PLAY_NONE;
1210
1211 /* Start clock */
1212 pcm_playback_play(0);
1213
1214 /* Get first packet */
1215 get_next_data(&audio_str, 0 );
1216
1217 /* skip audio packets here */
1218 while (audio_sync_start==0)
1219 {
1220 audio_str.status = STREAM_PLAYING;
1221 rb->yield();
1222 }
1223
1224 if (audio_sync_time>10000)
1225 {
1226 while (TS_TO_TICKS(audio_str.curr_pts) < audio_sync_time - 10000)
1227 {
1228 get_next_data(&audio_str, 0 );
1229 rb->priority_yield();
1230 }
1231 }
1232
1233 if (audio_str.curr_packet == NULL)
1234 goto done;
1235
1236 /* This is the decoding loop. */
1237 while (1)
1238 {
1239 int mad_stat;
1240 size_t len;
1241
1242 if (button_loop() == STREAM_STOPPED)
1243 goto audio_thread_quit;
1244
1245 if (pts->size <= 0)
1246 { 288 {
1247 /* Carry any overshoot to the next size since we're technically 289 if (stream_status() == STREAM_PLAYING) {
1248 -pts->size bytes into it already. If size is negative an audio 290 /* Playing => Paused */
1249 frame was split accross packets. Old has to be saved before 291 stream_pause();
1250 moving the tail. */ 292 backlight_use_settings(rb);
1251 if (pts_queue_remove_tail())
1252 {
1253 struct pts_queue_slot *old = pts;
1254 pts = pts_queue_tail();
1255 pts->size += old->size;
1256 old->size = 0;
1257 } 293 }
1258 } 294 else if (stream_status() == STREAM_PAUSED) {
1259 295 /* Paused => Playing */
1260 /** Buffering **/ 296 backlight_force_on(rb);
1261 if (mpabuf_used >= MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD) 297 stream_resume();
1262 {
1263 /* Above low watermark - do nothing */
1264 }
1265 else if (audio_str.curr_packet != NULL)
1266 {
1267 do
1268 {
1269 /* Get data from next audio packet */
1270 len = audio_str.curr_packet_end - audio_str.curr_packet;
1271
1272 if (audio_str.tagged)
1273 {
1274 struct pts_queue_slot *stamp = pts;
1275
1276 if (pts_queue_add_head())
1277 {
1278 stamp = pts_queue_head();
1279 stamp->pts = TS_TO_TICKS(audio_str.curr_pts);
1280 /* pts->size should have been zeroed when slot was
1281 freed */
1282 }
1283 /* else queue full - just count up from the last to make
1284 it look like more data in the same packet */
1285 stamp->size += len;
1286 audio_str.tagged = 0;
1287 }
1288 else
1289 {
1290 /* Add to the one just behind the head - this may be the
1291 tail or the previouly added head - whether or not we'll
1292 ever reach this is quite in question since audio always
1293 seems to have every packet timestamped */
1294 pts_queue_head()->size += len;
1295 }
1296
1297 /* Slide any remainder over to beginning - avoid function
1298 call overhead if no data remaining as well */
1299 if (mpabuf > mpa_buffer && mpabuf_used > 0)
1300 rb->memmove(mpa_buffer, mpabuf, mpabuf_used);
1301
1302 /* Splice this packet onto any remainder */
1303 rb->memcpy(mpa_buffer + mpabuf_used, audio_str.curr_packet,
1304 len);
1305
1306 mpabuf_used += len;
1307 mpabuf = mpa_buffer;
1308
1309 /* Get data from next audio packet */
1310 get_next_data(&audio_str, 0 );
1311 }
1312 while (audio_str.curr_packet != NULL &&
1313 mpabuf_used < MPA_MAX_FRAME_SIZE + MAD_BUFFER_GUARD);
1314 }
1315 else if (mpabuf_used <= 0)
1316 {
1317 /* Used up remainder of mpa buffer so quit */
1318 break;
1319 }
1320
1321 /** Decoding **/
1322 mad_stream_buffer(&stream, mpabuf, mpabuf_used);
1323
1324 mad_stat = mad_frame_decode(&frame, &stream);
1325
1326 if (stream.next_frame == NULL)
1327 {
1328 /* What to do here? (This really is fatal) */
1329 DEBUGF("/* What to do here? */\n");
1330 break;
1331 }
1332
1333 /* Next mad stream buffer is the next frame postion */
1334 mpabuf = (uint8_t *)stream.next_frame;
1335
1336 /* Adjust sizes by the frame size */
1337 len = stream.next_frame - stream.this_frame;
1338 mpabuf_used -= len;
1339 pts->size -= len;
1340
1341 if (mad_stat != 0)
1342 {
1343 if (stream.error == MAD_FLAG_INCOMPLETE
1344 || stream.error == MAD_ERROR_BUFLEN)
1345 {
1346 /* This makes the codec support partially corrupted files */
1347 if (++mad_errors > 30)
1348 break;
1349
1350 stream.error = 0;
1351 rb->priority_yield();
1352 continue;
1353 }
1354 else if (MAD_RECOVERABLE(stream.error))
1355 {
1356 stream.error = 0;
1357 rb->priority_yield();
1358 continue;
1359 }
1360 else
1361 {
1362 /* Some other unrecoverable error */
1363 DEBUGF("Unrecoverable error\n");
1364 }
1365
1366 break;
1367 }
1368
1369 mad_errors = 0; /* Clear errors */
1370
1371 /* Generate the pcm samples */
1372 mad_synth_frame(&synth, &frame);
1373
1374 /** Output **/
1375
1376 /* TODO: Output through core dsp. We'll still use our own PCM buffer
1377 since the core pcm buffer has no timestamping or clock facilities */
1378
1379 /* Add a frame of audio to the pcm buffer. Maximum is 1152 samples. */
1380 if (synth.pcm.length > 0)
1381 {
1382 int16_t *audio_data = (int16_t *)pcmbuf_head->data;
1383 size_t size = sizeof (*pcmbuf_head) + synth.pcm.length*4;
1384 size_t wait_for = size + 32*1024;
1385
1386 /* Leave at least 32KB free (this will be the currently
1387 playing chunk) */
1388 while (pcmbuf_used() + wait_for > PCMBUFFER_SIZE)
1389 {
1390 if (str_have_msg(&audio_str))
1391 {
1392 struct queue_event ev;
1393 str_look_msg(&audio_str, &ev);
1394
1395 if (ev.id == STREAM_QUIT)
1396 goto audio_thread_quit;
1397 }
1398
1399 rb->priority_yield();
1400 }
1401
1402 if (video_sync_start == 0 &&
1403 pts->pts+(uint32_t)synth.pcm.length<audio_sync_time) {
1404 synth.pcm.length = 0;
1405 size = 0;
1406 rb->yield();
1407 }
1408
1409 /* TODO: This part will be replaced with dsp calls soon */
1410 if (MAD_NCHANNELS(&frame.header) == 2)
1411 {
1412 int32_t *left = &synth.pcm.samples[0][0];
1413 int32_t *right = &synth.pcm.samples[1][0];
1414 int i = synth.pcm.length;
1415
1416 do
1417 {
1418 /* libmad outputs s3.28 */
1419 *audio_data++ = clip_sample(*left++ >> 13);
1420 *audio_data++ = clip_sample(*right++ >> 13);
1421 }
1422 while (--i > 0);
1423 }
1424 else /* mono */
1425 {
1426 int32_t *mono = &synth.pcm.samples[0][0];
1427 int i = synth.pcm.length;
1428
1429 do
1430 {
1431 int32_t s = clip_sample(*mono++ >> 13);
1432 *audio_data++ = s;
1433 *audio_data++ = s;
1434 }
1435 while (--i > 0);
1436 }
1437 /**/
1438
1439 pcmbuf_head->time = pts->pts;
1440 pcmbuf_head->size = size;
1441
1442 /* As long as we're on this timestamp, the time is just incremented
1443 by the number of samples */
1444 pts->pts += synth.pcm.length;
1445
1446 pcm_advance_buffer(&pcmbuf_head, size);
1447
1448 if (pcmbuf_threshold != PCMBUF_PLAY_ALL && pcmbuf_used() >= 64*1024)
1449 {
1450 /* We've reached our size treshold so start playing back the
1451 audio in the buffer and set the buffer to play all data */
1452 audio_str.status = STREAM_PLAYING;
1453 pcmbuf_threshold = PCMBUF_PLAY_ALL;
1454 pcm_playback_seek_time(pcmbuf_tail->time);
1455 video_sync_start = 1;
1456 }
1457
1458 /* Make this data available to DMA */
1459 pcmbuf_written += size;
1460 }
1461
1462 rb->yield();
1463 } /* end decoding loop */
1464
1465done:
1466 if (audio_str.status == STREAM_STOPPED)
1467 goto audio_thread_quit;
1468
1469 /* Force any residue to play if audio ended before reaching the
1470 threshold */
1471 if (pcmbuf_threshold != PCMBUF_PLAY_ALL && pcmbuf_used() > 0)
1472 {
1473 pcm_playback_play(pcmbuf_tail->time);
1474 pcmbuf_threshold = PCMBUF_PLAY_ALL;
1475 }
1476
1477 if (rb->pcm_is_playing() && !rb->pcm_is_paused())
1478 {
1479 /* Wait for audio to finish */
1480 while (pcmbuf_used() > 0)
1481 {
1482 if (button_loop() == STREAM_STOPPED)
1483 goto audio_thread_quit;
1484 rb->sleep(HZ/10);
1485 }
1486 }
1487
1488 audio_str.status = STREAM_DONE;
1489
1490 /* Process events until finished */
1491 while (button_loop() != STREAM_STOPPED)
1492 rb->sleep(HZ/4);
1493
1494audio_thread_quit:
1495 pcm_playback_stop();
1496
1497 audio_str.status = STREAM_TERMINATED;
1498}
1499
1500/* End of libmad stuff */
1501
1502/* The audio stack is stolen from the core codec thread (but not in uisim) */
1503#define AUDIO_STACKSIZE (9*1024)
1504uint32_t* audio_stack;
1505
1506#ifndef SIMULATOR
1507static uint32_t codec_stack_copy[AUDIO_STACKSIZE / sizeof(uint32_t)];
1508#endif
1509
1510/* TODO: Check if 4KB is appropriate - it works for my test streams,
1511 so maybe we can reduce it. */
1512#define VIDEO_STACKSIZE (4*1024)
1513static uint32_t video_stack[VIDEO_STACKSIZE / sizeof(uint32_t)] IBSS_ATTR;
1514
1515static void video_thread(void)
1516{
1517 struct queue_event ev;
1518 const mpeg2_info_t * info;
1519 mpeg2_state_t state;
1520 char str[80];
1521 uint32_t curr_time = 0;
1522 uint32_t period = 0; /* Frame period in clock ticks */
1523 uint32_t eta_audio = UINT_MAX, eta_video = 0;
1524 int32_t eta_early = 0, eta_late = 0;
1525 int frame_drop_level = 0;
1526 int skip_level = 0;
1527 int num_skipped = 0;
1528 /* Used to decide when to display FPS */
1529 unsigned long last_showfps = *rb->current_tick - HZ;
1530 /* Used to decide whether or not to force a frame update */
1531 unsigned long last_render = last_showfps;
1532
1533 mpeg2dec = mpeg2_init();
1534 if (mpeg2dec == NULL)
1535 {
1536 rb->splash(0, "mpeg2_init failed");
1537 /* Commit suicide */
1538 video_str.status = STREAM_TERMINATED;
1539 return;
1540 }
1541
1542 /* Clear the display - this is mainly just to indicate that the
1543 video thread has started successfully. */
1544 if (!video_thumb_print)
1545 {
1546 rb->lcd_clear_display();
1547 rb->lcd_update();
1548 }
1549
1550 /* Request the first packet data */
1551 get_next_data( &video_str, 0 );
1552
1553 if (video_str.curr_packet == NULL)
1554 goto video_thread_quit;
1555
1556 mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end);
1557 total_offset += video_str.curr_packet_end - video_str.curr_packet;
1558
1559 info = mpeg2_info (mpeg2dec);
1560
1561 while (1)
1562 {
1563 /* quickly check mailbox first */
1564 if (video_thumb_print)
1565 {
1566 if (video_str.status == STREAM_STOPPED)
1567 break;
1568 }
1569 else if (str_have_msg(&video_str))
1570 {
1571 while (1)
1572 {
1573 str_get_msg(&video_str, &ev);
1574
1575 switch (ev.id)
1576 {
1577 case STREAM_QUIT:
1578 video_str.status = STREAM_STOPPED;
1579 goto video_thread_quit;
1580 case STREAM_PAUSE:
1581 #if NUM_CORES > 1
1582 flush_icache();
1583 #endif
1584 video_str.status = STREAM_PAUSED;
1585 str_reply_msg(&video_str, 1);
1586 continue;
1587 }
1588
1589 break;
1590 } 298 }
1591 299
1592 video_str.status = STREAM_PLAYING;
1593 str_reply_msg(&video_str, 1);
1594 }
1595
1596 state = mpeg2_parse (mpeg2dec);
1597 rb->yield();
1598
1599 /* Prevent idle poweroff */
1600 rb->reset_poweroff_timer();
1601
1602 switch (state)
1603 {
1604 case STATE_BUFFER:
1605 /* Request next packet data */
1606 get_next_data( &video_str, 0 );
1607
1608 mpeg2_buffer (mpeg2dec, video_str.curr_packet, video_str.curr_packet_end);
1609 total_offset += video_str.curr_packet_end - video_str.curr_packet;
1610 info = mpeg2_info (mpeg2dec);
1611
1612 if (video_str.curr_packet == NULL)
1613 {
1614 /* No more data. */
1615 goto video_thread_quit;
1616 }
1617 continue;
1618
1619 case STATE_SEQUENCE:
1620 /* New GOP, inform output of any changes */
1621 vo_setup(info->sequence);
1622 break; 300 break;
1623 301 } /* MPEG_PAUSE*: */
1624 case STATE_PICTURE: 302
1625 { 303 case SYS_POWEROFF:
1626 int skip = 0; /* Assume no skip */ 304 case SYS_USB_CONNECTED:
1627 305 /* Stop and get the resume time before closing the file early */
1628 if (frame_drop_level >= 1 || skip_level > 0) 306 stream_stop();
1629 { 307 settings.resume_time = stream_get_resume_time();
1630 /* A frame will be dropped in the decoder */ 308 stream_close();
1631 309 ret = false;
1632 /* Frame type: I/P/B/D */ 310 /* Fall-through */
1633 int type = info->current_picture->flags & PIC_MASK_CODING_TYPE;
1634
1635 switch (type)
1636 {
1637 case PIC_FLAG_CODING_TYPE_I:
1638 case PIC_FLAG_CODING_TYPE_D:
1639 /* Level 5: Things are extremely late and all frames will be
1640 dropped until the next key frame */
1641 if (frame_drop_level >= 1)
1642 frame_drop_level = 0; /* Key frame - reset drop level */
1643 if (skip_level >= 5)
1644 {
1645 frame_drop_level = 1;
1646 skip_level = 0; /* reset */
1647 }
1648 break;
1649 case PIC_FLAG_CODING_TYPE_P:
1650 /* Level 4: Things are very late and all frames will be
1651 dropped until the next key frame */
1652 if (skip_level >= 4)
1653 {
1654 frame_drop_level = 1;
1655 skip_level = 0; /* reset */
1656 }
1657 break;
1658 case PIC_FLAG_CODING_TYPE_B:
1659 /* We want to drop something, so this B frame won't even
1660 be decoded. Drawing can happen on the next frame if so
1661 desired. Bring the level down as skips are done. */
1662 skip = 1;
1663 if (skip_level > 0)
1664 skip_level--;
1665 }
1666
1667 skip |= frame_drop_level;
1668 }
1669
1670 mpeg2_skip(mpeg2dec, skip);
1671 break;
1672 }
1673
1674 case STATE_SLICE:
1675 case STATE_END:
1676 case STATE_INVALID_END:
1677 {
1678 int32_t offset; /* Tick adjustment to keep sync */
1679
1680 /* draw current picture */
1681 if (!info->display_fbuf)
1682 break;
1683
1684 /* No limiting => no dropping - draw this frame */
1685 if (!settings.limitfps && (video_thumb_print == 0))
1686 {
1687 audio_sync_start = 1;
1688 video_sync_start = 1;
1689 goto picture_draw;
1690 }
1691
1692 /* Get presentation times in audio samples - quite accurate
1693 enough - add previous frame duration if not stamped */
1694 curr_time = (info->display_picture->flags & PIC_FLAG_TAGS) ?
1695 TS_TO_TICKS(info->display_picture->tag) : (curr_time + period);
1696
1697 period = TIME_TO_TICKS(info->sequence->frame_period);
1698
1699 if ( (video_thumb_print == 1 || video_sync_start == 0) &&
1700 ((int)(info->current_picture->flags & PIC_MASK_CODING_TYPE)
1701 == PIC_FLAG_CODING_TYPE_B))
1702 break;
1703
1704 eta_video = curr_time;
1705
1706 audio_sync_time = eta_video;
1707 audio_sync_start = 1;
1708
1709 while (video_sync_start == 0)
1710 rb->yield();
1711
1712 eta_audio = get_stream_time();
1713
1714 /* How early/late are we? > 0 = late, < 0 early */
1715 offset = eta_audio - eta_video;
1716
1717 if (!settings.skipframes)
1718 {
1719 /* Make no effort to determine whether this frame should be
1720 drawn or not since no action can be taken to correct the
1721 situation. We'll just wait if we're early and correct for
1722 lateness as much as possible. */
1723 if (offset < 0)
1724 offset = 0;
1725
1726 eta_late = AVERAGE(eta_late, offset, 4);
1727 offset = eta_late;
1728
1729 if ((uint32_t)offset > eta_video)
1730 offset = eta_video;
1731
1732 eta_video -= offset;
1733 goto picture_wait;
1734 }
1735
1736 /** Possibly skip this frame **/
1737
1738 /* Frameskipping has the following order of preference:
1739 *
1740 * Frame Type Who Notes/Rationale
1741 * B decoder arbitrarily drop - no decode or draw
1742 * Any renderer arbitrarily drop - will be I/D/P
1743 * P decoder must wait for I/D-frame - choppy
1744 * I/D decoder must wait for I/D-frame - choppy
1745 *
1746 * If a frame can be drawn and it has been at least 1/2 second,
1747 * the image will be updated no matter how late it is just to
1748 * avoid looking stuck.
1749 */
1750
1751 /* If we're late, set the eta to play the frame early so
1752 we may catch up. If early, especially because of a drop,
1753 mitigate a "snap" by moving back gradually. */
1754 if (offset >= 0) /* late or on time */
1755 {
1756 eta_early = 0; /* Not early now :( */
1757
1758 eta_late = AVERAGE(eta_late, offset, 4);
1759 offset = eta_late;
1760
1761 if ((uint32_t)offset > eta_video)
1762 offset = eta_video;
1763
1764 eta_video -= offset;
1765 }
1766 else
1767 {
1768 eta_late = 0; /* Not late now :) */
1769
1770 if (offset > eta_early)
1771 {
1772 /* Just dropped a frame and we're now early or we're
1773 coming back from being early */
1774 eta_early = offset;
1775 if ((uint32_t)-offset > eta_video)
1776 offset = -eta_video;
1777
1778 eta_video += offset;
1779 }
1780 else
1781 {
1782 /* Just early with an offset, do exponential drift back */
1783 if (eta_early != 0)
1784 {
1785 eta_early = AVERAGE(eta_early, 0, 8);
1786 eta_video = ((uint32_t)-eta_early > eta_video) ?
1787 0 : (eta_video + eta_early);
1788 }
1789
1790 offset = eta_early;
1791 }
1792 }
1793
1794 if (info->display_picture->flags & PIC_FLAG_SKIP)
1795 {
1796 /* This frame was set to skip so skip it after having updated
1797 timing information */
1798 num_skipped++;
1799 eta_early = INT32_MIN;
1800 goto picture_skip;
1801 }
1802
1803 if (skip_level == 3 && TIME_BEFORE(*rb->current_tick, last_render + HZ/2))
1804 {
1805 /* Render drop was set previously but nothing was dropped in the
1806 decoder or it's been to long since drawing the last frame. */
1807 skip_level = 0;
1808 num_skipped++;
1809 eta_early = INT32_MIN;
1810 goto picture_skip;
1811 }
1812
1813 /* At this point a frame _will_ be drawn - a skip may happen on
1814 the next however */
1815 skip_level = 0;
1816
1817 if (offset > CLOCK_RATE*110/1000)
1818 {
1819 /* Decide which skip level is needed in order to catch up */
1820
1821 /* TODO: Calculate this rather than if...else - this is rather
1822 exponential though */
1823 if (offset > CLOCK_RATE*367/1000)
1824 skip_level = 5; /* Decoder skip: I/D */
1825 if (offset > CLOCK_RATE*233/1000)
1826 skip_level = 4; /* Decoder skip: P */
1827 else if (offset > CLOCK_RATE*167/1000)
1828 skip_level = 3; /* Render skip */
1829 else if (offset > CLOCK_RATE*133/1000)
1830 skip_level = 2; /* Decoder skip: B */
1831 else
1832 skip_level = 1; /* Decoder skip: B */
1833 }
1834
1835 picture_wait:
1836 /* Wait until audio catches up */
1837 if (video_thumb_print)
1838 video_str.status = STREAM_STOPPED;
1839 else
1840 while (eta_video > eta_audio)
1841 {
1842 rb->priority_yield();
1843
1844 /* Make sure not to get stuck waiting here forever */
1845 if (str_have_msg(&video_str))
1846 {
1847 str_look_msg(&video_str, &ev);
1848
1849 /* If not to play, process up top */
1850 if (ev.id != STREAM_PLAY)
1851 goto rendering_finished;
1852
1853 /* Told to play but already playing */
1854 str_get_msg(&video_str, &ev);
1855 str_reply_msg(&video_str, 1);
1856 }
1857
1858 eta_audio = get_stream_time();
1859 }
1860
1861 picture_draw:
1862 /* Record last frame time */
1863 last_render = *rb->current_tick;
1864
1865 if (video_thumb_print)
1866 vo_draw_frame_thumb(info->display_fbuf->buf);
1867 else
1868 vo_draw_frame(info->display_fbuf->buf);
1869
1870 num_drawn++;
1871
1872 picture_skip:
1873 if (!settings.showfps)
1874 break;
1875
1876 /* Calculate and display fps */
1877 if (TIME_AFTER(*rb->current_tick, last_showfps + HZ))
1878 {
1879 uint32_t clock_ticks = get_playback_time() - count_start;
1880 int fps = 0;
1881
1882 if (clock_ticks != 0)
1883 fps = num_drawn*CLOCK_RATE*10ll / clock_ticks;
1884
1885 rb->snprintf(str, sizeof(str), "%d.%d %d %d ",
1886 fps / 10, fps % 10, num_skipped,
1887 info->display_picture->temporal_reference);
1888 rb->lcd_putsxy(0, 0, str);
1889 rb->lcd_update_rect(0, 0, LCD_WIDTH, 8);
1890
1891 last_showfps = *rb->current_tick;
1892 }
1893 break;
1894 }
1895
1896 default: 311 default:
312 rb->default_event_handler(button);
1897 break; 313 break;
1898 } 314 }
1899 rendering_finished:
1900 315
1901 rb->yield(); 316 rb->yield();
1902 } 317 } /* end while */
1903
1904 video_thread_quit:
1905 /* if video ends before time sync'd,
1906 besure the audio thread is closed */
1907 if (video_sync_start == 0)
1908 {
1909 audio_str.status = STREAM_STOPPED;
1910 audio_sync_start = 1;
1911 }
1912
1913 #if NUM_CORES > 1
1914 flush_icache();
1915 #endif
1916
1917 mpeg2_close (mpeg2dec);
1918
1919 /* Commit suicide */
1920 video_str.status = STREAM_TERMINATED;
1921}
1922
1923void initialize_stream( Stream *str, uint8_t *buffer_start, size_t disk_buf_len, int id )
1924{
1925 str->curr_packet_end = str->curr_packet = NULL;
1926 str->prev_packet_length = 0;
1927 str->prev_packet = str->curr_packet_end = buffer_start;
1928 str->buffer_remaining = disk_buf_len;
1929 str->id = id;
1930}
1931
1932void display_thumb(int in_file)
1933{
1934 size_t disk_buf_len;
1935
1936 video_thumb_print = 1;
1937 audio_sync_start = 1;
1938 video_sync_start = 1;
1939
1940 disk_buf_len = rb->read (in_file, disk_buf_start, disk_buf_size - MPEG_GUARDBUF_SIZE);
1941 disk_buf_tail = disk_buf_start + disk_buf_len;
1942 file_remaining = 0;
1943 initialize_stream(&video_str,disk_buf_start,disk_buf_len,0xe0);
1944
1945 video_str.status = STREAM_PLAYING;
1946
1947 if ((video_str.thread = rb->create_thread(video_thread,
1948 (uint8_t*)video_stack,VIDEO_STACKSIZE, 0,"mpgvideo"
1949 IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, COP))) == NULL)
1950 {
1951 rb->splash(HZ, "Cannot create video thread!");
1952 }
1953 else
1954 {
1955 rb->thread_wait(video_str.thread);
1956 }
1957 318
1958 if ( video_str.curr_packet_end == video_str.curr_packet) 319 rb->lcd_setfont(FONT_UI);
1959 rb->splash(0, "frame not available");
1960}
1961 320
1962int find_start_pts( int in_file ) 321 return ret;
1963{
1964 uint8_t *p;
1965 size_t read_length = 60*1024;
1966 size_t disk_buf_len;
1967
1968 start_pts_time = 0;
1969
1970 /* temporary read buffer size cannot exceed buffer size */
1971 if ( read_length > disk_buf_size )
1972 read_length = disk_buf_size;
1973
1974 /* read tail of file */
1975 rb->lseek( in_file, 0, SEEK_SET );
1976 disk_buf_len = rb->read( in_file, disk_buf_start, read_length );
1977 disk_buf_tail = disk_buf_start + disk_buf_len;
1978
1979 /* sync reader to this segment of the stream */
1980 p=disk_buf_start;
1981 if (sync_data_stream(&p))
1982 {
1983 DEBUGF("Could not sync stream\n");
1984 return PLUGIN_ERROR;
1985 }
1986
1987 /* find first PTS in audio stream. if the PTS can not be determined,
1988 set start_pts_time to 0 */
1989 audio_sync_start = 0;
1990 audio_sync_time = 0;
1991 video_sync_start = 0;
1992 {
1993 Stream tmp;
1994 initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0);
1995 int count=0;
1996 do
1997 {
1998 count++;
1999 get_next_data(&tmp, 2);
2000 }
2001 while (tmp.tagged != 1 && count < 30);
2002 if (tmp.tagged == 1)
2003 start_pts_time = (int)((tmp.curr_pts/45000)/30);
2004 }
2005 return 0;
2006} 322}
2007 323
2008int find_end_pts( int in_file )
2009{
2010 uint8_t *p;
2011 size_t read_length = 60*1024;
2012 size_t disk_buf_len;
2013
2014 end_pts_time = 0;
2015
2016 /* temporary read buffer size cannot exceed buffer size */
2017 if ( read_length > disk_buf_size )
2018 read_length = disk_buf_size;
2019
2020 /* read tail of file */
2021 rb->lseek( in_file, -1*read_length, SEEK_END );
2022 disk_buf_len = rb->read( in_file, disk_buf_start, read_length );
2023 disk_buf_tail = disk_buf_start + disk_buf_len;
2024
2025 /* sync reader to this segment of the stream */
2026 p=disk_buf_start;
2027 if (sync_data_stream(&p))
2028 {
2029 DEBUGF("Could not sync stream\n");
2030 return PLUGIN_ERROR;
2031 }
2032
2033 /* find last PTS in audio stream; will movie always have audio? if
2034 the play time can not be determined, set end_pts_time to 0 */
2035 audio_sync_start = 0;
2036 audio_sync_time = 0;
2037 video_sync_start = 0;
2038 {
2039 Stream tmp;
2040 initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0);
2041
2042 do
2043 {
2044 get_next_data(&tmp, 2);
2045 if (tmp.tagged == 1)
2046 /* 10 sec less to insure the video frame exist */
2047 end_pts_time = (int)((tmp.curr_pts/45000-10)/30);
2048 }
2049 while (tmp.curr_packet_end != NULL);
2050 }
2051 return 0;
2052}
2053
2054ssize_t seek_PTS( int in_file, int start_time, int accept_button )
2055{
2056 static ssize_t last_seek_pos = 0;
2057 static int last_start_time = 0;
2058 ssize_t seek_pos;
2059 size_t disk_buf_len;
2060 uint8_t *p;
2061 size_t read_length = 60*1024;
2062
2063 /* temporary read buffer size cannot exceed buffer size */
2064 if ( read_length > disk_buf_size )
2065 read_length = disk_buf_size;
2066
2067 if ( start_time == last_start_time )
2068 {
2069 seek_pos = last_seek_pos;
2070 rb->lseek(in_file,seek_pos,SEEK_SET);
2071 }
2072 else if ( start_time != 0 )
2073 {
2074 seek_pos = rb->filesize(in_file)*start_time/
2075 (end_pts_time-start_pts_time);
2076 int seek_pos_sec_inc = rb->filesize(in_file)/
2077 (end_pts_time-start_pts_time)/30;
2078
2079 if (seek_pos<0)
2080 seek_pos=0;
2081 if ((size_t)seek_pos > rb->filesize(in_file) - read_length)
2082 seek_pos = rb->filesize(in_file) - read_length;
2083 rb->lseek( in_file, seek_pos, SEEK_SET );
2084 disk_buf_len = rb->read( in_file, disk_buf_start, read_length );
2085 disk_buf_tail = disk_buf_start + disk_buf_len;
2086
2087 /* sync reader to this segment of the stream */
2088 p=disk_buf_start;
2089 if (sync_data_stream(&p))
2090 {
2091 DEBUGF("Could not sync stream\n");
2092 return PLUGIN_ERROR;
2093 }
2094
2095 /* find PTS >= start_time */
2096 audio_sync_start = 0;
2097 audio_sync_time = 0;
2098 video_sync_start = 0;
2099 {
2100 Stream tmp;
2101 initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0);
2102 int cont_seek_loop = 1;
2103 int coarse_seek = 1;
2104 do
2105 {
2106 if ( accept_button )
2107 {
2108 rb->yield();
2109 if (rb->button_queue_count())
2110 return -101;
2111 }
2112
2113 while ( get_next_data(&tmp, 1) == 1 )
2114 {
2115 if ( tmp.curr_packet_end == disk_buf_start )
2116 seek_pos += disk_buf_tail - disk_buf_start;
2117 else
2118 seek_pos += tmp.curr_packet_end - disk_buf_start;
2119 if ((size_t)seek_pos > rb->filesize(in_file) - read_length)
2120 seek_pos = rb->filesize(in_file) - read_length;
2121 rb->lseek( in_file, seek_pos, SEEK_SET );
2122 disk_buf_len = rb->read ( in_file, disk_buf_start, read_length );
2123 disk_buf_tail = disk_buf_start + disk_buf_len;
2124
2125 /* sync reader to this segment of the stream */
2126 p=disk_buf_start;
2127 initialize_stream(&tmp,p,disk_buf_len,0xc0);
2128 }
2129
2130 /* are we after start_time in the stream? */
2131 if ( coarse_seek && (int)(tmp.curr_pts/45000) >=
2132 (start_time+start_pts_time)*30 )
2133 {
2134 int time_to_backup = (int)(tmp.curr_pts/45000) -
2135 (start_time+start_pts_time)*30;
2136 if (time_to_backup == 0)
2137 time_to_backup++;
2138 seek_pos -= seek_pos_sec_inc * time_to_backup;
2139 seek_pos_sec_inc -= seek_pos_sec_inc/20; /* for stability */
2140 if (seek_pos<0)
2141 seek_pos=0;
2142 if ((size_t)seek_pos > rb->filesize(in_file) - read_length)
2143 seek_pos = rb->filesize(in_file) - read_length;
2144 rb->lseek( in_file, seek_pos, SEEK_SET );
2145 disk_buf_len = rb->read( in_file, disk_buf_start, read_length );
2146 disk_buf_tail = disk_buf_start + disk_buf_len;
2147
2148 /* sync reader to this segment of the stream */
2149 p=disk_buf_start;
2150 if (sync_data_stream(&p))
2151 {
2152 DEBUGF("Could not sync stream\n");
2153 return PLUGIN_ERROR;
2154 }
2155 initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0);
2156 continue;
2157 }
2158
2159 /* are we well before start_time in the stream? */
2160 if ( coarse_seek && (start_time+start_pts_time)*30 -
2161 (int)(tmp.curr_pts/45000) > 2 )
2162 {
2163 int time_to_advance = (start_time+start_pts_time)*30 -
2164 (int)(tmp.curr_pts/45000) - 2;
2165 if (time_to_advance <= 0)
2166 time_to_advance = 1;
2167 seek_pos += seek_pos_sec_inc * time_to_advance;
2168 if (seek_pos<0)
2169 seek_pos=0;
2170 if ((size_t)seek_pos > rb->filesize(in_file) - read_length)
2171 seek_pos = rb->filesize(in_file) - read_length;
2172 rb->lseek( in_file, seek_pos, SEEK_SET );
2173 disk_buf_len = rb->read ( in_file, disk_buf_start, read_length );
2174 disk_buf_tail = disk_buf_start + disk_buf_len;
2175
2176 /* sync reader to this segment of the stream */
2177 p=disk_buf_start;
2178 if (sync_data_stream(&p))
2179 {
2180 DEBUGF("Could not sync stream\n");
2181 return PLUGIN_ERROR;
2182 }
2183 initialize_stream(&tmp,p,disk_buf_len-(disk_buf_start-p),0xc0);
2184 continue;
2185 }
2186
2187 coarse_seek = 0;
2188
2189 /* are we at start_time in the stream? */
2190 if ( (int)(tmp.curr_pts/45000) >= (start_time+start_pts_time)*
2191 30 )
2192 cont_seek_loop = 0;
2193
2194 }
2195 while ( cont_seek_loop );
2196
2197
2198 DEBUGF("start diff: %u %u\n",(unsigned int)(tmp.curr_pts/45000),
2199 (start_time+start_pts_time)*30);
2200 seek_pos+=tmp.curr_packet_end-disk_buf_start;
2201
2202 last_seek_pos = seek_pos;
2203 last_start_time = start_time;
2204
2205 rb->lseek(in_file,seek_pos,SEEK_SET);
2206 }
2207 }
2208 else
2209 {
2210 seek_pos = 0;
2211 rb->lseek(in_file,0,SEEK_SET);
2212 last_seek_pos = seek_pos;
2213 last_start_time = start_time;
2214 }
2215 return seek_pos;
2216}
2217
2218enum plugin_status plugin_start(struct plugin_api* api, void* parameter) 324enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
2219{ 325{
2220 int status = PLUGIN_ERROR; /* assume failure */ 326 int status = PLUGIN_ERROR; /* assume failure */
2221 int result; 327 int result;
2222 int start_time = -1; 328 int err;
2223 void* audiobuf; 329 const char *errstring;
2224 ssize_t audiosize;
2225 int in_file;
2226 size_t disk_buf_len;
2227 ssize_t seek_pos;
2228 size_t audio_stack_size = 0; /* Keep gcc happy and init */
2229 int i;
2230#ifndef HAVE_LCD_COLOR
2231 long graysize;
2232 int grayscales;
2233#endif
2234
2235 audio_sync_start = 0;
2236 audio_sync_time = 0;
2237 video_sync_start = 0;
2238 330
2239 if (parameter == NULL) 331 if (parameter == NULL) {
2240 { 332 /* No file = GTFO */
2241 api->splash(HZ*2, "No File"); 333 api->splash(HZ*2, "No File");
2242 return PLUGIN_ERROR; 334 return PLUGIN_ERROR;
2243 } 335 }
336
337 /* Disable all talking before initializing IRAM */
2244 api->talk_disable(true); 338 api->talk_disable(true);
2245 339
2246 /* Initialize IRAM - stops audio and voice as well */ 340 /* Initialize IRAM - stops audio and voice as well */
2247 PLUGIN_IRAM_INIT(api) 341 PLUGIN_IRAM_INIT(api)
2248 342
2249 rb = api; 343 rb = api;
2250 rb->splash(0, "Loading...");
2251
2252 /* sets audiosize and returns buffer pointer */
2253 audiobuf = rb->plugin_get_audio_buffer(&audiosize);
2254
2255#if INPUT_SRC_CAPS != 0
2256 /* Select playback */
2257 rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK);
2258 rb->audio_set_output_source(AUDIO_SRC_PLAYBACK);
2259#endif
2260
2261 rb->pcm_set_frequency(SAMPR_44);
2262
2263#ifndef HAVE_LCD_COLOR
2264 /* initialize the grayscale buffer: 32 bitplanes for 33 shades of gray. */
2265 grayscales = gray_init(rb, audiobuf, audiosize, false, LCD_WIDTH, LCD_HEIGHT,
2266 32, 2<<8, &graysize) + 1;
2267 audiobuf += graysize;
2268 audiosize -= graysize;
2269 if (grayscales < 33 || audiosize <= 0)
2270 {
2271 rb->talk_disable(false);
2272 rb->splash(HZ, "gray buf error");
2273 return PLUGIN_ERROR;
2274 }
2275#endif
2276
2277 /* Initialise our malloc buffer */
2278 audiosize = mpeg_alloc_init(audiobuf,audiosize, LIBMPEG2BUFFER_SIZE);
2279 if (audiosize == 0)
2280 {
2281 rb->talk_disable(false);
2282 return PLUGIN_ERROR;
2283 }
2284
2285 /* Set disk pointers to NULL */
2286 disk_buf_end = disk_buf_start = NULL;
2287
2288 /* Grab most of the buffer for the compressed video - leave some for
2289 PCM audio data and some for libmpeg2 malloc use. */
2290 disk_buf_size = audiosize - (PCMBUFFER_SIZE+PCMBUFFER_GUARD_SIZE+
2291 MPABUF_SIZE);
2292
2293 DEBUGF("audiosize=%ld, disk_buf_size=%ld\n",audiosize,disk_buf_size);
2294 disk_buf_start = mpeg_malloc(disk_buf_size,-1);
2295
2296 if (disk_buf_start == NULL)
2297 {
2298 rb->talk_disable(false);
2299 return PLUGIN_ERROR;
2300 }
2301
2302 if (!init_mpabuf())
2303 {
2304 rb->talk_disable(false);
2305 return PLUGIN_ERROR;
2306 }
2307 if (!init_pcmbuf())
2308 {
2309 rb->talk_disable(false);
2310 return PLUGIN_ERROR;
2311 }
2312
2313 /* The remaining buffer is for use by libmpeg2 */
2314
2315 /* Open the video file */
2316 in_file = rb->open((char*)parameter,O_RDONLY);
2317
2318 if (in_file < 0){
2319 DEBUGF("Could not open %s\n",(char*)parameter);
2320 rb->talk_disable(false);
2321 return PLUGIN_ERROR;
2322 }
2323 filename = (char*)parameter;
2324 344
2325#ifdef HAVE_LCD_COLOR 345#ifdef HAVE_LCD_COLOR
2326 rb->lcd_set_backdrop(NULL); 346 rb->lcd_set_backdrop(NULL);
@@ -2328,239 +348,62 @@ enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
2328 rb->lcd_set_background(LCD_BLACK); 348 rb->lcd_set_background(LCD_BLACK);
2329#endif 349#endif
2330 350
2331 init_settings((char*)parameter);
2332
2333 /* Initialise libmad */
2334 rb->memset(mad_frame_overlap, 0, sizeof(mad_frame_overlap));
2335 init_mad(mad_frame_overlap);
2336
2337 disk_buf_end = disk_buf_start + disk_buf_size-MPEG_GUARDBUF_SIZE;
2338
2339 /* initalize start_pts_time and end_pts_time with the length (in half
2340 minutes) of the movie. zero if the time could not be determined */
2341 find_start_pts( in_file );
2342 find_end_pts( in_file );
2343
2344
2345 /* start menu */
2346 rb->lcd_clear_display(); 351 rb->lcd_clear_display();
2347 rb->lcd_update(); 352 rb->lcd_update();
2348 result = mpeg_start_menu(end_pts_time-start_pts_time, in_file);
2349
2350 switch (result)
2351 {
2352 case MPEG_START_QUIT:
2353 rb->talk_disable(false);
2354 return 0;
2355 default:
2356 start_time = settings.resume_time;
2357 break;
2358 }
2359
2360 /* basic time checks */
2361 if ( start_time < 0 )
2362 start_time = 0;
2363 else if ( start_time > (end_pts_time-start_pts_time) )
2364 start_time = (end_pts_time-start_pts_time);
2365
2366 /* Turn off backlight timeout */
2367 backlight_force_on(rb); /* backlight control in lib/helper.c */
2368
2369#ifdef HAVE_ADJUSTABLE_CPU_FREQ
2370 rb->cpu_boost(true);
2371#endif
2372
2373 /* From this point on we've altered settings, colors, cpu_boost, etc. and
2374 cannot just return PLUGIN_ERROR - instead drop though to cleanup code
2375 */
2376
2377#ifdef SIMULATOR
2378 /* The simulator thread implementation doesn't have stack buffers, and
2379 these parameters are ignored. */
2380 (void)i; /* Keep gcc happy */
2381 audio_stack = NULL;
2382 audio_stack_size = 0;
2383#else
2384 /* Borrow the codec thread's stack (in IRAM on most targets) */
2385 audio_stack = NULL;
2386 for (i = 0; i < MAXTHREADS; i++)
2387 {
2388 if (rb->strcmp(rb->threads[i].name,"codec")==0)
2389 {
2390 /* Wait to ensure the codec thread has blocked */
2391 while (rb->threads[i].state!=STATE_BLOCKED)
2392 rb->yield();
2393
2394 /* Now we can steal the stack */
2395 audio_stack = rb->threads[i].stack;
2396 audio_stack_size = rb->threads[i].stack_size;
2397
2398 /* Backup the codec thread's stack */
2399 rb->memcpy(codec_stack_copy,audio_stack,audio_stack_size);
2400
2401 break;
2402 }
2403 }
2404
2405 if (audio_stack == NULL)
2406 {
2407 /* This shouldn't happen, but deal with it anyway by using
2408 the copy instead */
2409 audio_stack = codec_stack_copy;
2410 audio_stack_size = AUDIO_STACKSIZE;
2411 }
2412#endif
2413
2414 rb->splash(0, "Loading...");
2415
2416 /* seek start time */
2417 seek_pos = seek_PTS( in_file, start_time, 0 );
2418
2419 video_thumb_print = 0;
2420 audio_sync_start = 0;
2421 audio_sync_time = 0;
2422 video_sync_start = 0;
2423 353
2424 /* Read some stream data */ 354 if (stream_init() < STREAM_OK) {
2425 disk_buf_len = rb->read (in_file, disk_buf_start, disk_buf_size - MPEG_GUARDBUF_SIZE); 355 DEBUGF("Could not initialize streams\n");
356 } else {
357 rb->splash(0, "Loading...");
2426 358
2427 disk_buf_tail = disk_buf_start + disk_buf_len; 359 init_settings((char*)parameter);
2428 file_remaining = rb->filesize(in_file);
2429 file_remaining -= disk_buf_len + seek_pos;
2430 360
2431 initialize_stream( &video_str, disk_buf_start, disk_buf_len, 0xe0 ); 361 err = stream_open((char *)parameter);
2432 initialize_stream( &audio_str, disk_buf_start, disk_buf_len, 0xc0 );
2433 362
2434 rb->mutex_init(&audio_str.msg_lock); 363 if (err >= STREAM_OK) {
2435 rb->mutex_init(&video_str.msg_lock); 364 /* start menu */
365 rb->lcd_clear_display();
366 rb->lcd_update();
367 result = mpeg_start_menu(stream_get_duration());
2436 368
2437 audio_str.status = STREAM_BUFFERING; 369 if (result != MPEG_START_QUIT) {
2438 video_str.status = STREAM_PLAYING; 370 /* Turn off backlight timeout */
371 /* backlight control in lib/helper.c */
372 backlight_force_on(rb);
2439 373
2440#ifndef HAVE_LCD_COLOR 374 /* Enter button loop and process UI */
2441 gray_show(true); 375 if (button_loop()) {
2442#endif 376 settings.resume_time = stream_get_resume_time();
377 }
2443 378
2444 init_stream_lock(); 379 /* Turn on backlight timeout (revert to settings) */
380 backlight_use_settings(rb);
381 }
2445 382
2446#if NUM_CORES > 1 383 stream_close();
2447 flush_icache();
2448#endif
2449 384
2450 /* We put the video thread on the second processor for multi-core targets. */ 385 rb->lcd_clear_display();
2451 if ((video_str.thread = rb->create_thread(video_thread, 386 rb->lcd_update();
2452 (uint8_t*)video_stack, VIDEO_STACKSIZE, 0,
2453 "mpgvideo" IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, COP))) == NULL)
2454 {
2455 rb->splash(HZ, "Cannot create video thread!");
2456 }
2457 else if ((audio_str.thread = rb->create_thread(audio_thread,
2458 (uint8_t*)audio_stack,audio_stack_size, 0,"mpgaudio"
2459 IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, CPU))) == NULL)
2460 {
2461 rb->splash(HZ, "Cannot create audio thread!");
2462 }
2463 else
2464 {
2465 rb->lcd_setfont(FONT_SYSFIXED);
2466
2467 /* Wait until both threads have finished their work */
2468 while ((audio_str.status >= 0) || (video_str.status >= 0))
2469 {
2470 size_t audio_remaining = audio_str.buffer_remaining;
2471 size_t video_remaining = video_str.buffer_remaining;
2472 387
2473 if (MIN(audio_remaining,video_remaining) < MPEG_LOW_WATERMARK) 388 save_settings(); /* Save settings (if they have changed) */
389 status = PLUGIN_OK;
390 } else {
391 DEBUGF("Could not open %s\n", (char*)parameter);
392 switch (err)
2474 { 393 {
2475 394 case STREAM_UNSUPPORTED:
2476 size_t bytes_to_read = disk_buf_size - MPEG_GUARDBUF_SIZE - 395 errstring = "Unsupported format";
2477 MAX(audio_remaining,video_remaining); 396 break;
2478 397 default:
2479 bytes_to_read = MIN(bytes_to_read,(size_t)(disk_buf_end-disk_buf_tail)); 398 errstring = "Error opening file: %d";
2480
2481 while (( bytes_to_read > 0) && (file_remaining > 0) &&
2482 ((audio_str.status != STREAM_DONE) || (video_str.status != STREAM_DONE)))
2483 {
2484
2485 size_t n;
2486 if ( video_sync_start != 0 )
2487 n = rb->read(in_file, disk_buf_tail, MIN(32*1024,bytes_to_read));
2488 else
2489 {
2490 n = rb->read(in_file, disk_buf_tail,bytes_to_read);
2491 if (n==0)
2492 rb->splash(30,"buffer fill error");
2493 }
2494
2495 bytes_to_read -= n;
2496 file_remaining -= n;
2497
2498 lock_stream();
2499 audio_str.buffer_remaining += n;
2500 video_str.buffer_remaining += n;
2501 unlock_stream();
2502
2503 disk_buf_tail += n;
2504
2505 rb->yield();
2506 }
2507
2508 if (disk_buf_tail == disk_buf_end)
2509 disk_buf_tail = disk_buf_start;
2510 } 399 }
2511 400
2512 rb->sleep(HZ/10); 401 rb->splash(HZ*2, errstring, err);
2513 } 402 }
2514
2515 rb->lcd_setfont(FONT_UI);
2516 status = PLUGIN_OK;
2517 }
2518
2519 /* Stop the threads and wait for them to terminate */
2520 if (video_str.thread != NULL)
2521 {
2522 str_send_msg(&video_str, STREAM_QUIT, 0);
2523 rb->thread_wait(video_str.thread);
2524 } 403 }
2525 404
2526 if (audio_str.thread != NULL) 405 stream_exit();
2527 {
2528 str_send_msg(&audio_str, STREAM_QUIT, 0);
2529 rb->thread_wait(audio_str.thread);
2530 }
2531
2532#if NUM_CORES > 1
2533 invalidate_icache();
2534#endif
2535
2536 vo_cleanup();
2537
2538#ifndef HAVE_LCD_COLOR
2539 gray_release();
2540#endif
2541
2542 rb->lcd_clear_display();
2543 rb->lcd_update();
2544
2545 mpeg2_close (mpeg2dec);
2546
2547 rb->close (in_file);
2548
2549#ifndef SIMULATOR
2550 /* Restore the codec thread's stack */
2551 rb->memcpy(audio_stack, codec_stack_copy, audio_stack_size);
2552#endif
2553
2554#ifdef HAVE_ADJUSTABLE_CPU_FREQ
2555 rb->cpu_boost(false);
2556#endif
2557
2558 save_settings(); /* Save settings (if they have changed) */
2559
2560 rb->pcm_set_frequency(HW_SAMPR_DEFAULT);
2561 406
2562 /* Turn on backlight timeout (revert to settings) */
2563 backlight_use_settings(rb); /* backlight control in lib/helper.c */
2564 rb->talk_disable(false); 407 rb->talk_disable(false);
2565 return status; 408 return status;
2566} 409}
diff --git a/apps/plugins/mpegplayer/mpegplayer.h b/apps/plugins/mpegplayer/mpegplayer.h
new file mode 100644
index 0000000000..ae1234d39d
--- /dev/null
+++ b/apps/plugins/mpegplayer/mpegplayer.h
@@ -0,0 +1,120 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Main mpegplayer config header.
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef MPEGPLAYER_H
22#define MPEGPLAYER_H
23
24/* Global API pointer */
25extern struct plugin_api* rb;
26
27#ifdef HAVE_SCHEDULER_BOOSTCTRL
28#define trigger_cpu_boost rb->trigger_cpu_boost
29#define cancel_cpu_boost rb->cancel_cpu_boost
30#endif
31/* #else function-like empty macros are defined in the headers */
32
33/* Memory allotments for various subsystems */
34#define MIN_MEMMARGIN (4*1024)
35
36enum mpeg_malloc_reason_t
37{
38 __MPEG_ALLOC_FIRST = -256,
39 MPEG_ALLOC_CODEC_MALLOC,
40 MPEG_ALLOC_CODEC_CALLOC,
41 MPEG_ALLOC_MPEG2_BUFFER,
42 MPEG_ALLOC_AUDIOBUF,
43 MPEG_ALLOC_PCMOUT,
44 MPEG_ALLOC_DISKBUF,
45};
46
47/** Video thread **/
48#define LIBMPEG2_ALLOC_SIZE (2*1024*1024)
49
50/** MPEG audio buffer **/
51#define AUDIOBUF_GUARD_SIZE (MPA_MAX_FRAME_SIZE + 2*MAD_BUFFER_GUARD)
52#define AUDIOBUF_SIZE (64*1024)
53#define AUDIOBUF_ALLOC_SIZE (AUDIOBUF_SIZE+AUDIOBUF_GUARD_SIZE)
54
55/** PCM buffer **/
56#define CLOCK_RATE 44100 /* Our clock rate in ticks/second (samplerate) */
57
58/* Define this as "1" to have a test tone instead of silence clip */
59#define SILENCE_TEST_TONE 0
60
61#define PCMOUT_BUFSIZE (CLOCK_RATE) /* 1s */
62#define PCMOUT_GUARD_SIZE (1152*4 + sizeof (struct pcm_frame_header))
63#define PCMOUT_ALLOC_SIZE (PCMOUT_BUFSIZE + PCMOUT_GUARD_SIZE)
64 /* Start pcm playback @ 25% full */
65#define PCMOUT_PLAY_WM (PCMOUT_BUFSIZE/4)
66 /* No valid audio frame is smaller */
67#define PCMOUT_LOW_WM (sizeof (struct pcm_frame_header))
68
69/** disk buffer **/
70#define DISK_BUF_LOW_WATERMARK (1024*1024)
71/* 65535+6 is required since each PES has a 6 byte header with a 16 bit
72 * packet length field */
73#define DISK_GUARDBUF_SIZE ALIGN_UP(65535+6, 4)
74
75#ifdef HAVE_LCD_COLOR
76#define DRAW_BLACK LCD_BLACK
77#define DRAW_DARKGRAY LCD_DARKGRAY
78#define DRAW_LIGHTGRAY LCD_LIGHTGRAY
79#define DRAW_WHITE LCD_WHITE
80#define lcd_(fn) rb->lcd_##fn
81#define lcd_splash splash
82
83#define GRAY_FLUSH_ICACHE()
84#define GRAY_INVALIDATE_ICACHE()
85#define GRAY_VIDEO_FLUSH_ICACHE()
86#define GRAY_VIDEO_INVALIDATE_ICACHE()
87#else
88#include "gray.h"
89#define DRAW_BLACK GRAY_BLACK
90#define DRAW_DARKGRAY GRAY_DARKGRAY
91#define DRAW_LIGHTGRAY GRAY_LIGHTGRAY
92#define DRAW_WHITE GRAY_WHITE
93#define lcd_(fn) gray_##fn
94
95#define GRAY_FLUSH_ICACHE() \
96 IF_COP(flush_icache())
97#define GRAY_INVALIDATE_ICACHE() \
98 IF_COP(invalidate_icache())
99#define GRAY_VIDEO_FLUSH_ICACHE() \
100 IF_COP(parser_send_video_msg(VIDEO_GRAY_CACHEOP, 0))
101#define GRAY_VIDEO_INVALIDATE_ICACHE() \
102 IF_COP(parser_send_video_msg(VIDEO_GRAY_CACHEOP, 1))
103#if NUM_CORES > 1
104#define GRAY_CACHE_MAINT
105#endif
106#endif
107
108#include "mpeg2.h"
109#include "video_out.h"
110#include "mpeg_stream.h"
111#include "mpeg_linkedlist.h"
112#include "mpeg_misc.h"
113#include "mpeg_alloc.h"
114#include "stream_thread.h"
115#include "parser.h"
116#include "pcm_output.h"
117#include "disk_buf.h"
118#include "stream_mgr.h"
119
120#endif /* MPEGPLAYER_H */
diff --git a/apps/plugins/mpegplayer/parser.h b/apps/plugins/mpegplayer/parser.h
new file mode 100644
index 0000000000..892a8a14d2
--- /dev/null
+++ b/apps/plugins/mpegplayer/parser.h
@@ -0,0 +1,101 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * AV parser inteface declarations
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef PARSER_H
22#define PARSER_H
23
24enum stream_formats
25{
26 STREAM_FMT_UNKNOWN = -1,
27 STREAM_FMT_MPEG_TS, /* MPEG transport stream */
28 STREAM_FMT_MPEG_PS, /* MPEG program stream */
29 STREAM_FMT_MPV, /* MPEG Video only (1 or 2) */
30 STREAM_FMT_MPA, /* MPEG Audio only */
31};
32
33/* Structure used by a thread that handles a single demuxed data stream and
34 * receives commands from the stream manager */
35enum stream_parse_states
36{
37 /* Stream is... */
38 SSTATE_SYNC, /* synchronizing by trying to find a start code */
39 SSTATE_PARSE, /* parsing the stream looking for packets */
40 SSTATE_END, /* at the end of data */
41};
42
43enum stream_parse_mode
44{
45 STREAM_PM_STREAMING = 0, /* Next packet when streaming */
46 STREAM_PM_RANDOM_ACCESS, /* Random-access parsing */
47};
48
49enum stream_parser_flags
50{
51 STREAMF_CAN_SEEK = 0x1, /* Seeking possible for this stream */
52};
53
54struct stream_parser
55{
56 /* Common generic parser data */
57 enum stream_formats format; /* Stream format */
58 uint32_t start_pts; /* The movie start time as represented by
59 the first audio PTS tag in the
60 stream converted to half minutes */
61 uint32_t end_pts; /* The movie end time as represented by
62 the maximum audio PTS tag in the
63 stream converted to half minutes */
64 uint32_t duration; /* Duration in PTS units */
65 unsigned flags; /* Various attributes set at init */
66 struct vo_ext dims; /* Movie dimensions in pixels */
67 uint32_t last_seek_time;
68 int (*next_data)(struct stream *str, enum stream_parse_mode type);
69 union /* A place for reusable no-cache parameters */
70 {
71 struct str_sync_data sd;
72 } parms;
73};
74
75extern struct stream_parser str_parser;
76
77/* MPEG parsing */
78uint8_t * mpeg_parser_scan_start_code(struct stream_scan *sk, uint32_t code);
79unsigned mpeg_parser_scan_pes(struct stream_scan *sk);
80uint32_t mpeg_parser_scan_scr(struct stream_scan *sk);
81uint32_t mpeg_parser_scan_pts(struct stream_scan *sk, unsigned id);
82off_t mpeg_stream_stream_seek_PTS(uint32_t time, int id);
83
84/* General parsing */
85bool parser_init(void);
86void str_initialize(struct stream *str, off_t pos);
87intptr_t parser_send_video_msg(long id, intptr_t data);
88bool parser_get_video_size(struct vo_ext *sz);
89int parser_init_stream(void);
90void parser_close_stream(void);
91static inline bool parser_can_seek(void)
92 { return str_parser.flags & STREAMF_CAN_SEEK; }
93uint32_t parser_seek_time(uint32_t time);
94void parser_prepare_streaming(void);
95void str_end_of_stream(struct stream *str);
96
97static inline int parser_get_next_data(struct stream *str,
98 enum stream_parse_mode type)
99 { return str_parser.next_data(str, type); }
100
101#endif /* PARSER_H */
diff --git a/apps/plugins/mpegplayer/pcm_output.c b/apps/plugins/mpegplayer/pcm_output.c
new file mode 100644
index 0000000000..281f7ddb72
--- /dev/null
+++ b/apps/plugins/mpegplayer/pcm_output.c
@@ -0,0 +1,278 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * PCM output buffer definitions
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "mpegplayer.h"
23
24/* Pointers */
25
26/* Start of buffer */
27static struct pcm_frame_header * ALIGNED_ATTR(4) pcm_buffer;
28/* End of buffer (not guard) */
29static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_end;
30/* Read pointer */
31static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_head IBSS_ATTR;
32/* Write pointer */
33static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR;
34
35/* Bytes */
36static uint64_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */
37static uint64_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */
38static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */
39
40/* Clock */
41static uint32_t clock_base IBSS_ATTR; /* Our base clock */
42static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */
43static int32_t clock_adjust IBSS_ATTR; /* Clock drift adjustment */
44
45/* Small silence clip. ~5.80ms @ 44.1kHz */
46static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 };
47
48/* Advance a PCM buffer pointer by size bytes circularly */
49static inline void pcm_advance_buffer(struct pcm_frame_header **p,
50 size_t size)
51{
52 *p = SKIPBYTES(*p, size);
53 if (*p >= pcmbuf_end)
54 *p = pcm_buffer;
55}
56
57/* Inline internally but not externally */
58inline ssize_t pcm_output_used(void)
59{
60 return (ssize_t)(pcmbuf_written - pcmbuf_read);
61}
62
63inline ssize_t pcm_output_free(void)
64{
65 return (ssize_t)(PCMOUT_BUFSIZE - pcmbuf_written + pcmbuf_read);
66}
67
68/* Audio DMA handler */
69static void get_more(unsigned char **start, size_t *size)
70{
71 ssize_t sz = pcm_output_used();
72
73 if (sz > pcmbuf_threshold)
74 {
75 pcmbuf_threshold = PCMOUT_LOW_WM;
76
77 while (1)
78 {
79 uint32_t time = pcmbuf_head->time;
80 int32_t offset = time - (clock_base + clock_adjust);
81
82 sz = pcmbuf_head->size;
83
84 if (sz < (ssize_t)(sizeof(pcmbuf_head) + 4) ||
85 (sz & 3) != 0)
86 {
87 /* Just show a warning about this - will never happen
88 * without a bug in the audio thread code or a clobbered
89 * buffer */
90 DEBUGF("get_more: invalid size (%ld)\n", sz);
91 }
92
93 if (offset < -100*CLOCK_RATE/1000)
94 {
95 /* Frame more than 100ms late - drop it */
96 pcm_advance_buffer(&pcmbuf_head, sz);
97 pcmbuf_read += sz;
98 if (pcmbuf_read < pcmbuf_written)
99 continue;
100 }
101 else if (offset < 100*CLOCK_RATE/1000)
102 {
103 /* Frame less than 100ms early - play it */
104 *start = (unsigned char *)pcmbuf_head->data;
105
106 pcm_advance_buffer(&pcmbuf_head, sz);
107 pcmbuf_read += sz;
108
109 sz -= sizeof (struct pcm_frame_header);
110
111 *size = sz;
112
113 /* Audio is time master - keep clock synchronized */
114 clock_adjust = time - clock_base;
115
116 /* Update base clock */
117 clock_base += sz >> 2;
118 return;
119 }
120 /* Frame will be dropped - play silence clip */
121 break;
122 }
123 }
124 else
125 {
126 /* Ran out so revert to default watermark */
127 pcmbuf_threshold = PCMOUT_PLAY_WM;
128 }
129
130 /* Keep clock going at all times */
131 *start = (unsigned char *)silence;
132 *size = sizeof (silence);
133
134 clock_base += sizeof (silence) / 4;
135
136 if (pcmbuf_read > pcmbuf_written)
137 pcmbuf_read = pcmbuf_written;
138}
139
140struct pcm_frame_header * pcm_output_get_buffer(void)
141{
142 return pcmbuf_tail;
143}
144
145void pcm_output_add_data(void)
146{
147 size_t size = pcmbuf_tail->size;
148 pcm_advance_buffer(&pcmbuf_tail, size);
149 pcmbuf_written += size;
150}
151
152/* Flushes the buffer - clock keeps counting */
153void pcm_output_flush(void)
154{
155 rb->pcm_play_lock();
156
157 pcmbuf_threshold = PCMOUT_PLAY_WM;
158 pcmbuf_read = pcmbuf_written = 0;
159 pcmbuf_head = pcmbuf_tail = pcm_buffer;
160
161 rb->pcm_play_unlock();
162}
163
164/* Seek the reference clock to the specified time - next audio data ready to
165 go to DMA should be on the buffer with the same time index or else the PCM
166 buffer should be empty */
167void pcm_output_set_clock(uint32_t time)
168{
169 rb->pcm_play_lock();
170
171 clock_base = time;
172 clock_start = time;
173 clock_adjust = 0;
174
175 rb->pcm_play_unlock();
176}
177
178uint32_t pcm_output_get_clock(void)
179{
180 return clock_base + clock_adjust
181 - (rb->pcm_get_bytes_waiting() >> 2);
182}
183
184uint32_t pcm_output_get_ticks(uint32_t *start)
185{
186 if (start)
187 *start = clock_start;
188
189 return clock_base - (rb->pcm_get_bytes_waiting() >> 2);
190}
191
192/* Pauses/Starts pcm playback - and the clock */
193void pcm_output_play_pause(bool play)
194{
195 rb->pcm_play_lock();
196
197 if (rb->pcm_is_playing())
198 {
199 rb->pcm_play_pause(play);
200 }
201 else if (play)
202 {
203 rb->pcm_play_data(get_more, NULL, 0);
204 }
205
206 rb->pcm_play_unlock();
207}
208
209/* Stops all playback and resets the clock */
210void pcm_output_stop(void)
211{
212 rb->pcm_play_lock();
213
214 if (rb->pcm_is_playing())
215 rb->pcm_play_stop();
216
217 pcm_output_flush();
218 pcm_output_set_clock(0);
219
220 rb->pcm_play_unlock();
221}
222
223/* Drains any data if the start threshold hasn't been reached */
224void pcm_output_drain(void)
225{
226 rb->pcm_play_lock();
227 pcmbuf_threshold = PCMOUT_LOW_WM;
228 rb->pcm_play_unlock();
229}
230
231bool pcm_output_init(void)
232{
233 pcm_buffer = mpeg_malloc(PCMOUT_ALLOC_SIZE, MPEG_ALLOC_PCMOUT);
234 if (pcm_buffer == NULL)
235 return false;
236
237 pcmbuf_threshold = PCMOUT_PLAY_WM;
238 pcmbuf_head = pcm_buffer;
239 pcmbuf_tail = pcm_buffer;
240 pcmbuf_end = SKIPBYTES(pcm_buffer, PCMOUT_BUFSIZE);
241 pcmbuf_read = 0;
242 pcmbuf_written = 0;
243
244 rb->pcm_set_frequency(SAMPR_44);
245
246#if INPUT_SRC_CAPS != 0
247 /* Select playback */
248 rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK);
249 rb->audio_set_output_source(AUDIO_SRC_PLAYBACK);
250#endif
251
252#if SILENCE_TEST_TONE
253 /* Make the silence clip a square wave */
254 const int16_t silence_amp = 32767 / 16;
255 unsigned i;
256
257 for (i = 0; i < ARRAYLEN(silence); i += 2)
258 {
259 if (i < ARRAYLEN(silence)/2)
260 {
261 silence[i] = silence_amp;
262 silence[i+1] = silence_amp;
263 }
264 else
265 {
266 silence[i] = -silence_amp;
267 silence[i+1] = -silence_amp;
268 }
269 }
270#endif
271
272 return true;
273}
274
275void pcm_output_exit(void)
276{
277 rb->pcm_set_frequency(HW_SAMPR_DEFAULT);
278}
diff --git a/apps/plugins/mpegplayer/pcm_output.h b/apps/plugins/mpegplayer/pcm_output.h
new file mode 100644
index 0000000000..8a230b87a5
--- /dev/null
+++ b/apps/plugins/mpegplayer/pcm_output.h
@@ -0,0 +1,46 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * PCM output buffer declarations
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef PCM_OUTPUT_H
22#define PCM_OUTPUT_H
23
24struct pcm_frame_header /* Header added to pcm data every time a decoded
25 audio frame is sent out */
26{
27 uint32_t size; /* size of this frame - including header */
28 uint32_t time; /* timestamp for this frame in audio ticks */
29 unsigned char data[]; /* open array of audio data */
30} ALIGNED_ATTR(4);
31
32bool pcm_output_init(void);
33void pcm_output_exit(void);
34void pcm_output_flush(void);
35void pcm_output_set_clock(uint32_t time);
36uint32_t pcm_output_get_clock(void);
37uint32_t pcm_output_get_ticks(uint32_t *start);
38void pcm_output_play_pause(bool play);
39void pcm_output_stop(void);
40void pcm_output_drain(void);
41struct pcm_frame_header * pcm_output_get_buffer(void);
42void pcm_output_add_data(void);
43ssize_t pcm_output_used(void);
44ssize_t pcm_output_free(void);
45
46#endif /* PCM_OUTPUT_H */
diff --git a/apps/plugins/mpegplayer/stream_mgr.c b/apps/plugins/mpegplayer/stream_mgr.c
new file mode 100644
index 0000000000..00b96173b6
--- /dev/null
+++ b/apps/plugins/mpegplayer/stream_mgr.c
@@ -0,0 +1,1096 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * AV stream manager implementation
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "mpegplayer.h"
23#include "gray.h"
24#include "mpeg_settings.h"
25
26static struct event_queue stream_mgr_queue NOCACHEBSS_ATTR;
27static struct queue_sender_list stream_mgr_queue_send NOCACHEBSS_ATTR;
28static uint32_t stream_mgr_thread_stack[DEFAULT_STACK_SIZE*2/sizeof(uint32_t)];
29
30struct stream_mgr stream_mgr NOCACHEBSS_ATTR;
31
32/* Forward decs */
33static int stream_on_close(void);
34
35struct str_broadcast_data
36{
37 long cmd; /* Command to send to stream */
38 intptr_t data; /* Data to send with command */
39};
40
41static inline void stream_mgr_lock(void)
42{
43 rb->mutex_lock(&stream_mgr.str_mtx);
44}
45
46static inline void stream_mgr_unlock(void)
47{
48 rb->mutex_unlock(&stream_mgr.str_mtx);
49}
50
51static inline void actl_lock(void)
52{
53 rb->mutex_lock(&stream_mgr.actl_mtx);
54}
55
56static inline void actl_unlock(void)
57{
58 rb->mutex_unlock(&stream_mgr.actl_mtx);
59}
60
61static inline void stream_mgr_post_msg(long id, intptr_t data)
62{
63 rb->queue_post(stream_mgr.q, id, data);
64}
65
66static inline intptr_t stream_mgr_send_msg(long id, intptr_t data)
67{
68 return rb->queue_send(stream_mgr.q, id, data);
69}
70
71static inline void stream_mgr_reply_msg(intptr_t retval)
72{
73 rb->queue_reply(stream_mgr.q, retval);
74}
75
76int str_next_data_not_ready(struct stream *str)
77{
78 /* Save the current window since it actually might be ready by the time
79 * the registration is received by buffering. */
80 off_t win_right = str->hdr.win_right;
81
82 if (str->hdr.win_right < disk_buf.filesize - MIN_BUFAHEAD &&
83 disk_buf.filesize > MIN_BUFAHEAD)
84 {
85 /* Set right edge to where probing left off + the minimum margin */
86 str->hdr.win_right += MIN_BUFAHEAD;
87 }
88 else
89 {
90 /* Request would be passed the end of the file */
91 str->hdr.win_right = disk_buf.filesize;
92 }
93
94 switch (disk_buf_send_msg(DISK_BUF_DATA_NOTIFY, (intptr_t)str))
95 {
96 case DISK_BUF_NOTIFY_OK:
97 /* Was ready - restore window and process */
98 str->hdr.win_right = win_right;
99 return STREAM_OK;
100
101 case DISK_BUF_NOTIFY_ERROR:
102 /* Error - quit parsing */
103 str_end_of_stream(str);
104 return STREAM_DATA_END;
105
106 default:
107 /* Not ready - go wait for notification from buffering. */
108 str->pkt_flags = 0;
109 return STREAM_DATA_NOT_READY;
110 }
111}
112
113void str_data_notify_received(struct stream *str)
114{
115 /* Normalize win_right back to the packet length */
116 if (str->state == SSTATE_END)
117 return;
118
119 if (str->curr_packet == NULL)
120 {
121 /* Nothing was yet parsed since init */
122 str->hdr.win_right = str->hdr.win_left;
123 }
124 else
125 {
126 /* Restore window based upon current packet */
127 str->hdr.win_right = str->hdr.win_left +
128 (str->curr_packet_end - str->curr_packet);
129 }
130}
131
132/* Set stream manager to a "no-file" state */
133static void stream_mgr_init_state(void)
134{
135 stream_mgr.filename = NULL;
136 stream_mgr.resume_time = 0;
137 stream_mgr.seeked = false;
138}
139
140/* Add a stream to the playback pool */
141void stream_add_stream(struct stream *str)
142{
143 actl_lock();
144
145 list_remove_item(&str->l);
146 list_add_item(&stream_mgr.strl, &str->l);
147
148 actl_unlock();
149}
150
151/* Callback for various list-moving operations */
152static bool strl_enum_callback(struct list_item *item, intptr_t data)
153{
154 actl_lock();
155
156 list_remove_item(item);
157
158 if (data == 1)
159 list_add_item(&stream_mgr.actl, item);
160
161 actl_unlock();
162
163 return true;
164}
165
166/* Clear all streams from active and playback pools */
167void stream_remove_streams(void)
168{
169 list_enum_items(&stream_mgr.strl, strl_enum_callback, 0);
170}
171
172/* Move the playback pool to the active list */
173void move_strl_to_actl(void)
174{
175 list_enum_items(&stream_mgr.strl, strl_enum_callback, 1);
176}
177
178/* Remove a stream from the active list and return it to the pool */
179static bool actl_stream_remove(struct stream *str)
180{
181 if (list_is_member(&stream_mgr.actl, &str->l))
182 {
183 actl_lock();
184
185 list_remove_item(&str->l);
186 list_add_item(&stream_mgr.strl, &str->l);
187
188 actl_unlock();
189 return true;
190 }
191
192 return false;
193}
194
195/* Broadcast a message to all active streams */
196static bool actl_stream_broadcast_callback(struct list_item *item,
197 struct str_broadcast_data *sbd)
198{
199 struct stream *str = TYPE_FROM_MEMBER(struct stream, item, l);
200
201 switch (sbd->cmd)
202 {
203 case STREAM_PLAY:
204 case STREAM_PAUSE:
205 break;
206
207 case STREAM_STOP:
208 if (sbd->data != 0)
209 {
210 actl_lock();
211
212 list_remove_item(item);
213 list_add_item(&stream_mgr.strl, item);
214
215 actl_unlock();
216 sbd->data = 0;
217 }
218 break;
219
220 default:
221 return false;
222 }
223
224 str_send_msg(str, sbd->cmd, sbd->data);
225 return true;
226}
227
228static void actl_stream_broadcast(int cmd, intptr_t data)
229{
230 struct str_broadcast_data sbd;
231 sbd.cmd = cmd;
232 sbd.data = data;
233 list_enum_items(&stream_mgr.actl,
234 (list_enum_callback_t)actl_stream_broadcast_callback,
235 (intptr_t)&sbd);
236}
237
238/* Set the current base clock */
239static void set_stream_clock(uint32_t time)
240{
241 /* Fudge: Start clock 100ms early to allow for some filling time */
242 if (time > 100*TS_SECOND/1000)
243 time -= 100*TS_SECOND/1000;
244 else
245 time = 0;
246
247 pcm_output_set_clock(TS_TO_TICKS(time));
248}
249
250/* Return the play time relative to the specified play time */
251static uint32_t time_from_whence(uint32_t time, int whence)
252{
253 int64_t currtime;
254
255 switch (whence)
256 {
257 case SEEK_SET:
258 /* Set the current time (time = unsigned offset from 0) */
259 if (time > str_parser.duration)
260 time = str_parser.duration;
261 break;
262 case SEEK_CUR:
263 /* Seek forward or backward from the current time
264 * (time = signed offset from current) */
265 if (stream_mgr.seeked)
266 currtime = str_parser.last_seek_time;
267 else
268 currtime = TICKS_TO_TS(pcm_output_get_clock());
269
270 currtime -= str_parser.start_pts;
271 currtime += (int32_t)time;
272
273 if (currtime < 0)
274 currtime = 0;
275 else if ((uint64_t)currtime > str_parser.duration)
276 currtime = str_parser.duration;
277
278 time = (uint32_t)currtime;
279 break;
280 case SEEK_END:
281 /* Seek from the end (time = unsigned offset from end) */
282 if (time > str_parser.duration)
283 time = str_parser.duration;
284 time = str_parser.duration - time;
285 break;
286 }
287
288 return time;
289}
290
291/* Handle seeking details if playing or paused */
292static uint32_t stream_seek_intl(uint32_t time, int whence, int status)
293{
294 /* seek start time */
295 bool was_buffering;
296
297 if (status == STREAM_PLAYING)
298 {
299 /* Keep clock from advancing while seeking */
300 pcm_output_play_pause(false);
301 }
302
303 /* Place streams in a non-running state - keep them on actl */
304 actl_stream_broadcast(STREAM_STOP, 0);
305
306 /* Stop all buffering or else risk clobbering random-access data */
307 was_buffering = disk_buf_send_msg(STREAM_STOP, 0);
308
309 time = time_from_whence(time, whence);
310 time = parser_seek_time(time);
311
312 if (status == STREAM_PLAYING)
313 {
314 /* Restart streams if currently playing */
315
316 /* Clear any seeked status */
317 stream_mgr.seeked = false;
318
319 /* Flush old PCM data */
320 pcm_output_flush();
321
322 /* Set the master clock */
323 set_stream_clock(time);
324
325 /* Prepare the parser and associated streams */
326 parser_prepare_streaming();
327
328 /* Start buffer using previous buffering status */
329 disk_buf_send_msg(STREAM_PLAY, was_buffering);
330
331 /* Tell each stream to start - may generate end of stream signals
332 * now - we'll handle this when finished */
333 actl_stream_broadcast(STREAM_PLAY, 0);
334
335 /* Actually start the clock */
336 pcm_output_play_pause(true);
337 }
338 else
339 {
340 /* Performed the seek - leave it at that until restarted */
341 stream_mgr.seeked = true;
342 }
343
344 return time;
345}
346
347/* Handle STREAM_OPEN */
348void stream_on_open(const char *filename)
349{
350 int err = STREAM_ERROR;
351
352 stream_mgr_lock();
353
354 trigger_cpu_boost();
355
356 /* Open the video file */
357 if (disk_buf_open(filename) >= 0)
358 {
359 /* Initialize the parser */
360 err = parser_init_stream();
361
362 if (err >= STREAM_OK)
363 {
364 /* File ok - save the opened filename */
365 stream_mgr.filename = filename;
366 }
367 }
368
369 /* If error - cleanup */
370 if (err < STREAM_OK)
371 stream_on_close();
372
373 cancel_cpu_boost();
374
375 stream_mgr_unlock();
376
377 stream_mgr_reply_msg(err);
378}
379
380/* Handler STREAM_PLAY */
381static void stream_on_play(void)
382{
383 int status = stream_mgr.status;
384
385 stream_mgr_lock();
386
387 if (status == STREAM_STOPPED)
388 {
389 uint32_t start;
390
391 /* We just say we're playing now */
392 stream_mgr.status = STREAM_PLAYING;
393
394 /* Reply with previous state */
395 stream_mgr_reply_msg(status);
396
397 trigger_cpu_boost();
398
399 /* Seek to initial position and set clock to that time */
400
401 /* Save the resume time */
402 start = str_parser.last_seek_time - str_parser.start_pts;
403 stream_mgr.resume_time = start;
404
405 start = stream_seek_intl(start, SEEK_SET, STREAM_STOPPED);
406
407 /* Fill list of all streams that will be playing */
408 move_strl_to_actl();
409
410 /* Clear any seeked status */
411 stream_mgr.seeked = false;
412
413 /* Set the master clock */
414 set_stream_clock(start);
415
416 /* Prepare the parser and associated streams */
417 parser_prepare_streaming();
418
419 /* Force buffering */
420 disk_buf_send_msg(STREAM_PLAY, true);
421
422 /* Tell each stream to start - may generate end of stream signals
423 * now - we'll handle this when finished */
424 actl_stream_broadcast(STREAM_PLAY, 0);
425
426 /* Actually start the clock */
427 pcm_output_play_pause(true);
428 }
429 else
430 {
431 /* Reply with previous state */
432 stream_mgr_reply_msg(status);
433 }
434
435 stream_mgr_unlock();
436}
437
438/* Handle STREAM_PAUSE */
439static void stream_on_pause(void)
440{
441 int status = stream_mgr.status;
442
443 stream_mgr_lock();
444
445 /* Reply with previous state */
446 stream_mgr_reply_msg(status);
447
448 if (status == STREAM_PLAYING)
449 {
450 /* Pause the clock */
451 pcm_output_play_pause(false);
452
453 /* Pause each active stream */
454 actl_stream_broadcast(STREAM_PAUSE, 0);
455
456 /* Pause the disk buffer - buffer may continue filling */
457 disk_buf_send_msg(STREAM_PAUSE, false);
458
459 /* Unboost the CPU */
460 cancel_cpu_boost();
461
462 /* Offically paused */
463 stream_mgr.status = STREAM_PAUSED;
464 }
465
466 stream_mgr_unlock();
467}
468
469/* Handle STREAM_RESUME */
470static void stream_on_resume(void)
471{
472 int status = stream_mgr.status;
473
474 stream_mgr_lock();
475
476 /* Reply with previous state */
477 stream_mgr_reply_msg(status);
478
479 if (status == STREAM_PAUSED)
480 {
481 /* Boost the CPU */
482 trigger_cpu_boost();
483
484 if (stream_mgr.seeked)
485 {
486 /* Have to give the parser notice to sync up streams */
487 stream_mgr.seeked = false;
488
489 /* Flush old PCM data */
490 pcm_output_flush();
491
492 /* Set the master clock */
493 set_stream_clock(str_parser.last_seek_time);
494
495 /* Prepare the parser and associated streams */
496 parser_prepare_streaming();
497 }
498
499 /* Don't force buffering */
500 disk_buf_send_msg(STREAM_PLAY, false);
501
502 /* Tell each stream to start - may generate end of stream signals
503 * now - we'll handle this when finished */
504 actl_stream_broadcast(STREAM_PLAY, 0);
505
506 /* Actually start the clock */
507 pcm_output_play_pause(true);
508
509 /* Officially playing */
510 stream_mgr.status = STREAM_PLAYING;
511 }
512
513 stream_mgr_unlock();
514}
515
516/* Handle STREAM_STOP */
517static void stream_on_stop(bool reply)
518{
519 int status = stream_mgr.status;
520
521 stream_mgr_lock();
522
523 if (reply)
524 stream_mgr_reply_msg(status);
525
526 if (status != STREAM_STOPPED)
527 {
528 /* Not stopped = paused or playing */
529 stream_mgr.seeked = false;
530
531 /* Pause the clock */
532 pcm_output_play_pause(false);
533
534 if (stream_can_seek())
535 {
536 /* Read the current stream time */
537 uint32_t time = TICKS_TO_TS(pcm_output_get_clock());
538
539 /* Assume invalidity */
540 stream_mgr.resume_time = 0;
541
542 if (time >= str_parser.start_pts && time < str_parser.end_pts)
543 {
544 /* Save the current stream time */
545 stream_mgr.resume_time = time - str_parser.start_pts;
546 }
547 }
548
549 /* Stop buffering */
550 disk_buf_send_msg(STREAM_STOP, 0);
551
552 /* Clear any still-active streams and remove from actl */
553 actl_stream_broadcast(STREAM_STOP, 1);
554
555 /* Stop PCM output (and clock) */
556 pcm_output_stop();
557
558 /* Cancel our processor boost */
559 cancel_cpu_boost();
560
561 stream_mgr.status = STREAM_STOPPED;
562 }
563
564 stream_mgr_unlock();
565}
566
567/* Handle STREAM_SEEK */
568static void stream_on_seek(struct stream_seek_data *skd)
569{
570 uint32_t time = skd->time;
571 int whence = skd->whence;
572
573 switch (whence)
574 {
575 case SEEK_SET:
576 case SEEK_CUR:
577 case SEEK_END:
578 if (stream_mgr.filename == NULL)
579 break;
580
581 stream_mgr_reply_msg(STREAM_OK);
582
583 stream_keep_disk_active();
584
585 stream_mgr_lock();
586
587 if (!stream_can_seek())
588 {
589 }
590 else if (stream_mgr.status != STREAM_STOPPED)
591 {
592 if (stream_mgr.status != STREAM_PLAYING)
593 {
594 trigger_cpu_boost();
595 }
596
597 stream_seek_intl(time, whence, stream_mgr.status);
598
599 if (stream_mgr.status != STREAM_PLAYING)
600 {
601 cancel_cpu_boost();
602 }
603 }
604 else
605 {
606 stream_mgr.seeked = true;
607 time = time_from_whence(time, whence);
608 parser_seek_time(time);
609 }
610
611 stream_mgr_unlock();
612 return;
613 }
614
615 stream_mgr_reply_msg(STREAM_ERROR);
616}
617
618/* Handle STREAM_CLOSE */
619static int stream_on_close(void)
620{
621 int status = STREAM_STOPPED;
622
623 stream_mgr_lock();
624
625 /* Any open file? */
626 if (stream_mgr.filename != NULL)
627 {
628 /* Yes - hide video */
629 stream_show_vo(false);
630 /* Stop any playback */
631 status = stream_mgr.status;
632 stream_on_stop(false);
633 /* Tell parser file is finished */
634 parser_close_stream();
635 /* Close file */
636 disk_buf_close();
637 /* Reinitialize manager */
638 stream_mgr_init_state();
639 }
640
641 stream_mgr_unlock();
642
643 return status;
644}
645
646/* Handle STREAM_EV_COMPLETE */
647static void stream_on_ev_complete(struct stream *str)
648{
649 stream_mgr_lock();
650
651 /* Stream is active? */
652 if (actl_stream_remove(str))
653 {
654 /* No - remove this stream from the active list */
655 DEBUGF(" finished: 0x%02x\n", str->id);
656 if (list_is_empty(&stream_mgr.actl))
657 {
658 /* All streams have acked - stop playback */
659 stream_on_stop(false);
660 stream_mgr.resume_time = 0; /* Played to end - no resume */
661 }
662 else
663 {
664 /* Stream is done - stop it and place back in pool */
665 str_send_msg(str, STREAM_STOP, 1);
666 }
667 }
668
669 stream_mgr_unlock();
670}
671
672/* Callback for stream to notify about events internal to them */
673void stream_generate_event(struct stream *str, long id, intptr_t data)
674{
675 if (str == NULL)
676 return;
677
678 switch (id)
679 {
680 case STREAM_EV_COMPLETE:
681 /* The last stream has ended */
682 stream_mgr_post_msg(STREAM_EV_COMPLETE, (intptr_t)str);
683 break;
684 }
685
686 (void)data;
687}
688
689/* Clear any particular notification for which a stream registered */
690void stream_clear_notify(struct stream *str, int for_msg)
691{
692 switch (for_msg)
693 {
694 case DISK_BUF_DATA_NOTIFY:
695 disk_buf_send_msg(DISK_BUF_CLEAR_DATA_NOTIFY, (intptr_t)str);
696 break;
697 }
698}
699
700/* Show/hide the video output */
701bool stream_show_vo(bool show)
702{
703 bool vis;
704 stream_mgr_lock();
705
706 vis = parser_send_video_msg(VIDEO_DISPLAY_SHOW, show);
707#ifndef HAVE_LCD_COLOR
708 GRAY_VIDEO_FLUSH_ICACHE();
709 GRAY_INVALIDATE_ICACHE();
710 gray_show(show);
711 GRAY_FLUSH_ICACHE();
712#endif
713 stream_mgr_unlock();
714
715 return vis;
716}
717
718/* Query the visibility of video output */
719bool stream_vo_is_visible(void)
720{
721 bool vis;
722 stream_mgr_lock();
723 vis = parser_send_video_msg(VIDEO_DISPLAY_IS_VISIBLE, 0);
724 stream_mgr_unlock();
725 return vis;
726}
727
728/* Return the video dimensions */
729bool stream_vo_get_size(struct vo_ext *sz)
730{
731 bool retval = false;
732
733 stream_mgr_lock();
734
735 if (str_parser.dims.w > 0 && str_parser.dims.h > 0)
736 {
737 *sz = str_parser.dims;
738 retval = true;
739 }
740
741 stream_mgr_unlock();
742
743 return retval;
744}
745
746#ifndef HAVE_LCD_COLOR
747/* Set the rectangle for the gray video overlay - clipped to screen */
748bool stream_set_gray_rect(const struct vo_rect *rc)
749{
750 bool retval = false;
751 struct vo_rect rc_gray;
752
753 stream_mgr_lock();
754
755 vo_rect_set_ext(&rc_gray, 0, 0, LCD_WIDTH, LCD_HEIGHT);
756
757 if (vo_rect_intersect(&rc_gray, &rc_gray, rc))
758 {
759 bool vo_vis = stream_show_vo(false);
760
761 GRAY_VIDEO_FLUSH_ICACHE();
762 GRAY_INVALIDATE_ICACHE();
763
764 gray_init(rb, stream_mgr.graymem, stream_mgr.graysize,
765 false, rc_gray.r - rc_gray.l, rc_gray.b - rc_gray.t,
766 32, 2<<8, NULL);
767
768 gray_set_position(rc_gray.l, rc_gray.t);
769 GRAY_FLUSH_ICACHE();
770
771 if (vo_vis)
772 {
773 stream_show_vo(true);
774 }
775 }
776
777 stream_mgr_unlock();
778
779 return retval;
780}
781
782/* Show/hide the gray video overlay (independently of vo visibility). */
783void stream_gray_show(bool show)
784{
785 stream_mgr_lock();
786
787 GRAY_VIDEO_FLUSH_ICACHE();
788 GRAY_INVALIDATE_ICACHE();
789 gray_show(show);
790 GRAY_FLUSH_ICACHE();
791
792 stream_mgr_unlock();
793}
794#endif
795
796/* Display a thumbnail at the last seek point */
797bool stream_display_thumb(const struct vo_rect *rc)
798{
799 bool retval;
800
801 if (rc == NULL)
802 return false;
803
804 stream_mgr_lock();
805
806 stream_mgr.parms.rc = *rc;
807 retval = parser_send_video_msg(VIDEO_PRINT_THUMBNAIL,
808 (intptr_t)&stream_mgr.parms.rc);
809
810 stream_mgr_unlock();
811 return retval;
812}
813
814/* Return the time playback should resume if interrupted */
815uint32_t stream_get_resume_time(void)
816{
817 uint32_t resume_time;
818
819 /* A stop request is async and replies before setting this - must lock */
820 stream_mgr_lock();
821
822 resume_time = stream_mgr.resume_time;
823
824 stream_mgr_unlock();
825
826 return resume_time;
827}
828
829/* Returns the smallest file window that includes all active streams'
830 * windows */
831static bool stream_get_window_callback(struct list_item *item,
832 struct stream_window *sw)
833{
834 struct stream *str = TYPE_FROM_MEMBER(struct stream, item, l);
835 off_t swl = str->hdr.win_left;
836 off_t swr = str->hdr.win_right;
837
838 if (swl < sw->left)
839 sw->left = swl;
840
841 if (swr > sw->right)
842 sw->right = swr;
843
844 return true;
845}
846
847bool stream_get_window(struct stream_window *sw)
848{
849 if (sw == NULL)
850 return false;
851
852 sw->left = LONG_MAX;
853 sw->right = LONG_MIN;
854
855 actl_lock();
856 list_enum_items(&stream_mgr.actl,
857 (list_enum_callback_t)stream_get_window_callback,
858 (intptr_t)sw);
859 actl_unlock();
860
861 return sw->left <= sw->right;
862}
863
864/* Playback control thread */
865static void stream_mgr_thread(void)
866{
867 struct queue_event ev;
868
869 while (1)
870 {
871 rb->queue_wait(stream_mgr.q, &ev);
872
873 switch (ev.id)
874 {
875 case STREAM_OPEN:
876 stream_on_open((const char *)ev.data);
877 break;
878
879 case STREAM_CLOSE:
880 stream_on_close();
881 break;
882
883 case STREAM_PLAY:
884 stream_on_play();
885 break;
886
887 case STREAM_PAUSE:
888 if (ev.data)
889 stream_on_resume();
890 else
891 stream_on_pause();
892 break;
893
894 case STREAM_STOP:
895 stream_on_stop(true);
896 break;
897
898 case STREAM_SEEK:
899 stream_on_seek((struct stream_seek_data *)ev.data);
900 break;
901
902 case STREAM_EV_COMPLETE:
903 stream_on_ev_complete((struct stream *)ev.data);
904 break;
905
906 case STREAM_QUIT:
907 if (stream_mgr.status != STREAM_STOPPED)
908 stream_on_stop(false);
909 return;
910 }
911 }
912}
913
914/* Stream command interface APIs */
915
916/* Opens a new file */
917int stream_open(const char *filename)
918{
919 if (stream_mgr.thread != NULL)
920 return stream_mgr_send_msg(STREAM_OPEN, (intptr_t)filename);
921 return STREAM_ERROR;
922}
923
924/* Plays the current file starting at time 'start' */
925int stream_play(void)
926{
927 if (stream_mgr.thread != NULL)
928 return stream_mgr_send_msg(STREAM_PLAY, 0);
929 return STREAM_ERROR;
930}
931
932/* Pauses playback if playing */
933int stream_pause(void)
934{
935 if (stream_mgr.thread != NULL)
936 return stream_mgr_send_msg(STREAM_PAUSE, false);
937 return STREAM_ERROR;
938}
939
940/* Resumes playback if paused */
941int stream_resume(void)
942{
943 if (stream_mgr.thread != NULL)
944 return stream_mgr_send_msg(STREAM_PAUSE, true);
945 return STREAM_ERROR;
946}
947
948/* Stops playback if not stopped */
949int stream_stop(void)
950{
951 if (stream_mgr.thread != NULL)
952 return stream_mgr_send_msg(STREAM_STOP, 0);
953 return STREAM_ERROR;
954}
955
956/* Seeks playback time to/by the specified time */
957int stream_seek(uint32_t time, int whence)
958{
959 int ret;
960
961 if (stream_mgr.thread == NULL)
962 return STREAM_ERROR;
963
964 stream_mgr_lock();
965
966 stream_mgr.parms.skd.time = time;
967 stream_mgr.parms.skd.whence = whence;
968
969 ret = stream_mgr_send_msg(STREAM_SEEK, (intptr_t)&stream_mgr.parms.skd);
970
971 stream_mgr_unlock();
972
973 return ret;
974}
975
976/* Closes the current file */
977int stream_close(void)
978{
979 if (stream_mgr.thread != NULL)
980 return stream_mgr_send_msg(STREAM_CLOSE, 0);
981 return STREAM_ERROR;
982}
983
984/* Initializes the playback engine */
985int stream_init(void)
986{
987 void *mem;
988 size_t memsize;
989
990 stream_mgr.status = STREAM_STOPPED;
991 stream_mgr_init_state();
992 list_initialize(&stream_mgr.actl);
993
994 /* Initialize our window to the outside world first */
995 rb->mutex_init(&stream_mgr.str_mtx);
996 rb->mutex_init(&stream_mgr.actl_mtx);
997
998 stream_mgr.q = &stream_mgr_queue;
999 rb->queue_init(stream_mgr.q, false);
1000 rb->queue_enable_queue_send(stream_mgr.q, &stream_mgr_queue_send);
1001
1002 /* sets audiosize and returns buffer pointer */
1003 mem = rb->plugin_get_audio_buffer(&memsize);
1004
1005 /* Initialize non-allocator blocks first */
1006#ifndef HAVE_LCD_COLOR
1007 int grayscales;
1008
1009 /* This can run on another processor - align data */
1010 memsize = CACHEALIGN_BUFFER(&mem, memsize);
1011 stream_mgr.graymem = mem;
1012
1013 /* initialize the grayscale buffer: 32 bitplanes for 33 shades of gray. */
1014 grayscales = gray_init(rb, mem, memsize, false,
1015 LCD_WIDTH, LCD_HEIGHT,
1016 32, 2<<8, &stream_mgr.graysize) + 1;
1017
1018 /* This can run on another processor - align size */
1019 stream_mgr.graysize = CACHEALIGN_UP(stream_mgr.graysize);
1020
1021 mem += stream_mgr.graysize;
1022 memsize -= stream_mgr.graysize;
1023
1024 if (grayscales < 33 || (ssize_t)memsize <= 0)
1025 {
1026 rb->splash(HZ, "graylib init failed!");
1027 return STREAM_ERROR;
1028 }
1029#endif /* !HAVE_LCD_COLOR */
1030
1031 stream_mgr.thread = rb->create_thread(stream_mgr_thread,
1032 stream_mgr_thread_stack, sizeof(stream_mgr_thread_stack),
1033 0, "mpgstream_mgr" IF_PRIO(, PRIORITY_SYSTEM) IF_COP(, CPU));
1034
1035 if (stream_mgr.thread == NULL)
1036 {
1037 rb->splash(HZ, "Could not create stream manager thread!");
1038 return STREAM_ERROR;
1039 }
1040
1041 /* Wait for thread to initialize */
1042 stream_mgr_send_msg(STREAM_NULL, 0);
1043
1044 /* Initialise our malloc buffer */
1045 if (!mpeg_alloc_init(mem, memsize))
1046 {
1047 rb->splash(HZ, "Out of memory in stream_init");
1048 }
1049 /* These inits use the allocator */
1050 else if (!pcm_output_init())
1051 {
1052 rb->splash(HZ, "Could not initialize PCM!");
1053 }
1054 else if (!audio_thread_init())
1055 {
1056 rb->splash(HZ, "Cannot create audio thread!");
1057 }
1058 else if (!video_thread_init())
1059 {
1060 rb->splash(HZ, "Cannot create video thread!");
1061 }
1062 /* Disk buffer takes max allotment of what's left so it must be last */
1063 else if (!disk_buf_init())
1064 {
1065 rb->splash(HZ, "Cannot create buffering thread!");
1066 }
1067 else if (!parser_init())
1068 {
1069 rb->splash(HZ, "Parser init failed!");
1070 }
1071 else
1072 {
1073 return STREAM_OK;
1074 }
1075
1076 return STREAM_ERROR;
1077}
1078
1079/* Cleans everything up */
1080void stream_exit(void)
1081{
1082 stream_close();
1083
1084 /* Stop the threads and wait for them to terminate */
1085 video_thread_exit();
1086 audio_thread_exit();
1087 disk_buf_exit();
1088 pcm_output_exit();
1089
1090 if (stream_mgr.thread != NULL)
1091 {
1092 stream_mgr_post_msg(STREAM_QUIT, 0);
1093 rb->thread_wait(stream_mgr.thread);
1094 stream_mgr.thread = NULL;
1095 }
1096}
diff --git a/apps/plugins/mpegplayer/stream_mgr.h b/apps/plugins/mpegplayer/stream_mgr.h
new file mode 100644
index 0000000000..63452ecbc0
--- /dev/null
+++ b/apps/plugins/mpegplayer/stream_mgr.h
@@ -0,0 +1,151 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * AV stream manager decalarations
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef STREAM_MGR_H
22#define STREAM_MGR_H
23
24/* Basic media control interface - this handles state changes and stream
25 * coordination with assistance from the parser */
26struct stream_mgr
27{
28 struct thread_entry *thread; /* Playback control thread */
29 struct event_queue *q; /* event queue for control thread */
30 const char *filename; /* Current filename */
31 uint32_t resume_time; /* The stream tick where playback was
32 stopped (or started) */
33 bool seeked; /* A seek happened and things must be
34 resynced */
35 int status; /* Current playback status */
36 struct list_item strl; /* List of available streams */
37 struct list_item actl; /* List of active streams */
38 struct mutex str_mtx; /* Main stream manager mutex */
39 struct mutex actl_mtx; /* Lock for current-streams list */
40#ifndef HAVE_LCD_COLOR
41 void *graymem;
42 size_t graysize;
43#endif
44 union /* A place for reusable non-cacheable parameters */
45 {
46 struct vo_rect rc;
47 struct stream_seek_data skd;
48 } parms;
49};
50
51extern struct stream_mgr stream_mgr NOCACHEBSS_ATTR;
52
53struct stream_window
54{
55 off_t left, right;
56};
57
58/** Interface for use by streams and other internal objects **/
59bool stream_get_window(struct stream_window *sw);
60void stream_clear_notify(struct stream *str, int for_msg);
61int str_next_data_not_ready(struct stream *str);
62/* Called by a stream to say it got its buffering notification */
63void str_data_notify_received(struct stream *str);
64void stream_add_stream(struct stream *str);
65void stream_remove_streams(void);
66
67enum stream_events
68{
69 __STREAM_EV_FIRST = STREAM_MESSAGE_LAST-1,
70 STREAM_EV_COMPLETE,
71};
72
73void stream_generate_event(struct stream *str, long id, intptr_t data);
74
75/** Main control functions **/
76
77/* Initialize the playback engine */
78int stream_init(void);
79
80/* Close the playback engine */
81void stream_exit(void);
82
83/* Open a new file */
84int stream_open(const char *filename);
85
86/* Close the current file */
87int stream_close(void);
88
89/* Plays from the current seekpoint if stopped */
90int stream_play(void);
91
92/* Pauses playback if playing */
93int stream_pause(void);
94
95/* Resumes playback if paused */
96int stream_resume(void);
97
98/* Stops all streaming activity if playing or paused */
99int stream_stop(void);
100
101/* Point stream at a particular time.
102 * whence = one of SEEK_SET, SEEK_CUR, SEEK_END */
103int stream_seek(uint32_t time, int whence);
104
105/* Show/Hide the video image at the current seekpoint */
106bool stream_show_vo(bool show);
107
108#ifndef HAVE_LCD_COLOR
109/* Set the gray overlay rectangle */
110bool stream_set_gray_rect(const struct vo_rect *rc);
111void stream_gray_show(bool show);
112#endif
113
114/* Display thumbnail of the current seekpoint */
115bool stream_display_thumb(const struct vo_rect *rc);
116
117/* Return video dimensions */
118bool stream_vo_get_size(struct vo_ext *sz);
119
120/* Returns the resume time in timestamp ticks */
121uint32_t stream_get_resume_time(void);
122
123/* Return the absolute stream time in clock ticks - adjusted by
124 * master clock stream via audio timestamps */
125static inline uint32_t stream_get_time(void)
126 { return pcm_output_get_clock(); }
127
128/* Return the absolute clock time in clock ticks - unadjusted */
129static inline uint32_t stream_get_ticks(uint32_t *start)
130 { return pcm_output_get_ticks(start); }
131
132/* Returns the current playback status */
133static inline int stream_status(void)
134 { return stream_mgr.status; }
135
136/* Returns the playback length of the stream */
137static inline uint32_t stream_get_duration(void)
138 { return str_parser.duration; }
139
140static inline bool stream_can_seek(void)
141 { return parser_can_seek(); }
142
143/* Keep the disk spinning (for seeking and browsing) */
144static inline void stream_keep_disk_active(void)
145{
146#ifndef HAVE_FLASH_STORAGE
147 rb->ata_spin();
148#endif
149 }
150
151#endif /* STREAM_MGR_H */
diff --git a/apps/plugins/mpegplayer/stream_thread.h b/apps/plugins/mpegplayer/stream_thread.h
new file mode 100644
index 0000000000..1962a66878
--- /dev/null
+++ b/apps/plugins/mpegplayer/stream_thread.h
@@ -0,0 +1,192 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Declarations for stream-specific threading
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef STREAM_THREAD_H
22#define STREAM_THREAD_H
23
24#define PKT_HAS_TS 0x1
25
26/* Stream header which is the minimum to receive asynchronous buffering
27 * notifications.
28 * Layed-out to allow streaming access after random-access parsing */
29struct stream_hdr
30{
31 struct event_queue *q; /* Communication queue - separate to allow it
32 to be placed in another section */
33 off_t win_left; /* Left position within data stream */
34 union
35 {
36 off_t win_right; /* Right position within data stream */
37 off_t pos; /* Start/current position for random-access read */
38 };
39 off_t limit; /* Limit for random-access read */
40 struct list_item nf; /* List for data notification */
41};
42
43struct stream
44{
45 struct stream_hdr hdr; /* Base stream data */
46 struct thread_entry *thread; /* Stream's thread */
47 uint8_t* curr_packet; /* Current stream packet beginning */
48 uint8_t* curr_packet_end; /* Current stream packet end */
49 struct list_item l; /* List of streams - either reserve pool
50 or active pool */
51 int state; /* State machine parsing mode */
52 uint32_t start_pts; /* First timestamp for stream */
53 uint32_t end_pts; /* Last timestamp for stream */
54 uint32_t pts; /* Last presentation timestamp */
55 uint32_t pkt_flags; /* PKT_* flags */
56 unsigned id; /* Stream identifier */
57};
58
59/* Make sure there there is always enough data buffered ahead for
60 * the worst possible case - regardless of whether a valid stream
61 * would actually produce that */
62#define MIN_BUFAHEAD (21+65535+6+65535+6) /* 131103 */
63
64/* States that a stream's thread assumes internally */
65enum thread_states
66{
67 /* Stream thread... */
68 TSTATE_INIT = 0, /* is initialized and primed */
69 TSTATE_DATA, /* is awaiting data to be available */
70 TSTATE_BUFFERING, /* is buffering data */
71 TSTATE_EOS, /* has hit the end of data */
72 TSTATE_DECODE, /* is in a decoding state */
73 TSTATE_RENDER, /* is in a rendering state */
74 TSTATE_RENDER_WAIT, /* is waiting to render */
75 TSTATE_RENDER_WAIT_END, /* is waiting on remaining data */
76};
77
78/* Commands that streams respond to */
79enum stream_message
80{
81 STREAM_NULL = 0, /* A NULL message for whatever reason -
82 usually ignored */
83 STREAM_PLAY, /* Start playback at current position */
84 STREAM_PAUSE, /* Stop playing and await further commands */
85 STREAM_RESET, /* Reset the stream for a discontinuity */
86 STREAM_STOP, /* Stop stream - requires a reset later */
87 STREAM_SEEK, /* Seek the current stream to a new location */
88 STREAM_OPEN, /* Open a new file */
89 STREAM_CLOSE, /* Close the current file */
90 STREAM_QUIT, /* Exit the stream and thread */
91 STREAM_NEEDS_SYNC, /* Need to sync before stream decoding? */
92 STREAM_SYNC, /* Sync to the specified time from some key point */
93 STREAM_FIND_END_TIME, /* Get the exact end time of an elementary
94 * stream - ie. time just after last frame is finished */
95 /* Disk buffer */
96 STREAM_DISK_BUF_FIRST,
97 DISK_BUF_DATA_NOTIFY = STREAM_DISK_BUF_FIRST,
98 DISK_BUF_CLEAR_DATA_NOTIFY, /* Cancel pending data notification */
99 DISK_BUF_CACHE_RANGE, /* Cache a range of the file in the buffer */
100 /* Audio stream */
101 STREAM_AUDIO_FIRST,
102 /* Video stream */
103 STREAM_VIDEO_FIRST,
104 VIDEO_DISPLAY_SHOW = STREAM_VIDEO_FIRST, /* Show/hide video output */
105 VIDEO_DISPLAY_IS_VISIBLE, /* Is the video output visible? */
106 VIDEO_GET_SIZE, /* Get the video dimensions */
107 VIDEO_PRINT_FRAME, /* Print the frame at the current position */
108 VIDEO_PRINT_THUMBNAIL, /* Print a thumbnail of the current position */
109#ifdef GRAY_CACHE_MAINT
110 VIDEO_GRAY_CACHEOP,
111#endif
112 STREAM_MESSAGE_LAST,
113};
114
115/* Data parameter for STREAM_SEEK */
116struct stream_seek_data
117{
118 uint32_t time; /* Time to seek to/by */
119 int whence; /* Specification of relationship to current position/file */
120};
121
122/* Data parameter for STREAM_SYNC */
123struct str_sync_data
124{
125 uint32_t time; /* Time to sync to */
126 struct stream_scan sk; /* Specification of start/limits/direction */
127};
128
129/* Stream status codes - not eqivalent to thread states */
130enum stream_status
131{
132 /* Stream status is... */
133 STREAM_DATA_END = -4, /* Stream has ended */
134 STREAM_DATA_NOT_READY = -3, /* Data was not available yet */
135 STREAM_UNSUPPORTED = -2, /* Format is unsupported */
136 STREAM_ERROR = -1, /* some kind of error - quit it or reset it */
137 STREAM_OK = 0, /* General inequality for success >= is OK, < error */
138 STREAM_STOPPED = 0, /* stopped and awaiting commands - send STREAM_INIT */
139 STREAM_PLAYING, /* playing and rendering its data */
140 STREAM_PAUSED, /* paused and awaiting commands */
141 /* Other status codes (> STREAM_OK) */
142 STREAM_MATCH, /* A good match was found */
143 STREAM_PERFECT_MATCH, /* Exactly what was wanted was found or
144 no better match is possible */
145 STREAM_NOT_FOUND, /* Match not found */
146};
147
148#define STR_FROM_HEADER(sh) ((struct stream *)(sh))
149
150/* Clip time to range for a particular stream */
151static inline uint32_t clip_time(struct stream *str, uint32_t time)
152{
153 if (time < str->start_pts)
154 time = str->start_pts;
155 else if (time >= str->end_pts)
156 time = str->end_pts;
157
158 return time;
159}
160
161extern struct stream video_str IBSS_ATTR;
162extern struct stream audio_str IBSS_ATTR;
163
164bool video_thread_init(void);
165void video_thread_exit(void);
166bool audio_thread_init(void);
167void audio_thread_exit(void);
168
169/* Some queue function wrappers to keep things clean-ish */
170
171/* For stream use only */
172static inline bool str_have_msg(struct stream *str)
173 { return !rb->queue_empty(str->hdr.q); }
174
175static inline void str_get_msg(struct stream *str, struct queue_event *ev)
176 { rb->queue_wait(str->hdr.q, ev); }
177
178static inline void str_get_msg_w_tmo(struct stream *str, struct queue_event *ev,
179 int timeout)
180 { rb->queue_wait_w_tmo(str->hdr.q, ev, timeout); }
181
182static inline void str_reply_msg(struct stream *str, intptr_t reply)
183 { rb->queue_reply(str->hdr.q, reply); }
184
185/* Public use */
186static inline intptr_t str_send_msg(struct stream *str, long id, intptr_t data)
187 { return rb->queue_send(str->hdr.q, id, data); }
188
189static inline void str_post_msg(struct stream *str, long id, intptr_t data)
190 { rb->queue_post(str->hdr.q, id, data); }
191
192#endif /* STREAM_THREAD_H */
diff --git a/apps/plugins/mpegplayer/video_out.h b/apps/plugins/mpegplayer/video_out.h
index ec3f7c65d3..08cd7aa848 100644
--- a/apps/plugins/mpegplayer/video_out.h
+++ b/apps/plugins/mpegplayer/video_out.h
@@ -21,7 +21,51 @@
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */ 22 */
23 23
24#ifndef VIDEO_OUT_H
25#define VIDEO_OUT_H
26
27/* Structure to hold width and height values */
28struct vo_ext
29{
30 int w, h;
31};
32
33/* Structure that defines a rectangle by its edges */
34struct vo_rect
35{
36 int l, t, r, b;
37};
38
24void vo_draw_frame (uint8_t * const * buf); 39void vo_draw_frame (uint8_t * const * buf);
25void vo_draw_frame_thumb (uint8_t * const * buf); 40bool vo_draw_frame_thumb (uint8_t * const * buf,
41 const struct vo_rect *rc);
42bool vo_init (void);
43bool vo_show (bool show);
44bool vo_is_visible(void);
26void vo_setup (const mpeg2_sequence_t * sequence); 45void vo_setup (const mpeg2_sequence_t * sequence);
46void vo_dimensions(struct vo_ext *sz);
27void vo_cleanup (void); 47void vo_cleanup (void);
48
49/* Sets all coordinates of a vo_rect to 0 */
50void vo_rect_clear(struct vo_rect *rc);
51/* Returns true if left >= right or top >= bottom */
52bool vo_rect_empty(const struct vo_rect *rc);
53/* Initializes a vo_rect using upper-left corner and extents */
54void vo_rect_set_ext(struct vo_rect *rc, int x, int y,
55 int width, int height);
56/* Query if two rectangles intersect
57 * If either are empty returns false */
58bool vo_rects_intersect(const struct vo_rect *rc1,
59 const struct vo_rect *rc2);
60
61/* Intersect two rectangles
62 * Resulting rectangle is placed in rc_dst.
63 * rc_dst is set to empty if they don't intersect.
64 * Empty source rectangles do not intersect any rectangle.
65 * rc_dst may be the same structure as rc1 or rc2.
66 * Returns true if the resulting rectangle is not empty. */
67bool vo_rect_intersect(struct vo_rect *rc_dst,
68 const struct vo_rect *rc1,
69 const struct vo_rect *rc2);
70
71#endif /* VIDEO_OUT_H */
diff --git a/apps/plugins/mpegplayer/video_out_rockbox.c b/apps/plugins/mpegplayer/video_out_rockbox.c
index 9dd8d6a467..d5e927e9f1 100644
--- a/apps/plugins/mpegplayer/video_out_rockbox.c
+++ b/apps/plugins/mpegplayer/video_out_rockbox.c
@@ -1,189 +1,404 @@
1/* 1/***************************************************************************
2 * video_out_null.c 2 * __________ __ ___.
3 * Copyright (C) 2000-2003 Michel Lespinasse <walken@zoy.org> 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Copyright (C) 1999-2000 Aaron Holtzman <aholtzma@ess.engr.uvic.ca> 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
5 * 9 *
6 * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. 10 * mpegplayer video output routines
7 * See http://libmpeg2.sourceforge.net/ for updates.
8 * 11 *
9 * mpeg2dec is free software; you can redistribute it and/or modify 12 * All files in this archive are subject to the GNU General Public License.
10 * it under the terms of the GNU General Public License as published by 13 * See the file COPYING in the source tree root for full license agreement.
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 * 14 *
14 * mpeg2dec is distributed in the hope that it will be useful, 15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * KIND, either express or implied.
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 * 17 *
19 * You should have received a copy of the GNU General Public License 18 ****************************************************************************/
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */
23
24#include "mpeg2dec_config.h" 19#include "mpeg2dec_config.h"
25 20
26#include "plugin.h" 21#include "plugin.h"
27#include "gray.h" 22#include "mpegplayer.h"
23
24struct vo_data
25{
26 int image_width;
27 int image_height;
28 int image_chroma_x;
29 int image_chroma_y;
30 int display_width;
31 int display_height;
32 int output_x;
33 int output_y;
34 int output_width;
35 int output_height;
36 bool visible;
37 bool thumb_mode;
38 void *last;
39};
40
41#ifdef PROC_NEEDS_CACHEALIGN
42/* Cache aligned and padded to avoid clobbering other processors' cacheable
43 * data */
44static uint8_t __vo_data[CACHEALIGN_UP(sizeof(struct vo_data))]
45 CACHEALIGN_ATTR;
46#define vo (*((struct vo_data *)__vo_data))
47#else
48static struct vo_data vo;
49#endif
50
51/* Draw a black rectangle if no video frame is available */
52static void vo_draw_black(void)
53{
54 int foreground = lcd_(get_foreground)();
28 55
29extern struct plugin_api* rb; 56 lcd_(set_foreground)(DRAW_BLACK);
30 57
31#include "mpeg2.h" 58 lcd_(fillrect)(vo.output_x, vo.output_y, vo.output_width,
32#include "video_out.h" 59 vo.output_height);
60 lcd_(update_rect)(vo.output_x, vo.output_y, vo.output_width,
61 vo.output_height);
33 62
34static int image_width; 63 lcd_(set_foreground)(foreground);
35static int image_height; 64}
36static int image_chroma_x;
37static int image_chroma_y;
38static int output_x;
39static int output_y;
40static int output_width;
41static int output_height;
42 65
43void vo_draw_frame (uint8_t * const * buf) 66static inline void yuv_blit(uint8_t * const * buf, int src_x, int src_y,
67 int stride, int x, int y, int width, int height)
44{ 68{
45#ifdef HAVE_LCD_COLOR 69#ifdef HAVE_LCD_COLOR
46 rb->lcd_yuv_blit(buf, 0,0,image_width, 70 rb->lcd_yuv_blit(buf, src_x, src_y, stride, x, y , width, height);
47 output_x,output_y,output_width,output_height);
48#else 71#else
49 gray_ub_gray_bitmap_part(buf[0],0,0,image_width, 72 gray_ub_gray_bitmap_part(buf[0], src_x, src_y, stride, x, y, width, height);
50 output_x,output_y,output_width,output_height);
51#endif 73#endif
52} 74}
53 75
76void vo_draw_frame(uint8_t * const * buf)
77{
78 if (!vo.visible)
79 {
80 /* Frame is hidden - copout */
81 DEBUGF("vo hidden\n");
82 return;
83 }
84 else if (buf == NULL)
85 {
86 /* No frame exists - draw black */
87 vo_draw_black();
88 DEBUGF("vo no frame\n");
89 return;
90 }
91
92 yuv_blit(buf, 0, 0, vo.image_width,
93 vo.output_x, vo.output_y, vo.output_width,
94 vo.output_height);
95}
96
54#if LCD_WIDTH >= LCD_HEIGHT 97#if LCD_WIDTH >= LCD_HEIGHT
55#define SCREEN_WIDTH LCD_WIDTH 98#define SCREEN_WIDTH LCD_WIDTH
56#define SCREEN_HEIGHT LCD_HEIGHT 99#define SCREEN_HEIGHT LCD_HEIGHT
57#else /* Assume the screen is rotates on portraid LCDs */ 100#else /* Assume the screen is rotated on portrait LCDs */
58#define SCREEN_WIDTH LCD_HEIGHT 101#define SCREEN_WIDTH LCD_HEIGHT
59#define SCREEN_HEIGHT LCD_WIDTH 102#define SCREEN_HEIGHT LCD_WIDTH
60#endif 103#endif
61 104
62uint8_t* tmpbufa = 0; 105static inline void vo_rect_clear_inl(struct vo_rect *rc)
63uint8_t* tmpbufb = 0; 106{
64uint8_t* tmpbufc = 0; 107 rc->l = rc->t = rc->r = rc->b = 0;
65uint8_t* tmpbuf[3]; 108}
66 109
67void vo_draw_frame_thumb (uint8_t * const * buf) 110static inline bool vo_rect_empty_inl(const struct vo_rect *rc)
68{ 111{
69 int r,c; 112 return rc == NULL || rc->l >= rc->r || rc->t >= rc->b;
113}
70 114
71#if LCD_WIDTH >= LCD_HEIGHT 115static inline bool vo_rects_intersect_inl(const struct vo_rect *rc1,
72 for (r=0;r<image_width/2;r++) 116 const struct vo_rect *rc2)
73 for (c=0;c<image_height/2;c++) 117{
74 *(tmpbuf[0]+c*image_width/2+r) = 118 return !vo_rect_empty_inl(rc1) &&
75 *(buf[0]+2*c*image_width+2*r); 119 !vo_rect_empty_inl(rc2) &&
76 120 rc1->l < rc2->r && rc1->r > rc2->l &&
77 for (r=0;r<image_width/4;r++) 121 rc1->t < rc2->b && rc1->b > rc2->t;
78 for (c=0;c<image_height/4;c++) 122}
123
124/* Sets all coordinates of a vo_rect to 0 */
125void vo_rect_clear(struct vo_rect *rc)
126{
127 vo_rect_clear_inl(rc);
128}
129
130/* Returns true if left >= right or top >= bottom */
131bool vo_rect_empty(const struct vo_rect *rc)
132{
133 return vo_rect_empty_inl(rc);
134}
135
136/* Initializes a vo_rect using upper-left corner and extents */
137void vo_rect_set_ext(struct vo_rect *rc, int x, int y,
138 int width, int height)
139{
140 rc->l = x;
141 rc->t = y;
142 rc->r = x + width;
143 rc->b = y + height;
144}
145
146/* Query if two rectangles intersect */
147bool vo_rects_intersect(const struct vo_rect *rc1,
148 const struct vo_rect *rc2)
149{
150 return vo_rects_intersect_inl(rc1, rc2);
151}
152
153/* Intersect two rectangles, placing the result in rc_dst */
154bool vo_rect_intersect(struct vo_rect *rc_dst,
155 const struct vo_rect *rc1,
156 const struct vo_rect *rc2)
157{
158 if (rc_dst != NULL)
79 { 159 {
80 *(tmpbuf[1]+c*image_width/4+r) = 160 if (vo_rects_intersect_inl(rc1, rc2))
81 *(buf[1]+c*image_width+2*r); 161 {
82 *(tmpbuf[2]+c*image_width/4+r) = 162 rc_dst->l = MAX(rc1->l, rc2->l);
83 *(buf[2]+c*image_width+2*r); 163 rc_dst->r = MIN(rc1->r, rc2->r);
164 rc_dst->t = MAX(rc1->t, rc2->t);
165 rc_dst->b = MIN(rc1->b, rc2->b);
166 return true;
167 }
168
169 vo_rect_clear_inl(rc_dst);
84 } 170 }
171
172 return false;
173}
174
175/* Shink or stretch each axis - rotate counter-clockwise to retain upright
176 * orientation on rotated displays (they rotate clockwise) */
177void stretch_image_plane(const uint8_t * src, uint8_t *dst, int stride,
178 int src_w, int src_h, int dst_w, int dst_h)
179{
180 uint8_t *dst_end = dst + dst_w*dst_h;
181
182#if LCD_WIDTH >= LCD_HEIGHT
183 int src_w2 = src_w*2; /* 2x dimensions (for rounding before division) */
184 int dst_w2 = dst_w*2;
185 int src_h2 = src_h*2;
186 int dst_h2 = dst_h*2;
187 int qw = src_w2 / dst_w2; /* src-dst width ratio quotient */
188 int rw = src_w2 - qw*dst_w2; /* src-dst width ratio remainder */
189 int qh = src_h2 / dst_h2; /* src-dst height ratio quotient */
190 int rh = src_h2 - qh*dst_h2; /* src-dst height ratio remainder */
191 int dw = dst_w; /* Width error accumulator */
192 int dh = dst_h; /* Height error accumulator */
85#else 193#else
86 for (r=0;r<image_width/2;r++) 194 int src_w2 = src_w*2;
87 for (c=0;c<image_height/2;c++) 195 int dst_w2 = dst_h*2;
88 *(tmpbuf[0]+(image_width/2-1-r)*image_height/2+c) = 196 int src_h2 = src_h*2;
89 *(buf[0]+2*c*image_width+2*r); 197 int dst_h2 = dst_w*2;
90 198 int qw = src_h2 / dst_w2;
91 for (r=0;r<image_width/4;r++) 199 int rw = src_h2 - qw*dst_w2;
92 for (c=0;c<image_height/4;c++) 200 int qh = src_w2 / dst_h2;
201 int rh = src_w2 - qh*dst_h2;
202 int dw = dst_h;
203 int dh = dst_w;
204
205 src += src_w - 1;
206#endif
207
208 while (1)
93 { 209 {
94 *(tmpbuf[1]+(image_width/4-1-r)*image_height/4+c) = 210 const uint8_t *s = src;
95 *(buf[1]+c*image_width+2*r); 211#if LCD_WIDTH >= LCD_HEIGHT
96 *(tmpbuf[2]+(image_width/4-1-r)*image_height/4+c) = 212 uint8_t * const dst_line_end = dst + dst_w;
97 *(buf[2]+c*image_width+2*r); 213#else
98 } 214 uint8_t * const dst_line_end = dst + dst_h;
99#endif 215#endif
216 while (1)
217 {
218 *dst++ = *s;
100 219
101rb->lcd_clear_display(); 220 if (dst >= dst_line_end)
102rb->lcd_update(); 221 {
222 dw = dst_w;
223 break;
224 }
103 225
104#ifdef HAVE_LCD_COLOR
105#ifdef SIMULATOR
106#if LCD_WIDTH >= LCD_HEIGHT 226#if LCD_WIDTH >= LCD_HEIGHT
107 rb->lcd_yuv_blit(tmpbuf,0,0,image_width/2, 227 s += qw;
108 (LCD_WIDTH-1-image_width/2)/2,
109 LCD_HEIGHT-50-(image_height/2),
110 output_width/2,output_height/2);
111
112#else 228#else
113 rb->lcd_yuv_blit(tmpbuf,0,0,image_height/2, 229 s += qw*stride;
114 LCD_HEIGHT-50-(image_height/2),
115 (LCD_WIDTH-1-image_width/2)/2,
116 output_height/2,output_width/2);
117#endif 230#endif
118#else 231 dw += rw;
232
233 if (dw >= dst_w2)
234 {
235 dw -= dst_w2;
119#if LCD_WIDTH >= LCD_HEIGHT 236#if LCD_WIDTH >= LCD_HEIGHT
120 rb->lcd_yuv_blit(tmpbuf,0,0,image_width/2, 237 s++;
121 (LCD_WIDTH-1-image_width/2)/2,
122 LCD_HEIGHT-50-(image_height/2),
123 output_width/2,output_height/2);
124#else 238#else
125 rb->lcd_yuv_blit(tmpbuf,0,0,image_height/2, 239 s += stride;
126 LCD_HEIGHT-50-(image_height/2),
127 (LCD_WIDTH-1-image_width/2)/2,
128 output_height/2,output_width/2);
129#endif
130#endif 240#endif
241 }
242 }
243
244 if (dst >= dst_end)
245 break;
246#if LCD_WIDTH >= LCD_HEIGHT
247 src += qh*stride;
131#else 248#else
249 src -= qh;
250#endif
251 dh += rh;
252
253 if (dh >= dst_h2)
254 {
255 dh -= dst_h2;
132#if LCD_WIDTH >= LCD_HEIGHT 256#if LCD_WIDTH >= LCD_HEIGHT
133 gray_ub_gray_bitmap_part(tmpbuf[0],0,0,image_width/2, 257 src += stride;
134 (LCD_WIDTH-1-image_width/2)/2,
135 LCD_HEIGHT-50-(image_height/2),
136 output_width/2,output_height/2);
137#else 258#else
138 gray_ub_gray_bitmap_part(tmpbuf[0],0,0,image_height/2, 259 src--;
139 LCD_HEIGHT-50-(image_height/2), 260#endif
140 (LCD_WIDTH-1-image_width/2)/2, 261 }
141 output_height/2,output_width/2); 262 }
263}
264
265bool vo_draw_frame_thumb(uint8_t * const * buf, const struct vo_rect *rc)
266{
267 void *mem;
268 size_t bufsize;
269 uint8_t *yuv[3];
270 struct vo_rect thumb_rc;
271 int thumb_width, thumb_height;
272 int thumb_uv_width, thumb_uv_height;
273
274 if (buf == NULL)
275 return false;
276
277 /* Obtain rectangle as clipped to the screen */
278 vo_rect_set_ext(&thumb_rc, 0, 0, LCD_WIDTH, LCD_HEIGHT);
279 if (!vo_rect_intersect(&thumb_rc, rc, &thumb_rc))
280 return true;
281
282 DEBUGF("thumb_rc: %d, %d, %d, %d\n", thumb_rc.l, thumb_rc.t,
283 thumb_rc.r, thumb_rc.b);
284
285 thumb_width = rc->r - rc->l;
286 thumb_height = rc->b - rc->t;
287 thumb_uv_width = thumb_width / 2;
288 thumb_uv_height = thumb_height / 2;
289
290 DEBUGF("thumb: w: %d h: %d uvw: %d uvh: %d\n", thumb_width,
291 thumb_height, thumb_uv_width, thumb_uv_height);
292
293 /* Use remaining mpeg2 buffer as temp space */
294 mem = mpeg2_get_buf(&bufsize);
295
296 if (bufsize < (size_t)(thumb_width*thumb_height)
297#ifdef HAVE_LCD_COLOR
298 + 2u*(thumb_uv_width * thumb_uv_height)
142#endif 299#endif
300 )
301 {
302 DEBUGF("thumb: insufficient buffer\n");
303 return false;
304 }
305
306 yuv[0] = mem;
307 stretch_image_plane(buf[0], yuv[0], vo.image_width,
308 vo.display_width, vo.display_height,
309 thumb_width, thumb_height);
310
311#ifdef HAVE_LCD_COLOR
312 yuv[1] = yuv[0] + thumb_width*thumb_height;
313 yuv[2] = yuv[1] + thumb_uv_width*thumb_uv_height;
314
315 stretch_image_plane(buf[1], yuv[1], vo.image_width / 2,
316 vo.display_width / 2, vo.display_height / 2,
317 thumb_uv_width, thumb_uv_height);
318
319 stretch_image_plane(buf[2], yuv[2], vo.image_width / 2,
320 vo.display_width / 2, vo.display_height / 2,
321 thumb_uv_width, thumb_uv_height);
143#endif 322#endif
323
324#if LCD_WIDTH >= LCD_HEIGHT
325 yuv_blit(yuv, 0, 0, thumb_width,
326 thumb_rc.l, thumb_rc.t,
327 thumb_rc.r - thumb_rc.l,
328 thumb_rc.b - thumb_rc.t);
329#else
330 yuv_blit(yuv, 0, 0, thumb_height,
331 thumb_rc.t, thumb_rc.l,
332 thumb_rc.b - thumb_rc.t,
333 thumb_rc.r - thumb_rc.l);
334#endif /* LCD_WIDTH >= LCD_HEIGHT */
335
336 return true;
144} 337}
145 338
146void vo_setup(const mpeg2_sequence_t * sequence) 339void vo_setup(const mpeg2_sequence_t * sequence)
147{ 340{
148 image_width=sequence->width; 341 vo.image_width = sequence->width;
149 image_height=sequence->height; 342 vo.image_height = sequence->height;
150 343 vo.display_width = sequence->display_width;
151 tmpbufa = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width* 344 vo.display_height = sequence->display_height;
152 image_height/4, -2); 345
153 tmpbufb = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width* 346 DEBUGF("vo_setup - w:%d h:%d\n", vo.display_width, vo.display_height);
154 image_height/16, -2); 347
155 tmpbufc = (uint8_t*)mpeg2_malloc(sizeof(uint8_t)*image_width* 348 vo.image_chroma_x = vo.image_width / sequence->chroma_width;
156 image_height/16, -2); 349 vo.image_chroma_y = vo.image_height / sequence->chroma_height;
157 tmpbuf[0] = tmpbufa; 350
158 tmpbuf[1] = tmpbufb; 351 if (sequence->display_width >= SCREEN_WIDTH)
159 tmpbuf[2] = tmpbufc; 352 {
160 353 vo.output_width = SCREEN_WIDTH;
161 image_chroma_x=image_width/sequence->chroma_width; 354 vo.output_x = 0;
162 image_chroma_y=image_height/sequence->chroma_height; 355 }
163 356 else
164 if (sequence->display_width >= SCREEN_WIDTH) { 357 {
165 output_width = SCREEN_WIDTH; 358 vo.output_width = sequence->display_width;
166 output_x = 0; 359 vo.output_x = (SCREEN_WIDTH - sequence->display_width) / 2;
167 } else {
168 output_width = sequence->display_width;
169 output_x = (SCREEN_WIDTH-sequence->display_width)/2;
170 } 360 }
171 361
172 if (sequence->display_height >= SCREEN_HEIGHT) { 362 if (sequence->display_height >= SCREEN_HEIGHT)
173 output_height = SCREEN_HEIGHT; 363 {
174 output_y = 0; 364 vo.output_height = SCREEN_HEIGHT;
175 } else { 365 vo.output_y = 0;
176 output_height = sequence->display_height; 366 }
177 output_y = (SCREEN_HEIGHT-sequence->display_height)/2; 367 else
368 {
369 vo.output_height = sequence->display_height;
370 vo.output_y = (SCREEN_HEIGHT - sequence->display_height) / 2;
178 } 371 }
179} 372}
180 373
374void vo_dimensions(struct vo_ext *sz)
375{
376 sz->w = vo.display_width;
377 sz->h = vo.display_height;
378}
379
380bool vo_init(void)
381{
382 vo.visible = false;
383 return true;
384}
385
386bool vo_show(bool show)
387{
388 bool vis = vo.visible;
389 vo.visible = show;
390 return vis;
391}
392
393bool vo_is_visible(void)
394{
395 return vo.visible;
396}
397
181void vo_cleanup(void) 398void vo_cleanup(void)
182{ 399{
183 if (tmpbufc) 400 vo.visible = false;
184 mpeg2_free(tmpbufc); 401#ifndef HAVE_LCD_COLOR
185 if (tmpbufb) 402 gray_release();
186 mpeg2_free(tmpbufb); 403#endif
187 if (tmpbufa)
188 mpeg2_free(tmpbufa);
189} 404}
diff --git a/apps/plugins/mpegplayer/video_thread.c b/apps/plugins/mpegplayer/video_thread.c
new file mode 100644
index 0000000000..e69089d734
--- /dev/null
+++ b/apps/plugins/mpegplayer/video_thread.c
@@ -0,0 +1,1040 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * mpegplayer video thread implementation
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "mpegplayer.h"
23#include "mpeg2dec_config.h"
24#include "gray.h"
25#include "video_out.h"
26#include "mpeg_settings.h"
27
28/** Video stream and thread **/
29
30/* Video thread data passed around to its various functions */
31struct video_thread_data
32{
33 mpeg2dec_t *mpeg2dec; /* Our video decoder */
34 const mpeg2_info_t *info; /* Info about video stream */
35 int state; /* Thread state */
36 int status; /* Media status */
37 struct queue_event ev; /* Our event queue to receive commands */
38 int num_drawn; /* Number of frames drawn since reset */
39 int num_skipped; /* Number of frames skipped since reset */
40 uint32_t curr_time; /* Current due time of frame */
41 uint32_t period; /* Frame period in clock ticks */
42 uint32_t eta_stream; /* Current time of stream */
43 uint32_t eta_video; /* Time that frame has been scheduled for */
44 int32_t eta_early; /* How early has the frame been decoded? */
45 int32_t eta_late; /* How late has the frame been decoded? */
46 int frame_drop_level; /* Drop severity */
47 int skip_level; /* Skip severity */
48 long last_showfps; /* Last time the FPS display was updated */
49 long last_render; /* Last time a frame was drawn */
50 int syncf_perfect; /* Last sync fit result */
51 uint32_t syncf_time; /* PTS of last synced frame */
52 uint32_t syncf_period; /* TS duration of last synced frame */
53};
54
55/* TODO: Check if 4KB is appropriate - it works for my test streams,
56 so maybe we can reduce it. */
57#define VIDEO_STACKSIZE (4*1024)
58static uint32_t video_stack[VIDEO_STACKSIZE / sizeof(uint32_t)] IBSS_ATTR;
59static struct event_queue video_str_queue NOCACHEBSS_ATTR;
60static struct queue_sender_list video_str_queue_send NOCACHEBSS_ATTR;
61struct stream video_str IBSS_ATTR;
62
63static void draw_fps(struct video_thread_data *td)
64{
65 uint32_t start;
66 uint32_t clock_ticks = stream_get_ticks(&start);
67 int fps = 0;
68 char str[80];
69
70 clock_ticks -= start;
71 if (clock_ticks != 0)
72 fps = muldiv_uint32(CLOCK_RATE*100, td->num_drawn, clock_ticks);
73
74 rb->snprintf(str, sizeof(str), "%d.%02d %d %d ",
75 fps / 100, fps % 100, td->num_skipped,
76 td->info->display_picture->temporal_reference);
77 rb->lcd_putsxy(0, 0, str);
78 rb->lcd_update_rect(0, 0, LCD_WIDTH, 8);
79
80 td->last_showfps = *rb->current_tick;
81}
82
83#if defined(DEBUG) || defined(SIMULATOR)
84static unsigned char pic_coding_type_char(unsigned type)
85{
86 switch (type)
87 {
88 case PIC_FLAG_CODING_TYPE_I:
89 return 'I'; /* Intra-coded */
90 case PIC_FLAG_CODING_TYPE_P:
91 return 'P'; /* Forward-predicted */
92 case PIC_FLAG_CODING_TYPE_B:
93 return 'B'; /* Bidirectionally-predicted */
94 case PIC_FLAG_CODING_TYPE_D:
95 return 'D'; /* DC-coded */
96 default:
97 return '?'; /* Say what? */
98 }
99}
100#endif /* defined(DEBUG) || defined(SIMULATOR) */
101
102/* Multi-use:
103 * 1) Find the sequence header and initialize video out
104 * 2) Find the end of the final frame
105 */
106static int video_str_scan(struct video_thread_data *td,
107 struct str_sync_data *sd)
108{
109 int retval = STREAM_ERROR;
110 uint32_t time = INVALID_TIMESTAMP;
111 uint32_t period = 0;
112 struct stream tmp_str;
113
114 tmp_str.id = video_str.id;
115 tmp_str.hdr.pos = sd->sk.pos;
116 tmp_str.hdr.limit = sd->sk.pos + sd->sk.len;
117
118 mpeg2_reset(td->mpeg2dec, false);
119 mpeg2_skip(td->mpeg2dec, 1);
120
121 while (1)
122 {
123 mpeg2_state_t mp2state = mpeg2_parse(td->mpeg2dec);
124 rb->yield();
125
126 switch (mp2state)
127 {
128 case STATE_BUFFER:
129 switch (parser_get_next_data(&tmp_str, STREAM_PM_RANDOM_ACCESS))
130 {
131 case STREAM_DATA_END:
132 DEBUGF("video_stream_scan:STREAM_DATA_END\n");
133 goto scan_finished;
134
135 case STREAM_OK:
136 if (tmp_str.pkt_flags & PKT_HAS_TS)
137 mpeg2_tag_picture(td->mpeg2dec, tmp_str.pts, 0);
138
139 mpeg2_buffer(td->mpeg2dec, tmp_str.curr_packet,
140 tmp_str.curr_packet_end);
141 td->info = mpeg2_info(td->mpeg2dec);
142 break;
143 }
144 break;
145
146 case STATE_SEQUENCE:
147 DEBUGF("video_stream_scan:STATE_SEQUENCE\n");
148 vo_setup(td->info->sequence);
149
150 if (td->ev.id == VIDEO_GET_SIZE)
151 {
152 retval = STREAM_OK;
153 goto scan_finished;
154 }
155 break;
156
157 case STATE_SLICE:
158 case STATE_END:
159 case STATE_INVALID_END:
160 {
161 if (td->info->display_picture == NULL)
162 break;
163
164 switch (td->ev.id)
165 {
166 case STREAM_SYNC:
167 retval = STREAM_OK;
168 goto scan_finished;
169
170 case STREAM_FIND_END_TIME:
171 if (td->info->display_picture->flags & PIC_FLAG_TAGS)
172 time = td->info->display_picture->tag;
173 else if (time != INVALID_TIMESTAMP)
174 time += period;
175
176 period = TC_TO_TS(td->info->sequence->frame_period);
177 break;
178 }
179
180 break;
181 }
182
183 default:
184 break;
185 }
186 }
187
188scan_finished:
189
190 if (td->ev.id == STREAM_FIND_END_TIME)
191 {
192 if (time != INVALID_TIMESTAMP)
193 {
194 sd->time = time + period;
195 retval = STREAM_PERFECT_MATCH;
196 }
197 else
198 {
199 retval = STREAM_NOT_FOUND;
200 }
201 }
202
203 mpeg2_skip(td->mpeg2dec, 0);
204 return retval;
205}
206
207static bool init_sequence(struct video_thread_data *td)
208{
209 struct str_sync_data sd;
210
211 sd.time = 0; /* Ignored */
212 sd.sk.pos = 0;
213 sd.sk.len = 1024*1024;
214 sd.sk.dir = SSCAN_FORWARD;
215
216 return video_str_scan(td, &sd) == STREAM_OK;
217}
218
219static bool check_needs_sync(struct video_thread_data *td, uint32_t time)
220{
221 uint32_t syncf_end;
222
223 DEBUGF("check_needs_sync:\n");
224 if (td->info == NULL || td->info->display_fbuf == NULL)
225 {
226 DEBUGF(" no fbuf\n");
227 return true;
228 }
229
230 if (td->syncf_perfect == 0)
231 {
232 DEBUGF(" no frame\n");
233 return true;
234 }
235
236 time = clip_time(&video_str, time);
237 syncf_end = td->syncf_time + td->syncf_period;
238
239 DEBUGF(" sft:%u t:%u sfte:%u\n", (unsigned)td->syncf_time,
240 (unsigned)time, (unsigned)syncf_end);
241
242 if (time < td->syncf_time)
243 return true;
244
245 if (time >= syncf_end)
246 return time < video_str.end_pts || syncf_end < video_str.end_pts;
247
248 return false;
249}
250
251/* Do any needed decoding/slide up to the specified time */
252static int sync_decoder(struct video_thread_data *td,
253 struct str_sync_data *sd)
254{
255 int retval = STREAM_ERROR;
256 int ipic = 0, ppic = 0;
257 uint32_t time = clip_time(&video_str, sd->time);
258
259 td->syncf_perfect = 0;
260 td->syncf_time = 0;
261 td->syncf_period = 0;
262 td->curr_time = 0;
263 td->period = 0;
264
265 /* Sometimes theres no sequence headers nearby and libmpeg2 may have reset
266 * fully at some point */
267 if ((td->info == NULL || td->info->sequence == NULL) && !init_sequence(td))
268 {
269 DEBUGF("sync_decoder=>init_sequence failed\n");
270 goto sync_finished;
271 }
272
273 video_str.hdr.pos = sd->sk.pos;
274 video_str.hdr.limit = sd->sk.pos + sd->sk.len;
275 mpeg2_reset(td->mpeg2dec, false);
276 mpeg2_skip(td->mpeg2dec, 1);
277
278 while (1)
279 {
280 mpeg2_state_t mp2state = mpeg2_parse(td->mpeg2dec);
281 rb->yield();
282
283 switch (mp2state)
284 {
285 case STATE_BUFFER:
286 switch (parser_get_next_data(&video_str, STREAM_PM_RANDOM_ACCESS))
287 {
288 case STREAM_DATA_END:
289 DEBUGF("sync_decoder:STR_DATA_END\n");
290 if (td->info && td->info->display_picture &&
291 !(td->info->display_picture->flags & PIC_FLAG_SKIP))
292 {
293 /* No frame matching the time was found up to the end of
294 * the stream - consider a perfect match since no better
295 * can be made */
296 retval = STREAM_PERFECT_MATCH;
297 td->syncf_perfect = 1;
298 }
299 goto sync_finished;
300
301 case STREAM_OK:
302 if (video_str.pkt_flags & PKT_HAS_TS)
303 mpeg2_tag_picture(td->mpeg2dec, video_str.pts, 0);
304
305 mpeg2_buffer(td->mpeg2dec, video_str.curr_packet,
306 video_str.curr_packet_end);
307 td->info = mpeg2_info(td->mpeg2dec);
308 break;
309 }
310 break;
311
312 case STATE_SEQUENCE:
313 DEBUGF(" STATE_SEQUENCE\n");
314 vo_setup(td->info->sequence);
315 break;
316
317 case STATE_GOP:
318 DEBUGF(" STATE_GOP: (%s)\n",
319 (td->info->gop->flags & GOP_FLAG_CLOSED_GOP) ?
320 "closed" : "open");
321 break;
322
323 case STATE_PICTURE:
324 {
325 int type = td->info->current_picture->flags
326 & PIC_MASK_CODING_TYPE;
327
328 switch (type)
329 {
330 case PIC_FLAG_CODING_TYPE_I:
331 /* I-frame; start decoding */
332 mpeg2_skip(td->mpeg2dec, 0);
333 ipic = 1;
334 break;
335 case PIC_FLAG_CODING_TYPE_P:
336 /* P-frames don't count without I-frames */
337 ppic = ipic;
338 break;
339 }
340
341 if (td->info->current_picture->flags & PIC_FLAG_TAGS)
342 {
343 DEBUGF(" STATE_PICTURE (%c): %u\n", pic_coding_type_char(type),
344 (unsigned)td->info->current_picture->tag);
345 }
346 else
347 {
348 DEBUGF(" STATE_PICTURE (%c): -\n", pic_coding_type_char(type));
349 }
350
351 break;
352 }
353
354 case STATE_SLICE:
355 case STATE_END:
356 case STATE_INVALID_END:
357 {
358 uint32_t syncf_end;
359
360 if (td->info->display_picture == NULL)
361 {
362 DEBUGF(" td->info->display_picture == NULL\n");
363 break; /* No picture */
364 }
365
366 int type = td->info->display_picture->flags
367 & PIC_MASK_CODING_TYPE;
368
369 if (td->info->display_picture->flags & PIC_FLAG_TAGS)
370 {
371 td->syncf_time = td->info->display_picture->tag;
372 DEBUGF(" frame tagged:%u (%c%s)\n", (unsigned)td->syncf_time,
373 pic_coding_type_char(type),
374 (td->info->display_picture->flags & PIC_FLAG_SKIP) ?
375 " skipped" : "");
376 }
377 else
378 {
379 td->syncf_time += td->syncf_period;
380 DEBUGF(" add period:%u (%c%s)\n", (unsigned)td->syncf_time,
381 pic_coding_type_char(type),
382 (td->info->display_picture->flags & PIC_FLAG_SKIP) ?
383 " skipped" : "");
384 }
385
386 td->syncf_period = TC_TO_TS(td->info->sequence->frame_period);
387 syncf_end = td->syncf_time + td->syncf_period;
388
389 DEBUGF(" ft:%u t:%u fe:%u (%c%s)",
390 (unsigned)td->syncf_time,
391 (unsigned)time,
392 (unsigned)(td->syncf_time + td->syncf_period),
393 pic_coding_type_char(type),
394 (td->info->display_picture->flags & PIC_FLAG_SKIP) ?
395 " skipped" : "");
396
397 td->curr_time = TS_TO_TICKS(td->syncf_time);
398 td->period = TS_TO_TICKS(td->syncf_period);
399
400 if (syncf_end <= time && syncf_end < video_str.end_pts)
401 {
402 /* Still too early and have not hit at EOS */
403 DEBUGF(" too early\n");
404 break;
405 }
406 else if (!(td->info->display_picture->flags & PIC_FLAG_SKIP))
407 {
408 /* One perfect point if dependent frames were decoded */
409 td->syncf_perfect = ipic;
410
411 if (type == PIC_FLAG_CODING_TYPE_B)
412 td->syncf_perfect &= ppic;
413
414 if ((td->syncf_time <= time && time < syncf_end) ||
415 syncf_end >= video_str.end_pts)
416 {
417 /* One perfect point for matching time goal */
418 DEBUGF(" ft<=t<fe\n");
419 td->syncf_perfect++;
420 }
421 else
422 {
423 DEBUGF(" ft>t\n");
424 }
425
426 /* Two or more perfect points = perfect match - yay! */
427 retval = (td->syncf_perfect >= 2) ?
428 STREAM_PERFECT_MATCH : STREAM_MATCH;
429 }
430 else
431 {
432 /* Too late, no I-Frame yet */
433 DEBUGF("\n");
434 }
435
436 goto sync_finished;
437 }
438
439 default:
440 break;
441 }
442
443 rb->yield();
444 } /* end while */
445
446sync_finished:
447 mpeg2_skip(td->mpeg2dec, 0);
448 return retval;
449}
450
451/* This only returns to play or quit */
452static void video_thread_msg(struct video_thread_data *td)
453{
454 while (1)
455 {
456 intptr_t reply = 0;
457
458 switch (td->ev.id)
459 {
460 case STREAM_PLAY:
461 td->status = STREAM_PLAYING;
462
463 switch (td->state)
464 {
465 case TSTATE_RENDER_WAIT:
466 /* Settings may have changed to nonlimited - just draw
467 * what was previously being waited for */
468 if (!settings.limitfps)
469 td->state = TSTATE_RENDER;
470 case TSTATE_DECODE:
471 case TSTATE_RENDER:
472 break;
473
474 case TSTATE_INIT:
475 /* Begin decoding state */
476 td->state = TSTATE_DECODE;
477 break;
478
479 case TSTATE_EOS:
480 /* At end of stream - no playback possible so fire the
481 * completion event */
482 stream_generate_event(&video_str, STREAM_EV_COMPLETE, 0);
483 break;
484 }
485
486 reply = td->state != TSTATE_EOS;
487 break;
488
489 case STREAM_PAUSE:
490 td->status = STREAM_PAUSED;
491 reply = td->state != TSTATE_EOS;
492 break;
493
494 case STREAM_STOP:
495 if (td->state == TSTATE_DATA)
496 stream_clear_notify(&audio_str, DISK_BUF_DATA_NOTIFY);
497
498 td->status = STREAM_STOPPED;
499 td->state = TSTATE_EOS;
500 reply = true;
501 break;
502
503 case VIDEO_DISPLAY_IS_VISIBLE:
504 reply = vo_is_visible();
505 break;
506
507 case VIDEO_DISPLAY_SHOW:
508 /* Show video and draw the last frame we had if any or reveal the
509 * underlying framebuffer if hiding */
510 reply = vo_show(!!td->ev.data);
511
512#ifdef HAVE_LCD_COLOR
513 /* Match graylib behavior as much as possible */
514 if (!td->ev.data == !reply)
515 break;
516
517 if (td->ev.data)
518 {
519 if (td->info != NULL && td->info->display_fbuf != NULL)
520 vo_draw_frame(td->info->display_fbuf->buf);
521 }
522 else
523 {
524 IF_COP(invalidate_icache());
525 rb->lcd_update();
526 }
527#else
528 GRAY_FLUSH_ICACHE();
529#endif
530 break;
531
532 case STREAM_RESET:
533 if (td->state == TSTATE_DATA)
534 stream_clear_notify(&audio_str, DISK_BUF_DATA_NOTIFY);
535
536 td->state = TSTATE_INIT;
537 td->status = STREAM_STOPPED;
538
539 /* Reset operational info but not sync info */
540 td->eta_stream = UINT32_MAX;
541 td->eta_video = 0;
542 td->eta_early = 0;
543 td->eta_late = 0;
544 td->frame_drop_level = 0;
545 td->skip_level = 0;
546 td->num_drawn = 0;
547 td->num_skipped = 0;
548 td->last_showfps = *rb->current_tick - HZ;
549 td->last_render = td->last_showfps;
550
551 reply = true;
552 break;
553
554 case STREAM_NEEDS_SYNC:
555 reply = check_needs_sync(td, td->ev.data);
556 break;
557
558 case STREAM_SYNC:
559 if (td->state == TSTATE_INIT)
560 reply = sync_decoder(td, (struct str_sync_data *)td->ev.data);
561 break;
562
563 case DISK_BUF_DATA_NOTIFY:
564 /* Our bun is done */
565 if (td->state != TSTATE_DATA)
566 break;
567
568 td->state = TSTATE_DECODE;
569 str_data_notify_received(&video_str);
570 break;
571
572 case VIDEO_PRINT_THUMBNAIL:
573 /* Print a thumbnail of whatever was last decoded - scale and
574 * position to fill the specified rectangle */
575 if (td->info != NULL && td->info->display_fbuf != NULL)
576 {
577 vo_draw_frame_thumb(td->info->display_fbuf->buf,
578 (struct vo_rect *)td->ev.data);
579 reply = true;
580 }
581 break;
582
583 case VIDEO_PRINT_FRAME:
584 /* Print the last frame decoded */
585 if (td->info != NULL && td->info->display_fbuf != NULL)
586 {
587 vo_draw_frame(td->info->display_fbuf->buf);
588 reply = true;
589 }
590 break;
591
592 case VIDEO_GET_SIZE:
593 {
594 if (td->state != TSTATE_INIT)
595 break;
596
597 if (init_sequence(td))
598 {
599 reply = true;
600 vo_dimensions((struct vo_ext *)td->ev.data);
601 }
602 break;
603 }
604
605 case STREAM_FIND_END_TIME:
606 if (td->state != TSTATE_INIT)
607 {
608 reply = STREAM_ERROR;
609 break;
610 }
611
612 reply = video_str_scan(td, (struct str_sync_data *)td->ev.data);
613 break;
614
615#ifdef GRAY_CACHE_MAINT
616 case VIDEO_GRAY_CACHEOP:
617 td->ev.data ?
618 GRAY_INVALIDATE_ICACHE() :
619 GRAY_FLUSH_ICACHE();
620 break;
621#endif
622
623 case STREAM_QUIT:
624 /* Time to go - make thread exit */
625 td->state = TSTATE_EOS;
626 return;
627 }
628
629 str_reply_msg(&video_str, reply);
630
631 if (td->status == STREAM_PLAYING)
632 {
633 switch (td->state)
634 {
635 case TSTATE_DECODE:
636 case TSTATE_RENDER:
637 case TSTATE_RENDER_WAIT:
638 /* These return when in playing state */
639 return;
640 }
641 }
642
643 str_get_msg(&video_str, &td->ev);
644 }
645}
646
647static void video_thread(void)
648{
649 struct video_thread_data td;
650
651 td.status = STREAM_STOPPED;
652 td.state = TSTATE_EOS;
653 td.mpeg2dec = mpeg2_init();
654 td.info = NULL;
655 td.syncf_perfect = 0;
656 td.syncf_time = 0;
657 td.syncf_period = 0;
658
659 if (td.mpeg2dec == NULL)
660 {
661 td.status = STREAM_ERROR;
662 /* Loop and wait for quit message */
663 while (1)
664 {
665 str_get_msg(&video_str, &td.ev);
666 if (td.ev.id == STREAM_QUIT)
667 return;
668 str_reply_msg(&video_str, STREAM_ERROR);
669 }
670 }
671
672 vo_init();
673
674 goto message_wait;
675
676 while (1)
677 {
678 mpeg2_state_t mp2state;
679 td.state = TSTATE_DECODE;
680
681 /* Check for any pending messages and process them */
682 if (str_have_msg(&video_str))
683 {
684 message_wait:
685 /* Wait for a message to be queued */
686 str_get_msg(&video_str, &td.ev);
687
688 message_process:
689 /* Process a message already dequeued */
690 video_thread_msg(&td);
691
692 switch (td.state)
693 {
694 /* These states are the only ones that should return */
695 case TSTATE_DECODE: goto picture_decode;
696 case TSTATE_RENDER: goto picture_draw;
697 case TSTATE_RENDER_WAIT: goto picture_wait;
698 /* Anything else is interpreted as an exit */
699 default:
700 vo_cleanup();
701 mpeg2_close(td.mpeg2dec);
702 return;
703 }
704 }
705
706 picture_decode:
707 mp2state = mpeg2_parse (td.mpeg2dec);
708 rb->yield();
709
710 switch (mp2state)
711 {
712 case STATE_BUFFER:
713 /* Request next packet data */
714 switch (parser_get_next_data(&video_str, STREAM_PM_STREAMING))
715 {
716 case STREAM_DATA_NOT_READY:
717 /* Wait for data to be buffered */
718 td.state = TSTATE_DATA;
719 goto message_wait;
720
721 case STREAM_DATA_END:
722 /* No more data. */
723 td.state = TSTATE_EOS;
724 if (td.status == STREAM_PLAYING)
725 stream_generate_event(&video_str, STREAM_EV_COMPLETE, 0);
726 goto message_wait;
727
728 case STREAM_OK:
729 if (video_str.pkt_flags & PKT_HAS_TS)
730 mpeg2_tag_picture(td.mpeg2dec, video_str.pts, 0);
731
732 mpeg2_buffer(td.mpeg2dec, video_str.curr_packet,
733 video_str.curr_packet_end);
734 td.info = mpeg2_info(td.mpeg2dec);
735 break;
736 }
737 break;
738
739 case STATE_SEQUENCE:
740 /* New video sequence, inform output of any changes */
741 vo_setup(td.info->sequence);
742 break;
743
744 case STATE_PICTURE:
745 {
746 int skip = 0; /* Assume no skip */
747
748 if (td.frame_drop_level >= 1 || td.skip_level > 0)
749 {
750 /* A frame will be dropped in the decoder */
751
752 /* Frame type: I/P/B/D */
753 int type = td.info->current_picture->flags
754 & PIC_MASK_CODING_TYPE;
755
756 switch (type)
757 {
758 case PIC_FLAG_CODING_TYPE_I:
759 case PIC_FLAG_CODING_TYPE_D:
760 /* Level 5: Things are extremely late and all frames will
761 be dropped until the next key frame */
762 if (td.frame_drop_level >= 1)
763 td.frame_drop_level = 0; /* Key frame - reset drop level */
764 if (td.skip_level >= 5)
765 {
766 td.frame_drop_level = 1;
767 td.skip_level = 0; /* reset */
768 }
769 break;
770 case PIC_FLAG_CODING_TYPE_P:
771 /* Level 4: Things are very late and all frames will be
772 dropped until the next key frame */
773 if (td.skip_level >= 4)
774 {
775 td.frame_drop_level = 1;
776 td.skip_level = 0; /* reset */
777 }
778 break;
779 case PIC_FLAG_CODING_TYPE_B:
780 /* We want to drop something, so this B frame won't even
781 be decoded. Drawing can happen on the next frame if so
782 desired. Bring the level down as skips are done. */
783 skip = 1;
784 if (td.skip_level > 0)
785 td.skip_level--;
786 }
787
788 skip |= td.frame_drop_level;
789 }
790
791 mpeg2_skip(td.mpeg2dec, skip);
792 break;
793 }
794
795 case STATE_SLICE:
796 case STATE_END:
797 case STATE_INVALID_END:
798 {
799 int32_t offset; /* Tick adjustment to keep sync */
800
801 /* draw current picture */
802 if (td.info->display_fbuf == NULL)
803 break; /* No picture */
804
805 /* Get presentation times in audio samples - quite accurate
806 enough - add previous frame duration if not stamped */
807 td.curr_time = (td.info->display_picture->flags & PIC_FLAG_TAGS) ?
808 TS_TO_TICKS(td.info->display_picture->tag) :
809 (td.curr_time + td.period);
810
811 td.period = TC_TO_TICKS(td.info->sequence->frame_period);
812
813 /* No limiting => no dropping - draw this frame */
814 if (!settings.limitfps)
815 {
816 goto picture_draw;
817 }
818
819 td.eta_video = td.curr_time;
820 td.eta_stream = stream_get_time();
821
822 /* How early/late are we? > 0 = late, < 0 early */
823 offset = td.eta_stream - td.eta_video;
824
825 if (!settings.skipframes)
826 {
827 /* Make no effort to determine whether this frame should be
828 drawn or not since no action can be taken to correct the
829 situation. We'll just wait if we're early and correct for
830 lateness as much as possible. */
831 if (offset < 0)
832 offset = 0;
833
834 td.eta_late = AVERAGE(td.eta_late, offset, 4);
835 offset = td.eta_late;
836
837 if ((uint32_t)offset > td.eta_video)
838 offset = td.eta_video;
839
840 td.eta_video -= offset;
841 goto picture_wait;
842 }
843
844 /** Possibly skip this frame **/
845
846 /* Frameskipping has the following order of preference:
847 *
848 * Frame Type Who Notes/Rationale
849 * B decoder arbitrarily drop - no decode or draw
850 * Any renderer arbitrarily drop - will be I/D/P
851 * P decoder must wait for I/D-frame - choppy
852 * I/D decoder must wait for I/D-frame - choppy
853 *
854 * If a frame can be drawn and it has been at least 1/2 second,
855 * the image will be updated no matter how late it is just to
856 * avoid looking stuck.
857 */
858
859 /* If we're late, set the eta to play the frame early so
860 we may catch up. If early, especially because of a drop,
861 mitigate a "snap" by moving back gradually. */
862 if (offset >= 0) /* late or on time */
863 {
864 td.eta_early = 0; /* Not early now :( */
865
866 td.eta_late = AVERAGE(td.eta_late, offset, 4);
867 offset = td.eta_late;
868
869 if ((uint32_t)offset > td.eta_video)
870 offset = td.eta_video;
871
872 td.eta_video -= offset;
873 }
874 else
875 {
876 td.eta_late = 0; /* Not late now :) */
877
878 if (offset > td.eta_early)
879 {
880 /* Just dropped a frame and we're now early or we're
881 coming back from being early */
882 td.eta_early = offset;
883 if ((uint32_t)-offset > td.eta_video)
884 offset = -td.eta_video;
885
886 td.eta_video += offset;
887 }
888 else
889 {
890 /* Just early with an offset, do exponential drift back */
891 if (td.eta_early != 0)
892 {
893 td.eta_early = AVERAGE(td.eta_early, 0, 8);
894 td.eta_video = ((uint32_t)-td.eta_early > td.eta_video) ?
895 0 : (td.eta_video + td.eta_early);
896 }
897
898 offset = td.eta_early;
899 }
900 }
901
902 if (td.info->display_picture->flags & PIC_FLAG_SKIP)
903 {
904 /* This frame was set to skip so skip it after having updated
905 timing information */
906 td.num_skipped++;
907 td.eta_early = INT32_MIN;
908 goto picture_skip;
909 }
910
911 if (td.skip_level == 3 &&
912 TIME_BEFORE(*rb->current_tick, td.last_render + HZ/2))
913 {
914 /* Render drop was set previously but nothing was dropped in the
915 decoder or it's been to long since drawing the last frame. */
916 td.skip_level = 0;
917 td.num_skipped++;
918 td.eta_early = INT32_MIN;
919 goto picture_skip;
920 }
921
922 /* At this point a frame _will_ be drawn - a skip may happen on
923 the next however */
924 td.skip_level = 0;
925
926 if (offset > CLOCK_RATE*110/1000)
927 {
928 /* Decide which skip level is needed in order to catch up */
929
930 /* TODO: Calculate this rather than if...else - this is rather
931 exponential though */
932 if (offset > CLOCK_RATE*367/1000)
933 td.skip_level = 5; /* Decoder skip: I/D */
934 if (offset > CLOCK_RATE*233/1000)
935 td.skip_level = 4; /* Decoder skip: P */
936 else if (offset > CLOCK_RATE*167/1000)
937 td.skip_level = 3; /* Render skip */
938 else if (offset > CLOCK_RATE*133/1000)
939 td.skip_level = 2; /* Decoder skip: B */
940 else
941 td.skip_level = 1; /* Decoder skip: B */
942 }
943
944 picture_wait:
945 td.state = TSTATE_RENDER_WAIT;
946
947 /* Wait until time catches up */
948 while (td.eta_video > td.eta_stream)
949 {
950 /* Watch for messages while waiting for the frame time */
951 int32_t eta_remaining = td.eta_video - td.eta_stream;
952 if (eta_remaining > CLOCK_RATE/HZ)
953 {
954 /* Several ticks to wait - do some sleeping */
955 int timeout = (eta_remaining - HZ) / (CLOCK_RATE/HZ);
956 str_get_msg_w_tmo(&video_str, &td.ev, MAX(timeout, 1));
957 if (td.ev.id != SYS_TIMEOUT)
958 goto message_process;
959 }
960 else
961 {
962 /* Just a little left - spin and be accurate */
963 rb->priority_yield();
964 if (str_have_msg(&video_str))
965 goto message_wait;
966 }
967
968 td.eta_stream = stream_get_time();
969 }
970
971 picture_draw:
972 /* Record last frame time */
973 td.last_render = *rb->current_tick;
974 vo_draw_frame(td.info->display_fbuf->buf);
975 td.num_drawn++;
976
977 picture_skip:
978 if (!settings.showfps)
979 break;
980
981 if (TIME_BEFORE(*rb->current_tick, td.last_showfps + HZ))
982 break;
983
984 /* Calculate and display fps */
985 draw_fps(&td);
986 break;
987 }
988
989 default:
990 break;
991 }
992
993 rb->yield();
994 } /* end while */
995}
996
997/* Initializes the video thread */
998bool video_thread_init(void)
999{
1000 intptr_t rep;
1001
1002 IF_COP(flush_icache());
1003
1004 video_str.hdr.q = &video_str_queue;
1005 rb->queue_init(video_str.hdr.q, false);
1006 rb->queue_enable_queue_send(video_str.hdr.q, &video_str_queue_send);
1007
1008 /* We put the video thread on another processor for multi-core targets. */
1009 video_str.thread = rb->create_thread(
1010 video_thread, video_stack, VIDEO_STACKSIZE, 0,
1011 "mpgvideo" IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, COP));
1012
1013 if (video_str.thread == NULL)
1014 return false;
1015
1016 /* Wait for thread to initialize */
1017 rep = str_send_msg(&video_str, STREAM_NULL, 0);
1018 IF_COP(invalidate_icache());
1019
1020 return rep == 0; /* Normally STREAM_NULL should be ignored */
1021}
1022
1023/* Terminates the video thread */
1024void video_thread_exit(void)
1025{
1026 if (video_str.thread != NULL)
1027 {
1028 str_post_msg(&video_str, STREAM_QUIT, 0);
1029 rb->thread_wait(video_str.thread);
1030 IF_COP(invalidate_icache());
1031 video_str.thread = NULL;
1032 }
1033 else
1034 {
1035 /* Some things were done before thread creation */
1036#ifndef HAVE_LCD_COLOR
1037 gray_release();
1038#endif
1039 }
1040}
diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config
index 27fae18b4c..375128e7c4 100644
--- a/apps/plugins/viewers.config
+++ b/apps/plugins/viewers.config
@@ -28,6 +28,8 @@ wav,viewers/test_codec,-
28bmp,apps/rockpaint,11 28bmp,apps/rockpaint,11
29mpg,viewers/mpegplayer,4 29mpg,viewers/mpegplayer,4
30mpeg,viewers/mpegplayer,4 30mpeg,viewers/mpegplayer,4
31mpv,viewers/mpegplayer,4
32m2v,viewers/mpegplayer,4
31iriver,viewers/iriver_flash,3 33iriver,viewers/iriver_flash,3
32tap,viewers/zxbox,12 34tap,viewers/zxbox,12
33sna,viewers/zxbox,12 35sna,viewers/zxbox,12