From c537d5958e8b421ac4f9bef6c8b9e7425a6cf167 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Wed, 27 Apr 2011 03:08:23 +0000 Subject: Commit FS#12069 - Playback rework - first stages. Gives as thorough as possible a treatment of codec management, track change and metadata logic as possible while maintaining fairly narrow focus and not rewriting everything all at once. Please see the rockbox-dev mail archive on 2011-04-25 (Playback engine rework) for a more thorough manifest of what was addressed. Plugins and codecs become incompatible. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29785 a1c6a512-1295-4272-9138-f99709370657 --- apps/appevents.h | 1 + apps/buffering.c | 288 ++- apps/buffering.h | 25 +- apps/codec_thread.c | 679 ++++--- apps/codec_thread.h | 7 +- apps/codecs.c | 96 +- apps/codecs.h | 67 +- apps/codecs/a52.c | 53 +- apps/codecs/a52_rm.c | 77 +- apps/codecs/aac.c | 62 +- apps/codecs/adx.c | 40 +- apps/codecs/aiff.c | 108 +- apps/codecs/aiff_enc.c | 54 +- apps/codecs/alac.c | 188 +- apps/codecs/ape.c | 54 +- apps/codecs/asap.c | 32 +- apps/codecs/atrac3_oma.c | 136 +- apps/codecs/atrac3_rm.c | 67 +- apps/codecs/au.c | 80 +- apps/codecs/codec_crt0.c | 50 +- apps/codecs/cook.c | 63 +- apps/codecs/flac.c | 62 +- apps/codecs/lib/codeclib.c | 25 +- apps/codecs/lib/codeclib.h | 3 +- apps/codecs/mod.c | 67 +- apps/codecs/mp3_enc.c | 59 +- apps/codecs/mpa.c | 76 +- apps/codecs/mpc.c | 76 +- apps/codecs/nsf.c | 82 +- apps/codecs/raac.c | 98 +- apps/codecs/shorten.c | 40 +- apps/codecs/sid.c | 72 +- apps/codecs/smaf.c | 82 +- apps/codecs/spc.c | 128 +- apps/codecs/speex.c | 61 +- apps/codecs/tta.c | 61 +- apps/codecs/vorbis.c | 74 +- apps/codecs/vox.c | 78 +- apps/codecs/wav.c | 106 +- apps/codecs/wav64.c | 113 +- apps/codecs/wav_enc.c | 54 +- apps/codecs/wavpack.c | 78 +- apps/codecs/wavpack_enc.c | 112 +- apps/codecs/wma.c | 77 +- apps/codecs/wmapro.c | 68 +- apps/codecs/wmavoice.c | 68 +- apps/gui/wps.c | 40 +- apps/menus/playback_menu.c | 4 + apps/metadata.c | 130 +- apps/metadata.h | 7 +- apps/metadata/nsf.c | 3 + apps/pcmbuf.c | 55 +- apps/pcmbuf.h | 10 +- apps/playback.c | 4740 ++++++++++++++++++++++++++++---------------- apps/playback.h | 78 +- apps/playlist.c | 43 +- apps/playlist.h | 3 - apps/plugin.c | 2 +- apps/plugin.h | 10 +- apps/plugins/test_codec.c | 45 +- firmware/export/kernel.h | 10 + firmware/kernel.c | 215 +- 62 files changed, 5634 insertions(+), 3808 deletions(-) diff --git a/apps/appevents.h b/apps/appevents.h index fd578b90a2..a303491ae9 100644 --- a/apps/appevents.h +++ b/apps/appevents.h @@ -35,6 +35,7 @@ enum { PLAYBACK_EVENT_TRACK_BUFFER, PLAYBACK_EVENT_TRACK_FINISH, PLAYBACK_EVENT_TRACK_CHANGE, + PLAYBACK_EVENT_TRACK_SKIP, PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE, }; diff --git a/apps/buffering.c b/apps/buffering.c index 578f0f261a..a130a787ff 100644 --- a/apps/buffering.c +++ b/apps/buffering.c @@ -58,7 +58,7 @@ #define GUARD_BUFSIZE (32*1024) /* Define LOGF_ENABLE to enable logf output in this file */ -/*#define LOGF_ENABLE*/ +/* #define LOGF_ENABLE */ #include "logf.h" /* macros to enable logf for queues @@ -82,8 +82,6 @@ #define LOGFQUEUE_SYS_TIMEOUT(...) #endif -/* default point to start buffer refill */ -#define BUFFERING_DEFAULT_WATERMARK (1024*128) /* amount of data to read in one read() call */ #define BUFFERING_DEFAULT_FILECHUNK (1024*32) @@ -94,6 +92,8 @@ struct memory_handle { int id; /* A unique ID for the handle */ enum data_type type; /* Type of data buffered with this handle */ + int8_t pinned; /* Count of references */ + int8_t signaled; /* Stop any attempt at waiting to get the data */ char path[MAX_PATH]; /* Path if data originated in a file */ int fd; /* File descriptor to path (-1 if closed) */ size_t data; /* Start index of the handle's data buffer */ @@ -125,9 +125,7 @@ static volatile size_t buf_ridx; /* current reading position */ /* Configuration */ static size_t conf_watermark = 0; /* Level to trigger filebuf fill */ -#if MEMORYSIZE > 8 static size_t high_watermark = 0; /* High watermark for rebuffer */ -#endif /* current memory handle in the linked list. NULL when the list is empty. */ static struct memory_handle *cur_handle; @@ -162,7 +160,6 @@ enum Q_REBUFFER_HANDLE, /* Request reset and rebuffering of a handle at a new file starting position. */ Q_CLOSE_HANDLE, /* Request closing a handle */ - Q_BASE_HANDLE, /* Set the reference handle for buf_useful_data */ /* Configuration: */ Q_START_FILL, /* Request that the buffering thread initiate a buffer @@ -222,6 +219,9 @@ static inline ssize_t ringbuf_add_cross(uintptr_t p1, size_t v, uintptr_t p2) /* Bytes available in the buffer */ #define BUF_USED ringbuf_sub(buf_widx, buf_ridx) +/* Real buffer watermark */ +#define BUF_WATERMARK MIN(conf_watermark, high_watermark) + /* LINKED LIST MANAGEMENT ====================== @@ -313,6 +313,12 @@ static struct memory_handle *add_handle(size_t data_size, bool can_wrap, /* Prevent buffering thread from looking at it */ new_handle->filerem = 0; + /* Handle can be moved by default */ + new_handle->pinned = 0; + + /* Handle data can be waited for by default */ + new_handle->signaled = 0; + /* only advance the buffer write index of the size of the struct */ buf_widx = ringbuf_add(buf_widx, sizeof(struct memory_handle)); @@ -364,6 +370,9 @@ static bool rm_handle(const struct memory_handle *h) buf_widx = cur_handle->widx; } } else { + /* If we don't find ourselves, this is a seriously incoherent + state with a corrupted list and severe action is needed! */ + panicf("rm_handle fail: %d", h->id); return false; } } @@ -385,8 +394,7 @@ 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) { if (cached_handle->id == handle_id) { return cached_handle; } else if (cached_handle->next && @@ -618,20 +626,22 @@ static void update_data_counters(struct data_counters *dc) static inline bool buffer_is_low(void) { update_data_counters(NULL); - return data_counters.useful < (conf_watermark / 2); + return data_counters.useful < BUF_WATERMARK / 2; } /* Q_BUFFER_HANDLE event and buffer data for the given handle. Return whether or not the buffering should continue explicitly. */ static bool buffer_handle(int handle_id, size_t to_buffer) { - logf("buffer_handle(%d)", handle_id); + logf("buffer_handle(%d, %lu)", handle_id, (unsigned long)to_buffer); struct memory_handle *h = find_handle(handle_id); bool stop = false; if (!h) return true; + logf(" type: %d", (int)h->type); + if (h->filerem == 0) { /* nothing left to buffer */ return true; @@ -659,13 +669,13 @@ static bool buffer_handle(int handle_id, size_t to_buffer) if (!get_metadata((struct mp3entry *)(buffer + h->data), h->fd, h->path)) { /* metadata parsing failed: clear the buffer. */ - memset(buffer + h->data, 0, sizeof(struct mp3entry)); + wipe_mp3entry((struct mp3entry *)(buffer + h->data)); } close(h->fd); h->fd = -1; h->filerem = 0; h->available = sizeof(struct mp3entry); - h->widx += sizeof(struct mp3entry); + h->widx = ringbuf_add(h->widx, sizeof(struct mp3entry)); send_event(BUFFER_EVENT_FINISHED, &handle_id); return true; } @@ -698,7 +708,7 @@ static bool buffer_handle(int handle_id, size_t to_buffer) break; } - DEBUGF("File ended %ld bytes early\n", (long)h->filerem); + logf("File ended %ld bytes early\n", (long)h->filerem); h->filesize -= h->filerem; h->filerem = 0; break; @@ -770,22 +780,31 @@ static bool close_handle(int handle_id) part of its data buffer or by moving all the data. */ static void shrink_handle(struct memory_handle *h) { - size_t delta; - if (!h) return; - if (h->type == TYPE_ID3 || h->type == TYPE_CUESHEET || - h->type == TYPE_BITMAP || h->type == TYPE_CODEC || - h->type == TYPE_ATOMIC_AUDIO) - { + if (h->type == TYPE_PACKET_AUDIO) { + /* only move the handle struct */ + /* data is pinned by default - if we start moving packet audio, + the semantics will determine whether or not data is movable + but the handle will remain movable in either case */ + size_t delta = ringbuf_sub(h->ridx, h->data); + + /* The value of delta might change for alignment reasons */ + if (!move_handle(&h, &delta, 0, true)) + return; + + h->data = ringbuf_add(h->data, delta); + h->available -= delta; + h->offset += delta; + } else { /* metadata handle: we can move all of it */ - if (!h->next || h->filerem != 0) - return; /* Last handle or not finished loading */ + if (h->pinned || !h->next || h->filerem != 0) + return; /* Pinned, last handle or not finished loading */ uintptr_t handle_distance = ringbuf_sub(ringbuf_offset(h->next), h->data); - delta = handle_distance - h->available; + size_t delta = handle_distance - h->available; /* The value of delta might change for alignment reasons */ if (!move_handle(&h, &delta, h->available, h->type==TYPE_CODEC)) @@ -806,15 +825,6 @@ static void shrink_handle(struct memory_handle *h) struct bitmap *bmp = (struct bitmap *)&buffer[h->data]; bmp->data = &buffer[h->data + sizeof(struct bitmap)]; } - } else { - /* only move the handle struct */ - delta = ringbuf_sub(h->ridx, h->data); - if (!move_handle(&h, &delta, 0, true)) - return; - - h->data = ringbuf_add(h->data, delta); - h->available -= delta; - h->offset += delta; } } @@ -962,6 +972,8 @@ int bufopen(const char *file, size_t offset, enum data_type type, mutex_unlock(&llist_mutex); return handle_id; } + else if (type == TYPE_UNKNOWN) + return ERR_UNSUPPORTED_TYPE; #ifdef APPLICATION /* loading code from memory is not supported in application builds */ else if (type == TYPE_CODEC) @@ -1083,7 +1095,12 @@ int bufopen(const char *file, size_t offset, enum data_type type, */ int bufalloc(const void *src, size_t size, enum data_type type) { - int handle_id = ERR_BUFFER_FULL; + int handle_id; + + if (type == TYPE_UNKNOWN) + return ERR_UNSUPPORTED_TYPE; + + handle_id = ERR_BUFFER_FULL; mutex_lock(&llist_mutex); @@ -1124,7 +1141,14 @@ int bufalloc(const void *src, size_t size, enum data_type type) bool bufclose(int handle_id) { logf("bufclose(%d)", handle_id); - +#if 0 + /* Don't interrupt the buffering thread if the handle is already + stale */ + if (!find_handle(handle_id)) { + logf(" handle already closed"); + return true; + } +#endif LOGFQUEUE("buffering >| Q_CLOSE_HANDLE %d", handle_id); return queue_send(&buffering_queue, Q_CLOSE_HANDLE, handle_id); } @@ -1236,9 +1260,10 @@ static int seek_handle(struct memory_handle *h, size_t newpos) /* Set reading index in handle (relatively to the start of the file). Access before the available data will trigger a rebuffer. - Return 0 for success and < 0 for failure: - -1 if the handle wasn't found - -2 if the new requested position was beyond the end of the file + Return 0 for success and for failure: + ERR_HANDLE_NOT_FOUND if the handle wasn't found + ERR_INVALID_VALUE if the new requested position was beyond the end of + the file */ int bufseek(int handle_id, size_t newpos) { @@ -1250,7 +1275,11 @@ int bufseek(int handle_id, size_t newpos) } /* Advance the reading index in a handle (relatively to its current position). - Return 0 for success and < 0 for failure */ + Return 0 for success and for failure: + ERR_HANDLE_NOT_FOUND if the handle wasn't found + ERR_INVALID_VALUE if the new requested position was beyond the end of + the file + */ int bufadvance(int handle_id, off_t offset) { struct memory_handle *h = find_handle(handle_id); @@ -1261,6 +1290,18 @@ int bufadvance(int handle_id, off_t offset) return seek_handle(h, newpos); } +/* Get the read position from the start of the file + Returns the offset from byte 0 of the file and for failure: + ERR_HANDLE_NOT_FOUND if the handle wasn't found + */ +off_t bufftell(int handle_id) +{ + const struct memory_handle *h = find_handle(handle_id); + if (!h) + return ERR_HANDLE_NOT_FOUND; + return h->offset + ringbuf_sub(h->ridx, h->data); +} + /* Used by bufread and bufgetdata to prepare the buffer and retrieve the * actual amount of data available for reading. This function explicitly * does not check the validity of the input handle. It does do range checks @@ -1306,7 +1347,7 @@ static struct memory_handle *prep_bufdata(int handle_id, size_t *size, /* it is not safe for a non-buffering thread to sleep while * holding a handle */ h = find_handle(handle_id); - if (!h) + if (!h || h->signaled != 0) return NULL; avail = handle_size_available(h); } @@ -1447,9 +1488,14 @@ SECONDARY EXPORTED FUNCTIONS buf_handle_offset buf_request_buffer_handle buf_set_base_handle +buf_handle_data_type +buf_is_handle +buf_pin_handle +buf_signal_handle +buf_length buf_used -register_buffering_callback -unregister_buffering_callback +buf_set_watermark +buf_get_watermark These functions are exported, to allow interaction with the buffer. They take care of the content of the structs, and rely on the linked list @@ -1472,8 +1518,61 @@ void buf_request_buffer_handle(int handle_id) void buf_set_base_handle(int handle_id) { - LOGFQUEUE("buffering > Q_BASE_HANDLE %d", handle_id); - queue_post(&buffering_queue, Q_BASE_HANDLE, handle_id); + mutex_lock(&llist_mutex); + base_handle_id = handle_id; + mutex_unlock(&llist_mutex); +} + +enum data_type buf_handle_data_type(int handle_id) +{ + const struct memory_handle *h = find_handle(handle_id); + if (!h) + return TYPE_UNKNOWN; + return h->type; +} + +ssize_t buf_handle_remaining(int handle_id) +{ + const struct memory_handle *h = find_handle(handle_id); + if (!h) + return ERR_HANDLE_NOT_FOUND; + return h->filerem; +} + +bool buf_is_handle(int handle_id) +{ + return find_handle(handle_id) != NULL; +} + +bool buf_pin_handle(int handle_id, bool pin) +{ + struct memory_handle *h = find_handle(handle_id); + if (!h) + return false; + + if (pin) { + h->pinned++; + } else if (h->pinned > 0) { + h->pinned--; + } + + return true; +} + +bool buf_signal_handle(int handle_id, bool signal) +{ + struct memory_handle *h = find_handle(handle_id); + if (!h) + return false; + + h->signaled = signal ? 1 : 0; + return true; +} + +/* Return the size of the ringbuffer */ +size_t buf_length(void) +{ + return buffer_len; } /* Return the amount of buffer space used */ @@ -1487,6 +1586,21 @@ void buf_set_watermark(size_t bytes) conf_watermark = bytes; } +size_t buf_get_watermark(void) +{ + return BUF_WATERMARK; +} + +#ifdef HAVE_IO_PRIORITY +void buf_back_off_storage(bool back_off) +{ + int priority = back_off ? + IO_PRIORITY_BACKGROUND : IO_PRIORITY_IMMEDIATE; + thread_set_io_priority(buffering_thread_id, priority); +} +#endif + +/** -- buffer thread helpers -- **/ static void shrink_buffer_inner(struct memory_handle *h) { if (h == NULL) @@ -1503,7 +1617,7 @@ static void shrink_buffer(void) shrink_buffer_inner(first_handle); } -void buffering_thread(void) +static void NORETURN_ATTR buffering_thread(void) { bool filling = false; struct queue_event ev; @@ -1511,19 +1625,21 @@ void buffering_thread(void) while (true) { - if (!filling) { + if (num_handles > 0) { + if (!filling) { + cancel_cpu_boost(); + } + queue_wait_w_tmo(&buffering_queue, &ev, filling ? 1 : HZ/2); + } else { + filling = false; cancel_cpu_boost(); + queue_wait(&buffering_queue, &ev); } - queue_wait_w_tmo(&buffering_queue, &ev, filling ? 5 : HZ/2); - switch (ev.id) { case Q_START_FILL: LOGFQUEUE("buffering < Q_START_FILL %d", (int)ev.data); - /* Call buffer callbacks here because this is one of two ways - * to begin a full buffer fill */ - send_event(BUFFER_EVENT_BUFFER_LOW, 0); shrink_buffer(); queue_reply(&buffering_queue, 1); filling |= buffer_handle((int)ev.data, 0); @@ -1553,36 +1669,21 @@ void buffering_thread(void) filling = true; break; - case Q_BASE_HANDLE: - LOGFQUEUE("buffering < Q_BASE_HANDLE %d", (int)ev.data); - base_handle_id = (int)ev.data; - break; - -#if (CONFIG_PLATFORM & PLATFORM_NATIVE) - case SYS_USB_CONNECTED: - LOGFQUEUE("buffering < SYS_USB_CONNECTED"); - usb_acknowledge(SYS_USB_CONNECTED_ACK); - usb_wait_for_disconnect(&buffering_queue); - break; -#endif - case SYS_TIMEOUT: LOGFQUEUE_SYS_TIMEOUT("buffering < SYS_TIMEOUT"); break; } - update_data_counters(NULL); - - /* If the buffer is low, call the callbacks to get new data */ - if (num_handles > 0 && data_counters.useful <= conf_watermark) - send_event(BUFFER_EVENT_BUFFER_LOW, 0); + if (num_handles == 0 || !queue_empty(&buffering_queue)) + continue; + update_data_counters(NULL); #if 0 /* TODO: This needs to be fixed to use the idle callback, disable it * for simplicity until its done right */ #if MEMORYSIZE > 8 /* If the disk is spinning, take advantage by filling the buffer */ - else if (storage_disk_is_active() && queue_empty(&buffering_queue)) { + else if (storage_disk_is_active()) { if (num_handles > 0 && data_counters.useful <= high_watermark) send_event(BUFFER_EVENT_BUFFER_LOW, 0); @@ -1597,15 +1698,23 @@ void buffering_thread(void) #endif #endif - if (queue_empty(&buffering_queue)) { - if (filling) { - if (data_counters.remaining > 0 && BUF_USED < buffer_len) - filling = fill_buffer(); - else if (data_counters.remaining == 0) - filling = false; - } else if (ev.id == SYS_TIMEOUT) { - if (data_counters.remaining > 0 && - data_counters.useful <= conf_watermark) { + if (filling) { + if (data_counters.remaining > 0 && BUF_USED < buffer_len) { + filling = fill_buffer(); + } + else if (data_counters.remaining == 0) { + filling = false; + } + } else if (ev.id == SYS_TIMEOUT) { + if (data_counters.useful < BUF_WATERMARK) { + /* The buffer is low and we're idle, just watching the levels + - call the callbacks to get new data */ + send_event(BUFFER_EVENT_BUFFER_LOW, NULL); + + /* Continue anything else we haven't finished - it might + get booted off or stop early because the receiver hasn't + had a chance to clear anything yet */ + if (data_counters.remaining > 0) { shrink_buffer(); filling = fill_buffer(); } @@ -1618,9 +1727,14 @@ void buffering_init(void) { mutex_init(&llist_mutex); - conf_watermark = BUFFERING_DEFAULT_WATERMARK; - - queue_init(&buffering_queue, true); + /* Thread should absolutely not respond to USB because if it waits first, + then it cannot properly service the handles and leaks will happen - + this is a worker thread and shouldn't need to care about any system + notifications. + *** + Whoever is using buffering should be responsible enough to clear all + the handles at the right time. */ + queue_init(&buffering_queue, false); buffering_thread_id = create_thread( buffering_thread, buffering_stack, sizeof(buffering_stack), CREATE_THREAD_FROZEN, buffering_thread_name IF_PRIO(, PRIORITY_BUFFERING) @@ -1636,6 +1750,9 @@ bool buffering_reset(char *buf, size_t buflen) /* Wraps of storage-aligned data must also be storage aligned, thus buf and buflen must be a aligned to an integer multiple of the storage alignment */ + + buflen -= GUARD_BUFSIZE; + STORAGE_ALIGN_BUFFER(buf, buflen); if (!buf || !buflen) @@ -1654,10 +1771,13 @@ bool buffering_reset(char *buf, size_t buflen) num_handles = 0; base_handle_id = -1; - /* Set the high watermark as 75% full...or 25% empty :) */ -#if MEMORYSIZE > 8 + /* Set the high watermark as 75% full...or 25% empty :) + This is the greatest fullness that will trigger low-buffer events + no matter what the setting because high-bitrate files can have + ludicrous margins that even exceed the buffer size - most common + with a huge anti-skip buffer but even without that setting, + staying constantly active in buffering is pointless */ high_watermark = 3*buflen / 4; -#endif thread_thaw(buffering_thread_id); @@ -1673,5 +1793,5 @@ void buffering_get_debugdata(struct buffering_debug *dbgdata) dbgdata->wasted_space = dc.wasted; dbgdata->buffered_data = dc.buffered; dbgdata->useful_data = dc.useful; - dbgdata->watermark = conf_watermark; + dbgdata->watermark = BUF_WATERMARK; } diff --git a/apps/buffering.h b/apps/buffering.h index 34d6d641c7..2e4cfd3968 100644 --- a/apps/buffering.h +++ b/apps/buffering.h @@ -28,14 +28,13 @@ enum data_type { + TYPE_UNKNOWN = 0, /* invalid type indicator */ + TYPE_ID3, TYPE_CODEC, TYPE_PACKET_AUDIO, TYPE_ATOMIC_AUDIO, - TYPE_ID3, TYPE_CUESHEET, TYPE_BITMAP, - TYPE_BUFFER, - TYPE_UNKNOWN, }; /* Error return values */ @@ -63,6 +62,7 @@ bool buffering_reset(char *buf, size_t buflen); * bufclose : Close an open handle * bufseek : Set handle reading index, relatively to the start of the file * bufadvance: Move handle reading index, relatively to current position + * bufftell : Return the handle's file read position * bufread : Copy data from a handle to a buffer * bufgetdata: Obtain a pointer for linear access to a "size" amount of data * bufgettail: Out-of-band get the last size bytes of a handle. @@ -81,28 +81,40 @@ int bufalloc(const void *src, size_t size, enum data_type type); bool bufclose(int handle_id); int bufseek(int handle_id, size_t newpos); int bufadvance(int handle_id, off_t offset); +off_t bufftell(int handle_id); ssize_t bufread(int handle_id, size_t size, void *dest); ssize_t bufgetdata(int handle_id, size_t size, void **data); ssize_t bufgettail(int handle_id, size_t size, void **data); ssize_t bufcuttail(int handle_id, size_t size); - /*************************************************************************** * SECONDARY FUNCTIONS * =================== * + * buf_handle_data_type: return the handle's data type + * buf_is_handle: is the handle valid? + * buf_pin_handle: Disallow/allow handle movement. Handle may still be removed. * buf_handle_offset: Get the offset of the first buffered byte from the file * buf_request_buffer_handle: Request buffering of a handle * buf_set_base_handle: Tell the buffering thread which handle is currently read + * buf_length: Total size of ringbuffer * buf_used: Total amount of buffer space used (including allocated space) + * buf_back_off_storage: tell buffering thread to take it easy ****************************************************************************/ +enum data_type buf_handle_data_type(int handle_id); +ssize_t buf_handle_remaining(int handle_id); +bool buf_is_handle(int handle_id); ssize_t buf_handle_offset(int handle_id); void buf_request_buffer_handle(int handle_id); void buf_set_base_handle(int handle_id); +size_t buf_length(void); size_t buf_used(void); - - +bool buf_pin_handle(int handle_id, bool pin); +bool buf_signal_handle(int handle_id, bool signal); +#ifdef HAVE_IO_PRIORITY +void buf_back_off_storage(bool back_off); +#endif /* Settings */ enum { @@ -110,6 +122,7 @@ enum { BUFFERING_SET_CHUNKSIZE, }; void buf_set_watermark(size_t bytes); +size_t buf_get_watermark(void); /* Debugging */ struct buffering_debug { diff --git a/apps/codec_thread.c b/apps/codec_thread.c index 65a7ebc7d5..7cf45c3490 100644 --- a/apps/codec_thread.c +++ b/apps/codec_thread.c @@ -9,6 +9,7 @@ * * Copyright (C) 2005-2007 Miika Pekkarinen * Copyright (C) 2007-2008 Nicolas Pennequin + * Copyright (C) 2011 Michael Sevakis * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -21,16 +22,14 @@ ****************************************************************************/ #include "config.h" #include "system.h" -#include "playback.h" -#include "codec_thread.h" #include "kernel.h" #include "codecs.h" -#include "buffering.h" +#include "codec_thread.h" #include "pcmbuf.h" +#include "playback.h" +#include "buffering.h" #include "dsp.h" -#include "abrepeat.h" #include "metadata.h" -#include "splash.h" /* Define LOGF_ENABLE to enable logf output in this file */ /*#define LOGF_ENABLE*/ @@ -57,38 +56,45 @@ #define LOGFQUEUE_SYS_TIMEOUT(...) #endif - /* Variables are commented with the threads that use them: - * A=audio, C=codec, V=voice. A suffix of - indicates that - * the variable is read but not updated on that thread. - + * A=audio, C=codec + * - = reads only + * * Unless otherwise noted, the extern variables are located * in playback.c. */ -/* Main state control */ - -/* Type of codec loaded? (C/A) */ -static int current_codectype SHAREDBSS_ATTR = AFMT_UNKNOWN; +/* Q_LOAD_CODEC parameter data */ +struct codec_load_info +{ + int hid; /* audio handle id (specify < 0 to use afmt) */ + int afmt; /* codec specification (AFMT_*) */ +}; -extern struct mp3entry *thistrack_id3, /* the currently playing track */ - *othertrack_id3; /* prev track during track-change-transition, or end of playlist, - * next track otherwise */ -/* Track change controls */ -extern struct event_queue audio_queue SHAREDBSS_ATTR; +/** --- Main state control --- **/ +static int codec_type = AFMT_UNKNOWN; /* Codec type (C,A-) */ +/* Private interfaces to main playback control */ +extern void audio_codec_update_elapsed(unsigned long value); +extern void audio_codec_update_offset(size_t value); +extern void audio_queue_post(long id, intptr_t data); extern struct codec_api ci; /* from codecs.c */ /* Codec thread */ static unsigned int codec_thread_id; /* For modifying thread priority later */ static struct event_queue codec_queue SHAREDBSS_ATTR; static struct queue_sender_list codec_queue_sender_list SHAREDBSS_ATTR; -static long codec_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)] - IBSS_ATTR; +static long codec_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)] IBSS_ATTR; static const char codec_thread_name[] = "codec"; +static void unload_codec(void); + +/* Messages are only ever sent one at a time to the codec from the audio + thread. This is important for correct operation unless playback is + stopped. */ + /* static routines */ static void codec_queue_ack(intptr_t ackme) { @@ -100,52 +106,63 @@ static intptr_t codec_queue_send(long id, intptr_t data) return queue_send(&codec_queue, id, data); } -/**************************************/ +/* Poll the state of the codec queue. Returns < 0 if the message is urgent + and any state should exit, > 0 if it's a run message (and it was + scrubbed), 0 if message was ignored. */ +static int codec_check_queue__have_msg(void) +{ + struct queue_event ev; -/** misc external functions */ + queue_peek(&codec_queue, &ev); -/* Used to check whether a new codec must be loaded. See array audio_formats[] - * in metadata.c */ -int get_codec_base_type(int type) -{ - int base_type = type; - switch (type) { - case AFMT_MPA_L1: - case AFMT_MPA_L2: - case AFMT_MPA_L3: - base_type = AFMT_MPA_L3; - break; - case AFMT_MPC_SV7: - case AFMT_MPC_SV8: - base_type = AFMT_MPC_SV7; - break; - case AFMT_MP4_AAC: - case AFMT_MP4_AAC_HE: - base_type = AFMT_MP4_AAC; - break; - case AFMT_SAP: - case AFMT_CMC: - case AFMT_CM3: - case AFMT_CMR: - case AFMT_CMS: - case AFMT_DMC: - case AFMT_DLT: - case AFMT_MPT: - case AFMT_MPD: - case AFMT_RMT: - case AFMT_TMC: - case AFMT_TM8: - case AFMT_TM2: - base_type = AFMT_SAP; - break; - default: - break; + /* Seek, pause or stop? Just peek and return if so. Codec + must handle the command after returing. Inserts will not + be allowed until it complies. */ + switch (ev.id) + { + case Q_CODEC_SEEK: + LOGFQUEUE("codec - Q_CODEC_SEEK", ev.id); + return -1; + case Q_CODEC_PAUSE: + LOGFQUEUE("codec - Q_CODEC_PAUSE", ev.id); + return -1; + case Q_CODEC_STOP: + LOGFQUEUE("codec - Q_CODEC_STOP", ev.id); + return -1; } - return base_type; + /* This is in error in this context unless it's "go, go, go!" */ + queue_wait(&codec_queue, &ev); + + if (ev.id == Q_CODEC_RUN) + { + logf("codec < Q_CODEC_RUN: already running!"); + codec_queue_ack(Q_CODEC_RUN); + return 1; + } + + /* Ignore it */ + logf("codec < bad req %ld (%s)", ev.id, __func__); + codec_queue_ack(Q_NULL); + return 0; +} + +/* Does the audio format type equal CODEC_TYPE_ENCODER? */ +static inline bool type_is_encoder(int afmt) +{ +#ifdef AUDIO_HAVE_RECORDING + return (afmt & CODEC_TYPE_MASK) == CODEC_TYPE_ENCODER; +#else + return false; + (void)afmt; +#endif } -const char *get_codec_filename(int cod_spec) +/**************************************/ + + +/** --- Miscellaneous external functions --- **/ +const char * get_codec_filename(int cod_spec) { const char *fname; @@ -173,7 +190,7 @@ const char *get_codec_filename(int cod_spec) #endif /* HAVE_RECORDING */ return fname; -} /* get_codec_filename */ +} /* Borrow the codec thread and return the ID */ void codec_thread_do_callback(void (*fn)(void), unsigned int *id) @@ -189,9 +206,9 @@ void codec_thread_do_callback(void (*fn)(void), unsigned int *id) } -/** codec API callbacks */ +/** --- codec API callbacks --- **/ -static void* codec_get_buffer(size_t *size) +static void * codec_get_buffer(size_t *size) { ssize_t s = CODEC_SIZE - codec_size; void *buf = &codecbuf[codec_size]; @@ -215,15 +232,19 @@ static void codec_pcmbuf_insert_callback( int inp_count; char *dest; - /* Prevent audio from a previous track from playing */ - if (ci.new_track || ci.stop_codec) - return; - - while ((dest = pcmbuf_request_buffer(&out_count)) == NULL) + while (1) { + if ((dest = pcmbuf_request_buffer(&out_count)) != NULL) + break; + cancel_cpu_boost(); - sleep(1); - if (ci.seek_time || ci.new_track || ci.stop_codec) + + /* It will be awhile before space is available but we want + "instant" response to any message */ + queue_wait_w_tmo(&codec_queue, NULL, HZ/20); + + if (!queue_empty(&codec_queue) && + codec_check_queue__have_msg() < 0) return; } @@ -247,62 +268,28 @@ static void codec_pcmbuf_insert_callback( count -= inp_count; } -} /* codec_pcmbuf_insert_callback */ +} -static void codec_set_elapsed_callback(unsigned long value) +/* helper function, not a callback */ +static bool codec_advance_buffer_counters(size_t amount) { - if (ci.seek_time) - return; - -#ifdef AB_REPEAT_ENABLE - ab_position_report(value); -#endif - - unsigned long latency = pcmbuf_get_latency(); - if (value < latency) - thistrack_id3->elapsed = 0; - else + if (bufadvance(ci.audio_hid, amount) < 0) { - unsigned long elapsed = value - latency; - if (elapsed > thistrack_id3->elapsed || - elapsed < thistrack_id3->elapsed - 2) - { - thistrack_id3->elapsed = elapsed; - } + ci.curpos = ci.filesize; + return false; } -} - -static void codec_set_offset_callback(size_t value) -{ - if (ci.seek_time) - return; - unsigned long latency = pcmbuf_get_latency() * thistrack_id3->bitrate / 8; - if (value < latency) - thistrack_id3->offset = 0; - else - thistrack_id3->offset = value - latency; -} - -/* helper function, not a callback */ -static void codec_advance_buffer_counters(size_t amount) -{ - bufadvance(get_audio_hid(), amount); ci.curpos += amount; + return true; } /* copy up-to size bytes into ptr and return the actual size copied */ static size_t codec_filebuf_callback(void *ptr, size_t size) { - ssize_t copy_n; - - if (ci.stop_codec) - return 0; - - copy_n = bufread(get_audio_hid(), size, ptr); + ssize_t copy_n = bufread(ci.audio_hid, size, ptr); /* Nothing requested OR nothing left */ - if (copy_n == 0) + if (copy_n <= 0) return 0; /* Update read and other position pointers */ @@ -310,15 +297,15 @@ static size_t codec_filebuf_callback(void *ptr, size_t size) /* Return the actual amount of data copied to the buffer */ return copy_n; -} /* codec_filebuf_callback */ +} -static void* codec_request_buffer_callback(size_t *realsize, size_t reqsize) +static void * codec_request_buffer_callback(size_t *realsize, size_t reqsize) { size_t copy_n = reqsize; ssize_t ret; void *ptr; - ret = bufgetdata(get_audio_hid(), reqsize, &ptr); + ret = bufgetdata(ci.audio_hid, reqsize, &ptr); if (ret >= 0) copy_n = MIN((size_t)ret, reqsize); else @@ -329,101 +316,103 @@ static void* codec_request_buffer_callback(size_t *realsize, size_t reqsize) *realsize = copy_n; return ptr; -} /* codec_request_buffer_callback */ +} static void codec_advance_buffer_callback(size_t amount) { - codec_advance_buffer_counters(amount); - codec_set_offset_callback(ci.curpos); + if (!codec_advance_buffer_counters(amount)) + return; + + audio_codec_update_offset(ci.curpos); } static bool codec_seek_buffer_callback(size_t newpos) { logf("codec_seek_buffer_callback"); - int ret = bufseek(get_audio_hid(), newpos); - if (ret == 0) { + int ret = bufseek(ci.audio_hid, newpos); + if (ret == 0) + { ci.curpos = newpos; return true; } - else { - return false; - } + + return false; } static void codec_seek_complete_callback(void) { - struct queue_event ev; - logf("seek_complete"); /* Clear DSP */ dsp_configure(ci.dsp, DSP_FLUSH, 0); /* Post notification to audio thread */ - LOGFQUEUE("audio > Q_AUDIO_SEEK_COMPLETE"); - queue_post(&audio_queue, Q_AUDIO_SEEK_COMPLETE, 0); - - /* Wait for ACK */ - queue_wait(&codec_queue, &ev); + LOGFQUEUE("audio > Q_AUDIO_CODEC_SEEK_COMPLETE"); + audio_queue_post(Q_AUDIO_CODEC_SEEK_COMPLETE, 0); - /* ACK back in context */ - codec_queue_ack(Q_AUDIO_SEEK_COMPLETE); + /* Wait for urgent or go message */ + do + { + queue_wait(&codec_queue, NULL); + } + while (codec_check_queue__have_msg() == 0); } -static bool codec_request_next_track_callback(void) +static void codec_configure_callback(int setting, intptr_t value) { - struct queue_event ev; - - logf("Request new track"); + if (!dsp_configure(ci.dsp, setting, value)) + { + logf("Illegal key: %d", setting); + } +} - audio_set_prev_elapsed(thistrack_id3->elapsed); +static enum codec_command_action + codec_get_command_callback(intptr_t *param) +{ + yield(); -#ifdef AB_REPEAT_ENABLE - ab_end_of_track_report(); -#endif + if (LIKELY(queue_empty(&codec_queue))) + return CODEC_ACTION_NULL; /* As you were */ - if (ci.stop_codec) + /* Process the message - return requested action and data (if any should + be expected) */ + while (1) { - /* Handle ACK in outer loop */ - LOGFQUEUE("codec: already stopping"); - return false; - } + enum codec_command_action action = CODEC_ACTION_NULL; + struct queue_event ev; + queue_wait(&codec_queue, &ev); - trigger_cpu_boost(); + switch (ev.id) + { + case Q_CODEC_RUN: /* Already running */ + LOGFQUEUE("codec < Q_CODEC_RUN"); + break; - /* Post request to audio thread */ - LOGFQUEUE("codec > audio Q_AUDIO_CHECK_NEW_TRACK"); - queue_post(&audio_queue, Q_AUDIO_CHECK_NEW_TRACK, 0); + case Q_CODEC_PAUSE: /* Stay here and wait */ + LOGFQUEUE("codec < Q_CODEC_PAUSE"); + codec_queue_ack(Q_CODEC_PAUSE); + continue; - /* Wait for ACK */ - queue_wait(&codec_queue, &ev); + case Q_CODEC_SEEK: /* Audio wants codec to seek */ + LOGFQUEUE("codec < Q_CODEC_SEEK %ld", ev.data); + *param = ev.data; + action = CODEC_ACTION_SEEK_TIME; + break; - if (ev.data == Q_CODEC_REQUEST_COMPLETE) - { - /* Seek to the beginning of the new track because if the struct - mp3entry was buffered, "elapsed" might not be zero (if the track has - been played already but not unbuffered) */ - codec_seek_buffer_callback(thistrack_id3->first_frame_offset); - } + case Q_CODEC_STOP: /* Must only return 0 in main loop */ + LOGFQUEUE("codec < Q_CODEC_STOP"); + action = CODEC_ACTION_HALT; + break; - /* ACK back in context */ - codec_queue_ack(Q_AUDIO_CHECK_NEW_TRACK); + default: /* This is in error in this context. */ + ev.id = Q_NULL; + logf("codec bad req %ld (%s)", ev.id, __func__); + } - if (ev.data != Q_CODEC_REQUEST_COMPLETE || ci.stop_codec) - { - LOGFQUEUE("codec <= request failed (%d)", ev.data); - return false; + codec_queue_ack(ev.id); + return action; } - - LOGFQUEUE("codec <= Q_CODEC_REQEST_COMPLETE"); - return true; -} - -static void codec_configure_callback(int setting, intptr_t value) -{ - if (!dsp_configure(ci.dsp, setting, value)) - { logf("Illegal key:%d", setting); } } /* Initialize codec API */ @@ -433,119 +422,215 @@ void codec_init_codec_api(void) CODEC_IDX_AUDIO); ci.codec_get_buffer = codec_get_buffer; ci.pcmbuf_insert = codec_pcmbuf_insert_callback; - ci.set_elapsed = codec_set_elapsed_callback; + ci.set_elapsed = audio_codec_update_elapsed; ci.read_filebuf = codec_filebuf_callback; ci.request_buffer = codec_request_buffer_callback; ci.advance_buffer = codec_advance_buffer_callback; ci.seek_buffer = codec_seek_buffer_callback; ci.seek_complete = codec_seek_complete_callback; - ci.request_next_track = codec_request_next_track_callback; - ci.set_offset = codec_set_offset_callback; + ci.set_offset = audio_codec_update_offset; ci.configure = codec_configure_callback; + ci.get_command = codec_get_command_callback; } -/* track change */ +/** --- CODEC THREAD --- **/ -/** CODEC THREAD */ -static void codec_thread(void) +/* Handle Q_CODEC_LOAD */ +static void load_codec(const struct codec_load_info *ev_data) { - struct queue_event ev; + int status = CODEC_ERROR; + /* Save a local copy so we can let the audio thread go ASAP */ + struct codec_load_info data = *ev_data; + bool const encoder = type_is_encoder(data.afmt); + if (codec_type != AFMT_UNKNOWN) + { + /* Must have unloaded it first */ + logf("a codec is already loaded"); + if (data.hid >= 0) + bufclose(data.hid); + return; + } - while (1) + trigger_cpu_boost(); + + if (!encoder) { - int status = CODEC_OK; - void *handle = NULL; - int hid; - const char *codec_fn; - -#ifdef HAVE_CROSSFADE - if (!pcmbuf_is_crossfade_active()) -#endif + /* Do this now because codec may set some things up at load time */ + dsp_configure(ci.dsp, DSP_RESET, 0); + } + + if (data.hid >= 0) + { + /* First try buffer load */ + status = codec_load_buf(data.hid, &ci); + bufclose(data.hid); + } + + if (status < 0) + { + /* Either not a valid handle or the buffer method failed */ + const char *codec_fn = get_codec_filename(data.afmt); + if (codec_fn) { - cancel_cpu_boost(); +#ifdef HAVE_IO_PRIORITY + buf_back_off_storage(true); +#endif + status = codec_load_file(codec_fn, &ci); +#ifdef HAVE_IO_PRIORITY + buf_back_off_storage(false); +#endif } + } + + if (status >= 0) + { + codec_type = data.afmt; + codec_queue_ack(Q_CODEC_LOAD); + return; + } + + /* Failed - get rid of it */ + unload_codec(); +} + +/* Handle Q_CODEC_RUN */ +static void run_codec(void) +{ + bool const encoder = type_is_encoder(codec_type); + int status; + + if (codec_type == AFMT_UNKNOWN) + { + logf("no codec to run"); + return; + } + + codec_queue_ack(Q_CODEC_RUN); + + trigger_cpu_boost(); + + if (!encoder) + { + /* This will be either the initial buffered offset or where it left off + if it remained buffered and we're skipping back to it and it is best + to have ci.curpos in sync with the handle's read position - it's the + codec's responsibility to ensure it has the correct positions - + playback is sorta dumb and only has a vague idea about what to + buffer based upon what metadata has to say */ + ci.curpos = bufftell(ci.audio_hid); + + /* Pin the codec's audio data in place */ + buf_pin_handle(ci.audio_hid, true); + } + + status = codec_run_proc(); + + if (!encoder) + { + /* Codec is done with it - let it move */ + buf_pin_handle(ci.audio_hid, false); + + /* Notify audio that we're done for better or worse - advise of the + status */ + LOGFQUEUE("codec > audio Q_AUDIO_CODEC_COMPLETE: %d", status); + audio_queue_post(Q_AUDIO_CODEC_COMPLETE, status); + } +} + +/* Handle Q_CODEC_SEEK */ +static void seek_codec(unsigned long time) +{ + if (codec_type == AFMT_UNKNOWN) + { + logf("no codec to seek"); + codec_queue_ack(Q_CODEC_SEEK); + codec_seek_complete_callback(); + return; + } + + /* Post it up one level */ + queue_post(&codec_queue, Q_CODEC_SEEK, time); + codec_queue_ack(Q_CODEC_SEEK); + + /* Have to run it again */ + run_codec(); +} + +/* Handle Q_CODEC_UNLOAD */ +static void unload_codec(void) +{ + /* Tell codec to clean up */ + codec_type = AFMT_UNKNOWN; + codec_close(); +} + +/* Handle Q_CODEC_DO_CALLBACK */ +static void do_callback(void (* callback)(void)) +{ + codec_queue_ack(Q_CODEC_DO_CALLBACK); + + if (callback) + { + cpucache_commit_discard(); + callback(); + cpucache_commit(); + } +} + +/* Codec thread function */ +static void NORETURN_ATTR codec_thread(void) +{ + struct queue_event ev; + + while (1) + { + cancel_cpu_boost(); queue_wait(&codec_queue, &ev); switch (ev.id) { - case Q_CODEC_LOAD_DISK: - LOGFQUEUE("codec < Q_CODEC_LOAD_DISK"); - codec_fn = get_codec_filename(ev.data); - if (!codec_fn) - break; -#ifdef AUDIO_HAVE_RECORDING - if (ev.data & CODEC_TYPE_ENCODER) - { - ev.id = Q_ENCODER_LOAD_DISK; - handle = codec_load_file(codec_fn, &ci); - if (handle) - codec_queue_ack(Q_ENCODER_LOAD_DISK); - } - else -#endif - { - codec_queue_ack(Q_CODEC_LOAD_DISK); - handle = codec_load_file(codec_fn, &ci); - } - break; + case Q_CODEC_LOAD: + LOGFQUEUE("codec < Q_CODEC_LOAD"); + load_codec((const struct codec_load_info *)ev.data); + break; - case Q_CODEC_LOAD: - LOGFQUEUE("codec < Q_CODEC_LOAD"); - codec_queue_ack(Q_CODEC_LOAD); - hid = (int)ev.data; - handle = codec_load_buf(hid, &ci); - bufclose(hid); - break; + case Q_CODEC_RUN: + LOGFQUEUE("codec < Q_CODEC_RUN"); + run_codec(); + break; - case Q_CODEC_DO_CALLBACK: - LOGFQUEUE("codec < Q_CODEC_DO_CALLBACK"); - codec_queue_ack(Q_CODEC_DO_CALLBACK); - if ((void*)ev.data != NULL) - { - cpucache_commit_discard(); - ((void (*)(void))ev.data)(); - cpucache_commit(); - } - break; + case Q_CODEC_PAUSE: + LOGFQUEUE("codec < Q_CODEC_PAUSE"); + break; - default: - LOGFQUEUE("codec < default : %ld", ev.id); - } + case Q_CODEC_SEEK: + LOGFQUEUE("codec < Q_CODEC_SEEK: %lu", (unsigned long)ev.data); + seek_codec(ev.data); + break; - if (handle) - { - /* Codec loaded - call the entrypoint */ - yield(); - logf("codec running"); - status = codec_begin(handle); - logf("codec stopped"); - codec_close(handle); - current_codectype = AFMT_UNKNOWN; - - if (ci.stop_codec) - status = CODEC_OK; - } + case Q_CODEC_UNLOAD: + LOGFQUEUE("codec < Q_CODEC_UNLOAD"); + unload_codec(); + break; - switch (ev.id) - { -#ifdef AUDIO_HAVE_RECORDING - case Q_ENCODER_LOAD_DISK: -#endif - case Q_CODEC_LOAD_DISK: - case Q_CODEC_LOAD: - /* Notify about the status */ - if (!handle) - status = CODEC_ERROR; - LOGFQUEUE("codec > audio notify status: %d", status); - queue_post(&audio_queue, ev.id, status); - break; + case Q_CODEC_DO_CALLBACK: + LOGFQUEUE("codec < Q_CODEC_DO_CALLBACK"); + do_callback((void (*)(void))ev.data); + break; + + default: + LOGFQUEUE("codec < default : %ld", ev.id); } } } + +/** --- Miscellaneous external interfaces -- **/ + +/* Create the codec thread and init kernel objects */ void make_codec_thread(void) { queue_init(&codec_queue, false); @@ -558,78 +643,86 @@ void make_codec_thread(void) codec_thread_id); } +/* Unfreeze the codec thread */ void codec_thread_resume(void) { thread_thaw(codec_thread_id); } +/* Is the current thread the codec thread? */ bool is_codec_thread(void) { return thread_self() == codec_thread_id; } #ifdef HAVE_PRIORITY_SCHEDULING +/* Obtain codec thread's current priority */ int codec_thread_get_priority(void) { return thread_get_priority(codec_thread_id); } +/* Set the codec thread's priority and return the old value */ int codec_thread_set_priority(int priority) { return thread_set_priority(codec_thread_id, priority); } #endif /* HAVE_PRIORITY_SCHEDULING */ -/* functions for audio thread use */ -intptr_t codec_ack_msg(intptr_t data, bool stop_codec) -{ - intptr_t resp; - LOGFQUEUE("codec >| Q_CODEC_ACK: %d", data); - if (stop_codec) - ci.stop_codec = true; - resp = codec_queue_send(Q_CODEC_ACK, data); - if (stop_codec) - codec_stop(); - LOGFQUEUE(" ack: %ld", resp); - return resp; -} +/** --- Functions for audio thread use --- **/ + +/* Load a decoder or encoder and set the format type */ bool codec_load(int hid, int cod_spec) { - bool retval = false; + struct codec_load_info parm = { hid, cod_spec }; - ci.stop_codec = false; - current_codectype = cod_spec; + LOGFQUEUE("audio >| codec Q_CODEC_LOAD: %d, %d", hid, cod_spec); + return codec_queue_send(Q_CODEC_LOAD, (intptr_t)&parm) != 0; +} - if (hid >= 0) - { - LOGFQUEUE("audio >| codec Q_CODEC_LOAD: %d", hid); - retval = codec_queue_send(Q_CODEC_LOAD, hid) != Q_NULL; - } - else - { - LOGFQUEUE("audio >| codec Q_CODEC_LOAD_DISK: %d", cod_spec); - retval = codec_queue_send(Q_CODEC_LOAD_DISK, cod_spec) != Q_NULL; - } +/* Begin decoding the current file */ +void codec_go(void) +{ + LOGFQUEUE("audio >| codec Q_CODEC_RUN"); + codec_queue_send(Q_CODEC_RUN, 0); +} - if (!retval) - { - ci.stop_codec = true; - current_codectype = AFMT_UNKNOWN; - } +/* Instruct the codec to seek to the specified time (should be properly + paused or stopped first to avoid possible buffering deadlock) */ +void codec_seek(long time) +{ + LOGFQUEUE("audio > codec Q_CODEC_SEEK: %ld", time); + codec_queue_send(Q_CODEC_SEEK, time); +} - return retval; +/* Pause the codec and make it wait for further instructions inside the + command callback */ +bool codec_pause(void) +{ + LOGFQUEUE("audio >| codec Q_CODEC_PAUSE"); + return codec_queue_send(Q_CODEC_PAUSE, 0) != Q_NULL; } +/* Stop codec if running - codec stays resident if loaded */ void codec_stop(void) { - ci.stop_codec = true; /* Wait until it's in the main loop */ - while (codec_ack_msg(0, false) != Q_NULL); - current_codectype = AFMT_UNKNOWN; + LOGFQUEUE("audio >| codec Q_CODEC_STOP"); + while (codec_queue_send(Q_CODEC_STOP, 0) != Q_NULL); +} + +/* Call the codec's exit routine and close all references */ +void codec_unload(void) +{ + codec_stop(); + LOGFQUEUE("audio >| codec Q_CODEC_UNLOAD"); + codec_queue_send(Q_CODEC_UNLOAD, 0); } +/* Return the afmt type of the loaded codec - sticks until calling + codec_unload unless initial load failed */ int codec_loaded(void) { - return current_codectype; + return codec_type; } diff --git a/apps/codec_thread.h b/apps/codec_thread.h index 7056e2cdf5..acd7e556e2 100644 --- a/apps/codec_thread.h +++ b/apps/codec_thread.h @@ -25,7 +25,6 @@ #include /* codec identity */ -int get_codec_base_type(int type); const char *get_codec_filename(int cod_spec); /* codec thread */ @@ -44,10 +43,14 @@ int codec_thread_set_priority(int priority); #endif /* codec commands - on audio thread only! */ -intptr_t codec_ack_msg(intptr_t data, bool stop_codec); bool codec_load(int hid, int cod_spec); +void codec_go(void); +bool codec_pause(void); +void codec_seek(long time); void codec_stop(void); +void codec_unload(void); int codec_loaded(void); + /* */ #endif /* _CODEC_THREAD_H */ diff --git a/apps/codecs.c b/apps/codecs.c index 28e8c2a1c0..25ace4969b 100644 --- a/apps/codecs.c +++ b/apps/codecs.c @@ -79,13 +79,10 @@ static int open(const char* pathname, int flags, ...) #endif struct codec_api ci = { - 0, /* filesize */ - 0, /* curpos */ + 0, /* filesize */ + 0, /* curpos */ NULL, /* id3 */ - NULL, /* taginfo_ready */ - false, /* stop_codec */ - 0, /* new_track */ - 0, /* seek_time */ + ERR_HANDLE_NOT_FOUND, /* audio_hid */ NULL, /* struct dsp_config *dsp */ NULL, /* codec_get_buffer */ NULL, /* pcmbuf_insert */ @@ -95,9 +92,9 @@ struct codec_api ci = { NULL, /* advance_buffer */ NULL, /* seek_buffer */ NULL, /* seek_complete */ - NULL, /* request_next_track */ NULL, /* set_offset */ NULL, /* configure */ + NULL, /* get_command */ /* kernel/ system */ #if defined(CPU_ARM) && CONFIG_PLATFORM & PLATFORM_NATIVE @@ -174,10 +171,16 @@ void codec_get_full_path(char *path, const char *codec_root_fn) CODECS_DIR, codec_root_fn); } -static void * codec_load_ram(void *handle, struct codec_api *api) +/** codec loading and call interface **/ +static void *curr_handle = NULL; +static struct codec_header *c_hdr = NULL; + +static int codec_load_ram(struct codec_api *api) { - struct codec_header *c_hdr = lc_get_header(handle); - struct lc_header *hdr = c_hdr ? &c_hdr->lc_hdr : NULL; + struct lc_header *hdr; + + c_hdr = lc_get_header(curr_handle); + hdr = c_hdr ? &c_hdr->lc_hdr : NULL; if (hdr == NULL || (hdr->magic != CODEC_MAGIC @@ -193,15 +196,17 @@ static void * codec_load_ram(void *handle, struct codec_api *api) ) { logf("codec header error"); - lc_close(handle); - return NULL; + lc_close(curr_handle); + curr_handle = NULL; + return CODEC_ERROR; } if (hdr->api_version > CODEC_API_VERSION || hdr->api_version < CODEC_MIN_API_VERSION) { logf("codec api version error"); - lc_close(handle); - return NULL; + lc_close(curr_handle); + curr_handle = NULL; + return CODEC_ERROR; } #if (CONFIG_PLATFORM & PLATFORM_NATIVE) @@ -212,63 +217,66 @@ static void * codec_load_ram(void *handle, struct codec_api *api) *(c_hdr->api) = api; - return handle; + logf("Codec: calling entrypoint"); + return c_hdr->entry_point(CODEC_LOAD); } -void * codec_load_buf(int hid, struct codec_api *api) +int codec_load_buf(int hid, struct codec_api *api) { - int rc; - void *handle; - rc = bufread(hid, CODEC_SIZE, codecbuf); + int rc = bufread(hid, CODEC_SIZE, codecbuf); + if (rc < 0) { logf("Codec: cannot read buf handle"); - return NULL; + return CODEC_ERROR; } - handle = lc_open_from_mem(codecbuf, rc); + curr_handle = lc_open_from_mem(codecbuf, rc); - if (handle == NULL) { - logf("error loading codec"); - return NULL; + if (curr_handle == NULL) { + logf("Codec: load error"); + return CODEC_ERROR; } - return codec_load_ram(handle, api); + return codec_load_ram(api); } -void * codec_load_file(const char *plugin, struct codec_api *api) +int codec_load_file(const char *plugin, struct codec_api *api) { char path[MAX_PATH]; - void *handle; codec_get_full_path(path, plugin); - handle = lc_open(path, codecbuf, CODEC_SIZE); + curr_handle = lc_open(path, codecbuf, CODEC_SIZE); - if (handle == NULL) { + if (curr_handle == NULL) { logf("Codec: cannot read file"); - return NULL; + return CODEC_ERROR; } - return codec_load_ram(handle, api); + return codec_load_ram(api); } -int codec_begin(void *handle) +int codec_run_proc(void) { - int status = CODEC_ERROR; - struct codec_header *c_hdr; - - c_hdr = lc_get_header(handle); - - if (c_hdr != NULL) { - logf("Codec: calling entry_point"); - status = c_hdr->entry_point(); + if (curr_handle == NULL) { + logf("Codec: no codec to run"); + return CODEC_ERROR; } - return status; + logf("Codec: entering run state"); + return c_hdr->run_proc(); } -void codec_close(void *handle) +int codec_close(void) { - if (handle) - lc_close(handle); + int status = CODEC_OK; + + if (curr_handle != NULL) { + logf("Codec: cleaning up"); + status = c_hdr->entry_point(CODEC_UNLOAD); + lc_close(curr_handle); + curr_handle = NULL; + } + + return status; } diff --git a/apps/codecs.h b/apps/codecs.h index d96b2a7c9a..5c50116038 100644 --- a/apps/codecs.h +++ b/apps/codecs.h @@ -75,12 +75,18 @@ #define CODEC_ENC_MAGIC 0x52454E43 /* RENC */ /* increase this every time the api struct changes */ -#define CODEC_API_VERSION 41 +#define CODEC_API_VERSION 42 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any new function which are "waiting" at the end of the function table) */ -#define CODEC_MIN_API_VERSION 41 +#define CODEC_MIN_API_VERSION 42 + +/* reasons for calling codec main entrypoint */ +enum codec_entry_call_reason { + CODEC_LOAD = 0, + CODEC_UNLOAD +}; /* codec return codes */ enum codec_status { @@ -88,6 +94,13 @@ enum codec_status { CODEC_ERROR = -1, }; +/* codec command action codes */ +enum codec_command_action { + CODEC_ACTION_HALT = -1, + CODEC_ACTION_NULL = 0, + CODEC_ACTION_SEEK_TIME = 1, +}; + /* NOTE: To support backwards compatibility, only add new functions at the end of the structure. Every time you add a new function, remember to increase CODEC_API_VERSION. If you make changes to the @@ -95,24 +108,12 @@ enum codec_status { version */ struct codec_api { - off_t filesize; /* Total file length */ off_t curpos; /* Current buffer position */ - /* For gapless mp3 */ struct mp3entry *id3; /* TAG metadata pointer */ - bool *taginfo_ready; /* Is metadata read */ + int audio_hid; /* Current audio handle */ - /* Codec should periodically check if stop_codec is set to true. - In case it is, codec must return immediately */ - volatile bool stop_codec; - /* Codec should periodically check if new_track is non zero. - When it is, the codec should request a new track. */ - volatile int new_track; - /* If seek_time != 0, codec should seek to that song position (in ms) - if codec supports seeking. */ - volatile long seek_time; - /* The dsp instance to be used for audio output */ struct dsp_config *dsp; @@ -138,14 +139,12 @@ struct codec_api { bool (*seek_buffer)(size_t newpos); /* Codec should call this function when it has done the seeking. */ void (*seek_complete)(void); - /* Request file change from file buffer. Returns true is next - track is available and changed. If return value is false, - codec should exit immediately with PLUGIN_OK status. */ - bool (*request_next_track)(void); - + /* Update the current position */ void (*set_offset)(size_t value); /* Configure different codec buffer parameters. */ void (*configure)(int setting, intptr_t value); + /* Obtain command action on what to do next */ + enum codec_command_action (*get_command)(intptr_t *param); /* kernel/ system */ #if defined(CPU_ARM) && CONFIG_PLATFORM & PLATFORM_NATIVE @@ -231,7 +230,8 @@ struct codec_api { /* codec header */ struct codec_header { struct lc_header lc_hdr; /* must be first */ - enum codec_status(*entry_point)(void); + enum codec_status(*entry_point)(enum codec_entry_call_reason reason); + enum codec_status(*run_proc)(void); struct codec_api **api; }; @@ -248,13 +248,15 @@ extern unsigned char plugin_end_addr[]; const struct codec_header __header \ __attribute__ ((section (".header")))= { \ { CODEC_MAGIC, TARGET_ID, CODEC_API_VERSION, \ - plugin_start_addr, plugin_end_addr }, codec_start, &ci }; + plugin_start_addr, plugin_end_addr }, codec_start, \ + codec_run, &ci }; /* encoders */ #define CODEC_ENC_HEADER \ const struct codec_header __header \ __attribute__ ((section (".header")))= { \ { CODEC_ENC_MAGIC, TARGET_ID, CODEC_API_VERSION, \ - plugin_start_addr, plugin_end_addr }, codec_start, &ci }; + plugin_start_addr, plugin_end_addr }, codec_start, \ + codec_run, &ci }; #else /* def SIMULATOR */ /* decoders */ @@ -262,12 +264,12 @@ extern unsigned char plugin_end_addr[]; const struct codec_header __header \ __attribute__((visibility("default"))) = { \ { CODEC_MAGIC, TARGET_ID, CODEC_API_VERSION, NULL, NULL }, \ - codec_start, &ci }; + codec_start, codec_run, &ci }; /* encoders */ #define CODEC_ENC_HEADER \ const struct codec_header __header = { \ { CODEC_ENC_MAGIC, TARGET_ID, CODEC_API_VERSION, NULL, NULL }, \ - codec_start, &ci }; + codec_start, codec_run, &ci }; #endif /* SIMULATOR */ #endif /* CODEC */ @@ -276,13 +278,14 @@ extern unsigned char plugin_end_addr[]; void codec_get_full_path(char *path, const char *codec_root_fn); /* defined by the codec loader (codec.c) */ -void * codec_load_buf(int hid, struct codec_api *api); -void * codec_load_file(const char* codec, struct codec_api *api); -int codec_begin(void *handle); -void codec_close(void *handle); +int codec_load_buf(int hid, struct codec_api *api); +int codec_load_file(const char* codec, struct codec_api *api); +int codec_run_proc(void); +int codec_halt(void); +int codec_close(void); /* defined by the codec */ -enum codec_status codec_start(void); -enum codec_status codec_main(void); +enum codec_status codec_start(enum codec_entry_call_reason reason); +enum codec_status codec_run(void); -#endif +#endif /* _CODECS_H_ */ diff --git a/apps/codecs/a52.c b/apps/codecs/a52.c index 00fdeea309..4cd293e37f 100644 --- a/apps/codecs/a52.c +++ b/apps/codecs/a52.c @@ -116,27 +116,31 @@ static void a52_decode_data(uint8_t *start, uint8_t *end) } /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); + ci->configure(DSP_SET_SAMPLE_DEPTH, 28); + } + else if (reason == CODEC_UNLOAD) { + if (state) + a52_free(state); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { size_t n; unsigned char *filebuf; int sample_loc; - int retval; + intptr_t param; - /* Generic codec initialisation */ - ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); - ci->configure(DSP_SET_SAMPLE_DEPTH, 28); - -next_track: - retval = CODEC_OK; - - if (codec_init()) { - retval = CODEC_ERROR; - goto exit; - } - - if (codec_wait_taginfo() != 0) - goto request_next_track; + if (codec_init()) + return CODEC_ERROR; ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); codec_set_replaygain(ci->id3); @@ -153,15 +157,18 @@ next_track: } } else { + ci->seek_buffer(ci->id3->first_frame_offset); samplesdone = 0; } while (1) { - if (ci->stop_codec || ci->new_track) + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - if (ci->seek_time) { - sample_loc = (ci->seek_time - 1)/1000 * ci->id3->frequency; + if (action == CODEC_ACTION_SEEK_TIME) { + sample_loc = param/1000 * ci->id3->frequency; if (ci->seek_buffer((sample_loc/A52_SAMPLESPERFRAME)*ci->id3->bytesperframe)) { samplesdone = sample_loc; @@ -179,11 +186,5 @@ next_track: ci->advance_buffer(n); } -request_next_track: - if (ci->request_next_track()) - goto next_track; - -exit: - a52_free(state); - return retval; + return CODEC_OK; } diff --git a/apps/codecs/a52_rm.c b/apps/codecs/a52_rm.c index f5e4923292..c1930aa7b4 100644 --- a/apps/codecs/a52_rm.c +++ b/apps/codecs/a52_rm.c @@ -124,36 +124,44 @@ static void a52_decode_data(uint8_t *start, uint8_t *end) } } - /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); + ci->configure(DSP_SET_SAMPLE_DEPTH, 28); + } + else if (reason == CODEC_UNLOAD) { + if (state) + a52_free(state); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { size_t n; uint8_t *filebuf; - int retval, consumed, packet_offset; + int consumed, packet_offset; int playback_on = -1; size_t resume_offset; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); - ci->configure(DSP_SET_SAMPLE_DEPTH, 28); - -next_track: - retval = CODEC_OK; + intptr_t param; + enum codec_command_action action = CODEC_ACTION_NULL; if (codec_init()) { - retval = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto request_next_track; - resume_offset = ci->id3->offset; ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); codec_set_replaygain(ci->id3); + ci->seek_buffer(ci->id3->first_frame_offset); + /* Intializations */ state = a52_init(0); ci->memset(&rmctx,0,sizeof(RMContext)); @@ -165,26 +173,34 @@ next_track: resume_offset -= rmctx.data_offset + DATA_HEADER_SIZE; /* put number of subpackets to skip in resume_offset */ resume_offset /= (rmctx.block_align + PACKET_HEADER_SIZE); - ci->seek_time = (int)resume_offset * ((rmctx.block_align * 8 * 1000)/rmctx.bit_rate); + param = (int)resume_offset * ((rmctx.block_align * 8 * 1000)/rmctx.bit_rate); + action = CODEC_ACTION_SEEK_TIME; + } + else { + /* Seek to the first packet */ + ci->advance_buffer(rmctx.data_offset + DATA_HEADER_SIZE ); } - - /* Seek to the first packet */ - ci->advance_buffer(rmctx.data_offset + DATA_HEADER_SIZE ); /* The main decoding loop */ while((unsigned)rmctx.audio_pkt_cnt < rmctx.nb_packets) { - ci->yield(); - if (ci->stop_codec || ci->new_track) + if (action == CODEC_ACTION_NULL) + action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - if (ci->seek_time) { - packet_offset = ci->seek_time / ((rmctx.block_align*8*1000)/rmctx.bit_rate); - ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE + packet_offset*(rmctx.block_align + PACKET_HEADER_SIZE)); + if (action == CODEC_ACTION_SEEK_TIME) { + packet_offset = param / ((rmctx.block_align*8*1000)/rmctx.bit_rate); + ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE + + packet_offset*(rmctx.block_align + PACKET_HEADER_SIZE)); rmctx.audio_pkt_cnt = packet_offset; - samplesdone = (rmctx.sample_rate/1000 * ci->seek_time); + samplesdone = (rmctx.sample_rate/1000 * param); + ci->set_elapsed(samplesdone/(frequency/1000)); ci->seek_complete(); } + action = CODEC_ACTION_NULL; + filebuf = ci->request_buffer(&n, rmctx.block_align + PACKET_HEADER_SIZE); consumed = rm_get_packet(&filebuf, &rmctx, &pkt); @@ -195,8 +211,7 @@ next_track: return CODEC_ERROR; } else { - retval = CODEC_OK; - goto exit; + break; } } @@ -205,11 +220,5 @@ next_track: ci->advance_buffer(pkt.length); } -request_next_track: - if (ci->request_next_track()) - goto next_track; - -exit: - a52_free(state); - return retval; + return CODEC_OK; } diff --git a/apps/codecs/aac.c b/apps/codecs/aac.c index 5638dc49a9..90cf0438ce 100644 --- a/apps/codecs/aac.c +++ b/apps/codecs/aac.c @@ -33,7 +33,19 @@ CODEC_HEADER #define FAAD_BYTE_BUFFER_SIZE (2048-12) /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); + ci->configure(DSP_SET_SAMPLE_DEPTH, 29); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { /* Note that when dealing with QuickTime/MPEG4 files, terminology is * a bit confusing. Files with sound are split up in chunks, where @@ -59,25 +71,15 @@ enum codec_status codec_main(void) uint32_t sbr_fac = 1; unsigned char c = 0; void *ret; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); - ci->configure(DSP_SET_SAMPLE_DEPTH, 29); - -next_track: - err = CODEC_OK; + intptr_t param; /* Clean and initialize decoder structures */ memset(&demux_res , 0, sizeof(demux_res)); if (codec_init()) { LOGF("FAAD: Codec init error\n"); - err = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - file_offset = ci->id3->offset; ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); @@ -85,12 +87,13 @@ next_track: stream_create(&input_stream,ci); + ci->seek_buffer(ci->id3->first_frame_offset); + /* if qtmovie_read returns successfully, the stream is up to * the movie data, which can be used directly by the decoder */ if (!qtmovie_read(&input_stream, &demux_res)) { LOGF("FAAD: File init error\n"); - err = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* initialise the sound converter */ @@ -98,8 +101,7 @@ next_track: if (!decoder) { LOGF("FAAD: Decode open error\n"); - err = CODEC_ERROR; - goto done; + return CODEC_ERROR; } NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(decoder); @@ -109,8 +111,7 @@ next_track: err = NeAACDecInit2(decoder, demux_res.codecdata, demux_res.codecdata_len, &s, &c); if (err) { LOGF("FAAD: DecInit: %d, %d\n", err, decoder->object_type); - err = CODEC_ERROR; - goto done; + return CODEC_ERROR; } #ifdef SBR_DEC @@ -150,20 +151,19 @@ next_track: /* The main decoding loop */ while (i < demux_res.num_sample_byte_sizes) { - ci->yield(); + enum codec_command_action action = ci->get_command(¶m); - if (ci->stop_codec || ci->new_track) { + if (action == CODEC_ACTION_HALT) break; - } /* Deal with any pending seek requests */ - if (ci->seek_time) { + if (action == CODEC_ACTION_SEEK_TIME) { /* Seek to the desired time position. Important: When seeking in SBR * upsampling files the seek_time must be divided by 2 when calling * m4a_seek and the resulting sound_samples_done must be expanded * by a factor 2. This is done via using sbr_fac. */ if (m4a_seek(&demux_res, &input_stream, - ((ci->seek_time-1)/10/sbr_fac)*(ci->id3->frequency/100), + (param/10/sbr_fac)*(ci->id3->frequency/100), &sound_samples_done, (int*) &i)) { sound_samples_done *= sbr_fac; elapsed_time = (sound_samples_done * 10) / (ci->id3->frequency / 100); @@ -194,8 +194,7 @@ next_track: else if (file_offset == 0) { LOGF("AAC: get_sample_offset error\n"); - err = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* Request the required number of bytes from the input buffer */ @@ -207,8 +206,7 @@ next_track: /* NeAACDecDecode may sometimes return NULL without setting error. */ if (ret == NULL || frame_info.error > 0) { LOGF("FAAD: decode error '%s'\n", NeAACDecGetErrorMessage(frame_info.error)); - err = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* Advance codec buffer (no need to call set_offset because of this) */ @@ -251,12 +249,6 @@ next_track: i++; } -done: LOGF("AAC: Decoded %lu samples\n", (unsigned long)sound_samples_done); - - if (ci->request_next_track()) - goto next_track; - -exit: - return err; + return CODEC_OK; } diff --git a/apps/codecs/adx.c b/apps/codecs/adx.c index 832b94797b..a1b57fce58 100644 --- a/apps/codecs/adx.c +++ b/apps/codecs/adx.c @@ -45,7 +45,19 @@ static const long cutoff = 500; static int16_t samples[WAV_CHUNK_SIZE] IBSS_ATTR; /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + /* we only render 16 bits */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 16); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { int channels; int sampleswritten, i; @@ -62,12 +74,8 @@ enum codec_status codec_main(void) off_t chanstart, bufoff; /*long coef1=0x7298L,coef2=-0x3350L;*/ long coef1, coef2; + intptr_t param; - /* Generic codec initialisation */ - /* we only render 16 bits */ - ci->configure(DSP_SET_SAMPLE_DEPTH, 16); - -next_track: DEBUGF("ADX: next_track\n"); if (codec_init()) { return CODEC_ERROR; @@ -77,10 +85,6 @@ next_track: /* init history */ ch1_1=ch1_2=ch2_1=ch2_2=0; - /* wait for track info to load */ - if (codec_wait_taginfo() != 0) - goto request_next_track; - codec_set_replaygain(ci->id3); /* Get header */ @@ -226,10 +230,10 @@ next_track: /* The main decoder loop */ while (!endofstream) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - } /* do we need to loop? */ if (bufoff > end_adr-18*channels && looping) { @@ -254,17 +258,17 @@ next_track: } /* do we need to seek? */ - if (ci->seek_time) { + if (action == CODEC_ACTION_SEEK_TIME) { uint32_t newpos; - DEBUGF("ADX: seek to %ldms\n",ci->seek_time); + DEBUGF("ADX: seek to %ldms\n", (long)param); endofstream = 0; loop_count = 0; fade_count = -1; /* disable fade */ fade_frames = 1; - newpos = (((uint64_t)avgbytespersec*(ci->seek_time - 1)) + newpos = (((uint64_t)avgbytespersec*param) / (1000LL*18*channels))*(18*channels); bufoff = chanstart + newpos; while (bufoff > end_adr-18*channels) { @@ -385,9 +389,5 @@ next_track: 1000LL/avgbytespersec); } -request_next_track: - if (ci->request_next_track()) - goto next_track; - return CODEC_OK; } diff --git a/apps/codecs/aiff.c b/apps/codecs/aiff.c index d4cf8660dd..3fc137eaeb 100644 --- a/apps/codecs/aiff.c +++ b/apps/codecs/aiff.c @@ -61,9 +61,20 @@ static const struct pcm_codec *get_codec(uint32_t formattag) return NULL; } -enum codec_status codec_main(void) +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { - int status; struct pcm_format format; uint32_t bytesdone, decodedsamples; uint32_t num_sample_frames = 0; @@ -77,38 +88,28 @@ enum codec_status codec_main(void) bool is_aifc = false; const struct pcm_codec *codec; uint32_t size; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); - -next_track: - status = CODEC_OK; + intptr_t param; if (codec_init()) { - status = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - codec_set_replaygain(ci->id3); /* Need to save offset for later use (cleared indirectly by advance_buffer) */ bytesdone = ci->id3->offset; /* assume the AIFF header is less than 1024 bytes */ + ci->seek_buffer(0); buf = ci->request_buffer(&n, 1024); if (n < 54) { - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (memcmp(buf, "FORM", 4) != 0) { DEBUGF("CODEC_ERROR: does not aiff format %4.4s\n", (char*)&buf[0]); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (memcmp(&buf[8], "AIFF", 4) == 0) is_aifc = false; @@ -117,8 +118,7 @@ next_track: else { DEBUGF("CODEC_ERROR: does not aiff format %4.4s\n", (char*)&buf[8]); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } buf += 12; @@ -141,8 +141,7 @@ next_track: { DEBUGF("CODEC_ERROR: 'COMM' chunk size=%lu < %d\n", (unsigned long)size, (is_aifc)?22:18); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* num_channels */ format.channels = ((buf[8]<<8)|buf[9]); @@ -154,8 +153,7 @@ next_track: /* sample_rate (don't use last 4 bytes, only integer fs) */ if (buf[16] != 0x40) { DEBUGF("CODEC_ERROR: weird sampling rate (no @)\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } format.samplespersec = ((buf[18]<<24)|(buf[19]<<16)|(buf[20]<<8)|buf[21])+1; format.samplespersec >>= (16 + 14 - buf[17]); @@ -181,8 +179,7 @@ next_track: } else if (memcmp(buf, "SSND", 4)==0) { if (format.bitspersample == 0) { DEBUGF("CODEC_ERROR: unsupported chunk order\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* offset2snd */ offset2snd = (buf[8]<<24)|(buf[9]<<16)|(buf[10]<<8)|buf[11]; @@ -205,21 +202,18 @@ next_track: buf += size; if (n < size) { DEBUGF("CODEC_ERROR: AIFF header size > 1024\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } n -= size; } /* while 'SSND' */ if (format.channels == 0) { DEBUGF("CODEC_ERROR: 'COMM' chunk not found or 0-channels file\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (format.numbytes == 0) { DEBUGF("CODEC_ERROR: 'SSND' chunk not found or has zero length\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } codec = get_codec(format.formattag); @@ -227,14 +221,12 @@ next_track: { DEBUGF("CODEC_ERROR: AIFC does not support compressionType: 0x%x\n", (unsigned int)format.formattag); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (!codec->set_format(&format)) { - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); @@ -245,21 +237,18 @@ next_track: ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); } else { DEBUGF("CODEC_ERROR: more than 2 channels unsupported\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (format.samplesperblock == 0) { DEBUGF("CODEC_ERROR: samplesperblock is 0\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (format.blockalign == 0) { DEBUGF("CODEC_ERROR: blockalign is 0\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* check chunksize */ @@ -269,8 +258,7 @@ next_track: if (format.chunksize == 0) { DEBUGF("CODEC_ERROR: chunksize is 0\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } firstblockposn = 1024 - n; @@ -283,13 +271,13 @@ next_track: PCM_SEEK_POS, NULL); if (newpos->pos > format.numbytes) - goto done; + return CODEC_OK; + if (ci->seek_buffer(firstblockposn + newpos->pos)) { bytesdone = newpos->pos; decodedsamples = newpos->samples; } - ci->seek_complete(); } else { /* already where we need to be */ bytesdone = 0; @@ -299,21 +287,29 @@ next_track: endofstream = 0; while (!endofstream) { - ci->yield(); - if (ci->stop_codec || ci->new_track) + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - if (ci->seek_time) { + if (action == CODEC_ACTION_SEEK_TIME) { /* 3rd args(read_buffer) is unnecessary in the format which AIFF supports. */ - struct pcm_pos *newpos = codec->get_seek_pos(ci->seek_time, PCM_SEEK_TIME, NULL); + struct pcm_pos *newpos = codec->get_seek_pos(param, PCM_SEEK_TIME, NULL); if (newpos->pos > format.numbytes) + { + ci->set_elapsed(ci->id3->length); + ci->seek_complete(); break; + } + if (ci->seek_buffer(firstblockposn + newpos->pos)) { bytesdone = newpos->pos; decodedsamples = newpos->samples; } + + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); ci->seek_complete(); } aifbuf = (uint8_t *)ci->request_buffer(&n, format.chunksize); @@ -326,11 +322,10 @@ next_track: endofstream = 1; } - status = codec->decode(aifbuf, n, samples, &bufcount); - if (status == CODEC_ERROR) + if (codec->decode(aifbuf, n, samples, &bufcount) == CODEC_ERROR) { DEBUGF("codec error\n"); - goto done; + return CODEC_ERROR; } ci->pcmbuf_insert(samples, NULL, bufcount); @@ -343,13 +338,6 @@ next_track: ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); } - status = CODEC_OK; - -done: - if (ci->request_next_track()) - goto next_track; -exit: - return status; + return CODEC_OK; } - diff --git a/apps/codecs/aiff_enc.c b/apps/codecs/aiff_enc.c index 69496f70ac..fc44196eb0 100644 --- a/apps/codecs/aiff_enc.c +++ b/apps/codecs/aiff_enc.c @@ -359,40 +359,42 @@ static bool init_encoder(void) return true; } /* init_encoder */ -/* main codec entry point */ -enum codec_status codec_main(void) +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) { - if (!init_encoder()) - return CODEC_ERROR; + if (reason == CODEC_LOAD) { + if (!init_encoder()) + return CODEC_ERROR; + } + else if (reason == CODEC_UNLOAD) { + /* reset parameters to initial state */ + ci->enc_set_parameters(NULL); + } + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ /* main encoding loop */ - while(!ci->stop_codec) + while (ci->get_command(NULL) != CODEC_ACTION_HALT) { - uint32_t *src; + uint32_t *src = (uint32_t *)ci->enc_get_pcm_data(PCM_CHUNK_SIZE); + struct enc_chunk_hdr *chunk; - while ((src = (uint32_t *)ci->enc_get_pcm_data(PCM_CHUNK_SIZE)) != NULL) - { - struct enc_chunk_hdr *chunk; - - if (ci->stop_codec) - break; + if (src == NULL) + continue; - chunk = ci->enc_get_chunk(); - chunk->enc_size = enc_size; - chunk->num_pcm = PCM_SAMP_PER_CHUNK; - chunk->enc_data = ENC_CHUNK_SKIP_HDR(chunk->enc_data, chunk); + chunk = ci->enc_get_chunk(); + chunk->enc_size = enc_size; + chunk->num_pcm = PCM_SAMP_PER_CHUNK; + chunk->enc_data = ENC_CHUNK_SKIP_HDR(chunk->enc_data, chunk); - chunk_to_aiff_format(src, (uint32_t *)chunk->enc_data); - - ci->enc_finish_chunk(); - ci->yield(); - } + chunk_to_aiff_format(src, (uint32_t *)chunk->enc_data); - ci->yield(); + ci->enc_finish_chunk(); } - /* reset parameters to initial state */ - ci->enc_set_parameters(NULL); - return CODEC_OK; -} /* codec_start */ +} diff --git a/apps/codecs/alac.c b/apps/codecs/alac.c index cd9129a278..82bda264c3 100644 --- a/apps/codecs/alac.c +++ b/apps/codecs/alac.c @@ -32,116 +32,114 @@ CODEC_HEADER static int32_t outputbuffer[ALAC_MAX_CHANNELS][ALAC_BLOCKSIZE] IBSS_ATTR; /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) { - size_t n; - demux_res_t demux_res; - stream_t input_stream; - uint32_t samplesdone; - uint32_t elapsedtime; - int samplesdecoded; - unsigned int i; - unsigned char* buffer; - alac_file alac; - int retval; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); - ci->configure(DSP_SET_SAMPLE_DEPTH, ALAC_OUTPUT_DEPTH-1); - - next_track: - retval = CODEC_OK; - - /* Clean and initialize decoder structures */ - memset(&demux_res , 0, sizeof(demux_res)); - if (codec_init()) { - LOGF("ALAC: Error initialising codec\n"); - retval = CODEC_ERROR; - goto exit; - } - - if (codec_wait_taginfo() != 0) - goto done; - - ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); - codec_set_replaygain(ci->id3); + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); + ci->configure(DSP_SET_SAMPLE_DEPTH, ALAC_OUTPUT_DEPTH-1); + } - stream_create(&input_stream,ci); + return CODEC_OK; +} - /* Read from ci->id3->offset before calling qtmovie_read. */ - samplesdone = (uint32_t)(((uint64_t)(ci->id3->offset) * ci->id3->frequency) / - (ci->id3->bitrate*128)); - - /* if qtmovie_read returns successfully, the stream is up to - * the movie data, which can be used directly by the decoder */ - if (!qtmovie_read(&input_stream, &demux_res)) { - LOGF("ALAC: Error initialising file\n"); - retval = CODEC_ERROR; - goto done; - } - - /* initialise the sound converter */ - create_alac(demux_res.sound_sample_size, demux_res.num_channels,&alac); - alac_set_info(&alac, demux_res.codecdata); - - /* Set i for first frame, seek to desired sample position for resuming. */ - i=0; - if (samplesdone > 0) { - if (m4a_seek(&demux_res, &input_stream, samplesdone, - &samplesdone, (int*) &i)) { - elapsedtime = (samplesdone * 10) / (ci->id3->frequency / 100); - ci->set_elapsed(elapsedtime); - } else { - samplesdone = 0; +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ + size_t n; + demux_res_t demux_res; + stream_t input_stream; + uint32_t samplesdone; + uint32_t elapsedtime = 0; + int samplesdecoded; + unsigned int i; + unsigned char* buffer; + alac_file alac; + intptr_t param; + + /* Clean and initialize decoder structures */ + memset(&demux_res , 0, sizeof(demux_res)); + if (codec_init()) { + LOGF("ALAC: Error initialising codec\n"); + return CODEC_ERROR; } - } - /* The main decoding loop */ - while (i < demux_res.num_sample_byte_sizes) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { - break; + ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); + codec_set_replaygain(ci->id3); + + ci->seek_buffer(0); + + stream_create(&input_stream,ci); + + /* Read from ci->id3->offset before calling qtmovie_read. */ + samplesdone = (uint32_t)(((uint64_t)(ci->id3->offset) * ci->id3->frequency) / + (ci->id3->bitrate*128)); + + /* if qtmovie_read returns successfully, the stream is up to + * the movie data, which can be used directly by the decoder */ + if (!qtmovie_read(&input_stream, &demux_res)) { + LOGF("ALAC: Error initialising file\n"); + return CODEC_ERROR; } - /* Deal with any pending seek requests */ - if (ci->seek_time) { - if (m4a_seek(&demux_res, &input_stream, - ((ci->seek_time-1)/10) * (ci->id3->frequency/100), - &samplesdone, (int *)&i)) { - elapsedtime=(samplesdone*10)/(ci->id3->frequency/100); - ci->set_elapsed(elapsedtime); - } - ci->seek_complete(); + /* initialise the sound converter */ + create_alac(demux_res.sound_sample_size, demux_res.num_channels,&alac); + alac_set_info(&alac, demux_res.codecdata); + + /* Set i for first frame, seek to desired sample position for resuming. */ + i=0; + if (samplesdone > 0) { + if (m4a_seek(&demux_res, &input_stream, samplesdone, + &samplesdone, (int*) &i)) { + elapsedtime = (samplesdone * 10) / (ci->id3->frequency / 100); + ci->set_elapsed(elapsedtime); + } else { + samplesdone = 0; + } } - /* Request the required number of bytes from the input buffer */ - buffer=ci->request_buffer(&n, ALAC_BYTE_BUFFER_SIZE); + /* The main decoding loop */ + while (i < demux_res.num_sample_byte_sizes) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; - /* Decode one block - returned samples will be host-endian */ - ci->yield(); - samplesdecoded=alac_decode_frame(&alac, buffer, outputbuffer, ci->yield); + /* Request the required number of bytes from the input buffer */ + buffer=ci->request_buffer(&n, ALAC_BYTE_BUFFER_SIZE); - /* Advance codec buffer by amount of consumed bytes */ - ci->advance_buffer(alac.bytes_consumed); + /* Deal with any pending seek requests */ + if (action == CODEC_ACTION_SEEK_TIME) { + if (m4a_seek(&demux_res, &input_stream, + (param/10) * (ci->id3->frequency/100), + &samplesdone, (int *)&i)) { + elapsedtime=(samplesdone*10)/(ci->id3->frequency/100); + } + ci->set_elapsed(elapsedtime); + ci->seek_complete(); + } - /* Output the audio */ - ci->yield(); - ci->pcmbuf_insert(outputbuffer[0], outputbuffer[1], samplesdecoded); + /* Request the required number of bytes from the input buffer */ + buffer=ci->request_buffer(&n, ALAC_BYTE_BUFFER_SIZE); - /* Update the elapsed-time indicator */ - samplesdone+=samplesdecoded; - elapsedtime=(samplesdone*10)/(ci->id3->frequency/100); - ci->set_elapsed(elapsedtime); + /* Decode one block - returned samples will be host-endian */ + samplesdecoded=alac_decode_frame(&alac, buffer, outputbuffer, ci->yield); + ci->yield(); - i++; - } + /* Advance codec buffer by amount of consumed bytes */ + ci->advance_buffer(alac.bytes_consumed); -done: - LOGF("ALAC: Decoded %lu samples\n",(unsigned long)samplesdone); + /* Output the audio */ + ci->pcmbuf_insert(outputbuffer[0], outputbuffer[1], samplesdecoded); - if (ci->request_next_track()) - goto next_track; + /* Update the elapsed-time indicator */ + samplesdone+=samplesdecoded; + elapsedtime=(samplesdone*10)/(ci->id3->frequency/100); + ci->set_elapsed(elapsedtime); + + i++; + } -exit: - return retval; + LOGF("ALAC: Decoded %lu samples\n",(unsigned long)samplesdone); + return CODEC_OK; } diff --git a/apps/codecs/ape.c b/apps/codecs/ape.c index 11d973ab26..8f95a01ec7 100644 --- a/apps/codecs/ape.c +++ b/apps/codecs/ape.c @@ -127,13 +127,23 @@ static void ape_resume(struct ape_ctx_t* ape_ctx, size_t resume_offset, } /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, APE_OUTPUT_DEPTH-1); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { struct ape_ctx_t ape_ctx; uint32_t samplesdone; uint32_t elapsedtime; size_t bytesleft; - int retval; uint32_t currentframe; uint32_t newfilepos; @@ -145,33 +155,24 @@ enum codec_status codec_main(void) int res; int firstbyte; size_t resume_offset; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, APE_OUTPUT_DEPTH-1); - -next_track: - retval = CODEC_OK; + intptr_t param; if (codec_init()) { LOGF("APE: Error initialising codec\n"); - retval = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - /* Remember the resume position - when the codec is opened, the playback engine will reset it. */ resume_offset = ci->id3->offset; + ci->seek_buffer(0); inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); /* Read the file headers to populate the ape_ctx struct */ if (ape_parseheaderbuf(inbuffer,&ape_ctx) < 0) { LOGF("APE: Error reading header\n"); - retval = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } /* Initialise the seektable for this file */ @@ -243,16 +244,16 @@ frame_start: /* Decode the frame a chunk at a time */ while (nblocks > 0) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) goto done; - } /* Deal with any pending seek requests */ - if (ci->seek_time) + if (action == CODEC_ACTION_SEEK_TIME) { if (ape_calc_seekpos(&ape_ctx, - ((ci->seek_time-1)/10) * (ci->id3->frequency/100), + (param/10) * (ci->id3->frequency/100), ¤tframe, &newfilepos, &samplestoskip)) @@ -266,9 +267,12 @@ frame_start: ci->seek_buffer(newfilepos); inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); + elapsedtime = (samplesdone*10)/(ape_ctx.samplerate/100); + ci->set_elapsed(elapsedtime); ci->seek_complete(); goto frame_start; /* Sorry... */ } + ci->seek_complete(); } @@ -281,8 +285,7 @@ frame_start: { /* Frame decoding error, abort */ LOGF("APE: Frame %lu, error %d\n",(unsigned long)currentframe,res); - retval = CODEC_ERROR; - goto done; + return CODEC_ERROR; } ci->yield(); @@ -320,10 +323,5 @@ frame_start: done: LOGF("APE: Decoded %lu samples\n",(unsigned long)samplesdone); - - if (ci->request_next_track()) - goto next_track; - -exit: - return retval; + return CODEC_OK; } diff --git a/apps/codecs/asap.c b/apps/codecs/asap.c index 9447c738d2..f12dc6a0c5 100644 --- a/apps/codecs/asap.c +++ b/apps/codecs/asap.c @@ -29,24 +29,21 @@ CODEC_HEADER static byte samples[CHUNK_SIZE] IBSS_ATTR; /* The sample buffer */ static ASAP_State asap; /* asap codec state */ -/* this is the codec entry point */ -enum codec_status codec_main(void) +/* this is called for each file to process */ +enum codec_status codec_run(void) { int n_bytes; int song; int duration; char* module; int bytesPerSample =2; + intptr_t param; -next_track: if (codec_init()) { DEBUGF("codec init failed\n"); return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto request_next_track; - codec_set_replaygain(ci->id3); int bytes_done =0; @@ -97,19 +94,20 @@ next_track: /* The main decoder loop */ while (1) { - ci->yield(); - if (ci->stop_codec || ci->new_track) + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - if (ci->seek_time) { - /* New time is ready in ci->seek_time */ - + if (action == CODEC_ACTION_SEEK_TIME) { + /* New time is ready in param */ + /* seek to pos */ - ASAP_Seek(&asap,ci->seek_time); - /* update elapsed */ - ci->set_elapsed(ci->seek_time); + ASAP_Seek(&asap,param); /* update bytes_done */ - bytes_done = ci->seek_time*44.1*2; + bytes_done = param*44.1*2; + /* update elapsed */ + ci->set_elapsed((bytes_done / 2) / 44.1); /* seek ready */ ci->seek_complete(); } @@ -129,10 +127,6 @@ next_track: if(n_bytes != sizeof(samples)) break; } - -request_next_track: - if (ci->request_next_track()) - goto next_track; return CODEC_OK; } diff --git a/apps/codecs/atrac3_oma.c b/apps/codecs/atrac3_oma.c index 73f3ad29fd..ab24783368 100644 --- a/apps/codecs/atrac3_oma.c +++ b/apps/codecs/atrac3_oma.c @@ -33,24 +33,22 @@ CODEC_HEADER static ATRAC3Context q IBSS_ATTR; -/* this is the codec entry point */ -enum codec_status codec_main(void) -{ +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ static size_t buff_size; int datasize, res, frame_counter, total_frames, seek_frame_offset; uint8_t *bit_buffer; int elapsed = 0; size_t resume_offset; + intptr_t param; + enum codec_command_action action = CODEC_ACTION_NULL; -next_track: if (codec_init()) { DEBUGF("codec init failed\n"); return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - resume_offset = ci->id3->offset; codec_set_replaygain(ci->id3); @@ -60,84 +58,88 @@ next_track: ci->configure(DSP_SET_SAMPLE_DEPTH, 17); /* Remark: atrac3 uses s15.0 by default, s15.2 was hacked. */ ci->configure(DSP_SET_STEREO_MODE, ci->id3->channels == 1 ? STEREO_MONO : STEREO_NONINTERLEAVED); - - res =atrac3_decode_init(&q, ci->id3); + + ci->seek_buffer(0); + + res = atrac3_decode_init(&q, ci->id3); if(res < 0) { DEBUGF("failed to initialize OMA atrac decoder\n"); return CODEC_ERROR; } + total_frames = (ci->id3->filesize - ci->id3->first_frame_offset) / FRAMESIZE; + frame_counter = 0; + /* check for a mid-track resume and force a seek time accordingly */ if(resume_offset > ci->id3->first_frame_offset) { resume_offset -= ci->id3->first_frame_offset; /* calculate resume_offset in frames */ resume_offset = (int)resume_offset / FRAMESIZE; - ci->seek_time = (int)resume_offset * ((FRAMESIZE * 8)/BITRATE); + param = (int)resume_offset * ((FRAMESIZE * 8)/BITRATE); + action = CODEC_ACTION_SEEK_TIME; + } + else { + ci->set_elapsed(0); + ci->seek_buffer(ci->id3->first_frame_offset); } - total_frames = (ci->id3->filesize - ci->id3->first_frame_offset) / FRAMESIZE; - frame_counter = 0; - - ci->set_elapsed(0); - ci->seek_buffer(0); - ci->advance_buffer(ci->id3->first_frame_offset); /* The main decoder loop */ -seek_start : while(frame_counter < total_frames) - { + { + if (action == CODEC_ACTION_NULL) + action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; + bit_buffer = (uint8_t *) ci->request_buffer(&buff_size, FRAMESIZE); - ci->yield(); - if (ci->stop_codec || ci->new_track) - goto done; - - if (ci->seek_time) { - ci->set_elapsed(ci->seek_time); - - /* Do not allow seeking beyond the file's length */ - if ((unsigned) ci->seek_time > ci->id3->length) { - ci->seek_complete(); - goto done; - } - - /* Seek to the start of the track */ - if (ci->seek_time == 1) { - ci->set_elapsed(0); - ci->seek_complete(); - ci->seek_buffer(ci->id3->first_frame_offset); - elapsed = 0; - goto seek_start; - } - seek_frame_offset = (ci->seek_time * BITRATE) / (8 * FRAMESIZE); - frame_counter = seek_frame_offset; - ci->seek_buffer(ci->id3->first_frame_offset + seek_frame_offset* FRAMESIZE); - bit_buffer = (uint8_t *) ci->request_buffer(&buff_size, FRAMESIZE); - elapsed = ci->seek_time; - - ci->set_elapsed(elapsed); - ci->seek_complete(); - } - - res = atrac3_decode_frame(FRAMESIZE, &q, &datasize, bit_buffer, FRAMESIZE); - - if(res != (int)FRAMESIZE) { - DEBUGF("codec error\n"); - return CODEC_ERROR; - } - - if(datasize) - ci->pcmbuf_insert(q.outSamples, q.outSamples + 1024, q.samples_per_frame / ci->id3->channels); - - elapsed += (FRAMESIZE * 8) / BITRATE; + if (action == CODEC_ACTION_SEEK_TIME) { + /* Do not allow seeking beyond the file's length */ + if ((unsigned) param > ci->id3->length) { + ci->set_elapsed(ci->id3->length); + ci->seek_complete(); + break; + } + + /* Seek to the start of the track */ + if (param == 0) { + elapsed = 0; + ci->set_elapsed(0); + ci->seek_buffer(ci->id3->first_frame_offset); + ci->seek_complete(); + action = CODEC_ACTION_NULL; + continue; + } + + seek_frame_offset = (param * BITRATE) / (8 * FRAMESIZE); + frame_counter = seek_frame_offset; + ci->seek_buffer(ci->id3->first_frame_offset + seek_frame_offset* FRAMESIZE); + bit_buffer = (uint8_t *) ci->request_buffer(&buff_size, FRAMESIZE); + elapsed = param; ci->set_elapsed(elapsed); - - ci->advance_buffer(FRAMESIZE); - frame_counter++; - } + ci->seek_complete(); + } + + action = CODEC_ACTION_NULL; - done: - if (ci->request_next_track()) - goto next_track; + res = atrac3_decode_frame(FRAMESIZE, &q, &datasize, bit_buffer, FRAMESIZE); + + if(res != (int)FRAMESIZE) { + DEBUGF("codec error\n"); + return CODEC_ERROR; + } + + if(datasize) + ci->pcmbuf_insert(q.outSamples, q.outSamples + 1024, + q.samples_per_frame / ci->id3->channels); + + elapsed += (FRAMESIZE * 8) / BITRATE; + ci->set_elapsed(elapsed); + + ci->advance_buffer(FRAMESIZE); + frame_counter++; + } return CODEC_OK; } diff --git a/apps/codecs/atrac3_rm.c b/apps/codecs/atrac3_rm.c index 6a77d24283..1322e917ed 100644 --- a/apps/codecs/atrac3_rm.c +++ b/apps/codecs/atrac3_rm.c @@ -41,9 +41,9 @@ static void init_rm(RMContext *rmctx) memcpy(ci->id3->id3v2buf, (char*)rmctx->codec_extradata, rmctx->extradata_size*sizeof(char)); } -/* this is the codec entry point */ -enum codec_status codec_main(void) -{ +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ static size_t buff_size; int datasize, res, consumed, i, time_offset; uint8_t *bit_buffer; @@ -52,16 +52,14 @@ enum codec_status codec_main(void) int scrambling_unit_size, num_units, elapsed = 0; int playback_on = -1; size_t resume_offset; + intptr_t param; + enum codec_command_action action = CODEC_ACTION_NULL; -next_track: if (codec_init()) { DEBUGF("codec init failed\n"); return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - resume_offset = ci->id3->offset; codec_set_replaygain(ci->id3); @@ -69,6 +67,7 @@ next_track: ci->memset(&pkt,0,sizeof(RMPacket)); ci->memset(&q,0,sizeof(ATRAC3Context)); + ci->seek_buffer(0); init_rm(&rmctx); ci->configure(DSP_SET_FREQUENCY, ci->id3->frequency); @@ -84,22 +83,25 @@ next_track: h = rmctx.sub_packet_h; scrambling_unit_size = h*fs; - res =atrac3_decode_init(&q, ci->id3); + res = atrac3_decode_init(&q, ci->id3); if(res < 0) { DEBUGF("failed to initialize RM atrac decoder\n"); return CODEC_ERROR; } - + /* check for a mid-track resume and force a seek time accordingly */ if(resume_offset > rmctx.data_offset + DATA_HEADER_SIZE) { resume_offset -= rmctx.data_offset + DATA_HEADER_SIZE; num_units = (int)resume_offset / scrambling_unit_size; /* put number of subpackets to skip in resume_offset */ resume_offset /= (sps + PACKET_HEADER_SIZE); - ci->seek_time = (int)resume_offset * ((sps * 8 * 1000)/rmctx.bit_rate); + param = (int)resume_offset * ((sps * 8 * 1000)/rmctx.bit_rate); + action = CODEC_ACTION_SEEK_TIME; } - - ci->set_elapsed(0); + else { + ci->set_elapsed(0); + } + ci->advance_buffer(rmctx.data_offset + DATA_HEADER_SIZE); /* The main decoder loop */ @@ -115,22 +117,23 @@ seek_start : return CODEC_ERROR; } else - goto done; + return CODEC_OK; } for(i = 0; i < rmctx.audio_pkt_cnt*(fs/sps) ; i++) { - ci->yield(); - if (ci->stop_codec || ci->new_track) - goto done; + if (action == CODEC_ACTION_NULL) + action = ci->get_command(¶m); - if (ci->seek_time) { - ci->set_elapsed(ci->seek_time); + if (action == CODEC_ACTION_HALT) + return CODEC_OK; + if (action == CODEC_ACTION_SEEK_TIME) { /* Do not allow seeking beyond the file's length */ - if ((unsigned) ci->seek_time > ci->id3->length) { + if ((unsigned) param > ci->id3->length) { + ci->set_elapsed(ci->id3->length); ci->seek_complete(); - goto done; + return CODEC_OK; } ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE); @@ -139,12 +142,13 @@ seek_start : rmctx.frame_number = 0; /* Seek to the start of the track */ - if (ci->seek_time == 1) { + if (param == 0) { ci->set_elapsed(0); ci->seek_complete(); + action = CODEC_ACTION_NULL; goto seek_start; } - num_units = ((ci->seek_time)/(sps*1000*8/rmctx.bit_rate))/(h*(fs/sps)); + num_units = (param/(sps*1000*8/rmctx.bit_rate))/(h*(fs/sps)); ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE + consumed * num_units); bit_buffer = (uint8_t *) ci->request_buffer(&buff_size, scrambling_unit_size); consumed = rm_get_packet(&bit_buffer, &rmctx, &pkt); @@ -155,12 +159,12 @@ seek_start : return CODEC_ERROR; } else - goto done; + return CODEC_OK; } packet_count = rmctx.nb_packets - rmctx.audio_pkt_cnt * num_units; - rmctx.frame_number = ((ci->seek_time)/(sps*1000*8/rmctx.bit_rate)); - while(rmctx.audiotimestamp > (unsigned) ci->seek_time) { + rmctx.frame_number = (param/(sps*1000*8/rmctx.bit_rate)); + while(rmctx.audiotimestamp > (unsigned) param) { rmctx.audio_pkt_cnt = 0; ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE + consumed * (num_units-1)); bit_buffer = (uint8_t *) ci->request_buffer(&buff_size, scrambling_unit_size); @@ -168,16 +172,19 @@ seek_start : packet_count += rmctx.audio_pkt_cnt; num_units--; } - time_offset = ci->seek_time - rmctx.audiotimestamp; + time_offset = param - rmctx.audiotimestamp; i = (time_offset/((sps * 8 * 1000)/rmctx.bit_rate)); elapsed = rmctx.audiotimestamp+(1000*8*sps/rmctx.bit_rate)*i; ci->set_elapsed(elapsed); ci->seek_complete(); } + + action = CODEC_ACTION_NULL; + if(pkt.length) res = atrac3_decode_frame(rmctx.block_align, &q, &datasize, pkt.frames[i], rmctx.block_align); else /* indicates that there are no remaining frames */ - goto done; + return CODEC_OK; if(res != rmctx.block_align) { DEBUGF("codec error\n"); @@ -196,9 +203,5 @@ seek_start : ci->advance_buffer(consumed); } - done : - if (ci->request_next_track()) - goto next_track; - - return CODEC_OK; + return CODEC_OK; } diff --git a/apps/codecs/au.c b/apps/codecs/au.c index 3f9436c9e7..e06f931cf9 100644 --- a/apps/codecs/au.c +++ b/apps/codecs/au.c @@ -106,9 +106,19 @@ static int convert_au_format(unsigned int encoding, struct pcm_format *fmt) } /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { - int status; struct pcm_format format; uint32_t bytesdone, decodedsamples; size_t n; @@ -119,22 +129,13 @@ enum codec_status codec_main(void) off_t firstblockposn; /* position of the first block in file */ const struct pcm_codec *codec; int offset = 0; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); - -next_track: - status = CODEC_OK; - + intptr_t param; + if (codec_init()) { DEBUGF("codec_init() error\n"); - status = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - codec_set_replaygain(ci->id3); /* Need to save offset for later use (cleared indirectly by advance_buffer) */ @@ -145,6 +146,7 @@ next_track: format.is_little_endian = false; /* set format */ + ci->seek_buffer(0); buf = ci->request_buffer(&n, 24); if (n < 24 || (memcmp(buf, ".snd", 4) != 0)) { @@ -170,8 +172,7 @@ next_track: if (offset < 24) { DEBUGF("CODEC_ERROR: sun audio offset size is small: %d\n", offset); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* data size */ format.numbytes = get_be32(buf + 8); @@ -182,8 +183,7 @@ next_track: if (format.formattag == AU_FORMAT_UNSUPPORT) { DEBUGF("CODEC_ERROR: sun audio unsupport format: %d\n", get_be32(buf + 12)); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* skip sample rate */ format.channels = get_be32(buf + 20); @@ -202,20 +202,17 @@ next_track: if (!codec) { DEBUGF("CODEC_ERROR: unsupport sun audio format: %x\n", (int)format.formattag); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (!codec->set_format(&format)) { - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (format.numbytes == 0) { DEBUGF("CODEC_ERROR: data size is 0\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* check chunksize */ @@ -225,8 +222,7 @@ next_track: if (format.chunksize == 0) { DEBUGF("CODEC_ERROR: chunksize is 0\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); @@ -236,8 +232,7 @@ next_track: ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); } else { DEBUGF("CODEC_ERROR: more than 2 channels\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* make sure we're at the correct offset */ @@ -253,7 +248,6 @@ next_track: bytesdone = newpos->pos; decodedsamples = newpos->samples; } - ci->seek_complete(); } else { /* already where we need to be */ bytesdone = 0; @@ -263,22 +257,29 @@ next_track: endofstream = 0; while (!endofstream) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - } - if (ci->seek_time) { + if (action == CODEC_ACTION_SEEK_TIME) { /* 3rd args(read_buffer) is unnecessary in the format which Sun Audio supports. */ - struct pcm_pos *newpos = codec->get_seek_pos(ci->seek_time, PCM_SEEK_TIME, NULL); + struct pcm_pos *newpos = codec->get_seek_pos(param, PCM_SEEK_TIME, NULL); if (newpos->pos > format.numbytes) + { + ci->set_elapsed(ci->id3->length); + ci->seek_complete(); break; + } + if (ci->seek_buffer(firstblockposn + newpos->pos)) { bytesdone = newpos->pos; decodedsamples = newpos->samples; } + + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); ci->seek_complete(); } @@ -290,11 +291,10 @@ next_track: endofstream = 1; } - status = codec->decode(aubuf, n, samples, &bufcount); - if (status == CODEC_ERROR) + if (codec->decode(aubuf, n, samples, &bufcount) == CODEC_ERROR) { DEBUGF("codec error\n"); - goto done; + return CODEC_ERROR; } ci->pcmbuf_insert(samples, NULL, bufcount); @@ -308,9 +308,5 @@ next_track: } done: - if (ci->request_next_track()) - goto next_track; - -exit: - return status; + return CODEC_OK; } diff --git a/apps/codecs/codec_crt0.c b/apps/codecs/codec_crt0.c index cf14e460ec..33876272c6 100644 --- a/apps/codecs/codec_crt0.c +++ b/apps/codecs/codec_crt0.c @@ -27,39 +27,45 @@ struct codec_api *ci DATA_ATTR; extern unsigned char plugin_bss_start[]; extern unsigned char plugin_end_addr[]; -extern enum codec_status codec_main(void); +extern enum codec_status codec_main(enum codec_entry_call_reason reason); /* stub, the entry point is called via its reference in __header to * avoid warning with certain compilers */ int _start(void) {return 0;} -enum codec_status codec_start(void) +enum codec_status codec_start(enum codec_entry_call_reason reason) { #if (CONFIG_PLATFORM & PLATFORM_NATIVE) -#ifdef USE_IRAM - extern char iramcopy[], iramstart[], iramend[], iedata[], iend[]; - size_t iram_size = iramend - iramstart; - size_t ibss_size = iend - iedata; - if (iram_size > 0 || ibss_size > 0) + if (reason == CODEC_LOAD) { - ci->memcpy(iramstart, iramcopy, iram_size); - ci->memset(iedata, 0, ibss_size); - /* make the icache (if it exists) up to date with the new code */ +#ifdef USE_IRAM + extern char iramcopy[], iramstart[], iramend[], iedata[], iend[]; + size_t iram_size = iramend - iramstart; + size_t ibss_size = iend - iedata; + if (iram_size > 0 || ibss_size > 0) + { + ci->memcpy(iramstart, iramcopy, iram_size); + ci->memset(iedata, 0, ibss_size); + /* make the icache (if it exists) up to date with the new code */ + ci->cpucache_invalidate(); + /* barrier to prevent reordering iram copy and BSS clearing, + * because the BSS segment alias the IRAM copy. + */ + asm volatile ("" ::: "memory"); + } +#endif /* PLUGIN_USE_IRAM */ + ci->memset(plugin_bss_start, 0, plugin_end_addr - plugin_bss_start); + /* Some parts of bss may be used via a no-cache alias (at least + * portalplayer has this). If we don't clear the cache, those aliases + * may read garbage */ ci->cpucache_invalidate(); - /* barrier to prevent reordering iram copy and BSS clearing, - * because the BSS segment alias the IRAM copy. - */ - asm volatile ("" ::: "memory"); } -#endif /* PLUGIN_USE_IRAM */ - ci->memset(plugin_bss_start, 0, plugin_end_addr - plugin_bss_start); - /* Some parts of bss may be used via a no-cache alias (at least - * portalplayer has this). If we don't clear the cache, those aliases - * may read garbage */ - ci->cpucache_invalidate(); -#endif +#endif /* CONFIG_PLATFORM */ - return codec_main(); + /* Note: If for any reason codec_main would not be called with CODEC_LOAD + * because the above code failed then it must not be ever be called with + * any other value and some strategy to avoid doing so must be conceived */ + return codec_main(reason); } #if defined(CPU_ARM) && (CONFIG_PLATFORM & PLATFORM_NATIVE) diff --git a/apps/codecs/cook.c b/apps/codecs/cook.c index 015618986c..a6b4a1153e 100644 --- a/apps/codecs/cook.c +++ b/apps/codecs/cook.c @@ -38,9 +38,9 @@ static void init_rm(RMContext *rmctx) memcpy(rmctx, (void*)(( (intptr_t)ci->id3->id3v2buf + 3 ) &~ 3), sizeof(RMContext)); } -/* this is the codec entry point */ -enum codec_status codec_main(void) -{ +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ static size_t buff_size; int datasize, res, consumed, i, time_offset; uint8_t *bit_buffer; @@ -48,16 +48,14 @@ enum codec_status codec_main(void) uint32_t packet_count; int scrambling_unit_size, num_units; size_t resume_offset; + intptr_t param = 0; + enum codec_command_action action = CODEC_ACTION_NULL; -next_track: if (codec_init()) { DEBUGF("codec init failed\n"); return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - resume_offset = ci->id3->offset; codec_set_replaygain(ci->id3); @@ -65,6 +63,8 @@ next_track: ci->memset(&pkt,0,sizeof(RMPacket)); ci->memset(&q,0,sizeof(COOKContext)); + ci->seek_buffer(0); + init_rm(&rmctx); ci->configure(DSP_SET_FREQUENCY, ci->id3->frequency); @@ -87,20 +87,21 @@ next_track: DEBUGF("failed to initialize cook decoder\n"); return CODEC_ERROR; } - + /* check for a mid-track resume and force a seek time accordingly */ if(resume_offset > rmctx.data_offset + DATA_HEADER_SIZE) { resume_offset -= rmctx.data_offset + DATA_HEADER_SIZE; num_units = (int)resume_offset / scrambling_unit_size; /* put number of subpackets to skip in resume_offset */ resume_offset /= (sps + PACKET_HEADER_SIZE); - ci->seek_time = (int)resume_offset * ((sps * 8 * 1000)/rmctx.bit_rate); + param = (int)resume_offset * ((sps * 8 * 1000)/rmctx.bit_rate); + action = CODEC_ACTION_SEEK_TIME; } ci->set_elapsed(0); ci->advance_buffer(rmctx.data_offset + DATA_HEADER_SIZE); - /* The main decoder loop */ + /* The main decoder loop */ seek_start : while(packet_count) { @@ -112,18 +113,19 @@ seek_start : } for(i = 0; i < rmctx.audio_pkt_cnt*(fs/sps) ; i++) - { - ci->yield(); - if (ci->stop_codec || ci->new_track) - goto done; + { + if (action == CODEC_ACTION_NULL) + action = ci->get_command(¶m); - if (ci->seek_time) { - ci->set_elapsed(ci->seek_time); + if (action == CODEC_ACTION_HALT) + return CODEC_OK; + if (action == CODEC_ACTION_SEEK_TIME) { /* Do not allow seeking beyond the file's length */ - if ((unsigned) ci->seek_time > ci->id3->length) { + if ((unsigned) param > ci->id3->length) { + ci->set_elapsed(ci->id3->length); ci->seek_complete(); - goto done; + return CODEC_OK; } ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE); @@ -132,22 +134,24 @@ seek_start : rmctx.frame_number = 0; /* Seek to the start of the track */ - if (ci->seek_time == 1) { + if (param == 0) { ci->set_elapsed(0); ci->seek_complete(); + action = CODEC_ACTION_NULL; goto seek_start; } - num_units = ((ci->seek_time)/(sps*1000*8/rmctx.bit_rate))/(h*(fs/sps)); + num_units = (param/(sps*1000*8/rmctx.bit_rate))/(h*(fs/sps)); ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE + consumed * num_units); bit_buffer = (uint8_t *) ci->request_buffer(&buff_size, scrambling_unit_size); consumed = rm_get_packet(&bit_buffer, &rmctx, &pkt); if(consumed < 0) { DEBUGF("rm_get_packet failed\n"); - return CODEC_ERROR; + ci->seek_complete(); + return CODEC_ERROR; } packet_count = rmctx.nb_packets - rmctx.audio_pkt_cnt * num_units; - rmctx.frame_number = ((ci->seek_time)/(sps*1000*8/rmctx.bit_rate)); - while(rmctx.audiotimestamp > (unsigned) ci->seek_time) { + rmctx.frame_number = (param/(sps*1000*8/rmctx.bit_rate)); + while(rmctx.audiotimestamp > (unsigned) param) { rmctx.audio_pkt_cnt = 0; ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE + consumed * (num_units-1)); bit_buffer = (uint8_t *) ci->request_buffer(&buff_size, scrambling_unit_size); @@ -155,11 +159,14 @@ seek_start : packet_count += rmctx.audio_pkt_cnt; num_units--; } - time_offset = ci->seek_time - rmctx.audiotimestamp; + time_offset = param - rmctx.audiotimestamp; i = (time_offset/((sps * 8 * 1000)/rmctx.bit_rate)); ci->set_elapsed(rmctx.audiotimestamp+(1000*8*sps/rmctx.bit_rate)*i); ci->seek_complete(); - } + } + + action = CODEC_ACTION_NULL; + res = cook_decode_frame(&rmctx,&q, rm_outbuf, &datasize, pkt.frames[i], rmctx.block_align); rmctx.frame_number++; @@ -181,9 +188,5 @@ seek_start : ci->advance_buffer(consumed); } - done : - if (ci->request_next_track()) - goto next_track; - - return CODEC_OK; + return CODEC_OK; } diff --git a/apps/codecs/flac.c b/apps/codecs/flac.c index 89d14b98a7..a5521b584f 100644 --- a/apps/codecs/flac.c +++ b/apps/codecs/flac.c @@ -418,40 +418,40 @@ static bool flac_seek_offset(FLACContext* fc, uint32_t offset) { } /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, FLAC_OUTPUT_DEPTH-1); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { int8_t *buf; FLACContext fc; - uint32_t samplesdone = 0; + uint32_t samplesdone; uint32_t elapsedtime; size_t bytesleft; int consumed; int res; int frame; - int retval; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, FLAC_OUTPUT_DEPTH-1); + intptr_t param; -next_track: - retval = CODEC_OK; - if (codec_init()) { LOGF("FLAC: Error initialising codec\n"); - retval = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - /* Need to save offset for later use (cleared indirectly by flac_init) */ samplesdone = ci->id3->offset; if (!flac_init(&fc,ci->id3->first_frame_offset)) { LOGF("FLAC: Error initialising codec\n"); - retval = CODEC_ERROR; - goto done; + return CODEC_ERROR; } ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); @@ -459,35 +459,34 @@ next_track: STEREO_MONO : STEREO_NONINTERLEAVED); codec_set_replaygain(ci->id3); - if (samplesdone) { - flac_seek_offset(&fc, samplesdone); - samplesdone=0; - } + flac_seek_offset(&fc, samplesdone); + samplesdone=0; /* The main decoding loop */ frame=0; buf = ci->request_buffer(&bytesleft, MAX_FRAMESIZE); while (bytesleft) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - } /* Deal with any pending seek requests */ - if (ci->seek_time) { - if (flac_seek(&fc,(uint32_t)(((uint64_t)(ci->seek_time-1) + if (action == CODEC_ACTION_SEEK_TIME) { + if (flac_seek(&fc,(uint32_t)(((uint64_t)param *ci->id3->frequency)/1000))) { /* Refill the input buffer */ buf = ci->request_buffer(&bytesleft, MAX_FRAMESIZE); } + + ci->set_elapsed(param); ci->seek_complete(); } if((res=flac_decode_frame(&fc,decoded0,decoded1,buf, bytesleft,ci->yield)) < 0) { LOGF("FLAC: Frame %d, error %d\n",frame,res); - retval = CODEC_ERROR; - goto done; + return CODEC_ERROR; } consumed=fc.gb.index/8; frame++; @@ -507,14 +506,7 @@ next_track: buf = ci->request_buffer(&bytesleft, MAX_FRAMESIZE); } - retval = CODEC_OK; -done: LOGF("FLAC: Decoded %lu samples\n",(unsigned long)samplesdone); - - if (ci->request_next_track()) - goto next_track; - -exit: - return retval; + return CODEC_OK; } diff --git a/apps/codecs/lib/codeclib.c b/apps/codecs/lib/codeclib.c index af0894c498..443c0bbdcf 100644 --- a/apps/codecs/lib/codeclib.c +++ b/apps/codecs/lib/codeclib.c @@ -33,6 +33,16 @@ unsigned char* mp3buf; // The actual MP3 buffer from Rockbox unsigned char* mallocbuf; // 512K from the start of MP3 buffer unsigned char* filebuf; // The rest of the MP3 buffer +/* this is the default codec entry point for when nothing needs to be done + on load or unload */ +enum codec_status __attribute__((weak)) +codec_main(enum codec_entry_call_reason reason) +{ + /* Nothing to do */ + return CODEC_OK; + (void)reason; +} + int codec_init(void) { mem_ptr = 0; @@ -41,7 +51,7 @@ int codec_init(void) return 0; } -void codec_set_replaygain(struct mp3entry* id3) +void codec_set_replaygain(const struct mp3entry *id3) { ci->configure(DSP_SET_TRACK_GAIN, id3->track_gain); ci->configure(DSP_SET_ALBUM_GAIN, id3->album_gain); @@ -49,19 +59,6 @@ void codec_set_replaygain(struct mp3entry* id3) ci->configure(DSP_SET_ALBUM_PEAK, id3->album_peak); } -/* Note: codec really needs its own private metdata copy for the current - track being processed in order to be stable. */ -int codec_wait_taginfo(void) -{ - while (!*ci->taginfo_ready && !ci->stop_codec && !ci->new_track) - ci->sleep(0); - if (ci->stop_codec) - return -1; - if (ci->new_track) - return 1; - return 0; -} - /* Various "helper functions" common to all the xxx2wav decoder plugins */ diff --git a/apps/codecs/lib/codeclib.h b/apps/codecs/lib/codeclib.h index 41b466ed1f..30091c5333 100644 --- a/apps/codecs/lib/codeclib.h +++ b/apps/codecs/lib/codeclib.h @@ -156,8 +156,7 @@ static inline unsigned int bs_generic(unsigned int v, int mode) /* Various codec helper functions */ int codec_init(void); -void codec_set_replaygain(struct mp3entry* id3); -int codec_wait_taginfo(void); /* 0 = success */ +void codec_set_replaygain(const struct mp3entry *id3); #ifdef RB_PROFILE void __cyg_profile_func_enter(void *this_fn, void *call_site) diff --git a/apps/codecs/mod.c b/apps/codecs/mod.c index 4ace721a1e..3dfaac663f 100644 --- a/apps/codecs/mod.c +++ b/apps/codecs/mod.c @@ -1218,47 +1218,37 @@ void synthrender(int32_t *renderbuffer, int samplecount) } } +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Make use of 44.1khz */ + ci->configure(DSP_SET_FREQUENCY, 44100); + /* Sample depth is 28 bit host endian */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 28); + /* Stereo output */ + ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED); + } + + return CODEC_OK; +} -enum codec_status codec_main(void) +/* this is called for each file to process */ +enum codec_status codec_run(void) { size_t n; unsigned char *modfile; int old_patterntableposition; - int bytesdone; + intptr_t param; -next_track: if (codec_init()) { return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto request_next_track; - codec_set_replaygain(ci->id3); /* Load MOD file */ - /* - * This is the save way - - size_t bytesfree; - unsigned int filesize; - - p = modfile; - bytesfree=sizeof(modfile); - while ((n = ci->read_filebuf(p, bytesfree)) > 0) { - p += n; - bytesfree -= n; - if (bytesfree == 0) - return CODEC_ERROR; - } - filesize = p-modfile; - - if (filesize == 0) - return CODEC_ERROR; - */ - - /* Directly use mod in buffer */ ci->seek_buffer(0); modfile = ci->request_buffer(&n, ci->filesize); if (!modfile || n < (size_t)ci->filesize) { @@ -1268,27 +1258,22 @@ next_track: initmodplayer(); loadmod(modfile); - /* Make use of 44.1khz */ - ci->configure(DSP_SET_FREQUENCY, 44100); - /* Sample depth is 28 bit host endian */ - ci->configure(DSP_SET_SAMPLE_DEPTH, 28); - /* Stereo output */ - ci->configure(DSP_SET_STEREO_MODE, STEREO_INTERLEAVED); - /* The main decoder loop */ ci->set_elapsed(0); bytesdone = 0; old_patterntableposition = 0; while (1) { - ci->yield(); - if (ci->stop_codec || ci->new_track) + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - if (ci->seek_time) { - /* New time is ready in ci->seek_time */ - modplayer.patterntableposition = ci->seek_time/1000; + if (action == CODEC_ACTION_SEEK_TIME) { + /* New time is ready in param */ + modplayer.patterntableposition = param/1000; modplayer.currentline = 0; + ci->set_elapsed(modplayer.patterntableposition*1000+500); ci->seek_complete(); } @@ -1305,9 +1290,5 @@ next_track: } -request_next_track: - if (ci->request_next_track()) - goto next_track; - return CODEC_OK; } diff --git a/apps/codecs/mp3_enc.c b/apps/codecs/mp3_enc.c index e7893fd14a..2f5528f74c 100644 --- a/apps/codecs/mp3_enc.c +++ b/apps/codecs/mp3_enc.c @@ -2584,45 +2584,46 @@ static bool enc_init(void) return true; } /* enc_init */ -enum codec_status codec_main(void) +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) { - /* Generic codec initialisation */ - if (!enc_init()) - return CODEC_ERROR; + if (reason == CODEC_LOAD) { + if (!enc_init()) + return CODEC_ERROR; + } + else if (reason == CODEC_UNLOAD) { + /* reset parameters to initial state */ + ci->enc_set_parameters(NULL); + } + + return CODEC_OK; +} +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ /* main encoding loop */ - while (!ci->stop_codec) + while(ci->get_command(NULL) != CODEC_ACTION_HALT) { - char *buffer; - - while ((buffer = ci->enc_get_pcm_data(pcm_chunk_size)) != NULL) - { - struct enc_chunk_hdr *chunk; - - if (ci->stop_codec) - break; + char *buffer = buffer = ci->enc_get_pcm_data(pcm_chunk_size); + struct enc_chunk_hdr *chunk; - chunk = ci->enc_get_chunk(); - chunk->enc_data = ENC_CHUNK_SKIP_HDR(chunk->enc_data, chunk); + if(buffer == NULL) + continue; - encode_frame(buffer, chunk); + chunk = ci->enc_get_chunk(); + chunk->enc_data = ENC_CHUNK_SKIP_HDR(chunk->enc_data, chunk); - if (chunk->num_pcm < samp_per_frame) - { - ci->enc_unget_pcm_data(pcm_chunk_size - chunk->num_pcm*4); - chunk->num_pcm = samp_per_frame; - } + encode_frame(buffer, chunk); - ci->enc_finish_chunk(); - - ci->yield(); + if (chunk->num_pcm < samp_per_frame) + { + ci->enc_unget_pcm_data(pcm_chunk_size - chunk->num_pcm*4); + chunk->num_pcm = samp_per_frame; } - ci->yield(); + ci->enc_finish_chunk(); } - /* reset parameters to initial state */ - ci->enc_set_parameters(NULL); - return CODEC_OK; -} /* codec_start */ +} diff --git a/apps/codecs/mpa.c b/apps/codecs/mpa.c index 4b49775029..c9e2131450 100644 --- a/apps/codecs/mpa.c +++ b/apps/codecs/mpa.c @@ -268,8 +268,8 @@ static bool mad_synth_thread_create(void) static void mad_synth_thread_quit(void) { - /*mop up COP thread*/ - die=1; + /* mop up COP thread */ + die = 1; ci->semaphore_release(&synth_pending_sem); ci->thread_wait(mad_synth_thread_id); ci->cpucache_invalidate(); @@ -299,9 +299,30 @@ static inline void mad_synth_thread_unwait_pcm(void) #endif /* MPA_SYNTH_ON_COP */ /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Create a decoder instance */ + if (codec_init()) + return CODEC_ERROR; + + ci->configure(DSP_SET_SAMPLE_DEPTH, MAD_F_FRACBITS); + + /* does nothing on 1 processor systems except return true */ + if(!mad_synth_thread_create()) + return CODEC_ERROR; + } + else if (reason == CODEC_UNLOAD) { + /* mop up COP thread - MT only */ + mad_synth_thread_quit(); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { - int status; size_t size; int file_end; int samples_to_skip; /* samples to skip in total for this file (at start) */ @@ -312,27 +333,12 @@ enum codec_status codec_main(void) unsigned long current_frequency = 0; int framelength; int padding = MAD_BUFFER_GUARD; /* to help mad decode the last frame */ - - if (codec_init()) - return CODEC_ERROR; - - /* Create a decoder instance */ - - ci->configure(DSP_SET_SAMPLE_DEPTH, MAD_F_FRACBITS); - - /*does nothing on 1 processor systems except return true*/ - if(!mad_synth_thread_create()) - return CODEC_ERROR; - -next_track: - status = CODEC_OK; + intptr_t param; /* Reinitializing seems to be necessary to avoid playback quircks when seeking. */ init_mad(); file_end = 0; - if (codec_wait_taginfo() != 0) - goto request_next_track; ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); current_frequency = ci->id3->frequency; @@ -379,29 +385,35 @@ next_track: /* This is the decoding loop. */ while (1) { - ci->yield(); - if (ci->stop_codec || ci->new_track) + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - if (ci->seek_time) { + if (action == CODEC_ACTION_SEEK_TIME) { int newpos; /*make sure the synth thread is idle before seeking - MT only*/ mad_synth_thread_wait_pcm(); mad_synth_thread_unwait_pcm(); - samplesdone = ((int64_t)(ci->seek_time-1))*current_frequency/1000; + samplesdone = ((int64_t)param)*current_frequency/1000; - if (ci->seek_time-1 == 0) { + if (param == 0) { newpos = ci->id3->first_frame_offset; samples_to_skip = start_skip; } else { - newpos = get_file_pos(ci->seek_time-1); + newpos = get_file_pos(param); samples_to_skip = 0; } if (!ci->seek_buffer(newpos)) + { + ci->seek_complete(); break; + } + + ci->set_elapsed((samplesdone * 1000) / current_frequency); ci->seek_complete(); init_mad(); framelength = 0; @@ -435,8 +447,7 @@ next_track: continue; } else { /* Some other unrecoverable error */ - status = CODEC_ERROR; - break; + return CODEC_ERROR; } } @@ -504,12 +515,5 @@ next_track: framelength - stop_skip); } -request_next_track: - if (ci->request_next_track()) - goto next_track; - - /*mop up COP thread - MT only*/ - mad_synth_thread_quit(); - - return status; + return CODEC_OK; } diff --git a/apps/codecs/mpc.c b/apps/codecs/mpc.c index 187c37e597..bbe2d9943b 100644 --- a/apps/codecs/mpc.c +++ b/apps/codecs/mpc.c @@ -52,8 +52,20 @@ static mpc_int32_t get_size_impl(mpc_reader *reader) return ci->filesize; } -/* This is the codec entry point. */ -enum codec_status codec_main(void) +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* musepack's sample representation is 18.14 + * DSP_SET_SAMPLE_DEPTH = 14 (FRACT) + 16 (NATIVE) - 1 (SIGN) = 29 */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 29); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { mpc_int64_t samplesdone; uint32_t frequency; /* 0.1 kHz accuracy */ @@ -64,39 +76,27 @@ enum codec_status codec_main(void) mpc_streaminfo info; mpc_frame_info frame; mpc_demux *demux = NULL; - int retval; + intptr_t param; frame.buffer = sample_buffer; - - /* musepack's sample representation is 18.14 - * DSP_SET_SAMPLE_DEPTH = 14 (FRACT) + 16 (NATIVE) - 1 (SIGN) = 29 */ - ci->configure(DSP_SET_SAMPLE_DEPTH, 29); - + /* Create a decoder instance */ reader.read = read_impl; reader.seek = seek_impl; reader.tell = tell_impl; reader.get_size = get_size_impl; -next_track: - retval = CODEC_OK; - if (codec_init()) - { - retval = CODEC_ERROR; - goto exit; - } + return CODEC_ERROR; - if (codec_wait_taginfo() != 0) - goto done; + /* Prep position */ + ci->seek_buffer(0); /* Initialize demux/decoder. */ demux = mpc_demux_init(&reader); if (NULL == demux) - { - retval = CODEC_ERROR; - goto done; - } + return CODEC_ERROR; + /* Read file's streaminfo data. */ mpc_demux_get_info(demux, &info); @@ -117,11 +117,8 @@ next_track: ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); else if (info.channels == 1) ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); - else - { - retval = CODEC_ERROR; - goto done; - } + else + return CODEC_ERROR; codec_set_replaygain(ci->id3); @@ -142,21 +139,24 @@ next_track: /* This is the decoding loop. */ do { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + return CODEC_OK; + /* Complete seek handler. */ - if (ci->seek_time) + if (action == CODEC_ACTION_SEEK_TIME) { - mpc_int64_t new_offset = ((ci->seek_time - 1)/10)*frequency; + mpc_int64_t new_offset = (param/10)*frequency; if (mpc_demux_seek_sample(demux, new_offset) == MPC_STATUS_OK) { samplesdone = new_offset; - ci->set_elapsed(ci->seek_time); } + + elapsed_time = (samplesdone*10)/frequency; + ci->set_elapsed(elapsed_time); ci->seek_complete(); } - - /* Stop or skip occured, exit decoding loop. */ - if (ci->stop_codec || ci->new_track) - break; /* Decode one frame. */ status = mpc_demux_decode(demux, &frame); @@ -164,8 +164,7 @@ next_track: if (frame.bits == -1) { /* Decoding error, exit decoding loop. */ - retval = (status == MPC_STATUS_OK) ? CODEC_OK : CODEC_ERROR; - goto done; + return (status == MPC_STATUS_OK) ? CODEC_OK : CODEC_ERROR; } else { @@ -181,11 +180,4 @@ next_track: ci->set_offset( (samplesdone * byterate)/(frequency*100) ); } } while (true); - -done: - if (ci->request_next_track()) - goto next_track; - -exit: - return retval; } diff --git a/apps/codecs/nsf.c b/apps/codecs/nsf.c index 2f37da81d2..72f6974214 100644 --- a/apps/codecs/nsf.c +++ b/apps/codecs/nsf.c @@ -4307,46 +4307,44 @@ static void set_codec_track(int t, int d) { nSilenceTrackMS=5000; SetFadeTime(track,track+fade, fNSFPlaybackSpeed,def); } - ci->id3->elapsed=d*1000; /* d is track no to display */ + ci->set_elapsed(d*1000); /* d is track no to display */ } +/** Operational info **/ +static int track = 0; +static char last_path[MAX_PATH]; +static int dontresettrack = 0; + /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* we only render 16 bits, 44.1KHz, Stereo */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 16); + ci->configure(DSP_SET_FREQUENCY, 44100); + ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); + + RebuildOutputTables(); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { int written; uint8_t *buf; size_t n; int endofstream; /* end of stream flag */ - int track; - int dontresettrack; - char last_path[MAX_PATH]; - int usingplaylist; - - /* we only render 16 bits */ - ci->configure(DSP_SET_SAMPLE_DEPTH, 16); - - ci->configure(DSP_SET_FREQUENCY, 44100); - ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); - - RebuildOutputTables(); - - dontresettrack=0; - last_path[0]='\0'; - track=0; + int usingplaylist = 0; -next_track: - usingplaylist=0; DEBUGF("NSF: next_track\n"); if (codec_init()) { return CODEC_ERROR; } DEBUGF("NSF: after init\n"); - - /* wait for track info to load */ - if (codec_wait_taginfo() != 0) - goto request_next_track; - codec_set_replaygain(ci->id3); /* Read the entire file */ @@ -4408,22 +4406,27 @@ init_nsf: reset_profile_timers(); while (!endofstream) { + intptr_t param; + enum codec_command_action action = ci->get_command(¶m); - ci->yield(); - if (ci->stop_codec || ci->new_track) { + if (action == CODEC_ACTION_HALT) break; - } - if (ci->seek_time >0) { - track=ci->seek_time/1000; - if (usingplaylist) { - if (track>=nPlaylistSize) break; - } else { - if (track>=nTrackCount) break; + if (action == CODEC_ACTION_SEEK_TIME) { + if (param > 0) { + track=param/1000; + if (usingplaylist) { + if (track>=nPlaylistSize) break; + } else { + if (track>=nTrackCount) break; + } + dontresettrack=1; + ci->seek_complete(); + goto init_nsf; + } + else { + ci->seek_complete(); } - ci->seek_complete(); - dontresettrack=1; - goto init_nsf; } ENTER_TIMER(total); @@ -4449,22 +4452,17 @@ init_nsf: print_timers(last_path,track); -request_next_track: - if (ci->request_next_track()) { if (ci->global_settings->repeat_mode==REPEAT_ONE) { /* in repeat one mode just advance to the next track */ track++; if (track>=nTrackCount) track=0; dontresettrack=1; /* at this point we can't tell if another file has been selected */ - goto next_track; } else { /* otherwise do a proper load of the next file */ dontresettrack=0; last_path[0]='\0'; } - goto next_track; /* when we fall through here we'll reload the file */ - } return CODEC_OK; } diff --git a/apps/codecs/raac.c b/apps/codecs/raac.c index b322ae7df3..4b73f41462 100644 --- a/apps/codecs/raac.c +++ b/apps/codecs/raac.c @@ -35,8 +35,21 @@ static void init_rm(RMContext *rmctx) static RMContext rmctx; static RMPacket pkt; + /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); + ci->configure(DSP_SET_SAMPLE_DEPTH, 29); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { static NeAACDecFrameInfo frame_info; NeAACDecHandle decoder; @@ -49,26 +62,21 @@ enum codec_status codec_main(void) unsigned char c = 0; /* channels */ int playback_on = -1; size_t resume_offset; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); - ci->configure(DSP_SET_SAMPLE_DEPTH, 29); - -next_track: - err = CODEC_OK; + intptr_t param; + enum codec_command_action action = CODEC_ACTION_NULL; if (codec_init()) { DEBUGF("FAAD: Codec init error\n"); return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - resume_offset = ci->id3->offset; ci->memset(&rmctx,0,sizeof(RMContext)); ci->memset(&pkt,0,sizeof(RMPacket)); + + ci->seek_buffer(0); + init_rm(&rmctx); ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); codec_set_replaygain(ci->id3); @@ -78,9 +86,9 @@ next_track: if (!decoder) { DEBUGF("FAAD: Decode open error\n"); - err = CODEC_ERROR; - goto done; - } + return CODEC_ERROR; + } + NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(decoder); conf->outputFormat = FAAD_FMT_16BIT; /* irrelevant, we don't convert */ NeAACDecSetConfiguration(decoder, conf); @@ -91,8 +99,7 @@ next_track: if (err) { DEBUGF("FAAD: DecInit: %d, %d\n", err, decoder->object_type); - err = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* check for a mid-track resume and force a seek time accordingly */ @@ -100,36 +107,38 @@ next_track: resume_offset -= rmctx.data_offset + DATA_HEADER_SIZE; /* put number of subpackets to skip in resume_offset */ resume_offset /= (rmctx.block_align + PACKET_HEADER_SIZE); - ci->seek_time = (int)resume_offset * ((rmctx.block_align * 8 * 1000)/rmctx.bit_rate); + param = (int)resume_offset * ((rmctx.block_align * 8 * 1000)/rmctx.bit_rate); + action = CODEC_ACTION_SEEK_TIME; } - - ci->id3->frequency = s; + + ci->id3->frequency = s; /* FIXME: Won't get it to the UI */ ci->set_elapsed(0); ci->advance_buffer(rmctx.data_offset + DATA_HEADER_SIZE); /* The main decoding loop */ -seek_start: while (1) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { + if (action == CODEC_ACTION_NULL) + action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - } - if (ci->seek_time) { - + if (action == CODEC_ACTION_SEEK_TIME) { /* Do not allow seeking beyond the file's length */ - if ((unsigned) ci->seek_time > ci->id3->length) { + if ((unsigned) param > ci->id3->length) { + ci->set_elapsed(ci->id3->length); ci->seek_complete(); - goto done; + break; } ci->seek_buffer(rmctx.data_offset + DATA_HEADER_SIZE); /* Seek to the start of the track */ - if (ci->seek_time == 1) { + if (param == 0) { ci->set_elapsed(0); ci->seek_complete(); - goto seek_start; + action = CODEC_ACTION_NULL; + continue; } skipped = 0; @@ -141,21 +150,30 @@ seek_start: if(playback_on == -1) { /* Error only if packet-parsing failed and playback hadn't started */ DEBUGF("rm_get_packet failed\n"); + ci->seek_complete(); return CODEC_ERROR; } - else - goto done; + else { + ci->seek_complete(); + return CODEC_OK; + } } skipped += pkt.length; - if(pkt.timestamp > (unsigned)ci->seek_time) break; + + if(pkt.timestamp > (unsigned)param) + break; + ci->advance_buffer(pkt.length); } ci->seek_buffer(pkt_offset + rmctx.data_offset + DATA_HEADER_SIZE); buffer = ci->request_buffer(&n,rmctx.audio_framesize + 1000); NeAACDecPostSeekReset(decoder, decoder->frame); + ci->set_elapsed(pkt.timestamp); ci->seek_complete(); } + action = CODEC_ACTION_NULL; + /* Request the required number of bytes from the input buffer */ buffer=ci->request_buffer(&n,rmctx.audio_framesize + 1000); consumed = rm_get_packet(&buffer, &rmctx, &pkt); @@ -167,20 +185,20 @@ seek_start: return CODEC_ERROR; } else - goto done; + break; } playback_on = 1; if (pkt.timestamp >= ci->id3->length) - goto done; + break; + /* Decode one block - returned samples will be host-endian */ for(i = 0; i < rmctx.sub_packet_cnt; i++) { ret = NeAACDecDecode(decoder, &frame_info, buffer, rmctx.sub_packet_lengths[i]); buffer += rmctx.sub_packet_lengths[i]; if (frame_info.error > 0) { DEBUGF("FAAD: decode error '%s'\n", NeAACDecGetErrorMessage(frame_info.error)); - err = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } ci->pcmbuf_insert(decoder->time_out[0], decoder->time_out[1], @@ -191,11 +209,5 @@ seek_start: ci->advance_buffer(pkt.length); } -done: - if (ci->request_next_track()) - goto next_track; - -exit: - return err; + return CODEC_OK; } - diff --git a/apps/codecs/shorten.c b/apps/codecs/shorten.c index 83a9c34da8..db66991679 100644 --- a/apps/codecs/shorten.c +++ b/apps/codecs/shorten.c @@ -37,7 +37,19 @@ static int32_t offset1[MAX_OFFSET_SIZE] IBSS_ATTR; static int8_t ibuf[MAX_BUFFER_SIZE] IBSS_ATTR; /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); + ci->configure(DSP_SET_SAMPLE_DEPTH, SHN_OUTPUT_DEPTH-1); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { ShortenContext sc; uint32_t samplesdone; @@ -45,21 +57,14 @@ enum codec_status codec_main(void) int8_t *buf; int consumed, res, nsamples; size_t bytesleft; + intptr_t param; - /* Generic codec initialisation */ - ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); - ci->configure(DSP_SET_SAMPLE_DEPTH, SHN_OUTPUT_DEPTH-1); - -next_track: /* Codec initialization */ if (codec_init()) { LOGF("Shorten: codec_init error\n"); return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto request_next_track; - codec_set_replaygain(ci->id3); /* Shorten decoder initialization */ @@ -103,14 +108,15 @@ seek_start: samplesdone = 0; buf = ci->request_buffer(&bytesleft, MAX_BUFFER_SIZE); while (bytesleft) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - } /* Seek to start of track */ - if (ci->seek_time == 1) { - if (ci->seek_buffer(sc.header_bits/8 + ci->id3->first_frame_offset)) { + if (action == CODEC_ACTION_SEEK_TIME) { + if (param == 0 && + ci->seek_buffer(sc.header_bits/8 + ci->id3->first_frame_offset)) { sc.bitindex = sc.header_bits - 8*(sc.header_bits/8); ci->set_elapsed(0); ci->seek_complete(); @@ -128,7 +134,7 @@ seek_start: if (res == FN_ERROR) { LOGF("Shorten: shorten_decode_frames error (%lu)\n", (unsigned long)samplesdone); - break; + return CODEC_ERROR; } else { /* Insert decoded samples in pcmbuf */ if (nsamples) { @@ -153,9 +159,5 @@ seek_start: sc.bitindex = sc.gb.index - 8*consumed; } -request_next_track: - if (ci->request_next_track()) - goto next_track; - return CODEC_OK; } diff --git a/apps/codecs/sid.c b/apps/codecs/sid.c index 52c1289fff..0edbabe0b6 100644 --- a/apps/codecs/sid.c +++ b/apps/codecs/sid.c @@ -1203,34 +1203,47 @@ unsigned short LoadSIDFromMemory(void *pSidData, unsigned short *load_addr, return *load_addr; } +static int nSamplesRendered = 0; +static int nSamplesPerCall = 882; /* This is PAL SID single speed (44100/50Hz) */ +static int nSamplesToRender = 0; -enum codec_status codec_main(void) +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) { - unsigned int filesize; + if (reason == CODEC_LOAD) { + /* Make use of 44.1khz */ + ci->configure(DSP_SWITCH_FREQUENCY, 44100); + /* Sample depth is 28 bit host endian */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 28); + /* Mono output */ + ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); + } + + return CODEC_OK; +} +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ + unsigned int filesize; unsigned short load_addr, init_addr, play_addr; unsigned char subSongsMax, subSong, song_speed; + intptr_t param; - int nSamplesRendered = 0; - int nSamplesPerCall = 882; /* This is PAL SID single speed (44100/50Hz) */ - int nSamplesToRender = 0; - -next_track: if (codec_init()) { return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto request_next_track; - codec_set_replaygain(ci->id3); /* Load SID file the read_filebuf callback will return the full requested - * size if at all possible, so there is no need to loop */ + * size if at all possible, so there is no need to loop */ + ci->seek_buffer(0); filesize = ci->read_filebuf(sidfile, sizeof(sidfile)); - if (filesize == 0) + if (filesize == 0) { return CODEC_ERROR; + } c64Init(44100); LoadSIDFromMemory(sidfile, &load_addr, &init_addr, &play_addr, @@ -1239,37 +1252,30 @@ next_track: cpuJSR(init_addr, subSong); /* Start the song initialize */ - /* Make use of 44.1khz */ - ci->configure(DSP_SWITCH_FREQUENCY, 44100); - /* Sample depth is 28 bit host endian */ - ci->configure(DSP_SET_SAMPLE_DEPTH, 28); - /* Mono output */ - ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); - - /* Set the elapsed time to the current subsong (in seconds) */ ci->set_elapsed(subSong*1000); /* The main decoder loop */ while (1) { - ci->yield(); - if (ci->stop_codec || ci->new_track) + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - if (ci->seek_time) { - /* New time is ready in ci->seek_time */ + if (action == CODEC_ACTION_SEEK_TIME) { + /* New time is ready in param */ /* Start playing from scratch */ c64Init(44100); - LoadSIDFromMemory(sidfile, &load_addr, &init_addr, &play_addr, &subSongsMax, &subSong, &song_speed, filesize); - sidPoke(24, 15); /* Turn on full volume */ - subSong = ci->seek_time / 1000; /* Now use the current seek time in seconds as subsong */ - cpuJSR(init_addr, subSong); /* Start the song initialize */ - nSamplesToRender = 0; /* Start the rendering from scratch */ - - ci->seek_complete(); + LoadSIDFromMemory(sidfile, &load_addr, &init_addr, &play_addr, + &subSongsMax, &subSong, &song_speed, filesize); + sidPoke(24, 15); /* Turn on full volume */ + subSong = param / 1000; /* Now use the current seek time in seconds as subsong */ + cpuJSR(init_addr, subSong); /* Start the song initialize */ + nSamplesToRender = 0; /* Start the rendering from scratch */ /* Set the elapsed time to the current subsong (in seconds) */ + ci->seek_complete(); ci->set_elapsed(subSong*1000); } @@ -1306,9 +1312,5 @@ next_track: ci->pcmbuf_insert(samples, NULL, CHUNK_SIZE); } -request_next_track: - if (ci->request_next_track()) - goto next_track; - return CODEC_OK; } diff --git a/apps/codecs/smaf.c b/apps/codecs/smaf.c index 3e8a41387d..9211daa9aa 100644 --- a/apps/codecs/smaf.c +++ b/apps/codecs/smaf.c @@ -332,9 +332,20 @@ static uint8_t *read_buffer(size_t *realsize) return buffer; } -enum codec_status codec_main(void) +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { - int status; uint32_t decodedsamples; size_t n; int bufcount; @@ -342,20 +353,10 @@ enum codec_status codec_main(void) uint8_t *smafbuf; off_t firstblockposn; /* position of the first block in file */ const struct pcm_codec *codec; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); + intptr_t param; -next_track: - status = CODEC_OK; - - if (codec_init()) { - status = CODEC_ERROR; - goto exit; - } - - if (codec_wait_taginfo() != 0) - goto done; + if (codec_init()) + return CODEC_ERROR; codec_set_replaygain(ci->id3); @@ -365,24 +366,22 @@ next_track: decodedsamples = 0; codec = 0; + ci->seek_buffer(0); if (!parse_header(&format, &firstblockposn)) { - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } codec = get_codec(format.formattag); if (codec == 0) { DEBUGF("CODEC_ERROR: unsupport audio format: 0x%x\n", (int)format.formattag); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (!codec->set_format(&format)) { - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* check chunksize */ @@ -392,8 +391,7 @@ next_track: if (format.chunksize == 0) { DEBUGF("CODEC_ERROR: chunksize is 0\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); @@ -404,12 +402,10 @@ next_track: ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); } else { DEBUGF("CODEC_ERROR: more than 2 channels unsupported\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } ci->seek_buffer(firstblockposn); - ci->seek_complete(); /* make sure we're at the correct offset */ if (bytesdone > (uint32_t) firstblockposn) @@ -419,13 +415,13 @@ next_track: PCM_SEEK_POS, &read_buffer); if (newpos->pos > format.numbytes) - goto done; + return CODEC_OK; + if (ci->seek_buffer(firstblockposn + newpos->pos)) { bytesdone = newpos->pos; decodedsamples = newpos->samples; } - ci->seek_complete(); } else { @@ -437,23 +433,32 @@ next_track: endofstream = 0; while (!endofstream) { - ci->yield(); - if (ci->stop_codec || ci->new_track) + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - if (ci->seek_time) { - struct pcm_pos *newpos = codec->get_seek_pos(ci->seek_time, PCM_SEEK_TIME, + if (action == CODEC_ACTION_SEEK_TIME) { + struct pcm_pos *newpos = codec->get_seek_pos(param, PCM_SEEK_TIME, &read_buffer); if (newpos->pos > format.numbytes) + { + ci->set_elapsed(ci->id3->length); + ci->seek_complete(); break; + } + if (ci->seek_buffer(firstblockposn + newpos->pos)) { bytesdone = newpos->pos; decodedsamples = newpos->samples; } + + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); ci->seek_complete(); } + smafbuf = (uint8_t *)ci->request_buffer(&n, format.chunksize); if (n == 0) @@ -464,11 +469,10 @@ next_track: endofstream = 1; } - status = codec->decode(smafbuf, n, samples, &bufcount); - if (status == CODEC_ERROR) + if (codec->decode(smafbuf, n, samples, &bufcount) == CODEC_ERROR) { DEBUGF("codec error\n"); - goto done; + return CODEC_ERROR; } ci->pcmbuf_insert(samples, NULL, bufcount); @@ -482,11 +486,5 @@ next_track: ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); } -done: - if (ci->request_next_track()) - goto next_track; - -exit: - return status; + return CODEC_OK; } - diff --git a/apps/codecs/spc.c b/apps/codecs/spc.c index 4db2878964..1b49761810 100644 --- a/apps/codecs/spc.c +++ b/apps/codecs/spc.c @@ -260,14 +260,6 @@ static inline void samples_release_rdbuf(void) static inline int32_t * samples_get_rdbuf(void) { ci->semaphore_wait(&sample_queue.emu_sem_head, TIMEOUT_BLOCK); - - if (ci->stop_codec || ci->new_track) - { - /* Told to stop. Buffer must be released. */ - samples_release_rdbuf(); - return NULL; - } - return sample_queue.wav_chunk[sample_queue.head & WAV_CHUNK_MASK].audio; } @@ -390,11 +382,10 @@ static inline void spc_emu_quit(void) } } -static inline bool spc_play_get_samples(int32_t **samples) +static inline int32_t * spc_play_get_samples(void) { /* obtain filled samples buffer */ - *samples = samples_get_rdbuf(); - return *samples != NULL; + return samples_get_rdbuf(); } static inline void spc_play_send_samples(int32_t *samples) @@ -433,15 +424,14 @@ static inline void spc_play_send_samples(int32_t *samples) #define spc_emu_quit() #define samples_release_rdbuf() -static inline bool spc_play_get_samples(int32_t **samples) +static inline int32_t * spc_play_get_samples(void) { ENTER_TIMER(render); /* fill samples buffer */ if ( SPC_play(&spc_emu,WAV_CHUNK_SIZE*2,wav_chunk) ) assert( false ); EXIT_TIMER(render); - *samples = wav_chunk; - return true; + return wav_chunk; } #endif /* SPC_DUAL_CORE */ @@ -454,6 +444,7 @@ static int play_track( void ) unsigned long fadeendsample = (ID666.length+ID666.fade)*(long long) SAMPLE_RATE/1000; int fadedec = 0; int fadevol = 0x7fffffffl; + intptr_t param; if (fadeendsample>fadestartsample) fadedec=0x7fffffffl/(fadeendsample-fadestartsample)+1; @@ -462,25 +453,26 @@ static int play_track( void ) while ( 1 ) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - } - if (ci->seek_time) { + if (action == CODEC_ACTION_SEEK_TIME) { int curtime = sampleswritten*1000LL/SAMPLE_RATE; - DEBUGF("seek to %ld\ncurrently at %d\n",ci->seek_time,curtime); - if (ci->seek_time < curtime) { + DEBUGF("seek to %ld\ncurrently at %d\n", (long)param, curtime); + if (param < curtime) { DEBUGF("seek backwards = reset\n"); + ci->set_elapsed(0); ci->seek_complete(); return 1; } + + ci->set_elapsed(curtime); ci->seek_complete(); } - int32_t *samples; - if (!spc_play_get_samples(&samples)) - break; + int32_t *samples = spc_play_get_samples(); sampleswritten += WAV_CHUNK_SIZE; @@ -532,67 +524,61 @@ static int play_track( void ) } /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) { - enum codec_status stat = CODEC_ERROR; - - if (!spc_emu_start()) - goto codec_quit; - - do - { - DEBUGF("SPC: next_track\n"); - if (codec_init()) { - goto codec_quit; - } - DEBUGF("SPC: after init\n"); + if (reason == CODEC_LOAD) { + if (!spc_emu_start()) + return CODEC_ERROR; ci->configure(DSP_SET_SAMPLE_DEPTH, 24); ci->configure(DSP_SET_FREQUENCY, SAMPLE_RATE); ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED); + } + else if (reason == CODEC_UNLOAD) { + spc_emu_quit(); + } - /* wait for track info to load */ - if (codec_wait_taginfo() != 0) - continue; - - codec_set_replaygain(ci->id3); + return CODEC_OK; +} - /* Read the entire file */ - DEBUGF("SPC: request initial buffer\n"); - ci->seek_buffer(0); - size_t buffersize; - uint8_t* buffer = ci->request_buffer(&buffersize, ci->filesize); - if (!buffer) { - goto codec_quit; +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ + DEBUGF("SPC: next_track\n"); + if (codec_init()) + return CODEC_ERROR; + DEBUGF("SPC: after init\n"); + + codec_set_replaygain(ci->id3); + + /* Read the entire file */ + DEBUGF("SPC: request initial buffer\n"); + ci->seek_buffer(0); + size_t buffersize; + uint8_t* buffer = ci->request_buffer(&buffersize, ci->filesize); + if (!buffer) + return CODEC_ERROR; + + DEBUGF("SPC: read size = 0x%lx\n",(unsigned long)buffersize); + do + { + if (load_spc_buffer(buffer, buffersize)) { + DEBUGF("SPC load failure\n"); + return CODEC_ERROR; } - DEBUGF("SPC: read size = 0x%lx\n",(unsigned long)buffersize); - do - { - if (load_spc_buffer(buffer, buffersize)) { - DEBUGF("SPC load failure\n"); - goto codec_quit; - } - - LoadID666(buffer+0x2e); + LoadID666(buffer+0x2e); - if (ci->global_settings->repeat_mode!=REPEAT_ONE && ID666.length==0) { - ID666.length=3*60*1000; /* 3 minutes */ - ID666.fade=5*1000; /* 5 seconds */ - } - - reset_profile_timers(); + if (ci->global_settings->repeat_mode!=REPEAT_ONE && ID666.length==0) { + ID666.length=3*60*1000; /* 3 minutes */ + ID666.fade=5*1000; /* 5 seconds */ } - while ( play_track() ); - print_timers(ci->id3->path); + reset_profile_timers(); } - while ( ci->request_next_track() ); + while ( play_track() ); - stat = CODEC_OK; + print_timers(ci->id3->path); -codec_quit: - spc_emu_quit(); - - return stat; + return CODEC_OK; } diff --git a/apps/codecs/speex.c b/apps/codecs/speex.c index 7a1efa9753..e394efc3d5 100644 --- a/apps/codecs/speex.c +++ b/apps/codecs/speex.c @@ -367,11 +367,12 @@ static void *process_header(spx_ogg_packet *op, return st; } -/* this is the codec entry point */ -enum codec_status codec_main(void) +/* this is called for each file to process */ +enum codec_status codec_run(void) { + int error = CODEC_ERROR; + SpeexBits bits; - int error; int eof = 0; spx_ogg_sync_state oy; spx_ogg_page og; @@ -383,7 +384,7 @@ enum codec_status codec_main(void) int eos = 0; SpeexStereoState *stereo; int channels = -1; - int rate = 0, samplerate = 0; + int samplerate = 0; int extra_headers = 0; int stream_init = 0; int page_nb_packets, frame_size, packet_count = 0; @@ -392,26 +393,22 @@ enum codec_status codec_main(void) unsigned long strtoffset = 0; void *st = NULL; int j = 0; + intptr_t param; memset(&bits, 0, sizeof(bits)); memset(&oy, 0, sizeof(oy)); /* Ogg handling still uses mallocs, so reset the malloc buffer per track */ -next_track: - error = CODEC_OK; - if (codec_init()) { - error = CODEC_ERROR; goto exit; } + ci->seek_buffer(0); + stereo = speex_stereo_state_init(); spx_ogg_sync_init(&oy); spx_ogg_alloc_buffer(&oy,2*CHUNKSIZE); - if (codec_wait_taginfo() != 0) - goto done; - strtoffset = ci->id3->offset; samplerate = ci->id3->frequency; @@ -419,30 +416,32 @@ next_track: eof = 0; while (!eof) { - ci->yield(); - if (ci->stop_codec || ci->new_track) + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; /*seek (seeks to the page before the position) */ - if (ci->seek_time) { + if (action == CODEC_ACTION_SEEK_TIME) { if(samplerate!=0&&packet_count>1){ LOGF("Speex seek page:%lld,%lld,%ld,%lld,%d\n", - ((spx_int64_t)ci->seek_time/1000) * + ((spx_int64_t)param/1000) * (spx_int64_t)samplerate, - page_granule, ci->seek_time, + page_granule, param, (page_granule/samplerate)*1000, samplerate); - speex_seek_page_granule(((spx_int64_t)ci->seek_time/1000) * + speex_seek_page_granule(((spx_int64_t)param/1000) * (spx_int64_t)samplerate, page_granule, &oy, headerssize); - ci->seek_complete(); } + + ci->set_elapsed(param); + ci->seek_complete(); } next_page: /*Get the ogg buffer for writing*/ if(get_more_data(&oy)<1){/*read error*/ - error=CODEC_ERROR; goto done; } @@ -477,8 +476,7 @@ next_page: nframes=1; if (!st){ - error=CODEC_ERROR; - goto exit; + goto done; } ci->configure(DSP_SET_FREQUENCY, ci->id3->frequency); @@ -557,31 +555,18 @@ next_page: } done: - if (ci->request_next_track()) { - - /* Clean things up for the next track */ + /* Clean things up for the next track */ + speex_bits_destroy(&bits); + if (st) if (st) speex_decoder_destroy(st); - if (stream_init == 1) - spx_ogg_stream_reset(&os); - - spx_ogg_sync_reset(&oy); - - cur_granule = stream_init = rate = samplerate = headerssize - = packet_count = eos = 0; - - goto next_track; - } - -exit: - speex_bits_destroy(&bits); - if (stream_init) spx_ogg_stream_destroy(&os); spx_ogg_sync_destroy(&oy); +exit: return error; } diff --git a/apps/codecs/tta.c b/apps/codecs/tta.c index 1d0846ea61..c75f2b0a57 100644 --- a/apps/codecs/tta.c +++ b/apps/codecs/tta.c @@ -34,36 +34,36 @@ CODEC_HEADER static int32_t samples[PCM_BUFFER_LENGTH * 2] IBSS_ATTR; /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, TTA_OUTPUT_DEPTH - 1); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { tta_info info; - int status; unsigned int decodedsamples; int endofstream; int new_pos = 0; int sample_count; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, TTA_OUTPUT_DEPTH - 1); + intptr_t param; -next_track: - status = CODEC_OK; - if (codec_init()) { DEBUGF("codec_init() error\n"); - status = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; + ci->seek_buffer(0); if (set_tta_info(&info) < 0 || player_init(&info) < 0) - { - status = CODEC_ERROR; - goto exit; - } + return CODEC_ERROR; codec_set_replaygain(ci->id3); @@ -74,8 +74,8 @@ next_track: ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); } else { DEBUGF("CODEC_ERROR: more than 2 channels\n"); - status = CODEC_ERROR; - goto done; + player_stop(); + return CODEC_ERROR; } /* The main decoder loop */ @@ -88,31 +88,31 @@ next_track: new_pos = set_position(ci->id3->offset, TTA_SEEK_POS); if (new_pos >= 0) decodedsamples = new_pos; - ci->seek_complete(); } while (!endofstream) { - ci->yield(); - if (ci->stop_codec || ci->new_track) + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - if (ci->seek_time) + if (action == CODEC_ACTION_SEEK_TIME) { - new_pos = set_position(ci->seek_time / SEEK_STEP, TTA_SEEK_TIME); + new_pos = set_position(param / SEEK_STEP, TTA_SEEK_TIME); if (new_pos >= 0) { decodedsamples = new_pos; - ci->seek_complete(); } + + ci->set_elapsed((uint64_t)info.LENGTH * 1000 * decodedsamples / info.DATALENGTH); + ci->seek_complete(); } sample_count = get_samples(samples); if (sample_count < 0) - { - status = CODEC_ERROR; break; - } + ci->pcmbuf_insert(samples, NULL, sample_count); decodedsamples += sample_count; if (decodedsamples >= info.DATALENGTH) @@ -120,11 +120,6 @@ next_track: ci->set_elapsed((uint64_t)info.LENGTH * 1000 * decodedsamples / info.DATALENGTH); } -done: player_stop(); - if (ci->request_next_track()) - goto next_track; - -exit: - return status; + return CODEC_OK; } diff --git a/apps/codecs/vorbis.c b/apps/codecs/vorbis.c index 0a36a37c8b..e02d459262 100644 --- a/apps/codecs/vorbis.c +++ b/apps/codecs/vorbis.c @@ -104,14 +104,25 @@ static bool vorbis_set_codec_parameters(OggVorbis_File *vf) } /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + if (codec_init()) + return CODEC_ERROR; + ci->configure(DSP_SET_SAMPLE_DEPTH, 24); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { ov_callbacks callbacks; OggVorbis_File vf; ogg_int32_t **pcm; - bool initialized = false; /* First init done? */ - int error; + int error = CODEC_ERROR; long n; int current_section; int previous_section; @@ -120,36 +131,24 @@ enum codec_status codec_main(void) ogg_int64_t vf_dataoffsets; ogg_uint32_t vf_serialnos; ogg_int64_t vf_pcmlengths[2]; - - ci->configure(DSP_SET_SAMPLE_DEPTH, 24); - - if (codec_init()) { - error = CODEC_ERROR; - goto exit; - } + intptr_t param; #if defined(CPU_ARM) || defined(CPU_COLDFIRE) || defined(CPU_MIPS) if (setjmp(rb_jump_buf) != 0) { - /* malloc failed; skip to next track */ - error = CODEC_ERROR; + /* malloc failed; finish with this track */ goto done; } #endif - -next_track: - error = CODEC_OK; - ogg_malloc_init(); - if (codec_wait_taginfo() != 0) - goto done; - /* Create a decoder instance */ callbacks.read_func = read_handler; callbacks.seek_func = initial_seek_handler; callbacks.tell_func = tell_handler; callbacks.close_func = close_handler; + ci->seek_buffer(0); + /* Open a non-seekable stream */ error = ov_open_callbacks(ci, &vf, NULL, 0, callbacks); @@ -186,15 +185,13 @@ next_track: vf.end = ci->id3->filesize; vf.ready_state = OPENED; vf.links = 1; - initialized = true; } else { DEBUGF("Vorbis: ov_open failed: %d\n", error); - error = CODEC_ERROR; goto done; } if (ci->id3->offset) { - ci->advance_buffer(ci->id3->offset); + ci->seek_buffer(ci->id3->offset); ov_raw_seek(&vf, ci->id3->offset); ci->set_elapsed(ov_time_tell(&vf)); ci->set_offset(ov_raw_tell(&vf)); @@ -203,14 +200,17 @@ next_track: previous_section = -1; eof = 0; while (!eof) { - ci->yield(); - if (ci->stop_codec || ci->new_track) + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - if (ci->seek_time) { - if (ov_time_seek(&vf, ci->seek_time - 1)) { + if (action == CODEC_ACTION_SEEK_TIME) { + if (ov_time_seek(&vf, param)) { //ci->logf("ov_time_seek failed"); } + + ci->set_elapsed(ov_time_tell(&vf)); ci->seek_complete(); } @@ -220,7 +220,6 @@ next_track: /* Change DSP and buffer settings for this bitstream */ if (current_section != previous_section) { if (!vorbis_set_codec_parameters(&vf)) { - error = CODEC_ERROR; goto done; } else { previous_section = current_section; @@ -238,6 +237,7 @@ next_track: } } + error = CODEC_OK; done: #if 0 /* defined(SIMULATOR) */ { @@ -249,18 +249,12 @@ done: #endif ogg_malloc_destroy(); - if (ci->request_next_track()) { - if (!initialized) - goto next_track; - /* Clean things up for the next track */ - vf.dataoffsets = NULL; - vf.offsets = NULL; - vf.serialnos = NULL; - vf.pcmlengths = NULL; - ov_clear(&vf); - goto next_track; - } - -exit: + /* Clean things up for the next track */ + vf.dataoffsets = NULL; + vf.offsets = NULL; + vf.serialnos = NULL; + vf.pcmlengths = NULL; + ov_clear(&vf); + return error; } diff --git a/apps/codecs/vox.c b/apps/codecs/vox.c index c7f39342c3..bf274c6917 100644 --- a/apps/codecs/vox.c +++ b/apps/codecs/vox.c @@ -44,9 +44,19 @@ static uint8_t *read_buffer(size_t *realsize) } /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { - int status; uint32_t decodedsamples; size_t n; int bufcount; @@ -54,26 +64,18 @@ enum codec_status codec_main(void) uint8_t *voxbuf; off_t firstblockposn = 0; /* position of the first block in file */ const struct pcm_codec *codec; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); - -next_track: - status = CODEC_OK; + intptr_t param; if (codec_init()) { DEBUGF("codec_init() error\n"); - status = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - codec_set_replaygain(ci->id3); /* Need to save offset for later use (cleared indirectly by advance_buffer) */ bytesdone = ci->id3->offset; + ci->seek_buffer(0); ci->memset(&format, 0, sizeof(struct pcm_format)); @@ -96,20 +98,16 @@ next_track: if (!codec) { DEBUGF("CODEC_ERROR: dialogic oki adpcm codec does not load.\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } - if (!codec->set_format(&format)) - { - status = CODEC_ERROR; - goto done; + if (!codec->set_format(&format)) { + return CODEC_ERROR; } if (format.numbytes == 0) { DEBUGF("CODEC_ERROR: data size is 0\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* check chunksize */ @@ -118,8 +116,7 @@ next_track: if (format.chunksize == 0) { DEBUGF("CODEC_ERROR: chunksize is 0\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); @@ -131,14 +128,14 @@ next_track: struct pcm_pos *newpos = codec->get_seek_pos(bytesdone - firstblockposn, PCM_SEEK_POS, &read_buffer); - if (newpos->pos > format.numbytes) - goto done; + if (newpos->pos > format.numbytes) { + return CODEC_OK; + } if (ci->seek_buffer(firstblockposn + newpos->pos)) { bytesdone = newpos->pos; decodedsamples = newpos->samples; } - ci->seek_complete(); } else { /* already where we need to be */ bytesdone = 0; @@ -148,22 +145,29 @@ next_track: endofstream = 0; while (!endofstream) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - } - if (ci->seek_time) { - struct pcm_pos *newpos = codec->get_seek_pos(ci->seek_time, PCM_SEEK_TIME, + if (action == CODEC_ACTION_SEEK_TIME) { + struct pcm_pos *newpos = codec->get_seek_pos(param, PCM_SEEK_TIME, &read_buffer); if (newpos->pos > format.numbytes) + { + ci->set_elapsed(ci->id3->length); + ci->seek_complete(); break; + } + if (ci->seek_buffer(firstblockposn + newpos->pos)) { bytesdone = newpos->pos; decodedsamples = newpos->samples; } + + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); ci->seek_complete(); } @@ -175,11 +179,10 @@ next_track: endofstream = 1; } - status = codec->decode(voxbuf, n, samples, &bufcount); - if (status == CODEC_ERROR) + if (codec->decode(voxbuf, n, samples, &bufcount) == CODEC_ERROR) { DEBUGF("codec error\n"); - goto done; + return CODEC_ERROR; } ci->pcmbuf_insert(samples, NULL, bufcount); @@ -192,10 +195,5 @@ next_track: ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); } -done: - if (ci->request_next_track()) - goto next_track; - -exit: - return status; + return CODEC_OK; } diff --git a/apps/codecs/wav.c b/apps/codecs/wav.c index e179470f27..42bcc7081f 100644 --- a/apps/codecs/wav.c +++ b/apps/codecs/wav.c @@ -151,9 +151,19 @@ static uint8_t *read_buffer(size_t *realsize) } /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { - int status; uint32_t decodedsamples; size_t n; int bufcount; @@ -163,38 +173,28 @@ enum codec_status codec_main(void) off_t firstblockposn; /* position of the first block in file */ const struct pcm_codec *codec; uint32_t size; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); - -next_track: - status = CODEC_OK; + intptr_t param; if (codec_init()) { DEBUGF("codec_init() error\n"); - status = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - codec_set_replaygain(ci->id3); /* Need to save offset for later use (cleared indirectly by advance_buffer) */ bytesdone = ci->id3->offset; /* get RIFF chunk header */ + ci->seek_buffer(0); buf = ci->request_buffer(&n, 12); if (n < 12) { DEBUGF("request_buffer error\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if ((memcmp(buf, "RIFF", 4) != 0) || (memcmp(&buf[8], "WAVE", 4) != 0)) { DEBUGF("CODEC_ERROR: missing riff header\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* advance to first WAVE chunk */ @@ -215,8 +215,7 @@ next_track: if (n < 8) { DEBUGF("data chunk request_buffer error\n"); /* no more chunks, 'data' chunk must not have been found */ - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* chunkSize */ @@ -225,8 +224,7 @@ next_track: if (size < 16) { DEBUGF("CODEC_ERROR: 'fmt ' chunk size=%lu < 16\n", (unsigned long)size); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* wFormatTag */ format.formattag=buf[8]|(buf[9]<<8); @@ -256,8 +254,7 @@ next_track: if (format.size < 22) { DEBUGF("CODEC_ERROR: WAVE_FORMAT_EXTENSIBLE is " "missing extension\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* wValidBitsPerSample */ format.bitspersample = buf[26]|(buf[27]<<8); @@ -273,8 +270,7 @@ next_track: { if (!set_msadpcm_coeffs(buf)) { - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } } @@ -284,8 +280,7 @@ next_track: { DEBUGF("CODEC_ERROR: unsupported wave format 0x%x\n", (unsigned int) format.formattag); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* riff 8bit linear pcm is unsigned */ @@ -295,8 +290,7 @@ next_track: /* set format, parse codec specific tag, check format, and calculate chunk size */ if (!codec->set_format(&format)) { - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } } else if (memcmp(buf, "data", 4) == 0) { format.numbytes = size; @@ -324,31 +318,26 @@ next_track: if (!codec) { DEBUGF("CODEC_ERROR: 'fmt ' chunk not found\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* common format check */ if (format.channels == 0) { DEBUGF("CODEC_ERROR: 'fmt ' chunk not found or 0-channels file\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (format.samplesperblock == 0) { DEBUGF("CODEC_ERROR: 'fmt ' chunk not found or 0-wSamplesPerBlock file\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (format.blockalign == 0) { DEBUGF("CODEC_ERROR: 'fmt ' chunk not found or 0-blockalign file\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (format.numbytes == 0) { DEBUGF("CODEC_ERROR: 'data' chunk not found or has zero-length\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* check chunksize */ @@ -358,8 +347,7 @@ next_track: if (format.chunksize == 0) { DEBUGF("CODEC_ERROR: chunksize is 0\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); @@ -369,8 +357,7 @@ next_track: ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); } else { DEBUGF("CODEC_ERROR: more than 2 channels\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* make sure we're at the correct offset */ @@ -380,13 +367,12 @@ next_track: PCM_SEEK_POS, &read_buffer); if (newpos->pos > format.numbytes) - goto done; + return CODEC_OK; if (ci->seek_buffer(firstblockposn + newpos->pos)) { bytesdone = newpos->pos; decodedsamples = newpos->samples; } - ci->seek_complete(); } else { /* already where we need to be */ bytesdone = 0; @@ -396,22 +382,28 @@ next_track: endofstream = 0; while (!endofstream) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - } - if (ci->seek_time) { - struct pcm_pos *newpos = codec->get_seek_pos(ci->seek_time, PCM_SEEK_TIME, + if (action == CODEC_ACTION_SEEK_TIME) { + struct pcm_pos *newpos = codec->get_seek_pos(param, PCM_SEEK_TIME, &read_buffer); - if (newpos->pos > format.numbytes) + { + ci->set_elapsed(ci->id3->length); + ci->seek_complete(); break; + } + if (ci->seek_buffer(firstblockposn + newpos->pos)) { bytesdone = newpos->pos; decodedsamples = newpos->samples; } + + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); ci->seek_complete(); } @@ -423,11 +415,10 @@ next_track: endofstream = 1; } - status = codec->decode(wavbuf, n, samples, &bufcount); - if (status == CODEC_ERROR) + if (codec->decode(wavbuf, n, samples, &bufcount) == CODEC_ERROR) { DEBUGF("codec error\n"); - goto done; + return CODEC_ERROR; } ci->pcmbuf_insert(samples, NULL, bufcount); @@ -440,10 +431,5 @@ next_track: ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); } -done: - if (ci->request_next_track()) - goto next_track; - -exit: - return status; + return CODEC_OK; } diff --git a/apps/codecs/wav64.c b/apps/codecs/wav64.c index 9dbdab8368..c763e6f7f0 100644 --- a/apps/codecs/wav64.c +++ b/apps/codecs/wav64.c @@ -159,9 +159,19 @@ static uint8_t *read_buffer(size_t *realsize) } /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { - int status; uint32_t decodedsamples; size_t n; int bufcount; @@ -171,39 +181,29 @@ enum codec_status codec_main(void) off_t firstblockposn; /* position of the first block in file */ const struct pcm_codec *codec; uint64_t size; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, PCM_OUTPUT_DEPTH-1); - -next_track: - status = CODEC_OK; + intptr_t param; if (codec_init()) { DEBUGF("codec_init() error\n"); - status = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } - if (codec_wait_taginfo() != 0) - goto done; - codec_set_replaygain(ci->id3); /* Need to save offset for later use (cleared indirectly by advance_buffer) */ bytesdone = ci->id3->offset; /* get RIFF chunk header */ + ci->seek_buffer(0); buf = ci->request_buffer(&n, 40); if (n < 40) { DEBUGF("request_buffer error\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if ((memcmp(buf , WAVE64_GUID_RIFF, 16) != 0) || (memcmp(buf+24, WAVE64_GUID_WAVE, 16) != 0)) { - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* advance to first WAVE chunk */ @@ -224,8 +224,7 @@ next_track: if (n < 8) { DEBUGF("data chunk request_buffer error\n"); /* no more chunks, 'data' chunk must not have been found */ - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* chunkSize */ @@ -233,8 +232,7 @@ next_track: if (memcmp(buf, WAVE64_GUID_FMT, 16) == 0) { if (size < 16) { DEBUGF("CODEC_ERROR: 'fmt ' chunk size=%d < 16\n", (int)size); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* wFormatTag */ format.formattag=buf[24]|(buf[25]<<8); @@ -263,8 +261,7 @@ next_track: if (format.size < 22) { DEBUGF("CODEC_ERROR: WAVE_FORMAT_EXTENSIBLE is " "missing extension\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* wValidBitsPerSample */ format.bitspersample = buf[42]|(buf[43]<<8); @@ -279,10 +276,7 @@ next_track: if (format.formattag == WAVE_FORMAT_ADPCM) { if (!set_msadpcm_coeffs(buf)) - { - status = CODEC_ERROR; - goto done; - } + return CODEC_ERROR; } /* get codec */ @@ -291,8 +285,7 @@ next_track: { DEBUGF("CODEC_ERROR: unsupported wave format 0x%x\n", (unsigned int) format.formattag); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* riff 8bit linear pcm is unsigned */ @@ -301,10 +294,7 @@ next_track: /* check format, and calculate chunk size */ if (!codec->set_format(&format)) - { - status = CODEC_ERROR; - goto done; - } + return CODEC_ERROR; } else if (memcmp(buf, WAVE64_GUID_DATA, 16) == 0) { format.numbytes = size; /* advance to start of data */ @@ -330,31 +320,26 @@ next_track: if (!codec) { DEBUGF("CODEC_ERROR: 'fmt ' chunk not found\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* common format check */ if (format.channels == 0) { DEBUGF("CODEC_ERROR: 'fmt ' chunk not found or 0-channels file\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (format.samplesperblock == 0) { DEBUGF("CODEC_ERROR: 'fmt ' chunk not found or 0-wSamplesPerBlock file\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (format.blockalign == 0) { DEBUGF("CODEC_ERROR: 'fmt ' chunk not found or 0-blockalign file\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } if (format.numbytes == 0) { DEBUGF("CODEC_ERROR: 'data' chunk not found or has zero-length\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* check chunksize */ @@ -364,8 +349,7 @@ next_track: if (format.chunksize == 0) { DEBUGF("CODEC_ERROR: chunksize is 0\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); @@ -375,8 +359,7 @@ next_track: ci->configure(DSP_SET_STEREO_MODE, STEREO_MONO); } else { DEBUGF("CODEC_ERROR: more than 2 channels\n"); - status = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* make sure we're at the correct offset */ @@ -385,14 +368,14 @@ next_track: struct pcm_pos *newpos = codec->get_seek_pos(bytesdone - firstblockposn, PCM_SEEK_POS, &read_buffer); - if (newpos->pos > format.numbytes) - goto done; + if (newpos->pos > format.numbytes) { + return CODEC_OK; + } if (ci->seek_buffer(firstblockposn + newpos->pos)) { bytesdone = newpos->pos; decodedsamples = newpos->samples; } - ci->seek_complete(); } else { /* already where we need to be */ bytesdone = 0; @@ -402,22 +385,29 @@ next_track: endofstream = 0; while (!endofstream) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) break; - } - if (ci->seek_time) { - struct pcm_pos *newpos = codec->get_seek_pos(ci->seek_time, PCM_SEEK_TIME, + if (action == CODEC_ACTION_SEEK_TIME) { + struct pcm_pos *newpos = codec->get_seek_pos(param, PCM_SEEK_TIME, &read_buffer); if (newpos->pos > format.numbytes) + { + ci->set_elapsed(ci->id3->length); + ci->seek_complete(); break; + } + if (ci->seek_buffer(firstblockposn + newpos->pos)) { bytesdone = newpos->pos; decodedsamples = newpos->samples; } + + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); ci->seek_complete(); } @@ -429,11 +419,10 @@ next_track: endofstream = 1; } - status = codec->decode(wavbuf, n, samples, &bufcount); - if (status == CODEC_ERROR) + if (codec->decode(wavbuf, n, samples, &bufcount) == CODEC_ERROR) { DEBUGF("codec error\n"); - goto done; + return CODEC_ERROR; } ci->pcmbuf_insert(samples, NULL, bufcount); @@ -445,12 +434,6 @@ next_track: endofstream = 1; ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); } - status = CODEC_OK; - -done: - if (ci->request_next_track()) - goto next_track; -exit: - return status; + return CODEC_OK; } diff --git a/apps/codecs/wav_enc.c b/apps/codecs/wav_enc.c index ef1a88ec23..e4afeaf93c 100644 --- a/apps/codecs/wav_enc.c +++ b/apps/codecs/wav_enc.c @@ -345,40 +345,42 @@ static bool init_encoder(void) return true; } /* init_encoder */ -/* main codec entry point */ -enum codec_status codec_main(void) +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) { - if (!init_encoder()) - return CODEC_ERROR; + if (reason == CODEC_LOAD) { + if (!init_encoder()) + return CODEC_ERROR; + } + else if (reason == CODEC_UNLOAD) { + /* reset parameters to initial state */ + ci->enc_set_parameters(NULL); + } + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ /* main encoding loop */ - while(!ci->stop_codec) + while(ci->get_command(NULL) != CODEC_ACTION_HALT) { - uint32_t *src; + uint32_t *src = (uint32_t *)ci->enc_get_pcm_data(PCM_CHUNK_SIZE); + struct enc_chunk_hdr *chunk; - while ((src = (uint32_t *)ci->enc_get_pcm_data(PCM_CHUNK_SIZE)) != NULL) - { - struct enc_chunk_hdr *chunk; - - if (ci->stop_codec) - break; + if(src == NULL) + continue; - chunk = ci->enc_get_chunk(); - chunk->enc_size = enc_size; - chunk->num_pcm = PCM_SAMP_PER_CHUNK; - chunk->enc_data = ENC_CHUNK_SKIP_HDR(chunk->enc_data, chunk); + chunk = ci->enc_get_chunk(); + chunk->enc_size = enc_size; + chunk->num_pcm = PCM_SAMP_PER_CHUNK; + chunk->enc_data = ENC_CHUNK_SKIP_HDR(chunk->enc_data, chunk); - chunk_to_wav_format(src, (uint32_t *)chunk->enc_data); + chunk_to_wav_format(src, (uint32_t *)chunk->enc_data); - ci->enc_finish_chunk(); - ci->yield(); - } - - ci->yield(); + ci->enc_finish_chunk(); } - /* reset parameters to initial state */ - ci->enc_set_parameters(NULL); - return CODEC_OK; -} /* codec_start */ +} diff --git a/apps/codecs/wavpack.c b/apps/codecs/wavpack.c index d27a9fb621..ccb9f41190 100644 --- a/apps/codecs/wavpack.c +++ b/apps/codecs/wavpack.c @@ -31,39 +31,39 @@ static int32_t temp_buffer [BUFFER_SIZE] IBSS_ATTR; static int32_t read_callback (void *buffer, int32_t bytes) { int32_t retval = ci->read_filebuf (buffer, bytes); - ci->id3->offset = ci->curpos; + ci->set_offset(ci->curpos); return retval; } /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 28); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { WavpackContext *wpc; char error [80]; int bps, nchans, sr_100; - int retval; + intptr_t param; - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, 28); + if (codec_init()) + return CODEC_ERROR; -next_track: - retval = CODEC_OK; + ci->seek_buffer (ci->id3->offset); - if (codec_init()) { - retval = CODEC_ERROR; - goto exit; - } - - if (codec_wait_taginfo() != 0) - goto done; - /* Create a decoder instance */ wpc = WavpackOpenFileInput (read_callback, error); - if (!wpc) { - retval = CODEC_ERROR; - goto done; - } + if (!wpc) + return CODEC_ERROR; ci->configure(DSP_SWITCH_FREQUENCY, WavpackGetSampleRate (wpc)); codec_set_replaygain(ci->id3); @@ -77,56 +77,48 @@ next_track: /* The main decoder loop */ while (1) { - int32_t nsamples; + int32_t nsamples; + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; - if (ci->seek_time && ci->taginfo_ready && ci->id3->length) { - ci->seek_time--; + if (action == CODEC_ACTION_SEEK_TIME) { int curpos_ms = WavpackGetSampleIndex (wpc) / sr_100 * 10; int n, d, skip; - if (ci->seek_time > curpos_ms) { - n = ci->seek_time - curpos_ms; + if (param > curpos_ms) { + n = param - curpos_ms; d = ci->id3->length - curpos_ms; skip = (int)((int64_t)(ci->filesize - ci->curpos) * n / d); ci->seek_buffer (ci->curpos + skip); } - else { - n = curpos_ms - ci->seek_time; + else if (curpos_ms != 0) { + n = curpos_ms - param; d = curpos_ms; skip = (int)((int64_t) ci->curpos * n / d); ci->seek_buffer (ci->curpos - skip); } wpc = WavpackOpenFileInput (read_callback, error); - ci->seek_complete(); - if (!wpc) + { + ci->seek_complete(); break; + } ci->set_elapsed (WavpackGetSampleIndex (wpc) / sr_100 * 10); - ci->yield (); + ci->seek_complete(); } nsamples = WavpackUnpackSamples (wpc, temp_buffer, BUFFER_SIZE / nchans); - if (!nsamples || ci->stop_codec || ci->new_track) - break; - - ci->yield (); - - if (ci->stop_codec || ci->new_track) + if (!nsamples) break; ci->pcmbuf_insert (temp_buffer, NULL, nsamples); - ci->set_elapsed (WavpackGetSampleIndex (wpc) / sr_100 * 10); - ci->yield (); } -done: - if (ci->request_next_track()) - goto next_track; - -exit: - return retval; + return CODEC_OK; } diff --git a/apps/codecs/wavpack_enc.c b/apps/codecs/wavpack_enc.c index d908e284be..730cf0734b 100644 --- a/apps/codecs/wavpack_enc.c +++ b/apps/codecs/wavpack_enc.c @@ -389,77 +389,79 @@ static bool init_encoder(void) return true; } /* init_encoder */ -enum codec_status codec_main(void) +/* this is the codec entry point */ +enum codec_status codec_main(enum codec_entry_call_reason reason) { - /* initialize params and config */ - if (!init_encoder()) - return CODEC_ERROR; + if (reason == CODEC_LOAD) { + /* initialize params and config */ + if (!init_encoder()) + return CODEC_ERROR; + } + else if (reason == CODEC_UNLOAD) { + /* reset parameters to initial state */ + ci->enc_set_parameters(NULL); + } + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) +{ /* main encoding loop */ - while(!ci->stop_codec) + while(ci->get_command(NULL) != CODEC_ACTION_HALT) { - uint8_t *src; + uint8_t *src = (uint8_t *)ci->enc_get_pcm_data(PCM_CHUNK_SIZE); + struct enc_chunk_hdr *chunk; + bool abort_chunk; + uint8_t *dst; + uint8_t *src_end; - while ((src = ci->enc_get_pcm_data(PCM_CHUNK_SIZE)) != NULL) - { - struct enc_chunk_hdr *chunk; - bool abort_chunk; - uint8_t *dst; - uint8_t *src_end; - - if(ci->stop_codec) - break; - - abort_chunk = true; + if(src == NULL) + continue; - chunk = ci->enc_get_chunk(); + chunk = ci->enc_get_chunk(); - /* reset counts and pointer */ - chunk->enc_size = 0; - chunk->num_pcm = 0; - chunk->enc_data = NULL; + /* reset counts and pointer */ + chunk->enc_size = 0; + chunk->num_pcm = 0; + chunk->enc_data = NULL; - dst = ENC_CHUNK_SKIP_HDR(dst, chunk); + dst = ENC_CHUNK_SKIP_HDR(dst, chunk); - WavpackStartBlock(wpc, dst, dst + data_size); + WavpackStartBlock(wpc, dst, dst + data_size); - chunk_to_int32((uint32_t*)src); - src = input_buffer; - src_end = src + input_size; + chunk_to_int32((uint32_t*)src); + src = input_buffer; + src_end = src + input_size; - /* encode chunk in four steps yielding between each */ - do + /* encode chunk in four steps yielding between each */ + do + { + abort_chunk = true; + if (WavpackPackSamples(wpc, (int32_t *)src, + PCM_SAMP_PER_CHUNK/4)) { - if (WavpackPackSamples(wpc, (int32_t *)src, - PCM_SAMP_PER_CHUNK/4)) - { - chunk->num_pcm += PCM_SAMP_PER_CHUNK/4; - ci->yield(); - /* could've been stopped in some way */ - abort_chunk = ci->stop_codec || - (chunk->flags & CHUNKF_ABORT); - } - - src += input_step; + chunk->num_pcm += PCM_SAMP_PER_CHUNK/4; + ci->yield(); + /* could've been stopped in some way */ + abort_chunk = chunk->flags & CHUNKF_ABORT; } - while (!abort_chunk && src < src_end); - if (!abort_chunk) - { - chunk->enc_data = dst; - if (chunk->num_pcm < PCM_SAMP_PER_CHUNK) - ci->enc_unget_pcm_data(PCM_CHUNK_SIZE - chunk->num_pcm*4); - /* finish the chunk and store chunk size info */ - chunk->enc_size = WavpackFinishBlock(wpc); - ci->enc_finish_chunk(); - } + src += input_step; } + while (!abort_chunk && src < src_end); - ci->yield(); + if (!abort_chunk) + { + chunk->enc_data = dst; + if (chunk->num_pcm < PCM_SAMP_PER_CHUNK) + ci->enc_unget_pcm_data(PCM_CHUNK_SIZE - chunk->num_pcm*4); + /* finish the chunk and store chunk size info */ + chunk->enc_size = WavpackFinishBlock(wpc); + ci->enc_finish_chunk(); + } } - /* reset parameters to initial state */ - ci->enc_set_parameters(NULL); - return CODEC_OK; -} /* codec_start */ +} diff --git a/apps/codecs/wma.c b/apps/codecs/wma.c index 1b46813444..c327fafb5a 100644 --- a/apps/codecs/wma.c +++ b/apps/codecs/wma.c @@ -29,53 +29,52 @@ CODEC_HEADER static WMADecodeContext wmadec; /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 29); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { uint32_t elapsedtime; - int retval; asf_waveformatex_t wfx; size_t resume_offset; int i; - int wmares, res; + int wmares; + int res = 0; uint8_t* audiobuf; int audiobufsize; int packetlength = 0; int errcount = 0; - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, 29); - -next_track: - retval = CODEC_OK; + intptr_t param; /* Proper reset of the decoder context. */ memset(&wmadec, 0, sizeof(wmadec)); - /* Wait for the metadata to be read */ - if (codec_wait_taginfo() != 0) - goto done; - /* Remember the resume position - when the codec is opened, the playback engine will reset it. */ resume_offset = ci->id3->offset; restart_track: - retval = CODEC_OK; - if (codec_init()) { LOGF("WMA: Error initialising codec\n"); - retval = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } /* Copy the format metadata we've stored in the id3 TOC field. This saves us from parsing it again here. */ memcpy(&wfx, ci->id3->toc, sizeof(wfx)); + ci->seek_buffer(ci->id3->first_frame_offset); if (wma_decode_init(&wmadec,&wfx) < 0) { LOGF("WMA: Unsupported or corrupt file\n"); - retval = CODEC_ERROR; - goto exit; + return CODEC_ERROR; } if (resume_offset > ci->id3->first_frame_offset) @@ -101,34 +100,35 @@ restart_track: codec_set_replaygain(ci->id3); /* The main decoding loop */ - - res = 1; while (res >= 0) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { - goto done; - } + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; /* Deal with any pending seek requests */ - if (ci->seek_time){ + if (action == CODEC_ACTION_SEEK_TIME) { - if (ci->seek_time == 1) { + if (param == 0) { + ci->set_elapsed(0); ci->seek_complete(); goto restart_track; /* Pretend you never saw this... */ } - elapsedtime = asf_seek(ci->seek_time, &wfx); + elapsedtime = asf_seek(param, &wfx); if (elapsedtime < 1){ + ci->set_elapsed(0); ci->seek_complete(); - goto next_track; + break; } /*DEBUGF("Seek returned %d\n", (int)elapsedtime);*/ - ci->set_elapsed(elapsedtime); /*flush the wma decoder state*/ wmadec.last_superframe_len = 0; wmadec.last_bitoffset = 0; + + ci->set_elapsed(elapsedtime); ci->seek_complete(); } errcount = 0; @@ -140,10 +140,15 @@ new_packet: * times. If we succeed, the error counter will be reset. */ + if (res == ASF_ERROR_EOF) { + /* File ended - not an error */ + break; + } + errcount++; DEBUGF("read_packet error %d, errcount %d\n",wmares, errcount); if (errcount > 5) { - goto done; + return CODEC_ERROR; } else { ci->advance_buffer(packetlength); goto new_packet; @@ -163,7 +168,7 @@ new_packet: errcount++; DEBUGF("WMA decode error %d, errcount %d\n",wmares, errcount); if (errcount > 5) { - goto done; + return CODEC_ERROR; } else { ci->advance_buffer(packetlength); goto new_packet; @@ -173,18 +178,12 @@ new_packet: elapsedtime += (wmares*10)/(wfx.rate/100); ci->set_elapsed(elapsedtime); } - ci->yield(); } } ci->advance_buffer(packetlength); } -done: /*LOGF("WMA: Decoded %ld samples\n",elapsedtime*wfx.rate/1000);*/ - - if (ci->request_next_track()) - goto next_track; -exit: - return retval; + return CODEC_OK; } diff --git a/apps/codecs/wmapro.c b/apps/codecs/wmapro.c index c02dddeeb3..b6a8e47f25 100644 --- a/apps/codecs/wmapro.c +++ b/apps/codecs/wmapro.c @@ -27,11 +27,22 @@ CODEC_HEADER int32_t *dec[2]; /* pointers to the output buffers in WMAProDecodeCtx in wmaprodec.c */ + /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, WMAPRO_DSP_SAMPLE_DEPTH); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { uint32_t elapsedtime; - int retval; asf_waveformatex_t wfx; /* Holds the stream properties */ size_t resume_offset; int res; /* Return values from asf_read_packet() and decode_packet() */ @@ -42,28 +53,15 @@ enum codec_status codec_main(void) int pktcnt = 0; /* Count of the packets played */ uint8_t *data; /* Pointer to decoder input buffer */ int size; /* Size of the input frame to the decoder */ - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, WMAPRO_DSP_SAMPLE_DEPTH); - - -next_track: - retval = CODEC_OK; - - /* Wait for the metadata to be read */ - if (codec_wait_taginfo() != 0) - goto done; + intptr_t param; /* Remember the resume position */ resume_offset = ci->id3->offset; restart_track: - retval = CODEC_OK; - if (codec_init()) { LOGF("(WMA PRO) Error: Error initialising codec\n"); - retval = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* Copy the format metadata we've stored in the id3 TOC field. This @@ -77,8 +75,7 @@ restart_track: if (decode_init(&wfx) < 0) { LOGF("(WMA PRO) Error: Unsupported or corrupt file\n"); - retval = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* Now advance the file position to the first frame */ @@ -91,23 +88,24 @@ restart_track: while (pktcnt < wfx.numpackets) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { - goto done; - } - - /* Deal with any pending seek requests */ - if (ci->seek_time){ + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; - if (ci->seek_time == 1) { + /* Deal with any pending seek requests */ + if (action == CODEC_ACTION_SEEK_TIME) { + if (param == 0) { + ci->set_elapsed(0); ci->seek_complete(); goto restart_track; /* Pretend you never saw this... */ } - elapsedtime = asf_seek(ci->seek_time, &wfx); + elapsedtime = asf_seek(param, &wfx); if (elapsedtime < 1){ + ci->set_elapsed(0); ci->seek_complete(); - goto next_track; + break; } ci->set_elapsed(elapsedtime); @@ -117,8 +115,8 @@ restart_track: res = asf_read_packet(&audiobuf, &audiobufsize, &packetlength, &wfx); if (res < 0) { - LOGF("(WMA PRO) Warning: asf_read_packet returned %d", res); - goto done; + LOGF("(WMA PRO) Warning: asf_read_packet returned %d", res); + return CODEC_ERROR; } else { data = audiobuf; size = audiobufsize; @@ -132,7 +130,7 @@ restart_track: res = decode_packet(&wfx, dec, &outlen, data, size); if(res < 0) { LOGF("(WMA PRO) Error: decode_packet returned %d", res); - goto done; + return CODEC_ERROR; } data += res; size -= res; @@ -152,10 +150,6 @@ restart_track: ci->advance_buffer(packetlength); } -done: - if (ci->request_next_track()) - goto next_track; - - return retval; + return CODEC_OK; } diff --git a/apps/codecs/wmavoice.c b/apps/codecs/wmavoice.c index ddf66828f1..64c8cd1692 100644 --- a/apps/codecs/wmavoice.c +++ b/apps/codecs/wmavoice.c @@ -52,10 +52,20 @@ static void init_codec_ctx(AVCodecContext *avctx, asf_waveformatex_t *wfx) } /* this is the codec entry point */ -enum codec_status codec_main(void) +enum codec_status codec_main(enum codec_entry_call_reason reason) +{ + if (reason == CODEC_LOAD) { + /* Generic codec initialisation */ + ci->configure(DSP_SET_SAMPLE_DEPTH, 31); + } + + return CODEC_OK; +} + +/* this is called for each file to process */ +enum codec_status codec_run(void) { uint32_t elapsedtime; - int retval; asf_waveformatex_t wfx; /* Holds the stream properties */ size_t resume_offset; int res; /* Return values from asf_read_packet() and decode_packet() */ @@ -64,27 +74,14 @@ enum codec_status codec_main(void) int packetlength = 0; /* Logical packet size (minus the header size) */ int outlen = 0; /* Number of bytes written to the output buffer */ int pktcnt = 0; /* Count of the packets played */ - - /* Generic codec initialisation */ - ci->configure(DSP_SET_SAMPLE_DEPTH, 31); - - -next_track: - retval = CODEC_OK; - - /* Wait for the metadata to be read */ - if (codec_wait_taginfo() != 0) - goto done; + intptr_t param; /* Remember the resume position */ resume_offset = ci->id3->offset; restart_track: - retval = CODEC_OK; - if (codec_init()) { LOGF("(WMA Voice) Error: Error initialising codec\n"); - retval = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* Copy the format metadata we've stored in the id3 TOC field. This @@ -97,14 +94,15 @@ restart_track: ci->configure(DSP_SET_STEREO_MODE, wfx.channels == 1 ? STEREO_MONO : STEREO_INTERLEAVED); codec_set_replaygain(ci->id3); + + ci->seek_buffer(0); /* Initialise the AVCodecContext */ init_codec_ctx(&avctx, &wfx); if (wmavoice_decode_init(&avctx) < 0) { LOGF("(WMA Voice) Error: Unsupported or corrupt file\n"); - retval = CODEC_ERROR; - goto done; + return CODEC_ERROR; } /* Now advance the file position to the first frame */ @@ -117,21 +115,24 @@ restart_track: while (pktcnt < wfx.numpackets) { - ci->yield(); - if (ci->stop_codec || ci->new_track) { - goto done; - } - + enum codec_command_action action = ci->get_command(¶m); + + if (action == CODEC_ACTION_HALT) + break; + /* Deal with any pending seek requests */ - if (ci->seek_time){ + if (action == CODEC_ACTION_SEEK_TIME) { + ci->set_elapsed(param); - if (ci->seek_time == 1) { + if (param == 0) { + ci->set_elapsed(0); ci->seek_complete(); goto restart_track; /* Pretend you never saw this... */ } - elapsedtime = asf_seek(ci->seek_time, &wfx); + elapsedtime = asf_seek(param, &wfx); if (elapsedtime < 1){ + ci->set_elapsed(0); ci->seek_complete(); goto next_track; } @@ -145,7 +146,7 @@ new_packet: if (res < 0) { LOGF("(WMA Voice) read_packet error %d\n",res); - goto done; + return CODEC_ERROR; } else { avpkt.data = audiobuf; avpkt.size = audiobufsize; @@ -165,8 +166,9 @@ new_packet: ci->advance_buffer(packetlength); goto new_packet; } - else - goto done; + else { + return CODEC_ERROR; + } } avpkt.data += res; avpkt.size -= res; @@ -186,10 +188,6 @@ new_packet: ci->advance_buffer(packetlength); } -done: - if (ci->request_next_track()) - goto next_track; - - return retval; + return CODEC_OK; } diff --git a/apps/gui/wps.c b/apps/gui/wps.c index c33268e6bd..069df09c36 100644 --- a/apps/gui/wps.c +++ b/apps/gui/wps.c @@ -219,10 +219,10 @@ static int skintouch_to_wps(struct wps_data *data) #endif case ACTION_TOUCH_SCROLLBAR: skin_get_global_state()->id3->elapsed = skin_get_global_state()->id3->length*offset/100; - if (!skin_get_global_state()->paused) #if (CONFIG_CODEC == SWCODEC) - audio_pre_ff_rewind(); + audio_pre_ff_rewind(); #else + if (!skin_get_global_state()->paused) audio_pause(); #endif audio_ff_rewind(skin_get_global_state()->id3->elapsed); @@ -300,10 +300,10 @@ bool ffwd_rew(int button) if ( (audio_status() & AUDIO_STATUS_PLAY) && skin_get_global_state()->id3 && skin_get_global_state()->id3->length ) { - if (!skin_get_global_state()->paused) #if (CONFIG_CODEC == SWCODEC) - audio_pre_ff_rewind(); + audio_pre_ff_rewind(); #else + if (!skin_get_global_state()->paused) audio_pause(); #endif #if CONFIG_KEYPAD == PLAYER_PAD @@ -472,10 +472,10 @@ static void prev_track(unsigned long skip_thresh) return; } - if (!state->paused) #if (CONFIG_CODEC == SWCODEC) - audio_pre_ff_rewind(); + audio_pre_ff_rewind(); #else + if (!state->paused) audio_pause(); #endif @@ -554,16 +554,20 @@ static void play_hop(int direction) { elapsed += step * direction; } - if((audio_status() & AUDIO_STATUS_PLAY) && !state->paused) + if(audio_status() & AUDIO_STATUS_PLAY) { #if (CONFIG_CODEC == SWCODEC) audio_pre_ff_rewind(); #else - audio_pause(); + if (!state->paused) + audio_pause(); #endif } + +#if (CONFIG_CODEC == SWCODEC) + audio_ff_rewind(elapsed); +#else audio_ff_rewind(state->id3->elapsed = elapsed); -#if (CONFIG_CODEC != SWCODEC) if (!state->paused) audio_resume(); #endif @@ -849,10 +853,10 @@ long gui_wps_show(void) { if (state->id3->cuesheet) { - if (!state->paused) #if (CONFIG_CODEC == SWCODEC) - audio_pre_ff_rewind(); + audio_pre_ff_rewind(); #else + if (!state->paused) audio_pause(); #endif audio_ff_rewind(0); @@ -1146,6 +1150,17 @@ static void nextid3available_callback(void* param) skin_request_full_update(WPS); } +#ifdef AUDIO_FAST_SKIP_PREVIEW +/* this is called on the audio_skip caller thread */ +static void track_skip_callback(void *param) +{ + struct wps_state *state = skin_get_global_state(); + state->id3 = audio_current_track(); + state->nid3 = audio_next_track(); + skin_request_full_update(WPS); + (void)param; +} +#endif /* AUDIO_FAST_SKIP_PREVIEW */ static void wps_state_init(void) { @@ -1167,6 +1182,9 @@ static void wps_state_init(void) /* add the WPS track event callbacks */ add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, track_changed_callback); add_event(PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE, false, nextid3available_callback); +#ifdef AUDIO_FAST_SKIP_PREVIEW + add_event(PLAYBACK_EVENT_TRACK_SKIP, false, track_skip_callback); +#endif } diff --git a/apps/menus/playback_menu.c b/apps/menus/playback_menu.c index 9ed0bc5651..4153c1c776 100644 --- a/apps/menus/playback_menu.c +++ b/apps/menus/playback_menu.c @@ -159,9 +159,13 @@ static int cuesheet_callback(int action,const struct menu_item_ex *this_item) switch (action) { case ACTION_EXIT_MENUITEM: /* on exit */ +#if CONFIG_CODEC == SWCODEC + audio_set_cuesheet(global_settings.cuesheet); +#else if (global_settings.cuesheet) splash(HZ*2, ID2P(LANG_PLEASE_REBOOT)); break; +#endif } return action; } diff --git a/apps/metadata.c b/apps/metadata.c index 46b9482bc7..e88603721b 100644 --- a/apps/metadata.c +++ b/apps/metadata.c @@ -33,7 +33,7 @@ #if CONFIG_CODEC == SWCODEC -/* For trailing tag stripping */ +/* For trailing tag stripping and base audio data types */ #include "buffering.h" #include "metadata/metadata_common.h" @@ -239,6 +239,94 @@ const int afmt_rec_format[AFMT_NUM_CODECS] = }; #endif /* CONFIG_CODEC == SWCODEC && defined (HAVE_RECORDING) */ +#if CONFIG_CODEC == SWCODEC +/* Get the canonical AFMT type */ +int get_audio_base_codec_type(int type) +{ + int base_type = type; + switch (type) { + case AFMT_MPA_L1: + case AFMT_MPA_L2: + case AFMT_MPA_L3: + base_type = AFMT_MPA_L3; + break; + case AFMT_MPC_SV7: + case AFMT_MPC_SV8: + base_type = AFMT_MPC_SV7; + break; + case AFMT_MP4_AAC: + case AFMT_MP4_AAC_HE: + base_type = AFMT_MP4_AAC; + break; + case AFMT_SAP: + case AFMT_CMC: + case AFMT_CM3: + case AFMT_CMR: + case AFMT_CMS: + case AFMT_DMC: + case AFMT_DLT: + case AFMT_MPT: + case AFMT_MPD: + case AFMT_RMT: + case AFMT_TMC: + case AFMT_TM8: + case AFMT_TM2: + base_type = AFMT_SAP; + break; + default: + break; + } + + return base_type; +} + +/* Get the basic audio type */ +enum data_type get_audio_base_data_type(int afmt) +{ + if ((unsigned)afmt >= AFMT_NUM_CODECS) + return TYPE_UNKNOWN; + + switch (get_audio_base_codec_type(afmt)) + { + case AFMT_NSF: + case AFMT_SPC: + case AFMT_SID: + case AFMT_MOD: + case AFMT_SAP: + /* Type must be allocated and loaded in its entirety onto + the buffer */ + return TYPE_ATOMIC_AUDIO; + + default: + /* Assume type may be loaded and discarded incrementally */ + return TYPE_PACKET_AUDIO; + + case AFMT_UNKNOWN: + /* Have no idea at all */ + return TYPE_UNKNOWN; + } +} + +/* Is the format allowed to buffer starting at some offset other than 0 + or first frame only for resume purposes? */ +bool format_buffers_with_offset(int afmt) +{ + switch (afmt) + { + case AFMT_MPA_L1: + case AFMT_MPA_L2: + case AFMT_MPA_L3: + case AFMT_WAVPACK: + /* Format may be loaded at the first needed frame */ + return true; + default: + /* Format must be loaded from the beginning of the file + (does not imply 'atomic', while 'atomic' implies 'no offset') */ + return false; + } +} +#endif /* CONFIG_CODEC == SWCODEC */ + /* Simple file type probing by looking at the filename extension. */ unsigned int probe_file_format(const char *filename) @@ -313,7 +401,7 @@ bool get_metadata(struct mp3entry* id3, int fd, const char* trackname) } /* Clear the mp3entry to avoid having bogus pointers appear */ - memset(id3, 0, sizeof(struct mp3entry)); + wipe_mp3entry(id3); /* Take our best guess at the codec type based on file extension */ id3->codectype = probe_file_format(trackname); @@ -414,6 +502,44 @@ void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig) adjust_mp3entry(dest, dest, orig); } +/* A shortcut to simplify the common task of clearing the struct */ +void wipe_mp3entry(struct mp3entry *id3) +{ + memset(id3, 0, sizeof (struct mp3entry)); +} + +#if CONFIG_CODEC == SWCODEC +/* Glean what is possible from the filename alone - does not parse metadata */ +void fill_metadata_from_path(struct mp3entry *id3, const char *trackname) +{ + char *p; + + /* Clear the mp3entry to avoid having bogus pointers appear */ + wipe_mp3entry(id3); + + /* Find the filename portion of the path */ + p = strrchr(trackname, '/'); + strlcpy(id3->id3v2buf, p ? ++p : id3->path, ID3V2_BUF_SIZE); + + /* Get the format from the extension and trim it off */ + p = strrchr(id3->id3v2buf, '.'); + if (p) + { + /* Might be wrong for container formats - should we bother? */ + id3->codectype = probe_file_format(p); + + if (id3->codectype != AFMT_UNKNOWN) + *p = '\0'; + } + + /* Set the filename as the title */ + id3->title = id3->id3v2buf; + + /* Copy the path info */ + strlcpy(id3->path, trackname, sizeof (id3->path)); +} +#endif /* CONFIG_CODEC == SWCODEC */ + #ifndef __PCTOOL__ #ifdef HAVE_TAGCACHE #if CONFIG_CODEC == SWCODEC diff --git a/apps/metadata.h b/apps/metadata.h index c22c1b3ecf..b268a3d474 100644 --- a/apps/metadata.h +++ b/apps/metadata.h @@ -266,9 +266,7 @@ struct mp3entry { /* resume related */ unsigned long offset; /* bytes played */ -#if CONFIG_CODEC != SWCODEC int index; /* playlist index */ -#endif #ifdef HAVE_TAGCACHE unsigned char autoresumable; /* caches result of autoresumable() */ @@ -309,9 +307,14 @@ bool get_metadata(struct mp3entry* id3, int fd, const char* trackname); bool mp3info(struct mp3entry *entry, const char *filename); void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig); void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig); +void wipe_mp3entry(struct mp3entry *id3); #if CONFIG_CODEC == SWCODEC +void fill_metadata_from_path(struct mp3entry *id3, const char *trackname); +int get_audio_base_codec_type(int type); void strip_tags(int handle_id); +enum data_type get_audio_base_data_type(int afmt); +bool format_buffers_with_offset(int afmt); #endif #ifdef HAVE_TAGCACHE diff --git a/apps/metadata/nsf.c b/apps/metadata/nsf.c index abb4e6fd80..9207a14048 100644 --- a/apps/metadata/nsf.c +++ b/apps/metadata/nsf.c @@ -40,6 +40,9 @@ bool get_nsf_metadata(int fd, struct mp3entry* id3) p = id3->id3v2buf; + /* Length */ + id3->length = buf[6]*1000; + /* Title */ memcpy(p, &buf[14], 32); id3->title = p; diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c index f548b156b3..c7baad08e4 100644 --- a/apps/pcmbuf.c +++ b/apps/pcmbuf.c @@ -86,6 +86,8 @@ static size_t pcmbuffer_pos IDATA_ATTR; /* Amount pcmbuffer_pos will be increased.*/ static size_t pcmbuffer_fillpos IDATA_ATTR; +static struct chunkdesc *first_desc; + /* Gapless playback */ static bool track_transition IDATA_ATTR; @@ -144,6 +146,11 @@ static void write_to_crossfade(size_t length); static void pcmbuf_finish_crossfade_enable(void); #endif +/* Callbacks into playback.c */ +extern void audio_pcmbuf_position_callback(unsigned int time); +extern void audio_pcmbuf_track_change(bool pcmbuf); +extern bool audio_pcmbuf_may_play(void); + /**************************************/ @@ -153,9 +160,8 @@ static void pcmbuf_finish_crossfade_enable(void); #ifndef SIMULATOR #undef DESC_DEBUG #endif + #ifdef DESC_DEBUG -static struct chunkdesc *first_desc; -static bool show_desc_in_use = false; #define DISPLAY_DESC(caller) while(!show_desc(caller)) #define DESC_IDX(desc) (desc ? desc - first_desc : -1) #define SHOW_DESC(desc) if(DESC_IDX(desc)==-1) DEBUGF("--"); \ @@ -231,6 +237,7 @@ static void commit_chunk(bool flush_next_time) /* Flush! Discard all data after the currently playing chunk, and make the current chunk play next */ logf("commit_chunk: flush"); + pcm_play_lock(); write_end_chunk->link = read_chunk->link; read_chunk->link = pcmbuf_current; while (write_end_chunk->link) @@ -238,6 +245,9 @@ static void commit_chunk(bool flush_next_time) write_end_chunk = write_end_chunk->link; pcmbuf_unplayed_bytes -= write_end_chunk->size; } + + read_chunk->end_of_track = track_transition; + pcm_play_unlock(); } /* If there is already a read buffer setup, add to it */ else @@ -248,7 +258,7 @@ static void commit_chunk(bool flush_next_time) /* Otherwise create the buffer */ read_chunk = pcmbuf_current; } - + /* If flush_next_time is true, then the current chunk will be thrown out * and the next chunk to be committed will be the next to be played. * This is used to empty the PCM buffer for a track change. */ @@ -354,7 +364,7 @@ static bool prepare_insert(size_t length) #endif { logf("pcm starting"); - if (!(audio_status() & AUDIO_STATUS_PAUSE)) + if (audio_pcmbuf_may_play()) pcmbuf_play_start(); } } @@ -373,8 +383,12 @@ void *pcmbuf_request_buffer(int *count) /* crossfade has begun, put the new track samples in fadebuf */ if (crossfade_active) { - *count = MIN(*count, CROSSFADE_BUFSIZE/4); - return fadebuf; + int cnt = MIN(*count, CROSSFADE_BUFSIZE/4); + if (prepare_insert(cnt << 2)) + { + *count = cnt; + return fadebuf; + } } else #endif @@ -421,9 +435,7 @@ void pcmbuf_write_complete(int count) static inline void init_pcmbuffers(void) { -#ifdef DESC_DEBUG first_desc = write_chunk; -#endif struct chunkdesc *next = write_chunk; next++; write_end_chunk = write_chunk; @@ -494,19 +506,27 @@ void pcmbuf_monitor_track_change(bool monitor) currently playing chunk. If not, cancel notification. */ track_transition = monitor; read_end_chunk->end_of_track = monitor; + if (!monitor) + { + /* Clear all notifications */ + struct chunkdesc *desc = first_desc; + struct chunkdesc *end = desc + pcmbuf_descs(); + while (desc < end) + desc++->end_of_track = false; + } } else { /* Post now if PCM stopped and last buffer was sent. */ track_transition = false; if (monitor) - audio_post_track_change(false); + audio_pcmbuf_track_change(false); } pcm_play_unlock(); } -void pcmbuf_start_track_change(bool auto_skip) +bool pcmbuf_start_track_change(bool auto_skip) { bool crossfade = false; #ifdef HAVE_CROSSFADE @@ -546,9 +566,6 @@ void pcmbuf_start_track_change(bool auto_skip) /* Cancel any pending automatic gapless transition */ pcmbuf_monitor_track_change(false); - - /* Notify the wps that the track change starts now */ - audio_post_track_change(false); /* Can't do two crossfades at once and, no fade if pcm is off now */ if ( @@ -559,7 +576,8 @@ void pcmbuf_start_track_change(bool auto_skip) { pcmbuf_play_stop(); pcm_play_unlock(); - return; + /* Notify playback that the track change starts now */ + return true; } /* Not enough data, or not crossfading, flush the old data instead */ @@ -584,6 +602,9 @@ void pcmbuf_start_track_change(bool auto_skip) /* Keep trigger outside the play lock or HW FIFO underruns can happen since frequency scaling is *not* always fast */ trigger_cpu_boost(); + + /* Notify playback that the track change starts now */ + return true; } else /* automatic and not crossfading, so do gapless track change */ { @@ -593,6 +614,7 @@ void pcmbuf_start_track_change(bool auto_skip) * as the last one in the track. */ logf(" gapless track change"); pcmbuf_monitor_track_change(true); + return false; } } @@ -623,7 +645,7 @@ static void pcmbuf_pcm_callback(unsigned char** start, size_t* size) if (pcmbuf_current->end_of_track) { track_transition = false; - audio_post_track_change(true); + audio_pcmbuf_track_change(true); } /* Put the finished chunk back into circulation */ @@ -955,9 +977,6 @@ static void write_to_crossfade(size_t length) return; } - /* Commit samples to the buffer */ - while (!prepare_insert(length)) - sleep(1); while (length > 0) { COMMIT_IF_NEEDED; diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h index 618b1babad..b7bf8c2b16 100644 --- a/apps/pcmbuf.h +++ b/apps/pcmbuf.h @@ -33,13 +33,21 @@ void pcmbuf_play_start(void); void pcmbuf_play_stop(void); void pcmbuf_pause(bool pause); void pcmbuf_monitor_track_change(bool monitor); -void pcmbuf_start_track_change(bool manual_skip); +bool pcmbuf_start_track_change(bool manual_skip); /* Crossfade */ #ifdef HAVE_CROSSFADE bool pcmbuf_is_crossfade_active(void); void pcmbuf_request_crossfade_enable(bool on_off); bool pcmbuf_is_same_size(void); +#else +/* Dummy functions with sensible returns */ +static inline bool pcmbuf_is_crossfade_active(void) + { return false; } +static inline void pcmbuf_request_crossfade_enable(bool on_off) + { return; (void)on_off; } +static inline bool pcmbuf_is_same_size(void) + { return true; } #endif /* Voice */ diff --git a/apps/playback.c b/apps/playback.c index 632fd05d3d..a369d15715 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -9,6 +9,7 @@ * * Copyright (C) 2005-2007 Miika Pekkarinen * Copyright (C) 2007-2008 Nicolas Pennequin + * Copyright (C) 2011 Michael Sevakis * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,53 +20,61 @@ * KIND, either express or implied. * ****************************************************************************/ - -/* TODO: Pause should be handled in here, rather than PCMBUF so that voice can - * play whilst audio is paused */ #include "config.h" #include "system.h" -#include -#include "playback.h" -#include "codec_thread.h" #include "kernel.h" +#include "panic.h" +#include "buffer.h" +#include "sound.h" +#include "ata.h" +#include "usb.h" #include "codecs.h" -#include "buffering.h" +#include "codec_thread.h" #include "voice_thread.h" -#include "usb.h" -#include "ata.h" +#include "metadata.h" +#include "cuesheet.h" +#include "buffering.h" +#include "talk.h" #include "playlist.h" +#include "abrepeat.h" #include "pcmbuf.h" -#include "buffer.h" -#include "cuesheet.h" +#include "playback.h" + #ifdef HAVE_TAGCACHE #include "tagcache.h" #endif + +#ifdef AUDIO_HAVE_RECORDING +#include "pcm_record.h" +#endif + #ifdef HAVE_LCD_BITMAP #ifdef HAVE_ALBUMART #include "albumart.h" #endif #endif -#include "sound.h" -#include "metadata.h" -#include "splash.h" -#include "talk.h" -#include "panic.h" -#ifdef HAVE_RECORDING -#include "pcm_record.h" -#endif +/* TODO: The audio thread really is doing multitasking of acting like a + consumer and producer of tracks. It may be advantageous to better + logically separate the two functions. I won't go that far just yet. */ +/* Internal support for voice playback */ #define PLAYBACK_VOICE -/* amount of guess-space to allow for codecs that must hunt and peck - * for their correct seeek target, 32k seems a good size */ +#if CONFIG_PLATFORM & PLATFORM_NATIVE +/* Application builds don't support direct code loading */ +#define HAVE_CODEC_BUFFERING +#endif + +/* Amount of guess-space to allow for codecs that must hunt and peck + * for their correct seek target, 32k seems a good size */ #define AUDIO_REBUFFER_GUESS_SIZE (1024*32) /* Define LOGF_ENABLE to enable logf output in this file */ -/*#define LOGF_ENABLE*/ +/* #define LOGF_ENABLE */ #include "logf.h" -/* macros to enable logf for queues +/* Macros to enable logf for queues logging on SYS_TIMEOUT can be disabled */ #ifdef SIMULATOR /* Define this for logf output of all queuing except SYS_TIMEOUT */ @@ -86,202 +95,292 @@ #define LOGFQUEUE_SYS_TIMEOUT(...) #endif +/* Variables are commented with the threads that use them: + * A=audio, C=codec, O=other. A suffix of "-" indicates that the variable is + * read but not updated on that thread. Audio is the only user unless otherwise + * specified. + */ -static enum filling_state { - STATE_IDLE, /* audio is stopped: nothing to do */ - STATE_FILLING, /* adding tracks to the buffer */ - STATE_FULL, /* can't add any more tracks */ - STATE_END_OF_PLAYLIST, /* all remaining tracks have been added */ - STATE_FINISHED, /* all remaining tracks are fully buffered */ - STATE_ENDING, /* audio playback is ending */ -#if (CONFIG_PLATFORM & PLATFORM_NATIVE) - STATE_USB, /* USB mode, ignore most messages */ +/** Miscellaneous **/ +bool audio_is_initialized = false; /* (A,O-) */ +extern struct codec_api ci; /* (A,C) */ + +/** Possible arrangements of the main buffer **/ +static enum audio_buffer_state +{ + AUDIOBUF_STATE_TRASHED = -1, /* trashed; must be reset */ + AUDIOBUF_STATE_INITIALIZED = 0, /* voice+audio OR audio-only */ + AUDIOBUF_STATE_VOICED_ONLY = 1, /* voice-only */ +} buffer_state = AUDIOBUF_STATE_TRASHED; /* (A,O) */ + +/** Main state control **/ +static bool ff_rw_mode SHAREDBSS_ATTR = false; /* Pre-ff-rewind mode (A,O-) */ + +enum play_status +{ + PLAY_STOPPED = 0, + PLAY_PLAYING = AUDIO_STATUS_PLAY, + PLAY_PAUSED = AUDIO_STATUS_PLAY | AUDIO_STATUS_PAUSE, +} play_status = PLAY_STOPPED; + +/* Sizeable things that only need exist during playback and not when stopped */ +static struct audio_scratch_memory +{ + struct mp3entry codec_id3; /* (A,C) */ + struct mp3entry unbuffered_id3; + struct cuesheet *curr_cue; /* Will follow this structure */ +} * audio_scratch_memory = NULL; + +/* These are used to store the current, next and optionally the peek-ahead + * mp3entry's - this guarentees that the pointer returned by audio_current/ + * next_track will be valid for the full duration of the currently playing + * track */ +enum audio_id3_types +{ + /* These are allocated statically */ + PLAYING_ID3 = 0, + NEXTTRACK_ID3, +#ifdef AUDIO_FAST_SKIP_PREVIEW + /* The real playing metadata must has to be protected since it contains + critical info for other features */ + PLAYING_PEEK_ID3, #endif -} filling; - -/* As defined in plugins/lib/xxx2wav.h */ -#define GUARD_BUFSIZE (32*1024) - -bool audio_is_initialized = false; -static bool audio_thread_ready SHAREDBSS_ATTR = false; - -/* Variables are commented with the threads that use them: * - * A=audio, C=codec, V=voice. A suffix of - indicates that * - * the variable is read but not updated on that thread. */ -/* TBD: Split out "audio" and "playback" (ie. calling) threads */ - -/* Main state control */ -static volatile bool playing SHAREDBSS_ATTR = false;/* Is audio playing? (A) */ -static volatile bool paused SHAREDBSS_ATTR = false; /* Is audio paused? (A/C-) */ + ID3_TYPE_NUM_STATIC, + /* These go in the scratch memory */ + UNBUFFERED_ID3 = ID3_TYPE_NUM_STATIC, + CODEC_ID3, +}; +static struct mp3entry static_id3_entries[ID3_TYPE_NUM_STATIC]; /* (A,O) */ -/* Ring buffer where compressed audio and codecs are loaded */ -static unsigned char *filebuf = NULL; /* Start of buffer (A/C-) */ -static size_t filebuflen = 0; /* Size of buffer (A/C-) */ -/* FIXME: make buf_ridx (C/A-) */ +/* Peeking functions can yield and mess us up */ +static struct mutex id3_mutex SHAREDBSS_ATTR; /* (A,0)*/ -/* Possible arrangements of the buffer */ -enum audio_buffer_state -{ - AUDIOBUF_STATE_TRASHED = -1, /* trashed; must be reset */ - AUDIOBUF_STATE_INITIALIZED = 0, /* voice+audio OR audio-only */ - AUDIOBUF_STATE_VOICED_ONLY = 1, /* voice-only */ -}; -static int buffer_state = AUDIOBUF_STATE_TRASHED; /* Buffer state */ -/* These are used to store the current and next (or prev if the current is the last) - * mp3entry's in a round-robin system. This guarentees that the pointer returned - * by audio_current/next_track will be valid for the full duration of the - * currently playing track */ -static struct mp3entry mp3entry_buf[2]; -struct mp3entry *thistrack_id3, /* the currently playing track */ - *othertrack_id3; /* prev track during track-change-transition, or end of playlist, - * next track otherwise */ -static struct mp3entry unbuffered_id3; /* the id3 for the first unbuffered track */ +/** For Scrobbler support **/ -/* for cuesheet support */ -static struct cuesheet *curr_cue = NULL; +/* Previous track elapsed time */ +static unsigned long prev_track_elapsed = 0; /* (A,O-) */ +/** For album art support **/ #define MAX_MULTIPLE_AA SKINNABLE_SCREENS_COUNT - #ifdef HAVE_ALBUMART -static struct albumart_slot { - struct dim dim; /* holds width, height of the albumart */ - int used; /* counter, increments if something uses it */ -} albumart_slots[MAX_MULTIPLE_AA]; +static struct albumart_slot +{ + struct dim dim; /* Holds width, height of the albumart */ + int used; /* Counter; increments if something uses it */ +} albumart_slots[MAX_MULTIPLE_AA]; /* (A,O) */ #define FOREACH_ALBUMART(i) for(i = 0;i < MAX_MULTIPLE_AA; i++) -#endif +#endif /* HAVE_ALBUMART */ -#define MAX_TRACK 128 -#define MAX_TRACK_MASK (MAX_TRACK-1) +/** Information used for tracking buffer fills **/ -/* Track info structure about songs in the file buffer (A/C-) */ -static struct track_info { - int audio_hid; /* The ID for the track's buffer handle */ - int id3_hid; /* The ID for the track's metadata handle */ - int codec_hid; /* The ID for the track's codec handle */ -#ifdef HAVE_ALBUMART - int aa_hid[MAX_MULTIPLE_AA];/* The ID for the track's album art handle */ +/* Buffer and thread state tracking */ +static enum filling_state +{ + STATE_BOOT = 0, /* audio thread is not ready yet */ + STATE_IDLE, /* audio is stopped: nothing to do */ + STATE_FILLING, /* adding tracks to the buffer */ + STATE_FULL, /* can't add any more tracks */ + STATE_END_OF_PLAYLIST, /* all remaining tracks have been added */ + STATE_FINISHED, /* all remaining tracks are fully buffered */ + STATE_ENDING, /* audio playback is ending */ + STATE_ENDED, /* audio playback is done */ +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) + STATE_USB, /* USB mode, ignore most messages */ #endif - int cuesheet_hid; /* The ID for the track's parsed cueesheet handle */ +} filling = STATE_BOOT; - size_t filesize; /* File total length */ +/* Track info - holds information about each track in the buffer */ +struct track_info +{ + /* In per-track allocated order: */ + int id3_hid; /* Metadata handle ID */ + int cuesheet_hid; /* Parsed cueesheet handle ID */ +#ifdef HAVE_ALBUMART + int aa_hid[MAX_MULTIPLE_AA];/* Album art handle IDs */ +#endif +#ifdef HAVE_CODEC_BUFFERING + int codec_hid; /* Buffered codec handle ID */ +#endif + int audio_hid; /* Main audio data handle ID */ + size_t filesize; /* File total length on disk + TODO: This should be stored + in the handle or the + id3 and would use less + ram */ +}; - bool taginfo_ready; /* Is metadata read */ - -} tracks[MAX_TRACK]; +/* Track list - holds info about all buffered tracks */ +#if MEMORYSIZE >= 32 +#define TRACK_LIST_LEN 128 /* Must be 2^int(+n) */ +#elif MEMORYSIZE >= 16 +#define TRACK_LIST_LEN 64 +#elif MEMORYSIZE >= 8 +#define TRACK_LIST_LEN 32 +#else +#define TRACK_LIST_LEN 16 +#endif -static volatile int track_ridx = 0; /* Track being decoded (A/C-) */ -static int track_widx = 0; /* Track being buffered (A) */ -#define CUR_TI (&tracks[track_ridx]) /* Playing track info pointer (A/C-) */ +#define TRACK_LIST_MASK (TRACK_LIST_LEN-1) -static struct track_info *prev_ti = NULL; /* Pointer to the previously played - track */ +static struct +{ + /* read, write and current are maintained unwrapped, limited only by the + unsigned int range and wrap-safe comparisons are used */ -/* Information used only for filling the buffer */ -/* Playlist steps from playing track to next track to be buffered (A) */ -static int last_peek_offset = 0; + /* NOTE: there appears to be a bug in arm-elf-eabi-gcc 4.4.4 for ARMv4 where + if 'end' follows 'start' in this structure, track_list_count performs + 'start - end' rather than 'end - start', giving negative count values... + so leave it this way for now! */ + unsigned int end; /* Next open position */ + unsigned int start; /* First track in list */ + unsigned int current; /* Currently decoding track */ + struct track_info tracks[TRACK_LIST_LEN]; /* Buffered track information */ +} track_list; /* (A, O-) */ -/* Scrobbler support */ -static unsigned long prev_track_elapsed = 0; /* Previous track elapsed time (C/A-)*/ -/* Track change controls */ -static bool automatic_skip = false; /* Who initiated in-progress skip? (A) */ -extern bool track_transition; /* Are we in a track transition? */ -static bool dir_skip = false; /* Is a directory skip pending? (A) */ -static bool new_playlist = false; /* Are we starting a new playlist? (A) */ -static int wps_offset = 0; /* Pending track change offset, to keep WPS responsive (A) */ -static bool skipped_during_pause = false; /* Do we need to clear the PCM buffer when playback resumes (A) */ +/* Playlist steps from playlist position to next track to be buffered */ +static int playlist_peek_offset = 0; -static bool start_play_g = false; /* Used by audio_load_track to notify - audio_finish_load_track about start_play */ +/* Metadata handle of track load in progress (meaning all handles have not + yet been opened for the track, id3 always exists or the track does not) -/* True when a track load is in progress, i.e. audio_load_track() has returned - * but audio_finish_load_track() hasn't been called yet. Used to avoid allowing - * audio_load_track() to get called twice in a row, which would cause problems. - */ -static bool track_load_started = false; + Tracks are keyed by their metadata handles if track list pointers are + insufficient to make comparisons */ +static int in_progress_id3_hid = ERR_HANDLE_NOT_FOUND; #ifdef HAVE_DISK_STORAGE -static size_t buffer_margin = 5; /* Buffer margin aka anti-skip buffer (A/C-) */ +/* Buffer margin A.K.A. anti-skip buffer (in seconds) */ +static size_t buffer_margin = 5; #endif -/* Event queues */ -struct event_queue audio_queue SHAREDBSS_ATTR; -static struct event_queue pcmbuf_queue SHAREDBSS_ATTR; +/* Values returned for track loading */ +enum track_load_status +{ + LOAD_TRACK_ERR_START_CODEC = -6, + LOAD_TRACK_ERR_FINISH_FAILED = -5, + LOAD_TRACK_ERR_FINISH_FULL = -4, + LOAD_TRACK_ERR_BUSY = -3, + LOAD_TRACK_ERR_NO_MORE = -2, + LOAD_TRACK_ERR_FAILED = -1, + LOAD_TRACK_OK = 0, + LOAD_TRACK_READY = 1, +}; + +/** Track change controls **/ + +/* What sort of skip is pending globally? */ +enum track_skip_type +{ + /* Relative to what user is intended to see: */ + /* Codec: +0, Track List: +0, Playlist: +0 */ + TRACK_SKIP_NONE = 0, /* no track skip */ + /* Codec: +1, Track List: +1, Playlist: +0 */ + TRACK_SKIP_AUTO, /* codec-initiated skip */ + /* Codec: +1, Track List: +1, Playlist: +1 */ + TRACK_SKIP_AUTO_NEW_PLAYLIST, /* codec-initiated skip is new playlist */ + /* Codec: xx, Track List: +0, Playlist: +0 */ + TRACK_SKIP_AUTO_END_PLAYLIST, /* codec-initiated end of playlist */ + /* Manual skip: Never pends */ + TRACK_SKIP_MANUAL, /* manual track skip */ + /* Manual skip: Never pends */ + TRACK_SKIP_DIR_CHANGE, /* manual directory skip */ +} skip_pending = TRACK_SKIP_NONE; + +/* Note about TRACK_SKIP_AUTO_NEW_PLAYLIST: + Fixing playlist code to be able to peek into the first song of + the next playlist would fix any issues and this wouldn't need + to be a special case since pre-advancing the playlist would be + unneeded - it could be much more like TRACK_SKIP_AUTO and all + actions that require reversal during an in-progress transition + would work as expected */ + +/* Used to indicate status for the events. Must be separate to satisfy all + clients so the correct metadata is read when sending the change events + and also so that it is read correctly outside the events. */ +static bool automatic_skip = false; /* (A, O-) */ + +/* Pending manual track skip offset */ +static int skip_offset = 0; /* (A, O) */ + +/* Track change notification */ +static struct +{ + unsigned int in; /* Number of pcmbuf posts (audio isr) */ + unsigned int out; /* Number of times audio has read the difference */ +} track_change = { 0, 0 }; + +/** Codec status **/ +/* Did the codec notify us it finished while we were paused or while still + in an automatic transition? + + If paused, it is necessary to defer a codec-initiated skip until resuming + or else the track will move forward while not playing audio! + + If in-progress, skips should not build-up ahead of where the WPS is when + really short tracks finish decoding. + + If it is forgotten, it will be missed altogether and playback will just sit + there looking stupid and comatose until the user does something */ +static bool codec_skip_pending = false; +static int codec_skip_status; +static bool codec_seeking = false; /* Codec seeking ack expected? */ -extern struct codec_api ci; -extern unsigned int codec_thread_id; -/* Multiple threads */ -/* Set the watermark to trigger buffer fill (A/C) */ -static void set_filebuf_watermark(void); +/* Event queues */ +static struct event_queue audio_queue SHAREDBSS_ATTR; /* Audio thread */ static struct queue_sender_list audio_queue_sender_list SHAREDBSS_ATTR; static long audio_stack[(DEFAULT_STACK_SIZE + 0x1000)/sizeof(long)]; static const char audio_thread_name[] = "audio"; +static unsigned int audio_thread_id = 0; + +/* Forward declarations */ +enum audio_start_playback_flags +{ + AUDIO_START_RESTART = 0x1, /* "Restart" playback (flush _all_ tracks) */ + AUDIO_START_NEWBUF = 0x2, /* Mark the audiobuffer as invalid */ +}; -static void audio_thread(void); -static void audio_initiate_track_change(long direction); -static bool audio_have_tracks(void); -static void audio_reset_buffer(void); +static void audio_start_playback(size_t offset, unsigned int flags); static void audio_stop_playback(void); +static void buffer_event_buffer_low_callback(void *data); +static void buffer_event_rebuffer_callback(void *data); +static void buffer_event_finished_callback(void *data); + /**************************************/ -/** Pcmbuf callbacks */ +/** --- audio_queue helpers --- **/ -/* Between the codec and PCM track change, we need to keep updating the - * "elapsed" value of the previous (to the codec, but current to the - * user/PCM/WPS) track, so that the progressbar reaches the end. - * During that transition, the WPS will display othertrack_id3. */ -void audio_pcmbuf_position_callback(unsigned int time) +/* codec thread needs access */ +void audio_queue_post(long id, intptr_t data) { - time += othertrack_id3->elapsed; - othertrack_id3->elapsed = (time >= othertrack_id3->length) - ? othertrack_id3->length : time; + queue_post(&audio_queue, id, data); } -/* Post message from pcmbuf that the end of the previous track - * has just been played. */ -void audio_post_track_change(bool pcmbuf) +static intptr_t audio_queue_send(long id, intptr_t data) { - if (pcmbuf) - { - LOGFQUEUE("pcmbuf > pcmbuf Q_AUDIO_TRACK_CHANGED"); - queue_post(&pcmbuf_queue, Q_AUDIO_TRACK_CHANGED, 0); - } - else - { - LOGFQUEUE("pcmbuf > audio Q_AUDIO_TRACK_CHANGED"); - queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0); - } + return queue_send(&audio_queue, id, data); } -/* Scan the pcmbuf queue and return true if a message pulled */ -static bool pcmbuf_queue_scan(struct queue_event *ev) -{ - if (!queue_empty(&pcmbuf_queue)) - { - /* Transfer message to audio queue */ - pcm_play_lock(); - /* Pull message - never, ever any blocking call! */ - queue_wait_w_tmo(&pcmbuf_queue, ev, 0); - pcm_play_unlock(); - return true; - } - - return false; -} +/** --- MP3Entry --- **/ -/** Helper functions */ +/* Does the mp3entry have enough info for us to use it? */ +static struct mp3entry * valid_mp3entry(const struct mp3entry *id3) +{ + return id3 && (id3->length != 0 || id3->filesize != 0) && + id3->codectype != AFMT_UNKNOWN ? (struct mp3entry *)id3 : NULL; +} -static struct mp3entry *bufgetid3(int handle_id) +/* Return a pointer to an mp3entry on the buffer, as it is */ +static struct mp3entry * bufgetid3(int handle_id) { if (handle_id < 0) return NULL; @@ -295,6 +394,7 @@ static struct mp3entry *bufgetid3(int handle_id) return id3; } +/* Read an mp3entry from the buffer, adjusted */ static bool bufreadid3(int handle_id, struct mp3entry *id3out) { struct mp3entry *id3 = bufgetid3(handle_id); @@ -308,1200 +408,1441 @@ static bool bufreadid3(int handle_id, struct mp3entry *id3out) return false; } -static bool clear_track_info(struct track_info *track) +/* Lock the id3 mutex */ +static void id3_mutex_lock(void) { - /* bufclose returns true if the handle is not found, or if it is closed - * successfully, so these checks are safe on non-existant handles */ - if (!track) - return false; - - if (track->codec_hid >= 0) { - if (bufclose(track->codec_hid)) - track->codec_hid = -1; - else - return false; - } - - if (track->id3_hid >= 0) { - if (bufclose(track->id3_hid)) - track->id3_hid = -1; - else - return false; - } + mutex_lock(&id3_mutex); +} - if (track->audio_hid >= 0) { - if (bufclose(track->audio_hid)) - track->audio_hid = -1; - else - return false; - } +/* Unlock the id3 mutex */ +static void id3_mutex_unlock(void) +{ + mutex_unlock(&id3_mutex); +} -#ifdef HAVE_ALBUMART +/* Return one of the collection of mp3entry pointers - collect them all here */ +static inline struct mp3entry * id3_get(enum audio_id3_types id3_num) +{ + switch (id3_num) { - int i; - FOREACH_ALBUMART(i) - { - if (track->aa_hid[i] >= 0) { - if (bufclose(track->aa_hid[i])) - track->aa_hid[i] = -1; - else - return false; - } - } + case UNBUFFERED_ID3: + return &audio_scratch_memory->unbuffered_id3; + case CODEC_ID3: + return &audio_scratch_memory->codec_id3; + default: + return &static_id3_entries[id3_num]; } -#endif +} - if (track->cuesheet_hid >= 0) { - if (bufclose(track->cuesheet_hid)) - track->cuesheet_hid = -1; - else - return false; - } +/* Copy an mp3entry into one of the mp3 entries */ +static void id3_write(enum audio_id3_types id3_num, + const struct mp3entry *id3_src) +{ + struct mp3entry *dest_id3 = id3_get(id3_num); - track->filesize = 0; - track->taginfo_ready = false; + if (id3_src) + copy_mp3entry(dest_id3, id3_src); + else + wipe_mp3entry(dest_id3); +} - return true; +/* Call id3_write "safely" because peek aheads can yield, even if the fast + preview isn't enabled */ +static void id3_write_locked(enum audio_id3_types id3_num, + const struct mp3entry *id3_src) +{ + id3_mutex_lock(); + id3_write(id3_num, id3_src); + id3_mutex_unlock(); } -/* --- External interfaces --- */ -/* This sends a stop message and the audio thread will dump all it's - subsequenct messages */ -void audio_hard_stop(void) +/** --- Track info --- **/ + +/* Close a handle and mark it invalid */ +static void track_info_close_handle(int *hid_p) { - /* Stop playback */ - LOGFQUEUE("audio >| audio Q_AUDIO_STOP: 1"); - queue_send(&audio_queue, Q_AUDIO_STOP, 1); -#ifdef PLAYBACK_VOICE - voice_stop(); -#endif + int hid = *hid_p; + + /* bufclose returns true if the handle is not found, or if it is closed + * successfully, so these checks are safe on non-existant handles */ + if (hid >= 0) + bufclose(hid); + + /* Always reset to "no handle" in case it was something else */ + *hid_p = ERR_HANDLE_NOT_FOUND; } -bool audio_restore_playback(int type) +/* Close all handles in a struct track_info and clear it */ +static void track_info_close(struct track_info *info) { - switch (type) - { - case AUDIO_WANT_PLAYBACK: - if (buffer_state != AUDIOBUF_STATE_INITIALIZED) - audio_reset_buffer(); - return true; - case AUDIO_WANT_VOICE: - if (buffer_state == AUDIOBUF_STATE_TRASHED) - audio_reset_buffer(); - return true; - default: - return false; - } + /* Close them in the order they are allocated on the buffer to speed up + the handle searching */ + track_info_close_handle(&info->id3_hid); + track_info_close_handle(&info->cuesheet_hid); +#ifdef HAVE_ALBUMART + int i; + FOREACH_ALBUMART(i) + track_info_close_handle(&info->aa_hid[i]); +#endif +#ifdef HAVE_CODEC_BUFFERING + track_info_close_handle(&info->codec_hid); +#endif + track_info_close_handle(&info->audio_hid); + info->filesize = 0; } -unsigned char *audio_get_buffer(bool talk_buf, size_t *buffer_size) +/* Invalidate all members to initial values - does not close handles */ +static void track_info_wipe(struct track_info * info) { - unsigned char *buf, *end; - - if (audio_is_initialized) - { - audio_hard_stop(); - } - /* else buffer_state will be AUDIOBUF_STATE_TRASHED at this point */ - - /* Reset the buffering thread so that it doesn't try to use the data */ - buffering_reset(filebuf, filebuflen); - - if (buffer_size == NULL) - { - /* Special case for talk_init to use since it already knows it's - trashed */ - buffer_state = AUDIOBUF_STATE_TRASHED; - return NULL; - } + info->id3_hid = ERR_HANDLE_NOT_FOUND; + info->cuesheet_hid = ERR_HANDLE_NOT_FOUND; +#ifdef HAVE_ALBUMART + int i; + FOREACH_ALBUMART(i) + info->aa_hid[i] = ERR_HANDLE_NOT_FOUND; +#endif +#ifdef HAVE_CODEC_BUFFERING + info->codec_hid = ERR_HANDLE_NOT_FOUND; +#endif + info->audio_hid = ERR_HANDLE_NOT_FOUND; + info->filesize = 0; +} - if (talk_buf || buffer_state == AUDIOBUF_STATE_TRASHED - || !talk_voice_required()) - { - logf("get buffer: talk, audio"); - /* Ok to use everything from audiobuf to audiobufend - voice is loaded, - the talk buffer is not needed because voice isn't being used, or - could be AUDIOBUF_STATE_TRASHED already. If state is - AUDIOBUF_STATE_VOICED_ONLY, no problem as long as memory isn't written - without the caller knowing what's going on. Changing certain settings - may move it to a worse condition but the memory in use by something - else will remain undisturbed. - */ - if (buffer_state != AUDIOBUF_STATE_TRASHED) - { - talk_buffer_steal(); - buffer_state = AUDIOBUF_STATE_TRASHED; - } - buf = audiobuf; - end = audiobufend; - } - else - { - /* Safe to just return this if already AUDIOBUF_STATE_VOICED_ONLY or - still AUDIOBUF_STATE_INITIALIZED */ - /* Skip talk buffer and move pcm buffer to end to maximize available - contiguous memory - no audio running means voice will not need the - swap space */ - logf("get buffer: audio"); - buf = audiobuf + talk_get_bufsize(); - end = audiobufend - pcmbuf_init(audiobufend); - buffer_state = AUDIOBUF_STATE_VOICED_ONLY; - } +/** --- Track list --- **/ - *buffer_size = end - buf; +/* Initialize the track list */ +static void track_list_init(void) +{ + int i; + for (i = 0; i < TRACK_LIST_LEN; i++) + track_info_wipe(&track_list.tracks[i]); - return buf; + track_list.start = track_list.end = track_list.current; } -bool audio_buffer_state_trashed(void) +/* Return number of items allocated in the list */ +static unsigned int track_list_count(void) { - return buffer_state == AUDIOBUF_STATE_TRASHED; + return track_list.end - track_list.start; } -#ifdef HAVE_RECORDING -unsigned char *audio_get_recording_buffer(size_t *buffer_size) +/* Return true if the list is empty */ +static inline bool track_list_empty(void) { - /* Stop audio, voice and obtain all available buffer space */ - audio_hard_stop(); - talk_buffer_steal(); + return track_list.end == track_list.start; +} - unsigned char *end = audiobufend; - buffer_state = AUDIOBUF_STATE_TRASHED; - *buffer_size = end - audiobuf; +/* Returns true if the list is holding the maximum number of items */ +static bool track_list_full(void) +{ + return track_list.end - track_list.start >= TRACK_LIST_LEN; +} - return (unsigned char *)audiobuf; +/* Test if the index is within the allocated range */ +static bool track_list_in_range(int pos) +{ + return (int)(pos - track_list.start) >= 0 && + (int)(pos - track_list.end) < 0; } -bool audio_load_encoder(int afmt) +static struct track_info * track_list_entry(int pos) { -#if (CONFIG_PLATFORM & PLATFORM_NATIVE) - LOGFQUEUE("audio >| Q_AUDIO_LOAD_ENCODER: %d", afmt); - return queue_send(&audio_queue, Q_AUDIO_LOAD_ENCODER, afmt) > 0; -#else - (void)afmt; - return true; -#endif -} /* audio_load_encoder */ + return &track_list.tracks[pos & TRACK_LIST_MASK]; +} -void audio_remove_encoder(void) +/* Return the info of the last allocation plus an offset, NULL if result is + out of bounds */ +static struct track_info * track_list_last(int offset) { -#if (CONFIG_PLATFORM & PLATFORM_NATIVE) - LOGFQUEUE("audio >| Q_AUDIO_LOAD_ENCODER: NULL"); - queue_send(&audio_queue, Q_AUDIO_LOAD_ENCODER, AFMT_UNKNOWN); -#endif -} /* audio_remove_encoder */ + /* Last is before the end since the end isn't inclusive */ + unsigned int pos = track_list.end + offset - 1; -#endif /* HAVE_RECORDING */ + if (!track_list_in_range(pos)) + return NULL; + return track_list_entry(pos); +} -struct mp3entry* audio_current_track(void) +/* Allocate space at the end for another track if not full */ +static struct track_info * track_list_alloc_track(void) { - const char *filename; - struct playlist_track_info trackinfo; - int cur_idx; - int offset = ci.new_track + wps_offset; - struct mp3entry *write_id3; + if (track_list_full()) + return NULL; - cur_idx = (track_ridx + offset) & MAX_TRACK_MASK; + return track_list_entry(track_list.end++); +} - if (cur_idx == track_ridx && *thistrack_id3->path) - { - /* The usual case */ - if (tracks[cur_idx].cuesheet_hid >= 0 && !thistrack_id3->cuesheet) - { - bufread(tracks[cur_idx].cuesheet_hid, sizeof(struct cuesheet), curr_cue); - thistrack_id3->cuesheet = curr_cue; - } - return thistrack_id3; - } - else if (automatic_skip && offset == -1 && *othertrack_id3->path) - { - /* We're in a track transition. The codec has moved on to the next track, - but the audio being played is still the same (now previous) track. - othertrack_id3.elapsed is being updated in an ISR by - codec_pcmbuf_position_callback */ - if (tracks[cur_idx].cuesheet_hid >= 0 && !thistrack_id3->cuesheet) - { - bufread(tracks[cur_idx].cuesheet_hid, sizeof(struct cuesheet), curr_cue); - othertrack_id3->cuesheet = curr_cue; - } - return othertrack_id3; - } +/* Remove the last track entry allocated in order to support backing out + of a track load */ +static void track_list_unalloc_track(void) +{ + if (track_list_empty()) + return; - if (offset != 0) - { - /* Codec may be using thistrack_id3, so it must not be overwritten. - If this is a manual skip, othertrack_id3 will become - thistrack_id3 in audio_check_new_track(). - FIXME: If this is an automatic skip, it probably means multiple - short tracks fit in the PCM buffer. Overwriting othertrack_id3 - can lead to an incorrect value later. - Note that othertrack_id3 may also be used for next track. - */ - write_id3 = othertrack_id3; - } - else - { - write_id3 = thistrack_id3; - } + track_list.end--; - if (tracks[cur_idx].id3_hid >= 0) + if (track_list.current == track_list.end && + track_list.current != track_list.start) { - /* The current track's info has been buffered but not read yet, so get it */ - if (bufreadid3(tracks[cur_idx].id3_hid, write_id3)) - return write_id3; + /* Current _must_ remain within bounds */ + track_list.current--; } - - /* We didn't find the ID3 metadata, so we fill temp_id3 with the little info - we have and return that. */ - - memset(write_id3, 0, sizeof(struct mp3entry)); - - playlist_get_track_info(NULL, playlist_next(0)+wps_offset, &trackinfo); - filename = trackinfo.filename; - if (!filename) - filename = "No file!"; - -#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) - if (tagcache_fill_tags(write_id3, filename)) - return write_id3; -#endif - - strlcpy(write_id3->path, filename, sizeof(write_id3->path)); - write_id3->title = strrchr(write_id3->path, '/'); - if (!write_id3->title) - write_id3->title = &write_id3->path[0]; - else - write_id3->title++; - - return write_id3; } -struct mp3entry* audio_next_track(void) +/* Return current track plus an offset, NULL if result is out of bounds */ +static struct track_info * track_list_current(int offset) { - int next_idx; - int offset = ci.new_track + wps_offset; + unsigned int pos = track_list.current + offset; - if (!audio_have_tracks()) + if (!track_list_in_range(pos)) return NULL; - if (wps_offset == -1 && *thistrack_id3->path) - { - /* We're in a track transition. The next track for the WPS is the one - currently being decoded. */ - return thistrack_id3; - } - - next_idx = (track_ridx + offset + 1) & MAX_TRACK_MASK; - - if (tracks[next_idx].id3_hid >= 0) - { - if (bufreadid3(tracks[next_idx].id3_hid, othertrack_id3)) - return othertrack_id3; - else - return NULL; - } + return track_list_entry(pos); +} - if (next_idx == track_widx) +/* Return current based upon what's intended that the user sees - not + necessarily where decoding is taking place */ +static struct track_info * track_list_user_current(int offset) +{ + if (skip_pending == TRACK_SKIP_AUTO || + skip_pending == TRACK_SKIP_AUTO_NEW_PLAYLIST) { - /* The next track hasn't been buffered yet, so we return the static - version of its metadata. */ - return &unbuffered_id3; + offset--; } - return NULL; + return track_list_current(offset); } -/* gets a copy of the id3 data */ -bool audio_peek_track(struct mp3entry* id3, int offset) +/* Advance current track by an offset, return false if result is out of + bounds */ +static struct track_info * track_list_advance_current(int offset) { - int next_idx; - int new_offset = ci.new_track + wps_offset + offset; - - if (!audio_have_tracks()) - return false; - next_idx = (track_ridx + new_offset) & MAX_TRACK_MASK; + unsigned int pos = track_list.current + offset; - if (tracks[next_idx].id3_hid >= 0) - return bufreadid3(tracks[next_idx].id3_hid, id3); + if (!track_list_in_range(pos)) + return NULL; - return false; + track_list.current = pos; + return track_list_entry(pos); } -#ifdef HAVE_ALBUMART - -int playback_current_aa_hid(int slot) +/* Clear tracks in the list, optionally preserving the current track - + returns 'false' if the operation was changed */ +enum track_clear_action { - if (slot < 0) - return -1; - int cur_idx; - int offset = ci.new_track + wps_offset; - - cur_idx = track_ridx + offset; - cur_idx &= MAX_TRACK_MASK; - return tracks[cur_idx].aa_hid[slot]; -} + TRACK_LIST_CLEAR_ALL = 0, /* Clear all tracks */ + TRACK_LIST_KEEP_CURRENT, /* Keep current only; clear before + after */ + TRACK_LIST_KEEP_NEW /* Keep current and those that follow */ +}; -int playback_claim_aa_slot(struct dim *dim) +static void track_list_clear(enum track_clear_action action) { - int i; + logf("%s(%d)", __func__, (int)action); - /* first try to find a slot already having the size to reuse it - * since we don't want albumart of the same size buffered multiple times */ - FOREACH_ALBUMART(i) - { - struct albumart_slot *slot = &albumart_slots[i]; - if (slot->dim.width == dim->width - && slot->dim.height == dim->height) - { - slot->used++; - return i; - } - } - /* size is new, find a free slot */ - FOREACH_ALBUMART(i) + /* Don't care now since rebuffering is imminent */ + buf_set_watermark(0); + + if (action != TRACK_LIST_CLEAR_ALL) { - if (!albumart_slots[i].used) - { - albumart_slots[i].used++; - albumart_slots[i].dim = *dim; - return i; - } - } - /* sorry, no free slot */ - return -1; -} + struct track_info *cur = track_list_current(0); -void playback_release_aa_slot(int slot) -{ - /* invalidate the albumart_slot */ - struct albumart_slot *aa_slot = &albumart_slots[slot]; + if (!cur || cur->id3_hid < 0) + action = TRACK_LIST_CLEAR_ALL; /* Nothing worthwhile keeping */ + } - if (aa_slot->used > 0) - aa_slot->used--; -} + /* Noone should see this progressing */ + int start = track_list.start; + int current = track_list.current; + int end = track_list.end; -#endif -void audio_play(long offset) -{ - logf("audio_play"); + track_list.start = current; -#ifdef PLAYBACK_VOICE - /* Truncate any existing voice output so we don't have spelling - * etc. over the first part of the played track */ - talk_force_shutup(); -#endif + switch (action) + { + case TRACK_LIST_CLEAR_ALL: + /* Result: .start = .current, .end = .current */ + track_list.end = current; + break; - /* Start playback */ - LOGFQUEUE("audio >| audio Q_AUDIO_PLAY: %ld", offset); - /* Don't return until playback has actually started */ - queue_send(&audio_queue, Q_AUDIO_PLAY, offset); -} + case TRACK_LIST_KEEP_CURRENT: + /* Result: .start = .current, .end = .current + 1 */ + track_list.end = current + 1; + break; -void audio_stop(void) -{ - /* Stop playback */ - LOGFQUEUE("audio >| audio Q_AUDIO_STOP"); - /* Don't return until playback has actually stopped */ - queue_send(&audio_queue, Q_AUDIO_STOP, 0); -} + case TRACK_LIST_KEEP_NEW: + /* Result: .start = .current, .end = .end */ + end = current; + break; + } -void audio_pause(void) -{ - LOGFQUEUE("audio >| audio Q_AUDIO_PAUSE"); - /* Don't return until playback has actually paused */ - queue_send(&audio_queue, Q_AUDIO_PAUSE, true); -} + /* Close all open handles in the range except the for the current track + if preserving that */ + while (start != end) + { + if (action != TRACK_LIST_KEEP_CURRENT || start != current) + { + struct track_info *info = + &track_list.tracks[start & TRACK_LIST_MASK]; -void audio_resume(void) -{ - LOGFQUEUE("audio >| audio Q_AUDIO_PAUSE resume"); - /* Don't return until playback has actually resumed */ - queue_send(&audio_queue, Q_AUDIO_PAUSE, false); -} + /* If this is the in-progress load, abort it */ + if (in_progress_id3_hid >= 0 && + info->id3_hid == in_progress_id3_hid) + { + in_progress_id3_hid = ERR_HANDLE_NOT_FOUND; + } -void audio_skip(int direction) -{ - if (playlist_check(ci.new_track + wps_offset + direction)) - { - if (global_settings.beep) - pcmbuf_beep(2000, 100, 2500*global_settings.beep); + track_info_close(info); + } - LOGFQUEUE("audio > audio Q_AUDIO_SKIP %d", direction); - queue_post(&audio_queue, Q_AUDIO_SKIP, direction); - /* Update wps while our message travels inside deep playback queues. */ - wps_offset += direction; + start++; } - else - { - /* No more tracks. */ - if (global_settings.beep) - pcmbuf_beep(1000, 100, 1500*global_settings.beep); - } -} - -void audio_next(void) -{ - audio_skip(1); } -void audio_prev(void) -{ - audio_skip(-1); -} -void audio_next_dir(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP 1"); - queue_post(&audio_queue, Q_AUDIO_DIR_SKIP, 1); -} +/** --- Audio buffer -- **/ -void audio_prev_dir(void) +/* What size is needed for the scratch buffer? */ +static size_t scratch_mem_size(void) { - LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP -1"); - queue_post(&audio_queue, Q_AUDIO_DIR_SKIP, -1); -} + size_t size = sizeof (struct audio_scratch_memory); -void audio_pre_ff_rewind(void) -{ - LOGFQUEUE("audio > audio Q_AUDIO_PRE_FF_REWIND"); - queue_post(&audio_queue, Q_AUDIO_PRE_FF_REWIND, 0); -} + if (global_settings.cuesheet) + size += sizeof (struct cuesheet); -void audio_ff_rewind(long newpos) -{ - LOGFQUEUE("audio > audio Q_AUDIO_FF_REWIND"); - queue_post(&audio_queue, Q_AUDIO_FF_REWIND, newpos); + return size; } -void audio_flush_and_reload_tracks(void) +/* Initialize the memory area where data is stored that is only used when + playing audio and anything depending upon it */ +static void scratch_mem_init(void *mem) { - LOGFQUEUE("audio > audio Q_AUDIO_FLUSH"); - queue_post(&audio_queue, Q_AUDIO_FLUSH, 0); -} + audio_scratch_memory = (struct audio_scratch_memory *)mem; + id3_write_locked(UNBUFFERED_ID3, NULL); + id3_write(CODEC_ID3, NULL); + ci.id3 = id3_get(CODEC_ID3); + audio_scratch_memory->curr_cue = NULL; -void audio_error_clear(void) -{ -#ifdef AUDIO_HAVE_RECORDING - pcm_rec_error_clear(); -#endif + if (global_settings.cuesheet) + { + audio_scratch_memory->curr_cue = + SKIPBYTES((struct cuesheet *)audio_scratch_memory, + sizeof (struct audio_scratch_memory)); + } } -int audio_status(void) +/* Set up the audio buffer for playback */ +static void audio_reset_buffer(void) { - int ret = 0; + /* + * Layout audio buffer as follows: + * [[|TALK]|SCRATCH|BUFFERING|PCM|] + */ - if (playing) - ret |= AUDIO_STATUS_PLAY; + /* see audio_get_recording_buffer if this is modified */ + logf("%s()", __func__); - if (paused) - ret |= AUDIO_STATUS_PAUSE; + /* If the setup of anything allocated before the file buffer is + changed, do check the adjustments after the buffer_alloc call + as it will likely be affected and need sliding over */ -#ifdef HAVE_RECORDING - /* Do this here for constitency with mpeg.c version */ - /* FIXME: pcm_rec_status() is deprecated */ - ret |= pcm_rec_status(); -#endif + /* Initially set up file buffer as all space available */ + unsigned char *filebuf = audiobuf + talk_get_bufsize(); + size_t filebuflen = audiobufend - filebuf; + size_t allocsize; - return ret; -} + ALIGN_BUFFER(filebuf, filebuflen, sizeof (intptr_t)); -bool audio_automatic_skip(void) -{ - return automatic_skip; -} + /* Subtract whatever the pcm buffer says it used plus the guard buffer */ + allocsize = pcmbuf_init(filebuf + filebuflen); -int audio_get_file_pos(void) -{ - return 0; -} + /* Make sure filebuflen is a pointer sized multiple after adjustment */ + allocsize = ALIGN_UP(allocsize, sizeof (intptr_t)); + if (allocsize > filebuflen) + goto bufpanic; -#ifdef HAVE_DISK_STORAGE -void audio_set_buffer_margin(int setting) -{ - static const unsigned short lookup[] = {5, 15, 30, 60, 120, 180, 300, 600}; - buffer_margin = lookup[setting]; - logf("buffer margin: %ld", (long)buffer_margin); - set_filebuf_watermark(); -} -#endif + filebuflen -= allocsize; -#ifdef HAVE_CROSSFADE -/* Take necessary steps to enable or disable the crossfade setting */ -void audio_set_crossfade(int enable) -{ - size_t offset; - bool was_playing; - size_t size; + /* Scratch memory */ + allocsize = scratch_mem_size(); + if (allocsize > filebuflen) + goto bufpanic; - /* Tell it the next setting to use */ - pcmbuf_request_crossfade_enable(enable); + scratch_mem_init(filebuf); + filebuf += allocsize; + filebuflen -= allocsize; - /* Return if size hasn't changed or this is too early to determine - which in the second case there's no way we could be playing - anything at all */ - if (pcmbuf_is_same_size()) return; + buffering_reset(filebuf, filebuflen); - offset = 0; - was_playing = playing; + /* Clear any references to the file buffer */ + buffer_state = AUDIOBUF_STATE_INITIALIZED; - /* Playback has to be stopped before changing the buffer size */ - if (was_playing) +#if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE) + /* Make sure everything adds up - yes, some info is a bit redundant but + aids viewing and the sumation of certain variables should add up to + the location of others. */ { - /* Store the track resume position */ - offset = thistrack_id3->offset; + size_t pcmbufsize; + const unsigned char *pcmbuf = pcmbuf_get_meminfo(&pcmbufsize); + logf("fbuf: %08X", (unsigned)filebuf); + logf("fbufe: %08X", (unsigned)(filebuf + filebuflen)); + logf("sbuf: %08X", (unsigned)audio_scratch_memory); + logf("sbufe: %08X", (unsigned)(audio_scratch_memory + allocsize)); + logf("pcmb: %08X", (unsigned)pcmbuf); + logf("pcmbe: %08X", (unsigned)(pcmbuf + pcmbufsize)); } +#endif - /* Blast it - audio buffer will have to be setup again next time - something plays */ - audio_get_buffer(true, &size); + return; - /* Restart playback if audio was running previously */ - if (was_playing) - audio_play(offset); +bufpanic: + panicf("%s(): EOM (%zu > %zu)", __func__, allocsize, filebuflen); } -#endif - -/* --- Routines called from multiple threads --- */ -static void set_filebuf_watermark(void) +/* Set the buffer margin to begin rebuffering when 'seconds' from empty */ +static void audio_update_filebuf_watermark(int seconds) { - if (!filebuf) - return; /* Audio buffers not yet set up */ + size_t bytes = 0; #ifdef HAVE_DISK_STORAGE - int seconds; int spinup = ata_spinup_time(); + + if (seconds == 0) + { + /* By current setting */ + seconds = buffer_margin; + } + else + { + /* New setting */ + buffer_margin = seconds; + + if (buf_get_watermark() == 0) + { + /* Write a watermark only if the audio thread already did so for + itself or it will fail to set the event and the watermark - if + it hasn't yet, it will use the new setting when it does */ + return; + } + } + if (spinup) - seconds = (spinup / HZ) + 1; + seconds += (spinup / HZ) + 1; else - seconds = 5; + seconds += 5; seconds += buffer_margin; #else /* flash storage */ - int seconds = 1; + seconds = 1; #endif - /* bitrate of last track in buffer dictates watermark */ - struct mp3entry* id3 = NULL; - if (tracks[track_widx].taginfo_ready) - id3 = bufgetid3(tracks[track_widx].id3_hid); - else - id3 = bufgetid3(tracks[track_widx-1].id3_hid); - if (!id3) { - logf("fwmark: No id3 for last track (r%d/w%d), aborting!", track_ridx, track_widx); - return; - } - size_t bytes = id3->bitrate * (1000/8) * seconds; - buf_set_watermark(bytes); - logf("fwmark: %d", bytes); -} - -/* --- Buffering callbacks --- */ - -static void buffering_low_buffer_callback(void *data) -{ - (void)data; - logf("low buffer callback"); - - if (filling == STATE_FULL || filling == STATE_END_OF_PLAYLIST) { - /* force a refill */ - LOGFQUEUE("buffering > audio Q_AUDIO_FILL_BUFFER"); - queue_post(&audio_queue, Q_AUDIO_FILL_BUFFER, 0); - } -} - -static void buffering_handle_rebuffer_callback(void *data) -{ - (void)data; - LOGFQUEUE("audio >| audio Q_AUDIO_FLUSH"); - queue_post(&audio_queue, Q_AUDIO_FLUSH, 0); -} + /* Watermark is a function of the bitrate of the last track in the buffer */ + struct mp3entry *id3 = NULL; + struct track_info *info = track_list_last(0); -static void buffering_handle_finished_callback(void *data) -{ - logf("handle %d finished buffering", *(int*)data); - int hid = (*(int*)data); + if (info) + id3 = valid_mp3entry(bufgetid3(info->id3_hid)); - if (hid == tracks[track_widx].id3_hid) + if (id3) { - int offset = ci.new_track + wps_offset; - int next_idx = (track_ridx + offset + 1) & MAX_TRACK_MASK; - /* The metadata handle for the last loaded track has been buffered. - We can ask the audio thread to load the rest of the track's data. */ - LOGFQUEUE("audio > audio Q_AUDIO_FINISH_LOAD"); - queue_post(&audio_queue, Q_AUDIO_FINISH_LOAD, 0); - if (tracks[next_idx].id3_hid == hid) - send_event(PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE, NULL); + if (get_audio_base_data_type(id3->codectype) == TYPE_PACKET_AUDIO) + { + bytes = id3->bitrate * (1000/8) * seconds; + } + else + { + /* Bitrate has no meaning to buffering margin for atomic audio - + rebuffer when it's the only track left unless it's the only + track that fits, in which case we should avoid constant buffer + low events */ + if (track_list_count() > 1) + bytes = info->filesize + 1; + } } else { - /* This is most likely an audio handle, so we strip the useless - trailing tags that are left. */ - strip_tags(hid); - - if (hid == tracks[track_widx-1].audio_hid - && filling == STATE_END_OF_PLAYLIST) - { - /* This was the last track in the playlist. - We now have all the data we need. */ - logf("last track finished buffering"); - filling = STATE_FINISHED; - } + /* Then set the minimum - this should not occur anyway */ + logf("fwmark: No id3 for last track (s%u/c%u/e%u)", + track_list.start, track_list.current, track_list.end); } + + /* Actually setting zero disables the notification and we use that + to detect that it has been reset */ + buf_set_watermark(MAX(bytes, 1)); + logf("fwmark: %lu", (unsigned long)bytes); } -/* --- Audio thread --- */ +/** -- Track change notification -- **/ -static bool audio_have_tracks(void) +/* Check the pcmbuf track changes and return write the message into the event + if there are any */ +static inline bool audio_pcmbuf_track_change_scan(void) { - return (audio_track_count() != 0); -} + if (track_change.out != track_change.in) + { + track_change.out++; + return true; + } -static int audio_free_track_count(void) -{ - /* Used tracks + free tracks adds up to MAX_TRACK - 1 */ - return MAX_TRACK - 1 - audio_track_count(); + return false; } -int audio_track_count(void) +/* Clear outstanding track change posts */ +static inline void audio_pcmbuf_track_change_clear(void) { - /* Calculate difference from track_ridx to track_widx - * taking into account a possible wrap-around. */ - return (MAX_TRACK + track_widx - track_ridx) & MAX_TRACK_MASK; + track_change.out = track_change.in; } -long audio_filebufused(void) +/* Post a track change notification - called by audio ISR */ +static inline void audio_pcmbuf_track_change_post(void) { - return (long) buf_used(); + track_change.in++; } -/* Update track info after successful a codec track change */ -static void audio_update_trackinfo(void) -{ - bool resume = false; - /* Load the curent track's metadata into curtrack_id3 */ - if (CUR_TI->id3_hid >= 0) - bufreadid3(CUR_TI->id3_hid, thistrack_id3); +/** --- Helper functions --- **/ + +/* Removes messages that might end up in the queue before or while processing + a manual track change. Responding to them would be harmful since they + belong to a previous track's playback period. Anything that would generate + the stale messages must first be put into a state where it will not do so. + */ +static void audio_clear_track_notifications(void) +{ + static const long filter_list[][2] = + { + /* codec messages */ + { Q_AUDIO_CODEC_SEEK_COMPLETE, Q_AUDIO_CODEC_COMPLETE }, + /* track change messages */ + { Q_AUDIO_TRACK_CHANGED, Q_AUDIO_TRACK_CHANGED }, + }; - /* Reset current position */ - thistrack_id3->elapsed = 0; + const int filter_count = ARRAYLEN(filter_list) - 1; -#ifdef HAVE_TAGCACHE - /* Ignoring resume position for automatic track change if so configured */ - resume = global_settings.autoresume_enable && - (!automatic_skip /* Resume all manually selected tracks */ - || global_settings.autoresume_automatic == AUTORESUME_NEXTTRACK_ALWAYS - || (global_settings.autoresume_automatic != AUTORESUME_NEXTTRACK_NEVER - /* Not never resume? */ - && autoresumable(thistrack_id3))); /* Pass Resume filter? */ -#endif + /* Remove any pcmbuf notifications */ + pcmbuf_monitor_track_change(false); + audio_pcmbuf_track_change_clear(); - if (!resume) + /* Scrub the audio queue of the old mold */ + while (queue_peek_ex(&audio_queue, NULL, + filter_count | QPEEK_REMOVE_EVENTS, + filter_list)) { - thistrack_id3->offset = 0; + yield(); /* Not strictly needed, per se, ad infinitum, ra, ra */ } +} - logf("audio_update_trackinfo: Set offset for %s to %lX\n", - thistrack_id3->title, thistrack_id3->offset); +/* Takes actions based upon track load status codes */ +static void audio_handle_track_load_status(int trackstat) +{ + switch (trackstat) + { + case LOAD_TRACK_ERR_NO_MORE: + if (track_list_count() > 0) + break; + + case LOAD_TRACK_ERR_START_CODEC: + audio_queue_post(Q_AUDIO_CODEC_COMPLETE, CODEC_ERROR); + break; - /* Update the codec API */ - ci.filesize = CUR_TI->filesize; - ci.id3 = thistrack_id3; - ci.curpos = 0; - ci.taginfo_ready = &CUR_TI->taginfo_ready; + default: + break; + } } -/* Clear tracks between write and read, non inclusive */ -static void audio_clear_track_entries(void) +/* Announce the end of playing the current track */ +static void audio_playlist_track_finish(void) { - int cur_idx = track_widx; + struct mp3entry *id3 = valid_mp3entry(id3_get(PLAYING_ID3)); - logf("Clearing tracks: r%d/w%d", track_ridx, track_widx); + playlist_update_resume_info(filling == STATE_ENDED ? NULL : id3); - /* Loop over all tracks from write-to-read */ - while (1) + if (id3) + { + send_event(PLAYBACK_EVENT_TRACK_FINISH, id3); + prev_track_elapsed = id3->elapsed; + } + else { - cur_idx = (cur_idx + 1) & MAX_TRACK_MASK; + prev_track_elapsed = 0; + } +} - if (cur_idx == track_ridx) - break; +/* Announce the beginning of the new track */ +static void audio_playlist_track_change(void) +{ + struct mp3entry *id3 = valid_mp3entry(id3_get(PLAYING_ID3)); - clear_track_info(&tracks[cur_idx]); - } + if (id3) + send_event(PLAYBACK_EVENT_TRACK_CHANGE, id3); + + playlist_update_resume_info(id3); } -/* Clear all tracks */ -static bool audio_release_tracks(void) +/* Change the data for the next track and send the event */ +static void audio_update_and_announce_next_track(const struct mp3entry *id3_next) { - int i, cur_idx; + id3_write_locked(NEXTTRACK_ID3, id3_next); + send_event(PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE, + id3_get(NEXTTRACK_ID3)); +} + +/* Bring the user current mp3entry up to date and set a new offset for the + buffered metadata */ +static void playing_id3_sync(struct track_info *user_info, size_t offset) +{ + id3_mutex_lock(); - logf("releasing all tracks"); + struct mp3entry *id3 = bufgetid3(user_info->id3_hid); - for(i = 0; i < MAX_TRACK; i++) + if (offset == (size_t)-1) { - cur_idx = (track_ridx + i) & MAX_TRACK_MASK; - if (!clear_track_info(&tracks[cur_idx])) - return false; + struct mp3entry *ply_id3 = id3_get(PLAYING_ID3); + size_t play_offset = ply_id3->offset; + long play_elapsed = ply_id3->elapsed; + id3_write(PLAYING_ID3, id3); + ply_id3->offset = play_offset; + ply_id3->elapsed = play_elapsed; + offset = 0; + } + else + { + id3_write(PLAYING_ID3, id3); } - return true; + if (id3) + id3->offset = offset; + + id3_mutex_unlock(); } -static bool audio_loadcodec(bool start_play) +/* Wipe-out track metadata - current is optional */ +static void wipe_track_metadata(bool current) { - int prev_track, hid; - char codec_path[MAX_PATH]; /* Full path to codec */ - const struct mp3entry *id3, *prev_id3; + id3_mutex_lock(); - if (tracks[track_widx].id3_hid < 0) { - return false; - } + if (current) + id3_write(PLAYING_ID3, NULL); - id3 = bufgetid3(tracks[track_widx].id3_hid); - if (!id3) - return false; + id3_write(NEXTTRACK_ID3, NULL); + id3_write(UNBUFFERED_ID3, NULL); - const char *codec_fn = get_codec_filename(id3->codectype); - if (codec_fn == NULL) - return false; + id3_mutex_unlock(); +} - tracks[track_widx].codec_hid = -1; +/* Called when buffering is completed on the last track handle */ +static void filling_is_finished(void) +{ + logf("last track finished buffering"); - if (start_play) - { - /* Load the codec directly from disk and save some memory. */ - track_ridx = track_widx; - ci.filesize = CUR_TI->filesize; - ci.id3 = thistrack_id3; - ci.taginfo_ready = &CUR_TI->taginfo_ready; - ci.curpos = 0; - return codec_load(-1, id3->codectype); - } + /* There's no more to load or watch for */ + buf_set_watermark(0); + filling = STATE_FINISHED; +} + +/* Stop the codec decoding or waiting for its data to be ready - returns + 'false' if the codec ended up stopped */ +static bool halt_decoding_track(bool stop) +{ + /* If it was waiting for us to clear the buffer to make a rebuffer + happen, it should cease otherwise codec_stop could deadlock waiting + for the codec to go to its main loop - codec's request will now + force-fail */ + bool retval = false; + + buf_signal_handle(ci.audio_hid, true); + + if (stop) + codec_stop(); else - { - /* If we already have another track than this one buffered */ - if (track_widx != track_ridx) - { - prev_track = (track_widx - 1) & MAX_TRACK_MASK; + retval = codec_pause(); - id3 = bufgetid3(tracks[track_widx].id3_hid); - prev_id3 = bufgetid3(tracks[prev_track].id3_hid); + audio_clear_track_notifications(); - /* If the previous codec is the same as this one and the current - * one is the correct one, there is no need to put another copy of - * it on the file buffer */ - if (id3 && prev_id3) - { - int codt = get_codec_base_type(id3->codectype); - int prev_codt = get_codec_base_type(prev_id3->codectype); - int cod_loaded = get_codec_base_type(codec_loaded()); + /* We now know it's idle and not waiting for buffered data */ + buf_signal_handle(ci.audio_hid, false); - if (codt == prev_codt && codt == cod_loaded) - { - logf("Reusing prev. codec"); - return true; - } - } + codec_skip_pending = false; + codec_seeking = false; + + return retval; +} + +/* Clear the PCM on a manual skip */ +static void audio_clear_paused_pcm(void) +{ + if (play_status == PLAY_PAUSED && !pcmbuf_is_crossfade_active()) + pcmbuf_play_stop(); +} + +/* End the ff/rw mode */ +static void audio_ff_rewind_end(void) +{ + /* A seamless seek (not calling audio_pre_ff_rewind) skips this + section */ + if (ff_rw_mode) + { + ff_rw_mode = false; + + if (codec_seeking) + { + /* Clear the buffer */ + pcmbuf_play_stop(); + } + + if (play_status != PLAY_PAUSED) + { + /* Seeking-while-playing, resume PCM playback */ + pcmbuf_pause(false); } } +} - codec_get_full_path(codec_path, codec_fn); +/* Complete the codec seek */ +static void audio_complete_codec_seek(void) +{ + /* If a seek completed while paused, 'paused' is true. + * If seeking from seek mode, 'ff_rw_mode' is true. */ + if (codec_seeking) + { + audio_ff_rewind_end(); + codec_seeking = false; /* set _after_ the call! */ + } + /* else it's waiting and we must repond */ +} - hid = tracks[track_widx].codec_hid = bufopen(codec_path, 0, TYPE_CODEC, NULL); +/* Get the current cuesheet pointer */ +static inline struct cuesheet * get_current_cuesheet(void) +{ + return audio_scratch_memory->curr_cue; +} - /* not an error if codec load it supported, will load it from disk - * application builds don't support it - */ - if (hid < 0 && hid != ERR_UNSUPPORTED_TYPE) - return false; +/* Read the cuesheet from the buffer */ +static void buf_read_cuesheet(int handle_id) +{ + struct cuesheet *cue = get_current_cuesheet(); - if (hid >= 0) - logf("Loaded codec"); - else - logf("Buffering codec unsupported, load later from disk"); + if (!cue || handle_id < 0) + return; - return true; + bufread(handle_id, sizeof (struct cuesheet), cue); } -/* Load metadata for the next track (with bufopen). The rest of the track - loading will be handled by audio_finish_load_track once the metadata has been - actually loaded by the buffering thread. */ -static bool audio_load_track(size_t offset, bool start_play) +/* Backend to peek/current/next track metadata interface functions - + fill in the mp3entry with as much information as we may obtain about + the track at the specified offset from the user current track - + returns false if no information exists with us */ +static bool audio_get_track_metadata(int offset, struct mp3entry *id3) { - char name_buf[MAX_PATH + 1]; - const char *trackname; - int fd = -1; - - if (track_load_started) { - /* There is already a track load in progress, so track_widx hasn't been - incremented yet. Loading another track would overwrite the one that - hasn't finished loading. */ - logf("audio_load_track(): a track load is already in progress"); + if (play_status == PLAY_STOPPED) return false; + + if (id3->path[0] != '\0') + return true; /* Already filled */ + + struct track_info *info = track_list_user_current(offset); + + if (!info) + { + struct mp3entry *ub_id3 = id3_get(UNBUFFERED_ID3); + + if (offset > 0 && track_list_user_current(offset - 1)) + { + /* Try the unbuffered id3 since we're moving forward */ + if (ub_id3->path[0] != '\0') + { + copy_mp3entry(id3, ub_id3); + return true; + } + } + } + else if (bufreadid3(info->id3_hid, id3)) + { + return true; } - start_play_g = start_play; /* will be read by audio_finish_load_track */ + /* We didn't find the ID3 metadata, so we fill it with the little info we + have and return that */ - /* Stop buffer filling if there is no free track entries. - Don't fill up the last track entry (we wan't to store next track - metadata there). */ - if (!audio_free_track_count()) + char path[MAX_PATH+1]; + if (playlist_peek(offset, path, sizeof (path))) { - logf("No free tracks"); - return false; +#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) + /* Try to get it from the database */ + if (!tagcache_fill_tags(id3, path)) +#endif + { + /* By now, filename is the only source of info */ + fill_metadata_from_path(id3, path); + } + + return true; } - last_peek_offset++; - tracks[track_widx].taginfo_ready = false; + wipe_mp3entry(id3); + + return false; +} + +/* Get a resume rewind adjusted offset from the ID3 */ +unsigned long resume_rewind_adjusted_offset(const struct mp3entry *id3) +{ + unsigned long offset = id3->offset; + size_t resume_rewind = global_settings.resume_rewind * + id3->bitrate * (1000/8); + + if (offset < resume_rewind) + offset = 0; + else + offset -= resume_rewind; + + return offset; +} + +/* Get the codec into ram and initialize it - keep it if it's ready */ +static bool audio_init_codec(struct track_info *track_info, + struct mp3entry *track_id3) +{ + int codt_loaded = get_audio_base_codec_type(codec_loaded()); + int hid = ERR_HANDLE_NOT_FOUND; - logf("Buffering track: r%d/w%d", track_ridx, track_widx); - /* Get track name from current playlist read position. */ - while ((trackname = playlist_peek(last_peek_offset, name_buf, - sizeof(name_buf))) != NULL) + if (codt_loaded != AFMT_UNKNOWN) { - /* Handle broken playlists. */ - fd = open(trackname, O_RDONLY); - if (fd < 0) + int codt = get_audio_base_codec_type(track_id3->codectype); + + if (codt == codt_loaded) { - logf("Open failed"); - /* Skip invalid entry from playlist. */ - playlist_skip_entry(NULL, last_peek_offset); + /* Codec is the same base type */ + logf("Reusing prev. codec: %d", track_id3->codectype); +#ifdef HAVE_CODEC_BUFFERING + /* Close any buffered codec (we could have skipped directly to a + format transistion that is the same format as the current track + and the buffered one is no longer needed) */ + track_info_close_handle(&track_info->codec_hid); +#endif + return true; } else - break; + { + /* New codec - first make sure the old one's gone */ + logf("Removing prev. codec: %d", codt_loaded); + codec_unload(); + } } - if (!trackname) + logf("New codec: %d/%d", track_id3->codectype, codec_loaded()); + +#ifdef HAVE_CODEC_BUFFERING + /* Codec thread will close the handle even if it fails and will load from + storage if hid is not valid or the buffer load fails */ + hid = track_info->codec_hid; + track_info->codec_hid = ERR_HANDLE_NOT_FOUND; +#endif + + return codec_load(hid, track_id3->codectype); + (void)track_info; /* When codec buffering isn't supported */ +} + +/* Start the codec for the current track scheduled to be decoded */ +static bool audio_start_codec(bool auto_skip) +{ + struct track_info *info = track_list_current(0); + struct mp3entry *cur_id3 = valid_mp3entry(bufgetid3(info->id3_hid)); + + if (!cur_id3) + return false; + + buf_pin_handle(info->id3_hid, true); + + if (!audio_init_codec(info, cur_id3)) { - logf("End-of-playlist"); - memset(&unbuffered_id3, 0, sizeof(struct mp3entry)); - filling = STATE_END_OF_PLAYLIST; + buf_pin_handle(info->id3_hid, false); + return false; + } + +#ifdef HAVE_TAGCACHE + bool autoresume_enable = global_settings.autoresume_enable; + + if (autoresume_enable && !cur_id3->offset) + { + /* Resume all manually selected tracks */ + bool resume = !auto_skip; - if (thistrack_id3->length == 0 && thistrack_id3->filesize == 0) + /* Send the "buffer" event to obtain the resume position for the codec */ + send_event(PLAYBACK_EVENT_TRACK_BUFFER, cur_id3); + + if (!resume) { - /* Stop playback if no valid track was found. */ - audio_stop_playback(); + /* Automatic skip - do further tests to see if we should just + ignore any autoresume position */ + int autoresume_automatic = global_settings.autoresume_automatic; + + switch (autoresume_automatic) + { + case AUTORESUME_NEXTTRACK_ALWAYS: + /* Just resume unconditionally */ + resume = true; + break; + case AUTORESUME_NEXTTRACK_NEVER: + /* Force-rewind it */ + break; + default: + /* Not "never resume" - pass resume filter? */ + resume = autoresumable(cur_id3); + } } - return false; + if (!resume) + cur_id3->offset = 0; + + logf("%s: Set offset for %s to %lX\n", __func__, + cur_id3->title, cur_id3->offset); } +#endif /* HAVE_TAGCACHE */ - tracks[track_widx].filesize = filesize(fd); + /* Rewind the required amount - if an autoresume was done, this also rewinds + that by the setting's amount - if (offset > tracks[track_widx].filesize) - offset = 0; + It would be best to have bookkeeping about whether or not the track + sounded or not since skipping to it or else skipping to it while paused + and back again will cause accumulation of silent rewinds - that's not + our job to track directly nor could it be in any reasonable way + */ + cur_id3->offset = resume_rewind_adjusted_offset(cur_id3); - /* Set default values */ - if (start_play) + /* Update the codec API with the metadata and track info */ + id3_write(CODEC_ID3, cur_id3); + + ci.audio_hid = info->audio_hid; + ci.filesize = info->filesize; + buf_set_base_handle(info->audio_hid); + + /* All required data is now available for the codec */ + codec_go(); + +#ifdef HAVE_TAGCACHE + if (!autoresume_enable || cur_id3->offset) +#endif { - buf_set_watermark(filebuflen/2); - dsp_configure(ci.dsp, DSP_RESET, 0); - playlist_update_resume_info(audio_current_track()); + /* Send the "buffer" event now */ + send_event(PLAYBACK_EVENT_TRACK_BUFFER, cur_id3); } - /* Get track metadata if we don't already have it. */ - if (tracks[track_widx].id3_hid < 0) + buf_pin_handle(info->id3_hid, false); + return true; + + (void)auto_skip; /* ifndef HAVE_TAGCACHE */ +} + + +/** --- Audio thread --- **/ + +/* Load and parse a cuesheet for the file - returns false if the buffer + is full */ +static bool audio_load_cuesheet(struct track_info *info, + struct mp3entry *track_id3) +{ + struct cuesheet *cue = get_current_cuesheet(); + track_id3->cuesheet = NULL; + + if (cue && info->cuesheet_hid == ERR_HANDLE_NOT_FOUND) { - tracks[track_widx].id3_hid = bufopen(trackname, 0, TYPE_ID3, NULL); + /* If error other than a full buffer, then mark it "unsupported" to + avoid reloading attempt */ + int hid = ERR_UNSUPPORTED_TYPE; + char cuepath[MAX_PATH]; - if (tracks[track_widx].id3_hid < 0) +#ifdef HAVE_IO_PRIORITY + buf_back_off_storage(true); +#endif + if (look_for_cuesheet_file(track_id3->path, cuepath)) { - /* Buffer is full. */ - get_metadata(&unbuffered_id3, fd, trackname); - last_peek_offset--; - close(fd); - logf("buffer is full for now (get metadata)"); - filling = STATE_FULL; - return false; - } + hid = bufalloc(NULL, sizeof (struct cuesheet), TYPE_CUESHEET); - if (track_widx == track_ridx) - { - /* TODO: Superfluos buffering call? */ - buf_request_buffer_handle(tracks[track_widx].id3_hid); - if (bufreadid3(tracks[track_widx].id3_hid, thistrack_id3)) - { - thistrack_id3->offset = offset; - logf("audio_load_track: set offset for %s to %lX\n", - thistrack_id3->title, - offset); + if (hid >= 0) + { + void *cuesheet = NULL; + bufgetdata(hid, sizeof (struct cuesheet), &cuesheet); + + if (parse_cuesheet(cuepath, (struct cuesheet *)cuesheet)) + { + /* Indicate cuesheet is present (while track remains + buffered) */ + track_id3->cuesheet = cue; + } + else + { + bufclose(hid); + hid = ERR_UNSUPPORTED_TYPE; + } } - else - memset(thistrack_id3, 0, sizeof(struct mp3entry)); } - if (start_play) +#ifdef HAVE_IO_PRIORITY + buf_back_off_storage(false); +#endif + if (hid == ERR_BUFFER_FULL) + { + logf("buffer is full for now (%s)", __func__); + return false; + } + else { - playlist_update_resume_info(audio_current_track()); + if (hid < 0) + logf("Cuesheet loading failed"); + + info->cuesheet_hid = hid; } } - close(fd); - track_load_started = true; /* Remember that we've started loading a track */ return true; } #ifdef HAVE_ALBUMART -/* Load any album art for the file */ -static void audio_load_albumart(struct mp3entry *track_id3) +/* Load any album art for the file - returns false if the buffer is full */ +static bool audio_load_albumart(struct track_info *info, + struct mp3entry *track_id3) { int i; - FOREACH_ALBUMART(i) { struct bufopen_bitmap_data user_data; - int hid = ERR_HANDLE_NOT_FOUND; + int *aa_hid = &info->aa_hid[i]; + int hid = ERR_UNSUPPORTED_TYPE; /* albumart_slots may change during a yield of bufopen, * but that's no problem */ - if (tracks[track_widx].aa_hid[i] >= 0 || !albumart_slots[i].used) + if (*aa_hid >= 0 || *aa_hid == ERR_UNSUPPORTED_TYPE || + !albumart_slots[i].used) continue; memset(&user_data, 0, sizeof(user_data)); - user_data.dim = &(albumart_slots[i].dim); + user_data.dim = &albumart_slots[i].dim; + +#ifdef HAVE_IO_PRIORITY + buf_back_off_storage(true); +#endif - /* we can only decode jpeg for embedded AA */ + /* We can only decode jpeg for embedded AA */ if (track_id3->embed_albumart && track_id3->albumart.type == AA_TYPE_JPG) { - user_data.embedded_albumart = &(track_id3->albumart); + user_data.embedded_albumart = &track_id3->albumart; hid = bufopen(track_id3->path, 0, TYPE_BITMAP, &user_data); } if (hid < 0 && hid != ERR_BUFFER_FULL) { - /* no embedded AA or it couldn't be loaded, try other sources */ + /* No embedded AA or it couldn't be loaded - try other sources */ char path[MAX_PATH]; if (find_albumart(track_id3, path, sizeof(path), - &(albumart_slots[i].dim))) + &albumart_slots[i].dim)) { user_data.embedded_albumart = NULL; hid = bufopen(path, 0, TYPE_BITMAP, &user_data); } } +#ifdef HAVE_IO_PRIORITY + buf_back_off_storage(false); +#endif if (hid == ERR_BUFFER_FULL) { - filling = STATE_FULL; - logf("buffer is full for now (get album art)"); + logf("buffer is full for now (%s)", __func__); + return false; } - else if (hid < 0) + else { - logf("Album art loading failed"); - } + /* If error other than a full buffer, then mark it "unsupported" + to avoid reloading attempt */ + if (hid < 0) + { + logf("Album art loading failed"); + hid = ERR_UNSUPPORTED_TYPE; + } - tracks[track_widx].aa_hid[i] = hid; + *aa_hid = hid; + } } + + return true; } -#endif +#endif /* HAVE_ALBUMART */ -/* Second part of the track loading: We now have the metadata available, so we - can load the codec, the album art and finally the audio data. - This is called on the audio thread after the buffering thread calls the - buffering_handle_finished_callback callback. */ -static void audio_finish_load_track(void) +#ifdef HAVE_CODEC_BUFFERING +/* Load a codec for the file onto the buffer - assumes we're working from the + currently loading track - not called for the current track */ +static bool audio_buffer_codec(struct track_info *track_info, + struct mp3entry *track_id3) { - size_t file_offset = 0; - size_t offset = 0; - bool start_play = start_play_g; + /* This will not be the current track -> it cannot be the first and the + current track cannot be ahead of buffering -> there is a previous + track entry which is either current or ahead of the current */ + struct track_info *prev_info = track_list_last(-1); + struct mp3entry *prev_id3 = bufgetid3(prev_info->id3_hid); - track_load_started = false; + /* If the previous codec is the same as this one, there is no need to + put another copy of it on the file buffer (in other words, only + buffer codecs at format transitions) */ + if (prev_id3) + { + int codt = get_audio_base_codec_type(track_id3->codectype); + int prev_codt = get_audio_base_codec_type(prev_id3->codectype); - if (tracks[track_widx].id3_hid < 0) { - logf("No metadata"); - return; + if (codt == prev_codt) + { + logf("Reusing prev. codec: %d", prev_id3->codectype); + return true; + } } + /* else just load it (harmless) */ + + /* Load the codec onto the buffer if possible */ + const char *codec_fn = get_codec_filename(track_id3->codectype); + if (!codec_fn) + return false; - struct mp3entry *track_id3; + char codec_path[MAX_PATH+1]; /* Full path to codec */ + codec_get_full_path(codec_path, codec_fn); - if (track_widx == track_ridx) - track_id3 = thistrack_id3; - else - track_id3 = bufgetid3(tracks[track_widx].id3_hid); + track_info->codec_hid = bufopen(codec_path, 0, TYPE_CODEC, NULL); - if (track_id3->length == 0 && track_id3->filesize == 0) + if (track_info->codec_hid >= 0) { - logf("audio_finish_load_track: invalid metadata"); + logf("Buffered codec: %d", afmt); + return true; + } - /* Invalid metadata */ - bufclose(tracks[track_widx].id3_hid); - tracks[track_widx].id3_hid = -1; + return false; +} +#endif /* HAVE_CODEC_BUFFERING */ - /* Skip invalid entry from playlist. */ - playlist_skip_entry(NULL, last_peek_offset--); +/* Load metadata for the next track (with bufopen). The rest of the track + loading will be handled by audio_finish_load_track once the metadata has + been actually loaded by the buffering thread. - /* load next track */ - LOGFQUEUE("audio > audio Q_AUDIO_FILL_BUFFER %d", (int)start_play); - queue_post(&audio_queue, Q_AUDIO_FILL_BUFFER, start_play); + Each track is arranged in the buffer as follows: + - return; - } - /* Try to load a cuesheet for the track */ - if (curr_cue) + The next will not be loaded until the previous succeeds if the buffer was + full at the time. To put any metadata after audio would make those handles + unmovable. +*/ +static int audio_load_track(void) +{ + if (in_progress_id3_hid >= 0) { - char cuepath[MAX_PATH]; - if (look_for_cuesheet_file(track_id3->path, cuepath)) + /* There must be an info pointer if the in-progress id3 is even there */ + struct track_info *info = track_list_last(0); + + if (info->id3_hid == in_progress_id3_hid) { - void *temp; - tracks[track_widx].cuesheet_hid = - bufalloc(NULL, sizeof(struct cuesheet), TYPE_CUESHEET); - if (tracks[track_widx].cuesheet_hid >= 0) + if (filling == STATE_FILLING) { - bufgetdata(tracks[track_widx].cuesheet_hid, - sizeof(struct cuesheet), &temp); - struct cuesheet *cuesheet = (struct cuesheet*)temp; - if (!parse_cuesheet(cuepath, cuesheet)) - { - bufclose(tracks[track_widx].cuesheet_hid); - track_id3->cuesheet = NULL; - } + /* Haven't finished the metadata but the notification is + anticipated to come soon */ + logf("%s(): in progress ok: %d". __func__, info->id3_hid); + return LOAD_TRACK_OK; + } + else if (filling == STATE_FULL) + { + /* Buffer was full trying to complete the load after the + metadata finished, so attempt to continue - older handles + should have been cleared already */ + logf("%s(): finishing load: %d". __func__, info->id3_hid); + filling = STATE_FILLING; + buffer_event_finished_callback(&info->id3_hid); + return LOAD_TRACK_OK; } } + + /* Some old, stray buffering message */ + logf("%s(): already in progress: %d". __func__, info->id3_hid); + return LOAD_TRACK_ERR_BUSY; } -#ifdef HAVE_ALBUMART - audio_load_albumart(track_id3); -#endif + filling = STATE_FILLING; - /* Load the codec. */ - if (!audio_loadcodec(start_play)) + struct track_info *info = track_list_alloc_track(); + if (info == NULL) { - if (tracks[track_widx].codec_hid == ERR_BUFFER_FULL) - { - /* No space for codec on buffer, not an error */ - filling = STATE_FULL; - return; - } - - /* This is an error condition, either no codec was found, or reading - * the codec file failed part way through, either way, skip the track */ - /* FIXME: We should not use splashf from audio thread! */ - splashf(HZ*2, "No codec for: %s", track_id3->path); - /* Skip invalid entry from playlist. */ - playlist_skip_entry(NULL, last_peek_offset); - return; + /* List is full so stop buffering tracks - however, attempt to obtain + metadata as the unbuffered id3 */ + logf("No free tracks"); + filling = STATE_FULL; } - track_id3->elapsed = 0; - offset = track_id3->offset; - size_t resume_rewind = (global_settings.resume_rewind * - track_id3->bitrate * 1000) / 8; + playlist_peek_offset++; - if (offset < resume_rewind) + logf("Buffering track: s%u/c%u/e%u/p%d", + track_list.start, track_list.current, track_list.end, + playlist_peek_offset); + + /* Get track name from current playlist read position */ + int fd = -1; + char name_buf[MAX_PATH + 1]; + const char *trackname; + + while (1) { - offset = 0; + + trackname = playlist_peek(playlist_peek_offset, name_buf, + sizeof (name_buf)); + + if (!trackname) + break; + + /* Test for broken playlists by probing for the files */ + fd = open(trackname, O_RDONLY); + if (fd >= 0) + break; + + logf("Open failed"); + /* Skip invalid entry from playlist */ + playlist_skip_entry(NULL, playlist_peek_offset); + + /* Sync the playlist if it isn't finished */ + if (playlist_peek(playlist_peek_offset, NULL, 0)) + playlist_next(0); } - else + + if (!trackname) { - offset -= resume_rewind; + /* No track - exhausted the playlist entries */ + logf("End-of-playlist"); + id3_write_locked(UNBUFFERED_ID3, NULL); + + if (filling != STATE_FULL) + track_list_unalloc_track(); /* Free this entry */ + + playlist_peek_offset--; /* Maintain at last index */ + + /* We can end up here after the real last track signals its completion + and miss the transition to STATE_FINISHED esp. if dropping the last + songs of a playlist late in their load (2nd stage) */ + info = track_list_last(0); + + if (info && buf_handle_remaining(info->audio_hid) == 0) + filling_is_finished(); + else + filling = STATE_END_OF_PLAYLIST; + + return LOAD_TRACK_ERR_NO_MORE; } - enum data_type type = TYPE_PACKET_AUDIO; + /* Successfully opened the file - get track metadata */ + if (filling == STATE_FULL || + (info->id3_hid = bufopen(trackname, 0, TYPE_ID3, NULL)) < 0) + { + /* Buffer or track list is full */ + struct mp3entry *ub_id3; + + playlist_peek_offset--; - switch (track_id3->codectype) { - case AFMT_MPA_L1: - case AFMT_MPA_L2: - case AFMT_MPA_L3: - if (offset > 0) { - file_offset = offset; + /* Load the metadata for the first unbuffered track */ + ub_id3 = id3_get(UNBUFFERED_ID3); + id3_mutex_lock(); + get_metadata(ub_id3, fd, trackname); + id3_mutex_unlock(); + + if (filling != STATE_FULL) + { + track_list_unalloc_track(); + filling = STATE_FULL; } - break; - case AFMT_WAVPACK: - if (offset > 0) { - file_offset = offset; - track_id3->elapsed = track_id3->length / 2; + logf("%s: buffer is full for now (%u tracks)", __func__, + track_list_count()); + } + else + { + /* Successful load initiation */ + info->filesize = filesize(fd); + in_progress_id3_hid = info->id3_hid; /* Remember what's in-progress */ + } + + close(fd); + return LOAD_TRACK_OK; +} + +/* Second part of the track loading: We now have the metadata available, so we + can load the codec, the album art and finally the audio data. + This is called on the audio thread after the buffering thread calls the + buffering_handle_finished_callback callback. */ +static int audio_finish_load_track(struct track_info *info) +{ + int trackstat = LOAD_TRACK_OK; + + if (info->id3_hid != in_progress_id3_hid) + { + /* We must not be here if not! */ + logf("%s: wrong track %d/%d", __func__, info->id3_hid, + in_progress_id3_hid); + return LOAD_TRACK_ERR_BUSY; + } + + /* The current track for decoding (there is always one if the list is + populated) */ + struct track_info *cur_info = track_list_current(0); + struct mp3entry *track_id3 = valid_mp3entry(bufgetid3(info->id3_hid)); + + if (!track_id3) + { + /* This is an error condition. Track cannot be played without valid + metadata; skip the track. */ + logf("No metadata for: %s", track_id3->path); + trackstat = LOAD_TRACK_ERR_FINISH_FAILED; + goto audio_finish_load_track_exit; + } + + /* Try to load a cuesheet for the track */ + if (!audio_load_cuesheet(info, track_id3)) + { + /* No space for cuesheet on buffer, not an error */ + filling = STATE_FULL; + goto audio_finish_load_track_exit; + } + +#ifdef HAVE_ALBUMART + /* Try to load album art for the track */ + if (!audio_load_albumart(info, track_id3)) + { + /* No space for album art on buffer, not an error */ + filling = STATE_FULL; + goto audio_finish_load_track_exit; + } +#endif + +#ifdef HAVE_CODEC_BUFFERING + /* Try to buffer a codec for the track */ + if (info != cur_info && !audio_buffer_codec(info, track_id3)) + { + if (info->codec_hid == ERR_BUFFER_FULL) + { + /* No space for codec on buffer, not an error */ + filling = STATE_FULL; + logf("buffer is full for now (%s)", __func__); + } + else + { + /* This is an error condition, either no codec was found, or + reading the codec file failed part way through, either way, + skip the track */ + logf("No codec for: %s", track_id3->path); + trackstat = LOAD_TRACK_ERR_FINISH_FAILED; } - break; - case AFMT_NSF: - case AFMT_SPC: - case AFMT_SID: - logf("Loading atomic %d",track_id3->codectype); - type = TYPE_ATOMIC_AUDIO; - break; - - default: - /* no special treatment needed */ - break; + goto audio_finish_load_track_exit; } +#endif /* HAVE_CODEC_BUFFERING */ + + /** Finally, load the audio **/ + size_t file_offset = 0; + track_id3->elapsed = 0; + + if (track_id3->offset >= info->filesize) + track_id3->offset = 0; + + logf("%s: set offset for %s to %lu\n", __func__, + id3->title, (unsigned long)offset); + + /* Adjust for resume rewind so we know what to buffer - starting the codec + calls it again, so we don't save it (and they shouldn't accumulate) */ + size_t offset = resume_rewind_adjusted_offset(track_id3); - track_id3->offset = offset; + enum data_type audiotype = get_audio_base_data_type(track_id3->codectype); + + if (audiotype == TYPE_ATOMIC_AUDIO) + logf("Loading atomic %d", track_id3->codectype); + + if (format_buffers_with_offset(track_id3->codectype)) + { + /* This format can begin buffering from any point */ + file_offset = offset; + } logf("load track: %s", track_id3->path); if (file_offset > AUDIO_REBUFFER_GUESS_SIZE) + { + /* We can buffer later in the file, adjust the hunt-and-peck margin */ file_offset -= AUDIO_REBUFFER_GUESS_SIZE; - else if (track_id3->first_frame_offset) - file_offset = track_id3->first_frame_offset; + } else - file_offset = 0; + { + /* No offset given or it is very minimal - begin at the first frame + according to the metadata */ + file_offset = track_id3->first_frame_offset; + } - tracks[track_widx].audio_hid = bufopen(track_id3->path, file_offset, type, - NULL); + int hid = bufopen(track_id3->path, file_offset, audiotype, NULL); - /* No space left, not an error */ - if (tracks[track_widx].audio_hid == ERR_BUFFER_FULL) + if (hid >= 0) { - filling = STATE_FULL; - logf("buffer is full for now (load audio)"); - return; + info->audio_hid = hid; + + if (info == cur_info) + { + /* This is the current track to decode - should be started now */ + trackstat = LOAD_TRACK_READY; + } } - else if (tracks[track_widx].audio_hid < 0) + else { - /* another error, do not continue either */ - logf("Could not add audio data handle"); - return; + /* Buffer could be full but not properly so if this is the only + track! */ + if (hid == ERR_BUFFER_FULL && audio_track_count() > 1) + { + filling = STATE_FULL; + logf("Buffer is full for now (%s)", __func__); + } + else + { + /* Nothing to play if no audio handle - skip this */ + logf("Could not add audio data handle"); + trackstat = LOAD_TRACK_ERR_FINISH_FAILED; + } } - /* All required data is now available for the codec -- unless the - autoresume feature is in effect. In the latter case, the codec - must wait until after PLAYBACK_EVENT_TRACK_BUFFER, which may - generate a resume position. */ -#ifdef HAVE_TAGCACHE - if (!global_settings.autoresume_enable || offset) -#endif - tracks[track_widx].taginfo_ready = true; - - if (start_play) +audio_finish_load_track_exit: + if (trackstat < LOAD_TRACK_OK) { - ci.curpos=file_offset; - buf_request_buffer_handle(tracks[track_widx].audio_hid); - } + playlist_skip_entry(NULL, playlist_peek_offset); + track_info_close(info); + track_list_unalloc_track(); - send_event(PLAYBACK_EVENT_TRACK_BUFFER, track_id3); + if (playlist_peek(playlist_peek_offset, NULL, 0)) + playlist_next(0); -#ifdef HAVE_TAGCACHE - /* In case the autoresume feature has been enabled, finally all - required data is available for the codec. */ - if (global_settings.autoresume_enable && !offset) - tracks[track_widx].taginfo_ready = true; -#endif - - track_widx = (track_widx + 1) & MAX_TRACK_MASK; + playlist_peek_offset--; + } - /* load next track */ - LOGFQUEUE("audio > audio Q_AUDIO_FILL_BUFFER"); - queue_post(&audio_queue, Q_AUDIO_FILL_BUFFER, 0); + if (filling != STATE_FULL) + { + /* Load next track - error or not */ + in_progress_id3_hid = ERR_HANDLE_NOT_FOUND; + LOGFQUEUE("audio > audio Q_AUDIO_FILL_BUFFER"); + audio_queue_post(Q_AUDIO_FILL_BUFFER, 0); + } + else + { + /* Full */ + trackstat = LOAD_TRACK_ERR_FINISH_FULL; + } - return; + return trackstat; } -static void audio_fill_file_buffer(bool start_play, size_t offset) +/* Start a new track load */ +static int audio_fill_file_buffer(void) { - trigger_cpu_boost(); + if (play_status == PLAY_STOPPED) + return LOAD_TRACK_ERR_FAILED; - /* No need to rebuffer if there are track skips pending, - * however don't cancel buffering on skipping while filling. */ - if (ci.new_track != 0 && filling != STATE_FILLING) - return; - filling = STATE_FILLING; + trigger_cpu_boost(); /* Must reset the buffer before use if trashed or voice only - voice file size shouldn't have changed so we can go straight from @@ -1511,772 +1852,1848 @@ static void audio_fill_file_buffer(bool start_play, size_t offset) logf("Starting buffer fill"); - if (!start_play) - audio_clear_track_entries(); + int trackstat = audio_load_track(); + + if (trackstat >= LOAD_TRACK_OK) + { + if (track_list_current(0) == track_list_user_current(0)) + playlist_next(0); - /* Save the current resume position once. */ - playlist_update_resume_info(audio_current_track()); + if (filling == STATE_FULL && !track_list_user_current(1)) + { + /* There are no user tracks on the buffer after this therefore + this is the next track */ + audio_update_and_announce_next_track(id3_get(UNBUFFERED_ID3)); + } + } - audio_load_track(offset, start_play); + return trackstat; } -static void audio_rebuffer(void) +/* Discard unwanted tracks and start refill from after the specified playlist + offset */ +static int audio_reset_and_rebuffer( + enum track_clear_action action, int peek_offset) { - logf("Forcing rebuffer"); + logf("Forcing rebuffer: 0x%X, %d", flags, peek_offset); - clear_track_info(CUR_TI); + id3_write_locked(UNBUFFERED_ID3, NULL); - /* Reset track pointers */ - track_widx = track_ridx; - audio_clear_track_entries(); + /* Remove unwanted tracks - caller must have ensured codec isn't using + any */ + track_list_clear(action); - /* Reset a possibly interrupted track load */ - track_load_started = false; + /* Refill at specified position (-1 starts at index offset 0) */ + playlist_peek_offset = peek_offset; /* Fill the buffer */ - last_peek_offset = -1; - ci.curpos = 0; - - if (!CUR_TI->taginfo_ready) - memset(thistrack_id3, 0, sizeof(struct mp3entry)); - - audio_fill_file_buffer(false, 0); + return audio_fill_file_buffer(); } -/* Called on request from the codec to get a new track. This is the codec part - of the track transition. */ -static void audio_last_track(bool automatic) +/* Handle buffering events + (Q_AUDIO_BUFFERING) */ +static void audio_on_buffering(int event) { - if (automatic) + enum track_clear_action action; + int peek_offset; + + if (track_list_empty()) + return; + + switch (event) { - ci.new_track = 0; - automatic_skip = false; + case BUFFER_EVENT_BUFFER_LOW: + if (filling != STATE_FULL && filling != STATE_END_OF_PLAYLIST) + return; /* Should be nothing left to fill */ - if (filling != STATE_ENDING) - { - /* Monitor remaining PCM before stopping */ - filling = STATE_ENDING; - pcmbuf_monitor_track_change(true); - } + /* Clear old tracks and continue buffering where it left off */ + action = TRACK_LIST_KEEP_NEW; + peek_offset = playlist_peek_offset; + break; - codec_stop(); + case BUFFER_EVENT_REBUFFER: + /* Remove all but the currently decoding track and redo buffering + after that */ + action = TRACK_LIST_KEEP_CURRENT; + peek_offset = (skip_pending == TRACK_SKIP_AUTO) ? 1 : 0; + break; + + default: + return; } - else + + switch (skip_pending) { - audio_stop_playback(); + case TRACK_SKIP_NONE: + case TRACK_SKIP_AUTO: + case TRACK_SKIP_AUTO_NEW_PLAYLIST: + audio_reset_and_rebuffer(action, peek_offset); + break; + + case TRACK_SKIP_AUTO_END_PLAYLIST: + /* Already finished */ + break; + + default: + /* Invalid */ + logf("Buffering call, inv. state: %d", (int)skip_pending); } } -static void audio_check_new_track(void) +/* Handle starting the next track load + (Q_AUDIO_FILL_BUFFER) */ +static void audio_on_fill_buffer(void) +{ + audio_handle_track_load_status(audio_fill_file_buffer()); +} + +/* Handle posted load track finish event + (Q_AUDIO_FINISH_LOAD_TRACK) */ +static void audio_on_finish_load_track(int id3_hid) { - int track_count; - int old_track_ridx; - int i, idx; - bool forward; - struct mp3entry *temp; + struct track_info *info = track_list_last(0); - if (ci.new_track == 0) + if (!info || !buf_is_handle(id3_hid)) + return; + + if (info == track_list_user_current(1)) { - ci.new_track++; - automatic_skip = true; + /* Just loaded the metadata right after the current position */ + audio_update_and_announce_next_track(bufgetid3(info->id3_hid)); } - track_count = audio_track_count(); - old_track_ridx = track_ridx; + if (audio_finish_load_track(info) != LOAD_TRACK_READY) + return; /* Not current track */ - /* Now it's good time to send track finish events. */ - send_event(PLAYBACK_EVENT_TRACK_FINISH, thistrack_id3); - /* swap the mp3entry pointers */ - temp = thistrack_id3; - thistrack_id3 = othertrack_id3; - othertrack_id3 = temp; - ci.id3 = thistrack_id3; - memset(thistrack_id3, 0, sizeof(struct mp3entry)); + bool is_user_current = info == track_list_user_current(0); - if (dir_skip) + if (is_user_current) { - dir_skip = false; - /* regardless of the return value we need to rebuffer. - if it fails the old playlist will resume, else the - next dir will start playing */ - playlist_next_dir(ci.new_track); - ci.new_track = 0; - audio_rebuffer(); - goto skip_done; + /* Copy cuesheet */ + buf_read_cuesheet(info->cuesheet_hid); } - if (new_playlist) - ci.new_track = 0; - - /* If the playlist isn't that big */ - if (automatic_skip) + if (audio_start_codec(automatic_skip)) { - while (!playlist_check(ci.new_track)) + if (is_user_current) { - if (ci.new_track >= 0) + /* Be sure all tagtree info is synchronized; it will be needed for the + track finish event - the sync will happen when finalizing a track + change otherwise */ + bool was_valid = valid_mp3entry(id3_get(PLAYING_ID3)); + + playing_id3_sync(info, -1); + + if (!was_valid) { - audio_last_track(true); - return; + /* Playing id3 hadn't been updated yet because no valid track + was yet available - treat like the first track */ + audio_playlist_track_change(); } - ci.new_track++; } } - - /* Update the playlist */ - last_peek_offset -= ci.new_track; - - if (playlist_next(ci.new_track) < 0) + else { - /* End of list */ - audio_last_track(automatic_skip); - return; + audio_handle_track_load_status(LOAD_TRACK_ERR_START_CODEC); } - - if (new_playlist) +} + +/* Called when handles other than metadata handles have finished buffering + (Q_AUDIO_HANDLE_FINISHED) */ +static void audio_on_handle_finished(int hid) +{ + /* Right now, only audio handles should end up calling this */ + if (filling == STATE_END_OF_PLAYLIST) { - ci.new_track = 1; - new_playlist = false; + struct track_info *info = track_list_last(0); + + /* Really we don't know which order the handles will actually complete + to zero bytes remaining since another thread is doing it - be sure + it's the right one */ + if (info && info->audio_hid == hid) + { + /* This was the last track in the playlist and we now have all the + data we need */ + filling_is_finished(); + } } +} - /* Save a pointer to the old track to allow later clearing */ - prev_ti = CUR_TI; +/* Called to make an outstanding track skip the current track and to send the + transition events */ +static void audio_finalise_track_change(bool delayed) +{ + switch (skip_pending) + { + case TRACK_SKIP_NONE: /* Manual skip */ + break; - for (i = 0; i < ci.new_track; i++) + case TRACK_SKIP_AUTO: + case TRACK_SKIP_AUTO_NEW_PLAYLIST: { - idx = (track_ridx + i) & MAX_TRACK_MASK; - struct mp3entry *id3 = bufgetid3(tracks[idx].id3_hid); - ssize_t offset = buf_handle_offset(tracks[idx].audio_hid); - if (!id3 || offset < 0 || (unsigned)offset > id3->first_frame_offset) + int playlist_delta = skip_pending == TRACK_SKIP_AUTO ? 1 : 0; + audio_playlist_track_finish(); + + if (!playlist_peek(playlist_delta, NULL, 0)) { - /* We don't have all the audio data for that track, so clear it, - but keep the metadata. */ - if (tracks[idx].audio_hid >= 0 && bufclose(tracks[idx].audio_hid)) - { - tracks[idx].audio_hid = -1; - tracks[idx].filesize = 0; - } + /* Track ended up rejected - push things ahead like the codec blew + it (because it was never started and now we're here where it + should have been decoding the next track by now) - next, a + directory change or end of playback will most likely happen */ + skip_pending = TRACK_SKIP_NONE; + audio_handle_track_load_status(LOAD_TRACK_ERR_START_CODEC); + return; } - } - /* Move to the new track */ - track_ridx = (track_ridx + ci.new_track) & MAX_TRACK_MASK; - buf_set_base_handle(CUR_TI->audio_hid); + if (!playlist_delta) + break; - if (automatic_skip) - { - wps_offset = -ci.new_track; + playlist_peek_offset -= playlist_delta; + if (playlist_next(playlist_delta) >= 0) + break; + /* What!? Disappear? Hopeless bleak despair */ + } + /* Fallthrough */ + case TRACK_SKIP_AUTO_END_PLAYLIST: + default: /* Invalid */ + filling = STATE_ENDED; + audio_stop_playback(); + return; } - /* If it is not safe to even skip this many track entries */ - if (ci.new_track >= track_count || ci.new_track <= track_count - MAX_TRACK) + struct track_info *info = track_list_current(0); + struct mp3entry *track_id3 = NULL; + + id3_mutex_lock(); + + /* Update the current cuesheet if any and enabled */ + if (info) { - ci.new_track = 0; - audio_rebuffer(); - goto skip_done; + buf_read_cuesheet(info->cuesheet_hid); + track_id3 = bufgetid3(info->id3_hid); } - forward = ci.new_track > 0; - ci.new_track = 0; + id3_write(PLAYING_ID3, track_id3); - /* If the target track is clearly not in memory */ - if (CUR_TI->filesize == 0 || !CUR_TI->taginfo_ready) + if (delayed) { - audio_rebuffer(); - goto skip_done; + /* Delayed skip where codec is ahead of user's current track */ + struct mp3entry *ci_id3 = id3_get(CODEC_ID3); + struct mp3entry *ply_id3 = id3_get(PLAYING_ID3); + ply_id3->elapsed = ci_id3->elapsed; + ply_id3->offset = ci_id3->offset; } - /* When skipping backwards, it is possible that we've found a track that's - * buffered, but which is around the track-wrap and therefore not the track - * we are looking for */ - if (!forward) - { - int cur_idx = track_ridx; - bool taginfo_ready = true; - /* We've wrapped the buffer backwards if new > old */ - bool wrap = track_ridx > old_track_ridx; + /* The skip is technically over */ + skip_pending = TRACK_SKIP_NONE; - while (1) - { - cur_idx = (cur_idx + 1) & MAX_TRACK_MASK; + /* Sync the next track information */ + info = track_list_current(1); - /* if we've advanced past the wrap when cur_idx is zeroed */ - if (!cur_idx) - wrap = false; + id3_write(NEXTTRACK_ID3, info ? bufgetid3(info->id3_hid) : + id3_get(UNBUFFERED_ID3)); - /* if we aren't still on the wrap and we've caught the old track */ - if (!(wrap || cur_idx < old_track_ridx)) - break; + id3_mutex_unlock(); - /* If we hit a track in between without valid tag info, bail */ - if (!tracks[cur_idx].taginfo_ready) - { - taginfo_ready = false; - break; - } - } - if (!taginfo_ready) - { - audio_rebuffer(); - } - } + audio_playlist_track_change(); +} -skip_done: - audio_update_trackinfo(); - pcmbuf_start_track_change(automatic_skip); +/* Actually begin a transition and take care of the codec change - may complete + it now or ask pcmbuf for notification depending on the type and what pcmbuf + has to say */ +static void audio_begin_track_change(bool auto_skip, int trackstat) +{ + /* Even if the new track is bad, the old track must be finished off */ + bool finalised = pcmbuf_start_track_change(auto_skip); - if (get_codec_base_type(codec_loaded()) == - get_codec_base_type(thistrack_id3->codectype)) + if (finalised) { - /* codec is the same base type */ - logf("New track loaded"); - codec_ack_msg(Q_CODEC_REQUEST_COMPLETE, false); + /* pcmbuf says that the transition happens now - complete it */ + audio_finalise_track_change(false); + + if (play_status == PLAY_STOPPED) + return; /* Stopped us */ } - else + + if (!auto_skip) + audio_clear_paused_pcm(); + + if (trackstat >= LOAD_TRACK_OK) { - /* a codec change is required */ - logf("New codec: %d/%d", thistrack_id3->codectype, codec_loaded()); - codec_ack_msg(Q_CODEC_REQUEST_COMPLETE, true); - codec_load(tracks[track_ridx].codec_hid, thistrack_id3->codectype); - tracks[track_ridx].codec_hid = -1; /* Codec thread will close it */ + struct track_info *info = track_list_current(0); + + if (info->audio_hid < 0) + return; + + /* Everything needed for the codec is ready - start it */ + if (audio_start_codec(auto_skip)) + { + if (finalised) + playing_id3_sync(info, -1); + return; + } + + trackstat = LOAD_TRACK_ERR_START_CODEC; } -} -unsigned long audio_prev_elapsed(void) -{ - return prev_track_elapsed; + audio_handle_track_load_status(trackstat); } -void audio_set_prev_elapsed(unsigned long setting) +/* Transition to end-of-playlist state and begin wait for PCM to finish */ +static void audio_monitor_end_of_playlist(void) { - prev_track_elapsed = setting; + skip_pending = TRACK_SKIP_AUTO_END_PLAYLIST; + filling = STATE_ENDING; + pcmbuf_monitor_track_change(true); } -/* Stop the codec and reset the PCM buffer */ -static void audio_stop_codec_flush(void) +/* Codec has completed decoding the track + (usually Q_AUDIO_CODEC_COMPLETE) */ +static void audio_on_codec_complete(int status) { - bool pcm_playing; - - pcmbuf_pause(true); + logf("%s(%d)", __func__, status); - codec_stop(); + if (play_status == PLAY_STOPPED) + return; - pcm_play_lock(); + /* If it didn't notify us first, don't expect "seek complete" message + since the codec can't post it now - do things like it would have + done */ + audio_complete_codec_seek(); - pcm_playing = pcm_is_playing(); + if (play_status == PLAY_PAUSED || skip_pending != TRACK_SKIP_NONE) + { + /* Old-hay on the ip-skay - codec has completed decoding - pcmbuf_play_stop(); - queue_clear(&pcmbuf_queue); + Paused: We're not sounding it, so just remember that it happened + and the resume will begin the transition - if (pcm_playing) - pcmbuf_pause(paused); + Skipping: There was already a skip in progress, remember it and + allow no further progress until the PCM from the previous + song has finished + */ + codec_skip_pending = true; + codec_skip_status = status; + return; + } - pcm_play_unlock(); -} + codec_skip_pending = false; -static void audio_stop_playback(void) -{ - if (playing) + if (status >= 0) { - /* If we were playing, save resume information */ - struct mp3entry *id3 = NULL; + /* Normal automatic skip */ + ab_end_of_track_report(); + } - if (!ci.stop_codec) - id3 = audio_current_track(); + int trackstat = LOAD_TRACK_OK; - /* Save the current playing spot, or NULL if the playlist has ended */ - playlist_update_resume_info(id3); + automatic_skip = true; + skip_pending = TRACK_SKIP_AUTO; - /* Now it's good time to send track finish events. Do this - only if this hasn't been done already as part of a track - switch. */ - if (id3 == thistrack_id3) - send_event(PLAYBACK_EVENT_TRACK_FINISH, thistrack_id3); + /* Does this track have an entry allocated? */ + struct track_info *info = track_list_advance_current(1); - /* TODO: Create auto bookmark too? */ + if (!info || info->audio_hid < 0) + { + bool end_of_playlist = false; - prev_track_elapsed = othertrack_id3->elapsed; + if (info) + { + /* Track load is not complete - it might have stopped on a + full buffer without reaching the audio handle or we just + arrived at it early + + If this type is atomic and we couldn't get the audio, + perhaps it would need to wrap to make the allocation and + handles are in the way - to maximize the liklihood it can + be allocated, clear all handles to reset the buffer and + its indexes to 0 - for packet audio, this should not be an + issue and a pointless full reload of all the track's + metadata may be avoided */ + + struct mp3entry *track_id3 = bufgetid3(info->id3_hid); + + if (track_id3 && + get_audio_base_data_type(track_id3->codectype) + == TYPE_PACKET_AUDIO) + { + /* Continue filling after this track */ + audio_reset_and_rebuffer(TRACK_LIST_KEEP_CURRENT, 1); + audio_begin_track_change(true, trackstat); + return; + } + /* else rebuffer at this track; status applies to the track we + want */ + } + else if (!playlist_peek(1, NULL, 0)) + { + /* Play sequence is complete - directory change or other playlist + resequencing - the playlist must now be advanced in order to + continue since a peek ahead to the next track is not possible */ + skip_pending = TRACK_SKIP_AUTO_NEW_PLAYLIST; + end_of_playlist = playlist_next(1) < 0; + } - remove_event(BUFFER_EVENT_BUFFER_LOW, buffering_low_buffer_callback); - } + if (!end_of_playlist) + { + trackstat = audio_reset_and_rebuffer(TRACK_LIST_CLEAR_ALL, + skip_pending == TRACK_SKIP_AUTO ? 0 : -1); - audio_stop_codec_flush(); - paused = false; - playing = false; - track_load_started = false; + if (trackstat == LOAD_TRACK_ERR_NO_MORE) + { + /* Failed to find anything afterall - do playlist switchover + instead */ + skip_pending = TRACK_SKIP_AUTO_NEW_PLAYLIST; + end_of_playlist = playlist_next(1) < 0; + } + } - filling = STATE_IDLE; + if (end_of_playlist) + { + audio_monitor_end_of_playlist(); + return; + } + } - /* Mark all entries null. */ - audio_clear_track_entries(); + audio_begin_track_change(true, trackstat); +} - /* Close all tracks */ - audio_release_tracks(); +/* Called when codec completes seek operation + (usually Q_AUDIO_CODEC_SEEK_COMPLETE) */ +static void audio_on_codec_seek_complete(void) +{ + logf("%s()", __func__); + audio_complete_codec_seek(); + codec_go(); } -static void audio_play_start(size_t offset) +/* Called when PCM track change has completed + (Q_AUDIO_TRACK_CHANGED) */ +static void audio_on_track_changed(void) { - int i; + /* Finish whatever is pending so that the WPS is in sync */ + audio_finalise_track_change(true); - send_event(PLAYBACK_EVENT_START_PLAYBACK, NULL); -#if INPUT_SRC_CAPS != 0 - audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); - audio_set_output_source(AUDIO_SRC_PLAYBACK); -#endif + if (codec_skip_pending) + { + /* Codec got ahead completing a short track - complete the + codec's skip and begin the next */ + codec_skip_pending = false; + audio_on_codec_complete(codec_skip_status); + } +} - paused = false; - audio_stop_codec_flush(); +/* Begin playback from an idle state, transition to a new playlist or + invalidate the buffer and resume (if playing). + (usually Q_AUDIO_PLAY, Q_AUDIO_REMAKE_AUDIO_BUFFER) */ +static void audio_start_playback(size_t offset, unsigned int flags) +{ + enum play_status old_status = play_status; - playing = true; - track_load_started = false; + if (flags & AUDIO_START_NEWBUF) + { + /* Mark the buffer dirty - if not playing, it will be reset next + time */ + if (buffer_state == AUDIOBUF_STATE_INITIALIZED) + buffer_state = AUDIOBUF_STATE_VOICED_ONLY; + } - ci.new_track = 0; - ci.seek_time = 0; - wps_offset = 0; + if (old_status != PLAY_STOPPED) + { + logf("%s(%lu): skipping", __func__, (unsigned long)offset); -#ifndef PLATFORM_HAS_VOLUME_CHANGE - sound_set_volume(global_settings.volume); -#endif - track_widx = track_ridx = 0; - buf_set_base_handle(-1); + halt_decoding_track(true); - /* Clear all track entries. */ - for (i = 0; i < MAX_TRACK; i++) { - clear_track_info(&tracks[i]); - } + automatic_skip = false; + ff_rw_mode = false; - last_peek_offset = -1; + if (flags & AUDIO_START_RESTART) + { + /* Clear out some stuff to resume the current track where it + left off */ + pcmbuf_play_stop(); + offset = id3_get(PLAYING_ID3)->offset; + track_list_clear(TRACK_LIST_CLEAR_ALL); + } + else + { + /* This is more-or-less treated as manual track transition */ + /* Save resume information for current track */ + audio_playlist_track_finish(); + track_list_clear(TRACK_LIST_CLEAR_ALL); + + /* Indicate manual track change */ + pcmbuf_start_track_change(false); + audio_clear_paused_pcm(); + wipe_track_metadata(true); + } + + /* Set after track finish event in case skip was in progress */ + skip_pending = TRACK_SKIP_NONE; + } + else + { + if (flags & AUDIO_START_RESTART) + return; /* Must already be playing */ + + /* Cold playback start from a stopped state */ + logf("%s(%lu): starting", __func__, offset); + + /* Set audio parameters */ +#if INPUT_SRC_CAPS != 0 + audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); + audio_set_output_source(AUDIO_SRC_PLAYBACK); +#endif +#ifndef PLATFORM_HAS_VOLUME_CHANGE + sound_set_volume(global_settings.volume); +#endif + /* Update our state */ + play_status = PLAY_PLAYING; + } + + /* Start fill from beginning of playlist */ + playlist_peek_offset = -1; + buf_set_base_handle(-1); /* Officially playing */ queue_reply(&audio_queue, 1); - audio_fill_file_buffer(true, offset); + /* Add these now - finish event for the first id3 will most likely be sent + immediately */ + add_event(BUFFER_EVENT_REBUFFER, false, buffer_event_rebuffer_callback); + add_event(BUFFER_EVENT_FINISHED, false, buffer_event_finished_callback); + + if (old_status == PLAY_STOPPED) + { + /* Send coldstart event */ + send_event(PLAYBACK_EVENT_START_PLAYBACK, NULL); + } + + /* Fill the buffer */ + int trackstat = audio_fill_file_buffer(); + + if (trackstat >= LOAD_TRACK_OK) + { + /* This is the currently playing track - get metadata, stat */ + playing_id3_sync(track_list_current(0), offset); + + if (valid_mp3entry(id3_get(PLAYING_ID3))) + { + /* Only if actually changing tracks... */ + if (!(flags & AUDIO_START_RESTART)) + audio_playlist_track_change(); + } + } + else + { + /* Found nothing playable */ + audio_handle_track_load_status(trackstat); + } +} + +/* Stop playback and enter an idle state + (usually Q_AUDIO_STOP) */ +static void audio_stop_playback(void) +{ + logf("%s()", __func__); + + if (play_status == PLAY_STOPPED) + return; + + /* Stop the codec and unload it */ + halt_decoding_track(true); + pcmbuf_play_stop(); + codec_unload(); + + /* Save resume information - "filling" might have been set to + "STATE_ENDED" by caller in order to facilitate end of playlist */ + audio_playlist_track_finish(); + + skip_pending = TRACK_SKIP_NONE; + automatic_skip = false; + + /* Close all tracks and mark them NULL */ + remove_event(BUFFER_EVENT_REBUFFER, buffer_event_rebuffer_callback); + remove_event(BUFFER_EVENT_FINISHED, buffer_event_finished_callback); + remove_event(BUFFER_EVENT_BUFFER_LOW, buffer_event_buffer_low_callback); + + track_list_clear(TRACK_LIST_CLEAR_ALL); + + /* Update our state */ + ff_rw_mode = false; + play_status = PLAY_STOPPED; + + wipe_track_metadata(true); + + /* Go idle */ + filling = STATE_IDLE; + cancel_cpu_boost(); +} + +/* Pause the playback of the current track + (Q_AUDIO_PAUSE) */ +static void audio_on_pause(bool pause) +{ + logf("%s(%s)", __func__, pause ? "true" : "false"); + + if (play_status == PLAY_STOPPED || pause == (play_status == PLAY_PAUSED)) + return; + + if (!ff_rw_mode) + { + /* Not in ff/rw mode - may set the state (otherwise this could make + old data play because seek hasn't completed and cleared it) */ + pcmbuf_pause(pause); + } + + play_status = pause ? PLAY_PAUSED : PLAY_PLAYING; + + if (!pause && codec_skip_pending) + { + /* Actually do the skip that is due - resets the status flag */ + audio_on_codec_complete(codec_skip_status); + } +} + +/* Skip a certain number of tracks forwards or backwards + (Q_AUDIO_SKIP) */ +static void audio_on_skip(void) +{ + id3_mutex_lock(); + + /* Eat the delta to keep it synced, even if not playing */ + int toskip = skip_offset; + skip_offset = 0; + + logf("%s(): %d", __func__, toskip); + + id3_mutex_unlock(); + + if (play_status == PLAY_STOPPED) + return; + + /* Force codec to abort this track */ + halt_decoding_track(true); + + /* Kill the ff/rw halt */ + ff_rw_mode = false; + + /* Manual skip */ + automatic_skip = false; + + /* If there was an auto skip in progress, there will be residual + advancement of the playlist and/or track list so compensation will be + required in order to end up in the right spot */ + int track_list_delta = toskip; + int playlist_delta = toskip; + + if (skip_pending != TRACK_SKIP_NONE) + { + if (skip_pending != TRACK_SKIP_AUTO_END_PLAYLIST) + track_list_delta--; + + if (skip_pending == TRACK_SKIP_AUTO_NEW_PLAYLIST) + playlist_delta--; + } + + audio_playlist_track_finish(); + skip_pending = TRACK_SKIP_NONE; + + /* Update the playlist current track now */ + while (playlist_next(playlist_delta) < 0) + { + /* Manual skip out of range (because the playlist wasn't updated + yet by us and so the check in audio_skip returned 'ok') - bring + back into range */ + int d = toskip < 0 ? 1 : -1; + + while (!playlist_check(playlist_delta)) + { + if (playlist_delta == d) + { + /* Had to move the opposite direction to correct, which is + wrong - this is the end */ + filling = STATE_ENDED; + audio_stop_playback(); + return; + } + + playlist_delta += d; + track_list_delta += d; + } + } + + /* Adjust things by how much the playlist was manually moved */ + playlist_peek_offset -= playlist_delta; + + struct track_info *info = track_list_advance_current(track_list_delta); + int trackstat = LOAD_TRACK_OK; + + if (!info || info->audio_hid < 0) + { + /* We don't know the next track thus we know we don't have it */ + trackstat = audio_reset_and_rebuffer(TRACK_LIST_CLEAR_ALL, -1); + } + + audio_begin_track_change(false, trackstat); +} + +/* Skip to the next/previous directory + (Q_AUDIO_DIR_SKIP) */ +static void audio_on_dir_skip(int direction) +{ + logf("%s(%d)", __func__, direction); + + id3_mutex_lock(); + skip_offset = 0; + id3_mutex_unlock(); + + if (play_status == PLAY_STOPPED) + return; + + /* Force codec to abort this track */ + halt_decoding_track(true); + + /* Kill the ff/rw halt */ + ff_rw_mode = false; + + /* Manual skip */ + automatic_skip = false; + + audio_playlist_track_finish(); + + /* Unless automatic and gapless, skips do not pend */ + skip_pending = TRACK_SKIP_NONE; + + /* Regardless of the return value we need to rebuffer. If it fails the old + playlist will resume, else the next dir will start playing. */ + playlist_next_dir(direction); + + wipe_track_metadata(false); + + int trackstat = audio_reset_and_rebuffer(TRACK_LIST_CLEAR_ALL, -1); + + if (trackstat == LOAD_TRACK_ERR_NO_MORE) + { + /* The day the music died - finish-off whatever is playing and call it + quits */ + audio_monitor_end_of_playlist(); + return; + } + + audio_begin_track_change(false, trackstat); +} + +/* Enter seek mode in order to start a seek + (Q_AUDIO_PRE_FF_REWIND) */ +static void audio_on_pre_ff_rewind(void) +{ + logf("%s()", __func__); + + if (play_status == PLAY_STOPPED || ff_rw_mode) + return; + + ff_rw_mode = true; + + if (play_status == PLAY_PAUSED) + return; + + pcmbuf_pause(true); +} + +/* Seek the playback of the current track to the specified time + (Q_AUDIO_FF_REWIND) */ +static void audio_on_ff_rewind(long time) +{ + logf("%s(%ld)", __func__, time); + + if (play_status == PLAY_STOPPED) + return; + + enum track_skip_type pending = skip_pending; - add_event(BUFFER_EVENT_BUFFER_LOW, false, buffering_low_buffer_callback); + switch (pending) + { + case TRACK_SKIP_NONE: /* The usual case */ + case TRACK_SKIP_AUTO: /* Have to back it out (fun!) */ + case TRACK_SKIP_AUTO_END_PLAYLIST: /* Still have the last codec used */ + { + struct mp3entry *id3 = id3_get(PLAYING_ID3); + struct mp3entry *ci_id3 = id3_get(CODEC_ID3); + + automatic_skip = false; + + /* Send event before clobbering the time */ + /* FIXME: Nasty, but the tagtree expects this so that rewinding and + then skipping back to this track resumes properly. Something else + should be sent. We're not _really_ finishing the track are we? */ + if (time == 0) + send_event(PLAYBACK_EVENT_TRACK_FINISH, id3); + + /* Prevent user codec time update - coerce to something that is + innocuous concerning lookaheads */ + if (pending == TRACK_SKIP_NONE) + skip_pending = TRACK_SKIP_AUTO_END_PLAYLIST; + + id3->elapsed = time; + queue_reply(&audio_queue, 1); + + bool haltres = halt_decoding_track(pending == TRACK_SKIP_AUTO); + + /* Need this set in case ff/rw mode + error but _after_ the codec + halt that will reset it */ + codec_seeking = true; + + if (pending == TRACK_SKIP_AUTO) + { + if (!track_list_advance_current(-1)) + { + /* Not in list - must rebuffer at the current playlist index */ + if (audio_reset_and_rebuffer(TRACK_LIST_CLEAR_ALL, -1) + < LOAD_TRACK_OK) + { + /* Codec is stopped */ + break; + } + } + } + + /* Set after audio_fill_file_buffer to disable playing id3 clobber if + rebuffer is needed */ + skip_pending = TRACK_SKIP_NONE; + struct track_info *cur_info = track_list_current(0); + + /* Track must complete the loading _now_ since a codec and audio + handle are needed in order to do the seek */ + if (cur_info->audio_hid < 0 && + audio_finish_load_track(cur_info) != LOAD_TRACK_READY) + { + /* Call above should push any load sequence - no need for + halt_decoding_track here if no skip was pending here because + there would not be a codec started if no audio handle was yet + opened */ + break; + } + + if (pending == TRACK_SKIP_AUTO) + { + if (!bufreadid3(cur_info->id3_hid, ci_id3) || + !audio_init_codec(cur_info, ci_id3)) + { + /* We should have still been able to get it - skip it and move + onto the next one - like it or not this track is borken */ + break; + } + + /* Set the codec API to the correct metadata and track info */ + ci.audio_hid = cur_info->audio_hid; + ci.filesize = cur_info->filesize; + buf_set_base_handle(cur_info->audio_hid); + } + + if (!haltres) + { + /* If codec must be (re)started, reset the offset */ + ci_id3->offset = 0; + } - LOGFQUEUE("audio > audio Q_AUDIO_TRACK_CHANGED"); - queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0); + codec_seek(time); + return; + } + + case TRACK_SKIP_AUTO_NEW_PLAYLIST: + { + /* We cannot do this because the playlist must be reversed by one + and it doesn't always return the same song when going backwards + across boundaries as forwards (either because of randomization + or inconsistency in deciding what the previous track should be), + therefore the whole operation would often end up as nonsense - + lock out seeking for a couple seconds */ + + /* Sure as heck cancel seek mode too! */ + audio_ff_rewind_end(); + return; + } + + default: + /* Won't see this */ + return; + } + + if (play_status == PLAY_STOPPED) + { + /* Playback ended because of an error completing a track load */ + return; + } + + /* Always fake it as a codec start error which will handle mode + cancellations and skip to the next track */ + audio_handle_track_load_status(LOAD_TRACK_ERR_START_CODEC); +} + +/* Invalidates all but currently playing track + (Q_AUDIO_FLUSH) */ +static void audio_on_audio_flush(void) +{ + logf("%s", __func__); + + if (track_list_empty()) + return; /* Nothing to flush out */ + + switch (skip_pending) + { + case TRACK_SKIP_NONE: + case TRACK_SKIP_AUTO_END_PLAYLIST: + /* Remove all but the currently playing track from the list and + refill after that */ + track_list_clear(TRACK_LIST_KEEP_CURRENT); + playlist_peek_offset = 0; + id3_write_locked(UNBUFFERED_ID3, NULL); + audio_update_and_announce_next_track(NULL); + + /* Ignore return since it's about the next track, not this one */ + audio_fill_file_buffer(); + + if (skip_pending == TRACK_SKIP_NONE) + break; + + /* There's now a track after this one now - convert to auto skip - + no skip should pend right now because multiple flush messages can + be fired which would cause a restart in the below cases */ + skip_pending = TRACK_SKIP_NONE; + audio_clear_track_notifications(); + audio_queue_post(Q_AUDIO_CODEC_COMPLETE, CODEC_OK); + break; + + case TRACK_SKIP_AUTO: + case TRACK_SKIP_AUTO_NEW_PLAYLIST: + /* Precisely removing what it already decoded for the next track is + not possible so a restart is required in order to continue the + currently playing track without the now invalid future track + playing */ + audio_start_playback(0, AUDIO_START_RESTART); + break; + + default: /* Nothing else is a state */ + break; + } } +#ifdef AUDIO_HAVE_RECORDING +/* Load the requested encoder type + (Q_AUDIO_LOAD_ENCODER) */ +static void audio_on_load_encoder(int afmt) +{ + bool res = true; + + if (play_status != PLAY_STOPPED) + audio_stop_playback(); /* Can't load both types at once */ + else + codec_unload(); /* Encoder still loaded, stop and unload it */ + + if (afmt != AFMT_UNKNOWN) + { + res = codec_load(-1, afmt | CODEC_TYPE_ENCODER); + if (res) + codec_go(); /* These are run immediately */ + } + + queue_reply(&audio_queue, res); +} +#endif /* AUDIO_HAVE_RECORDING */ -/* Invalidates all but currently playing track. */ -static void audio_invalidate_tracks(void) +static void audio_thread(void) { - if (audio_have_tracks()) + struct queue_event ev; + + pcm_postinit(); + + filling = STATE_IDLE; + + while (1) { - last_peek_offset = 0; - track_widx = track_ridx; + switch (filling) + { + /* Active states */ + case STATE_FULL: + case STATE_END_OF_PLAYLIST: + if (buf_get_watermark() == 0) + { + /* End of buffering for now, let's calculate the watermark, + register for a low buffer event and unboost */ + audio_update_filebuf_watermark(0); + add_event(BUFFER_EVENT_BUFFER_LOW, true, + buffer_event_buffer_low_callback); + } + /* Fall-through */ + case STATE_FINISHED: + /* All data was buffered */ + cancel_cpu_boost(); + /* Fall-through */ + case STATE_FILLING: + case STATE_ENDING: + if (audio_pcmbuf_track_change_scan()) + { + /* Transfer notification to audio queue event */ + ev.id = Q_AUDIO_TRACK_CHANGED; + ev.data = 1; + } + else + { + /* If doing auto skip, poll pcmbuf track notifications a bit + faster to promply detect the transition */ + queue_wait_w_tmo(&audio_queue, &ev, + skip_pending == TRACK_SKIP_NONE ? + HZ/2 : HZ/10); + } + break; + + /* Idle states */ + default: + queue_wait(&audio_queue, &ev); + +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) + switch (ev.id) + { +#ifdef AUDIO_HAVE_RECORDING + /* Must monitor the encoder message for recording so it can remove + it if we process the insertion before it does. It cannot simply + be removed from under recording however. */ + case Q_AUDIO_LOAD_ENCODER: + break; +#endif + case SYS_USB_DISCONNECTED: + filling = STATE_IDLE; + break; + + default: + if (filling == STATE_USB) + continue; + } +#endif /* CONFIG_PLATFORM */ + } + + switch (ev.id) + { + /** Codec and track change messages **/ + case Q_AUDIO_CODEC_COMPLETE: + /* Codec is done processing track and has gone idle */ + LOGFQUEUE("audio < Q_AUDIO_CODEC_COMPLETE: %ld", (long)ev.data); + audio_on_codec_complete(ev.data); + break; + + case Q_AUDIO_CODEC_SEEK_COMPLETE: + /* Codec is done seeking */ + LOGFQUEUE("audio < Q_AUDIO_SEEK_COMPLETE"); + audio_on_codec_seek_complete(); + break; + + case Q_AUDIO_TRACK_CHANGED: + /* PCM track change done */ + LOGFQUEUE("audio < Q_AUDIO_TRACK_CHANGED"); + audio_on_track_changed(); + break; - /* Mark all other entries null (also buffered wrong metadata). */ - audio_clear_track_entries(); + /** Control messages **/ + case Q_AUDIO_PLAY: + LOGFQUEUE("audio < Q_AUDIO_PLAY"); + audio_start_playback(ev.data, 0); + break; + + case Q_AUDIO_STOP: + LOGFQUEUE("audio < Q_AUDIO_STOP"); + audio_stop_playback(); + if (ev.data != 0) + queue_clear(&audio_queue); + break; + + case Q_AUDIO_PAUSE: + LOGFQUEUE("audio < Q_AUDIO_PAUSE"); + audio_on_pause(ev.data); + break; + + case Q_AUDIO_SKIP: + LOGFQUEUE("audio < Q_AUDIO_SKIP"); + audio_on_skip(); + break; - track_widx = (track_widx + 1) & MAX_TRACK_MASK; + case Q_AUDIO_DIR_SKIP: + LOGFQUEUE("audio < Q_AUDIO_DIR_SKIP"); + audio_on_dir_skip(ev.data); + break; + + case Q_AUDIO_PRE_FF_REWIND: + LOGFQUEUE("audio < Q_AUDIO_PRE_FF_REWIND"); + audio_on_pre_ff_rewind(); + break; + + case Q_AUDIO_FF_REWIND: + LOGFQUEUE("audio < Q_AUDIO_FF_REWIND"); + audio_on_ff_rewind(ev.data); + break; + + case Q_AUDIO_FLUSH: + LOGFQUEUE("audio < Q_AUDIO_FLUSH: %d", (int)ev.data); + audio_on_audio_flush(); + break; - audio_fill_file_buffer(false, 0); - send_event(PLAYBACK_EVENT_TRACK_CHANGE, thistrack_id3); + /** Buffering messages **/ + case Q_AUDIO_BUFFERING: + /* some buffering event */ + LOGFQUEUE("audio < Q_AUDIO_BUFFERING: %d", (int)ev.data); + audio_on_buffering(ev.data); + break; + + case Q_AUDIO_FILL_BUFFER: + /* continue buffering next track */ + LOGFQUEUE("audio < Q_AUDIO_FILL_BUFFER"); + audio_on_fill_buffer(); + break; + + case Q_AUDIO_FINISH_LOAD_TRACK: + /* metadata is buffered */ + LOGFQUEUE("audio < Q_AUDIO_FINISH_LOAD_TRACK"); + audio_on_finish_load_track(ev.data); + break; + + case Q_AUDIO_HANDLE_FINISHED: + /* some other type is buffered */ + LOGFQUEUE("audio < Q_AUDIO_HANDLE_FINISHED"); + audio_on_handle_finished(ev.data); + break; + + /** Miscellaneous messages **/ + case Q_AUDIO_REMAKE_AUDIO_BUFFER: + /* buffer needs to be reinitialized */ + LOGFQUEUE("audio < Q_AUDIO_REMAKE_AUDIO_BUFFER"); + audio_start_playback(0, AUDIO_START_RESTART | AUDIO_START_NEWBUF); + break; + +#ifdef HAVE_DISK_STORAGE + case Q_AUDIO_UPDATE_WATERMARK: + /* buffering watermark needs updating */ + LOGFQUEUE("audio < Q_AUDIO_UPDATE_WATERMARK: %d", (int)ev.data); + audio_update_filebuf_watermark(ev.data); + break; +#endif /* HAVE_DISK_STORAGE */ + +#ifdef AUDIO_HAVE_RECORDING + case Q_AUDIO_LOAD_ENCODER: + /* load an encoder for recording */ + LOGFQUEUE("audio < Q_AUDIO_LOAD_ENCODER: %d", (int)ev.data); + audio_on_load_encoder(ev.data); + break; +#endif /* AUDIO_HAVE_RECORDING */ + +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) + case SYS_USB_CONNECTED: + LOGFQUEUE("audio < SYS_USB_CONNECTED"); + audio_stop_playback(); +#ifdef PLAYBACK_VOICE + voice_stop(); +#endif + filling = STATE_USB; + usb_acknowledge(SYS_USB_CONNECTED_ACK); + break; +#endif /* (CONFIG_PLATFORM & PLATFORM_NATIVE) */ + + case SYS_TIMEOUT: + LOGFQUEUE_SYS_TIMEOUT("audio < SYS_TIMEOUT"); + break; + + default: + /* LOGFQUEUE("audio < default : %08lX", ev.id); */ + break; + } /* end switch */ + } /* end while */ +} + + +/* --- Buffering callbacks --- */ + +/* Called when fullness is below the watermark level */ +static void buffer_event_buffer_low_callback(void *data) +{ + logf("low buffer callback"); + LOGFQUEUE("buffering > audio Q_AUDIO_BUFFERING: buffer low"); + audio_queue_post(Q_AUDIO_BUFFERING, BUFFER_EVENT_BUFFER_LOW); + (void)data; +} + +/* Called when handles must be discarded in order to buffer new data */ +static void buffer_event_rebuffer_callback(void *data) +{ + logf("rebuffer callback"); + LOGFQUEUE("buffering > audio Q_AUDIO_BUFFERING: rebuffer"); + audio_queue_post(Q_AUDIO_BUFFERING, BUFFER_EVENT_REBUFFER); + (void)data; +} + +/* A handle has completed buffering and all required data is available */ +static void buffer_event_finished_callback(void *data) +{ + int hid = *(const int *)data; + const enum data_type htype = buf_handle_data_type(hid); + + logf("handle %d finished buffering (type:%u)", hid, (unsigned)htype); + + /* Limit queue traffic */ + switch (htype) + { + case TYPE_ID3: + /* The metadata handle for the last loaded track has been buffered. + We can ask the audio thread to load the rest of the track's data. */ + LOGFQUEUE("buffering > audio Q_AUDIO_FINISH_LOAD_TRACK: %d", hid); + audio_queue_post(Q_AUDIO_FINISH_LOAD_TRACK, hid); + break; + + case TYPE_PACKET_AUDIO: + /* Strip any useless trailing tags that are left. */ + strip_tags(hid); + /* Fall-through */ + case TYPE_ATOMIC_AUDIO: + LOGFQUEUE("buffering > audio Q_AUDIO_HANDLE_FINISHED: %d", hid); + audio_queue_post(Q_AUDIO_HANDLE_FINISHED, hid); + break; + + default: + /* Don't care to know about these */ + break; + } +} + + +/** -- Codec callbacks -- **/ + +/* Update elapsed times with latency-adjusted values */ +void audio_codec_update_elapsed(unsigned long value) +{ +#ifdef AB_REPEAT_ENABLE + ab_position_report(value); +#endif + + unsigned long latency = pcmbuf_get_latency(); + + if (LIKELY(value >= latency)) + { + unsigned long elapsed = value - latency; + + if (elapsed > value || elapsed < value - 2) + value = elapsed; + } + else + { + value = 0; + } + + /* Track codec: used later when updating the playing at the user + transition */ + id3_get(CODEC_ID3)->elapsed = value; + + /* If a skip is pending, the PCM buffer is updating the time on the + previous song */ + if (LIKELY(skip_pending == TRACK_SKIP_NONE)) + id3_get(PLAYING_ID3)->elapsed = value; +} + +/* Update offsets with latency-adjusted values */ +void audio_codec_update_offset(size_t value) +{ + struct mp3entry *ci_id3 = id3_get(CODEC_ID3); + unsigned long latency = pcmbuf_get_latency() * ci_id3->bitrate / 8; + + if (LIKELY(value >= latency)) + { + value -= latency; + } + else + { + value = 0; + } + + /* Track codec: used later when updating the playing id3 at the user + transition */ + ci_id3->offset = value; + + /* If a skip is pending, the PCM buffer is updating the time on the + previous song */ + if (LIKELY(skip_pending == TRACK_SKIP_NONE)) + id3_get(PLAYING_ID3)->offset = value; +} + + +/** --- Pcmbuf callbacks --- **/ + +/* Between the codec and PCM track change, we need to keep updating the + * "elapsed" value of the previous (to the codec, but current to the + * user/PCM/WPS) track, so that the progressbar reaches the end. */ +void audio_pcmbuf_position_callback(unsigned int time) +{ + struct mp3entry *id3 = id3_get(PLAYING_ID3); + + time += id3->elapsed; + + id3->elapsed = MIN(time, id3->length); +} + +/* Post message from pcmbuf that the end of the previous track has just + * been played */ +void audio_pcmbuf_track_change(bool pcmbuf) +{ + if (pcmbuf) + { + /* Notify of the change in special-purpose semaphore object */ + LOGFQUEUE("pcmbuf > pcmbuf Q_AUDIO_TRACK_CHANGED"); + audio_pcmbuf_track_change_post(); + } + else + { + /* Safe to post directly to the queue */ + LOGFQUEUE("pcmbuf > audio Q_AUDIO_TRACK_CHANGED"); + audio_queue_post(Q_AUDIO_TRACK_CHANGED, 0); + } +} + +/* May pcmbuf start PCM playback when the buffer is full enough? */ +bool audio_pcmbuf_may_play(void) +{ + return play_status == PLAY_PLAYING && !ff_rw_mode; +} + + +/** -- External interfaces -- **/ + +/* Return the playback and recording status */ +int audio_status(void) +{ + unsigned int ret = play_status; + +#ifdef AUDIO_HAVE_RECORDING + /* Do this here for constitency with mpeg.c version */ + ret |= pcm_rec_status(); +#endif + + return (int)ret; +} + +/* Clear all accumulated audio errors for playback and recording */ +void audio_error_clear(void) +{ +#ifdef AUDIO_HAVE_RECORDING + pcm_rec_error_clear(); +#endif +} + +/* Get a copy of the id3 data for the for current track + offset + skip delta */ +bool audio_peek_track(struct mp3entry *id3, int offset) +{ + bool retval = false; + + id3_mutex_lock(); + + if (play_status != PLAY_STOPPED) + { + id3->path[0] = '\0'; /* Null path means it should be filled now */ + retval = audio_get_track_metadata(offset + skip_offset, id3) && + id3->path[0] != '\0'; + } + + id3_mutex_unlock(); + + return retval; +} + +/* Return the mp3entry for the currently playing track */ +struct mp3entry * audio_current_track(void) +{ + struct mp3entry *id3; + + id3_mutex_lock(); + +#ifdef AUDIO_FAST_SKIP_PREVIEW + if (skip_offset != 0) + { + /* This is a peekahead */ + id3 = id3_get(PLAYING_PEEK_ID3); + audio_peek_track(id3, 0); + } + else +#endif + { + /* Normal case */ + id3 = id3_get(PLAYING_ID3); + audio_get_track_metadata(0, id3); + } + + id3_mutex_unlock(); + + return id3; +} + +/* Obtains the mp3entry for the next track from the current */ +struct mp3entry * audio_next_track(void) +{ + struct mp3entry *id3 = id3_get(NEXTTRACK_ID3); + + id3_mutex_lock(); + +#ifdef AUDIO_FAST_SKIP_PREVIEW + if (skip_offset != 0) + { + /* This is a peekahead */ + if (!audio_peek_track(id3, 1)) + id3 = NULL; + } + else +#endif + { + /* Normal case */ + if (!audio_get_track_metadata(1, id3)) + id3 = NULL; + } + + id3_mutex_unlock(); + + return id3; +} + +/* Start playback at the specified offset */ +void audio_play(long offset) +{ + logf("audio_play"); + +#ifdef PLAYBACK_VOICE + /* Truncate any existing voice output so we don't have spelling + * etc. over the first part of the played track */ + talk_force_shutup(); +#endif + + LOGFQUEUE("audio >| audio Q_AUDIO_PLAY: %ld", offset); + audio_queue_send(Q_AUDIO_PLAY, offset); +} + +/* Stop playback if playing */ +void audio_stop(void) +{ + LOGFQUEUE("audio >| audio Q_AUDIO_STOP"); + audio_queue_send(Q_AUDIO_STOP, 0); +} + +/* Pause playback if playing */ +void audio_pause(void) +{ + LOGFQUEUE("audio >| audio Q_AUDIO_PAUSE"); + audio_queue_send(Q_AUDIO_PAUSE, true); +} + +/* This sends a stop message and the audio thread will dump all its + subsequent messages */ +void audio_hard_stop(void) +{ + /* Stop playback */ + LOGFQUEUE("audio >| audio Q_AUDIO_STOP: 1"); + audio_queue_send(Q_AUDIO_STOP, 1); +#ifdef PLAYBACK_VOICE + voice_stop(); +#endif +} + +/* Resume playback if paused */ +void audio_resume(void) +{ + LOGFQUEUE("audio >| audio Q_AUDIO_PAUSE resume"); + audio_queue_send(Q_AUDIO_PAUSE, false); +} + +/* Skip the specified number of tracks forward or backward from the current */ +void audio_skip(int offset) +{ + id3_mutex_lock(); + + /* If offset has to be backed-out to stay in range, no skip is done */ + int accum = skip_offset + offset; + + while (offset != 0 && !playlist_check(accum)) + { + offset += offset < 0 ? 1 : -1; + accum = skip_offset + offset; + } + + if (offset != 0) + { + /* Accumulate net manual skip count since the audio thread last + processed one */ + skip_offset = accum; + + if (global_settings.beep) + pcmbuf_beep(2000, 100, 2500*global_settings.beep); + + LOGFQUEUE("audio > audio Q_AUDIO_SKIP %d", offset); + +#ifdef AUDIO_FAST_SKIP_PREVIEW + /* Do this before posting so that the audio thread can correct us + when things settle down - additionally, if audio gets a message + and the delta is zero, the Q_AUDIO_SKIP handler (audio_on_skip) + handler a skip event with the correct info but doesn't skip */ + send_event(PLAYBACK_EVENT_TRACK_SKIP, NULL); +#endif /* AUDIO_FAST_SKIP_PREVIEW */ + + /* Playback only needs the final state even if more than one is + processed because it wasn't removed in time */ + queue_remove_from_head(&audio_queue, Q_AUDIO_SKIP); + audio_queue_post(Q_AUDIO_SKIP, 0); + } + else + { + /* No more tracks */ + if (global_settings.beep) + pcmbuf_beep(1000, 100, 1500*global_settings.beep); + } + + id3_mutex_unlock(); +} + +/* Skip one track forward from the current */ +void audio_next(void) +{ + audio_skip(1); +} + +/* Skip one track backward from the current */ +void audio_prev(void) +{ + audio_skip(-1); +} + +/* Move one directory forward */ +void audio_next_dir(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP 1"); + audio_queue_post(Q_AUDIO_DIR_SKIP, 1); +} + +/* Move one directory backward */ +void audio_prev_dir(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_DIR_SKIP -1"); + audio_queue_post(Q_AUDIO_DIR_SKIP, -1); +} + +/* Pause playback in order to start a seek that flushes the old audio */ +void audio_pre_ff_rewind(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_PRE_FF_REWIND"); + audio_queue_post(Q_AUDIO_PRE_FF_REWIND, 0); +} + +/* Seek to the new time in the current track */ +void audio_ff_rewind(long time) +{ + LOGFQUEUE("audio > audio Q_AUDIO_FF_REWIND"); + audio_queue_post(Q_AUDIO_FF_REWIND, time); +} + +/* Clear all but the currently playing track then rebuffer */ +void audio_flush_and_reload_tracks(void) +{ + LOGFQUEUE("audio > audio Q_AUDIO_FLUSH"); + audio_queue_post(Q_AUDIO_FLUSH, 0); +} + +/* Return the pointer to the main audio buffer, optionally preserving + voicing */ +unsigned char * audio_get_buffer(bool talk_buf, size_t *buffer_size) +{ + unsigned char *buf, *end; + + if (audio_is_initialized) + { + audio_hard_stop(); + } + /* else buffer_state will be AUDIOBUF_STATE_TRASHED at this point */ + + if (buffer_size == NULL) + { + /* Special case for talk_init to use since it already knows it's + trashed */ + buffer_state = AUDIOBUF_STATE_TRASHED; + return NULL; + } + + if (talk_buf || buffer_state == AUDIOBUF_STATE_TRASHED + || !talk_voice_required()) + { + logf("get buffer: talk, audio"); + /* Ok to use everything from audiobuf to audiobufend - voice is loaded, + the talk buffer is not needed because voice isn't being used, or + could be AUDIOBUF_STATE_TRASHED already. If state is + AUDIOBUF_STATE_VOICED_ONLY, no problem as long as memory isn't written + without the caller knowing what's going on. Changing certain settings + may move it to a worse condition but the memory in use by something + else will remain undisturbed. + */ + if (buffer_state != AUDIOBUF_STATE_TRASHED) + { + talk_buffer_steal(); + buffer_state = AUDIOBUF_STATE_TRASHED; + } + + buf = audiobuf; + end = audiobufend; + } + else + { + /* Safe to just return this if already AUDIOBUF_STATE_VOICED_ONLY or + still AUDIOBUF_STATE_INITIALIZED */ + /* Skip talk buffer and move pcm buffer to end to maximize available + contiguous memory - no audio running means voice will not need the + swap space */ + logf("get buffer: audio"); + buf = audiobuf + talk_get_bufsize(); + end = audiobufend - pcmbuf_init(audiobufend); + buffer_state = AUDIOBUF_STATE_VOICED_ONLY; + } + + *buffer_size = end - buf; + + return buf; +} + +#ifdef HAVE_RECORDING +/* Stop audio, voice and obtain all available buffer space */ +unsigned char * audio_get_recording_buffer(size_t *buffer_size) +{ + audio_hard_stop(); + talk_buffer_steal(); + + unsigned char *end = audiobufend; + buffer_state = AUDIOBUF_STATE_TRASHED; + *buffer_size = end - audiobuf; + + return (unsigned char *)audiobuf; +} +#endif /* HAVE_RECORDING */ + +/* Restore audio buffer to a particular state (one more valid than the current + state) */ +bool audio_restore_playback(int type) +{ + switch (type) + { + case AUDIO_WANT_PLAYBACK: + if (buffer_state != AUDIOBUF_STATE_INITIALIZED) + audio_reset_buffer(); + return true; + case AUDIO_WANT_VOICE: + if (buffer_state == AUDIOBUF_STATE_TRASHED) + audio_reset_buffer(); + return true; + default: + return false; } } -static void audio_new_playlist(void) +/* Has the playback buffer been completely claimed? */ +bool audio_buffer_state_trashed(void) { - /* Prepare to start a new fill from the beginning of the playlist */ - last_peek_offset = -1; + return buffer_state == AUDIOBUF_STATE_TRASHED; +} - /* Signal the codec to initiate a track change forward */ - new_playlist = true; - ci.new_track = 1; - if (audio_have_tracks()) +/** --- Miscellaneous public interfaces --- **/ + +#ifdef HAVE_ALBUMART +/* Return which album art handle is current for the user in the given slot */ +int playback_current_aa_hid(int slot) +{ + if ((unsigned)slot < MAX_MULTIPLE_AA) { - if (paused) - skipped_during_pause = true; - track_widx = track_ridx; - audio_clear_track_entries(); + struct track_info *info = track_list_user_current(skip_offset); - track_widx = (track_widx + 1) & MAX_TRACK_MASK; + if (!info && abs(skip_offset) <= 1) + { + /* Give the actual position a go */ + info = track_list_user_current(0); + } - /* Mark the current track as invalid to prevent skipping back to it */ - CUR_TI->taginfo_ready = false; + if (info) + return info->aa_hid[slot]; } - /* Officially playing */ - queue_reply(&audio_queue, 1); - - audio_fill_file_buffer(false, 0); -} - -/* Called on manual track skip */ -static void audio_initiate_track_change(long direction) -{ - logf("audio_initiate_track_change(%ld)", direction); - - ci.new_track += direction; - wps_offset -= direction; - if (paused) - skipped_during_pause = true; -} - -/* Called on manual dir skip */ -static void audio_initiate_dir_change(long direction) -{ - dir_skip = true; - ci.new_track = direction; - if (paused) - skipped_during_pause = true; + return ERR_HANDLE_NOT_FOUND; } -/* Called when PCM track change is complete */ -static void audio_finalise_track_change(void) +/* Find an album art slot that doesn't match the dimensions of another that + is already claimed - increment the use count if it is */ +int playback_claim_aa_slot(struct dim *dim) { - logf("audio_finalise_track_change"); + int i; - if (automatic_skip) + /* First try to find a slot already having the size to reuse it since we + don't want albumart of the same size buffered multiple times */ + FOREACH_ALBUMART(i) { - wps_offset = 0; - automatic_skip = false; - - /* Invalidate prevtrack_id3 */ - memset(othertrack_id3, 0, sizeof(struct mp3entry)); + struct albumart_slot *slot = &albumart_slots[i]; - if (prev_ti && prev_ti->audio_hid < 0) + if (slot->dim.width == dim->width && + slot->dim.height == dim->height) { - /* No audio left so we clear all the track info. */ - clear_track_info(prev_ti); + slot->used++; + return i; } } - send_event(PLAYBACK_EVENT_TRACK_CHANGE, thistrack_id3); - playlist_update_resume_info(audio_current_track()); -} - -static void audio_seek_complete(void) -{ - logf("audio_seek_complete"); - - if (!playing) - return; - - /* If seeking-while-playing, pcm_is_paused() is true. - * If seeking-while-paused, audio_status PAUSE is true. - * A seamless seek skips this section. */ - ci.seek_time = 0; - pcm_play_lock(); - - if (pcm_is_paused() || paused) + /* Size is new, find a free slot */ + FOREACH_ALBUMART(i) { - /* Clear the buffer */ - pcmbuf_play_stop(); - - /* If seeking-while-playing, resume PCM playback */ - if (!paused) - pcmbuf_pause(false); + if (!albumart_slots[i].used) + { + albumart_slots[i].used++; + albumart_slots[i].dim = *dim; + return i; + } } - pcm_play_unlock(); + /* Sorry, no free slot */ + return -1; } -static void audio_codec_status_message(long reason, int status) +/* Invalidate the albumart_slot - decrement the use count if > 0 */ +void playback_release_aa_slot(int slot) { - /* TODO: Push the errors up to the normal UI somewhere */ - switch (reason) + if ((unsigned)slot < MAX_MULTIPLE_AA) { - case Q_CODEC_LOAD_DISK: - case Q_CODEC_LOAD: - if (!playing) - return; - - if (status < 0) - { - splash(HZ*2, "Codec failure"); - audio_check_new_track(); - } - break; + struct albumart_slot *aa_slot = &albumart_slots[slot]; -#ifdef AUDIO_HAVE_RECORDING - case Q_ENCODER_LOAD_DISK: - if (status < 0) - splash(HZ*2, "Encoder failure"); - break; -#endif /* AUDIO_HAVE_RECORDING */ + if (aa_slot->used > 0) + aa_slot->used--; } } +#endif /* HAVE_ALBUMART */ -/* - * Layout audio buffer as follows - iram buffer depends on target: - * [|SWAP:iram][|TALK]|FILE|GUARD|PCM|[SWAP:dram[|iram]|] - */ -static void audio_reset_buffer(void) -{ - /* see audio_get_recording_buffer if this is modified */ - logf("audio_reset_buffer"); - - /* If the setup of anything allocated before the file buffer is - changed, do check the adjustments after the buffer_alloc call - as it will likely be affected and need sliding over */ - - /* Initially set up file buffer as all space available */ - filebuf = audiobuf + talk_get_bufsize(); - filebuflen = audiobufend - filebuf; - - ALIGN_BUFFER(filebuf, filebuflen, sizeof (intptr_t)); - - /* Subtract whatever the pcm buffer says it used plus the guard buffer */ - size_t pcmbuf_size = pcmbuf_init(filebuf + filebuflen) + GUARD_BUFSIZE; - - /* Make sure filebuflen is a pointer sized multiple after adjustment */ - pcmbuf_size = ALIGN_UP(pcmbuf_size, sizeof (intptr_t)); - - if(pcmbuf_size > filebuflen) - panicf("%s(): EOM (%zu > %zu)", __func__, pcmbuf_size, filebuflen); - - filebuflen -= pcmbuf_size; - buffering_reset(filebuf, filebuflen); - /* Clear any references to the file buffer */ - buffer_state = AUDIOBUF_STATE_INITIALIZED; - -#if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE) - /* Make sure everything adds up - yes, some info is a bit redundant but - aids viewing and the sumation of certain variables should add up to - the location of others. */ - { - size_t pcmbufsize; - const unsigned char *pcmbuf = pcmbuf_get_meminfo(&pcmbufsize); - logf("fbuf: %08X", (unsigned)filebuf); - logf("fbufe: %08X", (unsigned)(filebuf + filebuflen)); - logf("gbuf: %08X", (unsigned)(filebuf + filebuflen)); - logf("gbufe: %08X", (unsigned)(filebuf + filebuflen + GUARD_BUFSIZE)); - logf("pcmb: %08X", (unsigned)pcmbuf); - logf("pcmbe: %08X", (unsigned)(pcmbuf + pcmbufsize)); - } +#ifdef HAVE_RECORDING +/* Load an encoder and run it */ +bool audio_load_encoder(int afmt) +{ +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) + LOGFQUEUE("audio >| Q_AUDIO_LOAD_ENCODER: %d", afmt); + return audio_queue_send(Q_AUDIO_LOAD_ENCODER, afmt) != 0; +#else + (void)afmt; + return true; #endif } -static void audio_thread(void) +/* Stop an encoder and unload it */ +void audio_remove_encoder(void) { - struct queue_event ev; - - pcm_postinit(); - - audio_thread_ready = true; - - while (1) - { - switch (filling) { - case STATE_IDLE: - queue_wait(&audio_queue, &ev); - break; - #if (CONFIG_PLATFORM & PLATFORM_NATIVE) - case STATE_USB: - queue_wait(&audio_queue, &ev); - switch (ev.id) { -#ifdef AUDIO_HAVE_RECORDING - /* Must monitor the encoder message for recording so it can - remove it if we process the insertion before it does. It - cannot simply be removed from under recording however. */ - case Q_AUDIO_LOAD_ENCODER: - break; -#endif - case SYS_USB_DISCONNECTED: - filling = STATE_IDLE; - default: - continue; - } - break; -#endif /* CONFIG_PLATFORM */ - - default: - /* End of buffering, let's calculate the watermark and - unboost */ - set_filebuf_watermark(); - cancel_cpu_boost(); - /* Fall-through */ - case STATE_FILLING: - case STATE_ENDING: - if (!pcmbuf_queue_scan(&ev)) - queue_wait_w_tmo(&audio_queue, &ev, HZ/2); - break; - } - - switch (ev.id) { - - case Q_AUDIO_FILL_BUFFER: - LOGFQUEUE("audio < Q_AUDIO_FILL_BUFFER %d", (int)ev.data); - audio_fill_file_buffer((bool)ev.data, 0); - break; - - case Q_AUDIO_FINISH_LOAD: - LOGFQUEUE("audio < Q_AUDIO_FINISH_LOAD"); - audio_finish_load_track(); - buf_set_base_handle(CUR_TI->audio_hid); - break; - - case Q_AUDIO_PLAY: - LOGFQUEUE("audio < Q_AUDIO_PLAY"); - if (playing && ev.data <= 0) - audio_new_playlist(); - else - { - audio_stop_playback(); - audio_play_start((size_t)ev.data); - } - break; - - case Q_AUDIO_STOP: - LOGFQUEUE("audio < Q_AUDIO_STOP"); - if (playing) - audio_stop_playback(); - if (ev.data != 0) - queue_clear(&audio_queue); - break; - - case Q_AUDIO_PAUSE: - LOGFQUEUE("audio < Q_AUDIO_PAUSE"); - if (!(bool) ev.data && skipped_during_pause -#ifdef HAVE_CROSSFADE - && !pcmbuf_is_crossfade_active() + LOGFQUEUE("audio >| Q_AUDIO_LOAD_ENCODER: NULL"); + audio_queue_send(Q_AUDIO_LOAD_ENCODER, AFMT_UNKNOWN); #endif - ) - pcmbuf_play_stop(); /* Flush old track on resume after skip */ - skipped_during_pause = false; - if (!playing) - break; - pcmbuf_pause((bool)ev.data); - paused = (bool)ev.data; - break; +} +#endif /* HAVE_RECORDING */ - case Q_AUDIO_SKIP: - LOGFQUEUE("audio < Q_AUDIO_SKIP"); - audio_initiate_track_change((long)ev.data); - break; +/* Is an automatic skip in progress? If called outside transistion callbacks, + indicates the last skip type at the time it was processed and isn't very + meaningful. */ +bool audio_automatic_skip(void) +{ + return automatic_skip; +} - case Q_AUDIO_PRE_FF_REWIND: - LOGFQUEUE("audio < Q_AUDIO_PRE_FF_REWIND"); - if (!playing) - break; - pcmbuf_pause(true); - break; +/* Would normally calculate byte offset from an elapsed time but is not + used on SWCODEC */ +int audio_get_file_pos(void) +{ + return 0; +} - case Q_AUDIO_FF_REWIND: - LOGFQUEUE("audio < Q_AUDIO_FF_REWIND"); - if (!playing) - break; +/* Return the elasped time of the track previous to the current */ +unsigned long audio_prev_elapsed(void) +{ + return prev_track_elapsed; +} - if (filling == STATE_ENDING) - { - /* Temp workaround: There is no codec available */ - if (!paused) - pcmbuf_pause(false); - break; - } +/* Is the audio thread ready to accept commands? */ +bool audio_is_thread_ready(void) +{ + return filling != STATE_BOOT; +} - if ((long)ev.data == 0) - { - /* About to restart the track - send track finish - events if not already done. */ - if (thistrack_id3 == audio_current_track()) - send_event(PLAYBACK_EVENT_TRACK_FINISH, thistrack_id3); - } +/* Return total file buffer length after accounting for the talk buf */ +size_t audio_get_filebuflen(void) +{ + return buf_length(); +} - if (automatic_skip) - { - /* An automatic track skip is in progress. Finalize it, - then go back to the previous track */ - audio_finalise_track_change(); - ci.new_track = -1; - } - ci.seek_time = (long)ev.data+1; - break; +/* How many tracks exist on the buffer - full or partial */ +int audio_track_count(void) + __attribute__((alias("track_list_count"))); - case Q_AUDIO_CHECK_NEW_TRACK: - LOGFQUEUE("audio < Q_AUDIO_CHECK_NEW_TRACK"); - audio_check_new_track(); - break; +/* Return total ringbuffer space occupied - ridx to widx */ +long audio_filebufused(void) +{ + return buf_used(); +} - case Q_AUDIO_DIR_SKIP: - LOGFQUEUE("audio < Q_AUDIO_DIR_SKIP"); - audio_initiate_dir_change(ev.data); - break; - case Q_AUDIO_FLUSH: - LOGFQUEUE("audio < Q_AUDIO_FLUSH"); - audio_invalidate_tracks(); - break; +/** -- Settings -- **/ - case Q_AUDIO_TRACK_CHANGED: - /* PCM track change done */ - LOGFQUEUE("audio < Q_AUDIO_TRACK_CHANGED"); - /* Set new playlist position for resuming. */ - playlist_update_resume_index(); - if (filling != STATE_ENDING) - audio_finalise_track_change(); - else if (playing) - audio_stop_playback(); - break; +/* Enable or disable cuesheet support and allocate/don't allocate the + extra associated resources */ +void audio_set_cuesheet(int enable) +{ + if (play_status == PLAY_STOPPED || !enable != !get_current_cuesheet()) + { + LOGFQUEUE("audio >| audio Q_AUDIO_REMAKE_AUDIO_BUFFER"); + audio_queue_send(Q_AUDIO_REMAKE_AUDIO_BUFFER, 0); + } +} - case Q_AUDIO_SEEK_COMPLETE: - /* Codec seek done */ - LOGFQUEUE("audio < Q_AUDIO_SEEK_COMPLETE"); - audio_seek_complete(); - codec_ack_msg(Q_AUDIO_SEEK_COMPLETE, false); - break; +#ifdef HAVE_DISK_STORAGE +/* Set the audio antiskip buffer margin by index */ +void audio_set_buffer_margin(int setting) +{ + static const unsigned short lookup[] = + { 5, 15, 30, 60, 120, 180, 300, 600 }; - case Q_CODEC_LOAD: - case Q_CODEC_LOAD_DISK: -#ifdef AUDIO_HAVE_RECORDING - case Q_ENCODER_LOAD_DISK: -#endif - /* These are received when a codec has finished normally or - upon a codec error */ - audio_codec_status_message(ev.id, ev.data); - break; + if ((unsigned)setting >= ARRAYLEN(lookup)) + setting = 0; -#if (CONFIG_PLATFORM & PLATFORM_NATIVE) - case SYS_USB_CONNECTED: - LOGFQUEUE("audio < SYS_USB_CONNECTED"); - if (playing) - audio_stop_playback(); -#ifdef PLAYBACK_VOICE - voice_stop(); -#endif - filling = STATE_USB; - usb_acknowledge(SYS_USB_CONNECTED_ACK); - break; -#endif + logf("buffer margin: %u", (unsigned)lookup[setting]); -#ifdef AUDIO_HAVE_RECORDING - case Q_AUDIO_LOAD_ENCODER: - if (playing) - audio_stop_playback(); - else - codec_stop(); /* If encoder still loaded, stop it */ + LOGFQUEUE("audio > audio Q_AUDIO_UPDATE_WATERMARK: %u", + (unsigned)lookup[setting]); + audio_queue_post(Q_AUDIO_UPDATE_WATERMARK, lookup[setting]); +} +#endif /* HAVE_DISK_STORAGE */ - if (ev.data == AFMT_UNKNOWN) - break; +#ifdef HAVE_CROSSFADE +/* Take necessary steps to enable or disable the crossfade setting */ +void audio_set_crossfade(int enable) +{ + /* Tell it the next setting to use */ + pcmbuf_request_crossfade_enable(enable); - queue_reply(&audio_queue, - codec_load(-1, ev.data | CODEC_TYPE_ENCODER)); - break; -#endif /* AUDIO_HAVE_RECORDING */ + /* Return if size hasn't changed or this is too early to determine + which in the second case there's no way we could be playing + anything at all */ + if (!pcmbuf_is_same_size()) + { + LOGFQUEUE("audio >| audio Q_AUDIO_REMAKE_AUDIO_BUFFER"); + audio_queue_send(Q_AUDIO_REMAKE_AUDIO_BUFFER, 0); + } +} +#endif /* HAVE_CROSSFADE */ - case SYS_TIMEOUT: - LOGFQUEUE_SYS_TIMEOUT("audio < SYS_TIMEOUT"); - break; - default: - /* LOGFQUEUE("audio < default : %08lX", ev.id); */ - break; - } /* end switch */ - } /* end while */ -} +/** -- Startup -- **/ -/* Initialize the audio system - called from init() in main.c. - * Last function because of all the references to internal symbols - */ +/* Initialize the audio system - called from init() in main.c */ void audio_init(void) { - unsigned int audio_thread_id; - /* Can never do this twice */ if (audio_is_initialized) { @@ -2290,31 +3707,20 @@ void audio_init(void) to send messages. Thread creation will be delayed however so nothing starts running until ready if something yields such as talk_init. */ queue_init(&audio_queue, true); - queue_init(&pcmbuf_queue, false); + + mutex_init(&id3_mutex); pcm_init(); codec_init_codec_api(); - thistrack_id3 = &mp3entry_buf[0]; - othertrack_id3 = &mp3entry_buf[1]; - - /* cuesheet support */ - if (global_settings.cuesheet) - curr_cue = (struct cuesheet*)buffer_alloc(sizeof(struct cuesheet)); - - /* initialize the buffer */ - filebuf = audiobuf; - - /* audio_reset_buffer must to know the size of voice buffer so init - talk first */ - talk_init(); - make_codec_thread(); + /* This thread does buffer, so match its priority */ audio_thread_id = create_thread(audio_thread, audio_stack, sizeof(audio_stack), CREATE_THREAD_FROZEN, - audio_thread_name IF_PRIO(, PRIORITY_USER_INTERFACE) + audio_thread_name + IF_PRIO(, MIN(PRIORITY_BUFFERING, PRIORITY_USER_INTERFACE)) IF_COP(, CPU)); queue_enable_queue_send(&audio_queue, &audio_queue_sender_list, @@ -2324,39 +3730,21 @@ void audio_init(void) voice_thread_init(); #endif + /* audio_reset_buffer must to know the size of voice buffer so init + talk first */ + talk_init(); + #ifdef HAVE_CROSSFADE /* Set crossfade setting for next buffer init which should be about... */ pcmbuf_request_crossfade_enable(global_settings.crossfade); #endif - /* initialize the buffering system */ - + /* Initialize the buffering system */ + track_list_init(); buffering_init(); /* ...now! Set up the buffers */ audio_reset_buffer(); - int i; - for(i = 0; i < MAX_TRACK; i++) - { - tracks[i].audio_hid = -1; - tracks[i].id3_hid = -1; - tracks[i].codec_hid = -1; - tracks[i].cuesheet_hid = -1; - } -#ifdef HAVE_ALBUMART - FOREACH_ALBUMART(i) - { - int j; - for (j = 0; j < MAX_TRACK; j++) - { - tracks[j].aa_hid[i] = -1; - } - } -#endif - - add_event(BUFFER_EVENT_REBUFFER, false, buffering_handle_rebuffer_callback); - add_event(BUFFER_EVENT_FINISHED, false, buffering_handle_finished_callback); - /* Probably safe to say */ audio_is_initialized = true; @@ -2365,26 +3753,10 @@ void audio_init(void) audio_set_buffer_margin(global_settings.buffer_margin); #endif - /* it's safe to let the threads run now */ + /* It's safe to let the threads run now */ #ifdef PLAYBACK_VOICE voice_thread_resume(); #endif codec_thread_resume(); thread_thaw(audio_thread_id); - -} /* audio_init */ - -bool audio_is_thread_ready(void) -{ - return audio_thread_ready; -} - -size_t audio_get_filebuflen(void) -{ - return filebuflen; -} - -int get_audio_hid() -{ - return CUR_TI->audio_hid; } diff --git a/apps/playback.h b/apps/playback.h index 76c394603f..225946cfaf 100644 --- a/apps/playback.h +++ b/apps/playback.h @@ -26,6 +26,16 @@ #include #include "config.h" +#if CONFIG_CODEC == SWCODEC +/* Including the code for fast previews is entirely optional since it + does add two more mp3entry's - for certain targets it may be less + beneficial such as flash-only storage */ +#if MEMORYSIZE > 2 +#define AUDIO_FAST_SKIP_PREVIEW +#endif + +#endif /* CONFIG_CODEC == SWCODEC */ + #ifdef HAVE_ALBUMART #include "bmp.h" @@ -67,6 +77,8 @@ long audio_filebufused(void); void audio_pre_ff_rewind(void); void audio_skip(int direction); void audio_hard_stop(void); /* Stops audio from serving playback */ + +void audio_set_cuesheet(int enable); #ifdef HAVE_CROSSFADE void audio_set_crossfade(int enable); #endif @@ -78,11 +90,10 @@ enum }; bool audio_restore_playback(int type); /* Restores the audio buffer to handle the requested playback */ size_t audio_get_filebuflen(void); -void audio_pcmbuf_position_callback(unsigned int time) ICODE_ATTR; -void audio_post_track_change(bool pcmbuf); -int get_audio_hid(void); -void audio_set_prev_elapsed(unsigned long setting); bool audio_buffer_state_trashed(void); + +/* Automatic transition? Only valid to call during the track change events, + otherwise the result is undefined. */ bool audio_automatic_skip(void); /* Define one constant that includes recording related functionality */ @@ -91,35 +102,62 @@ bool audio_automatic_skip(void); #endif enum { - Q_NULL = 0, + Q_NULL = 0, /* reserved */ + + /* -> audio */ Q_AUDIO_PLAY = 1, Q_AUDIO_STOP, Q_AUDIO_PAUSE, Q_AUDIO_SKIP, Q_AUDIO_PRE_FF_REWIND, Q_AUDIO_FF_REWIND, - Q_AUDIO_CHECK_NEW_TRACK, Q_AUDIO_FLUSH, - Q_AUDIO_TRACK_CHANGED, - Q_AUDIO_SEEK_COMPLETE, Q_AUDIO_DIR_SKIP, - Q_AUDIO_POSTINIT, - Q_AUDIO_FILL_BUFFER, - Q_AUDIO_FINISH_LOAD, - Q_CODEC_REQUEST_COMPLETE, - Q_CODEC_REQUEST_FAILED, + /* pcmbuf -> audio */ + Q_AUDIO_TRACK_CHANGED, + + /* audio -> audio */ + Q_AUDIO_FILL_BUFFER, /* continue buffering next track */ + + /* buffering -> audio */ + Q_AUDIO_BUFFERING, /* some buffer event */ + Q_AUDIO_FINISH_LOAD_TRACK, /* metadata is buffered */ + Q_AUDIO_HANDLE_FINISHED, /* some other type is buffered */ + + /* codec -> audio (*) */ + Q_AUDIO_CODEC_SEEK_COMPLETE, + Q_AUDIO_CODEC_COMPLETE, + + /* audio -> codec */ Q_CODEC_LOAD, - Q_CODEC_LOAD_DISK, + Q_CODEC_RUN, + Q_CODEC_PAUSE, + Q_CODEC_SEEK, + Q_CODEC_STOP, + Q_CODEC_UNLOAD, + + /*- miscellanous -*/ #ifdef AUDIO_HAVE_RECORDING - Q_AUDIO_LOAD_ENCODER, - Q_ENCODER_LOAD_DISK, - Q_ENCODER_RECORD, + /* -> codec */ + Q_AUDIO_LOAD_ENCODER, /* load an encoder for recording */ #endif - + /* -> codec */ Q_CODEC_DO_CALLBACK, - Q_CODEC_ACK, -}; + + /*- settings -*/ + +#ifdef HAVE_DISK_STORAGE + /* -> audio */ + Q_AUDIO_UPDATE_WATERMARK, /* buffering watermark needs updating */ #endif + /* -> audio */ + Q_AUDIO_REMAKE_AUDIO_BUFFER, /* buffer needs to be reinitialized */ +}; + +/* (*) If you change these, you must check audio_clear_track_notifications + in playback.c for correctness */ + +#endif /* _PLAYBACK_H */ diff --git a/apps/playlist.c b/apps/playlist.c index 14ebb7a198..d17bf230a5 100644 --- a/apps/playlist.c +++ b/apps/playlist.c @@ -822,9 +822,6 @@ static int add_track_to_playlist(struct playlist_info* playlist, playlist->amount++; playlist->num_inserted_tracks++; - /* Update index for resume. */ - playlist_update_resume_index(); - return insert_position; } @@ -925,9 +922,6 @@ static int remove_track_from_playlist(struct playlist_info* playlist, sync_control(playlist, false); } - /* Update index for resume. */ - playlist_update_resume_index(); - return 0; } @@ -987,9 +981,6 @@ static int randomise_playlist(struct playlist_info* playlist, playlist->first_index, NULL, NULL, NULL); } - /* Update index for resume. */ - playlist_update_resume_index(); - return 0; } @@ -1030,9 +1021,6 @@ static int sort_playlist(struct playlist_info* playlist, bool start_current, playlist->first_index, -1, NULL, NULL, NULL); } - /* Update index for resume. */ - playlist_update_resume_index(); - return 0; } @@ -1205,9 +1193,6 @@ static void find_and_set_playlist_index(struct playlist_info* playlist, break; } } - - /* Update index for resume. */ - playlist_update_resume_index(); } /* @@ -2486,6 +2471,12 @@ const char* playlist_peek(int steps, char* buf, size_t buf_size) if (index < 0) return NULL; +#if CONFIG_CODEC == SWCODEC + /* Just testing - don't care about the file name */ + if (!buf || !buf_size) + return ""; +#endif + control_file = playlist->indices[index] & PLAYLIST_INSERT_TYPE_MASK; seek = playlist->indices[index] & PLAYLIST_SEEK_MASK; @@ -2632,30 +2623,17 @@ int playlist_get_resume_info(int *resume_index) return 0; } -/* Get current playlist index. */ -int playlist_get_index(void) -{ - return current_playlist.index; -} - -/* Update resume index within playlist_info structure. */ -void playlist_update_resume_index(void) -{ - struct playlist_info* playlist = ¤t_playlist; - playlist->resume_index = playlist->index; -} - /* Update resume info for current playing song. Returns -1 on error. */ int playlist_update_resume_info(const struct mp3entry* id3) { struct playlist_info* playlist = ¤t_playlist; - + if (id3) { - if (global_status.resume_index != playlist->resume_index || + if (global_status.resume_index != playlist->index || global_status.resume_offset != id3->offset) { - global_status.resume_index = playlist->resume_index; + global_status.resume_index = playlist->index; global_status.resume_offset = id3->offset; status_save(); } @@ -3203,9 +3181,6 @@ int playlist_move(struct playlist_info* playlist, int index, int new_index) queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0); #endif - /* Update index for resume. */ - playlist_update_resume_index(); - return result; } diff --git a/apps/playlist.h b/apps/playlist.h index a0e3b579f7..9c45769981 100644 --- a/apps/playlist.h +++ b/apps/playlist.h @@ -90,7 +90,6 @@ struct playlist_info int buffer_end_pos; /* last position where buffer was written */ int index; /* index of current playing track */ int first_index; /* index of first song in playlist */ - int resume_index; /* index of playing track to resume */ int amount; /* number of tracks in the index */ int last_insert_pos; /* last position we inserted a track */ int seed; /* shuffle seed */ @@ -132,7 +131,6 @@ const char *playlist_peek(int steps, char* buf, size_t buf_size); int playlist_next(int steps); bool playlist_next_dir(int direction); int playlist_get_resume_info(int *resume_index); -int playlist_get_index(void); int playlist_update_resume_info(const struct mp3entry* id3); int playlist_get_display_index(void); int playlist_amount(void); @@ -176,6 +174,5 @@ int playlist_directory_tracksearch(const char* dirname, bool recurse, int (*callback)(char*, void*), void* context); int playlist_remove_all_tracks(struct playlist_info *playlist); -void playlist_update_resume_index(void); #endif /* __PLAYLIST_H__ */ diff --git a/apps/plugin.c b/apps/plugin.c index ea290c89a7..bb326d937b 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -692,7 +692,7 @@ static const struct plugin_api rockbox_api = { #if CONFIG_CODEC == SWCODEC codec_thread_do_callback, codec_load_file, - codec_begin, + codec_run_proc, codec_close, get_codec_filename, find_array_ptr, diff --git a/apps/plugin.h b/apps/plugin.h index 4537c6670b..cdf34e28b1 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -145,12 +145,12 @@ void* plugin_get_buffer(size_t *buffer_size); #define PLUGIN_MAGIC 0x526F634B /* RocK */ /* increase this every time the api struct changes */ -#define PLUGIN_API_VERSION 203 +#define PLUGIN_API_VERSION 204 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any new function which are "waiting" at the end of the function table) */ -#define PLUGIN_MIN_API_VERSION 203 +#define PLUGIN_MIN_API_VERSION 204 /* plugin return codes */ /* internal returns start at 0x100 to make exit(1..255) work */ @@ -799,9 +799,9 @@ struct plugin_api { #if CONFIG_CODEC == SWCODEC void (*codec_thread_do_callback)(void (*fn)(void), unsigned int *audio_thread_id); - void * (*codec_load_file)(const char* codec, struct codec_api *api); - int (*codec_begin)(void *handle); - void (*codec_close)(void *handle); + int (*codec_load_file)(const char* codec, struct codec_api *api); + int (*codec_run_proc)(void); + int (*codec_close)(void); const char *(*get_codec_filename)(int cod_spec); void ** (*find_array_ptr)(void **arr, void *ptr); int (*remove_array_ptr)(void **arr, void *ptr); diff --git a/apps/plugins/test_codec.c b/apps/plugins/test_codec.c index 855503a0ec..4bde1ba39d 100644 --- a/apps/plugins/test_codec.c +++ b/apps/plugins/test_codec.c @@ -127,7 +127,6 @@ struct test_track_info { }; static struct test_track_info track; -static bool taginfo_ready = true; static bool use_dsp; @@ -433,6 +432,7 @@ static void pcmbuf_insert_wav_checksum(const void *ch1, const void *ch2, int cou static void set_elapsed(unsigned long value) { elapsed = value; + ci.id3->elapsed = value; } @@ -482,6 +482,7 @@ static void* request_buffer(size_t *realsize, size_t reqsize) static void advance_buffer(size_t amount) { ci.curpos += amount; + ci.id3->offset = ci.curpos; } @@ -499,20 +500,17 @@ static void seek_complete(void) /* Do nothing */ } -/* Request file change from file buffer. Returns true is next - track is available and changed. If return value is false, - codec should exit immediately with PLUGIN_OK status. */ -static bool request_next_track(void) +/* Codec calls this to know what it should do next. */ +static enum codec_command_action get_command(intptr_t *param) { - /* We are only decoding a single track */ - return false; + rb->yield(); + return CODEC_ACTION_NULL; /* just continue processing */ + (void)param; } - static void set_offset(size_t value) { - /* ??? */ - (void)value; + ci.id3->offset = value; } @@ -546,6 +544,9 @@ static void init_ci(void) { /* --- Our "fake" implementations of the codec API functions. --- */ + ci.dsp = (struct dsp_config *)rb->dsp_configure(NULL, DSP_MYDSP, + CODEC_IDX_AUDIO); + ci.codec_get_buffer = codec_get_buffer; if (wavinfo.fd >= 0 || checksum) { @@ -560,11 +561,9 @@ static void init_ci(void) ci.advance_buffer = advance_buffer; ci.seek_buffer = seek_buffer; ci.seek_complete = seek_complete; - ci.request_next_track = request_next_track; ci.set_offset = set_offset; ci.configure = configure; - ci.dsp = (struct dsp_config *)rb->dsp_configure(NULL, DSP_MYDSP, - CODEC_IDX_AUDIO); + ci.get_command = get_command; /* --- "Core" functions --- */ @@ -620,20 +619,22 @@ static void init_ci(void) static void codec_thread(void) { const char* codecname; - void *handle; - int res = CODEC_ERROR; + int res; codecname = rb->get_codec_filename(track.id3.codectype); - /* Load the codec and start decoding. */ - handle = rb->codec_load_file(codecname,&ci); + /* Load the codec */ + res = rb->codec_load_file(codecname, &ci); - if (handle != NULL) + if (res >= 0) { - res = rb->codec_begin(handle); - rb->codec_close(handle); + /* Decode the file */ + res = rb->codec_run_proc(); } + /* Clean up */ + rb->codec_close(); + /* Signal to the main thread that we are done */ endtick = *rb->current_tick - rebuffertick; codec_playing = false; @@ -705,11 +706,7 @@ static enum plugin_status test_track(const char* filename) /* Prepare the codec struct for playing the whole file */ ci.filesize = track.filesize; ci.id3 = &track.id3; - ci.taginfo_ready = &taginfo_ready; ci.curpos = 0; - ci.stop_codec = false; - ci.new_track = 0; - ci.seek_time = 0; if (use_dsp) rb->dsp_configure(ci.dsp, DSP_RESET, 0); diff --git a/firmware/export/kernel.h b/firmware/export/kernel.h index 66efce33f6..54a53f3607 100644 --- a/firmware/export/kernel.h +++ b/firmware/export/kernel.h @@ -86,6 +86,7 @@ #define SYS_VOLUME_CHANGED MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 5) #define IS_SYSEVENT(ev) ((ev & SYS_EVENT) == SYS_EVENT) +#define EVENT_RESERVED (~0) #ifndef TIMEOUT_BLOCK #define TIMEOUT_BLOCK -1 @@ -249,6 +250,15 @@ extern bool queue_in_queue_send(struct event_queue *q); #endif /* HAVE_EXTENDED_MESSAGING_AND_NAME */ extern bool queue_empty(const struct event_queue* q); extern bool queue_peek(struct event_queue *q, struct queue_event *ev); + +#define QPEEK_FILTER_COUNT_MASK (0xffu) /* 0x00=1 filter, 0xff=256 filters */ +#define QPEEK_FILTER_HEAD_ONLY (1u << 8) /* Ignored if no filters */ +#define QPEEK_REMOVE_EVENTS (1u << 9) /* Remove or discard events */ +extern bool queue_peek_ex(struct event_queue *q, + struct queue_event *ev, + unsigned int flags, + const long (*filters)[2]); + extern void queue_clear(struct event_queue* q); extern void queue_remove_from_head(struct event_queue *q, long id); extern int queue_count(const struct event_queue *q); diff --git a/firmware/kernel.c b/firmware/kernel.c index 4fcfcb9d30..288ebbbede 100644 --- a/firmware/kernel.c +++ b/firmware/kernel.c @@ -516,8 +516,10 @@ void queue_wait(struct event_queue *q, struct queue_event *ev) oldlevel = disable_irq_save(); corelock_lock(&q->cl); - /* auto-reply */ +#ifdef HAVE_EXTENDED_MESSAGING_AND_NAME + /* Auto-reply (even if ev is NULL to avoid stalling a waiting thread) */ queue_do_auto_reply(q->send); +#endif while(1) { @@ -541,12 +543,18 @@ void queue_wait(struct event_queue *q, struct queue_event *ev) corelock_lock(&q->cl); } - q->read = rd + 1; - rd &= QUEUE_LENGTH_MASK; - *ev = q->events[rd]; +#ifdef HAVE_EXTENDED_MESSAGING_AND_NAME + if(ev) +#endif + { + q->read = rd + 1; + rd &= QUEUE_LENGTH_MASK; + *ev = q->events[rd]; - /* Get data for a waiting thread if one */ - queue_do_fetch_sender(q->send, rd); + /* Get data for a waiting thread if one */ + queue_do_fetch_sender(q->send, rd); + } + /* else just waiting on non-empty */ corelock_unlock(&q->cl); restore_irq(oldlevel); @@ -566,8 +574,10 @@ void queue_wait_w_tmo(struct event_queue *q, struct queue_event *ev, int ticks) oldlevel = disable_irq_save(); corelock_lock(&q->cl); - /* Auto-reply */ +#ifdef HAVE_EXTENDED_MESSAGING_AND_NAME + /* Auto-reply (even if ev is NULL to avoid stalling a waiting thread) */ queue_do_auto_reply(q->send); +#endif rd = q->read; wr = q->write; @@ -590,20 +600,26 @@ void queue_wait_w_tmo(struct event_queue *q, struct queue_event *ev, int ticks) wr = q->write; } - /* no worry about a removed message here - status is checked inside - locks - perhaps verify if timeout or false alarm */ - if (rd != wr) - { - q->read = rd + 1; - rd &= QUEUE_LENGTH_MASK; - *ev = q->events[rd]; - /* Get data for a waiting thread if one */ - queue_do_fetch_sender(q->send, rd); - } - else +#ifdef HAVE_EXTENDED_MESSAGING_AND_NAME + if(ev) +#endif { - ev->id = SYS_TIMEOUT; + /* no worry about a removed message here - status is checked inside + locks - perhaps verify if timeout or false alarm */ + if (rd != wr) + { + q->read = rd + 1; + rd &= QUEUE_LENGTH_MASK; + *ev = q->events[rd]; + /* Get data for a waiting thread if one */ + queue_do_fetch_sender(q->send, rd); + } + else + { + ev->id = SYS_TIMEOUT; + } } + /* else just waiting on non-empty */ corelock_unlock(&q->cl); restore_irq(oldlevel); @@ -740,23 +756,99 @@ void queue_reply(struct event_queue *q, intptr_t retval) } #endif /* HAVE_EXTENDED_MESSAGING_AND_NAME */ -bool queue_peek(struct event_queue *q, struct queue_event *ev) +#ifdef HAVE_EXTENDED_MESSAGING_AND_NAME +/* Scan the even queue from head to tail, returning any event from the + filter list that was found, optionally removing the event. If an + event is returned, synchronous events are handled in the same manner as + with queue_wait(_w_tmo); if discarded, then as queue_clear. + If filters are NULL, any event matches. If filters exist, the default + is to search the full queue depth. + Earlier filters take precedence. + + Return true if an event was found, false otherwise. */ +bool queue_peek_ex(struct event_queue *q, struct queue_event *ev, + unsigned int flags, const long (*filters)[2]) { - unsigned int rd; + bool have_msg; + unsigned int rd, wr; + int oldlevel; - if(q->read == q->write) - return false; + if(LIKELY(q->read == q->write)) + return false; /* Empty: do nothing further */ - bool have_msg = false; + have_msg = false; - int oldlevel = disable_irq_save(); + oldlevel = disable_irq_save(); corelock_lock(&q->cl); - rd = q->read; - if(rd != q->write) + /* Starting at the head, find first match */ + for(rd = q->read, wr = q->write; rd != wr; rd++) { - *ev = q->events[rd & QUEUE_LENGTH_MASK]; + struct queue_event *e = &q->events[rd & QUEUE_LENGTH_MASK]; + + if(filters) + { + /* Have filters - find the first thing that passes */ + const long (* f)[2] = filters; + const long (* const f_last)[2] = + &filters[flags & QPEEK_FILTER_COUNT_MASK]; + long id = e->id; + + do + { + if(UNLIKELY(id >= (*f)[0] && id <= (*f)[1])) + goto passed_filter; + } + while(++f <= f_last); + + if(LIKELY(!(flags & QPEEK_FILTER_HEAD_ONLY))) + continue; /* No match; test next event */ + else + break; /* Only check the head */ + } + /* else - anything passes */ + + passed_filter: + + /* Found a matching event */ have_msg = true; + + if(ev) + *ev = *e; /* Caller wants the event */ + + if(flags & QPEEK_REMOVE_EVENTS) + { + /* Do event removal */ + unsigned int r = q->read; + q->read = r + 1; /* Advance head */ + + if(ev) + { + /* Auto-reply */ + queue_do_auto_reply(q->send); + /* Get the thread waiting for reply, if any */ + queue_do_fetch_sender(q->send, rd & QUEUE_LENGTH_MASK); + } + else + { + /* Release any thread waiting on this message */ + queue_do_unblock_sender(q->send, rd & QUEUE_LENGTH_MASK); + } + + /* Slide messages forward into the gap if not at the head */ + while(rd != r) + { + unsigned int dst = rd & QUEUE_LENGTH_MASK; + unsigned int src = --rd & QUEUE_LENGTH_MASK; + + q->events[dst] = q->events[src]; + /* Keep sender wait list in sync */ + if(q->send) + q->send->senders[dst] = q->send->senders[src]; + } + } + + break; } corelock_unlock(&q->cl); @@ -765,30 +857,42 @@ bool queue_peek(struct event_queue *q, struct queue_event *ev) return have_msg; } -/* Poll queue to see if a message exists - careful in using the result if - * queue_remove_from_head is called when messages are posted - possibly use - * queue_wait_w_tmo(&q, 0) in that case or else a removed message that - * unsignals the queue may cause an unwanted block */ -bool queue_empty(const struct event_queue* q) +bool queue_peek(struct event_queue *q, struct queue_event *ev) { - return ( q->read == q->write ); + return queue_peek_ex(q, ev, 0, NULL); } -void queue_clear(struct event_queue* q) +void queue_remove_from_head(struct event_queue *q, long id) { - int oldlevel; + const long f[2] = { id, id }; + while (queue_peek_ex(q, NULL, + QPEEK_FILTER_HEAD_ONLY | QPEEK_REMOVE_EVENTS, &f)); +} +#else /* !HAVE_EXTENDED_MESSAGING_AND_NAME */ +/* The more powerful routines aren't required */ +bool queue_peek(struct event_queue *q, struct queue_event *ev) +{ + unsigned int rd; - oldlevel = disable_irq_save(); - corelock_lock(&q->cl); + if(q->read == q->write) + return false; - /* Release all threads waiting in the queue for a reply - - dequeued sent message will be handled by owning thread */ - queue_release_all_senders(q); + bool have_msg = false; - q->read = q->write; + int oldlevel = disable_irq_save(); + corelock_lock(&q->cl); + + rd = q->read; + if(rd != q->write) + { + *ev = q->events[rd & QUEUE_LENGTH_MASK]; + have_msg = true; + } corelock_unlock(&q->cl); restore_irq(oldlevel); + + return have_msg; } void queue_remove_from_head(struct event_queue *q, long id) @@ -816,6 +920,33 @@ void queue_remove_from_head(struct event_queue *q, long id) corelock_unlock(&q->cl); restore_irq(oldlevel); } +#endif /* HAVE_EXTENDED_MESSAGING_AND_NAME */ + +/* Poll queue to see if a message exists - careful in using the result if + * queue_remove_from_head is called when messages are posted - possibly use + * queue_wait_w_tmo(&q, 0) in that case or else a removed message that + * unsignals the queue may cause an unwanted block */ +bool queue_empty(const struct event_queue* q) +{ + return ( q->read == q->write ); +} + +void queue_clear(struct event_queue* q) +{ + int oldlevel; + + oldlevel = disable_irq_save(); + corelock_lock(&q->cl); + + /* Release all threads waiting in the queue for a reply - + dequeued sent message will be handled by owning thread */ + queue_release_all_senders(q); + + q->read = q->write; + + corelock_unlock(&q->cl); + restore_irq(oldlevel); +} /** * The number of events waiting in the queue. -- cgit v1.2.3