diff options
author | Michael Sevakis <jethead71@rockbox.org> | 2007-12-29 19:46:35 +0000 |
---|---|---|
committer | Michael Sevakis <jethead71@rockbox.org> | 2007-12-29 19:46:35 +0000 |
commit | a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1 (patch) | |
tree | d393a23d83549f99772bb156e59ffb88725148b6 | |
parent | 1d0f6b90ff43776e55b4b9f062c9bea3f99aa768 (diff) | |
download | rockbox-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
34 files changed, 7828 insertions, 2742 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 | ||
569 | int plugin_load(const char* plugin, void* parameter) | 586 | int 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 | ||
19 | slice.c | 19 | slice.c |
20 | video_out_rockbox.c | 20 | video_out_rockbox.c |
21 | video_thread.c | ||
22 | pcm_output.c | ||
23 | audio_thread.c | ||
24 | disk_buf.c | ||
21 | mpeg_settings.c | 25 | mpeg_settings.c |
26 | stream_mgr.c | ||
22 | mpegplayer.c | 27 | mpegplayer.c |
28 | mpeg_linkedlist.c | ||
29 | mpeg_parser.c | ||
30 | mpeg_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 | |||
28 | extern struct plugin_api* rb; | ||
29 | 26 | ||
30 | /* Main allocator */ | 27 | /* Main allocator */ |
31 | static off_t mem_ptr; | 28 | static off_t mem_ptr; |
@@ -33,9 +30,58 @@ static size_t bufsize; | |||
33 | static unsigned char* mallocbuf; | 30 | static unsigned char* mallocbuf; |
34 | 31 | ||
35 | /* libmpeg2 allocator */ | 32 | /* libmpeg2 allocator */ |
36 | static off_t mpeg2_mem_ptr; | 33 | static off_t mpeg2_mem_ptr NOCACHEBSS_ATTR; |
37 | static size_t mpeg2_bufsize; | 34 | static size_t mpeg2_bufsize NOCACHEBSS_ATTR; |
38 | static unsigned char *mpeg2_mallocbuf; | 35 | static unsigned char *mpeg2_mallocbuf NOCACHEBSS_ATTR; |
36 | static unsigned char *mpeg2_bufallocbuf NOCACHEBSS_ATTR; | ||
37 | |||
38 | #if defined(DEBUG) || defined(SIMULATOR) | ||
39 | const 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 | ||
40 | static void * mpeg_malloc_internal (unsigned char *mallocbuf, | 86 | static 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 | ||
67 | size_t mpeg_alloc_init(unsigned char *buf, size_t mallocsize, | 116 | void *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 | |||
126 | bool 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 | ||
99 | void *memcpy(void *dest, const void *src, size_t n) { | 154 | void *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 */ | ||
110 | void * mpeg2_malloc(unsigned size, mpeg2_alloc_t reason) | 167 | void * 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 | ||
116 | void mpeg2_free(void *ptr) | 178 | /* allocate dedicated buffer - memory behind buffer pointer becomes dedicated |
179 | so order is important */ | ||
180 | void * 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 */ | ||
192 | void * 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 */ | ||
202 | void 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 */ |
122 | void * codec_malloc(size_t size) | 209 | void * 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 | ||
128 | void * codec_calloc(size_t nmemb, size_t size) | 222 | void * 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 | ||
141 | void codec_free(void* ptr) | 236 | void 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 | ||
146 | void *memmove(void *dest, const void *src, size_t n) | 245 | void *memmove(void *dest, const void *src, size_t n) |
diff --git a/apps/plugins/mpegplayer/audio_thread.c b/apps/plugins/mpegplayer/audio_thread.c new file mode 100644 index 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 **/ | ||
27 | struct pts_queue_slot; | ||
28 | struct 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 */ | ||
38 | static uint32_t* audio_stack; | ||
39 | static size_t audio_stack_size; /* Keep gcc happy and init */ | ||
40 | #define AUDIO_STACKSIZE (9*1024) | ||
41 | #ifndef SIMULATOR | ||
42 | static uint32_t codec_stack_copy[AUDIO_STACKSIZE / sizeof(uint32_t)]; | ||
43 | #endif | ||
44 | static struct event_queue audio_str_queue NOCACHEBSS_ATTR; | ||
45 | static struct queue_sender_list audio_str_queue_send NOCACHEBSS_ATTR; | ||
46 | struct stream audio_str IBSS_ATTR; | ||
47 | |||
48 | /* libmad related definitions */ | ||
49 | static struct mad_stream stream IBSS_ATTR; | ||
50 | static struct mad_frame frame IBSS_ATTR; | ||
51 | static struct mad_synth synth IBSS_ATTR; | ||
52 | |||
53 | /* 2567 bytes */ | ||
54 | static 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 | ||
61 | static mad_fixed_t mad_frame_overlap[2][32][18] IBSS_ATTR; | ||
62 | #else | ||
63 | static 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) | ||
70 | struct 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 */ | ||
79 | struct | ||
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 | |||
91 | static inline int audiodesc_queue_count(void) | ||
92 | { | ||
93 | return audio_queue.write - audio_queue.read; | ||
94 | } | ||
95 | |||
96 | static 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 */ | ||
103 | static 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 */ | ||
115 | static 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 */ | ||
125 | static 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 */ | ||
131 | static 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 */ | ||
137 | static 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 | |||
148 | static 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 | |||
155 | static 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 */ | ||
226 | static 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 */ | ||
242 | static 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 | |||
332 | sync_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 | |||
356 | static 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 | |||
460 | static 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 */ | ||
666 | bool 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 */ | ||
730 | void 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) | |||
521 | void mpeg2_close (mpeg2dec_t * mpeg2dec) | 521 | void 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 | |||
24 | static struct mutex disk_buf_mtx NOCACHEBSS_ATTR; | ||
25 | static struct event_queue disk_buf_queue NOCACHEBSS_ATTR; | ||
26 | static struct queue_sender_list disk_buf_queue_send NOCACHEBSS_ATTR; | ||
27 | static uint32_t disk_buf_stack[DEFAULT_STACK_SIZE*2/sizeof(uint32_t)]; | ||
28 | |||
29 | struct disk_buf disk_buf NOCACHEBSS_ATTR; | ||
30 | static struct list_item nf_list; | ||
31 | |||
32 | static inline void disk_buf_lock(void) | ||
33 | { | ||
34 | rb->mutex_lock(&disk_buf_mtx); | ||
35 | } | ||
36 | |||
37 | static inline void disk_buf_unlock(void) | ||
38 | { | ||
39 | rb->mutex_unlock(&disk_buf_mtx); | ||
40 | } | ||
41 | |||
42 | static 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 | |||
49 | static 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 | |||
89 | static 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 */ | ||
110 | static 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 */ | ||
116 | static inline void clear_data_notifies(void) | ||
117 | { | ||
118 | list_clear_all(&nf_list); | ||
119 | } | ||
120 | |||
121 | /* Background buffering when streaming */ | ||
122 | static 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 | |||
252 | static 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 | |||
336 | static 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 | |||
347 | static 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 | |||
372 | static 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 | |||
438 | static 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 */ | ||
516 | static 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. */ | ||
588 | ssize_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. */ | ||
624 | ssize_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 | |||
659 | off_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. */ | ||
698 | ssize_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. */ | ||
724 | ssize_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 | |||
753 | void * 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 | |||
763 | void 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 | |||
783 | int 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 | |||
816 | intptr_t disk_buf_send_msg(long id, intptr_t data) | ||
817 | { | ||
818 | return rb->queue_send(disk_buf.q, id, data); | ||
819 | } | ||
820 | |||
821 | void disk_buf_post_msg(long id, intptr_t data) | ||
822 | { | ||
823 | rb->queue_post(disk_buf.q, id, data); | ||
824 | } | ||
825 | |||
826 | void disk_buf_reply_msg(intptr_t retval) | ||
827 | { | ||
828 | rb->queue_reply(disk_buf.q, retval); | ||
829 | } | ||
830 | |||
831 | bool 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 | |||
898 | void 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 | |||
28 | enum | ||
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 | |||
52 | struct 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 */ | ||
61 | struct 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 | |||
87 | extern struct disk_buf disk_buf NOCACHEBSS_ATTR; | ||
88 | |||
89 | static 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 | |||
104 | bool disk_buf_init(void); | ||
105 | void disk_buf_exit(void); | ||
106 | |||
107 | int disk_buf_open(const char *filename); | ||
108 | void disk_buf_close(void); | ||
109 | ssize_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)) | ||
114 | ssize_t disk_buf_read(void *buffer, size_t size); | ||
115 | ssize_t disk_buf_lseek(off_t offset, int whence); | ||
116 | |||
117 | static inline off_t disk_buf_ftell(void) | ||
118 | { return disk_buf.offset; } | ||
119 | |||
120 | static inline ssize_t disk_buf_filesize(void) | ||
121 | { return disk_buf.filesize; } | ||
122 | |||
123 | ssize_t disk_buf_prepare_streaming(off_t pos, size_t len); | ||
124 | ssize_t disk_buf_set_streaming_window(off_t left, off_t right); | ||
125 | void * disk_buf_offset2ptr(off_t offset); | ||
126 | int disk_buf_check_streaming_window(off_t left, off_t right); | ||
127 | |||
128 | intptr_t disk_buf_send_msg(long id, intptr_t data); | ||
129 | void disk_buf_post_msg(long id, intptr_t data); | ||
130 | void 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 | ||
191 | void * mpeg2_malloc (unsigned size, mpeg2_alloc_t reason); | 191 | void * mpeg2_malloc (unsigned size, mpeg2_alloc_t reason); |
192 | #if 0 | ||
192 | void mpeg2_free (void * buf); | 193 | void mpeg2_free (void * buf); |
194 | #endif | ||
195 | /* allocates a dedicated buffer and locks all previous allocation in place */ | ||
196 | void * mpeg2_bufalloc(unsigned size, mpeg2_alloc_t reason); | ||
197 | /* clears all non-dedicated buffer space */ | ||
198 | void mpeg2_mem_reset(void); | ||
193 | void mpeg2_alloc_init(unsigned char* buf, int mallocsize); | 199 | void 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 */ | ||
5 | void * mpeg2_get_buf(size_t *size); | ||
6 | void *mpeg_malloc(size_t size, mpeg2_alloc_t reason); | ||
7 | /* Grabs all the buffer available sans margin */ | ||
8 | void *mpeg_malloc_all(size_t *size_out, mpeg2_alloc_t reason); | ||
9 | /* Initializes the malloc buffer with the given base buffer */ | ||
10 | bool 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 */ | ||
26 | void 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? */ | ||
32 | bool 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? */ | ||
38 | bool 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? */ | ||
44 | bool 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 */ | ||
67 | void 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 */ | ||
89 | void 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 */ | ||
120 | void 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. */ | ||
136 | void 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 | |||
24 | struct 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 */ | ||
36 | void list_initialize(struct list_item *master_list_head); | ||
37 | |||
38 | /* Are there items after the head item? */ | ||
39 | bool list_is_empty(struct list_item *head_item); | ||
40 | |||
41 | /* Does the item belong to a list? */ | ||
42 | bool list_is_item_listed(struct list_item *item); | ||
43 | |||
44 | /* Is the item a member in a particular list? */ | ||
45 | bool 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 */ | ||
49 | void list_remove_item(struct list_item *item); | ||
50 | |||
51 | /* Add a list item after the base item */ | ||
52 | void list_add_item(struct list_item *head_item, | ||
53 | struct list_item *item); | ||
54 | |||
55 | /* Clear list items after the head item */ | ||
56 | void 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. */ | ||
63 | typedef bool (*list_enum_callback_t)(struct list_item *item, intptr_t data); | ||
64 | |||
65 | void 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 */ | ||
27 | void 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 */ | ||
43 | void 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 **/ | ||
52 | void 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 | |||
62 | void 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 **/ | ||
78 | uint32_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 */ | ||
30 | enum 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 | */ | ||
152 | struct 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 */ | ||
165 | void 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 */ | ||
169 | void stream_scan_offset(struct stream_scan *sk, off_t by); | ||
170 | |||
171 | /** Audio helpers **/ | ||
172 | static 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 **/ | ||
181 | struct hms | ||
182 | { | ||
183 | unsigned int hrs; | ||
184 | unsigned int min; | ||
185 | unsigned int sec; | ||
186 | unsigned int frac; | ||
187 | }; | ||
188 | |||
189 | void ts_to_hms(uint32_t ts, struct hms *hms); | ||
190 | void 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. */ | ||
202 | uint32_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 | |||
24 | struct stream_parser str_parser NOCACHEBSS_ATTR; | ||
25 | |||
26 | static 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 */ | ||
39 | void 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 */ | ||
54 | void 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 */ | ||
72 | static 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 | |||
78 | static 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 */ | ||
84 | uint8_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 */ | ||
118 | unsigned 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 */ | ||
152 | uint32_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 | |||
179 | uint32_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 | |||
252 | static 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 | |||
269 | static 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. */ | ||
344 | static 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 | |||
542 | static 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; | ||
566 | try_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 | |||
618 | static 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 | */ | ||
657 | static 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. */ | ||
933 | static 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 | |||
1002 | intptr_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 */ | ||
1040 | uint32_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 | |||
1051 | void 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 | |||
1077 | int 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 | |||
1172 | void parser_close_stream(void) | ||
1173 | { | ||
1174 | stream_remove_streams(); | ||
1175 | parser_init_state(); | ||
1176 | } | ||
1177 | |||
1178 | bool 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 | ||
7 | extern struct plugin_api* rb; | ||
8 | |||
9 | struct mpeg_settings settings; | 8 | struct mpeg_settings settings; |
10 | 9 | ||
11 | ssize_t seek_PTS(int in_file, int startTime, int accept_button); | ||
12 | void display_thumb(int in_file); | ||
13 | |||
14 | #ifndef HAVE_LCD_COLOR | ||
15 | void 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 | ||
22 | enum slider_state_t {state0, state1, state2, | 14 | #define THUMB_DELAY (75*HZ/100) |
23 | state3, state4, state5} slider_state; | ||
24 | |||
25 | volatile 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 | ||
192 | void draw_slider(int slider_ypos, int max_val, int current_val) | 181 | static 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 | |||
194 | void 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 | |||
255 | bool 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); | 291 | uint32_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 | ||
223 | int get_start_time(int play_time, int in_file) | 313 | int 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 | ||
339 | enum mpeg_start_id mpeg_start_menu(int play_time, int in_file) | 501 | enum 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 | ||
434 | void clear_resume_count(void) | 604 | void 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 | ||
559 | void save_settings(void) | 728 | void 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 | |||
4 | enum mpeg_option_id | 12 | enum 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 | ||
43 | extern struct mpeg_settings settings; | 51 | extern struct mpeg_settings settings; |
44 | 52 | ||
45 | int get_start_time(int play_time, int in_file); | 53 | int get_start_time(uint32_t duration); |
46 | enum mpeg_start_id mpeg_start_menu(int play_time, int in_file); | 54 | enum mpeg_start_id mpeg_start_menu(uint32_t duration); |
47 | enum mpeg_menu_id mpeg_menu(void); | 55 | enum mpeg_menu_id mpeg_menu(void); |
48 | void init_settings(const char* filename); | 56 | void init_settings(const char* filename); |
49 | void save_settings(void); | 57 | void save_settings(void); |
diff --git a/apps/plugins/mpegplayer/mpeg_stream.h b/apps/plugins/mpegplayer/mpeg_stream.h new file mode 100644 index 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 | | |
32 | NOTES: | 32 | * Stream Data | | (clock intf./ |
33 | 33 | * Requests | File Cache drift adj.) | |
34 | mpegplayer is structured as follows: | 34 | * | Disk I/O |
35 | 35 | * Stream services | |
36 | 1) Video thread (running on the COP for PortalPlayer targets). | 36 | * (timing, etc.) |
37 | 2) Audio thread (running on the main CPU to maintain consistency with | 37 | * |
38 | the audio FIQ hander on PP). | 38 | * Thread list: |
39 | 3) 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. | |
41 | Using the main thread for buffering wastes the 8KB main stack which is | 41 | * |
42 | in IRAM. However, 8KB is not enough for the audio thread to run (it | 42 | * 2) Stream Manager thread - Handles playback state, events from streams |
43 | needs 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 |
44 | order 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 |
45 | stack (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 | |
47 | The button loop (and hence pause/resume, main menu and, in the future, | 47 | * formats. |
48 | seeking) is placed in the audio thread. This keeps it on the main CPU | 48 | * |
49 | in PP targets and also allows buffering to continue in the background | 49 | * 3) Buffer thread - Buffers data in the background, generates notifications |
50 | whilst 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 | |
52 | A/V sync is not yet implemented but is planned to be achieved by | 52 | * random access requests when the file cache is missed. |
53 | syncing the master clock with the audio, and then (as is currently | 53 | * |
54 | implemented), syncing video with the master clock. This can happen in | 54 | * 4) Video thread (running on the COP for PortalPlayer targets) - Decodes |
55 | the 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. | |
57 | Seeking should probably happen in the main thread, as that's where the | 57 | * |
58 | buffering 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 | |
60 | On PortalPlayer targets, the main CPU is not being fully utilised - | 60 | * the PCM buffer for rendering by the audio device. |
61 | the bottleneck is the video decoding on the COP. One way to improve | 61 | * |
62 | that 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 |
63 | lcd_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 |
64 | patches 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 | |
66 | Notes about MPEG files: | 66 | * by and exposed by the stream manager to other streams and implemented at |
67 | 67 | * the PCM level. | |
68 | MPEG System Clock is 27MHz - i.e. 27000000 ticks/second. | 68 | * |
69 | 69 | * Notes about MPEG files: | |
70 | FPS is represented in terms of a frame period - this is always an | 70 | * |
71 | integer number of 27MHz ticks. | 71 | * MPEG System Clock is 27MHz - i.e. 27000000 ticks/second. |
72 | 72 | * | |
73 | e.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 |
74 | 900900 27MHz ticks. | 74 | * integer number of 27MHz ticks. |
75 | 75 | * | |
76 | In 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. | |
78 | Working with Rockbox's 100Hz tick, the common frame rates would need | 78 | * |
79 | to be as follows: | 79 | * In libmpeg2, info->sequence->frame_period contains the frame_period. |
80 | 80 | * | |
81 | FPS | 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): |
83 | 10* | 2700000 | 10 | 4410 | 4800 | 83 | * |
84 | 12* | 2250000 | 8.3333 | 3675 | 4000 | 84 | * FPS | 27Mhz | 100Hz | 44.1KHz | 48KHz |
85 | 15* | 1800000 | 6.6667 | 2940 | 3200 | 85 | * --------|----------------------------------------------------------- |
86 | 23.9760 | 1126125 | 4.170833333 | 1839.3375 | 2002 | 86 | * 10* | 2700000 | 10 | 4410 | 4800 |
87 | 24 | 1125000 | 4.166667 | 1837.5 | 2000 | 87 | * 12* | 2250000 | 8.3333 | 3675 | 4000 |
88 | 25 | 1080000 | 4 | 1764 | 1920 | 88 | * 15* | 1800000 | 6.6667 | 2940 | 3200 |
89 | 29.9700 | 900900 | 3.336667 | 1471,47 | 1601.6 | 89 | * 23.9760 | 1126125 | 4.170833333 | 1839.3375 | 2002 |
90 | 30 | 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 | ||
109 | PLUGIN_HEADER | 109 | PLUGIN_HEADER |
110 | PLUGIN_IRAM_DECLARE | 110 | PLUGIN_IRAM_DECLARE |
@@ -177,908 +177,43 @@ PLUGIN_IRAM_DECLARE | |||
177 | struct plugin_api* rb; | 177 | struct plugin_api* rb; |
178 | 178 | ||
179 | CACHE_FUNCTION_WRAPPERS(rb); | 179 | CACHE_FUNCTION_WRAPPERS(rb); |
180 | ALIGN_BUFFER_WRAPPER(rb); | ||
180 | 181 | ||
181 | extern void *mpeg_malloc(size_t size, mpeg2_alloc_t reason); | 182 | static bool button_loop(void) |
182 | extern size_t mpeg_alloc_init(unsigned char *buf, size_t mallocsize, | ||
183 | size_t libmpeg2size); | ||
184 | |||
185 | static mpeg2dec_t * mpeg2dec NOCACHEBSS_ATTR; | ||
186 | static int total_offset NOCACHEBSS_ATTR = 0; | ||
187 | static int num_drawn NOCACHEBSS_ATTR = 0; | ||
188 | static int count_start NOCACHEBSS_ATTR = 0; | ||
189 | |||
190 | /* Streams */ | ||
191 | typedef 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 | |||
213 | static Stream audio_str IBSS_ATTR; | ||
214 | static Stream video_str IBSS_ATTR; | ||
215 | |||
216 | /* Messages */ | ||
217 | enum | ||
218 | { | ||
219 | STREAM_PLAY, | ||
220 | STREAM_PAUSE, | ||
221 | STREAM_QUIT | ||
222 | }; | ||
223 | |||
224 | /* Status */ | ||
225 | enum | ||
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 */ | ||
237 | static 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); |
243 | static 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 */ | ||
262 | static 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 */ | ||
272 | static 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 */ | ||
284 | static 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 */ | ||
294 | static 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 | */ | ||
335 | static uint8_t *disk_buf_start IBSS_ATTR; /* Start pointer */ | ||
336 | static 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 */ | ||
342 | static uint8_t *disk_buf_tail IBSS_ATTR; /* Location of last data + 1 | ||
343 | filled into the buffer */ | ||
344 | static size_t disk_buf_size IBSS_ATTR; /* The total buffer length | ||
345 | including the guard | ||
346 | space */ | ||
347 | static size_t file_remaining IBSS_ATTR; | ||
348 | |||
349 | #if NUM_CORES > 1 | ||
350 | /* Some stream variables are shared between cores */ | ||
351 | struct mutex stream_lock IBSS_ATTR; | ||
352 | static inline void init_stream_lock(void) | ||
353 | { rb->mutex_init(&stream_lock); } | ||
354 | static inline void lock_stream(void) | ||
355 | { rb->mutex_lock(&stream_lock); } | ||
356 | static inline void unlock_stream(void) | ||
357 | { rb->mutex_unlock(&stream_lock); } | ||
358 | #else | ||
359 | /* No RMW issue here */ | ||
360 | static inline void init_stream_lock(void) | ||
361 | { } | ||
362 | static inline void lock_stream(void) | ||
363 | { } | ||
364 | static inline void unlock_stream(void) | ||
365 | { } | ||
366 | #endif | ||
367 | |||
368 | static 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 */ | ||
372 | static 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 */ | ||
378 | static 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 */ | ||
382 | static 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 */ | ||
386 | static 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 */ | ||
389 | static 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 */ | ||
392 | char *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 | |||
407 | static void pcm_playback_play_pause(bool play); | ||
408 | |||
409 | /* libmad related functions/definitions */ | ||
410 | #define INPUT_CHUNK_SIZE 8192 | ||
411 | |||
412 | struct mad_stream stream IBSS_ATTR; | ||
413 | struct mad_frame frame IBSS_ATTR; | ||
414 | struct mad_synth synth IBSS_ATTR; | ||
415 | |||
416 | unsigned 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 | ||
422 | static mad_fixed_t mad_frame_overlap[2][32][18] IBSS_ATTR; /* 4608 bytes */ | ||
423 | #else | ||
424 | static mad_fixed_t mad_frame_overlap[2][32][18] __attribute__((aligned(16))); /* 4608 bytes */ | ||
425 | #endif | ||
426 | |||
427 | static 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 */ | ||
527 | bool 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). */ | ||
565 | static 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 */ | ||
808 | uint8_t* mpa_buffer NOCACHEBSS_ATTR; | ||
809 | |||
810 | static 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) | ||
819 | struct 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 */ | ||
828 | static unsigned pts_queue_rd NOCACHEBSS_ATTR; | ||
829 | static unsigned pts_queue_wr NOCACHEBSS_ATTR; | ||
830 | |||
831 | /* Increments the queue head postion - should be used to preincrement */ | ||
832 | static 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 */ | ||
842 | static 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 */ | ||
852 | static 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 */ | ||
858 | static 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 */ | ||
864 | static 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 | |||
873 | struct 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 */ | ||
883 | static volatile uint64_t pcmbuf_read IBSS_ATTR; | ||
884 | static volatile uint64_t pcmbuf_written IBSS_ATTR; | ||
885 | static volatile ssize_t pcmbuf_threshold IBSS_ATTR; | ||
886 | static struct pcm_frame_header *pcm_buffer IBSS_ATTR; | ||
887 | static struct pcm_frame_header *pcmbuf_end IBSS_ATTR; | ||
888 | static struct pcm_frame_header * volatile pcmbuf_head IBSS_ATTR; | ||
889 | static struct pcm_frame_header * volatile pcmbuf_tail IBSS_ATTR; | ||
890 | |||
891 | static volatile uint32_t samplesplayed IBSS_ATTR; /* Our base clock */ | ||
892 | static volatile uint32_t samplestart IBSS_ATTR; /* Clock at playback start */ | ||
893 | static volatile int32_t sampleadjust IBSS_ATTR; /* Clock drift adjustment */ | ||
894 | |||
895 | static ssize_t pcmbuf_used(void) | ||
896 | { | ||
897 | return (ssize_t)(pcmbuf_written - pcmbuf_read); | ||
898 | } | ||
899 | |||
900 | static 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 */ | ||
917 | static 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 | |||
925 | static 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 */ | ||
966 | static 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 */ | ||
984 | static 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 */ | ||
1000 | static 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 */ | ||
1009 | static void pcm_playback_play_pause(bool play) | ||
1010 | { | ||
1011 | rb->pcm_play_pause(play); | ||
1012 | } | ||
1013 | |||
1014 | /* Stops all playback and resets the clock */ | ||
1015 | static 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 | |||
1027 | static uint32_t get_stream_time(void) | ||
1028 | { | ||
1029 | return samplesplayed + sampleadjust - (rb->pcm_get_bytes_waiting() >> 2); | ||
1030 | } | ||
1031 | |||
1032 | static uint32_t get_playback_time(void) | ||
1033 | { | ||
1034 | return samplesplayed + sampleadjust - | ||
1035 | samplestart - (rb->pcm_get_bytes_waiting() >> 2); | ||
1036 | } | ||
1037 | |||
1038 | static 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 | ||
1046 | static 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 | } | ||
1190 | quit: | ||
1191 | return audio_str.status; | ||
1192 | } | ||
1193 | |||
1194 | static 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 | |||
1465 | done: | ||
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 | |||
1494 | audio_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) | ||
1504 | uint32_t* audio_stack; | ||
1505 | |||
1506 | #ifndef SIMULATOR | ||
1507 | static 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) | ||
1513 | static uint32_t video_stack[VIDEO_STACKSIZE / sizeof(uint32_t)] IBSS_ATTR; | ||
1514 | |||
1515 | static 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 | |||
1923 | void 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 | |||
1932 | void 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 | ||
1962 | int 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 | ||
2008 | int 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 | |||
2054 | ssize_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 | |||
2218 | enum plugin_status plugin_start(struct plugin_api* api, void* parameter) | 324 | enum 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 */ | ||
25 | extern 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 | |||
36 | enum 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 | |||
24 | enum 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 */ | ||
35 | enum 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 | |||
43 | enum stream_parse_mode | ||
44 | { | ||
45 | STREAM_PM_STREAMING = 0, /* Next packet when streaming */ | ||
46 | STREAM_PM_RANDOM_ACCESS, /* Random-access parsing */ | ||
47 | }; | ||
48 | |||
49 | enum stream_parser_flags | ||
50 | { | ||
51 | STREAMF_CAN_SEEK = 0x1, /* Seeking possible for this stream */ | ||
52 | }; | ||
53 | |||
54 | struct 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 | |||
75 | extern struct stream_parser str_parser; | ||
76 | |||
77 | /* MPEG parsing */ | ||
78 | uint8_t * mpeg_parser_scan_start_code(struct stream_scan *sk, uint32_t code); | ||
79 | unsigned mpeg_parser_scan_pes(struct stream_scan *sk); | ||
80 | uint32_t mpeg_parser_scan_scr(struct stream_scan *sk); | ||
81 | uint32_t mpeg_parser_scan_pts(struct stream_scan *sk, unsigned id); | ||
82 | off_t mpeg_stream_stream_seek_PTS(uint32_t time, int id); | ||
83 | |||
84 | /* General parsing */ | ||
85 | bool parser_init(void); | ||
86 | void str_initialize(struct stream *str, off_t pos); | ||
87 | intptr_t parser_send_video_msg(long id, intptr_t data); | ||
88 | bool parser_get_video_size(struct vo_ext *sz); | ||
89 | int parser_init_stream(void); | ||
90 | void parser_close_stream(void); | ||
91 | static inline bool parser_can_seek(void) | ||
92 | { return str_parser.flags & STREAMF_CAN_SEEK; } | ||
93 | uint32_t parser_seek_time(uint32_t time); | ||
94 | void parser_prepare_streaming(void); | ||
95 | void str_end_of_stream(struct stream *str); | ||
96 | |||
97 | static 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 */ | ||
27 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcm_buffer; | ||
28 | /* End of buffer (not guard) */ | ||
29 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_end; | ||
30 | /* Read pointer */ | ||
31 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_head IBSS_ATTR; | ||
32 | /* Write pointer */ | ||
33 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR; | ||
34 | |||
35 | /* Bytes */ | ||
36 | static uint64_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */ | ||
37 | static uint64_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */ | ||
38 | static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */ | ||
39 | |||
40 | /* Clock */ | ||
41 | static uint32_t clock_base IBSS_ATTR; /* Our base clock */ | ||
42 | static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */ | ||
43 | static int32_t clock_adjust IBSS_ATTR; /* Clock drift adjustment */ | ||
44 | |||
45 | /* Small silence clip. ~5.80ms @ 44.1kHz */ | ||
46 | static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 }; | ||
47 | |||
48 | /* Advance a PCM buffer pointer by size bytes circularly */ | ||
49 | static 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 */ | ||
58 | inline ssize_t pcm_output_used(void) | ||
59 | { | ||
60 | return (ssize_t)(pcmbuf_written - pcmbuf_read); | ||
61 | } | ||
62 | |||
63 | inline ssize_t pcm_output_free(void) | ||
64 | { | ||
65 | return (ssize_t)(PCMOUT_BUFSIZE - pcmbuf_written + pcmbuf_read); | ||
66 | } | ||
67 | |||
68 | /* Audio DMA handler */ | ||
69 | static 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 | |||
140 | struct pcm_frame_header * pcm_output_get_buffer(void) | ||
141 | { | ||
142 | return pcmbuf_tail; | ||
143 | } | ||
144 | |||
145 | void 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 */ | ||
153 | void 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 */ | ||
167 | void 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 | |||
178 | uint32_t pcm_output_get_clock(void) | ||
179 | { | ||
180 | return clock_base + clock_adjust | ||
181 | - (rb->pcm_get_bytes_waiting() >> 2); | ||
182 | } | ||
183 | |||
184 | uint32_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 */ | ||
193 | void 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 */ | ||
210 | void 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 */ | ||
224 | void pcm_output_drain(void) | ||
225 | { | ||
226 | rb->pcm_play_lock(); | ||
227 | pcmbuf_threshold = PCMOUT_LOW_WM; | ||
228 | rb->pcm_play_unlock(); | ||
229 | } | ||
230 | |||
231 | bool 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 | |||
275 | void 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 | |||
24 | struct 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 | |||
32 | bool pcm_output_init(void); | ||
33 | void pcm_output_exit(void); | ||
34 | void pcm_output_flush(void); | ||
35 | void pcm_output_set_clock(uint32_t time); | ||
36 | uint32_t pcm_output_get_clock(void); | ||
37 | uint32_t pcm_output_get_ticks(uint32_t *start); | ||
38 | void pcm_output_play_pause(bool play); | ||
39 | void pcm_output_stop(void); | ||
40 | void pcm_output_drain(void); | ||
41 | struct pcm_frame_header * pcm_output_get_buffer(void); | ||
42 | void pcm_output_add_data(void); | ||
43 | ssize_t pcm_output_used(void); | ||
44 | ssize_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 | |||
26 | static struct event_queue stream_mgr_queue NOCACHEBSS_ATTR; | ||
27 | static struct queue_sender_list stream_mgr_queue_send NOCACHEBSS_ATTR; | ||
28 | static uint32_t stream_mgr_thread_stack[DEFAULT_STACK_SIZE*2/sizeof(uint32_t)]; | ||
29 | |||
30 | struct stream_mgr stream_mgr NOCACHEBSS_ATTR; | ||
31 | |||
32 | /* Forward decs */ | ||
33 | static int stream_on_close(void); | ||
34 | |||
35 | struct str_broadcast_data | ||
36 | { | ||
37 | long cmd; /* Command to send to stream */ | ||
38 | intptr_t data; /* Data to send with command */ | ||
39 | }; | ||
40 | |||
41 | static inline void stream_mgr_lock(void) | ||
42 | { | ||
43 | rb->mutex_lock(&stream_mgr.str_mtx); | ||
44 | } | ||
45 | |||
46 | static inline void stream_mgr_unlock(void) | ||
47 | { | ||
48 | rb->mutex_unlock(&stream_mgr.str_mtx); | ||
49 | } | ||
50 | |||
51 | static inline void actl_lock(void) | ||
52 | { | ||
53 | rb->mutex_lock(&stream_mgr.actl_mtx); | ||
54 | } | ||
55 | |||
56 | static inline void actl_unlock(void) | ||
57 | { | ||
58 | rb->mutex_unlock(&stream_mgr.actl_mtx); | ||
59 | } | ||
60 | |||
61 | static inline void stream_mgr_post_msg(long id, intptr_t data) | ||
62 | { | ||
63 | rb->queue_post(stream_mgr.q, id, data); | ||
64 | } | ||
65 | |||
66 | static 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 | |||
71 | static inline void stream_mgr_reply_msg(intptr_t retval) | ||
72 | { | ||
73 | rb->queue_reply(stream_mgr.q, retval); | ||
74 | } | ||
75 | |||
76 | int 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 | |||
113 | void 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 */ | ||
133 | static 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 */ | ||
141 | void 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 */ | ||
152 | static 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 */ | ||
167 | void 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 */ | ||
173 | void 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 */ | ||
179 | static 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 */ | ||
196 | static 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 | |||
228 | static 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 */ | ||
239 | static 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 */ | ||
251 | static 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 */ | ||
292 | static 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 */ | ||
348 | void 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 */ | ||
381 | static 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 */ | ||
439 | static 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 */ | ||
470 | static 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 */ | ||
517 | static 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 */ | ||
568 | static 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 */ | ||
619 | static 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 */ | ||
647 | static 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 */ | ||
673 | void 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 */ | ||
690 | void 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 */ | ||
701 | bool 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 */ | ||
719 | bool 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 */ | ||
729 | bool 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 */ | ||
748 | bool 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). */ | ||
783 | void 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 */ | ||
797 | bool 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 */ | ||
815 | uint32_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 */ | ||
831 | static 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 | |||
847 | bool 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 */ | ||
865 | static 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 */ | ||
917 | int 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' */ | ||
925 | int 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 */ | ||
933 | int 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 */ | ||
941 | int 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 */ | ||
949 | int 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 */ | ||
957 | int 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 */ | ||
977 | int 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 */ | ||
985 | int 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 */ | ||
1080 | void 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 */ | ||
26 | struct 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 | |||
51 | extern struct stream_mgr stream_mgr NOCACHEBSS_ATTR; | ||
52 | |||
53 | struct stream_window | ||
54 | { | ||
55 | off_t left, right; | ||
56 | }; | ||
57 | |||
58 | /** Interface for use by streams and other internal objects **/ | ||
59 | bool stream_get_window(struct stream_window *sw); | ||
60 | void stream_clear_notify(struct stream *str, int for_msg); | ||
61 | int str_next_data_not_ready(struct stream *str); | ||
62 | /* Called by a stream to say it got its buffering notification */ | ||
63 | void str_data_notify_received(struct stream *str); | ||
64 | void stream_add_stream(struct stream *str); | ||
65 | void stream_remove_streams(void); | ||
66 | |||
67 | enum stream_events | ||
68 | { | ||
69 | __STREAM_EV_FIRST = STREAM_MESSAGE_LAST-1, | ||
70 | STREAM_EV_COMPLETE, | ||
71 | }; | ||
72 | |||
73 | void stream_generate_event(struct stream *str, long id, intptr_t data); | ||
74 | |||
75 | /** Main control functions **/ | ||
76 | |||
77 | /* Initialize the playback engine */ | ||
78 | int stream_init(void); | ||
79 | |||
80 | /* Close the playback engine */ | ||
81 | void stream_exit(void); | ||
82 | |||
83 | /* Open a new file */ | ||
84 | int stream_open(const char *filename); | ||
85 | |||
86 | /* Close the current file */ | ||
87 | int stream_close(void); | ||
88 | |||
89 | /* Plays from the current seekpoint if stopped */ | ||
90 | int stream_play(void); | ||
91 | |||
92 | /* Pauses playback if playing */ | ||
93 | int stream_pause(void); | ||
94 | |||
95 | /* Resumes playback if paused */ | ||
96 | int stream_resume(void); | ||
97 | |||
98 | /* Stops all streaming activity if playing or paused */ | ||
99 | int stream_stop(void); | ||
100 | |||
101 | /* Point stream at a particular time. | ||
102 | * whence = one of SEEK_SET, SEEK_CUR, SEEK_END */ | ||
103 | int stream_seek(uint32_t time, int whence); | ||
104 | |||
105 | /* Show/Hide the video image at the current seekpoint */ | ||
106 | bool stream_show_vo(bool show); | ||
107 | |||
108 | #ifndef HAVE_LCD_COLOR | ||
109 | /* Set the gray overlay rectangle */ | ||
110 | bool stream_set_gray_rect(const struct vo_rect *rc); | ||
111 | void stream_gray_show(bool show); | ||
112 | #endif | ||
113 | |||
114 | /* Display thumbnail of the current seekpoint */ | ||
115 | bool stream_display_thumb(const struct vo_rect *rc); | ||
116 | |||
117 | /* Return video dimensions */ | ||
118 | bool stream_vo_get_size(struct vo_ext *sz); | ||
119 | |||
120 | /* Returns the resume time in timestamp ticks */ | ||
121 | uint32_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 */ | ||
125 | static inline uint32_t stream_get_time(void) | ||
126 | { return pcm_output_get_clock(); } | ||
127 | |||
128 | /* Return the absolute clock time in clock ticks - unadjusted */ | ||
129 | static inline uint32_t stream_get_ticks(uint32_t *start) | ||
130 | { return pcm_output_get_ticks(start); } | ||
131 | |||
132 | /* Returns the current playback status */ | ||
133 | static inline int stream_status(void) | ||
134 | { return stream_mgr.status; } | ||
135 | |||
136 | /* Returns the playback length of the stream */ | ||
137 | static inline uint32_t stream_get_duration(void) | ||
138 | { return str_parser.duration; } | ||
139 | |||
140 | static inline bool stream_can_seek(void) | ||
141 | { return parser_can_seek(); } | ||
142 | |||
143 | /* Keep the disk spinning (for seeking and browsing) */ | ||
144 | static 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 */ | ||
29 | struct 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 | |||
43 | struct 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 */ | ||
65 | enum 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 */ | ||
79 | enum 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 */ | ||
116 | struct 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 */ | ||
123 | struct 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 */ | ||
130 | enum 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 */ | ||
151 | static 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 | |||
161 | extern struct stream video_str IBSS_ATTR; | ||
162 | extern struct stream audio_str IBSS_ATTR; | ||
163 | |||
164 | bool video_thread_init(void); | ||
165 | void video_thread_exit(void); | ||
166 | bool audio_thread_init(void); | ||
167 | void audio_thread_exit(void); | ||
168 | |||
169 | /* Some queue function wrappers to keep things clean-ish */ | ||
170 | |||
171 | /* For stream use only */ | ||
172 | static inline bool str_have_msg(struct stream *str) | ||
173 | { return !rb->queue_empty(str->hdr.q); } | ||
174 | |||
175 | static inline void str_get_msg(struct stream *str, struct queue_event *ev) | ||
176 | { rb->queue_wait(str->hdr.q, ev); } | ||
177 | |||
178 | static 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 | |||
182 | static inline void str_reply_msg(struct stream *str, intptr_t reply) | ||
183 | { rb->queue_reply(str->hdr.q, reply); } | ||
184 | |||
185 | /* Public use */ | ||
186 | static 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 | |||
189 | static 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 */ | ||
28 | struct vo_ext | ||
29 | { | ||
30 | int w, h; | ||
31 | }; | ||
32 | |||
33 | /* Structure that defines a rectangle by its edges */ | ||
34 | struct vo_rect | ||
35 | { | ||
36 | int l, t, r, b; | ||
37 | }; | ||
38 | |||
24 | void vo_draw_frame (uint8_t * const * buf); | 39 | void vo_draw_frame (uint8_t * const * buf); |
25 | void vo_draw_frame_thumb (uint8_t * const * buf); | 40 | bool vo_draw_frame_thumb (uint8_t * const * buf, |
41 | const struct vo_rect *rc); | ||
42 | bool vo_init (void); | ||
43 | bool vo_show (bool show); | ||
44 | bool vo_is_visible(void); | ||
26 | void vo_setup (const mpeg2_sequence_t * sequence); | 45 | void vo_setup (const mpeg2_sequence_t * sequence); |
46 | void vo_dimensions(struct vo_ext *sz); | ||
27 | void vo_cleanup (void); | 47 | void vo_cleanup (void); |
48 | |||
49 | /* Sets all coordinates of a vo_rect to 0 */ | ||
50 | void vo_rect_clear(struct vo_rect *rc); | ||
51 | /* Returns true if left >= right or top >= bottom */ | ||
52 | bool vo_rect_empty(const struct vo_rect *rc); | ||
53 | /* Initializes a vo_rect using upper-left corner and extents */ | ||
54 | void 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 */ | ||
58 | bool 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. */ | ||
67 | bool 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 | |||
24 | struct 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 */ | ||
44 | static uint8_t __vo_data[CACHEALIGN_UP(sizeof(struct vo_data))] | ||
45 | CACHEALIGN_ATTR; | ||
46 | #define vo (*((struct vo_data *)__vo_data)) | ||
47 | #else | ||
48 | static struct vo_data vo; | ||
49 | #endif | ||
50 | |||
51 | /* Draw a black rectangle if no video frame is available */ | ||
52 | static void vo_draw_black(void) | ||
53 | { | ||
54 | int foreground = lcd_(get_foreground)(); | ||
28 | 55 | ||
29 | extern 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 | ||
34 | static int image_width; | 63 | lcd_(set_foreground)(foreground); |
35 | static int image_height; | 64 | } |
36 | static int image_chroma_x; | ||
37 | static int image_chroma_y; | ||
38 | static int output_x; | ||
39 | static int output_y; | ||
40 | static int output_width; | ||
41 | static int output_height; | ||
42 | 65 | ||
43 | void vo_draw_frame (uint8_t * const * buf) | 66 | static 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 | ||
76 | void 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 | ||
62 | uint8_t* tmpbufa = 0; | 105 | static inline void vo_rect_clear_inl(struct vo_rect *rc) |
63 | uint8_t* tmpbufb = 0; | 106 | { |
64 | uint8_t* tmpbufc = 0; | 107 | rc->l = rc->t = rc->r = rc->b = 0; |
65 | uint8_t* tmpbuf[3]; | 108 | } |
66 | 109 | ||
67 | void vo_draw_frame_thumb (uint8_t * const * buf) | 110 | static 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 | 115 | static 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 */ | ||
125 | void 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 */ | ||
131 | bool 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 */ | ||
137 | void 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 */ | ||
147 | bool 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 */ | ||
154 | bool 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) */ | ||
177 | void 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 | ||
101 | rb->lcd_clear_display(); | 220 | if (dst >= dst_line_end) |
102 | rb->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 | |||
265 | bool 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 | ||
146 | void vo_setup(const mpeg2_sequence_t * sequence) | 339 | void 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 | ||
374 | void vo_dimensions(struct vo_ext *sz) | ||
375 | { | ||
376 | sz->w = vo.display_width; | ||
377 | sz->h = vo.display_height; | ||
378 | } | ||
379 | |||
380 | bool vo_init(void) | ||
381 | { | ||
382 | vo.visible = false; | ||
383 | return true; | ||
384 | } | ||
385 | |||
386 | bool vo_show(bool show) | ||
387 | { | ||
388 | bool vis = vo.visible; | ||
389 | vo.visible = show; | ||
390 | return vis; | ||
391 | } | ||
392 | |||
393 | bool vo_is_visible(void) | ||
394 | { | ||
395 | return vo.visible; | ||
396 | } | ||
397 | |||
181 | void vo_cleanup(void) | 398 | void 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 */ | ||
31 | struct 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) | ||
58 | static uint32_t video_stack[VIDEO_STACKSIZE / sizeof(uint32_t)] IBSS_ATTR; | ||
59 | static struct event_queue video_str_queue NOCACHEBSS_ATTR; | ||
60 | static struct queue_sender_list video_str_queue_send NOCACHEBSS_ATTR; | ||
61 | struct stream video_str IBSS_ATTR; | ||
62 | |||
63 | static 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) | ||
84 | static 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 | */ | ||
106 | static 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 | |||
188 | scan_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 | |||
207 | static 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 | |||
219 | static 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 */ | ||
252 | static 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 | |||
446 | sync_finished: | ||
447 | mpeg2_skip(td->mpeg2dec, 0); | ||
448 | return retval; | ||
449 | } | ||
450 | |||
451 | /* This only returns to play or quit */ | ||
452 | static 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 | |||
647 | static 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 */ | ||
998 | bool 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 */ | ||
1024 | void 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,- | |||
28 | bmp,apps/rockpaint,11 | 28 | bmp,apps/rockpaint,11 |
29 | mpg,viewers/mpegplayer,4 | 29 | mpg,viewers/mpegplayer,4 |
30 | mpeg,viewers/mpegplayer,4 | 30 | mpeg,viewers/mpegplayer,4 |
31 | mpv,viewers/mpegplayer,4 | ||
32 | m2v,viewers/mpegplayer,4 | ||
31 | iriver,viewers/iriver_flash,3 | 33 | iriver,viewers/iriver_flash,3 |
32 | tap,viewers/zxbox,12 | 34 | tap,viewers/zxbox,12 |
33 | sna,viewers/zxbox,12 | 35 | sna,viewers/zxbox,12 |
diff --git a/firmware/export/system.h b/firmware/export/system.h index cba4b81631..b973b57fd9 100644 --- a/firmware/export/system.h +++ b/firmware/export/system.h | |||
@@ -243,16 +243,18 @@ static inline uint32_t swap_odd_even32(uint32_t value) | |||
243 | #define CACHEALIGN_DOWN(x) \ | 243 | #define CACHEALIGN_DOWN(x) \ |
244 | ((typeof (x))ALIGN_DOWN_P2((uintptr_t)(x), CACHEALIGN_BITS)) | 244 | ((typeof (x))ALIGN_DOWN_P2((uintptr_t)(x), CACHEALIGN_BITS)) |
245 | /* Aligns at least to the greater of size x or CACHEALIGN_SIZE */ | 245 | /* Aligns at least to the greater of size x or CACHEALIGN_SIZE */ |
246 | #define CACHEALIGN_AT_LEAST_ATTR(x) __attribute__((aligned(CACHEALIGN_UP(x)))) | 246 | #define CACHEALIGN_AT_LEAST_ATTR(x) \ |
247 | __attribute__((aligned(CACHEALIGN_UP(x)))) | ||
247 | /* Aligns a buffer pointer and size to proper boundaries */ | 248 | /* Aligns a buffer pointer and size to proper boundaries */ |
248 | #define CACHEALIGN_BUFFER(start, size) \ | 249 | #define CACHEALIGN_BUFFER(start, size) \ |
249 | ({ align_buffer((start), (size), CACHEALIGN_SIZE); }) | 250 | ({ align_buffer(PUN_PTR(void **, (start)), (size), CACHEALIGN_SIZE); }) |
250 | 251 | ||
251 | #else /* ndef PROC_NEEDS_CACHEALIGN */ | 252 | #else /* ndef PROC_NEEDS_CACHEALIGN */ |
252 | 253 | ||
253 | /* Cache alignment attributes and sizes are not enabled */ | 254 | /* Cache alignment attributes and sizes are not enabled */ |
254 | #define CACHEALIGN_ATTR | 255 | #define CACHEALIGN_ATTR |
255 | #define CACHEALIGN_AT_LEAST_ATTR(x) __attribute__((aligned(x))) | 256 | #define CACHEALIGN_AT_LEAST_ATTR(x) \ |
257 | __attribute__((aligned(x))) | ||
256 | #define CACHEALIGN_UP(x) (x) | 258 | #define CACHEALIGN_UP(x) (x) |
257 | #define CACHEALIGN_DOWN(x) (x) | 259 | #define CACHEALIGN_DOWN(x) (x) |
258 | /* Make no adjustments */ | 260 | /* Make no adjustments */ |
@@ -261,4 +263,8 @@ static inline uint32_t swap_odd_even32(uint32_t value) | |||
261 | 263 | ||
262 | #endif /* PROC_NEEDS_CACHEALIGN */ | 264 | #endif /* PROC_NEEDS_CACHEALIGN */ |
263 | 265 | ||
266 | /* Double-cast to avoid 'dereferencing type-punned pointer will | ||
267 | * break strict aliasing rules' B.S. */ | ||
268 | #define PUN_PTR(type, p) ((type)(intptr_t)(p)) | ||
269 | |||
264 | #endif /* __SYSTEM_H__ */ | 270 | #endif /* __SYSTEM_H__ */ |
diff --git a/firmware/export/thread.h b/firmware/export/thread.h index df18f7b095..0b1500cd99 100644 --- a/firmware/export/thread.h +++ b/firmware/export/thread.h | |||
@@ -42,11 +42,20 @@ | |||
42 | #define PRIORITY_SYSTEM 6 /* All other firmware threads */ | 42 | #define PRIORITY_SYSTEM 6 /* All other firmware threads */ |
43 | #define PRIORITY_BACKGROUND 8 /* Normal application threads */ | 43 | #define PRIORITY_BACKGROUND 8 /* Normal application threads */ |
44 | 44 | ||
45 | /* TODO: Only a minor tweak to create_thread would be needed to let | ||
46 | * thread slots be caller allocated - no essential threading functionality | ||
47 | * depends upon an array */ | ||
45 | #if CONFIG_CODEC == SWCODEC | 48 | #if CONFIG_CODEC == SWCODEC |
49 | |||
50 | #ifdef HAVE_RECORDING | ||
51 | #define MAXTHREADS 18 | ||
52 | #else | ||
46 | #define MAXTHREADS 17 | 53 | #define MAXTHREADS 17 |
54 | #endif | ||
55 | |||
47 | #else | 56 | #else |
48 | #define MAXTHREADS 11 | 57 | #define MAXTHREADS 11 |
49 | #endif | 58 | #endif /* CONFIG_CODE == * */ |
50 | 59 | ||
51 | #define DEFAULT_STACK_SIZE 0x400 /* Bytes */ | 60 | #define DEFAULT_STACK_SIZE 0x400 /* Bytes */ |
52 | 61 | ||
diff --git a/firmware/general.c b/firmware/general.c index cc3710c8f3..117e6c3548 100644 --- a/firmware/general.c +++ b/firmware/general.c | |||
@@ -77,8 +77,6 @@ int make_list_from_caps32(unsigned long src_mask, | |||
77 | return count; | 77 | return count; |
78 | } /* make_list_from_caps32 */ | 78 | } /* make_list_from_caps32 */ |
79 | 79 | ||
80 | /* Only needed for cache aligning atm */ | ||
81 | #ifdef PROC_NEEDS_CACHEALIGN | ||
82 | /* Align a buffer and size to a size boundary while remaining within | 80 | /* Align a buffer and size to a size boundary while remaining within |
83 | * the original boundaries */ | 81 | * the original boundaries */ |
84 | size_t align_buffer(void **start, size_t size, size_t align) | 82 | size_t align_buffer(void **start, size_t size, size_t align) |
@@ -98,4 +96,3 @@ size_t align_buffer(void **start, size_t size, size_t align) | |||
98 | *start = newstart; | 96 | *start = newstart; |
99 | return newend - newstart; | 97 | return newend - newstart; |
100 | } | 98 | } |
101 | #endif /* PROC_NEEDS_CACHEALIGN */ | ||