From eaf8b2d76d3f69b919ff1c3a55e7ad2f07456bd4 Mon Sep 17 00:00:00 2001 From: Linus Nielsen Feltzing Date: Tue, 5 Jul 2005 08:43:36 +0000 Subject: Patch #1232549 by Ryan Jackson, adds seeking and comments to Vorbis playback git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7025 a1c6a512-1295-4272-9138-f99709370657 --- apps/codecs.c | 1 + apps/codecs.h | 4 +- apps/codecs/lib/xxx2wav.c | 6 +- apps/codecs/vorbis.c | 149 +++++++++++++++++++++++++------ apps/metadata.c | 223 +++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 350 insertions(+), 33 deletions(-) (limited to 'apps') 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),¤t_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; +} + -- cgit v1.2.3