bufread, bufgetdata: Better check for when data isn't ready.
[Rockbox-MoB.git] / testplugin.c
blob41a248ab7b4722f92e2acb510ab13fedf8d13453
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 can_add_handle : Indicate whether it's safe to add a handle.
303 data_rem : Total amount of data needing to be buffered
304 wasted_space : Total amount of space available for freeing
306 These functions are used by the buffering thread to manage buffer space.
309 /* Buffer data for the given handle. Return the amount of data buffered
310 or -1 if the handle wasn't found */
311 static ssize_t buffer_handle(int handle_id)
313 DEBUGF("buffer_handle(%d)\n", handle_id);
314 struct memory_handle *h = find_handle(handle_id);
315 if (!h)
316 return -1;
318 if (h->filerem == 0) {
319 /* nothing left to buffer */
320 return 0;
323 if (h->fd < 0) /* file closed, reopen */
325 if (*h->path)
326 h->fd = rb->open(h->path, O_RDONLY);
327 else
328 return -1;
330 if (h->fd < 0)
331 return -1;
333 if (h->offset)
334 rb->lseek(h->fd, h->offset, SEEK_SET);
337 ssize_t ret = 0;
338 while (h->filerem > 0)
340 /* max amount to copy */
341 size_t copy_n = MIN( MIN(h->filerem, conf_filechunk),
342 buffer_len - h->widx);
344 /* stop copying if it would overwrite the reading position
345 or the next handle */
346 if (RINGBUF_ADD_CROSS(h->widx, copy_n, buf_ridx) >= 0 || (h->next &&
347 RINGBUF_ADD_CROSS(h->widx, copy_n, (unsigned)
348 ((void *)h->next - (void *)buffer)) > 0))
349 break;
351 /* rc is the actual amount read */
352 int rc = rb->read(h->fd, &buffer[h->widx], copy_n);
354 if (rc < 0)
356 DEBUGF("File ended %ld bytes early\n", (long)h->filerem);
357 h->filesize -= h->filerem;
358 h->filerem = 0;
359 break;
362 /* Advance buffer */
363 h->widx = RINGBUF_ADD(h->widx, rc);
364 if (h == cur_handle)
365 buf_widx = h->widx;
366 h->available += rc;
367 ret += rc;
368 h->filerem -= rc;
371 if (h->filerem == 0) {
372 /* finished buffering the file */
373 rb->close(h->fd);
374 h->fd = -1;
377 DEBUGF("buffered %ld bytes (%ld of %ld available, rem: %ld, off: %ld)\n",
378 ret, h->available, h->filesize, h->filerem, h->offset);
380 graph_view(100);
382 return ret;
385 /* Free buffer space by moving the handle struct right before the useful
386 part of its data buffer or by moving all the data. */
387 static void free_buffer(int handle_id)
389 struct memory_handle *h = find_handle(handle_id);
390 if (!h)
391 return;
393 size_t delta;
394 /* The value of delta might change for alignment reasons */
396 if (h->next && (h->type == TYPE_ID3 || h->type == TYPE_CUESHEET ||
397 h->type == TYPE_IMAGE) && h->filerem == 0 )
399 /* metadata handle: we can move all of it */
400 delta = RINGBUF_SUB( (unsigned)((void *)h->next - (void *)buffer),
401 h->data) - h->available;
402 h = move_handle(h, &delta, h->available);
403 if (!h) return;
404 h->data = RINGBUF_ADD(h->data, delta);
405 h->ridx = RINGBUF_ADD(h->ridx, delta);
406 h->widx = RINGBUF_ADD(h->widx, delta);
407 DEBUGF("free_buffer(%d): metadata, moved by %ld bytes\n",
408 handle_id, delta);
410 else
412 /* only move the handle struct */
413 delta = RINGBUF_SUB(h->ridx, h->data);
414 h = move_handle(h, &delta, 0);
415 if (!h) return;
416 h->data = RINGBUF_ADD(h->data, delta);
417 h->available -= delta;
418 h->offset += delta;
419 DEBUGF("free_buffer(%d): audio, %ld bytes freed\n", handle_id, delta);
423 /* Fill the buffer by buffering as much data as possible for handles that still
424 have data left to buffer */
425 static void fill_buffer(void)
427 DEBUGF("fill buffer()\n");
428 struct memory_handle *m = first_handle;
429 while (m) {
430 if (m->filerem > 0) {
431 buffer_handle(m->id);
433 m = m->next;
437 /* Check whether it's safe to add a new handle and reserve space to let the
438 current one finish buffering its data. Used by bufopen and bufgetdata as
439 a preliminary check before even trying to physically add the handle.
440 Returns true if it's ok to add a new handle, false if not.
442 static bool can_add_handle(void)
444 if (cur_handle && cur_handle->filerem > 0) {
445 /* the current handle hasn't finished buffering. We can only add
446 a new one if there is already enough free space to finish
447 the buffering. */
448 if (cur_handle->filerem < (buffer_len - BUF_USED)) {
449 /* Before adding the new handle we reserve some space for the
450 current one to finish buffering its data. */
451 buf_widx = RINGBUF_ADD(buf_widx, cur_handle->filerem);
452 } else {
453 return false;
457 return true;
460 /* Return the total amount of data left to be buffered for all the handles */
461 static size_t data_rem(void)
463 size_t ret = 0;
465 struct memory_handle *m = first_handle;
466 while (m) {
467 ret += m->filerem;
468 m = m->next;
471 return ret;
474 /* Return the amount of data we have but don't need anymore. This data can be
475 safely erased to reclaim buffer space. */
476 static size_t wasted_space(void)
478 size_t ret = 0;
480 struct memory_handle *m = first_handle;
481 while (m) {
482 ret += RINGBUF_SUB(m->ridx, m->data);
483 m = m->next;
486 return ret;
491 BUFFERING API FUNCTIONS
492 =======================
494 bufopen : Request the opening of a new handle for a file
495 bufalloc : Open a new handle for data other than a file.
496 bufclose : Close an open handle
497 bufseek : Set the read pointer in a handle
498 bufadvance : Move the read pointer in a handle
499 bufread : Copy data from a handle into a given buffer
500 bufgetdata : Give a pointer to the handle's data
502 These functions are exported, to allow interaction with the buffer.
503 They take care of the content of the structs, and rely on the linked list
504 management functions for all the actual handle management work.
508 /* Request a file be buffered
509 filename: name of the file to open
510 offset:starting offset to buffer from the file
511 return value: <0 if the file cannot be opened, or one file already
512 queued to be opened, otherwise the handle for the file in the buffer
514 int bufopen(char *file, size_t offset, enum data_type type)
516 if (!can_add_handle())
517 return -2;
519 int fd = rb->open(file, O_RDONLY);
520 if (fd < 0)
521 return -1;
523 size_t size = rb->filesize(fd) - offset;
525 if (type != TYPE_AUDIO &&
526 size + sizeof(struct memory_handle) > buffer_len - buf_widx)
528 /* for types other than audio, the data can't wrap */
529 return -2;
532 DEBUGF("bufopen: %s (offset: %ld) (%ld bytes needed)...\n",
533 file, offset, size);
535 struct memory_handle *h = add_handle(&size);
536 if (!h)
538 DEBUGF("failed to add handle\n");
539 rb->close(fd);
540 return -2;
543 if (offset) rb->lseek(fd, offset, SEEK_SET);
544 rb->strncpy(h->path, file, MAX_PATH);
545 h->fd = fd;
546 h->filesize = rb->filesize(fd);
547 h->filerem = h->filesize - offset;
548 h->offset = offset;
549 h->ridx = buf_widx;
550 h->widx = buf_widx;
551 h->data = buf_widx;
552 h->available = 0;
553 h->type = type;
555 DEBUGF("allocated %ld bytes. ID: %d\n", size, h->id);
556 return h->id;
559 /* Open a new handle from data that isn't in a file.
560 src is the source buffer from which to copy data. It can be NULL to simply
561 reserve buffer space.
562 size is the requested size. The call will only be successful if the
563 requested amount of data can entirely fit in the buffer without wrapping.
564 Return value is the handle id for success or <0 for failure.
566 int bufalloc(void *src, size_t size, enum data_type type)
568 if (!can_add_handle())
569 return -2;
571 if (size + sizeof(struct memory_handle) > buffer_len - buf_widx)
572 /* The data would need to wrap. */
573 return -2;
575 size_t allocsize = size;
576 struct memory_handle *h = add_handle(&allocsize);
578 if (!h || allocsize != size)
579 return -2;
581 if (src)
582 rb->memcpy(&buffer[buf_widx], src, size);
584 h->fd = -1;
585 *h->path = 0;
586 h->filesize = size;
587 h->filerem = 0;
588 h->offset = 0;
589 h->ridx = buf_widx;
590 h->widx = buf_widx;
591 h->data = buf_widx;
592 h->available = size;
593 h->type = type;
595 buf_widx = RINGBUF_ADD(buf_widx, size);
597 return h->id;
600 /* Close the handle. Return 0 for success and < 0 for failure */
601 int bufclose(int handle_id)
603 DEBUGF("bufclose(%d)\n", handle_id);
604 struct memory_handle *h = find_handle(handle_id);
605 if (!h)
606 return -1;
608 rm_handle(h);
609 return 0;
612 /* Set reading index in handle (relatively to the start of the file).
613 Access before the available data will trigger a rebuffer.
614 TODO: Test this
615 TODO: Maybe force an immediate rebuffer by calling buffer_handle() ?
616 Return 0 for success and < 0 for failure:
617 -1 if the handle wasn't found
618 -2 if there is no data available at the new position
619 (the reading index is still moved)
620 -3 if the new requested position was beyond the end of the file
622 int bufseek(int handle_id, size_t newpos)
624 int ret = 0;
625 struct memory_handle *h = find_handle(handle_id);
626 if (!h)
627 return -1;
629 if (newpos > h->filesize) {
630 /* access beyond the end of the file */
631 return -3;
634 else if (newpos < h->offset) {
635 /* access before what we currently have. A rebuffer is needed. */
636 h->offset = newpos;
637 h->available = 0;
638 h->filerem = h->filesize - newpos;
639 /* having changed filerem should be enough to trigger the rebuffer. */
640 h->widx = h->data;
641 ret = -2;
644 else if (newpos > h->offset + h->available) {
645 /* data isn't available yet. */
646 ret = -2;
649 h->ridx = RINGBUF_ADD(h->data, newpos);
650 return ret;
653 /* Advance the reading index in a handle (relatively to its current position).
654 Return 0 for success and < 0 for failure
655 TODO: Add some rebuffering like in bufseek */
656 int bufadvance(int handle_id, off_t offset)
658 struct memory_handle *h = find_handle(handle_id);
659 if (!h)
660 return -1;
662 if (offset >= 0)
664 /* check for access beyond what's available */
665 if ((size_t)offset > (h->available - RINGBUF_SUB(h->ridx, h->data)))
666 return -2;
668 h->ridx = RINGBUF_ADD(h->ridx, offset);
670 else
672 /* check for access before what's available */
673 if ((size_t)(-offset) > RINGBUF_SUB(h->ridx, h->data))
674 return -2;
676 h->ridx = RINGBUF_SUB(h->ridx, (size_t)(-offset));
679 return 0;
682 /* Copy data from the given handle to the dest buffer.
683 Return the number of bytes copied or < 0 for failure. */
684 ssize_t bufread(int handle_id, size_t size, char *dest)
686 struct memory_handle *h = find_handle(handle_id);
687 size_t buffered_data;
688 if (!h)
689 return -1;
691 if (h->available < size && h->filerem > 0) /* Data isn't ready */
692 return -2;
694 if (h->available == 0 && h->filerem == 0) /* File is finished reading */
695 return 0;
697 buffered_data = MIN(size, h->available - RINGBUF_SUB(h->ridx, h->data));
699 if (h->ridx + buffered_data > buffer_len)
701 /* the data wraps around the end of the buffer */
702 size_t read = buffer_len - h->ridx;
703 rb->memcpy(dest, &buffer[h->ridx], read);
704 rb->memcpy(dest+read, buffer, buffered_data - read);
706 else rb->memcpy(dest, &buffer[h->ridx], buffered_data);
708 return buffered_data;
711 /* Update the "data" pointer to make the handle's data available to the caller.
712 Return the length of the available linear data or < 0 for failure.
713 size is the amount of linear data requested. it can be 0 to get as
714 much as possible.
715 The guard buffer may be used to provide the requested size */
716 ssize_t bufgetdata(int handle_id, size_t size, unsigned char **data)
718 struct memory_handle *h = find_handle(handle_id);
719 if (!h)
720 return -1;
722 if (h->available < size && h->filerem > 0) /* Data isn't ready */
723 return -2;
725 if (h->available == 0 && h->filerem == 0) /* File is finished reading */
726 return 0;
728 ssize_t ret;
730 if (h->ridx + size > buffer_len &&
731 h->available - RINGBUF_SUB(h->ridx, h->data) >= size)
733 /* the data wraps around the end of the buffer :
734 use the guard buffer to provide the requested amount of data. */
735 size_t copy_n = MIN(h->ridx + size - buffer_len, GUARD_SIZE);
736 rb->memcpy(guard_buffer, (unsigned char *)buffer, copy_n);
737 ret = buffer_len - h->ridx + copy_n;
738 DEBUGF("used the guard buffer to complete\n");
740 else
742 ret = MIN(h->available - RINGBUF_SUB(h->ridx, h->data),
743 buffer_len - h->ridx);
746 *data = (unsigned char *)&buffer[h->ridx];
748 /* DEBUGF("bufgetdata(%d): h->ridx=%ld, ret=%ld\n", handle_id,
749 (long)h->ridx, ret); */
750 return ret;
755 UTILITY FUNCTIONS
756 =================
759 bool test_ll(void)
761 struct memory_handle *m1, *m2, *m3, *m4;
763 if (cur_handle != NULL || first_handle != NULL)
764 return false;
766 m1 = add_handle(NULL);
768 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
769 return false;
771 m2 = add_handle(NULL);
773 if (cur_handle != m2 || first_handle != m1 || m1->next != m2 || m2->next != NULL)
774 return false;
776 m3 = add_handle(NULL);
778 if (cur_handle != m3 || first_handle != m1 || m2->next != m3 || m3->next != NULL)
779 return false;
781 rm_handle(m2);
783 if (cur_handle != m3 || first_handle != m1 || m1->next != m3)
784 return false;
786 rm_handle(m3);
788 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
789 return false;
791 m4 = add_handle(NULL);
793 if (cur_handle != m4 || first_handle != m1 || m1->next != m4 || m4->next != NULL)
794 return false;
796 rm_handle(m1);
798 if (cur_handle != m4 || first_handle != m4)
799 return false;
801 rm_handle(m4);
803 if (cur_handle != NULL || first_handle != NULL)
804 return false;
806 m1 = add_handle(NULL);
807 m2 = add_handle(NULL);
808 m3 = add_handle(NULL);
809 m4 = add_handle(NULL);
811 if (cur_handle != m4 || first_handle != m1)
812 return false;
814 size_t delta = 1024*100;
815 m2 = move_handle(m2, &delta, 0);
817 if (cur_handle != m4 || first_handle != m1 || m1->next != m2 || m2->next != m3)
818 return false;
820 delta = 1024*100*3;
821 m1 = move_handle(m1, &delta, 0);
823 if (cur_handle != m4 || first_handle != m1 || m1->next != m2)
824 return false;
826 rm_handle(m1);
827 rm_handle(m2);
828 rm_handle(m3);
829 rm_handle(m4);
831 if (cur_handle != NULL || first_handle != NULL)
832 return false;
834 return true;
837 /* display a nice graphical view of the ringbuffer. */
838 static void graph_view(int width)
840 #ifndef ROCKBOX_HAS_LOGF
841 int i, r_pos, w_pos;
842 r_pos = buf_ridx * width / buffer_len;
843 w_pos = buf_widx * width / buffer_len;
845 DEBUGF("|");
846 for (i=0; i <= width; i++)
848 if (i != r_pos && i != w_pos)
850 if (buf_ridx <= buf_widx)
852 if (i > r_pos && i < w_pos) {
853 DEBUGF(">");
854 } else {
855 DEBUGF("-");
858 else
860 if (i > r_pos || i < w_pos) {
861 DEBUGF(">");
862 } else {
863 DEBUGF("-");
867 else
869 if (i == r_pos && i == w_pos)
871 if (buf_ridx <= buf_widx) {
872 DEBUGF("RW");
873 } else {
874 DEBUGF("WR");
876 } else if (i == r_pos) {
877 DEBUGF("R");
878 } else if (i == w_pos) {
879 DEBUGF("W");
883 DEBUGF("|");
884 DEBUGF("\n");
885 #else
886 (void)width;
887 #endif
890 void print_progress(size_t amount, int file, int numfiles)
892 char buf[32];
893 rb->snprintf(buf, sizeof(buf), "file %d of %d", file, numfiles);
894 rb->lcd_puts(0, 0, buf);
895 rb->snprintf(buf, sizeof(buf), "read: %ld", amount);
896 rb->lcd_puts(0, 1, buf);
899 void print_metadata(int handle_id)
901 ssize_t ret;
902 char *artist, *title;
903 unsigned char *buf;
905 ret = bufgetdata(handle_id, 0, &buf);
907 if (ret < 0)
908 return;
910 artist = buf;
911 title = artist + rb->strlen(artist) + 1;
913 rb->lcd_puts(0, 3, artist);
914 rb->lcd_puts(0, 4, title);
917 int read_metadata(char *filename, char *buf, int bufsize)
919 int ret;
920 char *newline;
921 int fd = rb->open(filename, O_RDONLY);
922 if (fd < 0)
923 return -1;
925 ret = rb->read(fd, buf, bufsize);
926 newline = rb->strchr(buf, '\n');
927 *newline = '\0';
928 newline = rb->strchr(newline + 1, '\n');
929 *newline = '\0';
931 rb->close(fd);
933 return ret;
936 bool buffer_init(void)
938 buffer = rb->plugin_get_audio_buffer(&buffer_len);
939 if (!buffer)
941 DEBUGF("couldn't allocate buffer\n");
942 return false;
944 buffer_len -= GUARD_SIZE;
945 guard_buffer = buffer + buffer_len;
947 buf_widx = 0;
948 buf_ridx = 0;
950 first_handle = NULL;
951 num_handles = 0;
953 conf_filechunk = AUDIO_DEFAULT_FILECHUNK;
955 return true;
958 bool disk_is_spinning(void)
960 return true;
965 THREADING CODE
966 ==============
969 static long codec_stack[4*DEFAULT_STACK_SIZE/sizeof(long)];
970 static struct thread_entry* codecthread_id;
972 static long bufopen_stack[2*DEFAULT_STACK_SIZE/sizeof(long)];
973 static struct thread_entry* bufopenthread_id;
975 bool done_playing = false;
977 #define MAX_HANDLES 16
979 int handles[MAX_HANDLES];
980 int meta_handles[MAX_HANDLES];
982 void codec_thread(void)
984 int idx = 0;
985 int fd = -1; /* used to write the files out as they are read */
986 unsigned char *data;
987 char outfile[MAX_PATH];
988 long read, total = 0;
990 while (1)
992 if (!done_playing)
994 if (handles[idx] > 0) {
996 /* create the output file */
997 if (fd < 0) {
998 rb->snprintf(outfile, MAX_PATH, "/file%d.mp3", idx);
999 fd = rb->open(outfile, O_CREAT|O_TRUNC|O_WRONLY);
1000 if (fd < 0) {
1001 DEBUGF("couldn't create file\n");
1002 rb->splash(HZ, "couldn't create file");
1006 /* read as much data as possible */
1007 do {
1008 read = bufgetdata(handles[idx], GUARD_SIZE, &data);
1009 if (read >= 0) {
1010 read = MIN(read, GUARD_SIZE);
1011 rb->write(fd, data, read);
1012 total += read;
1013 bufadvance(handles[idx], read);
1014 rb->lcd_clear_display();
1015 print_progress(total, idx+1, num_files);
1016 print_metadata(meta_handles[idx]);
1017 rb->lcd_update();
1019 rb->sleep(HZ/100);
1020 } while (read > 0);
1022 if (read >= 0 && total >= 0) {
1023 /* some data was read */
1024 DEBUGF("read %ld bytes from handle %d\n", total,
1025 handles[idx]);
1028 /* check the return value to determine what exactly happened */
1029 if (read == -2) {
1030 DEBUGF("data for handle %d isn't ready\n", handles[idx]);
1031 } else if (read == -1) {
1032 DEBUGF("couldn't find handle %d\n", handles[idx]);
1033 } else if (read == 0) {
1034 DEBUGF("finished reading handle %d\n", handles[idx]);
1035 bufclose(handles[idx]);
1036 bufclose(meta_handles[idx]);
1037 rb->close(fd);
1038 fd = -1;
1039 total = 0;
1041 /* move on to the next file and check if we've finished */
1042 idx++;
1043 if (idx >= num_files) {
1044 done_playing = true;
1045 break;
1051 rb->sleep(HZ/10);
1054 DEBUGF("removing the codec thread\n");
1055 rb->remove_thread(NULL);
1058 void bufopen_thread(void)
1060 int idx = 0, ret;
1061 char filename[MAX_PATH];
1062 char buf[MAX_PATH];
1063 int meta_len;
1065 while (idx < num_files)
1067 if (meta_handles[idx] <= 0)
1069 /* read the metadata file */
1070 rb->snprintf(filename, MAX_PATH, "/meta%s.txt", files[idx]);
1071 meta_len = read_metadata(filename, buf, MAX_PATH);
1073 /* copy the metadata to a new handle */
1074 if (meta_len > 0)
1075 meta_handles[idx] = bufalloc(buf, meta_len, TYPE_ID3);
1078 if (meta_handles[idx] > 0)
1080 /* open the audio file */
1081 ret = bufopen(files[idx], 0, TYPE_AUDIO);
1082 if (ret > 0) {
1083 handles[idx++] = ret;
1084 } else {
1085 /* couldn't open the audio file, close the metadata handle */
1086 bufclose(meta_handles[idx]);
1089 rb->sleep(HZ*2);
1092 DEBUGF("bufopen thread finished\n");
1093 rb->remove_thread(NULL);
1097 /* this is the plugin entry point */
1098 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1100 (void)parameter;
1101 rb = api;
1103 buffer_init();
1105 if (!test_ll())
1107 DEBUGF("linked list test failed\n");
1108 rb->splash(HZ, "linked list test failed");
1109 return PLUGIN_ERROR;
1112 codecthread_id = rb->create_thread(codec_thread,
1113 codec_stack,
1114 sizeof(codec_stack),
1115 "codec"
1116 IF_PRIO(,PRIORITY_PLAYBACK)
1117 IF_COP(, CPU, false));
1119 bufopenthread_id = rb->create_thread(bufopen_thread,
1120 bufopen_stack,
1121 sizeof(bufopen_stack),
1122 "bufopen"
1123 IF_PRIO(,PRIORITY_BACKGROUND)
1124 IF_COP(, CPU, false));
1126 if (!codecthread_id)
1128 rb->splash(HZ, "failed to create codec thread");
1129 return PLUGIN_ERROR;
1131 else if (!bufopenthread_id)
1133 rb->splash(HZ, "failed to create bufopen thread");
1134 return PLUGIN_ERROR;
1136 else
1139 while (!done_playing)
1141 if (data_rem() > 0 && wasted_space() > buffer_len/5) {
1142 DEBUGF("there is %ld bytes of wasted space\n", wasted_space());
1144 /* free buffer from outdated audio data */
1145 struct memory_handle *m = first_handle;
1146 while (m) {
1147 if (m->type == TYPE_AUDIO)
1148 free_buffer(m->id);
1149 m = m->next;
1152 /* free buffer by moving metadata */
1153 m = first_handle;
1154 while (m) {
1155 if (m->type != TYPE_AUDIO)
1156 free_buffer(m->id);
1157 m = m->next;
1160 graph_view(100);
1163 if (data_rem() > 0 && BUF_USED < 3*buffer_len/4 &&
1164 disk_is_spinning())
1166 DEBUGF("%ld bytes left to buffer and the buffer is low\n",
1167 data_rem());
1168 fill_buffer();
1169 } else {
1170 rb->sleep(HZ/2);
1173 DEBUGF("done playing\n");
1174 rb->yield();
1177 rb->backlight_on();
1178 DEBUGF("end of plugin\n");
1179 return PLUGIN_OK;