summaryrefslogtreecommitdiff
path: root/apps/plugins/mpegplayer/disk_buf.c
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2007-12-29 19:46:35 +0000
committerMichael Sevakis <jethead71@rockbox.org>2007-12-29 19:46:35 +0000
commita222f27c4a17ed8f9809cda7861fe5f23d4cc0c1 (patch)
treed393a23d83549f99772bb156e59ffb88725148b6 /apps/plugins/mpegplayer/disk_buf.c
parent1d0f6b90ff43776e55b4b9f062c9bea3f99aa768 (diff)
downloadrockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.tar.gz
rockbox-a222f27c4a17ed8f9809cda7861fe5f23d4cc0c1.zip
mpegplayer: Make playback engine fully seekable and frame-accurate and split into logical parts. Be sure to have all current features work. Actual UI for seeking will be added soon. Recommended GOP size is about 15-30 frames depending on target or seeking can be slow with really long GOPs (nature of MPEG video). More refined encoding recommendations for a particular player should be posted soon.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15977 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/plugins/mpegplayer/disk_buf.c')
-rw-r--r--apps/plugins/mpegplayer/disk_buf.c906
1 files changed, 906 insertions, 0 deletions
diff --git a/apps/plugins/mpegplayer/disk_buf.c b/apps/plugins/mpegplayer/disk_buf.c
new file mode 100644
index 0000000000..a408b90a67
--- /dev/null
+++ b/apps/plugins/mpegplayer/disk_buf.c
@@ -0,0 +1,906 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * mpegplayer buffering routines
11 *
12 * Copyright (c) 2007 Michael Sevakis
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 * See the file COPYING in the source tree root for full license agreement.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "mpegplayer.h"
23
24static struct mutex disk_buf_mtx NOCACHEBSS_ATTR;
25static struct event_queue disk_buf_queue NOCACHEBSS_ATTR;
26static struct queue_sender_list disk_buf_queue_send NOCACHEBSS_ATTR;
27static uint32_t disk_buf_stack[DEFAULT_STACK_SIZE*2/sizeof(uint32_t)];
28
29struct disk_buf disk_buf NOCACHEBSS_ATTR;
30static struct list_item nf_list;
31
32static inline void disk_buf_lock(void)
33{
34 rb->mutex_lock(&disk_buf_mtx);
35}
36
37static inline void disk_buf_unlock(void)
38{
39 rb->mutex_unlock(&disk_buf_mtx);
40}
41
42static inline void disk_buf_on_clear_data_notify(struct stream_hdr *sh)
43{
44 DEBUGF("DISK_BUF_CLEAR_DATA_NOTIFY: 0x%02X (cleared)\n",
45 STR_FROM_HEADER(sh)->id);
46 list_remove_item(&sh->nf);
47}
48
49static int disk_buf_on_data_notify(struct stream_hdr *sh)
50{
51 DEBUGF("DISK_BUF_DATA_NOTIFY: 0x%02X ", STR_FROM_HEADER(sh)->id);
52
53 if (sh->win_left <= sh->win_right)
54 {
55 /* Check if the data is already ready already */
56 if (disk_buf_is_data_ready(sh, 0))
57 {
58 /* It was - don't register */
59 DEBUGF("(was ready)\n"
60 " swl:%lu swr:%lu\n"
61 " dwl:%lu dwr:%lu\n",
62 sh->win_left, sh->win_right,
63 disk_buf.win_left, disk_buf.win_right);
64 /* Be sure it's not listed though if multiple requests were made */
65 list_remove_item(&sh->nf);
66 return DISK_BUF_NOTIFY_OK;
67 }
68
69 switch (disk_buf.state)
70 {
71 case TSTATE_DATA:
72 case TSTATE_BUFFERING:
73 case TSTATE_INIT:
74 disk_buf.state = TSTATE_BUFFERING;
75 list_add_item(&nf_list, &sh->nf);
76 DEBUGF("(registered)\n"
77 " swl:%lu swr:%lu\n"
78 " dwl:%lu dwr:%lu\n",
79 sh->win_left, sh->win_right,
80 disk_buf.win_left, disk_buf.win_right);
81 return DISK_BUF_NOTIFY_REGISTERED;
82 }
83 }
84
85 DEBUGF("(error)\n");
86 return DISK_BUF_NOTIFY_ERROR;
87}
88
89static bool check_data_notifies_callback(struct list_item *item,
90 intptr_t data)
91{
92 struct stream_hdr *sh = TYPE_FROM_MEMBER(struct stream_hdr, item, nf);
93
94 if (disk_buf_is_data_ready(sh, 0))
95 {
96 /* Remove from list then post notification - post because send
97 * could result in a wait for each thread to finish resulting
98 * in deadlock */
99 list_remove_item(item);
100 str_post_msg(STR_FROM_HEADER(sh), DISK_BUF_DATA_NOTIFY, 0);
101 DEBUGF("DISK_BUF_DATA_NOTIFY: 0x%02X (notified)\n",
102 STR_FROM_HEADER(sh)->id);
103 }
104
105 return true;
106 (void)data;
107}
108
109/* Check registered streams and notify them if their data is available */
110static void check_data_notifies(void)
111{
112 list_enum_items(&nf_list, check_data_notifies_callback, 0);
113}
114
115/* Clear all registered notifications - do not post them */
116static inline void clear_data_notifies(void)
117{
118 list_clear_all(&nf_list);
119}
120
121/* Background buffering when streaming */
122static inline void disk_buf_buffer(void)
123{
124 struct stream_window sw;
125
126 switch (disk_buf.state)
127 {
128 default:
129 {
130 size_t wm;
131 uint32_t time;
132
133 /* Get remaining minimum data based upon the stream closest to the
134 * right edge of the window */
135 if (!stream_get_window(&sw))
136 break;
137
138 time = stream_get_ticks(NULL);
139 wm = muldiv_uint32(5*CLOCK_RATE, sw.right - disk_buf.pos_last,
140 time - disk_buf.time_last);
141 wm = MIN(wm, (size_t)disk_buf.size);
142 wm = MAX(wm, DISK_BUF_LOW_WATERMARK);
143
144 disk_buf.time_last = time;
145 disk_buf.pos_last = sw.right;
146
147 /* Fast attack, slow decay */
148 disk_buf.low_wm = (wm > (size_t)disk_buf.low_wm) ?
149 wm : AVERAGE(disk_buf.low_wm, wm, 16);
150
151#if 0
152 rb->splash(0, "*%10ld %10ld", disk_buf.low_wm,
153 disk_buf.win_right - sw.right);
154#endif
155
156 if (disk_buf.win_right - sw.right > disk_buf.low_wm)
157 break;
158
159 disk_buf.state = TSTATE_BUFFERING;
160 } /* default: */
161
162 /* Fall-through */
163 case TSTATE_BUFFERING:
164 {
165 ssize_t len, n;
166 uint32_t tag, *tag_p;
167
168 /* Limit buffering up to the stream with the least progress */
169 if (!stream_get_window(&sw))
170 {
171 disk_buf.state = TSTATE_DATA;
172 break;
173 }
174
175 /* Wrap pointer */
176 if (disk_buf.tail >= disk_buf.end)
177 disk_buf.tail = disk_buf.start;
178
179 len = disk_buf.size - disk_buf.win_right + sw.left;
180
181 if (len < DISK_BUF_PAGE_SIZE)
182 {
183 /* Free space is less than one page */
184 disk_buf.state = TSTATE_DATA;
185 disk_buf.low_wm = DISK_BUF_LOW_WATERMARK;
186 break;
187 }
188
189 len = disk_buf.tail - disk_buf.start;
190 tag = MAP_OFFSET_TO_TAG(disk_buf.win_right);
191 tag_p = &disk_buf.cache[len >> DISK_BUF_PAGE_SHIFT];
192
193 if (*tag_p != tag)
194 {
195 if (disk_buf.need_seek)
196 {
197 rb->lseek(disk_buf.in_file, disk_buf.win_right, SEEK_SET);
198 disk_buf.need_seek = false;
199 }
200
201 n = rb->read(disk_buf.in_file, disk_buf.tail, DISK_BUF_PAGE_SIZE);
202
203 if (n <= 0)
204 {
205 /* Error or end of stream */
206 disk_buf.state = TSTATE_EOS;
207 break;
208 }
209
210 if (len < DISK_GUARDBUF_SIZE)
211 {
212 /* Autoguard guard-o-rama - maintain guardbuffer coherency */
213 rb->memcpy(disk_buf.end + len, disk_buf.tail,
214 MIN(DISK_GUARDBUF_SIZE - len, n));
215 }
216
217 /* Update the cache entry for this page */
218 *tag_p = tag;
219 }
220 else
221 {
222 /* Skipping a read */
223 n = MIN(DISK_BUF_PAGE_SIZE,
224 disk_buf.filesize - disk_buf.win_right);
225 disk_buf.need_seek = true;
226 }
227
228 disk_buf.tail += DISK_BUF_PAGE_SIZE;
229
230 /* Keep left edge moving forward */
231
232 /* Advance right edge in temp variable first, then move
233 * left edge if overflow would occur. This avoids a stream
234 * thinking its data might be available when it actually
235 * may not end up that way after a slide of the window. */
236 len = disk_buf.win_right + n;
237
238 if (len - disk_buf.win_left > disk_buf.size)
239 disk_buf.win_left += n;
240
241 disk_buf.win_right = len;
242
243 /* Continue buffering until filled or file end */
244 rb->yield();
245 } /* TSTATE_BUFFERING: */
246
247 case TSTATE_EOS:
248 break;
249 } /* end switch */
250}
251
252static void disk_buf_on_reset(ssize_t pos)
253{
254 int page;
255 uint32_t tag;
256 off_t anchor;
257
258 disk_buf.state = TSTATE_INIT;
259 disk_buf.status = STREAM_STOPPED;
260 clear_data_notifies();
261
262 if (pos >= disk_buf.filesize)
263 {
264 /* Anchor on page immediately following the one containing final
265 * data */
266 anchor = disk_buf.file_pages * DISK_BUF_PAGE_SIZE;
267 disk_buf.win_left = disk_buf.filesize;
268 }
269 else
270 {
271 anchor = pos & ~DISK_BUF_PAGE_MASK;
272 disk_buf.win_left = anchor;
273 }
274
275 /* Collect all valid data already buffered that is contiguous with the
276 * current position - probe to left, then to right */
277 if (anchor > 0)
278 {
279 page = MAP_OFFSET_TO_PAGE(anchor);
280 tag = MAP_OFFSET_TO_TAG(anchor);
281
282 do
283 {
284 if (--tag, --page < 0)
285 page = disk_buf.pgcount - 1;
286
287 if (disk_buf.cache[page] != tag)
288 break;
289
290 disk_buf.win_left = tag << DISK_BUF_PAGE_SHIFT;
291 }
292 while (tag > 0);
293 }
294
295 if (anchor < disk_buf.filesize)
296 {
297 page = MAP_OFFSET_TO_PAGE(anchor);
298 tag = MAP_OFFSET_TO_TAG(anchor);
299
300 do
301 {
302 if (disk_buf.cache[page] != tag)
303 break;
304
305 if (++tag, ++page >= disk_buf.pgcount)
306 page = 0;
307
308 anchor += DISK_BUF_PAGE_SIZE;
309 }
310 while (anchor < disk_buf.filesize);
311 }
312
313 if (anchor >= disk_buf.filesize)
314 {
315 disk_buf.win_right = disk_buf.filesize;
316 disk_buf.state = TSTATE_EOS;
317 }
318 else
319 {
320 disk_buf.win_right = anchor;
321 }
322
323 disk_buf.tail = disk_buf.start + MAP_OFFSET_TO_BUFFER(anchor);
324
325 DEBUGF("disk buf reset\n"
326 " dwl:%ld dwr:%ld\n",
327 disk_buf.win_left, disk_buf.win_right);
328
329 /* Next read position is at right edge */
330 rb->lseek(disk_buf.in_file, disk_buf.win_right, SEEK_SET);
331 disk_buf.need_seek = false;
332
333 disk_buf_reply_msg(disk_buf.win_right - disk_buf.win_left);
334}
335
336static void disk_buf_on_stop(void)
337{
338 bool was_buffering = disk_buf.state == TSTATE_BUFFERING;
339
340 disk_buf.state = TSTATE_EOS;
341 disk_buf.status = STREAM_STOPPED;
342 clear_data_notifies();
343
344 disk_buf_reply_msg(was_buffering);
345}
346
347static void disk_buf_on_play_pause(bool play, bool forcefill)
348{
349 struct stream_window sw;
350
351 if (disk_buf.state != TSTATE_EOS)
352 {
353 if (forcefill)
354 {
355 /* Force buffer filling to top */
356 disk_buf.state = TSTATE_BUFFERING;
357 }
358 else if (disk_buf.state != TSTATE_BUFFERING)
359 {
360 /* If not filling already, simply monitor */
361 disk_buf.state = TSTATE_DATA;
362 }
363 }
364 /* else end of stream - no buffering to do */
365
366 disk_buf.pos_last = stream_get_window(&sw) ? sw.right : 0;
367 disk_buf.time_last = stream_get_ticks(NULL);
368
369 disk_buf.status = play ? STREAM_PLAYING : STREAM_PAUSED;
370}
371
372static int disk_buf_on_load_range(struct dbuf_range *rng)
373{
374 uint32_t tag = rng->tag_start;
375 uint32_t tag_end = rng->tag_end;
376 int page = rng->pg_start;
377
378 /* Check if a seek is required */
379 bool need_seek = rb->lseek(disk_buf.in_file, 0, SEEK_CUR)
380 != (off_t)(tag << DISK_BUF_PAGE_SHIFT);
381
382 do
383 {
384 uint32_t *tag_p = &disk_buf.cache[page];
385
386 if (*tag_p != tag)
387 {
388 /* Page not cached - load it */
389 ssize_t o, n;
390
391 if (need_seek)
392 {
393 rb->lseek(disk_buf.in_file, tag << DISK_BUF_PAGE_SHIFT,
394 SEEK_SET);
395 need_seek = false;
396 }
397
398 o = page << DISK_BUF_PAGE_SHIFT;
399 n = rb->read(disk_buf.in_file, disk_buf.start + o,
400 DISK_BUF_PAGE_SIZE);
401
402 if (n < 0)
403 {
404 /* Read error */
405 return DISK_BUF_NOTIFY_ERROR;
406 }
407
408 if (n == 0)
409 {
410 /* End of file */
411 break;
412 }
413
414 if (o < DISK_GUARDBUF_SIZE)
415 {
416 /* Autoguard guard-o-rama - maintain guardbuffer coherency */
417 rb->memcpy(disk_buf.end + o, disk_buf.start + o,
418 MIN(DISK_GUARDBUF_SIZE - o, n));
419 }
420
421 /* Update the cache entry */
422 *tag_p = tag;
423 }
424 else
425 {
426 /* Skipping a disk read - must seek on next one */
427 need_seek = true;
428 }
429
430 if (++page >= disk_buf.pgcount)
431 page = 0;
432 }
433 while (++tag <= tag_end);
434
435 return DISK_BUF_NOTIFY_OK;
436}
437
438static void disk_buf_thread(void)
439{
440 struct queue_event ev;
441
442 disk_buf.state = TSTATE_EOS;
443 disk_buf.status = STREAM_STOPPED;
444
445 while (1)
446 {
447 if (disk_buf.state != TSTATE_EOS)
448 {
449 /* Poll buffer status and messages */
450 rb->queue_wait_w_tmo(disk_buf.q, &ev,
451 disk_buf.state == TSTATE_BUFFERING ?
452 0 : HZ/5);
453 }
454 else
455 {
456 /* Sit idle and wait for commands */
457 rb->queue_wait(disk_buf.q, &ev);
458 }
459
460 switch (ev.id)
461 {
462 case SYS_TIMEOUT:
463 if (disk_buf.state == TSTATE_EOS)
464 break;
465
466 disk_buf_buffer();
467
468 /* Check for any due notifications if any are pending */
469 if (nf_list.next != NULL)
470 check_data_notifies();
471
472 /* Still more data left? */
473 if (disk_buf.state != TSTATE_EOS)
474 continue;
475
476 /* Nope - end of stream */
477 break;
478
479 case DISK_BUF_CACHE_RANGE:
480 disk_buf_reply_msg(disk_buf_on_load_range(
481 (struct dbuf_range *)ev.data));
482 break;
483
484 case STREAM_RESET:
485 disk_buf_on_reset(ev.data);
486 break;
487
488 case STREAM_STOP:
489 disk_buf_on_stop();
490 break;
491
492 case STREAM_PAUSE:
493 case STREAM_PLAY:
494 disk_buf_on_play_pause(ev.id == STREAM_PLAY, ev.data != 0);
495 disk_buf_reply_msg(1);
496 break;
497
498 case STREAM_QUIT:
499 disk_buf.state = TSTATE_EOS;
500 return;
501
502 case DISK_BUF_DATA_NOTIFY:
503 disk_buf_reply_msg(disk_buf_on_data_notify(
504 (struct stream_hdr *)ev.data));
505 break;
506
507 case DISK_BUF_CLEAR_DATA_NOTIFY:
508 disk_buf_on_clear_data_notify((struct stream_hdr *)ev.data);
509 disk_buf_reply_msg(1);
510 break;
511 }
512 }
513}
514
515/* Caches some data from the current file */
516static int disk_buf_probe(off_t start, size_t length,
517 void **p, size_t *outlen)
518{
519 off_t end;
520 uint32_t tag, tag_end;
521 int page;
522
523 /* Can't read past end of file */
524 if (length > (size_t)(disk_buf.filesize - disk_buf.offset))
525 {
526 length = disk_buf.filesize - disk_buf.offset;
527 }
528
529 /* Can't cache more than the whole buffer size */
530 if (length > (size_t)disk_buf.size)
531 {
532 length = disk_buf.size;
533 }
534 /* Zero-length probes permitted */
535
536 end = start + length;
537
538 /* Prepare the range probe */
539 tag = MAP_OFFSET_TO_TAG(start);
540 tag_end = MAP_OFFSET_TO_TAG(end);
541 page = MAP_OFFSET_TO_PAGE(start);
542
543 /* If the end is on a page boundary, check one less or an extra
544 * one will be probed */
545 if (tag_end > tag && (end & DISK_BUF_PAGE_MASK) == 0)
546 {
547 tag_end--;
548 }
549
550 if (p != NULL)
551 {
552 *p = disk_buf.start + (page << DISK_BUF_PAGE_SHIFT)
553 + (start & DISK_BUF_PAGE_MASK);
554 }
555
556 if (outlen != NULL)
557 {
558 *outlen = length;
559 }
560
561 /* Obtain initial load point. If all data was cached, no message is sent
562 * otherwise begin on the first page that is not cached. Since we have to
563 * send the message anyway, the buffering thread will determine what else
564 * requires loading on its end in order to cache the specified range. */
565 do
566 {
567 if (disk_buf.cache[page] != tag)
568 {
569 static struct dbuf_range rng NOCACHEBSS_ATTR;
570 DEBUGF("disk_buf: cache miss\n");
571 rng.tag_start = tag;
572 rng.tag_end = tag_end;
573 rng.pg_start = page;
574 return rb->queue_send(disk_buf.q, DISK_BUF_CACHE_RANGE,
575 (intptr_t)&rng);
576 }
577
578 if (++page >= disk_buf.pgcount)
579 page = 0;
580 }
581 while (++tag <= tag_end);
582
583 return DISK_BUF_NOTIFY_OK;
584}
585
586/* Attempt to get a pointer to size bytes on the buffer. Returns real amount of
587 * data available as well as the size of non-wrapped data after *p. */
588ssize_t _disk_buf_getbuffer(size_t size, void **pp, void **pwrap, size_t *sizewrap)
589{
590 disk_buf_lock();
591
592 if (disk_buf_probe(disk_buf.offset, size, pp, &size) == DISK_BUF_NOTIFY_OK)
593 {
594 if (pwrap && sizewrap)
595 {
596 uint8_t *p = (uint8_t *)*pp;
597
598 if (p + size > disk_buf.end + DISK_GUARDBUF_SIZE)
599 {
600 /* Return pointer to wraparound and the size of same */
601 size_t nowrap = (disk_buf.end + DISK_GUARDBUF_SIZE) - p;
602 *pwrap = disk_buf.start + DISK_GUARDBUF_SIZE;
603 *sizewrap = size - nowrap;
604 }
605 else
606 {
607 *pwrap = NULL;
608 *sizewrap = 0;
609 }
610 }
611 }
612 else
613 {
614 size = -1;
615 }
616
617 disk_buf_unlock();
618
619 return size;
620}
621
622/* Read size bytes of data into a buffer - advances the buffer pointer
623 * and returns the real size read. */
624ssize_t disk_buf_read(void *buffer, size_t size)
625{
626 uint8_t *p;
627
628 disk_buf_lock();
629
630 if (disk_buf_probe(disk_buf.offset, size, PUN_PTR(void **, &p),
631 &size) == DISK_BUF_NOTIFY_OK)
632 {
633 if (p + size > disk_buf.end + DISK_GUARDBUF_SIZE)
634 {
635 /* Read wraps */
636 size_t nowrap = (disk_buf.end + DISK_GUARDBUF_SIZE) - p;
637 rb->memcpy(buffer, p, nowrap);
638 rb->memcpy(buffer + nowrap, disk_buf.start + DISK_GUARDBUF_SIZE,
639 size - nowrap);
640 }
641 else
642 {
643 /* Read wasn't wrapped or guardbuffer holds it */
644 rb->memcpy(buffer, p, size);
645 }
646
647 disk_buf.offset += size;
648 }
649 else
650 {
651 size = -1;
652 }
653
654 disk_buf_unlock();
655
656 return size;
657}
658
659off_t disk_buf_lseek(off_t offset, int whence)
660{
661 disk_buf_lock();
662
663 /* The offset returned is the result of the current thread's action and
664 * may be invalidated so a local result is returned and not the value
665 * of disk_buf.offset directly */
666 switch (whence)
667 {
668 case SEEK_SET:
669 /* offset is just the offset */
670 break;
671 case SEEK_CUR:
672 offset += disk_buf.offset;
673 break;
674 case SEEK_END:
675 offset = disk_buf.filesize + offset;
676 break;
677 default:
678 disk_buf_unlock();
679 return -2; /* Invalid request */
680 }
681
682 if (offset < 0 || offset > disk_buf.filesize)
683 {
684 offset = -3;
685 }
686 else
687 {
688 disk_buf.offset = offset;
689 }
690
691 disk_buf_unlock();
692
693 return offset;
694}
695
696/* Prepare the buffer to enter the streaming state. Evaluates the available
697 * streaming window. */
698ssize_t disk_buf_prepare_streaming(off_t pos, size_t len)
699{
700 disk_buf_lock();
701
702 if (pos < 0)
703 pos = 0;
704 else if (pos > disk_buf.filesize)
705 pos = disk_buf.filesize;
706
707 DEBUGF("prepare streaming:\n pos:%ld len:%lu\n", pos, len);
708
709 pos = disk_buf_lseek(pos, SEEK_SET);
710 disk_buf_probe(pos, len, NULL, &len);
711
712 DEBUGF(" probe done: pos:%ld len:%lu\n", pos, len);
713
714 len = disk_buf_send_msg(STREAM_RESET, pos);
715
716 disk_buf_unlock();
717
718 return len;
719}
720
721/* Set the streaming window to an arbitrary position within the file. Makes no
722 * probes to validate data. Use after calling another function to cause data
723 * to be cached and correct values are known. */
724ssize_t disk_buf_set_streaming_window(off_t left, off_t right)
725{
726 ssize_t len;
727
728 disk_buf_lock();
729
730 if (left < 0)
731 left = 0;
732 else if (left > disk_buf.filesize)
733 left = disk_buf.filesize;
734
735 if (left > right)
736 right = left;
737
738 if (right > disk_buf.filesize)
739 right = disk_buf.filesize;
740
741 disk_buf.win_left = left;
742 disk_buf.win_right = right;
743 disk_buf.tail = disk_buf.start + ((right + DISK_BUF_PAGE_SIZE-1) &
744 ~DISK_BUF_PAGE_MASK) % disk_buf.size;
745
746 len = disk_buf.win_right - disk_buf.win_left;
747
748 disk_buf_unlock();
749
750 return len;
751}
752
753void * disk_buf_offset2ptr(off_t offset)
754{
755 if (offset < 0)
756 offset = 0;
757 else if (offset > disk_buf.filesize)
758 offset = disk_buf.filesize;
759
760 return disk_buf.start + (offset % disk_buf.size);
761}
762
763void disk_buf_close(void)
764{
765 disk_buf_lock();
766
767 if (disk_buf.in_file >= 0)
768 {
769 rb->close(disk_buf.in_file);
770 disk_buf.in_file = -1;
771
772 /* Invalidate entire cache */
773 rb->memset(disk_buf.cache, 0xff,
774 disk_buf.pgcount*sizeof (*disk_buf.cache));
775 disk_buf.file_pages = 0;
776 disk_buf.filesize = 0;
777 disk_buf.offset = 0;
778 }
779
780 disk_buf_unlock();
781}
782
783int disk_buf_open(const char *filename)
784{
785 int fd;
786
787 disk_buf_lock();
788
789 disk_buf_close();
790
791 fd = rb->open(filename, O_RDONLY);
792
793 if (fd >= 0)
794 {
795 ssize_t filesize = rb->filesize(fd);
796
797 if (filesize <= 0)
798 {
799 rb->close(disk_buf.in_file);
800 }
801 else
802 {
803 disk_buf.filesize = filesize;
804 /* Number of file pages rounded up toward +inf */
805 disk_buf.file_pages = ((size_t)filesize + DISK_BUF_PAGE_SIZE-1)
806 / DISK_BUF_PAGE_SIZE;
807 disk_buf.in_file = fd;
808 }
809 }
810
811 disk_buf_unlock();
812
813 return fd;
814}
815
816intptr_t disk_buf_send_msg(long id, intptr_t data)
817{
818 return rb->queue_send(disk_buf.q, id, data);
819}
820
821void disk_buf_post_msg(long id, intptr_t data)
822{
823 rb->queue_post(disk_buf.q, id, data);
824}
825
826void disk_buf_reply_msg(intptr_t retval)
827{
828 rb->queue_reply(disk_buf.q, retval);
829}
830
831bool disk_buf_init(void)
832{
833 disk_buf.thread = NULL;
834 list_initialize(&nf_list);
835
836 rb->mutex_init(&disk_buf_mtx);
837
838 disk_buf.q = &disk_buf_queue;
839 rb->queue_init(disk_buf.q, false);
840 rb->queue_enable_queue_send(disk_buf.q, &disk_buf_queue_send);
841
842 disk_buf.state = TSTATE_EOS;
843 disk_buf.status = STREAM_STOPPED;
844
845 disk_buf.in_file = -1;
846 disk_buf.filesize = 0;
847 disk_buf.win_left = 0;
848 disk_buf.win_right = 0;
849 disk_buf.time_last = 0;
850 disk_buf.pos_last = 0;
851 disk_buf.low_wm = DISK_BUF_LOW_WATERMARK;
852
853 disk_buf.start = mpeg_malloc_all(&disk_buf.size, MPEG_ALLOC_DISKBUF);
854 if (disk_buf.start == NULL)
855 return false;
856
857#ifdef PROC_NEEDS_CACHEALIGN
858 disk_buf.size = CACHEALIGN_BUFFER(&disk_buf.start, disk_buf.size);
859 disk_buf.start = UNCACHED_ADDR(disk_buf.start);
860#endif
861 disk_buf.size -= DISK_GUARDBUF_SIZE;
862 disk_buf.pgcount = disk_buf.size / DISK_BUF_PAGE_SIZE;
863
864 /* Fit it as tightly as possible */
865 while (disk_buf.pgcount*(sizeof (*disk_buf.cache) + DISK_BUF_PAGE_SIZE)
866 > (size_t)disk_buf.size)
867 {
868 disk_buf.pgcount--;
869 }
870
871 disk_buf.cache = (typeof (disk_buf.cache))disk_buf.start;
872 disk_buf.start += sizeof (*disk_buf.cache)*disk_buf.pgcount;
873 disk_buf.size = disk_buf.pgcount*DISK_BUF_PAGE_SIZE;
874 disk_buf.end = disk_buf.start + disk_buf.size;
875 disk_buf.tail = disk_buf.start;
876
877 DEBUGF("disk_buf info:\n"
878 " page count: %d\n"
879 " size: %ld\n",
880 disk_buf.pgcount, disk_buf.size);
881
882 rb->memset(disk_buf.cache, 0xff,
883 disk_buf.pgcount*sizeof (*disk_buf.cache));
884
885 disk_buf.thread = rb->create_thread(
886 disk_buf_thread, disk_buf_stack, sizeof(disk_buf_stack), 0,
887 "mpgbuffer" IF_PRIO(, PRIORITY_BUFFERING) IF_COP(, CPU));
888
889 if (disk_buf.thread == NULL)
890 return false;
891
892 /* Wait for thread to initialize */
893 disk_buf_send_msg(STREAM_NULL, 0);
894
895 return true;
896}
897
898void disk_buf_exit(void)
899{
900 if (disk_buf.thread != NULL)
901 {
902 rb->queue_post(disk_buf.q, STREAM_QUIT, 0);
903 rb->thread_wait(disk_buf.thread);
904 disk_buf.thread = NULL;
905 }
906}