mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-12-05 08:30:27 +01:00
5e0a0765ce
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
896 lines
21 KiB
C
896 lines
21 KiB
C
/*
|
|
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;
|
|
}
|