367 lines
12 KiB
C
367 lines
12 KiB
C
/********************************************************************
|
|
* *
|
|
* THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. *
|
|
* USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
|
|
* GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
|
|
* IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
|
|
* *
|
|
* THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2012 *
|
|
* by the Xiph.Org Foundation and contributors http://www.xiph.org/ *
|
|
* *
|
|
********************************************************************
|
|
|
|
function: stdio-based convenience library for opening/seeking/decoding
|
|
last mod: $Id: vorbisfile.c 17573 2010-10-27 14:53:59Z xiphmont $
|
|
|
|
********************************************************************/
|
|
#ifdef OPUS_HAVE_CONFIG_H
|
|
#include "opus_config.h"
|
|
#endif
|
|
|
|
#include "internal.h"
|
|
#include <sys/types.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#if defined(_WIN32)
|
|
# include <io.h>
|
|
#endif
|
|
|
|
typedef struct OpusMemStream OpusMemStream;
|
|
|
|
#define OP_MEM_SIZE_MAX (~(size_t)0>>1)
|
|
#define OP_MEM_DIFF_MAX ((ptrdiff_t)OP_MEM_SIZE_MAX)
|
|
|
|
/*The context information needed to read from a block of memory as if it were a
|
|
file.*/
|
|
struct OpusMemStream{
|
|
/*The block of memory to read from.*/
|
|
const unsigned char *data;
|
|
/*The total size of the block.
|
|
This must be at most OP_MEM_SIZE_MAX to prevent signed overflow while
|
|
seeking.*/
|
|
ptrdiff_t size;
|
|
/*The current file position.
|
|
This is allowed to be set arbitrarily greater than size (i.e., past the end
|
|
of the block, though we will not read data past the end of the block), but
|
|
is not allowed to be negative (i.e., before the beginning of the block).*/
|
|
ptrdiff_t pos;
|
|
};
|
|
|
|
static int op_fread(void *_stream,unsigned char *_ptr,int _buf_size){
|
|
FILE *stream;
|
|
size_t ret;
|
|
/*Check for empty read.*/
|
|
if(_buf_size<=0)return 0;
|
|
stream=(FILE *)_stream;
|
|
ret=fread(_ptr,1,_buf_size,stream);
|
|
OP_ASSERT(ret<=(size_t)_buf_size);
|
|
/*If ret==0 and !feof(stream), there was a read error.*/
|
|
return ret>0||feof(stream)?(int)ret:OP_EREAD;
|
|
}
|
|
|
|
static int op_fseek(void *_stream,opus_int64 _offset,int _whence){
|
|
#if defined(_WIN32)
|
|
/*_fseeki64() is not exposed until MSCVCRT80.
|
|
This is the default starting with MSVC 2005 (_MSC_VER>=1400), but we want
|
|
to allow linking against older MSVCRT versions for compatibility back to
|
|
XP without installing extra runtime libraries.
|
|
i686-pc-mingw32 does not have fseeko() and requires
|
|
__MSVCRT_VERSION__>=0x800 for _fseeki64(), which screws up linking with
|
|
other libraries (that don't use MSVCRT80 from MSVC 2005 by default).
|
|
i686-w64-mingw32 does have fseeko() and respects _FILE_OFFSET_BITS, but I
|
|
don't know how to detect that at compile time.
|
|
We could just use fseeko64() (which is available in both), but its
|
|
implemented using fgetpos()/fsetpos() just like this code, except without
|
|
the overflow checking, so we prefer our version.*/
|
|
opus_int64 pos;
|
|
/*We don't use fpos_t directly because it might be a struct if __STDC__ is
|
|
non-zero or _INTEGRAL_MAX_BITS < 64.
|
|
I'm not certain when the latter is true, but someone could in theory set
|
|
the former.
|
|
Either way, it should be binary compatible with a normal 64-bit int (this
|
|
assumption is not portable, but I believe it is true for MSVCRT).*/
|
|
OP_ASSERT(sizeof(pos)==sizeof(fpos_t));
|
|
/*Translate the seek to an absolute one.*/
|
|
if(_whence==SEEK_CUR){
|
|
int ret;
|
|
ret=fgetpos((FILE *)_stream,(fpos_t *)&pos);
|
|
if(ret)return ret;
|
|
}
|
|
else if(_whence==SEEK_END)pos=_filelengthi64(_fileno((FILE *)_stream));
|
|
else if(_whence==SEEK_SET)pos=0;
|
|
else return -1;
|
|
/*Check for errors or overflow.*/
|
|
if(pos<0||_offset<-pos||_offset>OP_INT64_MAX-pos)return -1;
|
|
pos+=_offset;
|
|
return fsetpos((FILE *)_stream,(fpos_t *)&pos);
|
|
#else
|
|
/*This function actually conforms to the SUSv2 and POSIX.1-2001, so we prefer
|
|
it except on Windows.*/
|
|
return fseeko((FILE *)_stream,(off_t)_offset,_whence);
|
|
#endif
|
|
}
|
|
|
|
static opus_int64 op_ftell(void *_stream){
|
|
#if defined(_WIN32)
|
|
/*_ftelli64() is not exposed until MSCVCRT80, and ftello()/ftello64() have
|
|
the same problems as fseeko()/fseeko64() in MingW.
|
|
See above for a more detailed explanation.*/
|
|
opus_int64 pos;
|
|
OP_ASSERT(sizeof(pos)==sizeof(fpos_t));
|
|
return fgetpos((FILE *)_stream,(fpos_t *)&pos)?-1:pos;
|
|
#else
|
|
/*This function actually conforms to the SUSv2 and POSIX.1-2001, so we prefer
|
|
it except on Windows.*/
|
|
return ftello((FILE *)_stream);
|
|
#endif
|
|
}
|
|
|
|
static const OpusFileCallbacks OP_FILE_CALLBACKS={
|
|
op_fread,
|
|
op_fseek,
|
|
op_ftell,
|
|
(op_close_func)fclose
|
|
};
|
|
|
|
#if defined(_WIN32)
|
|
# include <stddef.h>
|
|
# include <errno.h>
|
|
|
|
/*Windows doesn't accept UTF-8 by default, and we don't have a wchar_t API,
|
|
so if we just pass the path to fopen(), then there'd be no way for a user
|
|
of our API to open a Unicode filename.
|
|
Instead, we translate from UTF-8 to UTF-16 and use Windows' wchar_t API.
|
|
This makes this API more consistent with platforms where the character set
|
|
used by fopen is the same as used on disk, which is generally UTF-8, and
|
|
with our metadata API, which always uses UTF-8.*/
|
|
static wchar_t *op_utf8_to_utf16(const char *_src){
|
|
wchar_t *dst;
|
|
size_t len;
|
|
len=strlen(_src);
|
|
/*Worst-case output is 1 wide character per 1 input character.*/
|
|
dst=(wchar_t *)_ogg_malloc(sizeof(*dst)*(len+1));
|
|
if(dst!=NULL){
|
|
size_t si;
|
|
size_t di;
|
|
for(di=si=0;si<len;si++){
|
|
int c0;
|
|
c0=(unsigned char)_src[si];
|
|
if(!(c0&0x80)){
|
|
/*Start byte says this is a 1-byte sequence.*/
|
|
dst[di++]=(wchar_t)c0;
|
|
continue;
|
|
}
|
|
else{
|
|
int c1;
|
|
/*This is safe, because c0 was not 0 and _src is NUL-terminated.*/
|
|
c1=(unsigned char)_src[si+1];
|
|
if((c1&0xC0)==0x80){
|
|
/*Found at least one continuation byte.*/
|
|
if((c0&0xE0)==0xC0){
|
|
wchar_t w;
|
|
/*Start byte says this is a 2-byte sequence.*/
|
|
w=(c0&0x1F)<<6|c1&0x3F;
|
|
if(w>=0x80U){
|
|
/*This is a 2-byte sequence that is not overlong.*/
|
|
dst[di++]=w;
|
|
si++;
|
|
continue;
|
|
}
|
|
}
|
|
else{
|
|
int c2;
|
|
/*This is safe, because c1 was not 0 and _src is NUL-terminated.*/
|
|
c2=(unsigned char)_src[si+2];
|
|
if((c2&0xC0)==0x80){
|
|
/*Found at least two continuation bytes.*/
|
|
if((c0&0xF0)==0xE0){
|
|
wchar_t w;
|
|
/*Start byte says this is a 3-byte sequence.*/
|
|
w=(c0&0xF)<<12|(c1&0x3F)<<6|c2&0x3F;
|
|
if(w>=0x800U&&(w<0xD800||w>=0xE000)&&w<0xFFFE){
|
|
/*This is a 3-byte sequence that is not overlong, not a
|
|
UTF-16 surrogate pair value, and not a 'not a character'
|
|
value.*/
|
|
dst[di++]=w;
|
|
si+=2;
|
|
continue;
|
|
}
|
|
}
|
|
else{
|
|
int c3;
|
|
/*This is safe, because c2 was not 0 and _src is
|
|
NUL-terminated.*/
|
|
c3=(unsigned char)_src[si+3];
|
|
if((c3&0xC0)==0x80){
|
|
/*Found at least three continuation bytes.*/
|
|
if((c0&0xF8)==0xF0){
|
|
opus_uint32 w;
|
|
/*Start byte says this is a 4-byte sequence.*/
|
|
w=(c0&7)<<18|(c1&0x3F)<<12|(c2&0x3F)<<6&(c3&0x3F);
|
|
if(w>=0x10000U&&w<0x110000U){
|
|
/*This is a 4-byte sequence that is not overlong and not
|
|
greater than the largest valid Unicode code point.
|
|
Convert it to a surrogate pair.*/
|
|
w-=0x10000;
|
|
dst[di++]=(wchar_t)(0xD800+(w>>10));
|
|
dst[di++]=(wchar_t)(0xDC00+(w&0x3FF));
|
|
si+=3;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*If we got here, we encountered an illegal UTF-8 sequence.*/
|
|
_ogg_free(dst);
|
|
return NULL;
|
|
}
|
|
OP_ASSERT(di<=len);
|
|
dst[di]='\0';
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
#endif
|
|
|
|
void *op_fopen(OpusFileCallbacks *_cb,const char *_path,const char *_mode){
|
|
FILE *fp;
|
|
#if !defined(_WIN32)
|
|
fp=fopen(_path,_mode);
|
|
#else
|
|
fp=NULL;
|
|
if(_path==NULL||_mode==NULL)errno=EINVAL;
|
|
else{
|
|
wchar_t *wpath;
|
|
wchar_t *wmode;
|
|
wpath=op_utf8_to_utf16(_path);
|
|
wmode=op_utf8_to_utf16(_mode);
|
|
if(wmode==NULL)errno=EINVAL;
|
|
else if(wpath==NULL)errno=ENOENT;
|
|
else fp=_wfopen(wpath,wmode);
|
|
_ogg_free(wmode);
|
|
_ogg_free(wpath);
|
|
}
|
|
#endif
|
|
if(fp!=NULL)*_cb=*&OP_FILE_CALLBACKS;
|
|
return fp;
|
|
}
|
|
|
|
void *op_fdopen(OpusFileCallbacks *_cb,int _fd,const char *_mode){
|
|
FILE *fp;
|
|
fp=fdopen(_fd,_mode);
|
|
if(fp!=NULL)*_cb=*&OP_FILE_CALLBACKS;
|
|
return fp;
|
|
}
|
|
|
|
void *op_freopen(OpusFileCallbacks *_cb,const char *_path,const char *_mode,
|
|
void *_stream){
|
|
FILE *fp;
|
|
#if !defined(_WIN32)
|
|
fp=freopen(_path,_mode,(FILE *)_stream);
|
|
#else
|
|
fp=NULL;
|
|
if(_path==NULL||_mode==NULL)errno=EINVAL;
|
|
else{
|
|
wchar_t *wpath;
|
|
wchar_t *wmode;
|
|
wpath=op_utf8_to_utf16(_path);
|
|
wmode=op_utf8_to_utf16(_mode);
|
|
if(wmode==NULL)errno=EINVAL;
|
|
else if(wpath==NULL)errno=ENOENT;
|
|
else fp=_wfreopen(wpath,wmode,(FILE *)_stream);
|
|
_ogg_free(wmode);
|
|
_ogg_free(wpath);
|
|
}
|
|
#endif
|
|
if(fp!=NULL)*_cb=*&OP_FILE_CALLBACKS;
|
|
return fp;
|
|
}
|
|
|
|
static int op_mem_read(void *_stream,unsigned char *_ptr,int _buf_size){
|
|
OpusMemStream *stream;
|
|
ptrdiff_t size;
|
|
ptrdiff_t pos;
|
|
stream=(OpusMemStream *)_stream;
|
|
/*Check for empty read.*/
|
|
if(_buf_size<=0)return 0;
|
|
size=stream->size;
|
|
pos=stream->pos;
|
|
/*Check for EOF.*/
|
|
if(pos>=size)return 0;
|
|
/*Check for a short read.*/
|
|
_buf_size=(int)OP_MIN(size-pos,_buf_size);
|
|
memcpy(_ptr,stream->data+pos,_buf_size);
|
|
pos+=_buf_size;
|
|
stream->pos=pos;
|
|
return _buf_size;
|
|
}
|
|
|
|
static int op_mem_seek(void *_stream,opus_int64 _offset,int _whence){
|
|
OpusMemStream *stream;
|
|
ptrdiff_t pos;
|
|
stream=(OpusMemStream *)_stream;
|
|
pos=stream->pos;
|
|
OP_ASSERT(pos>=0);
|
|
switch(_whence){
|
|
case SEEK_SET:{
|
|
/*Check for overflow:*/
|
|
if(_offset<0||_offset>OP_MEM_DIFF_MAX)return -1;
|
|
pos=(ptrdiff_t)_offset;
|
|
}break;
|
|
case SEEK_CUR:{
|
|
/*Check for overflow:*/
|
|
if(_offset<-pos||_offset>OP_MEM_DIFF_MAX-pos)return -1;
|
|
pos=(ptrdiff_t)(pos+_offset);
|
|
}break;
|
|
case SEEK_END:{
|
|
ptrdiff_t size;
|
|
size=stream->size;
|
|
OP_ASSERT(size>=0);
|
|
/*Check for overflow:*/
|
|
if(_offset>size||_offset<size-OP_MEM_DIFF_MAX)return -1;
|
|
pos=(ptrdiff_t)(size-_offset);
|
|
}break;
|
|
default:return -1;
|
|
}
|
|
stream->pos=pos;
|
|
return 0;
|
|
}
|
|
|
|
static opus_int64 op_mem_tell(void *_stream){
|
|
OpusMemStream *stream;
|
|
stream=(OpusMemStream *)_stream;
|
|
return (ogg_int64_t)stream->pos;
|
|
}
|
|
|
|
static int op_mem_close(void *_stream){
|
|
_ogg_free(_stream);
|
|
return 0;
|
|
}
|
|
|
|
static const OpusFileCallbacks OP_MEM_CALLBACKS={
|
|
op_mem_read,
|
|
op_mem_seek,
|
|
op_mem_tell,
|
|
op_mem_close
|
|
};
|
|
|
|
void *op_mem_stream_create(OpusFileCallbacks *_cb,
|
|
const unsigned char *_data,size_t _size){
|
|
OpusMemStream *stream;
|
|
if(_size>OP_MEM_SIZE_MAX)return NULL;
|
|
stream=(OpusMemStream *)_ogg_malloc(sizeof(*stream));
|
|
if(stream!=NULL){
|
|
*_cb=*&OP_MEM_CALLBACKS;
|
|
stream->data=_data;
|
|
stream->size=_size;
|
|
stream->pos=0;
|
|
}
|
|
return stream;
|
|
}
|