|
|
|
/*
|
|
|
|
reader.c - compact version of famous library mpg123
|
|
|
|
Copyright (C) 2017 Uncle Mike
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "mpg123.h"
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <io.h>
|
|
|
|
#else
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define READER_STREAM 0
|
|
|
|
#define READER_FEED 1
|
|
|
|
#define READER_BUF_STREAM 2
|
|
|
|
|
|
|
|
static int default_init( mpg123_handle_t *fr );
|
|
|
|
static mpg_off_t get_fileinfo( mpg123_handle_t *fr );
|
|
|
|
|
|
|
|
// methods for the buffer chain, mainly used for feed reader, but not just that.
|
|
|
|
static buffy_t* buffy_new( size_t size, size_t minsize )
|
|
|
|
{
|
|
|
|
buffy_t *newbuf = malloc( sizeof( buffy_t ));
|
|
|
|
|
|
|
|
if( newbuf == NULL )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
newbuf->realsize = size > minsize ? size : minsize;
|
|
|
|
newbuf->data = malloc( newbuf->realsize );
|
|
|
|
|
|
|
|
if( newbuf->data == NULL )
|
|
|
|
{
|
|
|
|
free( newbuf );
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
newbuf->size = 0;
|
|
|
|
newbuf->next = NULL;
|
|
|
|
|
|
|
|
return newbuf;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void buffy_del( buffy_t *buf )
|
|
|
|
{
|
|
|
|
if( buf )
|
|
|
|
{
|
|
|
|
free( buf->data );
|
|
|
|
free( buf );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete this buffy and all following buffies.
|
|
|
|
static void buffy_del_chain( buffy_t *buf )
|
|
|
|
{
|
|
|
|
while( buf )
|
|
|
|
{
|
|
|
|
buffy_t *next = buf->next;
|
|
|
|
buffy_del( buf );
|
|
|
|
buf = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetch a buffer from the pool (if possible) or create one.
|
|
|
|
static buffy_t* bc_alloc( bufferchain_t *bc, size_t size )
|
|
|
|
{
|
|
|
|
// Easy route: Just try the first available buffer.
|
|
|
|
// size does not matter, it's only a hint for creation of new buffers.
|
|
|
|
if( bc->pool )
|
|
|
|
{
|
|
|
|
buffy_t *buf = bc->pool;
|
|
|
|
|
|
|
|
bc->pool = buf->next;
|
|
|
|
buf->next = NULL; // that shall be set to a sensible value later.
|
|
|
|
buf->size = 0;
|
|
|
|
bc->pool_fill--;
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
return buffy_new( size, bc->bufblock );
|
|
|
|
}
|
|
|
|
|
|
|
|
// either stuff the buffer back into the pool or free it for good.
|
|
|
|
static void bc_free( bufferchain_t *bc, buffy_t* buf )
|
|
|
|
{
|
|
|
|
if( !buf ) return;
|
|
|
|
|
|
|
|
if( bc->pool_fill < bc->pool_size )
|
|
|
|
{
|
|
|
|
buf->next = bc->pool;
|
|
|
|
bc->pool = buf;
|
|
|
|
bc->pool_fill++;
|
|
|
|
}
|
|
|
|
else buffy_del( buf );
|
|
|
|
}
|
|
|
|
|
|
|
|
// make the buffer count in the pool match the pool size.
|
|
|
|
static int bc_fill_pool( bufferchain_t *bc )
|
|
|
|
{
|
|
|
|
// remove superfluous ones.
|
|
|
|
while( bc->pool_fill > bc->pool_size )
|
|
|
|
{
|
|
|
|
// lazyness: Just work on the front.
|
|
|
|
buffy_t *buf = bc->pool;
|
|
|
|
bc->pool = buf->next;
|
|
|
|
buffy_del( buf );
|
|
|
|
bc->pool_fill--;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add missing ones.
|
|
|
|
while( bc->pool_fill < bc->pool_size )
|
|
|
|
{
|
|
|
|
// again, just work on the front.
|
|
|
|
buffy_t *buf = buffy_new( 0, bc->bufblock ); // use default block size.
|
|
|
|
if( !buf ) return -1;
|
|
|
|
|
|
|
|
buf->next = bc->pool;
|
|
|
|
bc->pool = buf;
|
|
|
|
bc->pool_fill++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bc_init( bufferchain_t *bc )
|
|
|
|
{
|
|
|
|
bc->first = NULL;
|
|
|
|
bc->last = bc->first;
|
|
|
|
bc->size = 0;
|
|
|
|
bc->pos = 0;
|
|
|
|
bc->firstpos = 0;
|
|
|
|
bc->fileoff = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bc_reset( bufferchain_t *bc )
|
|
|
|
{
|
|
|
|
// free current chain, possibly stuffing back into the pool.
|
|
|
|
while( bc->first )
|
|
|
|
{
|
|
|
|
buffy_t *buf = bc->first;
|
|
|
|
bc->first = buf->next;
|
|
|
|
bc_free( bc, buf );
|
|
|
|
}
|
|
|
|
|
|
|
|
bc_fill_pool( bc ); // ignoring an error here...
|
|
|
|
bc_init( bc );
|
|
|
|
}
|
|
|
|
|
|
|
|
// create a new buffy at the end to be filled.
|
|
|
|
static int bc_append( bufferchain_t *bc, mpg_ssize_t size )
|
|
|
|
{
|
|
|
|
buffy_t *newbuf;
|
|
|
|
|
|
|
|
if( size < 1 )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
newbuf = bc_alloc( bc, size );
|
|
|
|
if( newbuf == NULL ) return -2;
|
|
|
|
|
|
|
|
if( bc->last != NULL )
|
|
|
|
bc->last->next = newbuf;
|
|
|
|
else if( bc->first == NULL )
|
|
|
|
bc->first = newbuf;
|
|
|
|
|
|
|
|
bc->last = newbuf;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void bc_prepare( bufferchain_t *bc, size_t pool_size, size_t bufblock )
|
|
|
|
{
|
|
|
|
bc_poolsize( bc, pool_size, bufblock );
|
|
|
|
bc->pool = NULL;
|
|
|
|
bc->pool_fill = 0;
|
|
|
|
bc_init( bc ); // ensure that members are zeroed for read-only use.
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t bc_fill( bufferchain_t *bc )
|
|
|
|
{
|
|
|
|
return (size_t)(bc->size - bc->pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
void bc_poolsize( bufferchain_t *bc, size_t pool_size, size_t bufblock )
|
|
|
|
{
|
|
|
|
bc->pool_size = pool_size;
|
|
|
|
bc->bufblock = bufblock;
|
|
|
|
}
|
|
|
|
|
|
|
|
void bc_cleanup( bufferchain_t *bc )
|
|
|
|
{
|
|
|
|
buffy_del_chain( bc->pool );
|
|
|
|
bc->pool_fill = 0;
|
|
|
|
bc->pool = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// append a new buffer and copy content to it.
|
|
|
|
static int bc_add( bufferchain_t *bc, const byte *data, mpg_ssize_t size )
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
mpg_ssize_t part = 0;
|
|
|
|
|
|
|
|
while( size > 0 )
|
|
|
|
{
|
|
|
|
// try to fill up the last buffer block.
|
|
|
|
if( bc->last != NULL && bc->last->size < bc->last->realsize )
|
|
|
|
{
|
|
|
|
part = bc->last->realsize - bc->last->size;
|
|
|
|
if( part > size ) part = size;
|
|
|
|
|
|
|
|
memcpy( bc->last->data + bc->last->size, data, part );
|
|
|
|
bc->last->size += part;
|
|
|
|
size -= part;
|
|
|
|
bc->size += part;
|
|
|
|
data += part;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there is still data left, put it into a new buffer block.
|
|
|
|
if( size > 0 && ( ret = bc_append( bc, size )) != 0 )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// common handler for "You want more than I can give." situation.
|
|
|
|
static mpg_ssize_t bc_need_more( bufferchain_t *bc )
|
|
|
|
{
|
|
|
|
// go back to firstpos, undo the previous reads
|
|
|
|
bc->pos = bc->firstpos;
|
|
|
|
|
|
|
|
return MPG123_NEED_MORE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// give some data, advancing position but not forgetting yet.
|
|
|
|
static mpg_ssize_t bc_give( bufferchain_t *bc, byte *out, mpg_ssize_t size )
|
|
|
|
{
|
|
|
|
buffy_t *b = bc->first;
|
|
|
|
mpg_ssize_t gotcount = 0;
|
|
|
|
mpg_ssize_t offset = 0;
|
|
|
|
|
|
|
|
if( bc->size - bc->pos < size )
|
|
|
|
return bc_need_more( bc );
|
|
|
|
|
|
|
|
// find the current buffer
|
|
|
|
while( b != NULL && ( offset + b->size ) <= bc->pos )
|
|
|
|
{
|
|
|
|
offset += b->size;
|
|
|
|
b = b->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
// now start copying from there
|
|
|
|
while( gotcount < size && ( b != NULL ))
|
|
|
|
{
|
|
|
|
mpg_ssize_t loff = bc->pos - offset;
|
|
|
|
mpg_ssize_t chunk = size - gotcount; // amount of bytes to get from here...
|
|
|
|
|
|
|
|
if( chunk > b->size - loff )
|
|
|
|
chunk = b->size - loff;
|
|
|
|
|
|
|
|
memcpy( out + gotcount, b->data + loff, chunk );
|
|
|
|
gotcount += chunk;
|
|
|
|
bc->pos += chunk;
|
|
|
|
offset += b->size;
|
|
|
|
b = b->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return gotcount;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip some bytes and return the new position.
|
|
|
|
// the buffers are still there, just the read pointer is moved!
|
|
|
|
static mpg_ssize_t bc_skip( bufferchain_t *bc, mpg_ssize_t count )
|
|
|
|
{
|
|
|
|
if( count >= 0 )
|
|
|
|
{
|
|
|
|
if( bc->size - bc->pos < count )
|
|
|
|
return bc_need_more( bc );
|
|
|
|
return bc->pos += count;
|
|
|
|
}
|
|
|
|
|
|
|
|
return MPG123_ERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
static mpg_ssize_t bc_seekback( bufferchain_t *bc, mpg_ssize_t count )
|
|
|
|
{
|
|
|
|
if( count >= 0 && count <= bc->pos )
|
|
|
|
return bc->pos -= count;
|
|
|
|
return MPG123_ERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
// throw away buffies that we passed.
|
|
|
|
static void bc_forget( bufferchain_t *bc )
|
|
|
|
{
|
|
|
|
buffy_t *b = bc->first;
|
|
|
|
|
|
|
|
// free all buffers that are def'n'tly outdated
|
|
|
|
// we have buffers until filepos... delete all buffers fully below it
|
|
|
|
while( b != NULL && bc->pos >= b->size )
|
|
|
|
{
|
|
|
|
buffy_t *n = b->next; // != NULL or this is indeed the end and the last cycle anyway
|
|
|
|
|
|
|
|
if( n == NULL )
|
|
|
|
bc->last = NULL; // Going to delete the last buffy...
|
|
|
|
bc->fileoff += b->size;
|
|
|
|
bc->pos -= b->size;
|
|
|
|
bc->size -= b->size;
|
|
|
|
bc_free( bc, b );
|
|
|
|
b = n;
|
|
|
|
}
|
|
|
|
|
|
|
|
bc->first = b;
|
|
|
|
bc->firstpos = bc->pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
// reader for input via manually provided buffers
|
|
|
|
static int feed_init( mpg123_handle_t *fr )
|
|
|
|
{
|
|
|
|
bc_init( &fr->rdat.buffer );
|
|
|
|
bc_fill_pool( &fr->rdat.buffer );
|
|
|
|
fr->rdat.filelen = 0;
|
|
|
|
fr->rdat.filepos = 0;
|
|
|
|
fr->rdat.flags |= READER_BUFFERED;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// externally called function, returns 0 on success, -1 on error
|
|
|
|
int feed_more( mpg123_handle_t *fr, const byte *in, long count )
|
|
|
|
{
|
|
|
|
if( bc_add( &fr->rdat.buffer, in, count ) != 0 )
|
|
|
|
return MPG123_ERR;
|
|
|
|
|
|
|
|
return MPG123_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static mpg_ssize_t feed_read( mpg123_handle_t *fr, byte *out, mpg_ssize_t count )
|
|
|
|
{
|
|
|
|
mpg_ssize_t gotcount = bc_give( &fr->rdat.buffer, out, count );
|
|
|
|
|
|
|
|
if( gotcount >= 0 && gotcount != count )
|
|
|
|
return MPG123_ERR;
|
|
|
|
|
|
|
|
return gotcount;
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns reached position... negative ones are bad...
|
|
|
|
static mpg_off_t feed_skip_bytes( mpg123_handle_t *fr, mpg_off_t len )
|
|
|
|
{
|
|
|
|
// this is either the new buffer offset or some negative error value.
|
|
|
|
mpg_off_t res = bc_skip( &fr->rdat.buffer, (mpg_ssize_t)len );
|
|
|
|
if( res < 0 ) return res;
|
|
|
|
|
|
|
|
return fr->rdat.buffer.fileoff + res;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int feed_back_bytes( mpg123_handle_t *fr, mpg_off_t bytes )
|
|
|
|
{
|
|
|
|
if( bytes >= 0 )
|
|
|
|
return bc_seekback(&fr->rdat.buffer, (mpg_ssize_t)bytes) >= 0 ? 0 : MPG123_ERR;
|
|
|
|
return feed_skip_bytes( fr, -bytes ) >= 0 ? 0 : MPG123_ERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int feed_seek_frame( mpg123_handle_t *fr, mpg_off_t num )
|
|
|
|
{
|
|
|
|
return MPG123_ERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
// not just for feed reader, also for self-feeding buffered reader.
|
|
|
|
static void buffered_forget( mpg123_handle_t *fr )
|
|
|
|
{
|
|
|
|
bc_forget( &fr->rdat.buffer );
|
|
|
|
fr->rdat.filepos = fr->rdat.buffer.fileoff + fr->rdat.buffer.pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
mpg_off_t feed_set_pos( mpg123_handle_t *fr, mpg_off_t pos )
|
|
|
|
{
|
|
|
|
bufferchain_t *bc = &fr->rdat.buffer;
|
|
|
|
|
|
|
|
if( pos >= bc->fileoff && pos - bc->fileoff < bc->size )
|
|
|
|
{
|
|
|
|
// we have the position!
|
|
|
|
bc->pos = (mpg_ssize_t)(pos - bc->fileoff);
|
|
|
|
|
|
|
|
// next input after end of buffer...
|
|
|
|
return bc->fileoff + bc->size;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// i expect to get the specific position on next feed. Forget what I have now.
|
|
|
|
bc_reset( bc );
|
|
|
|
bc->fileoff = pos;
|
|
|
|
|
|
|
|
// next input from exactly that position.
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// the specific stuff for buffered stream reader.
|
|
|
|
static mpg_ssize_t buffered_fullread( mpg123_handle_t *fr, byte *out, mpg_ssize_t count )
|
|
|
|
{
|
|
|
|
bufferchain_t *bc = &fr->rdat.buffer;
|
|
|
|
mpg_ssize_t gotcount;
|
|
|
|
|
|
|
|
if( bc->size - bc->pos < count )
|
|
|
|
{
|
|
|
|
// add more stuff to buffer. If hitting end of file, adjust count.
|
|
|
|
byte readbuf[4096];
|
|
|
|
|
|
|
|
mpg_ssize_t need = count - (bc->size - bc->pos);
|
|
|
|
|
|
|
|
while( need > 0 )
|
|
|
|
{
|
|
|
|
mpg_ssize_t got = fr->rdat.fullread( fr, readbuf, sizeof( readbuf ));
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if( got < 0 )
|
|
|
|
return MPG123_ERR;
|
|
|
|
|
|
|
|
if( got > 0 && ( ret = bc_add( bc, readbuf, got )) != 0 )
|
|
|
|
return MPG123_ERR;
|
|
|
|
|
|
|
|
need -= got; // may underflow here...
|
|
|
|
|
|
|
|
if( got < sizeof( readbuf )) // that naturally catches got == 0, too.
|
|
|
|
break; // end.
|
|
|
|
}
|
|
|
|
|
|
|
|
if( bc->size - bc->pos < count )
|
|
|
|
count = bc->size - bc->pos; // we want only what we got.
|
|
|
|
}
|
|
|
|
|
|
|
|
gotcount = bc_give( bc, out, count );
|
|
|
|
|
|
|
|
if( gotcount != count )
|
|
|
|
return MPG123_ERR;
|
|
|
|
return gotcount;
|
|
|
|
}
|
|
|
|
|
|
|
|
// stream based operation
|
|
|
|
static mpg_ssize_t plain_fullread( mpg123_handle_t *fr, byte *buf, mpg_ssize_t count )
|
|
|
|
{
|
|
|
|
mpg_ssize_t ret, cnt=0;
|
|
|
|
|
|
|
|
// there used to be a check for expected file end here (length value or ID3 flag).
|
|
|
|
// this is not needed:
|
|
|
|
// 1. EOF is indicated by fdread returning zero bytes anyway.
|
|
|
|
// 2. We get false positives of EOF for either files that grew or
|
|
|
|
// 3. ... files that have ID3v1 tags in between (stream with intro).
|
|
|
|
while( cnt < count )
|
|
|
|
{
|
|
|
|
ret = fr->rdat.fdread( fr, buf + cnt, count - cnt );
|
|
|
|
|
|
|
|
if( ret < 0 ) return MPG123_ERR;
|
|
|
|
if( ret == 0 ) break;
|
|
|
|
|
|
|
|
if(!( fr->rdat.flags & READER_BUFFERED ))
|
|
|
|
fr->rdat.filepos += ret;
|
|
|
|
cnt += ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return cnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
// wrappers for actual reading/seeking... I'm full of wrappers here.
|
|
|
|
static mpg_off_t io_seek( reader_data_t *rdat, mpg_off_t offset, int whence )
|
|
|
|
{
|
|
|
|
if( rdat->flags & READER_HANDLEIO )
|
|
|
|
{
|
|
|
|
if( rdat->r_lseek_handle != NULL )
|
|
|
|
return rdat->r_lseek_handle( rdat->iohandle, offset, whence );
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rdat->lseek( rdat->filept, offset, whence );
|
|
|
|
}
|
|
|
|
|
|
|
|
static mpg_ssize_t io_read( reader_data_t *rdat, void *buf, size_t count )
|
|
|
|
{
|
|
|
|
if( rdat->flags & READER_HANDLEIO )
|
|
|
|
{
|
|
|
|
if( rdat->r_read_handle != NULL )
|
|
|
|
return rdat->r_read_handle( rdat->iohandle, buf, count );
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rdat->read( rdat->filept, buf, count );
|
|
|
|
}
|
|
|
|
|
|
|
|
// A normal read and a read with timeout.
|
|
|
|
static mpg_ssize_t plain_read( mpg123_handle_t *fr, void *buf, size_t count )
|
|
|
|
{
|
|
|
|
return io_read( &fr->rdat, buf, count );
|
|
|
|
}
|
|
|
|
|
|
|
|
static mpg_off_t stream_lseek( mpg123_handle_t *fr, mpg_off_t pos, int whence )
|
|
|
|
{
|
|
|
|
mpg_off_t ret;
|
|
|
|
|
|
|
|
ret = io_seek( &fr->rdat, pos, whence );
|
|
|
|
|
|
|
|
if( ret >= 0 )
|
|
|
|
{
|
|
|
|
fr->rdat.filepos = ret;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fr->err = MPG123_LSEEK_FAILED;
|
|
|
|
ret = MPG123_ERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void stream_close( mpg123_handle_t *fr )
|
|
|
|
{
|
|
|
|
if( fr->rdat.flags & READER_FD_OPENED )
|
|
|
|
close( fr->rdat.filept );
|
|
|
|
|
|
|
|
fr->rdat.filept = 0;
|
|
|
|
|
|
|
|
if( fr->rdat.flags & READER_BUFFERED )
|
|
|
|
bc_reset( &fr->rdat.buffer );
|
|
|
|
|
|
|
|
if( fr->rdat.flags & READER_HANDLEIO )
|
|
|
|
{
|
|
|
|
if( fr->rdat.cleanup_handle != NULL )
|
|
|
|
fr->rdat.cleanup_handle( fr->rdat.iohandle );
|
|
|
|
fr->rdat.iohandle = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int stream_seek_frame( mpg123_handle_t *fr, mpg_off_t newframe )
|
|
|
|
{
|
|
|
|
// seekable streams can go backwards and jump forwards.
|
|
|
|
// non-seekable streams still can go forward, just not jump.
|
|
|
|
if(( fr->rdat.flags & READER_SEEKABLE ) || ( newframe >= fr->num ))
|
|
|
|
{
|
|
|
|
mpg_off_t preframe; // a leading frame we jump to
|
|
|
|
mpg_off_t seek_to; // the byte offset we want to reach
|
|
|
|
mpg_off_t to_skip; // bytes to skip to get there (can be negative)
|
|
|
|
|
|
|
|
// now seek to nearest leading index position and read from there until newframe is reached.
|
|
|
|
// we use skip_bytes, which handles seekable and non-seekable streams
|
|
|
|
// (the latter only for positive offset, which we ensured before entering here).
|
|
|
|
seek_to = frame_index_find( fr, newframe, &preframe );
|
|
|
|
|
|
|
|
// no need to seek to index position if we are closer already.
|
|
|
|
// but I am picky about fr->num == newframe, play safe by reading the frame again.
|
|
|
|
// if you think that's stupid, don't call a seek to the current frame.
|
|
|
|
if( fr->num >= newframe || fr->num < preframe )
|
|
|
|
{
|
|
|
|
to_skip = seek_to - fr->rd->tell( fr );
|
|
|
|
if( fr->rd->skip_bytes( fr, to_skip ) != seek_to )
|
|
|
|
return MPG123_ERR;
|
|
|
|
|
|
|
|
fr->num = preframe - 1; // watch out! I am going to read preframe... fr->num should indicate the frame before!
|
|
|
|
}
|
|
|
|
|
|
|
|
while( fr->num < newframe )
|
|
|
|
{
|
|
|
|
// try to be non-fatal now... frameNum only gets advanced on success anyway
|
|
|
|
if( !read_frame( fr )) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// now the wanted frame should be ready for decoding.
|
|
|
|
return MPG123_OK;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fr->err = MPG123_NO_SEEK;
|
|
|
|
return MPG123_ERR; // invalid, no seek happened
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// return FALSE on error, TRUE on success, READER_MORE on occasion
|
|
|
|
static int generic_head_read( mpg123_handle_t *fr, ulong *newhead )
|
|
|
|
{
|
|
|
|
byte hbuf[4];
|
|
|
|
int ret = fr->rd->fullread( fr, hbuf, 4 );
|
|
|
|
|
|
|
|
if( ret == MPG123_NEED_MORE )
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if( ret != 4 ) return FALSE;
|
|
|
|
|
|
|
|
*newhead = ((ulong) hbuf[0] << 24) | ((ulong) hbuf[1] << 16) | ((ulong) hbuf[2] << 8) | (ulong) hbuf[3];
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// return FALSE on error, TRUE on success, READER_MORE on occasion
|
|
|
|
static int generic_head_shift( mpg123_handle_t *fr, ulong *head )
|
|
|
|
{
|
|
|
|
byte hbuf;
|
|
|
|
int ret = fr->rd->fullread( fr, &hbuf, 1 );
|
|
|
|
|
|
|
|
if( ret == MPG123_NEED_MORE )
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if( ret != 1 ) return FALSE;
|
|
|
|
|
|
|
|
*head <<= 8;
|
|
|
|
*head |= hbuf;
|
|
|
|
*head &= 0xffffffff;
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns reached position... negative ones are bad...
|
|
|
|
static mpg_off_t stream_skip_bytes( mpg123_handle_t *fr, mpg_off_t len )
|
|
|
|
{
|
|
|
|
if( fr->rdat.flags & READER_SEEKABLE )
|
|
|
|
{
|
|
|
|
mpg_off_t ret = stream_lseek( fr, len, SEEK_CUR );
|
|
|
|
return (ret < 0) ? MPG123_ERR : ret;
|
|
|
|
}
|
|
|
|
else if( len >= 0 )
|
|
|
|
{
|
|
|
|
byte buf[1024]; // ThOr: Compaq cxx complained and it makes sense to me... or should one do a cast? What for?
|
|
|
|
mpg_ssize_t ret;
|
|
|
|
|
|
|
|
while( len > 0 )
|
|
|
|
{
|
|
|
|
mpg_ssize_t num = len < (mpg_off_t)sizeof( buf ) ? (mpg_ssize_t)len : (mpg_ssize_t)sizeof( buf );
|
|
|
|
ret = fr->rd->fullread( fr, buf, num );
|
|
|
|
if( ret < 0 ) return ret;
|
|
|
|
else if( ret == 0 ) break; // EOF... an error? interface defined to tell the actual position...
|
|
|
|
len -= ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fr->rd->tell( fr );
|
|
|
|
}
|
|
|
|
else if( fr->rdat.flags & READER_BUFFERED )
|
|
|
|
{
|
|
|
|
// perhaps we _can_ go a bit back.
|
|
|
|
if( fr->rdat.buffer.pos >= -len )
|
|
|
|
{
|
|
|
|
fr->rdat.buffer.pos += len;
|
|
|
|
return fr->rd->tell( fr );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fr->err = MPG123_NO_SEEK;
|
|
|
|
return MPG123_ERR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fr->err = MPG123_NO_SEEK;
|
|
|
|
return MPG123_ERR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// return 0 on success...
|
|
|
|
static int stream_back_bytes( mpg123_handle_t *fr, mpg_off_t bytes )
|
|
|
|
{
|
|
|
|
mpg_off_t want = fr->rd->tell( fr ) - bytes;
|
|
|
|
|
|
|
|
if( want < 0 ) return MPG123_ERR;
|
|
|
|
|
|
|
|
if( stream_skip_bytes( fr, -bytes ) != want )
|
|
|
|
return MPG123_ERR;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// returns size on success...
|
|
|
|
static int generic_read_frame_body( mpg123_handle_t *fr, byte *buf, int size )
|
|
|
|
{
|
|
|
|
long l;
|
|
|
|
|
|
|
|
if(( l = fr->rd->fullread( fr, buf, size )) != size )
|
|
|
|
return MPG123_ERR;
|
|
|
|
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
|
|
|
|
static mpg_off_t generic_tell( mpg123_handle_t *fr )
|
|
|
|
{
|
|
|
|
if( fr->rdat.flags & READER_BUFFERED )
|
|
|
|
fr->rdat.filepos = fr->rdat.buffer.fileoff + fr->rdat.buffer.pos;
|
|
|
|
|
|
|
|
return fr->rdat.filepos;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this does not (fully) work for non-seekable streams... You have to check for that flag, pal!
|
|
|
|
static void stream_rewind( mpg123_handle_t *fr )
|
|
|
|
{
|
|
|
|
if( fr->rdat.flags & READER_SEEKABLE )
|
|
|
|
{
|
|
|
|
fr->rdat.filepos = stream_lseek( fr, 0, SEEK_SET );
|
|
|
|
fr->rdat.buffer.fileoff = fr->rdat.filepos;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( fr->rdat.flags & READER_BUFFERED )
|
|
|
|
{
|
|
|
|
fr->rdat.buffer.pos = 0;
|
|
|
|
fr->rdat.buffer.firstpos = 0;
|
|
|
|
fr->rdat.filepos = fr->rdat.buffer.fileoff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns length of a file (if filept points to a file)
|
|
|
|
// reads the last 128 bytes information into buffer
|
|
|
|
// ... that is not totally safe...
|
|
|
|
static mpg_off_t get_fileinfo( mpg123_handle_t *fr )
|
|
|
|
{
|
|
|
|
mpg_off_t len;
|
|
|
|
|
|
|
|
if(( len = io_seek( &fr->rdat, 0, SEEK_END )) < 0 )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if( io_seek( &fr->rdat, -128, SEEK_END ) < 0 )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if( fr->rd->fullread( fr, (byte *)fr->id3buf, 128 ) != 128 )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if( !strncmp((char *)fr->id3buf, "TAG", 3 ))
|
|
|
|
len -= 128;
|
|
|
|
|
|
|
|
if( io_seek( &fr->rdat, 0, SEEK_SET ) < 0 )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if( len <= 0 )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bad_init( mpg123_handle_t *mh ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
|
|
|
|
static mpg_ssize_t bad_fullread( mpg123_handle_t *mh, byte *data, mpg_ssize_t count ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
|
|
|
|
static int bad_head_read( mpg123_handle_t *mh, ulong *newhead ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
|
|
|
|
static int bad_head_shift( mpg123_handle_t *mh, ulong *head ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
|
|
|
|
static mpg_off_t bad_skip_bytes( mpg123_handle_t *mh, mpg_off_t len ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
|
|
|
|
static int bad_read_frame_body( mpg123_handle_t *mh, byte *data, int size ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
|
|
|
|
static int bad_back_bytes( mpg123_handle_t *mh, mpg_off_t bytes ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
|
|
|
|
static int bad_seek_frame( mpg123_handle_t *mh, mpg_off_t num ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
|
|
|
|
static mpg_off_t bad_tell( mpg123_handle_t *mh ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }
|
|
|
|
static void bad_rewind( mpg123_handle_t *mh ) { }
|
|
|
|
static void bad_close( mpg123_handle_t *mh ) { }
|
|
|
|
|
|
|
|
static reader_t bad_reader =
|
|
|
|
{
|
|
|
|
bad_init,
|
|
|
|
bad_close,
|
|
|
|
bad_fullread,
|
|
|
|
bad_head_read,
|
|
|
|
bad_head_shift,
|
|
|
|
bad_skip_bytes,
|
|
|
|
bad_read_frame_body,
|
|
|
|
bad_back_bytes,
|
|
|
|
bad_seek_frame,
|
|
|
|
bad_tell,
|
|
|
|
bad_rewind,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
void open_bad( mpg123_handle_t *mh )
|
|
|
|
{
|
|
|
|
mh->rd = &bad_reader;
|
|
|
|
mh->rdat.flags = 0;
|
|
|
|
bc_init( &mh->rdat.buffer );
|
|
|
|
mh->rdat.filelen = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static reader_t readers[] =
|
|
|
|
{
|
|
|
|
{ // READER_STREAM
|
|
|
|
default_init,
|
|
|
|
stream_close,
|
|
|
|
plain_fullread,
|
|
|
|
generic_head_read,
|
|
|
|
generic_head_shift,
|
|
|
|
stream_skip_bytes,
|
|
|
|
generic_read_frame_body,
|
|
|
|
stream_back_bytes,
|
|
|
|
stream_seek_frame,
|
|
|
|
generic_tell,
|
|
|
|
stream_rewind,
|
|
|
|
NULL
|
|
|
|
},
|
|
|
|
{ // READER_FEED
|
|
|
|
feed_init,
|
|
|
|
stream_close,
|
|
|
|
feed_read,
|
|
|
|
generic_head_read,
|
|
|
|
generic_head_shift,
|
|
|
|
feed_skip_bytes,
|
|
|
|
generic_read_frame_body,
|
|
|
|
feed_back_bytes,
|
|
|
|
feed_seek_frame,
|
|
|
|
generic_tell,
|
|
|
|
stream_rewind,
|
|
|
|
buffered_forget
|
|
|
|
},
|
|
|
|
{ // READER_BUF_STREAM
|
|
|
|
default_init,
|
|
|
|
stream_close,
|
|
|
|
buffered_fullread,
|
|
|
|
generic_head_read,
|
|
|
|
generic_head_shift,
|
|
|
|
stream_skip_bytes,
|
|
|
|
generic_read_frame_body,
|
|
|
|
stream_back_bytes,
|
|
|
|
stream_seek_frame,
|
|
|
|
generic_tell,
|
|
|
|
stream_rewind,
|
|
|
|
buffered_forget
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// final code common to open_stream and open_stream_handle.
|
|
|
|
static int open_finish( mpg123_handle_t *fr )
|
|
|
|
{
|
|
|
|
fr->rd = &readers[READER_STREAM];
|
|
|
|
if( fr->rd->init( fr ) < 0 )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return MPG123_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
int open_stream_handle( mpg123_handle_t *fr, void *iohandle )
|
|
|
|
{
|
|
|
|
fr->rdat.filelen = -1;
|
|
|
|
fr->rdat.filept = -1;
|
|
|
|
fr->rdat.iohandle = iohandle;
|
|
|
|
fr->rdat.flags = 0;
|
|
|
|
fr->rdat.flags |= READER_HANDLEIO;
|
|
|
|
|
|
|
|
return open_finish( fr );
|
|
|
|
}
|
|
|
|
|
|
|
|
int open_feed( mpg123_handle_t *fr )
|
|
|
|
{
|
|
|
|
fr->rd = &readers[READER_FEED];
|
|
|
|
fr->rdat.flags = 0;
|
|
|
|
|
|
|
|
if( fr->rd->init( fr ) < 0 )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int default_init( mpg123_handle_t *fr )
|
|
|
|
{
|
|
|
|
fr->rdat.fdread = plain_read;
|
|
|
|
fr->rdat.read = fr->rdat.r_read != NULL ? fr->rdat.r_read : read;
|
|
|
|
fr->rdat.lseek = fr->rdat.r_lseek != NULL ? fr->rdat.r_lseek : lseek;
|
|
|
|
fr->rdat.filelen = get_fileinfo( fr );
|
|
|
|
fr->rdat.filepos = 0;
|
|
|
|
|
|
|
|
// don't enable seeking on ICY streams, just plain normal files.
|
|
|
|
// this check is necessary since the client can enforce ICY parsing on files that would otherwise be seekable.
|
|
|
|
// it is a task for the future to make the ICY parsing safe with seeks ... or not.
|
|
|
|
if( fr->rdat.filelen >= 0 )
|
|
|
|
{
|
|
|
|
fr->rdat.flags |= READER_SEEKABLE;
|
|
|
|
if( !strncmp((char *)fr->id3buf,"TAG", 3 ))
|
|
|
|
{
|
|
|
|
fr->rdat.flags |= READER_ID3TAG;
|
|
|
|
fr->metaflags |= MPG123_NEW_ID3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( fr->p.flags & MPG123_SEEKBUFFER )
|
|
|
|
{
|
|
|
|
// switch reader to a buffered one, if allowed.
|
|
|
|
if( fr->rd == &readers[READER_STREAM] )
|
|
|
|
{
|
|
|
|
fr->rd = &readers[READER_BUF_STREAM];
|
|
|
|
fr->rdat.fullread = plain_fullread;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bc_init( &fr->rdat.buffer );
|
|
|
|
fr->rdat.filelen = 0; // we carry the offset, but never know how big the stream is.
|
|
|
|
fr->rdat.flags |= READER_BUFFERED;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|