/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2007 Nicolas Pennequin * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/#include "plugin.h"PLUGIN_HEADERstruct plugin_api* rb;#define GUARD_SIZE (32*1024)/* amount of data to read in one read() call */#define AUDIO_DEFAULT_FILECHUNK (1024*32)/* Ring buffer helper macros *//* Buffer pointer (p) plus value (v), wrapped if necessary */#define RINGBUF_ADD(p,v) ((p+v)<buffer_len ? p+v : p+v-buffer_len)/* Buffer pointer (p) minus value (v), wrapped if necessary */#define RINGBUF_SUB(p,v) ((p>=v) ? p-v : p+buffer_len-v)/* How far value (v) plus buffer pointer (p1) will cross buffer pointer (p2) */#define RINGBUF_ADD_CROSS(p1,v,p2) \((p1<p2) ? (int)(p1+v)-(int)p2 : (int)(p1+v-p2)-(int)buffer_len)/* Bytes available in the buffer */#define BUF_USED RINGBUF_SUB(buf_widx, buf_ridx)struct memory_handle { int id; /* A unique ID for the handle */ //enum data_type type; //enum data_state state; char path[MAX_PATH]; int fd; size_t data; /* Start index of the handle's data buffer */ size_t ridx; /* Current read pointer, relative to the main buffer */ size_t widx; /* Current write pointer */ size_t filesize; /* File total length */ size_t filerem; /* Remaining bytes of file NOT in buffer */ size_t available; /* Available bytes to read from buffer */ size_t offset; /* Offset at which we started reading the file */ struct memory_handle *next;};static char *buffer;static char *guard_buffer;static size_t buffer_len;static size_t buf_widx;static size_t buf_ridx;static size_t conf_filechunk;/* current memory handle in the linked list. NULL when the list is empty. */static struct memory_handle *cur_handle;/* first memory handle in the linked list. NULL when the list is empty. */static struct memory_handle *first_handle;static int num_handles;static void graph_view(int width);/* add a new handle to the linked list and return it. It will have become the new current handle */static struct memory_handle *add_handle(void){ rb->logf("add_handle"); /* this will give each handle a unique id */ static int cur_handle_id = 0; int data_size = sizeof(struct memory_handle); /* check that we actually can add the handle and its data */ if (RINGBUF_ADD_CROSS(buf_widx, data_size, buf_ridx) >= 0) { return NULL; } struct memory_handle *new_handle = (struct memory_handle *)(buffer + buf_widx); buf_widx = RINGBUF_ADD(buf_widx, data_size); if (!first_handle) { /* the new handle is the first one */ first_handle = new_handle; } if (cur_handle) { cur_handle->next = new_handle; } cur_handle = new_handle; rb->logf("cur_handle_id: %d", cur_handle_id); cur_handle->id = cur_handle_id; rb->logf("cur_handle->id: %d", cur_handle->id); cur_handle->next = NULL; num_handles++; cur_handle_id++; rb->logf("cur_handle: %lx", (unsigned long)cur_handle); rb->logf("cur_handle->id: %d", cur_handle->id); return cur_handle;}/* delete a given memory handle from the linked list and return true for success. Nothing is actually erased from memory. */static bool rm_handle(struct memory_handle *h){ if (h == first_handle) { first_handle = h->next; if (h == cur_handle) { rb->logf("removing the first and last handle\n"); cur_handle = NULL; buf_ridx = buf_widx; } else { buf_ridx = (void *)first_handle - (void *)buffer; } } else { struct memory_handle *m = first_handle; while (m && m->next != h) { m = m->next; } if (h && m && m->next == h) { m->next = h->next; if (h == cur_handle) { cur_handle = m; } } else { return false; } } num_handles--; return true;}/* these are unfortunalty needed to be global so move_handle can invalidate them */static int cached_handle_id = -1;static struct memory_handle *cached_handle = NULL;/* Return a pointer to the memory handle of given ID. NULL if the handle wasn't found */static struct memory_handle *find_handle(int handle_id){ /* simple caching because most of the time the requested handle will either be the same as the last, or the one after the last */ if (cached_handle) { if (cached_handle_id == handle_id && cached_handle_id == cached_handle->id) return cached_handle; else if (cached_handle->next && (cached_handle->next->id == handle_id)) { /* JD's quick testing showd this block was only entered 2/1971 calls to find_handle. 8/1971 calls to find_handle resulted in a cache miss */ cached_handle = cached_handle->next; cached_handle_id = handle_id; return cached_handle; } } struct memory_handle *m = first_handle; while (m && m->id != handle_id) { m = m->next; } cached_handle_id = handle_id; cached_handle = m; return (m && m->id == handle_id) ? m : NULL;}/* Move a memory handle to newpos. Return a pointer to the new location of the handle */static struct memory_handle *move_handle(size_t newpos, struct memory_handle *h){ struct memory_handle *dest = (struct memory_handle *)(buffer + newpos); /* Invalidate the cache to prevent it from keeping the old location of h */ if (h == cached_handle) cached_handle = NULL; /* the cur_handle pointer might need updating */ if (h == cur_handle) { cur_handle = dest; } if (h == first_handle) { first_handle = dest; buf_ridx = newpos; } else { struct memory_handle *m = first_handle; while (m && m->next != h) { m = m->next; } if (h && m && m->next == h) { m->next = dest; } else { return NULL; } } rb->memmove(dest, h, sizeof(struct memory_handle)); return dest;}/* Buffer data for the given handle. Return the amount of data buffered or -1 if the handle wasn't found */static int buffer_handle(int handle_id){ rb->logf("buffer_handle(%d)", handle_id); struct memory_handle *h = find_handle(handle_id); if (!h) return -1; if (h->filerem == 0) { /* nothing left to buffer */ return 0; } if (h->fd < 0) /* file closed, reopen */ { if (*h->path) h->fd = rb->open(h->path, O_RDONLY); else return -1; if (h->fd < 0) return -1; if (h->offset) rb->lseek(h->fd, h->offset, SEEK_SET); } int ret = 0; while (h->filerem > 0) { //rb->logf("h: %d\n", (void *)h - (void *)buffer); //rb->logf("buf_widx: %ld\n", (long)buf_widx); /* max amount to copy */ size_t copy_n = MIN(conf_filechunk, buffer_len - buf_widx); /* stop copying if it would overwrite the reading position */ if (RINGBUF_ADD_CROSS(buf_widx, copy_n, buf_ridx) >= 0) break; /* rc is the actual amount read */ int rc = rb->read(h->fd, &buffer[buf_widx], copy_n); if (rc < 0) { //rb->logf("failed on fd %d at buf_widx = %ld for handle %d\n", h->fd, (long)buf_widx, h->id); rb->logf("File ended %ld bytes early\n", (long)h->filerem); h->filesize -= h->filerem; h->filerem = 0; break; } /* Advance buffer */ h->widx = RINGBUF_ADD(h->widx, rc); if (h == cur_handle) buf_widx = h->widx; h->available += rc; ret += rc; h->filerem -= rc; } if (h->filerem == 0) { /* finished buffering the file */ rb->close(h->fd); } rb->logf("buffered %d bytes (%d of %d available, remaining: %d)\n", ret, h->available, h->filesize, h->filerem); return ret;}/* Free buffer space by moving the first handle struct right before the useful part of its data buffer */static void free_buffer(int handle_id){ rb->logf("free_buffer(%d)\n", handle_id); struct memory_handle *h = find_handle(handle_id); if (!h) return; size_t delta = RINGBUF_SUB(h->ridx, h->data); size_t dest = RINGBUF_ADD((void *)h - (void *)buffer, delta); //rb->logf("buf_ridx: %ld, buf_widx: %ld\n", (long)buf_ridx, (long)buf_widx); //rb->logf("dest: %ld, delta: %ld\n", (long)dest, (long)delta); h = move_handle(dest, h); h->data = RINGBUF_ADD(h->data, delta); h->ridx = h->data; h->available -= delta; //graph_view(100);}/* Request a file be buffered filename: name of the file t open offset: starting offset to buffer from the file RETURNS: <0 if the file cannot be opened, or one file already queued to be opened, otherwise the handle for the file in the buffer*/int bufopen(char *file, size_t offset){ /* add the file to the buffering queue. */ /* for now, we'll assume the queue is always empty, so the handle gets added immediately */ rb->logf("bufopen: %s (offset: %d)\n", file, offset); int fd = rb->open(file, O_RDONLY); if (fd < 0) return -1; if (offset) rb->lseek(fd, offset, SEEK_SET); struct memory_handle *h = add_handle(); if (!h) return -1; rb->strncpy(h->path, file, MAX_PATH); h->fd = fd; h->filesize = rb->filesize(fd); h->filerem = h->filesize - offset; h->offset = offset; h->ridx = buf_widx; h->widx = buf_widx; h->data = buf_widx; h->available = 0; rb->logf("h: %lx", (unsigned long)h); rb->logf("h->id: %d", h->id); rb->logf("added handle : %d\n", h->id); return h->id;}/* Close the handle. Return 0 for success and < 0 for failure */int bufclose(int handle_id){ rb->logf("bufclose(%d)\n", handle_id); struct memory_handle *h = find_handle(handle_id); if (!h) return -1; rm_handle(h); return 0;}/* Set the reading index in a handle (relatively to the start of the handle data). Return 0 for success and < 0 for failure */int bufseek(int handle_id, size_t offset){ struct memory_handle *h = find_handle(handle_id); if (!h) return -1; if (offset > h->available) return -2; h->ridx = RINGBUF_ADD(h->data, offset); return 0;}/* Advance the reading index in a handle (relatively to its current position). Return 0 for success and < 0 for failure */int bufadvance(int handle_id, ssize_t offset){ struct memory_handle *h = find_handle(handle_id); if (!h) return -1; if (offset >= 0) { if (offset > h->available - RINGBUF_SUB(h->ridx, h->data)) return -2; h->ridx = RINGBUF_ADD(h->ridx, offset); } else { if (-offset > RINGBUF_SUB(h->ridx, h->data)) return -2; h->ridx = RINGBUF_SUB(h->ridx, -offset); } return 0;}/* Copy data from the given handle to the dest buffer. Return the number of bytes copied or -1 for failure. */int bufread(int handle_id, size_t size, char *dest){ struct memory_handle *h = find_handle(handle_id); size_t buffered_data; if (!h) return -1; if (h->available == 0) return 0; buffered_data = MIN(size, h->available); if (h->ridx + buffered_data > buffer_len) { size_t read = buffer_len - h->ridx; rb->memcpy(dest, &buffer[h->ridx], read); rb->memcpy(dest+read, buffer, buffered_data - read); } else rb->memcpy(dest, &buffer[h->ridx], buffered_data); h->ridx = RINGBUF_ADD(h->ridx, buffered_data); h->available -= buffered_data; return buffered_data;}/* Update the "data" pointer to make the handle's data available to the caller. Return the length of the available linear data or -1 for failure. */long bufgetdata(int handle_id, size_t size, unsigned char **data){ struct memory_handle *h = find_handle(handle_id); if (!h) return -1; long ret; if (h->ridx + size > buffer_len && h->available - RINGBUF_SUB(h->ridx, h->data) >= size) { /* use the guard buffer to provide what was requested. */ int copy_n = h->ridx + size - buffer_len; rb->memcpy(guard_buffer, (unsigned char *)buffer, copy_n); ret = size; } else { ret = MIN(h->available - RINGBUF_SUB(h->ridx, h->data), buffer_len - h->ridx); } *data = (unsigned char *)(buffer + h->ridx); //rb->logf("bufgetdata(%d): h->ridx=%ld, ret=%ld\n", handle_id, (long)h->ridx, ret); return ret;}bool test_ll(void){ struct memory_handle *m1, *m2, *m3, *m4; if (cur_handle != NULL || first_handle != NULL) return false; m1 = add_handle(); if (cur_handle != m1 || first_handle != m1 || m1->next != NULL) return false; m2 = add_handle(); if (cur_handle != m2 || first_handle != m1 || m1->next != m2 || m2->next != NULL) return false; m3 = add_handle(); if (cur_handle != m3 || first_handle != m1 || m2->next != m3 || m3->next != NULL) return false; rm_handle(m2); if (cur_handle != m3 || first_handle != m1 || m1->next != m3) return false; rm_handle(m3); if (cur_handle != m1 || first_handle != m1 || m1->next != NULL) return false; m4 = add_handle(); if (cur_handle != m4 || first_handle != m1 || m1->next != m4 || m4->next != NULL) return false; rm_handle(m1); if (cur_handle != m4 || first_handle != m4) return false; rm_handle(m4); if (cur_handle != NULL || first_handle != NULL) return false; m1 = add_handle(); m2 = add_handle(); m3 = add_handle(); m4 = add_handle(); if (cur_handle != m4 || first_handle != m1) return false;/* int m2id = m2->id; m2 = move_handle(m2->data + 1024*1024, m2); if (cur_handle != m4 || first_handle != m1 || m1->next != m2 || m2->next != m3 || find_handle(m2id) != m2) return false; int m1id = m1->id; m1 = move_handle(m1->data + 1024*1024*3, m1); if (cur_handle != m4 || first_handle != m1 || m1->next != m2 || find_handle(m1id) != m1) return false;*/ rm_handle(m1); rm_handle(m2); rm_handle(m3); rm_handle(m4); if (cur_handle != NULL || first_handle != NULL) return false; return true;}#if 0static void list_handles(void){ struct memory_handle *m = first_handle; while (m) { rb->logf("%02d - %d\n", m->id, (void *)m-(void *)buffer); m = m->next; }}#endif/* display a nice graphical view of the ringbuffer. */static void graph_view(int width){ int i, r_pos, w_pos; r_pos = buf_ridx * width / buffer_len; w_pos = buf_widx * width / buffer_len; rb->logf("|"); for (i=0; i <= width; i++) { if (i != r_pos && i != w_pos) { if (buf_ridx <= buf_widx) { if (i > r_pos && i < w_pos) { rb->logf(">"); } else { rb->logf("-"); } } else { if (i > r_pos || i < w_pos) { rb->logf(">"); } else { rb->logf("-"); } } } else { if (i == r_pos && i == w_pos) { if (buf_ridx <= buf_widx) { rb->logf("RW"); } else { rb->logf("WR"); } } else if (i == r_pos) { rb->logf("R"); } else if (i == w_pos) { rb->logf("W"); } } } rb->logf("|"); rb->logf("\n");}bool buffer_init(void){ buffer = rb->plugin_get_audio_buffer(&buffer_len); if (!buffer) { rb->logf("couldn't allocate buffer\n"); return false; } buffer_len -= GUARD_SIZE; guard_buffer = buffer + buffer_len; buf_widx = 0; buf_ridx = 0; first_handle = NULL; num_handles = 0; conf_filechunk = AUDIO_DEFAULT_FILECHUNK; return true;}/* returns true if the file still has some on disk unread */bool handle_has_data(int handle){ struct memory_handle *m = find_handle(handle); if (m) { return m->filerem != 0; } return false;}bool need_rebuffer(void){ size_t free, wasted; wasted = first_handle? RINGBUF_SUB(first_handle->ridx, first_handle->data) : 0; while (wasted > buffer_len / 2) { free_buffer(first_handle->id); wasted = first_handle? RINGBUF_SUB(first_handle->ridx, first_handle->data) : 0; } free = buffer_len - BUF_USED; return ((free >= buffer_len/4));}bool disk_is_spinning(void){ return true;}#define MAX_HANDLES 64/* this is the plugin entry point */enum plugin_status plugin_start(struct plugin_api* api, void* parameter){ (void)parameter; rb = api; int argc = 6; char *argv[] = { NULL, "/070623 - David Vendetta.mp3", "/africanism all stars feat the hard boys - hard (bob sinclair remix).mp3", "/alan_braxe__kris_menace_-_lumberjack.mp3", "/Ame - Rej (A Hundred Birds Remix).mp3", "/Antena - Camino del Sol (Joakim Remix).mp3" }; int next_file = 1; int last_handle = -1; int handle_order[MAX_HANDLES]; int reading_handle = 0; bool done_buffering = false, done_playing = false; //char read_buffer[GUARD_SIZE]; unsigned char *data; int fd = -1; /* used to write the files out as they are read */ int current_handle = -1; struct memory_handle *m = NULL; buffer_init(); if (!test_ll()) { rb->logf("linked list test failed\n"); rb->splash(HZ, "linked list test failed"); return PLUGIN_ERROR; } buffer_init(); rb->logf("test\n"); while (!done_buffering || !done_playing) { rb->logf("buffer usage: %d handles_used: %d\n", BUF_USED, num_handles); //graph_view(100); /* "Buffering thread" section */ if (!done_buffering && need_rebuffer() && disk_is_spinning()) { m = find_handle(current_handle); if ( !m || ((m->filerem == 0) && (m->next == NULL))) { int h = bufopen(argv[next_file], 0); m = find_handle(h); if (h >= 0) { next_file++; //rb->logf("new handle %d\n",h); last_handle++; handle_order[last_handle] = h; buffer_handle(m->id); } else { rb->logf("error\n"); } current_handle = h; } else { if (m->filerem == 0) { m = m->next; current_handle = m?m->id:-1; } if (m) { rb->logf("buffering handle %d\n",m->id); buffer_handle(m->id); } } if (next_file == argc && m->filerem == 0) done_buffering = true; } /* "Playback thread" section */ else { rb->logf("reading handle: %d\n", handle_order[reading_handle]); long read; long total = 0; char file[MAX_PATH]; if (reading_handle >= last_handle && !handle_has_data(handle_order[reading_handle])) done_playing = true; if (fd < 0) { rb->snprintf(file, MAX_PATH, "/file%d.mp3", reading_handle); fd = rb->open(file, O_CREAT|O_TRUNC|O_WRONLY); if (fd < 0) { rb->logf("ERROROROROR\n"); rb->splash(HZ, "couldn't create file"); return PLUGIN_ERROR; } } do { //read = bufread(handle_order[reading_handle], GUARD_SIZE,read_buffer); //write(fd, read_buffer, read); read = bufgetdata(handle_order[reading_handle], GUARD_SIZE, &data); read = MIN(read, GUARD_SIZE); rb->write(fd, data, read); total += read; bufadvance(handle_order[reading_handle], read); } while (read > 0); rb->logf("read %ld bytes from handle %d\n", total, handle_order[reading_handle]); /* close the fd/handle if there is no more data or an error */ if (read < 0 || handle_has_data(handle_order[reading_handle]) == false) { rb->logf("finished reading %d\n",handle_order[reading_handle]); bufclose(handle_order[reading_handle]); rb->close(fd); reading_handle++; fd = -1; } else { rb->logf("there is data left to buffer for %d\n", handle_order[reading_handle]); } } } rb->logf("buffer usage: %d handles_used: %d\n", BUF_USED,num_handles); //graph_view(100); return 0; return PLUGIN_OK;}