buffer_handle: Remember that we closed the FD once the file is buffered.
[Rockbox-MoB.git] / testplugin.c
blob3cc2db0b4f4745511f39d4a530d6f4ad2ec7c660
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);
373 h->fd = -1;
376 DEBUGF("buffered %ld bytes (%ld of %ld available, rem: %ld, off: %ld)\n",
377 ret, h->available, h->filesize, h->filerem, h->offset);
379 graph_view(100);
381 return ret;
384 /* Free buffer space by moving the handle struct right before the useful
385 part of its data buffer or by moving all the data. */
386 static void free_buffer(int handle_id)
388 struct memory_handle *h = find_handle(handle_id);
389 if (!h)
390 return;
392 size_t delta;
393 /* The value of delta might change for alignment reasons */
395 if (h->next && (h->type == TYPE_ID3 || h->type == TYPE_CUESHEET ||
396 h->type == TYPE_IMAGE) && h->filerem == 0 )
398 /* metadata handle: we can move all of it */
399 delta = RINGBUF_SUB( (unsigned)((void *)h->next - (void *)buffer),
400 h->data) - h->available;
401 h = move_handle(h, &delta, h->available);
402 if (!h) return;
403 h->data = RINGBUF_ADD(h->data, delta);
404 h->ridx = RINGBUF_ADD(h->ridx, delta);
405 h->widx = RINGBUF_ADD(h->widx, delta);
406 DEBUGF("free_buffer(%d): metadata, moved by %ld bytes\n",
407 handle_id, delta);
409 else
411 /* only move the handle struct */
412 delta = RINGBUF_SUB(h->ridx, h->data);
413 h = move_handle(h, &delta, 0);
414 if (!h) return;
415 h->data = RINGBUF_ADD(h->data, delta);
416 h->available -= delta;
417 h->offset += delta;
418 DEBUGF("free_buffer(%d): audio, %ld bytes freed\n", handle_id, delta);
422 /* Fill the buffer by buffering as much data as possible for handles that still
423 have data left to buffer */
424 static void fill_buffer(void)
426 DEBUGF("fill buffer()\n");
427 struct memory_handle *m = first_handle;
428 while (m) {
429 if (m->filerem > 0) {
430 buffer_handle(m->id);
432 m = m->next;
436 /* Return the total amount of data left to be buffered for all the handles */
437 static size_t data_rem(void)
439 size_t ret = 0;
441 struct memory_handle *m = first_handle;
442 while (m) {
443 ret += m->filerem;
444 m = m->next;
447 return ret;
450 /* Return the amount of data we have but don't need anymore. This data can be
451 safely erased to reclaim buffer space. */
452 static size_t wasted_space(void)
454 size_t ret = 0;
456 struct memory_handle *m = first_handle;
457 while (m) {
458 ret += RINGBUF_SUB(m->ridx, m->data);
459 m = m->next;
462 return ret;
467 BUFFERING API FUNCTIONS
468 =======================
470 bufopen : Request the opening of a new handle for a file
471 bufclose : Close an open handle
472 bufseek : Set the read pointer in a handle
473 bufadvance : Move the read pointer in a handle
474 bufread : Copy data from a handle into a given buffer
475 bufgetdata : Give a pointer to the handle's data
477 These functions are exported, to allow interaction with the buffer.
478 They take care of the content of the structs, and rely on the linked list
479 management functions for all the actual handle management work.
483 /* Request a file be buffered
484 filename: name of the file to open
485 offset:starting offset to buffer from the file
486 return value: <0 if the file cannot be opened, or one file already
487 queued to be opened, otherwise the handle for the file in the buffer
489 int bufopen(char *file, size_t offset, enum data_type type)
491 if (cur_handle && cur_handle->filerem > 0) {
492 /* the current handle hasn't finished buffering. We can only add
493 a new one if there is already enough free space to finish
494 the buffering. */
495 if (cur_handle->filerem < (buffer_len - BUF_USED)) {
496 /* Before adding the new handle we reserve some space for the
497 current one to finish buffering its data. */
498 buf_widx = RINGBUF_ADD(buf_widx, cur_handle->filerem);
499 } else {
500 return -2;
504 int fd = rb->open(file, O_RDONLY);
505 if (fd < 0)
506 return -1;
508 size_t size = rb->filesize(fd) - offset;
510 if (type != TYPE_AUDIO &&
511 size + sizeof(struct memory_handle) > buffer_len - buf_widx)
513 /* for types other than audio, the data can't wrap */
514 return -2;
517 DEBUGF("bufopen: %s (offset: %ld) (%ld bytes needed)...\n",
518 file, offset, size);
520 struct memory_handle *h = add_handle(&size);
521 if (!h)
523 DEBUGF("failed to add handle\n");
524 rb->close(fd);
525 return -2;
528 if (offset) rb->lseek(fd, offset, SEEK_SET);
529 rb->strncpy(h->path, file, MAX_PATH);
530 h->fd = fd;
531 h->filesize = rb->filesize(fd);
532 h->filerem = h->filesize - offset;
533 h->offset = offset;
534 h->ridx = buf_widx;
535 h->widx = buf_widx;
536 h->data = buf_widx;
537 h->available = 0;
538 h->type = type;
540 DEBUGF("allocated %ld bytes. ID: %d\n", size, h->id);
541 return h->id;
544 /* Close the handle. Return 0 for success and < 0 for failure */
545 int bufclose(int handle_id)
547 DEBUGF("bufclose(%d)\n", handle_id);
548 struct memory_handle *h = find_handle(handle_id);
549 if (!h)
550 return -1;
552 rm_handle(h);
553 return 0;
556 /* Set reading index in handle (relatively to the start of the file).
557 Access before the available data will trigger a rebuffer.
558 TODO: Test this
559 TODO: Maybe force an immediate rebuffer by calling buffer_handle() ?
560 Return 0 for success and < 0 for failure:
561 -1 if the handle wasn't found
562 -2 if there is no data available at the new position
563 (the reading index is still moved)
564 -3 if the new requested position was beyond the end of the file
566 int bufseek(int handle_id, size_t newpos)
568 int ret = 0;
569 struct memory_handle *h = find_handle(handle_id);
570 if (!h)
571 return -1;
573 if (newpos > h->filesize) {
574 /* access beyond the end of the file */
575 return -3;
578 else if (newpos < h->offset) {
579 /* access before what we currently have. A rebuffer is needed. */
580 h->offset = newpos;
581 h->available = 0;
582 h->filerem = h->filesize - newpos;
583 /* having changed filerem should be enough to trigger the rebuffer. */
584 h->widx = h->data;
585 ret = -2;
588 else if (newpos > h->offset + h->available) {
589 /* data isn't available yet. */
590 ret = -2;
593 h->ridx = RINGBUF_ADD(h->data, newpos);
594 return ret;
597 /* Advance the reading index in a handle (relatively to its current position).
598 Return 0 for success and < 0 for failure
599 TODO: Add some rebuffering like in bufseek */
600 int bufadvance(int handle_id, off_t offset)
602 struct memory_handle *h = find_handle(handle_id);
603 if (!h)
604 return -1;
606 if (offset >= 0)
608 /* check for access beyond what's available */
609 if ((size_t)offset > (h->available - RINGBUF_SUB(h->ridx, h->data)))
610 return -2;
612 h->ridx = RINGBUF_ADD(h->ridx, offset);
614 else
616 /* check for access before what's available */
617 if ((size_t)(-offset) > RINGBUF_SUB(h->ridx, h->data))
618 return -2;
620 h->ridx = RINGBUF_SUB(h->ridx, (size_t)(-offset));
623 return 0;
626 /* Copy data from the given handle to the dest buffer.
627 Return the number of bytes copied or < 0 for failure. */
628 ssize_t bufread(int handle_id, size_t size, char *dest)
630 struct memory_handle *h = find_handle(handle_id);
631 size_t buffered_data;
632 if (!h)
633 return -1;
635 if (h->available == 0 && h->filerem > 0) /* Data isn't ready */
636 return -2;
638 if (h->available == 0 && h->filerem == 0) /* File is finished reading */
639 return 0;
641 buffered_data = MIN(size, h->available - RINGBUF_SUB(h->ridx, h->data));
643 if (h->ridx + buffered_data > buffer_len)
645 /* the data wraps around the end of the buffer */
646 size_t read = buffer_len - h->ridx;
647 rb->memcpy(dest, &buffer[h->ridx], read);
648 rb->memcpy(dest+read, buffer, buffered_data - read);
650 else rb->memcpy(dest, &buffer[h->ridx], buffered_data);
652 return buffered_data;
655 /* Update the "data" pointer to make the handle's data available to the caller.
656 Return the length of the available linear data or < 0 for failure.
657 size is the amount of linear data requested. it can be 0 to get as
658 much as possible.
659 The guard buffer may be used to provide the requested size */
660 ssize_t bufgetdata(int handle_id, size_t size, unsigned char **data)
662 struct memory_handle *h = find_handle(handle_id);
663 if (!h)
664 return -1;
666 if (h->available == 0 && h->filerem > 0) /* Data isn't ready */
667 return -2;
669 if (h->available == 0 && h->filerem == 0) /* File is finished reading */
670 return 0;
672 ssize_t ret;
674 if (h->ridx + size > buffer_len &&
675 h->available - RINGBUF_SUB(h->ridx, h->data) >= size)
677 /* the data wraps around the end of the buffer :
678 use the guard buffer to provide the requested amount of data. */
679 size_t copy_n = MIN(h->ridx + size - buffer_len, GUARD_SIZE);
680 rb->memcpy(guard_buffer, (unsigned char *)buffer, copy_n);
681 ret = buffer_len - h->ridx + copy_n;
682 DEBUGF("used the guard buffer to complete\n");
684 else
686 ret = MIN(h->available - RINGBUF_SUB(h->ridx, h->data),
687 buffer_len - h->ridx);
690 *data = (unsigned char *)&buffer[h->ridx];
692 /* DEBUGF("bufgetdata(%d): h->ridx=%ld, ret=%ld\n", handle_id,
693 (long)h->ridx, ret); */
694 return ret;
699 UTILITY FUNCTIONS
700 =================
703 bool test_ll(void)
705 struct memory_handle *m1, *m2, *m3, *m4;
707 if (cur_handle != NULL || first_handle != NULL)
708 return false;
710 m1 = add_handle(NULL);
712 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
713 return false;
715 m2 = add_handle(NULL);
717 if (cur_handle != m2 || first_handle != m1 || m1->next != m2 || m2->next != NULL)
718 return false;
720 m3 = add_handle(NULL);
722 if (cur_handle != m3 || first_handle != m1 || m2->next != m3 || m3->next != NULL)
723 return false;
725 rm_handle(m2);
727 if (cur_handle != m3 || first_handle != m1 || m1->next != m3)
728 return false;
730 rm_handle(m3);
732 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
733 return false;
735 m4 = add_handle(NULL);
737 if (cur_handle != m4 || first_handle != m1 || m1->next != m4 || m4->next != NULL)
738 return false;
740 rm_handle(m1);
742 if (cur_handle != m4 || first_handle != m4)
743 return false;
745 rm_handle(m4);
747 if (cur_handle != NULL || first_handle != NULL)
748 return false;
750 m1 = add_handle(NULL);
751 m2 = add_handle(NULL);
752 m3 = add_handle(NULL);
753 m4 = add_handle(NULL);
755 if (cur_handle != m4 || first_handle != m1)
756 return false;
758 size_t delta = 1024*100;
759 m2 = move_handle(m2, &delta, 0);
761 if (cur_handle != m4 || first_handle != m1 || m1->next != m2 || m2->next != m3)
762 return false;
764 delta = 1024*100*3;
765 m1 = move_handle(m1, &delta, 0);
767 if (cur_handle != m4 || first_handle != m1 || m1->next != m2)
768 return false;
770 rm_handle(m1);
771 rm_handle(m2);
772 rm_handle(m3);
773 rm_handle(m4);
775 if (cur_handle != NULL || first_handle != NULL)
776 return false;
778 return true;
781 /* display a nice graphical view of the ringbuffer. */
782 static void graph_view(int width)
784 #ifndef ROCKBOX_HAS_LOGF
785 int i, r_pos, w_pos;
786 r_pos = buf_ridx * width / buffer_len;
787 w_pos = buf_widx * width / buffer_len;
789 DEBUGF("|");
790 for (i=0; i <= width; i++)
792 if (i != r_pos && i != w_pos)
794 if (buf_ridx <= buf_widx)
796 if (i > r_pos && i < w_pos) {
797 DEBUGF(">");
798 } else {
799 DEBUGF("-");
802 else
804 if (i > r_pos || i < w_pos) {
805 DEBUGF(">");
806 } else {
807 DEBUGF("-");
811 else
813 if (i == r_pos && i == w_pos)
815 if (buf_ridx <= buf_widx) {
816 DEBUGF("RW");
817 } else {
818 DEBUGF("WR");
820 } else if (i == r_pos) {
821 DEBUGF("R");
822 } else if (i == w_pos) {
823 DEBUGF("W");
827 DEBUGF("|");
828 DEBUGF("\n");
829 #else
830 (void)width;
831 #endif
834 void print_progress(size_t amount, int file, int numfiles)
836 char buf[32];
837 rb->snprintf(buf, sizeof(buf), "file %d of %d", file, numfiles);
838 rb->lcd_puts(0, 0, buf);
839 rb->snprintf(buf, sizeof(buf), "read: %ld", amount);
840 rb->lcd_puts(0, 1, buf);
843 void print_metadata(int handle_id)
845 ssize_t ret;
846 char *artist, *title, *newline;
847 char art[50], ttl[50];
848 int artist_len, title_len;
849 unsigned char *buf;
851 ret = bufgetdata(handle_id, 0, &buf);
853 artist = buf;
854 newline = rb->strchr(artist, '\n');
855 artist_len = newline - artist;
856 rb->strncpy(art, artist, artist_len);
857 art[artist_len] = 0;
859 title = newline + 1;
860 newline = rb->strchr(title, '\n');
861 title_len = newline - title;
862 rb->strncpy(ttl, title, title_len);
863 ttl[title_len] = 0;
865 rb->lcd_puts(0, 3, art);
866 rb->lcd_puts(0, 4, ttl);
869 bool buffer_init(void)
871 buffer = rb->plugin_get_audio_buffer(&buffer_len);
872 if (!buffer)
874 DEBUGF("couldn't allocate buffer\n");
875 return false;
877 buffer_len -= GUARD_SIZE;
878 guard_buffer = buffer + buffer_len;
880 buf_widx = 0;
881 buf_ridx = 0;
883 first_handle = NULL;
884 num_handles = 0;
886 conf_filechunk = AUDIO_DEFAULT_FILECHUNK;
888 return true;
891 bool disk_is_spinning(void)
893 return true;
898 THREADING CODE
899 ==============
902 static long codec_stack[4*DEFAULT_STACK_SIZE/sizeof(long)];
903 static struct thread_entry* codecthread_id;
905 static long bufopen_stack[2*DEFAULT_STACK_SIZE/sizeof(long)];
906 static struct thread_entry* bufopenthread_id;
908 bool done_playing = false;
910 #define MAX_HANDLES 16
912 int handles[MAX_HANDLES];
913 int meta_handles[MAX_HANDLES];
915 void codec_thread(void)
917 int idx = 0;
918 int fd = -1; /* used to write the files out as they are read */
919 unsigned char *data;
920 char outfile[MAX_PATH];
921 long read, total = 0;
923 while (1)
925 if (!done_playing)
927 if (handles[idx] > 0) {
929 /* create the output file */
930 if (fd < 0) {
931 rb->snprintf(outfile, MAX_PATH, "/file%d.mp3", idx);
932 fd = rb->open(outfile, O_CREAT|O_TRUNC|O_WRONLY);
933 if (fd < 0) {
934 DEBUGF("couldn't create file\n");
935 rb->splash(HZ, "couldn't create file");
939 /* read as much data as possible */
940 do {
941 read = bufgetdata(handles[idx], GUARD_SIZE, &data);
942 if (read >= 0) {
943 read = MIN(read, GUARD_SIZE);
944 rb->write(fd, data, read);
945 total += read;
946 bufadvance(handles[idx], read);
947 rb->lcd_clear_display();
948 print_progress(total, idx+1, num_files);
949 print_metadata(meta_handles[idx]);
950 rb->lcd_update();
952 rb->sleep(HZ/100);
953 } while (read > 0);
955 if (read >= 0 && total >= 0) {
956 /* some data was read */
957 DEBUGF("read %ld bytes from handle %d\n", total,
958 handles[idx]);
961 /* check the return value to determine what exactly happened */
962 if (read == -2) {
963 DEBUGF("data for handle %d isn't ready\n", handles[idx]);
964 } else if (read == -1) {
965 DEBUGF("couldn't find handle %d\n", handles[idx]);
966 } else if (read == 0) {
967 DEBUGF("finished reading handle %d\n", handles[idx]);
968 bufclose(handles[idx]);
969 bufclose(meta_handles[idx]);
970 rb->close(fd);
971 fd = -1;
972 total = 0;
974 /* move on to the next file and check if we've finished */
975 idx++;
976 if (idx >= num_files) {
977 done_playing = true;
978 break;
984 rb->sleep(HZ/10);
987 DEBUGF("removing the codec thread\n");
988 rb->remove_thread(NULL);
991 void bufopen_thread(void)
993 int idx = 0, ret;
994 char buf[MAX_PATH];
995 while (idx < num_files)
997 /* open the metadata file */
998 rb->snprintf(buf, MAX_PATH, "/meta%s.txt", files[idx]);
999 meta_handles[idx] = bufopen(buf, 0, TYPE_ID3);
1000 if (meta_handles[idx] > 0)
1002 /* open the audio file */
1003 ret = bufopen(files[idx], 0, TYPE_AUDIO);
1004 if (ret > 0) {
1005 handles[idx++] = ret;
1006 } else {
1007 /* couldn't open the audio file, close the metadata handle */
1008 bufclose(meta_handles[idx]);
1011 rb->sleep(HZ*2);
1014 DEBUGF("bufopen thread finished\n");
1015 rb->remove_thread(NULL);
1019 /* this is the plugin entry point */
1020 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1022 (void)parameter;
1023 rb = api;
1025 buffer_init();
1027 if (!test_ll())
1029 DEBUGF("linked list test failed\n");
1030 rb->splash(HZ, "linked list test failed");
1031 return PLUGIN_ERROR;
1034 codecthread_id = rb->create_thread(codec_thread,
1035 codec_stack,
1036 sizeof(codec_stack),
1037 "codec"
1038 IF_PRIO(,PRIORITY_PLAYBACK)
1039 IF_COP(, CPU, false));
1041 bufopenthread_id = rb->create_thread(bufopen_thread,
1042 bufopen_stack,
1043 sizeof(bufopen_stack),
1044 "bufopen"
1045 IF_PRIO(,PRIORITY_BACKGROUND)
1046 IF_COP(, CPU, false));
1048 if (!codecthread_id)
1050 rb->splash(HZ, "failed to create codec thread");
1051 return PLUGIN_ERROR;
1053 else if (!bufopenthread_id)
1055 rb->splash(HZ, "failed to create bufopen thread");
1056 return PLUGIN_ERROR;
1058 else
1061 while (!done_playing)
1063 if (data_rem() > 0 && wasted_space() > buffer_len/5) {
1064 DEBUGF("there is %ld bytes of wasted space\n", wasted_space());
1066 /* free buffer from outdated audio data */
1067 struct memory_handle *m = first_handle;
1068 while (m) {
1069 if (m->type == TYPE_AUDIO)
1070 free_buffer(m->id);
1071 m = m->next;
1074 /* free buffer by moving metadata */
1075 m = first_handle;
1076 while (m) {
1077 if (m->type != TYPE_AUDIO)
1078 free_buffer(m->id);
1079 m = m->next;
1082 graph_view(100);
1085 if (data_rem() > 0 && BUF_USED < 3*buffer_len/4 &&
1086 disk_is_spinning())
1088 DEBUGF("%ld bytes left to buffer and the buffer is low\n",
1089 data_rem());
1090 fill_buffer();
1091 } else {
1092 rb->sleep(HZ/2);
1095 DEBUGF("done playing\n");
1096 rb->yield();
1099 rb->backlight_on();
1100 DEBUGF("end of plugin\n");
1101 return PLUGIN_OK;