mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-10-13 18:17:39 -04:00
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
This commit is contained in:
parent
a10bb59331
commit
eaf8b2d76d
9 changed files with 471 additions and 33 deletions
|
@ -243,6 +243,7 @@ struct codec_api ci = {
|
||||||
logf,
|
logf,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
memchr,
|
||||||
};
|
};
|
||||||
|
|
||||||
int codec_load_ram(char* codecptr, size_t size, void* ptr2, size_t bufwrap)
|
int codec_load_ram(char* codecptr, size_t size, void* ptr2, size_t bufwrap)
|
||||||
|
|
|
@ -240,7 +240,7 @@ struct codec_api {
|
||||||
#endif
|
#endif
|
||||||
#if CONFIG_HWCODEC == MASNONE
|
#if CONFIG_HWCODEC == MASNONE
|
||||||
void (*pcm_play_data)(const unsigned char *start, int size,
|
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_play_stop)(void);
|
||||||
void (*pcm_set_frequency)(unsigned int frequency);
|
void (*pcm_set_frequency)(unsigned int frequency);
|
||||||
bool (*pcm_is_playing)(void);
|
bool (*pcm_is_playing)(void);
|
||||||
|
@ -326,6 +326,8 @@ struct codec_api {
|
||||||
#ifdef ROCKBOX_HAS_LOGF
|
#ifdef ROCKBOX_HAS_LOGF
|
||||||
void (*logf)(const char *fmt, ...);
|
void (*logf)(const char *fmt, ...);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void *(*memchr)(const void *s1, int c, size_t n);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* defined by the codec loader (codec.c) */
|
/* defined by the codec loader (codec.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) {
|
void* memchr(const void *s, int c, size_t n) {
|
||||||
/* TO DO: Implement for Tremor */
|
return(local_rb->memchr(s,c,n));
|
||||||
(void)s;
|
|
||||||
(void)c;
|
|
||||||
(void)n;
|
|
||||||
return(NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void* memmove(const void *s1, const void *s2, size_t n) {
|
void* memmove(const void *s1, const void *s2, size_t n) {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "codecs.h"
|
#include "codecs.h"
|
||||||
|
|
||||||
#include "Tremor/ivorbisfile.h"
|
#include "Tremor/ivorbisfile.h"
|
||||||
|
#include "Tremor/ogg.h"
|
||||||
#include "playback.h"
|
#include "playback.h"
|
||||||
#include "dsp.h"
|
#include "dsp.h"
|
||||||
#include "lib/codeclib.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);
|
return rb->read_filebuf(ptr, nmemb*size);
|
||||||
}
|
}
|
||||||
|
|
||||||
int seek_handler(void *datasource, ogg_int64_t offset, int whence)
|
int initial_seek_handler(void *datasource, ogg_int64_t offset, int whence) {
|
||||||
{
|
|
||||||
/* We are not seekable at the moment */
|
|
||||||
(void)datasource;
|
(void)datasource;
|
||||||
(void)offset;
|
(void)offset;
|
||||||
(void)whence;
|
(void)whence;
|
||||||
return -1;
|
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)
|
int close_handler(void *datasource)
|
||||||
{
|
{
|
||||||
(void)datasource;
|
(void)datasource;
|
||||||
|
@ -93,22 +109,26 @@ enum codec_status codec_start(struct codec_api* api)
|
||||||
long n;
|
long n;
|
||||||
int current_section;
|
int current_section;
|
||||||
int eof;
|
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);
|
TEST_CODEC_API(api);
|
||||||
|
|
||||||
/* if you are using a global api pointer, don't forget to copy it!
|
/* 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;
|
rb = api;
|
||||||
|
|
||||||
#ifdef USE_IRAM
|
#ifdef USE_IRAM
|
||||||
rb->memcpy(iramstart, iramcopy, iramend-iramstart);
|
rb->memcpy(iramstart, iramcopy, iramend-iramstart);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
rb->configure(CODEC_SET_FILEBUF_LIMIT, (int *)(1024*1024*2));
|
rb->configure(CODEC_SET_FILEBUF_LIMIT, (int *)(1024*1024*2));
|
||||||
rb->configure(CODEC_SET_FILEBUF_CHUNKSIZE, (int *)(1024*64));
|
rb->configure(CODEC_SET_FILEBUF_CHUNKSIZE, (int *)(1024*64));
|
||||||
|
|
||||||
rb->configure(DSP_DITHER, (bool *)false);
|
rb->configure(DSP_DITHER, (bool *)false);
|
||||||
rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_INTERLEAVED);
|
|
||||||
rb->configure(DSP_SET_SAMPLE_DEPTH, (int *)(16));
|
rb->configure(DSP_SET_SAMPLE_DEPTH, (int *)(16));
|
||||||
|
|
||||||
/* We need to flush reserver memory every track load. */
|
/* 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 */
|
/* Create a decoder instance */
|
||||||
callbacks.read_func=read_handler;
|
callbacks.read_func=read_handler;
|
||||||
callbacks.seek_func=seek_handler;
|
callbacks.seek_func=initial_seek_handler;
|
||||||
callbacks.tell_func=tell_handler;
|
callbacks.tell_func=tell_handler;
|
||||||
callbacks.close_func=close_handler;
|
callbacks.close_func=close_handler;
|
||||||
|
|
||||||
|
/* Open a non-seekable stream */
|
||||||
error=ov_open_callbacks(rb,&vf,NULL,0,callbacks);
|
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);
|
vi=ov_info(&vf,-1);
|
||||||
|
|
||||||
if (vi==NULL) {
|
if (vi==NULL) {
|
||||||
// rb->splash(HZ*2, true, "Vorbis Error");
|
//rb->splash(HZ*2, true, "Vorbis Error");
|
||||||
return CODEC_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;
|
eof=0;
|
||||||
|
rb->yield();
|
||||||
while (!eof) {
|
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 */
|
/* Read host-endian signed 16 bit PCM samples */
|
||||||
n=ov_read(&vf,pcmbuf,sizeof(pcmbuf),¤t_section);
|
n=ov_read(&vf,pcmbuf,sizeof(pcmbuf),¤t_section);
|
||||||
|
|
||||||
if (n==0) {
|
if (n==0) {
|
||||||
eof=1;
|
eof=1;
|
||||||
}
|
} else if (n < 0) {
|
||||||
else if (n < 0) {
|
|
||||||
DEBUGF("Error decoding frame\n");
|
DEBUGF("Error decoding frame\n");
|
||||||
} else {
|
} else {
|
||||||
rb->yield();
|
while (!rb->audiobuffer_insert(pcmbuf, n)) {
|
||||||
if (rb->stop_codec || rb->reload_codec)
|
|
||||||
break ;
|
|
||||||
|
|
||||||
while (!rb->audiobuffer_insert(pcmbuf, n))
|
|
||||||
rb->yield();
|
rb->yield();
|
||||||
|
if ( rb->seek_time ) {
|
||||||
rb->set_elapsed(ov_time_tell(&vf));
|
/* 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;
|
return CODEC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
223
apps/metadata.c
223
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_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 get_metadata(struct track_info* track, int fd, const char* trackname,
|
||||||
bool v1first) {
|
bool v1first) {
|
||||||
unsigned long totalsamples,bytespersample,channels,bitspersample,numbytes;
|
unsigned long totalsamples,bytespersample,channels,bitspersample,numbytes;
|
||||||
|
unsigned long serialno=0, last_serialno=0;
|
||||||
int bytesperframe;
|
int bytesperframe;
|
||||||
unsigned char* buf;
|
unsigned char* buf;
|
||||||
int i,j,eof;
|
int i,j,eof;
|
||||||
|
@ -259,13 +264,29 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
|
||||||
return(false);
|
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. */
|
/* Ogg stores integers in little-endian format. */
|
||||||
track->id3.filesize=filesize(fd);
|
track->id3.filesize=filesize(fd);
|
||||||
track->id3.frequency=buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24);
|
track->id3.frequency=buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24);
|
||||||
channels=buf[39];
|
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
|
/* 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);
|
lseek(fd, -32*1024, SEEK_END);
|
||||||
eof=0;
|
eof=0;
|
||||||
|
@ -283,8 +304,9 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
|
||||||
i=0;
|
i=0;
|
||||||
while (i < (j-5)) {
|
while (i < (j-5)) {
|
||||||
if (memcmp(&buf[i],"OggS",5)==0) {
|
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);
|
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 */
|
j=0; /* We can discard the rest of the buffer */
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
@ -300,7 +322,18 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
|
||||||
j=0;
|
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;
|
track->id3.length=(totalsamples/track->id3.frequency)*1000;
|
||||||
|
|
||||||
/* The following calculation should use datasize, not filesize (i.e. exclude comments etc) */
|
/* 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;
|
*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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,3 +125,4 @@ Bryan Vandyke
|
||||||
Hristo Kovachev
|
Hristo Kovachev
|
||||||
Sander Sweers
|
Sander Sweers
|
||||||
Antonius Hellman
|
Antonius Hellman
|
||||||
|
Ryan Jackson
|
||||||
|
|
|
@ -14,6 +14,7 @@ common/file.c
|
||||||
common/disk.c
|
common/disk.c
|
||||||
common/errno.c
|
common/errno.c
|
||||||
common/memcmp.c
|
common/memcmp.c
|
||||||
|
common/memchr.c
|
||||||
common/qsort.c
|
common/qsort.c
|
||||||
common/random.c
|
common/random.c
|
||||||
common/sprintf.c
|
common/sprintf.c
|
||||||
|
|
116
firmware/common/memchr.c
Normal file
116
firmware/common/memchr.c
Normal file
|
@ -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 */
|
||||||
|
}
|
|
@ -78,6 +78,9 @@ struct mp3entry {
|
||||||
unsigned int length; /* song length */
|
unsigned int length; /* song length */
|
||||||
unsigned int elapsed; /* ms played */
|
unsigned int elapsed; /* ms played */
|
||||||
|
|
||||||
|
/* Added for Vorbis */
|
||||||
|
unsigned long samples; /* number of samples in track */
|
||||||
|
|
||||||
/* MP3 stream specific info */
|
/* MP3 stream specific info */
|
||||||
long bpf; /* bytes per frame */
|
long bpf; /* bytes per frame */
|
||||||
long tpf; /* time per frame */
|
long tpf; /* time per frame */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue