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 /apps/plugins/mpegplayer/stream_mgr.c | |
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
Diffstat (limited to 'apps/plugins/mpegplayer/stream_mgr.c')
-rw-r--r-- | apps/plugins/mpegplayer/stream_mgr.c | 1096 |
1 files changed, 1096 insertions, 0 deletions
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 | } | ||