bufopen: better description of the offset parameter.
[Rockbox-MoB.git] / testplugin.c
blob3e2df0f006a34d22b6c75d647654f021ee09c267
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);
408 /* when moving a struct mp3entry we need to readjust its pointers. */
409 if (h->type == TYPE_ID3 && h->filesize == sizeof(struct mp3entry)) {
410 rb->adjust_mp3entry((struct mp3entry *)&buffer[h->data],
411 (void *)&buffer[h->data],
412 (void *)&buffer[RINGBUF_SUB(h->data, delta)]);
415 DEBUGF("free_buffer(%d): metadata, moved by %ld bytes\n",
416 handle_id, delta);
418 else
420 /* only move the handle struct */
421 delta = RINGBUF_SUB(h->ridx, h->data);
422 h = move_handle(h, &delta, 0);
423 if (!h) return;
424 h->data = RINGBUF_ADD(h->data, delta);
425 h->available -= delta;
426 h->offset += delta;
427 DEBUGF("free_buffer(%d): audio, %ld bytes freed\n", handle_id, delta);
431 /* Fill the buffer by buffering as much data as possible for handles that still
432 have data left to buffer */
433 static void fill_buffer(void)
435 DEBUGF("fill buffer()\n");
436 struct memory_handle *m = first_handle;
437 while (m) {
438 if (m->filerem > 0) {
439 buffer_handle(m->id);
441 m = m->next;
445 /* Check whether it's safe to add a new handle and reserve space to let the
446 current one finish buffering its data. Used by bufopen and bufgetdata as
447 a preliminary check before even trying to physically add the handle.
448 Returns true if it's ok to add a new handle, false if not.
450 static bool can_add_handle(void)
452 if (cur_handle && cur_handle->filerem > 0) {
453 /* the current handle hasn't finished buffering. We can only add
454 a new one if there is already enough free space to finish
455 the buffering. */
456 if (cur_handle->filerem < (buffer_len - BUF_USED)) {
457 /* Before adding the new handle we reserve some space for the
458 current one to finish buffering its data. */
459 buf_widx = RINGBUF_ADD(buf_widx, cur_handle->filerem);
460 } else {
461 return false;
465 return true;
468 /* Return the total amount of data left to be buffered for all the handles */
469 static size_t data_rem(void)
471 size_t ret = 0;
473 struct memory_handle *m = first_handle;
474 while (m) {
475 ret += m->filerem;
476 m = m->next;
479 return ret;
482 /* Return the amount of data we have but don't need anymore. This data can be
483 safely erased to reclaim buffer space. */
484 static size_t wasted_space(void)
486 size_t ret = 0;
488 struct memory_handle *m = first_handle;
489 while (m) {
490 ret += RINGBUF_SUB(m->ridx, m->data);
491 m = m->next;
494 return ret;
499 BUFFERING API FUNCTIONS
500 =======================
502 bufopen : Request the opening of a new handle for a file
503 bufalloc : Open a new handle for data other than a file.
504 bufclose : Close an open handle
505 bufseek : Set the read pointer in a handle
506 bufadvance : Move the read pointer in a handle
507 bufread : Copy data from a handle into a given buffer
508 bufgetdata : Give a pointer to the handle's data
510 These functions are exported, to allow interaction with the buffer.
511 They take care of the content of the structs, and rely on the linked list
512 management functions for all the actual handle management work.
516 /* Request a file be buffered
517 filename: name of the file to open
518 offset: offset at which to start buffering the file, useful when the first
519 (offset-1) bytes of the file aren't needed.
520 return value: <0 if the file cannot be opened, or one file already
521 queued to be opened, otherwise the handle for the file in the buffer
523 int bufopen(char *file, size_t offset, enum data_type type)
525 if (!can_add_handle())
526 return -2;
528 int fd = rb->open(file, O_RDONLY);
529 if (fd < 0)
530 return -1;
532 size_t size = rb->filesize(fd) - offset;
534 if (type != TYPE_AUDIO &&
535 size + sizeof(struct memory_handle) > buffer_len - buf_widx)
537 /* for types other than audio, the data can't wrap */
538 return -2;
541 DEBUGF("bufopen: %s (offset: %ld) (%ld bytes needed)...\n",
542 file, offset, size);
544 struct memory_handle *h = add_handle(&size);
545 if (!h)
547 DEBUGF("failed to add handle\n");
548 rb->close(fd);
549 return -2;
552 if (offset) rb->lseek(fd, offset, SEEK_SET);
553 rb->strncpy(h->path, file, MAX_PATH);
554 h->fd = fd;
555 h->filesize = rb->filesize(fd);
556 h->filerem = h->filesize - offset;
557 h->offset = offset;
558 h->ridx = buf_widx;
559 h->widx = buf_widx;
560 h->data = buf_widx;
561 h->available = 0;
562 h->type = type;
564 DEBUGF("allocated %ld bytes. ID: %d\n", size, h->id);
565 return h->id;
568 /* Open a new handle from data that isn't in a file.
569 src is the source buffer from which to copy data. It can be NULL to simply
570 reserve buffer space.
571 size is the requested size. The call will only be successful if the
572 requested amount of data can entirely fit in the buffer without wrapping.
573 Return value is the handle id for success or <0 for failure.
575 int bufalloc(void *src, size_t size, enum data_type type)
577 if (!can_add_handle())
578 return -2;
580 if (size + sizeof(struct memory_handle) > buffer_len - buf_widx)
581 /* The data would need to wrap. */
582 return -2;
584 size_t allocsize = size;
585 struct memory_handle *h = add_handle(&allocsize);
587 if (!h || allocsize != size)
588 return -2;
590 if (src) {
591 if (type == TYPE_ID3 && size == sizeof(struct mp3entry)) {
592 /* specially take care of struct mp3entry */
593 rb->copy_mp3entry((struct mp3entry *)&buffer[buf_widx],
594 (struct mp3entry *)src);
595 } else {
596 rb->memcpy(&buffer[buf_widx], src, size);
600 h->fd = -1;
601 *h->path = 0;
602 h->filesize = size;
603 h->filerem = 0;
604 h->offset = 0;
605 h->ridx = buf_widx;
606 h->widx = buf_widx;
607 h->data = buf_widx;
608 h->available = size;
609 h->type = type;
611 buf_widx = RINGBUF_ADD(buf_widx, size);
613 return h->id;
616 /* Close the handle. Return 0 for success and < 0 for failure */
617 int bufclose(int handle_id)
619 DEBUGF("bufclose(%d)\n", handle_id);
620 struct memory_handle *h = find_handle(handle_id);
621 if (!h)
622 return -1;
624 rm_handle(h);
625 return 0;
628 /* Set reading index in handle (relatively to the start of the file).
629 Access before the available data will trigger a rebuffer.
630 TODO: Test this
631 TODO: Maybe force an immediate rebuffer by calling buffer_handle() ?
632 Return 0 for success and < 0 for failure:
633 -1 if the handle wasn't found
634 -2 if there is no data available at the new position
635 (the reading index is still moved)
636 -3 if the new requested position was beyond the end of the file
638 int bufseek(int handle_id, size_t newpos)
640 int ret = 0;
641 struct memory_handle *h = find_handle(handle_id);
642 if (!h)
643 return -1;
645 if (newpos > h->filesize) {
646 /* access beyond the end of the file */
647 return -3;
650 else if (newpos < h->offset) {
651 /* access before what we currently have. A rebuffer is needed. */
652 h->offset = newpos;
653 h->available = 0;
654 h->filerem = h->filesize - newpos;
655 /* having changed filerem should be enough to trigger the rebuffer. */
656 h->widx = h->data;
657 ret = -2;
660 else if (newpos > h->offset + h->available) {
661 /* data isn't available yet. */
662 ret = -2;
665 h->ridx = RINGBUF_ADD(h->data, newpos);
666 return ret;
669 /* Advance the reading index in a handle (relatively to its current position).
670 Return 0 for success and < 0 for failure
671 TODO: Add some rebuffering like in bufseek */
672 int bufadvance(int handle_id, off_t offset)
674 struct memory_handle *h = find_handle(handle_id);
675 if (!h)
676 return -1;
678 if (offset >= 0)
680 /* check for access beyond what's available */
681 if ((size_t)offset > (h->available - RINGBUF_SUB(h->ridx, h->data)))
682 return -2;
684 h->ridx = RINGBUF_ADD(h->ridx, offset);
686 else
688 /* check for access before what's available */
689 if ((size_t)(-offset) > RINGBUF_SUB(h->ridx, h->data))
690 return -2;
692 h->ridx = RINGBUF_SUB(h->ridx, (size_t)(-offset));
695 return 0;
698 /* Copy data from the given handle to the dest buffer.
699 Return the number of bytes copied or < 0 for failure. */
700 ssize_t bufread(int handle_id, size_t size, char *dest)
702 struct memory_handle *h = find_handle(handle_id);
703 size_t buffered_data;
704 if (!h)
705 return -1;
707 if (h->available < size && h->filerem > 0) /* Data isn't ready */
708 return -2;
710 if (h->available == 0 && h->filerem == 0) /* File is finished reading */
711 return 0;
713 buffered_data = MIN(size, h->available - RINGBUF_SUB(h->ridx, h->data));
715 if (h->ridx + buffered_data > buffer_len)
717 /* the data wraps around the end of the buffer */
718 size_t read = buffer_len - h->ridx;
719 rb->memcpy(dest, &buffer[h->ridx], read);
720 rb->memcpy(dest+read, buffer, buffered_data - read);
722 else rb->memcpy(dest, &buffer[h->ridx], buffered_data);
724 return buffered_data;
727 /* Update the "data" pointer to make the handle's data available to the caller.
728 Return the length of the available linear data or < 0 for failure.
729 size is the amount of linear data requested. it can be 0 to get as
730 much as possible.
731 The guard buffer may be used to provide the requested size */
732 ssize_t bufgetdata(int handle_id, size_t size, unsigned char **data)
734 struct memory_handle *h = find_handle(handle_id);
735 if (!h)
736 return -1;
738 if (h->available < size && h->filerem > 0) /* Data isn't ready */
739 return -2;
741 if (h->available == 0 && h->filerem == 0) /* File is finished reading */
742 return 0;
744 ssize_t ret;
746 if (h->ridx + size > buffer_len &&
747 h->available - RINGBUF_SUB(h->ridx, h->data) >= size)
749 /* the data wraps around the end of the buffer :
750 use the guard buffer to provide the requested amount of data. */
751 size_t copy_n = MIN(h->ridx + size - buffer_len, GUARD_SIZE);
752 rb->memcpy(guard_buffer, (unsigned char *)buffer, copy_n);
753 ret = buffer_len - h->ridx + copy_n;
754 DEBUGF("used the guard buffer to complete\n");
756 else
758 ret = MIN(h->available - RINGBUF_SUB(h->ridx, h->data),
759 buffer_len - h->ridx);
762 *data = (unsigned char *)&buffer[h->ridx];
764 /* DEBUGF("bufgetdata(%d): h->ridx=%ld, ret=%ld\n", handle_id,
765 (long)h->ridx, ret); */
766 return ret;
771 UTILITY FUNCTIONS
772 =================
775 bool test_ll(void)
777 struct memory_handle *m1, *m2, *m3, *m4;
779 if (cur_handle != NULL || first_handle != NULL)
780 return false;
782 m1 = add_handle(NULL);
784 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
785 return false;
787 m2 = add_handle(NULL);
789 if (cur_handle != m2 || first_handle != m1 || m1->next != m2 || m2->next != NULL)
790 return false;
792 m3 = add_handle(NULL);
794 if (cur_handle != m3 || first_handle != m1 || m2->next != m3 || m3->next != NULL)
795 return false;
797 rm_handle(m2);
799 if (cur_handle != m3 || first_handle != m1 || m1->next != m3)
800 return false;
802 rm_handle(m3);
804 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
805 return false;
807 m4 = add_handle(NULL);
809 if (cur_handle != m4 || first_handle != m1 || m1->next != m4 || m4->next != NULL)
810 return false;
812 rm_handle(m1);
814 if (cur_handle != m4 || first_handle != m4)
815 return false;
817 rm_handle(m4);
819 if (cur_handle != NULL || first_handle != NULL)
820 return false;
822 m1 = add_handle(NULL);
823 m2 = add_handle(NULL);
824 m3 = add_handle(NULL);
825 m4 = add_handle(NULL);
827 if (cur_handle != m4 || first_handle != m1)
828 return false;
830 size_t delta = 1024*100;
831 m2 = move_handle(m2, &delta, 0);
833 if (cur_handle != m4 || first_handle != m1 || m1->next != m2 || m2->next != m3)
834 return false;
836 delta = 1024*100*3;
837 m1 = move_handle(m1, &delta, 0);
839 if (cur_handle != m4 || first_handle != m1 || m1->next != m2)
840 return false;
842 rm_handle(m1);
843 rm_handle(m2);
844 rm_handle(m3);
845 rm_handle(m4);
847 if (cur_handle != NULL || first_handle != NULL)
848 return false;
850 return true;
853 /* display a nice graphical view of the ringbuffer. */
854 static void graph_view(int width)
856 #ifndef ROCKBOX_HAS_LOGF
857 int i, r_pos, w_pos;
858 r_pos = buf_ridx * width / buffer_len;
859 w_pos = buf_widx * width / buffer_len;
861 DEBUGF("|");
862 for (i=0; i <= width; i++)
864 if (i != r_pos && i != w_pos)
866 if (buf_ridx <= buf_widx)
868 if (i > r_pos && i < w_pos) {
869 DEBUGF(">");
870 } else {
871 DEBUGF("-");
874 else
876 if (i > r_pos || i < w_pos) {
877 DEBUGF(">");
878 } else {
879 DEBUGF("-");
883 else
885 if (i == r_pos && i == w_pos)
887 if (buf_ridx <= buf_widx) {
888 DEBUGF("RW");
889 } else {
890 DEBUGF("WR");
892 } else if (i == r_pos) {
893 DEBUGF("R");
894 } else if (i == w_pos) {
895 DEBUGF("W");
899 DEBUGF("|");
900 DEBUGF("\n");
901 #else
902 (void)width;
903 #endif
906 void print_progress(size_t amount, int file, int numfiles)
908 char buf[32];
909 rb->snprintf(buf, sizeof(buf), "file %d of %d", file, numfiles);
910 rb->lcd_puts(0, 0, buf);
911 rb->snprintf(buf, sizeof(buf), "read: %ld", amount);
912 rb->lcd_puts(0, 1, buf);
915 void print_metadata(int handle_id)
917 ssize_t ret;
918 struct mp3entry *id3;
920 ret = bufgetdata(handle_id, 0, (unsigned char **)&id3);
922 if (ret < 0)
923 return;
925 rb->lcd_puts(0, 3, id3->artist);
926 rb->lcd_puts(0, 4, id3->title);
929 bool buffer_init(void)
931 buffer = rb->plugin_get_audio_buffer(&buffer_len);
932 if (!buffer)
934 DEBUGF("couldn't allocate buffer\n");
935 return false;
937 buffer_len -= GUARD_SIZE;
938 guard_buffer = buffer + buffer_len;
940 buf_widx = 0;
941 buf_ridx = 0;
943 first_handle = NULL;
944 num_handles = 0;
946 conf_filechunk = AUDIO_DEFAULT_FILECHUNK;
948 return true;
951 bool disk_is_spinning(void)
953 return true;
958 THREADING CODE
959 ==============
962 static long codec_stack[4*DEFAULT_STACK_SIZE/sizeof(long)];
963 static struct thread_entry* codecthread_id;
965 static long bufopen_stack[4*DEFAULT_STACK_SIZE/sizeof(long)];
966 static struct thread_entry* bufopenthread_id;
968 bool done_playing = false;
970 #define MAX_HANDLES 16
972 int handles[MAX_HANDLES];
973 int meta_handles[MAX_HANDLES];
975 void codec_thread(void)
977 int idx = 0;
978 int fd = -1; /* used to write the files out as they are read */
979 unsigned char *data;
980 char outfile[MAX_PATH];
981 long read, total = 0;
983 while (1)
985 if (!done_playing)
987 if (handles[idx] > 0) {
989 /* create the output file */
990 if (fd < 0) {
991 rb->snprintf(outfile, MAX_PATH, "/file%d.mp3", idx);
992 fd = rb->open(outfile, O_CREAT|O_TRUNC|O_WRONLY);
993 if (fd < 0) {
994 DEBUGF("couldn't create file\n");
995 rb->splash(HZ, "couldn't create file");
999 /* read as much data as possible */
1000 do {
1001 read = bufgetdata(handles[idx], GUARD_SIZE, &data);
1002 if (read >= 0) {
1003 read = MIN(read, GUARD_SIZE);
1004 rb->write(fd, data, read);
1005 total += read;
1006 bufadvance(handles[idx], read);
1007 rb->lcd_clear_display();
1008 print_progress(total, idx+1, num_files);
1009 print_metadata(meta_handles[idx]);
1010 rb->lcd_update();
1012 rb->sleep(HZ/100);
1013 } while (read > 0);
1015 if (read >= 0 && total >= 0) {
1016 /* some data was read */
1017 DEBUGF("read %ld bytes from handle %d\n", total,
1018 handles[idx]);
1021 /* check the return value to determine what exactly happened */
1022 if (read == -2) {
1023 DEBUGF("data for handle %d isn't ready\n", handles[idx]);
1024 } else if (read == -1) {
1025 DEBUGF("couldn't find handle %d\n", handles[idx]);
1026 } else if (read == 0) {
1027 DEBUGF("finished reading handle %d\n", handles[idx]);
1028 bufclose(handles[idx]);
1029 bufclose(meta_handles[idx]);
1030 rb->close(fd);
1031 fd = -1;
1032 total = 0;
1034 /* move on to the next file and check if we've finished */
1035 idx++;
1036 if (idx >= num_files) {
1037 done_playing = true;
1038 break;
1044 rb->sleep(HZ/10);
1047 DEBUGF("removing the codec thread\n");
1048 rb->remove_thread(NULL);
1051 void bufopen_thread(void)
1053 int idx = 0, ret, fd;
1054 struct mp3entry id3;
1055 bool meta_loaded;
1057 while (idx < num_files)
1059 if (meta_handles[idx] <= 0)
1061 /* read the metadata */
1062 fd = rb->open(files[idx], O_RDONLY);
1063 meta_loaded = rb->get_metadata(&id3, fd, files[idx], false);
1065 /* copy the metadata to a new handle */
1066 if (meta_loaded)
1067 meta_handles[idx] = bufalloc(&id3, sizeof(struct mp3entry),
1068 TYPE_ID3);
1070 rb->close(fd);
1073 if (meta_handles[idx] > 0 || !meta_loaded)
1075 /* open the audio file */
1076 ret = bufopen(files[idx], 0, TYPE_AUDIO);
1077 if (ret > 0) {
1078 handles[idx++] = ret;
1079 } else {
1080 /* couldn't open the audio file, close the metadata handle */
1081 if (meta_handles[idx] > 0) {
1082 bufclose(meta_handles[idx]);
1083 meta_handles[idx] = -1;
1087 rb->sleep(HZ*2);
1090 DEBUGF("bufopen thread finished\n");
1091 rb->remove_thread(NULL);
1095 /* this is the plugin entry point */
1096 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
1098 (void)parameter;
1099 rb = api;
1101 buffer_init();
1103 if (!test_ll())
1105 DEBUGF("linked list test failed\n");
1106 rb->splash(HZ, "linked list test failed");
1107 return PLUGIN_ERROR;
1110 codecthread_id = rb->create_thread(codec_thread,
1111 codec_stack,
1112 sizeof(codec_stack),
1113 "codec"
1114 IF_PRIO(,PRIORITY_PLAYBACK)
1115 IF_COP(, CPU, false));
1117 bufopenthread_id = rb->create_thread(bufopen_thread,
1118 bufopen_stack,
1119 sizeof(bufopen_stack),
1120 "bufopen"
1121 IF_PRIO(,PRIORITY_BACKGROUND)
1122 IF_COP(, CPU, false));
1124 if (!codecthread_id)
1126 rb->splash(HZ, "failed to create codec thread");
1127 return PLUGIN_ERROR;
1129 else if (!bufopenthread_id)
1131 rb->splash(HZ, "failed to create bufopen thread");
1132 return PLUGIN_ERROR;
1134 else
1137 while (!done_playing)
1139 if (data_rem() > 0 && wasted_space() > buffer_len/5) {
1140 DEBUGF("there is %ld bytes of wasted space\n", wasted_space());
1142 /* free buffer from outdated audio data */
1143 struct memory_handle *m = first_handle;
1144 while (m) {
1145 if (m->type == TYPE_AUDIO)
1146 free_buffer(m->id);
1147 m = m->next;
1150 /* free buffer by moving metadata */
1151 m = first_handle;
1152 while (m) {
1153 if (m->type != TYPE_AUDIO)
1154 free_buffer(m->id);
1155 m = m->next;
1158 graph_view(100);
1161 if (data_rem() > 0 && BUF_USED < 3*buffer_len/4 &&
1162 disk_is_spinning())
1164 DEBUGF("%ld bytes left to buffer and the buffer is low\n",
1165 data_rem());
1166 fill_buffer();
1167 } else {
1168 rb->sleep(HZ/2);
1171 DEBUGF("done playing\n");
1172 rb->yield();
1175 rb->backlight_on();
1176 DEBUGF("end of plugin\n");
1177 return PLUGIN_OK;