bufopen: better description of the offset parameter.
[Rockbox-MoB.git] / testapp.c
blobd0d42f8c8e048354911dfe407112d5d47a4ef425
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 ****************************************************************************/
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <stdbool.h>
23 #include <string.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include "buffering.h"
27 #include "helpers.h"
29 #define BUFFER_SIZE (32*1024*1024)
30 #define GUARD_SIZE (32*1024)
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 struct memory_handle {
44 int id; /* A unique ID for the handle */
45 enum data_type type;
46 enum data_state state;
47 char path[MAX_PATH];
48 int fd;
49 size_t data; /* Start index of the handle's data buffer */
50 size_t data_len; /* Length of the data buffer for the handle */
51 size_t ridx; /* Current read pointer, relative to the main buffer */
52 size_t widx; /* Current write pointer */
53 size_t filesize; /* File total length */
54 size_t filerem; /* Remaining bytes of file NOT in buffer */
55 size_t available; /* Available bytes to read from buffer */
56 size_t offset; /* Offset at which we started reading the file */
57 struct memory_handle *next;
60 static char *buffer;
61 static char *guard_buffer;
63 static size_t buffer_len;
64 static size_t buf_widx;
65 static size_t buf_ridx;
67 static size_t conf_filechunk;
69 /* current memory handle in the linked list. NULL when the list is empty. */
70 static struct memory_handle *cur_handle;
71 /* first memory handle in the linked list. NULL when the list is empty. */
72 static struct memory_handle *first_handle;
73 static int num_handles;
75 static void graph_view(int width);
78 /* add a new handle to the linked list and return it. It will have become the
79 new current handle. The handle will reserve "data_size" bytes or if that's
80 not possible, decrease "data_size" to allow adding the handle. */
81 static struct memory_handle *add_handle(int *data_size)
83 /* this will give each handle a unique id */
84 static int cur_handle_id = 0;
86 int len = (data_size ? *data_size : 0) + sizeof(struct memory_handle);
88 /* check that we actually can add the handle and its data */
89 int overlap = RINGBUF_ADD_CROSS(buf_widx, len + 3, buf_ridx);
90 if (overlap >= 0) {
91 *data_size -= overlap;
92 len -= overlap;
94 if (len < sizeof(struct memory_handle)) {
95 return NULL;
98 /* make sure buf_widx is 32-bit aligned so that the handle struct is. */
99 buf_widx = (RINGBUF_ADD(buf_widx, 3)) & ~3;
101 struct memory_handle *new_handle = (struct memory_handle *)(buffer + buf_widx);
103 /* only advance the buffer write index of the size of the struct */
104 buf_widx = RINGBUF_ADD(buf_widx, sizeof(struct memory_handle));
106 if (!first_handle) {
107 /* the new handle is the first one */
108 first_handle = new_handle;
111 if (cur_handle) {
112 cur_handle->next = new_handle;
115 cur_handle = new_handle;
116 cur_handle->id = cur_handle_id++;
117 cur_handle->next = NULL;
118 num_handles++;
119 return cur_handle;
122 /* delete a given memory handle from the linked list
123 and return true for success. Nothing is actually erased from memory. */
124 static bool rm_handle(struct memory_handle *h)
126 if (h == first_handle) {
127 first_handle = h->next;
128 if (h == cur_handle) {
129 DEBUGF("removing the first and last handle\n");
130 cur_handle = NULL;
131 buf_ridx = buf_widx;
132 } else {
133 buf_ridx = (void *)first_handle - (void *)buffer;
135 } else {
136 struct memory_handle *m = first_handle;
137 while (m && m->next != h) {
138 m = m->next;
140 if (h && m && m->next == h) {
141 m->next = h->next;
142 if (h == cur_handle) {
143 cur_handle = m;
145 } else {
146 return false;
150 num_handles--;
151 return true;
154 /* these are unfortunalty needed to be global
155 so move_handle can invalidate them */
156 static int cached_handle_id = -1;
157 static struct memory_handle *cached_handle = NULL;
159 /* Return a pointer to the memory handle of given ID.
160 NULL if the handle wasn't found */
161 static struct memory_handle *find_handle(int handle_id)
163 /* simple caching because most of the time the requested handle
164 will either be the same as the last, or the one after the last */
165 if (cached_handle)
167 if (cached_handle_id == handle_id && cached_handle_id == cached_handle->id)
168 return cached_handle;
169 else if (cached_handle->next && (cached_handle->next->id == handle_id))
171 /* JD's quick testing showd this block was only entered
172 2/1971 calls to find_handle.
173 8/1971 calls to find_handle resulted in a cache miss */
174 cached_handle = cached_handle->next;
175 cached_handle_id = handle_id;
176 return cached_handle;
180 struct memory_handle *m = first_handle;
181 while (m && m->id != handle_id) {
182 m = m->next;
184 cached_handle_id = handle_id;
185 cached_handle = m;
186 return (m && m->id == handle_id) ? m : NULL;
189 /* Move a memory handle to newpos.
190 Return a pointer to the new location of the handle */
191 static struct memory_handle *move_handle(size_t newpos, struct memory_handle *h)
193 /* make sure newpos is 32-bit aligned so that the handle struct is. */
194 newpos = (RINGBUF_ADD(newpos, 3)) & ~3;
196 DEBUGF("move_handle\n");
197 struct memory_handle *dest = (struct memory_handle *)(buffer + newpos);
199 /* Invalidate the cache to prevent it from keeping the old location of h */
200 if (h == cached_handle)
201 cached_handle = NULL;
203 /* the cur_handle pointer might need updating */
204 if (h == cur_handle) {
205 cur_handle = dest;
208 if (h == first_handle) {
209 first_handle = dest;
210 buf_ridx = newpos;
211 } else {
212 struct memory_handle *m = first_handle;
213 while (m && m->next != h) {
214 m = m->next;
216 if (h && m && m->next == h) {
217 m->next = dest;
218 } else {
219 return NULL;
222 memmove(dest, h, sizeof(struct memory_handle));
223 return dest;
225 /* Buffer data for the given handle. Return the amount of data buffered
226 or -1 if the handle wasn't found */
227 static int buffer_handle(int handle_id)
229 struct memory_handle *h = find_handle(handle_id);
230 if (!h)
231 return -1;
233 if (h->filerem == 0) {
234 /* nothing left to buffer */
235 return 0;
238 if (h->fd < 0) /* file closed, reopen */
240 if (*h->path)
241 h->fd = open(h->path, O_RDONLY);
242 else
243 return -1;
245 if (h->fd < 0)
246 return -1;
248 if (h->offset)
249 lseek(h->fd, h->offset, SEEK_SET);
252 int ret = 0;
253 while (h->filerem > 0)
255 //DEBUGF("h: %d\n", (void *)h - (void *)buffer);
256 //DEBUGF("buf_widx: %ld\n", (long)buf_widx);
257 /* max amount to copy */
258 size_t copy_n = MIN(conf_filechunk, buffer_len - buf_widx);
260 /* stop copying if it would overwrite the reading position */
261 if (RINGBUF_ADD_CROSS(buf_widx, copy_n, buf_ridx) >= 0)
262 break;
264 /* rc is the actual amount read */
265 int rc = read(h->fd, &buffer[buf_widx], copy_n);
267 if (rc < 0)
269 //DEBUGF("failed on fd %d at buf_widx = %ld for handle %d\n", h->fd, (long)buf_widx, h->id);
270 DEBUGF("File ended %ld bytes early\n", (long)h->filerem);
271 h->filesize -= h->filerem;
272 h->filerem = 0;
273 break;
276 /* Advance buffer */
277 h->widx = RINGBUF_ADD(h->widx, rc);
278 if (h == cur_handle)
279 buf_widx = h->widx;
280 h->available += rc;
281 ret += rc;
282 h->filerem -= rc;
285 if (h->filerem == 0) {
286 /* finished buffering the file */
287 close(h->fd);
290 DEBUGF("buffered %d bytes (%d of %d available, remaining: %d)\n",
291 ret, h->available, h->filesize, h->filerem);
292 return ret;
295 /* Free buffer space by moving the first handle struct right before the useful
296 part of its data buffer */
297 static void free_buffer(int handle_id)
299 DEBUGF("free_buffer(%d)\n", handle_id);
300 struct memory_handle *h = find_handle(handle_id);
301 if (!h)
302 return;
304 size_t delta = RINGBUF_SUB(h->ridx, h->data);
305 size_t dest = RINGBUF_ADD((void *)h - (void *)buffer, delta);
306 //DEBUGF("buf_ridx: %ld, buf_widx: %ld\n", (long)buf_ridx, (long)buf_widx);
307 //DEBUGF("dest: %ld, delta: %ld\n", (long)dest, (long)delta);
308 h = move_handle(dest, h);
309 h->data = RINGBUF_ADD(h->data, delta);
310 h->ridx = h->data;
311 h->available -= delta;
312 graph_view(100);
315 /* Request a file be buffered
316 filename: name of the file t open
317 offset: starting offset to buffer from the file
318 RETURNS: <0 if the file cannot be opened, or one file already
319 queued to be opened, otherwise the handle for the file in the buffer
321 int bufopen(char *file, size_t offset)
323 /* add the file to the buffering queue. */
324 /* for now, we'll assume the queue is always empty, so the handle
325 gets added immediately */
327 DEBUGF("bufopen: %s (offset: %d)\n", file, offset);
329 int fd = open(file, O_RDONLY);
330 if (fd < 0)
331 return -1;
333 if (offset)
334 lseek(fd, offset, SEEK_SET);
336 int size = filesize(fd) - offset;
337 struct memory_handle *h = add_handle(&size);
338 if (!h)
340 DEBUGF("failed to add handle\n");
341 return -1;
344 strncpy(h->path, file, MAX_PATH);
345 h->fd = fd;
346 h->filesize = filesize(fd);
347 h->filerem = h->filesize - offset;
348 h->offset = offset;
349 h->ridx = buf_widx;
350 h->widx = buf_widx;
351 h->data = buf_widx;
352 h->data_len = size;
353 h->available = 0;
355 DEBUGF("added handle : %d\n", h->id);
356 return h->id;
359 /* Close the handle. Return 0 for success and < 0 for failure */
360 int bufclose(int handle_id)
362 DEBUGF("bufclose(%d)\n", handle_id);
363 struct memory_handle *h = find_handle(handle_id);
364 if (!h)
365 return -1;
367 rm_handle(h);
368 return 0;
371 /* Set the reading index in a handle (relatively to the start of the handle data).
372 Return 0 for success and < 0 for failure */
373 int bufseek(int handle_id, size_t offset)
375 struct memory_handle *h = find_handle(handle_id);
376 if (!h)
377 return -1;
379 if (offset > h->available)
380 return -2;
382 h->ridx = RINGBUF_ADD(h->data, offset);
383 return 0;
386 /* Advance the reading index in a handle (relatively to its current position).
387 Return 0 for success and < 0 for failure */
388 int bufadvance(int handle_id, ssize_t offset)
390 struct memory_handle *h = find_handle(handle_id);
391 if (!h)
392 return -1;
394 if (offset >= 0)
396 if (offset > h->available - RINGBUF_SUB(h->ridx, h->data))
397 return -2;
399 h->ridx = RINGBUF_ADD(h->ridx, offset);
401 else
403 if (-offset > RINGBUF_SUB(h->ridx, h->data))
404 return -2;
406 h->ridx = RINGBUF_SUB(h->ridx, -offset);
409 return 0;
412 /* Copy data from the given handle to the dest buffer.
413 Return the number of bytes copied or -1 for failure. */
414 int bufread(int handle_id, size_t size, char *dest)
416 struct memory_handle *h = find_handle(handle_id);
417 size_t buffered_data;
418 if (!h)
419 return -1;
420 if (h->available == 0)
421 return 0;
422 buffered_data = MIN(size, h->available);
424 if (h->ridx + buffered_data > buffer_len)
426 size_t read = buffer_len - h->ridx;
427 memcpy(dest, &buffer[h->ridx], read);
428 memcpy(dest+read, buffer, buffered_data - read);
430 else memcpy(dest, &buffer[h->ridx], buffered_data);
432 h->ridx = RINGBUF_ADD(h->ridx, buffered_data);
433 h->available -= buffered_data;
434 return buffered_data;
437 /* Update the "data" pointer to make the handle's data available to the caller.
438 Return the length of the available linear data or -1 for failure. */
439 long bufgetdata(int handle_id, size_t size, unsigned char **data)
441 struct memory_handle *h = find_handle(handle_id);
442 if (!h)
443 return -1;
445 long ret;
447 if (h->ridx + size > buffer_len &&
448 h->available - RINGBUF_SUB(h->ridx, h->data) >= size)
450 /* use the guard buffer to provide what was requested. */
451 int copy_n = h->ridx + size - buffer_len;
452 memcpy(guard_buffer, (unsigned char *)buffer, copy_n);
453 ret = size;
455 else
457 ret = MIN(h->available - RINGBUF_SUB(h->ridx, h->data),
458 buffer_len - h->ridx);
461 *data = (unsigned char *)(buffer + h->ridx);
463 //DEBUGF("bufgetdata(%d): h->ridx=%ld, ret=%ld\n", handle_id, (long)h->ridx, ret);
464 return ret;
467 bool test_ll(void)
469 struct memory_handle *m1, *m2, *m3, *m4;
471 if (cur_handle != NULL || first_handle != NULL)
472 return false;
474 m1 = add_handle(NULL);
476 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
477 return false;
479 m2 = add_handle(NULL);
481 if (cur_handle != m2 || first_handle != m1 || m1->next != m2 || m2->next != NULL)
482 return false;
484 m3 = add_handle(NULL);
486 if (cur_handle != m3 || first_handle != m1 || m2->next != m3 || m3->next != NULL)
487 return false;
489 rm_handle(m2);
491 if (cur_handle != m3 || first_handle != m1 || m1->next != m3)
492 return false;
494 rm_handle(m3);
496 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
497 return false;
499 m4 = add_handle(NULL);
501 if (cur_handle != m4 || first_handle != m1 || m1->next != m4 || m4->next != NULL)
502 return false;
504 rm_handle(m1);
506 if (cur_handle != m4 || first_handle != m4)
507 return false;
509 rm_handle(m4);
511 if (cur_handle != NULL || first_handle != NULL)
512 return false;
514 m1 = add_handle(NULL);
515 m2 = add_handle(NULL);
516 m3 = add_handle(NULL);
517 m4 = add_handle(NULL);
519 if (cur_handle != m4 || first_handle != m1)
520 return false;
522 m2 = move_handle(RINGBUF_ADD(m2->data, 1024*1024), m2);
524 if (cur_handle != m4 || first_handle != m1 || m1->next != m2 || m2->next != m3)
525 return false;
527 m1 = move_handle(RINGBUF_ADD(m1->data, 1024*1024*3), m1);
529 if (cur_handle != m4 || first_handle != m1 || m1->next != m2)
530 return false;
532 rm_handle(m1);
533 rm_handle(m2);
534 rm_handle(m3);
535 rm_handle(m4);
537 if (cur_handle != NULL || first_handle != NULL)
538 return false;
540 return true;
543 #if 0
544 static void list_handles(void)
546 struct memory_handle *m = first_handle;
547 while (m) {
548 DEBUGF("%02d - %d\n", m->id, (void *)m-(void *)buffer);
549 m = m->next;
552 #endif
554 /* display a nice graphical view of the ringbuffer. */
555 static void graph_view(int width)
557 int i, r_pos, w_pos;
558 r_pos = buf_ridx * width / buffer_len;
559 w_pos = buf_widx * width / buffer_len;
561 DEBUGF("|");
562 for (i=0; i <= width; i++)
564 if (i != r_pos && i != w_pos)
566 if (buf_ridx <= buf_widx)
568 if (i > r_pos && i < w_pos) {
569 DEBUGF(">");
570 } else {
571 DEBUGF("-");
574 else
576 if (i > r_pos || i < w_pos) {
577 DEBUGF(">");
578 } else {
579 DEBUGF("-");
583 else
585 if (i == r_pos && i == w_pos)
587 if (buf_ridx <= buf_widx) {
588 DEBUGF("RW");
589 } else {
590 DEBUGF("WR");
592 } else if (i == r_pos) {
593 DEBUGF("R");
594 } else if (i == w_pos) {
595 DEBUGF("W");
599 DEBUGF("|");
600 DEBUGF("\n");
603 void buffer_init(void)
605 buffer = (char *)malloc(sizeof(char) * (BUFFER_SIZE + GUARD_SIZE));
606 if (!buffer)
608 DEBUGF("couldn't allocate buffer\n");
609 exit(1);
611 buffer_len = BUFFER_SIZE;
612 guard_buffer = buffer + BUFFER_SIZE;
614 buf_widx = 0;
615 buf_ridx = 0;
617 first_handle = NULL;
618 num_handles = 0;
620 conf_filechunk = AUDIO_DEFAULT_FILECHUNK;
623 /* returns true if the file still has some on disk unread */
624 bool handle_has_data(int handle)
626 struct memory_handle *m = find_handle(handle);
627 if (m)
629 return m->filerem != 0;
631 return false;
634 bool need_rebuffer(void)
636 size_t free, wasted;
637 wasted = first_handle? RINGBUF_SUB(first_handle->ridx, first_handle->data) : 0;
638 while (wasted > buffer_len / 2)
640 free_buffer(first_handle->id);
641 wasted = first_handle? RINGBUF_SUB(first_handle->ridx, first_handle->data) : 0;
643 free = buffer_len - BUF_USED;
644 return ((free >= buffer_len/4));
647 bool disk_is_spinning(void)
649 return true;
652 #define MAX_HANDLES 64
653 int main(int argc, char *argv[])
655 int next_file = 1;
656 int last_handle = -1;
657 int handle_order[MAX_HANDLES];
658 int reading_handle = 0;
659 bool done_buffering = false, done_playing = false;
660 //char read_buffer[GUARD_SIZE];
661 unsigned char *data;
662 int fd = -1; /* used to write the files out as they are read */
663 int current_handle = -1;
664 struct memory_handle *m = NULL;
665 buffer_init();
667 if (!test_ll())
669 DEBUGF("linked list test failed\n");
670 exit(1);
673 buffer_init();
675 while (!done_buffering || !done_playing)
677 DEBUGF("buffer usage: %d handles_used: %d\n", BUF_USED, num_handles);
678 graph_view(100);
680 /* "Buffering thread" section */
681 if (!done_buffering && need_rebuffer() && disk_is_spinning())
683 m = find_handle(current_handle);
684 if ( !m || ((m->filerem == 0) && (m->next == NULL)))
686 int h = bufopen(argv[next_file], 0);
687 m = find_handle(h);
688 if (h >= 0)
690 next_file++;
691 //DEBUGF("new handle %d\n",h);
692 last_handle++;
693 handle_order[last_handle] = h;
694 buffer_handle(m->id);
696 current_handle = h;
698 else
700 if (m->filerem == 0)
702 m = m->next;
703 current_handle = m?m->id:-1;
705 if (m)
707 DEBUGF("buffering handle %d\n",m->id);
708 buffer_handle(m->id);
712 if (next_file == argc && m->filerem == 0)
713 done_buffering = true;
715 /* "Playback thread" section */
716 else
718 DEBUGF("reading handle: %d\n", handle_order[reading_handle]);
719 long read;
720 long total = 0;
721 char file[MAX_PATH];
722 if (reading_handle >= last_handle
723 && !handle_has_data(handle_order[reading_handle]))
724 done_playing = true;
726 if (fd < 0)
728 snprintf(file, MAX_PATH, "./file%d.mp3", reading_handle);
729 fd = open(file, O_CREAT|O_TRUNC|O_WRONLY, 0770);
730 if (fd < 0)
732 DEBUGF("ERROROROROR\n");
733 exit(1);
738 //read = bufread(handle_order[reading_handle], GUARD_SIZE,read_buffer);
739 //write(fd, read_buffer, read);
740 read = bufgetdata(handle_order[reading_handle], GUARD_SIZE, &data);
741 read = MIN(read, GUARD_SIZE);
742 write(fd, data, read);
743 total += read;
744 bufadvance(handle_order[reading_handle], read);
746 while (read > 0);
748 DEBUGF("read %ld bytes from handle %d\n", total, handle_order[reading_handle]);
750 /* close the fd/handle if there is no more data or an error */
751 if (read < 0 || handle_has_data(handle_order[reading_handle]) == false)
753 DEBUGF("finished reading %d\n",handle_order[reading_handle]);
754 bufclose(handle_order[reading_handle]);
755 close(fd);
756 reading_handle++;
757 fd = -1;
759 else
761 DEBUGF("there is data left to buffer for %d\n", handle_order[reading_handle]);
766 DEBUGF("buffer usage: %d handles_used: %d\n", BUF_USED,num_handles);
767 graph_view(100);
769 free(buffer);
770 return 0;