summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/codecs.c1
-rw-r--r--apps/codecs.h4
-rw-r--r--apps/codecs/lib/xxx2wav.c6
-rw-r--r--apps/codecs/vorbis.c149
-rw-r--r--apps/metadata.c223
-rw-r--r--docs/CREDITS1
-rw-r--r--firmware/SOURCES1
-rw-r--r--firmware/common/memchr.c116
-rw-r--r--firmware/export/id3.h3
9 files changed, 471 insertions, 33 deletions
diff --git a/apps/codecs.c b/apps/codecs.c
index 16338c263c..8b4f9b5f32 100644
--- a/apps/codecs.c
+++ b/apps/codecs.c
@@ -243,6 +243,7 @@ struct codec_api ci = {
logf,
#endif
+ memchr,
};
int codec_load_ram(char* codecptr, size_t size, void* ptr2, size_t bufwrap)
diff --git a/apps/codecs.h b/apps/codecs.h
index 1a03139be8..5878ca9185 100644
--- a/apps/codecs.h
+++ b/apps/codecs.h
@@ -240,7 +240,7 @@ struct codec_api {
#endif
#if CONFIG_HWCODEC == MASNONE
void (*pcm_play_data)(const unsigned char *start, int size,
- void (*get_more)(unsigned char** start, long*size));
+ void (*get_more)(unsigned char** start, long*size));
void (*pcm_play_stop)(void);
void (*pcm_set_frequency)(unsigned int frequency);
bool (*pcm_is_playing)(void);
@@ -326,6 +326,8 @@ struct codec_api {
#ifdef ROCKBOX_HAS_LOGF
void (*logf)(const char *fmt, ...);
#endif
+
+ void *(*memchr)(const void *s1, int c, size_t n);
};
/* defined by the codec loader (codec.c) */
diff --git a/apps/codecs/lib/xxx2wav.c b/apps/codecs/lib/xxx2wav.c
index 1323e07475..b437ca56db 100644
--- a/apps/codecs/lib/xxx2wav.c
+++ b/apps/codecs/lib/xxx2wav.c
@@ -91,11 +91,7 @@ int memcmp(const void *s1, const void *s2, size_t n) {
}
void* memchr(const void *s, int c, size_t n) {
- /* TO DO: Implement for Tremor */
- (void)s;
- (void)c;
- (void)n;
- return(NULL);
+ return(local_rb->memchr(s,c,n));
}
void* memmove(const void *s1, const void *s2, size_t n) {
diff --git a/apps/codecs/vorbis.c b/apps/codecs/vorbis.c
index 9afeb053e1..946f2f9377 100644
--- a/apps/codecs/vorbis.c
+++ b/apps/codecs/vorbis.c
@@ -20,6 +20,7 @@
#include "codecs.h"
#include "Tremor/ivorbisfile.h"
+#include "Tremor/ogg.h"
#include "playback.h"
#include "dsp.h"
#include "lib/codeclib.h"
@@ -51,15 +52,30 @@ size_t read_handler(void *ptr, size_t size, size_t nmemb, void *datasource)
return rb->read_filebuf(ptr, nmemb*size);
}
-int seek_handler(void *datasource, ogg_int64_t offset, int whence)
-{
- /* We are not seekable at the moment */
+int initial_seek_handler(void *datasource, ogg_int64_t offset, int whence) {
(void)datasource;
(void)offset;
(void)whence;
return -1;
}
+int seek_handler(void *datasource, ogg_int64_t offset, int whence)
+{
+ (void)datasource;
+
+ if ( whence == SEEK_CUR ) {
+ offset += rb->curpos;
+ } else if ( whence == SEEK_END ) {
+ offset += rb->filesize;
+ }
+
+ if (rb->seek_buffer(offset)) {
+ return 0;
+ }
+
+ return -1;
+}
+
int close_handler(void *datasource)
{
(void)datasource;
@@ -93,22 +109,26 @@ enum codec_status codec_start(struct codec_api* api)
long n;
int current_section;
int eof;
+ ogg_int64_t vf_offsets[2];
+ ogg_int64_t vf_dataoffsets;
+ ogg_uint32_t vf_serialnos;
+ ogg_int64_t vf_pcmlengths[2];
+ int current_stereo_mode = -1;
TEST_CODEC_API(api);
/* if you are using a global api pointer, don't forget to copy it!
- otherwise you will get lovely "I04: IllInstr" errors... :-) */
+ otherwise you will get lovely "I04: IllInstr" errors... :-) */
rb = api;
-#ifdef USE_IRAM
+ #ifdef USE_IRAM
rb->memcpy(iramstart, iramcopy, iramend-iramstart);
-#endif
-
+ #endif
+
rb->configure(CODEC_SET_FILEBUF_LIMIT, (int *)(1024*1024*2));
rb->configure(CODEC_SET_FILEBUF_CHUNKSIZE, (int *)(1024*64));
-
+
rb->configure(DSP_DITHER, (bool *)false);
- rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_INTERLEAVED);
rb->configure(DSP_SET_SAMPLE_DEPTH, (int *)(16));
/* We need to flush reserver memory every track load. */
@@ -129,44 +149,123 @@ enum codec_status codec_start(struct codec_api* api)
/* Create a decoder instance */
callbacks.read_func=read_handler;
- callbacks.seek_func=seek_handler;
+ callbacks.seek_func=initial_seek_handler;
callbacks.tell_func=tell_handler;
callbacks.close_func=close_handler;
+ /* Open a non-seekable stream */
error=ov_open_callbacks(rb,&vf,NULL,0,callbacks);
+ /* If the non-seekable open was successful, we need to supply the missing
+ * data to make it seekable. This is a hack, but it's reasonable since we
+ * don't want to read the whole file into the buffer before we start
+ * playing. Using Tremor's seekable open routine would cause us to do
+ * this, so we pretend not to be seekable at first. Then we fill in the
+ * missing fields of vf with 1) information in rb->id3, and 2) info
+ * obtained by Tremor in the above ov_open call.
+ *
+ * Note that this assumes there is only ONE logical Vorbis bitstream in our
+ * physical Ogg bitstream. This is verified in metadata.c, well before we
+ * get here.
+ */
+ if ( !error ) {
+ //rb->logf("no error");
+ /* FIXME Should these be dynamically allocated? */
+ vf.offsets = vf_offsets;
+ vf.dataoffsets = &vf_dataoffsets;
+ vf.serialnos = &vf_serialnos;
+ vf.pcmlengths = vf_pcmlengths;
+
+ vf.offsets[0] = 0;
+ vf.offsets[1] = rb->id3->filesize;
+ vf.dataoffsets[0] = vf.offset;
+ vf.pcmlengths[0] = 0;
+ vf.pcmlengths[1] = rb->id3->samples;
+ vf.serialnos[0] = vf.current_serialno;
+ vf.callbacks.seek_func=seek_handler;
+ vf.seekable = 1;
+ vf.offset = 58; /* length of Ogg header */
+ vf.end = rb->id3->filesize;
+ vf.ready_state = OPENED;
+ vf.links = 1;
+
+ /*if(ov_raw_seek(&vf,0)){
+ rb->logf("seek err");
+ }
+ */
+
+ } else {
+ //rb->logf("ov_open: %d", error);
+ }
+
vi=ov_info(&vf,-1);
if (vi==NULL) {
- // rb->splash(HZ*2, true, "Vorbis Error");
+ //rb->splash(HZ*2, true, "Vorbis Error");
return CODEC_ERROR;
}
-
+
+ rb->configure(DSP_SET_FREQUENCY, (int *)rb->id3->frequency);
+
+ if (vi->channels == 2) {
+ if (current_stereo_mode != STEREO_INTERLEAVED) {
+ rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_INTERLEAVED);
+ current_stereo_mode = STEREO_INTERLEAVED;
+ }
+ } else if (vi->channels == 1) {
+ if (current_stereo_mode != STEREO_MONO) {
+ rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_MONO);
+ current_stereo_mode = STEREO_MONO;
+ }
+ }
+
eof=0;
+ rb->yield();
while (!eof) {
+ if (rb->stop_codec || rb->reload_codec)
+ break ;
+
+ if (rb->seek_time) {
+
+ if (ov_time_seek(&vf, rb->seek_time)) {
+ //rb->logf("ov_time_seek failed");
+ }
+ rb->seek_time = 0;
+ }
+
/* Read host-endian signed 16 bit PCM samples */
n=ov_read(&vf,pcmbuf,sizeof(pcmbuf),&current_section);
if (n==0) {
eof=1;
- }
- else if (n < 0) {
+ } else if (n < 0) {
DEBUGF("Error decoding frame\n");
} else {
- rb->yield();
- if (rb->stop_codec || rb->reload_codec)
- break ;
-
- while (!rb->audiobuffer_insert(pcmbuf, n))
+ while (!rb->audiobuffer_insert(pcmbuf, n)) {
rb->yield();
-
- rb->set_elapsed(ov_time_tell(&vf));
+ if ( rb->seek_time ) {
+ /* Hmmm, a seek was requested. Throw out the
+ * buffer and go back to the top of the loop.
+ */
+ break;
+ }
+ }
+ if ( !rb->seek_time ) {
+ rb->set_elapsed(ov_time_tell(&vf));
+ rb->yield();
+ }
}
}
-
- if (rb->request_next_track())
- goto next_track;
+ if (rb->request_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;
+ }
+
return CODEC_OK;
}
-
diff --git a/apps/metadata.c b/apps/metadata.c
index f59917ceb8..6ba2331d26 100644
--- a/apps/metadata.c
+++ b/apps/metadata.c
@@ -91,9 +91,14 @@ const long wavpack_sample_rates [] = { 6000, 8000, 9600, 11025, 12000, 16000,
static bool get_apetag_info (struct mp3entry *entry, int fd);
+static bool get_vorbis_comments (struct mp3entry *entry, int fd);
+
+static void little_endian_to_native (void *data, char *format);
+
bool get_metadata(struct track_info* track, int fd, const char* trackname,
bool v1first) {
unsigned long totalsamples,bytespersample,channels,bitspersample,numbytes;
+ unsigned long serialno=0, last_serialno=0;
int bytesperframe;
unsigned char* buf;
int i,j,eof;
@@ -259,13 +264,29 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
return(false);
}
+ /* We need to ensure the serial number from this page is the
+ * same as the one from the last page (since we only support
+ * a single bitstream).
+ */
+ serialno = buf[14]|(buf[15]<<8)|(buf[16]<<16)|(buf[17]<<24);
+
/* Ogg stores integers in little-endian format. */
track->id3.filesize=filesize(fd);
track->id3.frequency=buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24);
channels=buf[39];
+ if ( !get_vorbis_comments(&(track->id3), fd) ) {
+ logf("get_vorbis_comments failed");
+ return(false);
+ }
+
+ /* Set id3 genre to something bogus, otherwise vorbis tracks
+ * without genre tags will show up as 'Blues'
+ */
+ track->id3.genre=255;
+
/* We now need to search for the last page in the file - identified by
- by ('O','g','g','S',0) and retrieve totalsamples */
+ by ('O','g','g','S',0) and retrieve totalsamples */
lseek(fd, -32*1024, SEEK_END);
eof=0;
@@ -283,8 +304,9 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
i=0;
while (i < (j-5)) {
if (memcmp(&buf[i],"OggS",5)==0) {
- if (i < (j-10)) {
+ if (i < (j-17)) {
totalsamples=(buf[i+6])|(buf[i+7]<<8)|(buf[i+8]<<16)|(buf[i+9]<<24);
+ last_serialno=(buf[i+14])|(buf[i+15]<<8)|(buf[i+16]<<16)|(buf[i+17]<<24);
j=0; /* We can discard the rest of the buffer */
} else {
break;
@@ -300,7 +322,18 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
j=0;
}
}
+
+ /* This file has mutiple vorbis bitstreams (or is corrupt) */
+ /* FIXME we should display an error here */
+ if (serialno != last_serialno) {
+ track->taginfo_ready=false;
+ logf("serialno mismatch");
+ logf("%ld", serialno);
+ logf("%ld", last_serialno);
+ return false;
+ }
+ track->id3.samples=totalsamples;
track->id3.length=(totalsamples/track->id3.frequency)*1000;
/* The following calculation should use datasize, not filesize (i.e. exclude comments etc) */
@@ -712,3 +745,189 @@ static void UTF8ToAnsi (unsigned char *pUTF8)
*pAnsi = 0;
}
+/* This function extracts the information stored in the Vorbis comment header
+ * and stores it in id3v2buf of the current track. Currently the combined
+ * lengths of title, genre, album, and artist must be no longer than 296 bytes
+ * (the remaining 4 bytes are the null bytes at the end of the strings). This
+ * is wrong, since vorbis comments can be up to 2^32 - 1 bytes long. In
+ * practice I don't think this limitation will cause a problem.
+ *
+ * According to the docs, a vorbis bitstream *must* have a comment packet even
+ * if that packet is empty. Therefore if this function returns false the
+ * bitstream is corrupt and shouldn't be used.
+ *
+ * Additionally, vorbis comments *may* take up more than one Ogg page, and this
+ * only looks at the first page of comments.
+ */
+static bool get_vorbis_comments (struct mp3entry *entry, int fd)
+{
+ int vendor_length;
+ int comment_count;
+ int comment_length;
+ int i = 0;
+ unsigned char temp[300];
+ int buffer_remaining = sizeof(entry->id3v2buf);
+ char *buffer = entry->id3v2buf;
+ char **p = NULL;
+ int segments;
+ int packet_remaining = 0;
+
+ /* Comments are in second Ogg page */
+ if ( lseek(fd, 58, SEEK_SET) < 0 ) {
+ return false;
+ }
+
+ /* Minimum header length for Ogg pages is 27 */
+ if (read(fd, temp, 27) < 27) {
+ return false;
+ }
+
+ if (memcmp(temp,"OggS",4)!=0) {
+ logf("1: Not an Ogg Vorbis file");
+ return(false);
+ }
+
+ segments=temp[26];
+ /* read in segment table */
+ if (read(fd, temp, segments) < segments) {
+ return false;
+ }
+
+ /* The second packet in a vorbis stream is the comment packet. It *may*
+ * extend beyond the second page, but usually does not. Here we find the
+ * length of the comment packet (or the rest of the page if the comment
+ * packet extends to the third page).
+ */
+ for (i = 0; i < segments; i++) {
+ packet_remaining += temp[i];
+ /* The last segment of a packet is always < 255 bytes */
+ if (temp[i] < 255) {
+ break;
+ }
+ }
+
+ /* Now read in packet header (type and id string) */
+ if(read(fd, temp, 7) < 7) {
+ return false;
+ }
+
+ /* The first byte of a packet is the packet type; comment packets are
+ * type 3.
+ */
+ if ((temp[0] != 3) || (memcmp(temp + 1,"vorbis",6)!=0)) {
+ logf("Not a vorbis comment packet");
+ return false;
+ }
+
+ packet_remaining -= 7;
+
+
+ /* We've read in all header info, now start reading comments */
+
+ if (read(fd, &vendor_length, 4) < 4) {
+ return false;
+ }
+ little_endian_to_native(&vendor_length, "L");
+ lseek(fd, vendor_length, SEEK_CUR);
+
+ if (read(fd, &comment_count, 4) < 4) {
+ return false;
+ }
+ little_endian_to_native(&comment_count, "L");
+ packet_remaining -= (vendor_length + 8);
+ if ( packet_remaining <= 0 ) {
+ return true;
+ }
+
+ for ( i = 0; i < comment_count; i++ ) {
+ int name_length = 0;
+
+ if (read(fd, &comment_length, 4) < 4) {
+ return false;
+ }
+
+ little_endian_to_native(&comment_length, "L");
+
+ /* Quit if we've passed the end of the page */
+ packet_remaining -= (comment_length + 4);
+ if ( packet_remaining <= 0 ) {
+ return true;
+ }
+
+ /* Skip comment if it won't fit in buffer */
+ if ( (unsigned int)comment_length >= sizeof(temp) ) {
+ lseek(fd, comment_length, SEEK_CUR);
+ continue;
+ }
+
+ if ( read(fd, temp, comment_length) < comment_length ) {
+ return false;
+ }
+
+ temp[comment_length] = '\0';
+ UTF8ToAnsi(temp);
+ comment_length = strlen(temp);
+
+ if (strncasecmp(temp, "TITLE=", 6) == 0) {
+ name_length = 5;
+ p = &(entry->title);
+ } else if (strncasecmp(temp, "ALBUM=", 6) == 0) {
+ name_length = 5;
+ p = &(entry->album);
+ } else if (strncasecmp(temp, "ARTIST=", 7) == 0) {
+ name_length = 6;
+ p = &(entry->artist);
+ } else if (strncasecmp(temp, "GENRE=", 6) == 0) {
+ name_length = 5;
+ p = &(entry->genre_string);
+ } else if (strncasecmp(temp, "DATE=", 5) == 0) {
+ int j=0;
+ /* verify that this is a number */
+ /* Note: vorbis uses UTF-8 for its comments, so it is
+ * safe to compare the values against ASCII 0 and 9
+ */
+ while ( j < (comment_length - 5) ) {
+ if ( (temp[5+j] < '0') || (temp[5+j] > '9') ) {
+ break;
+ }
+ j++;
+ }
+ if ( j == (comment_length - 5) ) {
+ p = NULL;
+ entry->year = atoi(temp + 5);
+ }
+ } else if (strncasecmp(temp, "TRACKNUMBER=", 12) == 0) {
+ int j=0;
+ /* verify that this is a number */
+ /* Note: vorbis uses UTF-8 for its comments, so it is
+ * safe to compare the values against ASCII 0 and 9
+ */
+ while ( j < (comment_length - 12) ) {
+ if ( (temp[12+j] < '0') || (temp[12+j] > '9') ) {
+ break;
+ }
+ j++;
+ }
+ if ( j == (comment_length - 12) ) {
+ p = NULL;
+ entry->tracknum = atoi(temp + 12);
+ }
+ } else {
+ p = NULL;
+ }
+
+ if (p) {
+ comment_length -= (name_length + 1);
+ if ( comment_length < buffer_remaining ) {
+ strncpy(buffer, temp + name_length + 1, comment_length);
+ buffer[comment_length] = '\0';
+ *p = buffer;
+ buffer += comment_length + 1;
+ buffer_remaining -= comment_length + 1;
+ }
+ }
+ }
+
+ return true;
+}
+
diff --git a/docs/CREDITS b/docs/CREDITS
index e055051f89..caef9240bb 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -125,3 +125,4 @@ Bryan Vandyke
Hristo Kovachev
Sander Sweers
Antonius Hellman
+Ryan Jackson
diff --git a/firmware/SOURCES b/firmware/SOURCES
index 1a1eacbdc4..199c4cb80e 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -14,6 +14,7 @@ common/file.c
common/disk.c
common/errno.c
common/memcmp.c
+common/memchr.c
common/qsort.c
common/random.c
common/sprintf.c
diff --git a/firmware/common/memchr.c b/firmware/common/memchr.c
new file mode 100644
index 0000000000..a7ff222a61
--- /dev/null
+++ b/firmware/common/memchr.c
@@ -0,0 +1,116 @@
+/*
+FUNCTION
+ <<memchr>>---search for character in memory
+
+INDEX
+ memchr
+
+ANSI_SYNOPSIS
+ #include <string.h>
+ void * memchr(const void *<[s1]>, int <[c]>, size_t <[n]>);
+
+TRAD_SYNOPSIS
+ #include <string.h>
+ void * memchr(<[s1]>, <[c]>, <[n]>);
+ void *<[string]>;
+ int *<[c]>;
+ size_t *<[n]>;
+
+DESCRIPTION
+ This function scans the first <[n]> bytes of the memory pointed
+ to by <[s1]> for the character <[c]> (converted to a char).
+
+RETURNS
+ Returns a pointer to the matching byte, or a null pointer if
+ <[c]> does not occur in <[s1]>.
+
+PORTABILITY
+<<memchr>> is ANSI C.
+
+<<memchr>> requires no supporting OS subroutines.
+
+QUICKREF
+ memchr ansi pure
+*/
+
+#include <string.h>
+#include <limits.h>
+
+/* Nonzero if X is not aligned on a "long" boundary. */
+#define UNALIGNED(X) ((long)X & (sizeof (long) - 1))
+
+/* How many bytes are loaded each iteration of the word copy loop. */
+#define LBLOCKSIZE (sizeof (long))
+
+#if LONG_MAX == 2147483647L
+#define DETECTNULL(X) (((X) - 0x01010101) & ~(X) & 0x80808080)
+#else
+#if LONG_MAX == 9223372036854775807L
+/* Nonzero if X (a long int) contains a NULL byte. */
+#define DETECTNULL(X) (((X) - 0x0101010101010101) & ~(X) & 0x8080808080808080)
+#else
+#error long int is not a 32bit or 64bit type.
+#endif
+#endif
+
+/* DETECTCHAR returns nonzero if (long)X contains the byte used
+ to fill (long)MASK. */
+#define DETECTCHAR(X,MASK) (DETECTNULL(X ^ MASK))
+
+void *
+_DEFUN (memchr, (s1, i, n),
+ _CONST void *s1 _AND
+ int i _AND size_t n)
+{
+ _CONST unsigned char *s = (_CONST unsigned char *)s1;
+#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__)
+ unsigned char c = (unsigned char)i;
+
+ while (n-- > 0)
+ {
+ if (*s == c)
+ {
+ return (void *)s;
+ }
+ s++;
+ }
+
+ return NULL;
+#else
+ unsigned char c = (unsigned char)i;
+ unsigned long mask,j;
+ unsigned long *aligned_addr;
+
+ if (!UNALIGNED (s))
+ {
+ mask = 0;
+ for (j = 0; j < LBLOCKSIZE; j++)
+ mask = (mask << 8) | c;
+
+ aligned_addr = (unsigned long*)s;
+ while ((!DETECTCHAR (*aligned_addr, mask)) && (n>LBLOCKSIZE))
+ {
+ aligned_addr++;
+ n -= LBLOCKSIZE;
+ }
+
+ /* The block of bytes currently pointed to by aligned_addr
+ may contain the target character or there may be less than
+ LBLOCKSIZE bytes left to search. We check the last few
+ bytes using the bytewise search. */
+
+ s = (unsigned char*)aligned_addr;
+ }
+
+ while (n-- > 0)
+ {
+ if (*s == c)
+ {
+ return (void *)s;
+ }
+ s++;
+ }
+
+ return NULL;
+#endif /* not PREFER_SIZE_OVER_SPEED */
+}
diff --git a/firmware/export/id3.h b/firmware/export/id3.h
index a1fff903f0..8fd41a1286 100644
--- a/firmware/export/id3.h
+++ b/firmware/export/id3.h
@@ -78,6 +78,9 @@ struct mp3entry {
unsigned int length; /* song length */
unsigned int elapsed; /* ms played */
+ /* Added for Vorbis */
+ unsigned long samples; /* number of samples in track */
+
/* MP3 stream specific info */
long bpf; /* bytes per frame */
long tpf; /* time per frame */