1435 lines
35 KiB
C#
1435 lines
35 KiB
C#
|
/* csvorbis
|
||
|
* Copyright (C) 2000 ymnk, JCraft,Inc.
|
||
|
*
|
||
|
* Written by: 2000 ymnk<ymnk@jcraft.com>
|
||
|
* Ported to C# from JOrbis by: Mark Crichton <crichton@gimp.org>
|
||
|
*
|
||
|
* Thanks go to the JOrbis team, for licencing the code under the
|
||
|
* LGPL, making my job a lot easier.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU Library General Public License
|
||
|
* as published by the Free Software Foundation; either version 2 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 Library General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Library General Public
|
||
|
* License along with this program; if not, write to the Free Software
|
||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||
|
*/
|
||
|
|
||
|
|
||
|
using System;
|
||
|
using System.IO;
|
||
|
using csogg;
|
||
|
|
||
|
namespace csvorbis
|
||
|
{
|
||
|
public class VorbisFile
|
||
|
{
|
||
|
static int CHUNKSIZE=8500;
|
||
|
static int SEEK_SET=0;
|
||
|
static int SEEK_CUR=1;
|
||
|
static int SEEK_END=2;
|
||
|
|
||
|
static int OV_FALSE=-1;
|
||
|
static int OV_EOF=-2;
|
||
|
static int OV_HOLE=-3;
|
||
|
|
||
|
static int OV_EREAD=-128;
|
||
|
static int OV_EFAULT=-129;
|
||
|
static int OV_EIMPL=-130;
|
||
|
static int OV_EINVAL=-131;
|
||
|
static int OV_ENOTVORBIS=-132;
|
||
|
static int OV_EBADHEADER=-133;
|
||
|
static int OV_EVERSION=-134;
|
||
|
static int OV_ENOTAUDIO=-135;
|
||
|
static int OV_EBADPACKET=-136;
|
||
|
static int OV_EBADLINK=-137;
|
||
|
static int OV_ENOSEEK=-138;
|
||
|
|
||
|
FileStream datasource;
|
||
|
bool skable=false;
|
||
|
long offset;
|
||
|
long end;
|
||
|
|
||
|
SyncState oy=new SyncState();
|
||
|
|
||
|
int links;
|
||
|
long[] offsets;
|
||
|
long[] dataoffsets;
|
||
|
int[] serialnos;
|
||
|
long[] pcmlengths;
|
||
|
Info[] vi;
|
||
|
Comment[] vc;
|
||
|
|
||
|
// Decoding working state local storage
|
||
|
long pcm_offset;
|
||
|
bool decode_ready=false;
|
||
|
int current_serialno;
|
||
|
int current_link;
|
||
|
|
||
|
float bittrack;
|
||
|
float samptrack;
|
||
|
|
||
|
StreamState os;
|
||
|
DspState vd;
|
||
|
Block vb;
|
||
|
|
||
|
|
||
|
private VorbisFile()
|
||
|
{
|
||
|
os=new StreamState(); // take physical pages, weld into a logical
|
||
|
// stream of packets
|
||
|
vd=new DspState(); // central working state for
|
||
|
// the packet->PCM decoder
|
||
|
vb=new Block(vd); // local working space for packet->PCM decode
|
||
|
}
|
||
|
|
||
|
public VorbisFile(String file) : this()
|
||
|
{
|
||
|
FileStream inst=null;
|
||
|
|
||
|
try{ inst=new FileStream(file, FileMode.Open, FileAccess.Read);}
|
||
|
catch(Exception e)
|
||
|
{
|
||
|
throw new csorbisException("VorbisFile: "+e.Message);
|
||
|
}
|
||
|
int ret=open(inst, null, 0);
|
||
|
if(ret==-1)
|
||
|
{
|
||
|
throw new csorbisException("VorbisFile: open return -1");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public VorbisFile(FileStream inst, byte[] initial, int ibytes) : this()
|
||
|
{
|
||
|
int ret=open(inst, initial, ibytes);
|
||
|
}
|
||
|
|
||
|
private int get_data()
|
||
|
{
|
||
|
int index=oy.buffer(CHUNKSIZE);
|
||
|
byte[] buffer=oy.data;
|
||
|
// int bytes=callbacks.read_func(buffer, index, 1, CHUNKSIZE, datasource);
|
||
|
int bytes=0;
|
||
|
try
|
||
|
{
|
||
|
bytes=datasource.Read(buffer, index, CHUNKSIZE);
|
||
|
}
|
||
|
catch(Exception e)
|
||
|
{
|
||
|
Console.Error.WriteLine(e.Message);
|
||
|
return OV_EREAD;
|
||
|
}
|
||
|
oy.wrote(bytes);
|
||
|
if(bytes==-1)
|
||
|
{
|
||
|
bytes=0;
|
||
|
}
|
||
|
return bytes;
|
||
|
}
|
||
|
|
||
|
private void seek_helper(long offst)
|
||
|
{
|
||
|
//callbacks.seek_func(datasource, offst, SEEK_SET);
|
||
|
fseek(datasource, offst, SEEK_SET);
|
||
|
this.offset=offst;
|
||
|
oy.reset();
|
||
|
}
|
||
|
|
||
|
private int get_next_page(Page page, long boundary)
|
||
|
{
|
||
|
if(boundary>0) boundary+=offset;
|
||
|
while(true)
|
||
|
{
|
||
|
int more;
|
||
|
if(boundary>0 && offset>=boundary)return OV_FALSE;
|
||
|
more=oy.pageseek(page);
|
||
|
if(more<0){offset-=more;}
|
||
|
else
|
||
|
{
|
||
|
if(more==0)
|
||
|
{
|
||
|
if(boundary==0)return OV_FALSE;
|
||
|
// if(get_data()<=0)return -1;
|
||
|
int ret=get_data();
|
||
|
if(ret==0) return OV_EOF;
|
||
|
if(ret<0) return OV_EREAD;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int ret=(int)offset; //!!!
|
||
|
offset+=more;
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private int get_prev_page(Page page)
|
||
|
{
|
||
|
long begin=offset; //!!!
|
||
|
int ret;
|
||
|
int offst=-1;
|
||
|
while(offst==-1)
|
||
|
{
|
||
|
begin-=CHUNKSIZE;
|
||
|
if(begin<0)
|
||
|
begin=0;
|
||
|
seek_helper(begin);
|
||
|
while(offset<begin+CHUNKSIZE)
|
||
|
{
|
||
|
ret=get_next_page(page, begin+CHUNKSIZE-offset);
|
||
|
if(ret==OV_EREAD){ return OV_EREAD; }
|
||
|
if(ret<0){ break; }
|
||
|
else{ offst=ret; }
|
||
|
}
|
||
|
}
|
||
|
seek_helper(offst); //!!!
|
||
|
ret=get_next_page(page, CHUNKSIZE);
|
||
|
if(ret<0)
|
||
|
{
|
||
|
//System.err.println("Missed page fencepost at end of logical bitstream Exiting");
|
||
|
//System.exit(1);
|
||
|
return OV_EFAULT;
|
||
|
}
|
||
|
return offst;
|
||
|
}
|
||
|
|
||
|
int bisect_forward_serialno(long begin, long searched, long end, int currentno, int m)
|
||
|
{
|
||
|
long endsearched=end;
|
||
|
long next=end;
|
||
|
Page page=new Page();
|
||
|
int ret;
|
||
|
|
||
|
while(searched<endsearched)
|
||
|
{
|
||
|
long bisect;
|
||
|
if(endsearched-searched<CHUNKSIZE)
|
||
|
{
|
||
|
bisect=searched;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bisect=(searched+endsearched)/2;
|
||
|
}
|
||
|
|
||
|
seek_helper(bisect);
|
||
|
ret=get_next_page(page, -1);
|
||
|
if(ret==OV_EREAD) return OV_EREAD;
|
||
|
if(ret<0 || page.serialno()!=currentno)
|
||
|
{
|
||
|
endsearched=bisect;
|
||
|
if(ret>=0)next=ret;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
searched=ret+page.header_len+page.body_len;
|
||
|
}
|
||
|
}
|
||
|
seek_helper(next);
|
||
|
ret=get_next_page(page, -1);
|
||
|
if(ret==OV_EREAD) return OV_EREAD;
|
||
|
|
||
|
if(searched>=end || ret==-1)
|
||
|
{
|
||
|
links=m+1;
|
||
|
offsets=new long[m+2];
|
||
|
offsets[m+1]=searched;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ret=bisect_forward_serialno(next, offset, end, page.serialno(), m+1);
|
||
|
if(ret==OV_EREAD)return OV_EREAD;
|
||
|
}
|
||
|
offsets[m]=begin;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// uses the local ogg_stream storage in vf; this is important for
|
||
|
// non-streaming input sources
|
||
|
int fetch_headers(Info vi, Comment vc, int[] serialno, Page og_ptr)
|
||
|
{
|
||
|
//System.err.println("fetch_headers");
|
||
|
Page og=new Page();
|
||
|
Packet op=new Packet();
|
||
|
int ret;
|
||
|
|
||
|
if(og_ptr==null)
|
||
|
{
|
||
|
ret=get_next_page(og, CHUNKSIZE);
|
||
|
if(ret==OV_EREAD)return OV_EREAD;
|
||
|
if(ret<0) return OV_ENOTVORBIS;
|
||
|
og_ptr=og;
|
||
|
}
|
||
|
|
||
|
if(serialno!=null)serialno[0]=og_ptr.serialno();
|
||
|
|
||
|
os.init(og_ptr.serialno());
|
||
|
|
||
|
// extract the initial header from the first page and verify that the
|
||
|
// Ogg bitstream is in fact Vorbis data
|
||
|
|
||
|
vi.init();
|
||
|
vc.init();
|
||
|
|
||
|
int i=0;
|
||
|
while(i<3)
|
||
|
{
|
||
|
os.pagein(og_ptr);
|
||
|
while(i<3)
|
||
|
{
|
||
|
int result=os.packetout(op);
|
||
|
if(result==0)break;
|
||
|
if(result==-1)
|
||
|
{
|
||
|
Console.Error.WriteLine("Corrupt header in logical bitstream.");
|
||
|
//goto bail_header;
|
||
|
vi.clear();
|
||
|
vc.clear();
|
||
|
os.clear();
|
||
|
return -1;
|
||
|
}
|
||
|
if(vi.synthesis_headerin(vc, op)!=0)
|
||
|
{
|
||
|
Console.Error.WriteLine("Illegal header in logical bitstream.");
|
||
|
//goto bail_header;
|
||
|
vi.clear();
|
||
|
vc.clear();
|
||
|
os.clear();
|
||
|
return -1;
|
||
|
}
|
||
|
i++;
|
||
|
}
|
||
|
if(i<3)
|
||
|
if(get_next_page(og_ptr, 1)<0)
|
||
|
{
|
||
|
Console.Error.WriteLine("Missing header in logical bitstream.");
|
||
|
//goto bail_header;
|
||
|
vi.clear();
|
||
|
vc.clear();
|
||
|
os.clear();
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// last step of the OggVorbis_File initialization; get all the
|
||
|
// vorbis_info structs and PCM positions. Only called by the seekable
|
||
|
// initialization (local stream storage is hacked slightly; pay
|
||
|
// attention to how that's done)
|
||
|
void prefetch_all_headers(Info first_i,Comment first_c, int dataoffset)
|
||
|
{
|
||
|
Page og=new Page();
|
||
|
int ret;
|
||
|
|
||
|
vi=new Info[links];
|
||
|
vc=new Comment[links];
|
||
|
dataoffsets=new long[links];
|
||
|
pcmlengths=new long[links];
|
||
|
serialnos=new int[links];
|
||
|
|
||
|
for(int i=0;i<links;i++)
|
||
|
{
|
||
|
if(first_i!=null && first_c!=null && i==0)
|
||
|
{
|
||
|
// we already grabbed the initial header earlier. This just
|
||
|
// saves the waste of grabbing it again
|
||
|
// !!!!!!!!!!!!!
|
||
|
vi[i]=first_i;
|
||
|
//memcpy(vf->vi+i,first_i,sizeof(vorbis_info));
|
||
|
vc[i]=first_c;
|
||
|
//memcpy(vf->vc+i,first_c,sizeof(vorbis_comment));
|
||
|
dataoffsets[i]=dataoffset;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// seek to the location of the initial header
|
||
|
seek_helper(offsets[i]); //!!!
|
||
|
if(fetch_headers(vi[i], vc[i], null, null)==-1)
|
||
|
{
|
||
|
Console.Error.WriteLine("Error opening logical bitstream #"+(i+1)+"\n");
|
||
|
dataoffsets[i]=-1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dataoffsets[i]=offset;
|
||
|
os.clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// get the serial number and PCM length of this link. To do this,
|
||
|
// get the last page of the stream
|
||
|
long end=offsets[i+1]; //!!!
|
||
|
seek_helper(end);
|
||
|
|
||
|
while(true)
|
||
|
{
|
||
|
ret=get_prev_page(og);
|
||
|
if(ret==-1)
|
||
|
{
|
||
|
// this should not be possible
|
||
|
Console.Error.WriteLine("Could not find last page of logical "+
|
||
|
"bitstream #"+(i)+"\n");
|
||
|
vi[i].clear();
|
||
|
vc[i].clear();
|
||
|
break;
|
||
|
}
|
||
|
if(og.granulepos()!=-1)
|
||
|
{
|
||
|
serialnos[i]=og.serialno();
|
||
|
pcmlengths[i]=og.granulepos();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int make_decode_ready()
|
||
|
{
|
||
|
#if NET_2_1
|
||
|
if(decode_ready) throw new Exception ("make_decode_ready: 1");
|
||
|
#else
|
||
|
if(decode_ready)Environment.Exit(1);
|
||
|
#endif
|
||
|
vd.synthesis_init(vi[0]);
|
||
|
vb.init(vd);
|
||
|
decode_ready=true;
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
int open_seekable()
|
||
|
{
|
||
|
Info initial_i=new Info();
|
||
|
Comment initial_c=new Comment();
|
||
|
int serialno;
|
||
|
long end;
|
||
|
int ret;
|
||
|
int dataoffset;
|
||
|
Page og=new Page();
|
||
|
// is this even vorbis...?
|
||
|
int[] foo=new int[1];
|
||
|
ret=fetch_headers(initial_i, initial_c, foo, null);
|
||
|
serialno=foo[0];
|
||
|
dataoffset=(int)offset; //!!
|
||
|
os.clear();
|
||
|
if(ret==-1)return(-1);
|
||
|
// we can seek, so set out learning all about this file
|
||
|
skable=true;
|
||
|
//(callbacks.seek_func)(datasource, 0, SEEK_END);
|
||
|
fseek(datasource, 0, SEEK_END);
|
||
|
//offset=end=(callbacks.tell_func)(datasource);
|
||
|
offset=ftell(datasource);
|
||
|
end=offset;
|
||
|
// We get the offset for the last page of the physical bitstream.
|
||
|
// Most OggVorbis files will contain a single logical bitstream
|
||
|
end=get_prev_page(og);
|
||
|
// moer than one logical bitstream?
|
||
|
if(og.serialno()!=serialno)
|
||
|
{
|
||
|
// Chained bitstream. Bisect-search each logical bitstream
|
||
|
// section. Do so based on serial number only
|
||
|
if(bisect_forward_serialno(0,0,end+1,serialno,0)<0)
|
||
|
{
|
||
|
clear();
|
||
|
return OV_EREAD;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Only one logical bitstream
|
||
|
if(bisect_forward_serialno(0,end,end+1,serialno,0)<0)
|
||
|
{
|
||
|
clear();
|
||
|
return OV_EREAD;
|
||
|
}
|
||
|
}
|
||
|
prefetch_all_headers(initial_i, initial_c, dataoffset);
|
||
|
return(raw_seek(0));
|
||
|
}
|
||
|
|
||
|
int open_nonseekable()
|
||
|
{
|
||
|
//System.err.println("open_nonseekable");
|
||
|
// we cannot seek. Set up a 'single' (current) logical bitstream entry
|
||
|
links=1;
|
||
|
vi=new Info[links]; vi[0]=new Info(); // ??
|
||
|
vc=new Comment[links]; vc[0]=new Comment(); // ?? bug?
|
||
|
|
||
|
// Try to fetch the headers, maintaining all the storage
|
||
|
int[]foo=new int[1];
|
||
|
if(fetch_headers(vi[0], vc[0], foo, null)==-1)return(-1);
|
||
|
current_serialno=foo[0];
|
||
|
make_decode_ready();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// clear out the current logical bitstream decoder
|
||
|
void decode_clear()
|
||
|
{
|
||
|
os.clear();
|
||
|
vd.clear();
|
||
|
vb.clear();
|
||
|
decode_ready=false;
|
||
|
bittrack=0.0f;
|
||
|
samptrack=0.0f;
|
||
|
}
|
||
|
|
||
|
// fetch and process a packet. Handles the case where we're at a
|
||
|
// bitstream boundary and dumps the decoding machine. If the decoding
|
||
|
// machine is unloaded, it loads it. It also keeps pcm_offset up to
|
||
|
// date (seek and read both use this. seek uses a special hack with
|
||
|
// readp).
|
||
|
//
|
||
|
// return: -1) hole in the data (lost packet)
|
||
|
// 0) need more date (only if readp==0)/eof
|
||
|
// 1) got a packet
|
||
|
|
||
|
int process_packet(int readp)
|
||
|
{
|
||
|
Page og=new Page();
|
||
|
|
||
|
// handle one packet. Try to fetch it from current stream state
|
||
|
// extract packets from page
|
||
|
while(true)
|
||
|
{
|
||
|
// process a packet if we can. If the machine isn't loaded,
|
||
|
// neither is a page
|
||
|
if(decode_ready)
|
||
|
{
|
||
|
Packet op=new Packet();
|
||
|
int result=os.packetout(op);
|
||
|
long granulepos;
|
||
|
// if(result==-1)return(-1); // hole in the data. For now, swallow
|
||
|
// and go. We'll need to add a real
|
||
|
// error code in a bit.
|
||
|
if(result>0)
|
||
|
{
|
||
|
// got a packet. process it
|
||
|
granulepos=op.granulepos;
|
||
|
if(vb.synthesis(op)==0)
|
||
|
{ // lazy check for lazy
|
||
|
// header handling. The
|
||
|
// header packets aren't
|
||
|
// audio, so if/when we
|
||
|
// submit them,
|
||
|
// vorbis_synthesis will
|
||
|
// reject them
|
||
|
// suck in the synthesis data and track bitrate
|
||
|
{
|
||
|
int oldsamples=vd.synthesis_pcmout(null, null);
|
||
|
vd.synthesis_blockin(vb);
|
||
|
samptrack+=vd.synthesis_pcmout(null, null)-oldsamples;
|
||
|
bittrack+=op.bytes*8;
|
||
|
}
|
||
|
|
||
|
// update the pcm offset.
|
||
|
if(granulepos!=-1 && op.e_o_s==0)
|
||
|
{
|
||
|
int link=(skable?current_link:0);
|
||
|
int samples;
|
||
|
// this packet has a pcm_offset on it (the last packet
|
||
|
// completed on a page carries the offset) After processing
|
||
|
// (above), we know the pcm position of the *last* sample
|
||
|
// ready to be returned. Find the offset of the *first*
|
||
|
//
|
||
|
// As an aside, this trick is inaccurate if we begin
|
||
|
// reading anew right at the last page; the end-of-stream
|
||
|
// granulepos declares the last frame in the stream, and the
|
||
|
// last packet of the last page may be a partial frame.
|
||
|
// So, we need a previous granulepos from an in-sequence page
|
||
|
// to have a reference point. Thus the !op.e_o_s clause above
|
||
|
|
||
|
samples=vd.synthesis_pcmout(null, null);
|
||
|
granulepos-=samples;
|
||
|
for(int i=0;i<link;i++)
|
||
|
{
|
||
|
granulepos+=pcmlengths[i];
|
||
|
}
|
||
|
pcm_offset=granulepos;
|
||
|
}
|
||
|
return(1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(readp==0)return(0);
|
||
|
if(get_next_page(og,-1)<0)return(0); // eof. leave unitialized
|
||
|
|
||
|
// bitrate tracking; add the header's bytes here, the body bytes
|
||
|
// are done by packet above
|
||
|
bittrack+=og.header_len*8;
|
||
|
|
||
|
// has our decoding just traversed a bitstream boundary?
|
||
|
if(decode_ready)
|
||
|
{
|
||
|
if(current_serialno!=og.serialno())
|
||
|
{
|
||
|
decode_clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Do we need to load a new machine before submitting the page?
|
||
|
// This is different in the seekable and non-seekable cases.
|
||
|
//
|
||
|
// In the seekable case, we already have all the header
|
||
|
// information loaded and cached; we just initialize the machine
|
||
|
// with it and continue on our merry way.
|
||
|
//
|
||
|
// In the non-seekable (streaming) case, we'll only be at a
|
||
|
// boundary if we just left the previous logical bitstream and
|
||
|
// we're now nominally at the header of the next bitstream
|
||
|
|
||
|
if(!decode_ready)
|
||
|
{
|
||
|
int i;
|
||
|
if(skable)
|
||
|
{
|
||
|
current_serialno=og.serialno();
|
||
|
|
||
|
// match the serialno to bitstream section. We use this rather than
|
||
|
// offset positions to avoid problems near logical bitstream
|
||
|
// boundaries
|
||
|
for(i=0;i<links;i++)
|
||
|
{
|
||
|
if(serialnos[i]==current_serialno)break;
|
||
|
}
|
||
|
if(i==links)return(-1); // sign of a bogus stream. error out,
|
||
|
// leave machine uninitialized
|
||
|
current_link=i;
|
||
|
|
||
|
os.init(current_serialno);
|
||
|
os.reset();
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// we're streaming
|
||
|
// fetch the three header packets, build the info struct
|
||
|
int[] foo = new int[1];
|
||
|
int ret=fetch_headers(vi[0], vc[0], foo, og);
|
||
|
current_serialno=foo[0];
|
||
|
if(ret!=0)return ret;
|
||
|
current_link++;
|
||
|
i=0;
|
||
|
}
|
||
|
make_decode_ready();
|
||
|
}
|
||
|
os.pagein(og);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//The helpers are over; it's all toplevel interface from here on out
|
||
|
// clear out the OggVorbis_File struct
|
||
|
int clear()
|
||
|
{
|
||
|
vb.clear();
|
||
|
vd.clear();
|
||
|
os.clear();
|
||
|
|
||
|
if(vi!=null && links!=0)
|
||
|
{
|
||
|
for(int i=0;i<links;i++)
|
||
|
{
|
||
|
vi[i].clear();
|
||
|
vc[i].clear();
|
||
|
}
|
||
|
vi=null;
|
||
|
vc=null;
|
||
|
}
|
||
|
if(dataoffsets!=null)dataoffsets=null;
|
||
|
if(pcmlengths!=null)pcmlengths=null;
|
||
|
if(serialnos!=null)serialnos=null;
|
||
|
if(offsets!=null)offsets=null;
|
||
|
oy.clear();
|
||
|
//if(datasource!=null)(vf->callbacks.close_func)(vf->datasource);
|
||
|
//memset(vf,0,sizeof(OggVorbis_File));
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
static int fseek(FileStream fis,
|
||
|
//int64_t off,
|
||
|
long off,
|
||
|
int whence)
|
||
|
{
|
||
|
if(fis.CanSeek == true)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if(whence==SEEK_SET)
|
||
|
{
|
||
|
fis.Seek(off, 0);
|
||
|
}
|
||
|
else if(whence==SEEK_END)
|
||
|
{
|
||
|
fis.Seek(fis.Length - off, 0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Console.Error.WriteLine("seek: "+whence+" is not supported");
|
||
|
}
|
||
|
}
|
||
|
catch(Exception e)
|
||
|
{
|
||
|
Console.Error.WriteLine(e.Message);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
try
|
||
|
{
|
||
|
if(whence==0){ fis.Seek(0, 0); }
|
||
|
fis.Seek(off, 0);
|
||
|
}
|
||
|
catch(Exception e)
|
||
|
{
|
||
|
Console.Error.WriteLine(e.Message);
|
||
|
return -1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static long ftell(FileStream fis)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if(fis.CanSeek == true)
|
||
|
{
|
||
|
return (fis.Position);
|
||
|
}
|
||
|
}
|
||
|
catch(Exception e)
|
||
|
{
|
||
|
Console.Error.WriteLine(e.Message);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// inspects the OggVorbis file and finds/documents all the logical
|
||
|
// bitstreams contained in it. Tries to be tolerant of logical
|
||
|
// bitstream sections that are truncated/woogie.
|
||
|
//
|
||
|
// return: -1) error
|
||
|
// 0) OK
|
||
|
|
||
|
int open(FileStream iis, byte[] initial, int ibytes)
|
||
|
{
|
||
|
return open_callbacks(iis, initial, ibytes);
|
||
|
}
|
||
|
|
||
|
int open_callbacks(FileStream iis, byte[] initial, int ibytes)
|
||
|
{
|
||
|
int ret;
|
||
|
datasource=iis;
|
||
|
//callbacks = _callbacks;
|
||
|
// init the framing state
|
||
|
oy.init();
|
||
|
|
||
|
// perhaps some data was previously read into a buffer for testing
|
||
|
// against other stream types. Allow initialization from this
|
||
|
// previously read data (as we may be reading from a non-seekable
|
||
|
// stream)
|
||
|
if(initial!=null)
|
||
|
{
|
||
|
int index=oy.buffer(ibytes);
|
||
|
Array.Copy(initial, 0, oy.data, index, ibytes);
|
||
|
oy.wrote(ibytes);
|
||
|
}
|
||
|
// can we seek? Stevens suggests the seek test was portable
|
||
|
if(iis.CanSeek == true){ ret=open_seekable(); }
|
||
|
else{ ret=open_nonseekable(); }
|
||
|
if(ret!=0)
|
||
|
{
|
||
|
datasource=null;
|
||
|
clear();
|
||
|
}
|
||
|
return(ret);
|
||
|
}
|
||
|
|
||
|
// How many logical bitstreams in this physical bitstream?
|
||
|
public int streams()
|
||
|
{
|
||
|
return links;
|
||
|
}
|
||
|
|
||
|
// Is the FILE * associated with vf seekable?
|
||
|
public bool seekable()
|
||
|
{
|
||
|
return skable;
|
||
|
}
|
||
|
|
||
|
// returns the bitrate for a given logical bitstream or the entire
|
||
|
// physical bitstream. If the file is open for random access, it will
|
||
|
// find the *actual* average bitrate. If the file is streaming, it
|
||
|
// returns the nominal bitrate (if set) else the average of the
|
||
|
// upper/lower bounds (if set) else -1 (unset).
|
||
|
//
|
||
|
// If you want the actual bitrate field settings, get them from the
|
||
|
// vorbis_info structs
|
||
|
|
||
|
public int bitrate(int i)
|
||
|
{
|
||
|
if(i>=links)return(-1);
|
||
|
if(!skable && i!=0)return(bitrate(0));
|
||
|
if(i<0)
|
||
|
{
|
||
|
long bits=0;
|
||
|
for(int j=0;j<links;j++)
|
||
|
{
|
||
|
bits+=(offsets[j+1]-dataoffsets[j])*8;
|
||
|
}
|
||
|
return((int)Math.Round(bits/time_total(-1)));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(skable)
|
||
|
{
|
||
|
// return the actual bitrate
|
||
|
return((int)Math.Round((offsets[i+1]-dataoffsets[i])*8/time_total(i)));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// return nominal if set
|
||
|
if(vi[i].bitrate_nominal>0)
|
||
|
{
|
||
|
return vi[i].bitrate_nominal;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(vi[i].bitrate_upper>0)
|
||
|
{
|
||
|
if(vi[i].bitrate_lower>0)
|
||
|
{
|
||
|
return (vi[i].bitrate_upper+vi[i].bitrate_lower)/2;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return vi[i].bitrate_upper;
|
||
|
}
|
||
|
}
|
||
|
return(-1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// returns the actual bitrate since last call. returns -1 if no
|
||
|
// additional data to offer since last call (or at beginning of stream)
|
||
|
public int bitrate_instant()
|
||
|
{
|
||
|
int _link=(skable?current_link:0);
|
||
|
if(samptrack==0)return(-1);
|
||
|
int ret=(int)(bittrack/samptrack*vi[_link].rate+.5);
|
||
|
bittrack=0.0f;
|
||
|
samptrack=0.0f;
|
||
|
return(ret);
|
||
|
}
|
||
|
|
||
|
public int serialnumber(int i)
|
||
|
{
|
||
|
if(i>=links)return(-1);
|
||
|
if(!skable && i>=0)return(serialnumber(-1));
|
||
|
if(i<0)
|
||
|
{
|
||
|
return(current_serialno);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return(serialnos[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// returns: total raw (compressed) length of content if i==-1
|
||
|
// raw (compressed) length of that logical bitstream for i==0 to n
|
||
|
// -1 if the stream is not seekable (we can't know the length)
|
||
|
|
||
|
public long raw_total(int i)
|
||
|
{
|
||
|
if(!skable || i>=links)return(-1);
|
||
|
if(i<0)
|
||
|
{
|
||
|
long acc=0; // bug?
|
||
|
for(int j=0;j<links;j++)
|
||
|
{
|
||
|
acc+=raw_total(j);
|
||
|
}
|
||
|
return(acc);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return(offsets[i+1]-offsets[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// returns: total PCM length (samples) of content if i==-1
|
||
|
// PCM length (samples) of that logical bitstream for i==0 to n
|
||
|
// -1 if the stream is not seekable (we can't know the length)
|
||
|
public long pcm_total(int i)
|
||
|
{
|
||
|
if(!skable || i>=links)return(-1);
|
||
|
if(i<0)
|
||
|
{
|
||
|
long acc=0;
|
||
|
for(int j=0;j<links;j++)
|
||
|
{
|
||
|
acc+=pcm_total(j);
|
||
|
}
|
||
|
return(acc);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return(pcmlengths[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// returns: total seconds of content if i==-1
|
||
|
// seconds in that logical bitstream for i==0 to n
|
||
|
// -1 if the stream is not seekable (we can't know the length)
|
||
|
public float time_total(int i)
|
||
|
{
|
||
|
if(!skable || i>=links)return(-1);
|
||
|
if(i<0)
|
||
|
{
|
||
|
float acc=0;
|
||
|
for(int j=0;j<links;j++)
|
||
|
{
|
||
|
acc+=time_total(j);
|
||
|
}
|
||
|
return(acc);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return((float)(pcmlengths[i])/vi[i].rate);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// seek to an offset relative to the *compressed* data. This also
|
||
|
// immediately sucks in and decodes pages to update the PCM cursor. It
|
||
|
// will cross a logical bitstream boundary, but only if it can't get
|
||
|
// any packets out of the tail of the bitstream we seek to (so no
|
||
|
// surprises).
|
||
|
//
|
||
|
// returns zero on success, nonzero on failure
|
||
|
|
||
|
public int raw_seek(int pos)
|
||
|
{
|
||
|
if(!skable)return(-1); // don't dump machine if we can't seek
|
||
|
if(pos<0 || pos>offsets[links])
|
||
|
{
|
||
|
//goto seek_error;
|
||
|
pcm_offset=-1;
|
||
|
decode_clear();
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// clear out decoding machine state
|
||
|
pcm_offset=-1;
|
||
|
decode_clear();
|
||
|
|
||
|
// seek
|
||
|
seek_helper(pos);
|
||
|
|
||
|
// we need to make sure the pcm_offset is set. We use the
|
||
|
// _fetch_packet helper to process one packet with readp set, then
|
||
|
// call it until it returns '0' with readp not set (the last packet
|
||
|
// from a page has the 'granulepos' field set, and that's how the
|
||
|
// helper updates the offset
|
||
|
|
||
|
switch(process_packet(1))
|
||
|
{
|
||
|
case 0:
|
||
|
// oh, eof. There are no packets remaining. Set the pcm offset to
|
||
|
// the end of file
|
||
|
pcm_offset=pcm_total(-1);
|
||
|
return(0);
|
||
|
case -1:
|
||
|
// error! missing data or invalid bitstream structure
|
||
|
//goto seek_error;
|
||
|
pcm_offset=-1;
|
||
|
decode_clear();
|
||
|
return -1;
|
||
|
default:
|
||
|
// all OK
|
||
|
break;
|
||
|
}
|
||
|
while(true)
|
||
|
{
|
||
|
switch(process_packet(0))
|
||
|
{
|
||
|
case 0:
|
||
|
// the offset is set. If it's a bogus bitstream with no offset
|
||
|
// information, it's not but that's not our fault. We still run
|
||
|
// gracefully, we're just missing the offset
|
||
|
return(0);
|
||
|
case -1:
|
||
|
// error! missing data or invalid bitstream structure
|
||
|
//goto seek_error;
|
||
|
pcm_offset=-1;
|
||
|
decode_clear();
|
||
|
return -1;
|
||
|
default:
|
||
|
// continue processing packets
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// seek_error:
|
||
|
// dump the machine so we're in a known state
|
||
|
//pcm_offset=-1;
|
||
|
//decode_clear();
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// seek to a sample offset relative to the decompressed pcm stream
|
||
|
// returns zero on success, nonzero on failure
|
||
|
|
||
|
public int pcm_seek(long pos)
|
||
|
{
|
||
|
int link=-1;
|
||
|
long total=pcm_total(-1);
|
||
|
|
||
|
if(!skable)return(-1); // don't dump machine if we can't seek
|
||
|
if(pos<0 || pos>total)
|
||
|
{
|
||
|
//goto seek_error;
|
||
|
pcm_offset=-1;
|
||
|
decode_clear();
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// which bitstream section does this pcm offset occur in?
|
||
|
for(link=links-1;link>=0;link--)
|
||
|
{
|
||
|
total-=pcmlengths[link];
|
||
|
if(pos>=total)break;
|
||
|
}
|
||
|
|
||
|
// search within the logical bitstream for the page with the highest
|
||
|
// pcm_pos preceeding (or equal to) pos. There is a danger here;
|
||
|
// missing pages or incorrect frame number information in the
|
||
|
// bitstream could make our task impossible. Account for that (it
|
||
|
// would be an error condition)
|
||
|
{
|
||
|
long target=pos-total;
|
||
|
long end=offsets[link+1];
|
||
|
long begin=offsets[link];
|
||
|
int best=(int)begin;
|
||
|
|
||
|
Page og=new Page();
|
||
|
while(begin<end)
|
||
|
{
|
||
|
long bisect;
|
||
|
int ret;
|
||
|
|
||
|
if(end-begin<CHUNKSIZE)
|
||
|
{
|
||
|
bisect=begin;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bisect=(end+begin)/2;
|
||
|
}
|
||
|
|
||
|
seek_helper(bisect);
|
||
|
ret=get_next_page(og,end-bisect);
|
||
|
|
||
|
if(ret==-1)
|
||
|
{
|
||
|
end=bisect;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
long granulepos=og.granulepos();
|
||
|
if(granulepos<target)
|
||
|
{
|
||
|
best=ret; // raw offset of packet with granulepos
|
||
|
begin=offset; // raw offset of next packet
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
end=bisect;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// found our page. seek to it (call raw_seek).
|
||
|
if(raw_seek(best)!=0)
|
||
|
{
|
||
|
//goto seek_error;
|
||
|
pcm_offset=-1;
|
||
|
decode_clear();
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// verify result
|
||
|
if(pcm_offset>=pos)
|
||
|
{
|
||
|
//goto seek_error;
|
||
|
pcm_offset=-1;
|
||
|
decode_clear();
|
||
|
return -1;
|
||
|
}
|
||
|
if(pos>pcm_total(-1))
|
||
|
{
|
||
|
//goto seek_error;
|
||
|
pcm_offset=-1;
|
||
|
decode_clear();
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// discard samples until we reach the desired position. Crossing a
|
||
|
// logical bitstream boundary with abandon is OK.
|
||
|
while(pcm_offset<pos)
|
||
|
{
|
||
|
float[][] pcm;
|
||
|
int target=(int)(pos-pcm_offset);
|
||
|
float[][][] _pcm=new float[1][][];
|
||
|
int[] _index=new int[getInfo(-1).channels];
|
||
|
int samples=vd.synthesis_pcmout(_pcm, _index);
|
||
|
pcm=_pcm[0];
|
||
|
|
||
|
if(samples>target)samples=target;
|
||
|
vd.synthesis_read(samples);
|
||
|
pcm_offset+=samples;
|
||
|
|
||
|
if(samples<target)
|
||
|
if(process_packet(1)==0)
|
||
|
{
|
||
|
pcm_offset=pcm_total(-1); // eof
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
|
||
|
// seek_error:
|
||
|
// dump machine so we're in a known state
|
||
|
//pcm_offset=-1;
|
||
|
//decode_clear();
|
||
|
//return -1;
|
||
|
}
|
||
|
|
||
|
// seek to a playback time relative to the decompressed pcm stream
|
||
|
// returns zero on success, nonzero on failure
|
||
|
public int time_seek(float seconds)
|
||
|
{
|
||
|
// translate time to PCM position and call pcm_seek
|
||
|
|
||
|
int link=-1;
|
||
|
long pcm_tot=pcm_total(-1);
|
||
|
float time_tot=time_total(-1);
|
||
|
|
||
|
if(!skable)return(-1); // don't dump machine if we can't seek
|
||
|
if(seconds<0 || seconds>time_tot)
|
||
|
{
|
||
|
//goto seek_error;
|
||
|
pcm_offset=-1;
|
||
|
decode_clear();
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// which bitstream section does this time offset occur in?
|
||
|
for(link=links-1;link>=0;link--)
|
||
|
{
|
||
|
pcm_tot-=pcmlengths[link];
|
||
|
time_tot-=time_total(link);
|
||
|
if(seconds>=time_tot)break;
|
||
|
}
|
||
|
|
||
|
// enough information to convert time offset to pcm offset
|
||
|
{
|
||
|
long target=(long)(pcm_tot+(seconds-time_tot)*vi[link].rate);
|
||
|
return(pcm_seek(target));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// tell the current stream offset cursor. Note that seek followed by
|
||
|
// tell will likely not give the set offset due to caching
|
||
|
public long raw_tell()
|
||
|
{
|
||
|
return(offset);
|
||
|
}
|
||
|
|
||
|
// return PCM offset (sample) of next PCM sample to be read
|
||
|
public long pcm_tell()
|
||
|
{
|
||
|
return(pcm_offset);
|
||
|
}
|
||
|
|
||
|
// return time offset (seconds) of next PCM sample to be read
|
||
|
public float time_tell()
|
||
|
{
|
||
|
// translate time to PCM position and call pcm_seek
|
||
|
|
||
|
int link=-1;
|
||
|
long pcm_tot=0;
|
||
|
float time_tot=0.0f;
|
||
|
|
||
|
if(skable)
|
||
|
{
|
||
|
pcm_tot=pcm_total(-1);
|
||
|
time_tot=time_total(-1);
|
||
|
|
||
|
// which bitstream section does this time offset occur in?
|
||
|
for(link=links-1;link>=0;link--)
|
||
|
{
|
||
|
pcm_tot-=pcmlengths[link];
|
||
|
time_tot-=time_total(link);
|
||
|
if(pcm_offset>=pcm_tot)break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return((float)time_tot+(float)(pcm_offset-pcm_tot)/vi[link].rate);
|
||
|
}
|
||
|
|
||
|
// link: -1) return the vorbis_info struct for the bitstream section
|
||
|
// currently being decoded
|
||
|
// 0-n) to request information for a specific bitstream section
|
||
|
//
|
||
|
// In the case of a non-seekable bitstream, any call returns the
|
||
|
// current bitstream. NULL in the case that the machine is not
|
||
|
// initialized
|
||
|
|
||
|
public Info getInfo(int link)
|
||
|
{
|
||
|
if(skable)
|
||
|
{
|
||
|
if(link<0)
|
||
|
{
|
||
|
if(decode_ready)
|
||
|
{
|
||
|
return vi[current_link];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(link>=links)
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return vi[link];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(decode_ready)
|
||
|
{
|
||
|
return vi[0];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public Comment getComment(int link)
|
||
|
{
|
||
|
if(skable)
|
||
|
{
|
||
|
if(link<0)
|
||
|
{
|
||
|
if(decode_ready){ return vc[current_link]; }
|
||
|
else{ return null; }
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(link>=links){ return null;}
|
||
|
else{ return vc[link]; }
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(decode_ready){ return vc[0]; }
|
||
|
else{ return null; }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int host_is_big_endian()
|
||
|
{
|
||
|
return 0;
|
||
|
//the above isn't really right...
|
||
|
}
|
||
|
|
||
|
// up to this point, everything could more or less hide the multiple
|
||
|
// logical bitstream nature of chaining from the toplevel application
|
||
|
// if the toplevel application didn't particularly care. However, at
|
||
|
// the point that we actually read audio back, the multiple-section
|
||
|
// nature must surface: Multiple bitstream sections do not necessarily
|
||
|
// have to have the same number of channels or sampling rate.
|
||
|
//
|
||
|
// read returns the sequential logical bitstream number currently
|
||
|
// being decoded along with the PCM data in order that the toplevel
|
||
|
// application can take action on channel/sample rate changes. This
|
||
|
// number will be incremented even for streamed (non-seekable) streams
|
||
|
// (for seekable streams, it represents the actual logical bitstream
|
||
|
// index within the physical bitstream. Note that the accessor
|
||
|
// functions above are aware of this dichotomy).
|
||
|
//
|
||
|
// input values: buffer) a buffer to hold packed PCM data for return
|
||
|
// length) the byte length requested to be placed into buffer
|
||
|
// bigendianp) should the data be packed LSB first (0) or
|
||
|
// MSB first (1)
|
||
|
// word) word size for output. currently 1 (byte) or
|
||
|
// 2 (16 bit short)
|
||
|
//
|
||
|
// return values: -1) error/hole in data
|
||
|
// 0) EOF
|
||
|
// n) number of bytes of PCM actually returned. The
|
||
|
// below works on a packet-by-packet basis, so the
|
||
|
// return length is not related to the 'length' passed
|
||
|
// in, just guaranteed to fit.
|
||
|
//
|
||
|
// *section) set to the logical bitstream number
|
||
|
|
||
|
public int read(byte[] buffer,int length,
|
||
|
int bigendianp, int word, int sgned, int[] bitstream)
|
||
|
{
|
||
|
int host_endian = host_is_big_endian();
|
||
|
int index=0;
|
||
|
|
||
|
while(true)
|
||
|
{
|
||
|
if(decode_ready)
|
||
|
{
|
||
|
float[][] pcm;
|
||
|
float[][][] _pcm=new float[1][][];
|
||
|
int[] _index=new int[getInfo(-1).channels];
|
||
|
int samples=vd.synthesis_pcmout(_pcm, _index);
|
||
|
pcm=_pcm[0];
|
||
|
if(samples!=0)
|
||
|
{
|
||
|
// yay! proceed to pack data into the byte buffer
|
||
|
int channels=getInfo(-1).channels;
|
||
|
int bytespersample=word * channels;
|
||
|
if(samples>length/bytespersample)samples=length/bytespersample;
|
||
|
|
||
|
// a tight loop to pack each size
|
||
|
{
|
||
|
int val;
|
||
|
if(word==1)
|
||
|
{
|
||
|
int off=(sgned!=0?0:128);
|
||
|
for(int j=0;j<samples;j++)
|
||
|
{
|
||
|
for(int i=0;i<channels;i++)
|
||
|
{
|
||
|
val=(int)(pcm[i][_index[i]+j]*128.0 + 0.5);
|
||
|
if(val>127)val=127;
|
||
|
else if(val<-128)val=-128;
|
||
|
buffer[index++]=(byte)(val+off);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int off=(sgned!=0?0:32768);
|
||
|
|
||
|
if(host_endian==bigendianp)
|
||
|
{
|
||
|
if(sgned!=0)
|
||
|
{
|
||
|
for(int i=0;i<channels;i++)
|
||
|
{ // It's faster in this order
|
||
|
int src=_index[i];
|
||
|
int dest=i*2;
|
||
|
for(int j=0;j<samples;j++)
|
||
|
{
|
||
|
val=(int)(pcm[i][src+j]*32767.0);
|
||
|
if(val>32767)val=32767;
|
||
|
else if(val<-32768)val=-32768;
|
||
|
buffer[dest]=(byte)(val);
|
||
|
buffer[dest+1]=(byte)((uint)val >> 8);
|
||
|
dest+=bytespersample;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for(int i=0;i<channels;i++)
|
||
|
{
|
||
|
float[] src=pcm[i];
|
||
|
int dest=i;
|
||
|
for(int j=0;j<samples;j++)
|
||
|
{
|
||
|
val=(int)(src[j]*32768.0 + 0.5);
|
||
|
if(val>32767)val=32767;
|
||
|
else if(val<-32768)val=-32768;
|
||
|
buffer[dest]=(byte)((uint)(val+off) >> 8);
|
||
|
buffer[dest+1]=(byte)(val+off);
|
||
|
dest+=channels*2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if(bigendianp!=0)
|
||
|
{
|
||
|
for(int j=0;j<samples;j++)
|
||
|
{
|
||
|
for(int i=0;i<channels;i++)
|
||
|
{
|
||
|
val=(int)(pcm[i][j]*32768.0 + 0.5);
|
||
|
if(val>32767)val=32767;
|
||
|
else if(val<-32768)val=-32768;
|
||
|
val+=off;
|
||
|
buffer[index++]=(byte)((uint)val >> 8);
|
||
|
buffer[index++]=(byte)val;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//int val;
|
||
|
for(int j=0;j<samples;j++)
|
||
|
{
|
||
|
for(int i=0;i<channels;i++)
|
||
|
{
|
||
|
val=(int)(pcm[i][j]*32768.0 + 0.5);
|
||
|
if(val>32767)val=32767;
|
||
|
else if(val<-32768)val=-32768;
|
||
|
val+=off;
|
||
|
buffer[index++]=(byte)val;
|
||
|
buffer[index++]=(byte)((uint)val >> 8);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
vd.synthesis_read(samples);
|
||
|
pcm_offset+=samples;
|
||
|
if(bitstream!=null)bitstream[0]=current_link;
|
||
|
return(samples*bytespersample);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// suck in another packet
|
||
|
switch(process_packet(1))
|
||
|
{
|
||
|
case 0:
|
||
|
return(0);
|
||
|
case -1:
|
||
|
return -1;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
public Info[] getInfo(){return vi;}
|
||
|
public Comment[] getComment(){return vc;}
|
||
|
}
|
||
|
}
|