bufopen: Add support for data types that can't wrap.
[Rockbox-MoB.git] / testplugin.c
blobd641b7aa77c6b043743276552b85899329e036a3
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Nicolas Pennequin
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
21 #include "plugin.h"
23 PLUGIN_HEADER
25 struct plugin_api* rb;
27 #define GUARD_SIZE (32*1024)
29 /* amount of data to read in one read() call */
30 #define AUDIO_DEFAULT_FILECHUNK (1024*32)
32 /* Ring buffer helper macros */
33 /* Buffer pointer (p) plus value (v), wrapped if necessary */
34 #define RINGBUF_ADD(p,v) ((p+v)<buffer_len ? p+v : p+v-buffer_len)
35 /* Buffer pointer (p) minus value (v), wrapped if necessary */
36 #define RINGBUF_SUB(p,v) ((p>=v) ? p-v : p+buffer_len-v)
37 /* How far value (v) plus buffer pointer (p1) will cross buffer pointer (p2) */
38 #define RINGBUF_ADD_CROSS(p1,v,p2) \
39 ((p1<p2) ? (int)(p1+v)-(int)p2 : (int)(p1+v-p2)-(int)buffer_len)
40 /* Bytes available in the buffer */
41 #define BUF_USED RINGBUF_SUB(buf_widx, buf_ridx)
43 #ifdef ROCKBOX_HAS_LOGF
44 #define DEBUGF rb->logf
45 #endif
47 int num_files = 5;
48 char *files[] = {
49 "/a.mp3",
50 "/b.mp3",
51 "/c.mp3",
52 "/d.mp3",
53 "/e.mp3" };
55 static void graph_view(int width);
57 enum data_type {
58 TYPE_CODEC,
59 TYPE_AUDIO,
60 TYPE_STREAM,
61 TYPE_ID3,
62 TYPE_CUESHEET,
63 TYPE_IMAGE,
64 TYPE_BUFFER,
65 TYPE_UNKNOWN,
68 struct memory_handle {
69 int id; /* A unique ID for the handle */
70 enum data_type type;
71 char path[MAX_PATH];
72 int fd;
73 size_t data; /* Start index of the handle's data buffer */
74 size_t ridx; /* Current read pointer, relative to the main buffer */
75 size_t widx; /* Current write pointer */
76 size_t filesize; /* File total length */
77 size_t filerem; /* Remaining bytes of file NOT in buffer */
78 size_t available; /* Available bytes to read from buffer */
79 size_t offset; /* Offset at which we started reading the file */
80 struct memory_handle *next;
82 /* at all times, we have: filesize == offset + available + filerem */
85 static char *buffer;
86 static char *guard_buffer;
88 static size_t buffer_len;
90 static size_t buf_widx; /* current writing position */
91 static size_t buf_ridx; /* current reading position */
92 /* buf_*idx are values relative to the buffer, not real pointers. */
94 static size_t conf_filechunk;
96 /* current memory handle in the linked list. NULL when the list is empty. */
97 static struct memory_handle *cur_handle;
98 /* first memory handle in the linked list. NULL when the list is empty. */
99 static struct memory_handle *first_handle;
101 static int num_handles; /* number of handles in the list */
103 /* Handle cache (makes find_handle faster).
104 These need to be global so that move_handle can invalidate them. */
105 static int cached_handle_id = -1;
106 static struct memory_handle *cached_handle = NULL;
110 LINKED LIST MANAGEMENT
111 ======================
113 add_handle : Add a handle to the list
114 rm_handle : Remove a handle from the list
115 find_handle : Get a handle pointer from an ID
116 move_handle : Move a handle in the buffer (with or without its data)
118 These functions only handle the linked list structure. They don't touch the
119 contents of the struct memory_handle headers. They also change the buf_*idx
120 pointers when necessary and manage the handle IDs.
122 The first and current (== last) handle are kept track of.
123 A new handle is added at buf_widx and becomes the current one.
124 buf_widx always points to the current writing position for the current handle
125 buf_ridx always points to the location of the first handle.
126 buf_ridx == buf_widx means the buffer is empty.
130 /* Add a new handle to the linked list and return it. It will have become the
131 new current handle. The handle will reserve "data_size" bytes or if that's
132 not possible, decrease "data_size" to allow adding the handle. */
133 static struct memory_handle *add_handle(size_t *data_size)
135 /* this will give each handle a unique id */
136 static int cur_handle_id = 1;
138 /* make sure buf_widx is 32-bit aligned so that the handle struct is,
139 but before that we check we can actually align. */
140 if (RINGBUF_ADD_CROSS(buf_widx, 3, buf_ridx) >= 0) {
141 return NULL;
143 buf_widx = (RINGBUF_ADD(buf_widx, 3)) & ~3;
145 size_t len = (data_size ? *data_size : 0)
146 + sizeof(struct memory_handle);
148 /* check that we actually can add the handle and its data */
149 int overlap = RINGBUF_ADD_CROSS(buf_widx, len, buf_ridx);
150 if (overlap >= 0) {
151 *data_size -= overlap;
152 len -= overlap;
154 if (len < sizeof(struct memory_handle)) {
155 /* There isn't even enough space to write the struct */
156 return NULL;
159 struct memory_handle *new_handle =
160 (struct memory_handle *)(&buffer[buf_widx]);
162 /* only advance the buffer write index of the size of the struct */
163 buf_widx = RINGBUF_ADD(buf_widx, sizeof(struct memory_handle));
165 if (!first_handle) {
166 /* the new handle is the first one */
167 first_handle = new_handle;
170 if (cur_handle) {
171 cur_handle->next = new_handle;
174 cur_handle = new_handle;
175 cur_handle->id = cur_handle_id++;
176 cur_handle->next = NULL;
177 num_handles++;
178 return cur_handle;
181 /* Delete a given memory handle from the linked list
182 and return true for success. Nothing is actually erased from memory. */
183 static bool rm_handle(struct memory_handle *h)
185 if (h == first_handle) {
186 first_handle = h->next;
187 if (h == cur_handle) {
188 /* h was the first and last handle: the buffer is now empty */
189 cur_handle = NULL;
190 buf_ridx = buf_widx;
191 } else {
192 /* update buf_ridx to point to the new first handle */
193 buf_ridx = (void *)first_handle - (void *)buffer;
195 } else {
196 struct memory_handle *m = first_handle;
197 while (m && m->next != h) {
198 m = m->next;
200 if (h && m && m->next == h) {
201 m->next = h->next;
202 if (h == cur_handle) {
203 cur_handle = m;
205 } else {
206 return false;
210 num_handles--;
211 return true;
214 /* Return a pointer to the memory handle of given ID.
215 NULL if the handle wasn't found */
216 static struct memory_handle *find_handle(int handle_id)
218 /* simple caching because most of the time the requested handle
219 will either be the same as the last, or the one after the last */
220 if (cached_handle)
222 if (cached_handle_id == handle_id &&
223 cached_handle_id == cached_handle->id)
224 return cached_handle;
225 else if (cached_handle->next && (cached_handle->next->id == handle_id))
227 /* JD's quick testing showd this block was only entered
228 2/1971 calls to find_handle.
229 8/1971 calls to find_handle resulted in a cache miss */
230 cached_handle = cached_handle->next;
231 cached_handle_id = handle_id;
232 return cached_handle;
236 struct memory_handle *m = first_handle;
237 while (m && m->id != handle_id) {
238 m = m->next;
240 cached_handle_id = handle_id;
241 cached_handle = m;
242 return (m && m->id == handle_id) ? m : NULL;
245 /* Move a memory handle and data_size of its data of delta.
246 Return a pointer to the new location of the handle.
247 delta is the value of which to move the struct data.
248 data_size is the amount of data to move along with the struct. */
249 static struct memory_handle *move_handle(struct memory_handle *h,
250 size_t *delta, size_t data_size)
252 if (*delta < 4) {
253 /* aligning backwards would yield a negative result,
254 and moving the handle of such a small amount is a waste
255 of time anyway. */
256 return NULL;
258 /* make sure delta is 32-bit aligned so that the handle struct is. */
259 *delta = (*delta - 3) & ~3;
261 size_t newpos = RINGBUF_ADD((void *)h - (void *)buffer, *delta);
263 struct memory_handle *dest = (struct memory_handle *)(&buffer[newpos]);
265 /* Invalidate the cache to prevent it from keeping the old location of h */
266 if (h == cached_handle)
267 cached_handle = NULL;
269 /* the cur_handle pointer might need updating */
270 if (h == cur_handle) {
271 cur_handle = dest;
274 if (h == first_handle) {
275 first_handle = dest;
276 buf_ridx = newpos;
277 } else {
278 struct memory_handle *m = first_handle;
279 while (m && m->next != h) {
280 m = m->next;
282 if (h && m && m->next == h) {
283 m->next = dest;
284 } else {
285 return NULL;
289 rb->memmove(dest, h, sizeof(struct memory_handle) + data_size);
291 return dest;
296 BUFFER SPACE MANAGEMENT
297 =======================
299 buffer_handle : Buffer data for a handle
300 free_buffer : Free buffer space by moving a handle
301 fill_buffer : Call buffer_handle for all handles that have data to buffer
302 data_rem : Total amount of data needing to be buffered
303 wasted_space : Total amount of space available for freeing
305 These functions are used by the buffering thread to manage buffer space.
308 /* Buffer data for the given handle. Return the amount of data buffered
309 or -1 if the handle wasn't found */
310 static ssize_t buffer_handle(int handle_id)
312 DEBUGF("buffer_handle(%d)\n", handle_id);
313 struct memory_handle *h = find_handle(handle_id);
314 if (!h)
315 return -1;
317 if (h->filerem == 0) {
318 /* nothing left to buffer */
319 return 0;
322 if (h->fd < 0) /* file closed, reopen */
324 if (*h->path)
325 h->fd = rb->open(h->path, O_RDONLY);
326 else
327 return -1;
329 if (h->fd < 0)
330 return -1;
332 if (h->offset)
333 rb->lseek(h->fd, h->offset, SEEK_SET);
336 ssize_t ret = 0;
337 while (h->filerem > 0)
339 /* max amount to copy */
340 size_t copy_n = MIN( MIN(h->filerem, conf_filechunk),
341 buffer_len - h->widx);
343 /* stop copying if it would overwrite the reading position
344 or the next handle */
345 if (RINGBUF_ADD_CROSS(h->widx, copy_n, buf_ridx) >= 0 || (h->next &&
346 RINGBUF_ADD_CROSS(h->widx, copy_n, (unsigned)
347 ((void *)h->next - (void *)buffer)) > 0))
348 break;
350 /* rc is the actual amount read */
351 int rc = rb->read(h->fd, &buffer[h->widx], copy_n);
353 if (rc < 0)
355 DEBUGF("File ended %ld bytes early\n", (long)h->filerem);
356 h->filesize -= h->filerem;
357 h->filerem = 0;
358 break;
361 /* Advance buffer */
362 h->widx = RINGBUF_ADD(h->widx, rc);
363 if (h == cur_handle)
364 buf_widx = h->widx;
365 h->available += rc;
366 ret += rc;
367 h->filerem -= rc;
370 if (h->filerem == 0) {
371 /* finished buffering the file */
372 rb->close(h->fd);
375 DEBUGF("buffered %ld bytes (%ld of %ld available, rem: %ld, off: %ld)\n",
376 ret, h->available, h->filesize, h->filerem, h->offset);
378 graph_view(100);
380 return ret;
383 /* Free buffer space by moving the handle struct right before the useful
384 part of its data buffer or by moving all the data. */
385 static void free_buffer(int handle_id)
387 struct memory_handle *h = find_handle(handle_id);
388 if (!h)
389 return;
391 size_t delta;
392 /* The value of delta might change for alignment reasons */
394 if (h->next && (h->type == TYPE_ID3 || h->type == TYPE_CUESHEET ||
395 h->type == TYPE_IMAGE) && h->filerem == 0 )
397 /* metadata handle: we can move all of it */
398 delta = RINGBUF_SUB( (unsigned)((void *)h->next - (void *)buffer),
399 h->data) - h->available;
400 h = move_handle(h, &delta, h->available);
401 if (!h) return;
402 h->data = RINGBUF_ADD(h->data, delta);
403 h->ridx = RINGBUF_ADD(h->ridx, delta);
404 h->widx = RINGBUF_ADD(h->widx, delta);
405 DEBUGF("free_buffer(%d): metadata, moved by %ld bytes\n",
406 handle_id, delta);
408 else
410 /* only move the handle struct */
411 delta = RINGBUF_SUB(h->ridx, h->data);
412 h = move_handle(h, &delta, 0);
413 if (!h) return;
414 h->data = RINGBUF_ADD(h->data, delta);
415 h->available -= delta;
416 h->offset += delta;
417 DEBUGF("free_buffer(%d): audio, %ld bytes freed\n", handle_id, delta);
421 /* Fill the buffer by buffering as much data as possible for handles that still
422 have data left to buffer */
423 static void fill_buffer(void)
425 DEBUGF("fill buffer()\n");
426 struct memory_handle *m = first_handle;
427 while (m) {
428 if (m->filerem > 0) {
429 buffer_handle(m->id);
431 m = m->next;
435 /* Return the total amount of data left to be buffered for all the handles */
436 static size_t data_rem(void)
438 size_t ret = 0;
440 struct memory_handle *m = first_handle;
441 while (m) {
442 ret += m->filerem;
443 m = m->next;
446 return ret;
449 /* Return the amount of data we have but don't need anymore. This data can be
450 safely erased to reclaim buffer space. */
451 static size_t wasted_space(void)
453 size_t ret = 0;
455 struct memory_handle *m = first_handle;
456 while (m) {
457 ret += RINGBUF_SUB(m->ridx, m->data);
458 m = m->next;
461 return ret;
466 BUFFERING API FUNCTIONS
467 =======================
469 bufopen : Request the opening of a new handle for a file
470 bufclose : Close an open handle
471 bufseek : Set the read pointer in a handle
472 bufadvance : Move the read pointer in a handle
473 bufread : Copy data from a handle into a given buffer
474 bufgetdata : Give a pointer to the handle's data
476 These functions are exported, to allow interaction with the buffer.
477 They take care of the content of the structs, and rely on the linked list
478 management functions for all the actual handle management work.
482 /* Request a file be buffered
483 filename: name of the file to open
484 offset:starting offset to buffer from the file
485 return value: <0 if the file cannot be opened, or one file already
486 queued to be opened, otherwise the handle for the file in the buffer
488 int bufopen(char *file, size_t offset, enum data_type type)
490 if (cur_handle && cur_handle->filerem > 0) {
491 /* the current handle hasn't finished buffering. We can only add
492 a new one if there is already enough free space to finish
493 the buffering. */
494 if (cur_handle->filerem < (buffer_len - BUF_USED)) {
495 /* Before adding the new handle we reserve some space for the
496 current one to finish buffering its data. */
497 buf_widx = RINGBUF_ADD(buf_widx, cur_handle->filerem);
498 } else {
499 return -2;
503 int fd = rb->open(file, O_RDONLY);
504 if (fd < 0)
505 return -1;
507 size_t size = rb->filesize(fd) - offset;
509 if (type != TYPE_AUDIO &&
510 size + sizeof(struct memory_handle) > buffer_len - buf_widx)
512 /* for types other than audio, the data can't wrap */
513 return -2;
516 DEBUGF("bufopen: %s (offset: %ld) (%ld bytes needed)...\n",
517 file, offset, size);
519 struct memory_handle *h = add_handle(&size);
520 if (!h)
522 DEBUGF("failed to add handle\n");
523 rb->close(fd);
524 return -2;
527 if (offset) rb->lseek(fd, offset, SEEK_SET);
528 rb->strncpy(h->path, file, MAX_PATH);
529 h->fd = fd;
530 h->filesize = rb->filesize(fd);
531 h->filerem = h->filesize - offset;
532 h->offset = offset;
533 h->ridx = buf_widx;
534 h->widx = buf_widx;
535 h->data = buf_widx;
536 h->available = 0;
537 h->type = type;
539 DEBUGF("allocated %ld bytes. ID: %d\n", size, h->id);
540 return h->id;
543 /* Close the handle. Return 0 for success and < 0 for failure */
544 int bufclose(int handle_id)
546 DEBUGF("bufclose(%d)\n", handle_id);
547 struct memory_handle *h = find_handle(handle_id);
548 if (!h)
549 return -1;
551 rm_handle(h);
552 return 0;
555 /* Set reading index in handle (relatively to the start of the file).
556 Access before the available data will trigger a rebuffer.
557 TODO: Test this
558 TODO: Maybe force an immediate rebuffer by calling buffer_handle() ?
559 Return 0 for success and < 0 for failure:
560 -1 if the handle wasn't found
561 -2 if there is no data available at the new position
562 (the reading index is still moved)
563 -3 if the new requested position was beyond the end of the file
565 int bufseek(int handle_id, size_t newpos)
567 int ret = 0;
568 struct memory_handle *h = find_handle(handle_id);
569 if (!h)
570 return -1;
572 if (newpos > h->filesize) {
573 /* access beyond the end of the file */
574 return -3;
577 else if (newpos < h->offset) {
578 /* access before what we currently have. A rebuffer is needed. */
579 h->offset = newpos;
580 h->available = 0;
581 h->filerem = h->filesize - newpos;
582 /* having changed filerem should be enough to trigger the rebuffer. */
583 h->widx = h->data;
584 ret = -2;
587 else if (newpos > h->offset + h->available) {
588 /* data isn't available yet. */
589 ret = -2;
592 h->ridx = RINGBUF_ADD(h->data, newpos);
593 return ret;
596 /* Advance the reading index in a handle (relatively to its current position).
597 Return 0 for success and < 0 for failure
598 TODO: Add some rebuffering like in bufseek */
599 int bufadvance(int handle_id, off_t offset)
601 struct memory_handle *h = find_handle(handle_id);
602 if (!h)
603 return -1;
605 if (offset >= 0)
607 /* check for access beyond what's available */
608 if ((size_t)offset > (h->available - RINGBUF_SUB(h->ridx, h->data)))
609 return -2;
611 h->ridx = RINGBUF_ADD(h->ridx, offset);
613 else
615 /* check for access before what's available */
616 if ((size_t)(-offset) > RINGBUF_SUB(h->ridx, h->data))
617 return -2;
619 h->ridx = RINGBUF_SUB(h->ridx, (size_t)(-offset));
622 return 0;
625 /* Copy data from the given handle to the dest buffer.
626 Return the number of bytes copied or < 0 for failure. */
627 ssize_t bufread(int handle_id, size_t size, char *dest)
629 struct memory_handle *h = find_handle(handle_id);
630 size_t buffered_data;
631 if (!h)
632 return -1;
634 if (h->available == 0 && h->filerem > 0) /* Data isn't ready */
635 return -2;
637 if (h->available == 0 && h->filerem == 0) /* File is finished reading */
638 return 0;
640 buffered_data = MIN(size, h->available - RINGBUF_SUB(h->ridx, h->data));
642 if (h->ridx + buffered_data > buffer_len)
644 /* the data wraps around the end of the buffer */
645 size_t read = buffer_len - h->ridx;
646 rb->memcpy(dest, &buffer[h->ridx], read);
647 rb->memcpy(dest+read, buffer, buffered_data - read);
649 else rb->memcpy(dest, &buffer[h->ridx], buffered_data);
651 return buffered_data;
654 /* Update the "data" pointer to make the handle's data available to the caller.
655 Return the length of the available linear data or < 0 for failure.
656 size is the amount of linear data requested. it can be 0 to get as
657 much as possible.
658 The guard buffer may be used to provide the requested size */
659 ssize_t bufgetdata(int handle_id, size_t size, unsigned char **data)
661 struct memory_handle *h = find_handle(handle_id);
662 if (!h)
663 return -1;
665 if (h->available == 0 && h->filerem > 0) /* Data isn't ready */
666 return -2;
668 if (h->available == 0 && h->filerem == 0) /* File is finished reading */
669 return 0;
671 ssize_t ret;
673 if (h->ridx + size > buffer_len &&
674 h->available - RINGBUF_SUB(h->ridx, h->data) >= size)
676 /* the data wraps around the end of the buffer :
677 use the guard buffer to provide the requested amount of data. */
678 size_t copy_n = MIN(h->ridx + size - buffer_len, GUARD_SIZE);
679 rb->memcpy(guard_buffer, (unsigned char *)buffer, copy_n);
680 ret = buffer_len - h->ridx + copy_n;
681 DEBUGF("used the guard buffer to complete\n");
683 else
685 ret = MIN(h->available - RINGBUF_SUB(h->ridx, h->data),
686 buffer_len - h->ridx);
689 *data = (unsigned char *)&buffer[h->ridx];
691 /* DEBUGF("bufgetdata(%d): h->ridx=%ld, ret=%ld\n", handle_id,
692 (long)h->ridx, ret); */
693 return ret;
698 UTILITY FUNCTIONS
699 =================
702 bool test_ll(void)
704 struct memory_handle *m1, *m2, *m3, *m4;
706 if (cur_handle != NULL || first_handle != NULL)
707 return false;
709 m1 = add_handle(NULL);
711 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
712 return false;
714 m2 = add_handle(NULL);
716 if (cur_handle != m2 || first_handle != m1 || m1->next != m2 || m2->next != NULL)
717 return false;
719 m3 = add_handle(NULL);
721 if (cur_handle != m3 || first_handle != m1 || m2->next != m3 || m3->next != NULL)
722 return false;
724 rm_handle(m2);
726 if (cur_handle != m3 || first_handle != m1 || m1->next != m3)
727 return false;
729 rm_handle(m3);
731 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
732 return false;
734 m4 = add_handle(NULL);
736 if (cur_handle != m4 || first_handle != m1 || m1->next != m4 || m4->next != NULL)
737 return false;
739 rm_handle(m1);
741 if (cur_handle != m4 || first_handle != m4)
742 return false;
744 rm_handle(m4);
746 if (cur_handle != NULL || first_handle != NULL)
747 return false;
749 m1 = add_handle(NULL);
750 m2 = add_handle(NULL);
751 m3 = add_handle(NULL);
752 m4 = add_handle(NULL);
754 if (cur_handle != m4 || first_handle != m1)
755 return false;
757 size_t delta = 1024*100;
758 m2 = move_handle(m2, &delta, 0);
760 if (cur_handle != m4 || first_handle != m1 || m1->next != m2 || m2->next != m3)
761 return false;
763 delta = 1024*100*3;
764 m1 = move_handle(m1, &delta, 0);
766 if (cur_handle != m4 || first_handle != m1 || m1->next != m2)
767 return false;
769 rm_handle(m1);
770 rm_handle(m2);
771 rm_handle(m3);
772 rm_handle(m4);
774 if (cur_handle != NULL || first_handle != NULL)
775 return false;
777 return true;
780 /* display a nice graphical view of the ringbuffer. */
781 static void graph_view(int width)
783 #ifndef ROCKBOX_HAS_LOGF
784 int i, r_pos, w_pos;
785 r_pos = buf_ridx * width / buffer_len;
786 w_pos = buf_widx * width / buffer_len;
788 DEBUGF("|");
789 for (i=0; i <= width; i++)
791 if (i != r_pos && i != w_pos)
793 if (buf_ridx <= buf_widx)
795 if (i > r_pos && i < w_pos) {
796 DEBUGF(">");
797 } else {
798 DEBUGF("-");
801 else
803 if (i > r_pos || i < w_pos) {
804 DEBUGF(">");
805 } else {
806 DEBUGF("-");
810 else
812 if (i == r_pos && i == w_pos)
814 if (buf_ridx <= buf_widx) {
815 DEBUGF("RW");
816 } else {
817 DEBUGF("WR");
819 } else if (i == r_pos) {
820 DEBUGF("R");
821 } else if (i == w_pos) {
822 DEBUGF("W");
826 DEBUGF("|");
827 DEBUGF("\n");
828 #else
829 (void)width;
830 #endif
833 void print_progress(size_t amount, int file, int numfiles)
835 char buf[32];
836 rb->snprintf(buf, sizeof(buf), "file %d of %d", file, numfiles);
837 rb->lcd_puts(0, 0, buf);
838 rb->snprintf(buf, sizeof(buf), "read: %ld", amount);
839 rb->lcd_puts(0, 1, buf);
842 void print_metadata(int handle_id)
844 ssize_t ret;
845 char *artist, *title, *newline;
846 char art[50], ttl[50];
847 int artist_len, title_len;
848 unsigned char *buf;
850 ret = bufgetdata(handle_id, 0, &buf);
852 artist = buf;
853 newline = rb->strchr(artist, '\n');
854 artist_len = newline - artist;
855 rb->strncpy(art, artist, artist_len);
856 art[artist_len] = 0;
858 title = newline + 1;
859 newline = rb->strchr(title, '\n');
860 title_len = newline - title;
861 rb->strncpy(ttl, title, title_len);
862 ttl[title_len] = 0;
864 rb->lcd_puts(0, 3, art);
865 rb->lcd_puts(0, 4, ttl);
868 bool buffer_init(void)
870 buffer = rb->plugin_get_audio_buffer(&buffer_len);
871 if (!buffer)
873 DEBUGF("couldn't allocate buffer\n");
874 return false;
876 buffer_len -= GUARD_SIZE;
877 guard_buffer = buffer + buffer_len;
879 buf_widx = 0;
880 buf_ridx = 0;
882 first_handle = NULL;
883 num_handles = 0;
885 conf_filechunk = AUDIO_DEFAULT_FILECHUNK;
887 return true;
890 bool disk_is_spinning(void)
892 return true;
897 THREADING CODE
898 ==============
901 static long codec_stack[4*DEFAULT_STACK_SIZE/sizeof(long)];
902 static struct thread_entry* codecthread_id;
904 static long bufopen_stack[2*DEFAULT_STACK_SIZE/sizeof(long)];
905 static struct thread_entry* bufopenthread_id;
907 bool done_playing = false;
909 #define MAX_HANDLES 16
911 int handles[MAX_HANDLES];
912 int meta_handles[MAX_HANDLES];
914 void codec_thread(void)
916 int idx = 0;
917 int fd = -1; /* used to write the files out as they are read */
918 unsigned char *data;
919 char outfile[MAX_PATH];
920 long read, total = 0;
922 while (1)
924 if (!done_playing)
926 if (handles[idx] > 0) {
928 /* create the output file */
929 if (fd < 0) {
930 rb->snprintf(outfile, MAX_PATH, "/file%d.mp3", idx);
931 fd = rb->open(outfile, O_CREAT|O_TRUNC|O_WRONLY);
932 if (fd < 0) {
933 DEBUGF("couldn't create file\n");
934 rb->splash(HZ, "couldn't create file");
938 /* read as much data as possible */
939 do {
940 read = bufgetdata(handles[idx], GUARD_SIZE, &data);
941 if (read >= 0) {
942 read = MIN(read, GUARD_SIZE);
943 rb->write(fd, data, read);
944 total += read;
945 bufadvance(handles[idx], read);
946 rb->lcd_clear_display();
947 print_progress(total, idx+1, num_files);
948 print_metadata(meta_handles[idx]);
949 rb->lcd_update();
951 rb->sleep(HZ/100);
952 } while (read > 0);
954 if (read >= 0 && total >= 0) {
955 /* some data was read */
956 DEBUGF("read %ld bytes from handle %d\n", total,
957 handles[idx]);
960 /* check the return value to determine what exactly happened */
961 if (read == -2) {
962 DEBUGF("data for handle %d isn't ready\n", handles[idx]);
963 } else if (read == -1) {
964 DEBUGF("couldn't find handle %d\n", handles[idx]);
965 } else if (read == 0) {
966 DEBUGF("finished reading handle %d\n", handles[idx]);
967 bufclose(handles[idx]);
968 bufclose(meta_handles[idx]);
969 rb->close(fd);
970 fd = -1;
971 total = 0;
973 /* move on to the next file and check if we've finished */
974 idx++;
975 if (idx >= num_files) {
976 done_playing = true;
977 break;
983 rb->sleep(HZ/10);
986 DEBUGF("removing the codec thread\n");
987 rb->remove_thread(NULL);
990 void bufopen_thread(void)
992 int idx = 0, ret;
993 char buf[MAX_PATH];
994 while (idx < num_files)
996 /* open the metadata file */
997 rb->snprintf(buf, MAX_PATH, "/meta%s.txt", files[idx]);
998 meta_handles[idx] = bufopen(buf, 0, TYPE_ID3);
999 if (meta_handles[idx] > 0)
1001 /* open the audio file */
1002 ret = bufopen(files[idx], 0, TYPE_AUDIO);
1003 if (ret > 0) {
1004 handles[idx++] = ret;
1005 } else {
1006 /* couldn't open the audio file, close the metadata handle */
1007 bufclose(meta_handles[idx]);
1010 rb->sleep(HZ*2);
1013 DEBUGF("bufopen thread finished\n");
1014 rb->remove_thread(NULL);
1018 /* this is the plugin entry point */
1019 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1021 (void)parameter;
1022 rb = api;
1024 buffer_init();
1026 if (!test_ll())
1028 DEBUGF("linked list test failed\n");
1029 rb->splash(HZ, "linked list test failed");
1030 return PLUGIN_ERROR;
1033 codecthread_id = rb->create_thread(codec_thread,
1034 codec_stack,
1035 sizeof(codec_stack),
1036 "codec"
1037 IF_PRIO(,PRIORITY_PLAYBACK)
1038 IF_COP(, CPU, false));
1040 bufopenthread_id = rb->create_thread(bufopen_thread,
1041 bufopen_stack,
1042 sizeof(bufopen_stack),
1043 "bufopen"
1044 IF_PRIO(,PRIORITY_BACKGROUND)
1045 IF_COP(, CPU, false));
1047 if (!codecthread_id)
1049 rb->splash(HZ, "failed to create codec thread");
1050 return PLUGIN_ERROR;
1052 else if (!bufopenthread_id)
1054 rb->splash(HZ, "failed to create bufopen thread");
1055 return PLUGIN_ERROR;
1057 else
1060 while (!done_playing)
1062 if (data_rem() > 0 && wasted_space() > buffer_len/5) {
1063 DEBUGF("there is %ld bytes of wasted space\n", wasted_space());
1065 /* free buffer from outdated audio data */
1066 struct memory_handle *m = first_handle;
1067 while (m) {
1068 if (m->type == TYPE_AUDIO)
1069 free_buffer(m->id);
1070 m = m->next;
1073 /* free buffer by moving metadata */
1074 m = first_handle;
1075 while (m) {
1076 if (m->type != TYPE_AUDIO)
1077 free_buffer(m->id);
1078 m = m->next;
1081 graph_view(100);
1084 if (data_rem() > 0 && BUF_USED < 3*buffer_len/4 &&
1085 disk_is_spinning())
1087 DEBUGF("%ld bytes left to buffer and the buffer is low\n",
1088 data_rem());
1089 fill_buffer();
1090 } else {
1091 rb->sleep(HZ/2);
1094 DEBUGF("done playing\n");
1095 rb->yield();
1098 rb->backlight_on();
1099 DEBUGF("end of plugin\n");
1100 return PLUGIN_OK;