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/disk_buf.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/disk_buf.c')
-rw-r--r-- | apps/plugins/mpegplayer/disk_buf.c | 906 |
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 | |||
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 | } | ||