/* * VorbisFormatConversionProvider.java * * This file is part of Tritonus: http://www.tritonus.org/ */ /* * Copyright (c) 1999 - 2003 by Matthias Pfisterer * Copyright (c) 2001 by Florian Bomers <http://www.bomers.de> * * 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. */ /* |<--- this code is formatted to fit into 80 columns --->| */ package org.tritonus.sampled.convert.vorbis; import java.io.EOFException; import java.io.InputStream; import java.io.IOException; import java.util.Arrays; import java.util.Random; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import org.tritonus.lowlevel.ogg.*; import org.tritonus.lowlevel.vorbis.Block; import org.tritonus.lowlevel.vorbis.Comment; import org.tritonus.lowlevel.vorbis.DspState; import org.tritonus.lowlevel.vorbis.Info; import org.tritonus.share.TDebug; import org.tritonus.share.sampled.AudioFormats; import org.tritonus.share.sampled.convert.TAsynchronousFilteredAudioInputStream; import org.tritonus.share.sampled.convert.TEncodingFormatConversionProvider; /** ConversionProvider for ogg vorbis encoding. This FormatConversionProvider uses the native libraries libogg, libvorbis and libvorbisenc to implement encoding to ogg vorbis. @author Matthias Pfisterer */ public class VorbisFormatConversionProvider extends TEncodingFormatConversionProvider { // only used as abbreviation private static final AudioFormat.Encoding VORBIS = new AudioFormat.Encoding("VORBIS"); private static final AudioFormat.Encoding PCM_SIGNED = new AudioFormat.Encoding("PCM_SIGNED"); private static final AudioFormat[] INPUT_FORMATS = { // mono, 16 bit signed new AudioFormat(PCM_SIGNED, -1.0F, 16, 1, 2, -1.0F, false), new AudioFormat(PCM_SIGNED, -1.0F, 16, 1, 2, -1.0F, true), // stereo, 16 bit signed new AudioFormat(PCM_SIGNED, -1.0F, 16, 2, 4, -1.0F, false), new AudioFormat(PCM_SIGNED, -1.0F, 16, 2, 4, -1.0F, true), // TODO: other channel configurations // mono // TODO: mechanism to make the double specification with // different endianess obsolete. new AudioFormat(VORBIS, -1.0F, -1, 1, -1, -1.0F, false), new AudioFormat(VORBIS, -1.0F, -1, 1, -1, -1.0F, true), // stereo new AudioFormat(VORBIS, -1.0F, -1, 2, -1, -1.0F, false), new AudioFormat(VORBIS, -1.0F, -1, 2, -1, -1.0F, true), // TODO: other channel configurations }; // private static final AudioFormat[] OUTPUT_FORMATS = // { // // mono // // TODO: mechanism to make the double specification with // // different endianess obsolete. // new AudioFormat(VORBIS, -1.0F, -1, 1, -1, -1.0F, false), // new AudioFormat(VORBIS, -1.0F, -1, 1, -1, -1.0F, true), // // stereo // new AudioFormat(VORBIS, -1.0F, -1, 2, -1, -1.0F, false), // new AudioFormat(VORBIS, -1.0F, -1, 2, -1, -1.0F, true), // // TODO: other channel configurations // }; /* Default settings for encoding. */ private static final boolean DEFAULT_VBR = true; private static final float DEFAULT_QUALITY = 0.5F; private static final int DEFAULT_MAX_BITRATE = 256; private static final int DEFAULT_NOM_BITRATE = 128; private static final int DEFAULT_MIN_BITRATE = 32; // TODO: refresh from time to time to allow adding the lib at runtime... private static boolean LIB_AVAILABLE = Ogg.isLibraryAvailable(); /** Constructor. */ public VorbisFormatConversionProvider() { super(Arrays.asList(INPUT_FORMATS), Arrays.asList(INPUT_FORMATS) /* true, // new behaviour false*/); // bidirectional .. constants UNIDIR../BIDIR..? if (!LIB_AVAILABLE) { disable(); } if (TDebug.TraceAudioConverter) { TDebug.out("VorbisFormatConversionProvider.<init>(): begin"); } if (TDebug.TraceAudioConverter) { TDebug.out("VorbisFormatConversionProvider.<init>(): end"); } } @Override public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream audioInputStream) { if (!LIB_AVAILABLE) { throw new IllegalArgumentException("format conversion not supported: ogg/vorbis native library not found."); } if (TDebug.TraceAudioConverter) { TDebug.out(">VorbisFormatConversionProvider.getAudioInputStream(): begin"); } /** The AudioInputStream to return. */ AudioInputStream convertedAudioInputStream = null; if (TDebug.TraceAudioConverter) { TDebug.out("checking if conversion supported"); TDebug.out("from: " + audioInputStream.getFormat()); TDebug.out("to: " + targetFormat); } // what is this ??? targetFormat = getDefaultTargetFormat(targetFormat, audioInputStream.getFormat()); if (isConversionSupported(targetFormat, audioInputStream.getFormat())) { if (targetFormat.getEncoding().equals(VORBIS)) { if (TDebug.TraceAudioConverter) TDebug.out("conversion supported; trying to create EncodedVorbisAudioInputStream"); convertedAudioInputStream = new EncodedVorbisAudioInputStream( targetFormat, audioInputStream); } else { if (TDebug.TraceAudioConverter) { TDebug.out("conversion supported; trying to create DecodedVorbisAudioInputStream"); } convertedAudioInputStream = new DecodedVorbisAudioInputStream( targetFormat, audioInputStream); } } else { if (TDebug.TraceAudioConverter) { TDebug.out("<conversion not supported; throwing IllegalArgumentException"); } throw new IllegalArgumentException("conversion not supported"); } if (TDebug.TraceAudioConverter) { TDebug.out("<VorbisFormatConversionProvider.getAudioInputStream(): end"); } return convertedAudioInputStream; } protected AudioFormat getDefaultTargetFormat(AudioFormat targetFormat, AudioFormat sourceFormat) { if (!LIB_AVAILABLE) { throw new IllegalArgumentException("format conversion not supported: ogg/vorbis native library not found."); } if (TDebug.TraceAudioConverter) { TDebug.out("VorbisFormatConversionProvider.getDefaultTargetFormat(): target format: " + targetFormat); } if (TDebug.TraceAudioConverter) { TDebug.out("VorbisFormatConversionProvider.getDefaultTargetFormat(): source format: " + sourceFormat); } AudioFormat newTargetFormat = null; // return first of the matching formats // pre-condition: the predefined target formats (FORMATS2) must be well-defined ! for (AudioFormat format: getCollectionTargetFormats()) { if (AudioFormats.matches(targetFormat, format)) { newTargetFormat = format; } } if (newTargetFormat == null) { throw new IllegalArgumentException("conversion not supported"); } if (TDebug.TraceAudioConverter) { TDebug.out("VorbisFormatConversionProvider.getDefaultTargetFormat(): new target format: " + newTargetFormat); } // hacked together... // ... only works for PCM target encoding ... newTargetFormat = new AudioFormat( targetFormat.getEncoding(), sourceFormat.getSampleRate(), newTargetFormat.getSampleSizeInBits(), newTargetFormat.getChannels(), newTargetFormat.getFrameSize(), sourceFormat.getSampleRate(), newTargetFormat.isBigEndian(), targetFormat.properties()); if (TDebug.TraceAudioConverter) TDebug.out("VorbisFormatConversionProvider.getDefaultTargetFormat(): really new target format: " + newTargetFormat); return newTargetFormat; } /** AudioInputStream returned on encoding to ogg vorbis. An instance of this class is returned if you call AudioSystem.getAudioInputStream(AudioFormat, AudioInputStream) to encode a PCM stream. This class contains the logic of maintaining buffers and calling the encoder. */ public static class EncodedVorbisAudioInputStream extends TAsynchronousFilteredAudioInputStream { /** How many PCM frames to encode at once. */ private static final int READ = 1024; private AudioInputStream m_decodedStream; private byte[] m_abReadbuffer; private StreamState m_streamState; private Page m_page; private Packet m_packet; private Info m_info; private Comment m_comment; private DspState m_dspState; private Block m_block; private boolean eos = false; public EncodedVorbisAudioInputStream( AudioFormat outputFormat, AudioInputStream inputStream) { super(outputFormat, AudioSystem.NOT_SPECIFIED, 262144, 16384); if (!LIB_AVAILABLE) { throw new IllegalArgumentException("format conversion not supported: ogg/vorbis native library not found."); } if (TDebug.TraceAudioConverter) TDebug.out(">EncodedVorbisAudioInputStream.<init>(): begin"); m_decodedStream = inputStream; m_abReadbuffer = new byte[READ * getFrameSize()]; Object property = null; property = outputFormat.getProperty("vbr"); boolean bUseVBR = DEFAULT_VBR; if (property instanceof Boolean) { bUseVBR = ((Boolean) property).booleanValue(); if (TDebug.TraceAudioConverter) TDebug.out("using VBR: " + bUseVBR); } property = outputFormat.getProperty("quality"); float fQuality = DEFAULT_QUALITY; if (property instanceof Integer) { fQuality = ((Integer) property).intValue() / 10.0F; bUseVBR = true; if (TDebug.TraceAudioConverter) TDebug.out("using quality (automatically switching VBR on): " + fQuality); } int nNominalBitrate = DEFAULT_NOM_BITRATE; int nMinBitrate = DEFAULT_MIN_BITRATE; int nMaxBitrate = DEFAULT_MAX_BITRATE; property = outputFormat.getProperty("bitrate"); if (property instanceof Integer) { nNominalBitrate = ((Integer) property).intValue() /*/ 1024*/; nMinBitrate = nNominalBitrate; nMaxBitrate = nNominalBitrate; bUseVBR = false; if (TDebug.TraceAudioConverter) TDebug.out("using nominal bitrate (automatically switching VBR off): " + nNominalBitrate); } property = outputFormat.getProperty("vorbis.min_bitrate"); if (property instanceof Integer) { nMinBitrate = ((Integer) property).intValue() /*/ 1024*/; } property = outputFormat.getProperty("vorbis.max_bitrate"); if (property instanceof Integer) { nMaxBitrate = ((Integer) property).intValue() / 1024; } m_streamState = new StreamState(); m_page = new Page(); m_packet = new Packet(); m_info = new Info(); m_comment = new Comment(); m_dspState = new DspState(); m_block = new Block(); m_info.init(); int nSampleRate = (int) inputStream.getFormat().getSampleRate(); if (TDebug.TraceAudioConverter) TDebug.out("sample rate: " + nSampleRate); if (TDebug.TraceAudioConverter) TDebug.out("channels: " + getChannels()); if (bUseVBR) { if (TDebug.TraceAudioConverter) TDebug.out("using VBR with quality: " + fQuality); m_info.encodeInitVBR(getChannels(), nSampleRate, fQuality); } else { if (TDebug.TraceAudioConverter) TDebug.out("using fixed bitrate(max/nom/min): " + nMaxBitrate + "/" + nNominalBitrate + "/" + nMinBitrate); m_info.encodeInit(getChannels(), nSampleRate, nMaxBitrate, nNominalBitrate, nMinBitrate); } m_comment.init(); m_comment.addTag("ENCODER","Tritonus libvorbis wrapper"); m_dspState.initAnalysis(m_info); m_block.init(m_dspState); Random random = new Random(System.currentTimeMillis()); m_streamState.init(random.nextInt()); Packet header = new Packet(); Packet header_comm = new Packet(); Packet header_code = new Packet(); m_dspState.headerOut(m_comment, header, header_comm, header_code); m_streamState.packetIn(header); m_streamState.packetIn(header_comm); m_streamState.packetIn(header_code); while (true) { int result = m_streamState.flush(m_page); if(result == 0) { break; } getCircularBuffer().write(m_page.getHeader()); getCircularBuffer().write(m_page.getBody()); } if (TDebug.TraceAudioConverter) { TDebug.out("<EncodedVorbisAudioInputStream.<init>(): end"); } } public void execute() { if (TDebug.TraceAudioConverter) { TDebug.out(">EncodedVorbisAudioInputStream.execute(): begin"); } int nFrameSize = getFrameSize(); int nChannels = getChannels(); boolean bBigEndian = isBigEndian(); int nBytesPerSample = nFrameSize / nChannels; int nSampleSizeInBits = nBytesPerSample * 8; float fScale = (float) Math.pow(2.0, nSampleSizeInBits - 1); if (TDebug.TraceAudioConverter) { TDebug.out("frame size: " + nFrameSize); TDebug.out("channels: " + nChannels); TDebug.out("big endian: " + bBigEndian); TDebug.out("sample size (bits): " + nSampleSizeInBits); TDebug.out("bytes per sample: " + nBytesPerSample); TDebug.out("scale: " + fScale); } while (!eos && writeMore()) { if (TDebug.TraceAudioConverter) { TDebug.out("writeMore(): " + writeMore()); } int bytes; try { bytes = m_decodedStream.read(m_abReadbuffer); if (TDebug.TraceAudioConverter) { TDebug.out("read from PCM stream: " + bytes); } } catch (IOException e) { if (TDebug.TraceAllExceptions || TDebug.TraceAudioConverter) { TDebug.out(e); } m_streamState.clear(); m_block.clear(); m_dspState.clear(); m_comment.clear(); m_info.clear(); try { close(); } catch (IOException e1) { if (TDebug.TraceAllExceptions || TDebug.TraceAudioConverter) { TDebug.out(e1); } } if (TDebug.TraceAudioConverter) { TDebug.out("<"); } return; } if (bytes == 0 || bytes == -1) { if (TDebug.TraceAudioConverter) { TDebug.out("EOS reached; calling DspState.write(0)"); } m_dspState.write(null, 0); } else { int nFrames = bytes / nFrameSize; if (TDebug.TraceAudioConverter) { TDebug.out("processing frames: " + nFrames); } float[][] buffer = new float[nChannels][READ]; /* uninterleave samples */ for (int i = 0; i < nFrames; i++) { for (int nChannel = 0; nChannel < nChannels; nChannel++) { int nSample; nSample = bytesToInt16(m_abReadbuffer, i * nFrameSize + nChannel * nBytesPerSample, bBigEndian); buffer[nChannel][i] = nSample / fScale; } } m_dspState.write(buffer, nFrames); } while (m_dspState.blockOut(m_block) == 1) { m_block.analysis(null); m_block.addBlock(); while (m_dspState.flushPacket(m_packet) != 0) { m_streamState.packetIn(m_packet); while (!eos /*&& writeMore()*/) { int result = m_streamState.pageOut(m_page); if(result == 0) { break; } getCircularBuffer().write(m_page.getHeader()); getCircularBuffer().write(m_page.getBody()); if (m_page.isEos()) { eos = true; if (TDebug.TraceAudioConverter) { TDebug.out("page has detected EOS"); } } } } } } if (eos) { if (TDebug.TraceAudioConverter) { TDebug.out("EOS; shutting down encoder"); } m_streamState.clear(); m_block.clear(); m_dspState.clear(); m_comment.clear(); m_info.clear(); getCircularBuffer().close(); try { close(); } catch (IOException e) { if (TDebug.TraceAllExceptions || TDebug.TraceAudioConverter) { TDebug.out(e); } } } if (TDebug.TraceAudioConverter) { TDebug.out("<EncodedVorbisAudioInputStream.execute(): end"); } } private int getChannels() { return m_decodedStream.getFormat().getChannels(); } private int getFrameSize() { return m_decodedStream.getFormat().getFrameSize(); } private boolean isBigEndian() { return m_decodedStream.getFormat().isBigEndian(); } @Override public void close() throws IOException { super.close(); m_decodedStream.close(); } // copied from TConversionTool private static int bytesToInt16(byte[] buffer, int byteOffset, boolean bigEndian) { return bigEndian ? ((buffer[byteOffset]<<8) | (buffer[byteOffset+1] & 0xFF)) : ((buffer[byteOffset+1]<<8) | (buffer[byteOffset] & 0xFF)); } } /** AudioInputStream returned on decoding of ogg vorbis. An instance of this class is returned if you call AudioSystem.getAudioInputStream(AudioFormat, AudioInputStream) to decode an ogg/vorbis stream. This class contains the logic of maintaining buffers and calling the decoder. */ /* Class should be private, but is public due to a bug (?) in the aspectj compiler. */ /*private*/public static class DecodedVorbisAudioInputStream extends TAsynchronousFilteredAudioInputStream { private static final int INPUT_BUFFER_SIZE = 4096; private static final int BUFFER_MULTIPLE = 4; private static final int BUFFER_SIZE = BUFFER_MULTIPLE * 256 * 2; private static final int CONVSIZE = BUFFER_SIZE * 2; private InputStream m_oggBitStream = null; private byte[] m_abInputBuffer; // Ogg structures private SyncState m_oggSyncState = null; private StreamState m_oggStreamState = null; private Page m_oggPage = null; private Packet m_oggPacket = null; // Vorbis structures private Info m_vorbisInfo = null; private Comment m_vorbisComment = null; private DspState m_vorbisDspState = null; private Block m_vorbisBlock = null; // private List m_songComments = new ArrayList(); // is altered later in a dubious way //$$fb field not used //private int convsize = -1; // BUFFER_SIZE * 2; // TODO: further checking private byte[] convbuffer = new byte[CONVSIZE]; private float[][] m_aPcmOut; private boolean m_bHeadersExpected; /** * Constructor. */ public DecodedVorbisAudioInputStream(AudioFormat outputFormat, AudioInputStream bitStream) { super(outputFormat, AudioSystem.NOT_SPECIFIED); if (!LIB_AVAILABLE) { throw new IllegalArgumentException("format conversion not supported: ogg/vorbis native library not found."); } if (TDebug.TraceAudioConverter) { TDebug.out("DecodedVorbisAudioInputStream.<init>(): begin"); } m_oggBitStream = bitStream; m_bHeadersExpected = true; init_jorbis(); if (TDebug.TraceAudioConverter) { TDebug.out("DecodedVorbisAudioInputStream.<init>(): end"); } } /** * Initializes all the jOrbis and jOgg vars that are used for song playback. */ private void init_jorbis() { m_abInputBuffer = new byte[INPUT_BUFFER_SIZE]; m_oggSyncState = new SyncState(); m_oggStreamState = new StreamState(); m_oggPage = new Page(); m_oggPacket = new Packet(); m_vorbisInfo = new Info(); m_vorbisComment = new Comment(); m_vorbisDspState = new DspState(); m_vorbisBlock = new Block(); m_vorbisBlock.init(m_vorbisDspState); m_oggSyncState.init(); } /** Callback from circular buffer. */ public void execute() { if (TDebug.TraceAudioConverter) TDebug.out(">DecodedVorbisAudioInputStream.execute(): begin"); if (m_bHeadersExpected) { if (TDebug.TraceAudioConverter) TDebug.out("reading headers..."); // Headers (+ Comments). try { readHeaders(); } catch (IOException e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } closePhysicalStream(); if (TDebug.TraceAudioConverter) TDebug.out("<DecodedVorbisAudioInputStream.execute(): end"); return; } m_bHeadersExpected = false; setupVorbisStructures(); } if (TDebug.TraceAudioConverter) TDebug.out("decoding..."); // Decoding ! while (writeMore()) { try { readOggPacket(); } catch (IOException e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } closePhysicalStream(); m_vorbisInfo.free(); m_vorbisComment.free(); m_vorbisDspState.free(); m_vorbisBlock.free(); if (TDebug.TraceAudioConverter) TDebug.out("<DecodedVorbisAudioInputStream.execute(): end"); return; } decodeDataPacket(); } if (m_oggPacket.isEos()) { if (TDebug.TraceAudioConverter) TDebug.out("end of vorbis stream reached"); /* The end of the vorbis stream is reached. So we shut down the logical bitstream and vorbis structures. */ m_oggStreamState.clear(); m_vorbisBlock.clear(); m_vorbisDspState.clear(); m_vorbisInfo.clear(); m_bHeadersExpected = true; } if (TDebug.TraceAudioConverter) TDebug.out("<DecodedVorbisAudioInputStream.execute(): end"); } private void closePhysicalStream() { if (TDebug.TraceAudioConverter) TDebug.out("DecodedVorbisAudioInputStream.closePhysicalStream(): begin"); m_oggSyncState.clear(); try { if (m_oggBitStream != null) { m_oggBitStream.close(); } getCircularBuffer().close(); } catch (Exception e) { if (TDebug.TraceAllExceptions) { TDebug.out(e); } } if (TDebug.TraceAudioConverter) TDebug.out("DecodedVorbisAudioInputStream.closePhysicalStream(): end"); } /** Read and process all three vorbis headers. */ private void readHeaders() throws IOException { readIdentificationHeader(); readCommentAndCodebookHeaders(); processComments(); } /** Read the vorbis identification header. @throw IOException */ private void readIdentificationHeader() throws IOException { readOggPage(); m_oggStreamState.init(m_oggPage.getSerialNo()); m_vorbisInfo.init(); m_vorbisComment.init(); if (m_oggStreamState.pageIn(m_oggPage) < 0) { throw new IOException("can't read first page of Ogg bitstream data, perhaps stream version mismatch"); } if (m_oggStreamState.packetOut(m_oggPacket) != 1) { throw new IOException("can't read initial header packet"); } if (m_vorbisInfo.headerIn(m_vorbisComment, m_oggPacket) < 0) { throw new IOException("packet is not a vorbis header"); } } /** Read the comment header and the codebook header pages. */ private void readCommentAndCodebookHeaders() throws IOException { for (int i = 0; i < 2; i++) { readOggPacket(); if (m_vorbisInfo.headerIn(m_vorbisComment, m_oggPacket) < 0) { throw new IOException("packet is not a vorbis header"); } } } /** */ private void processComments() { // byte[][] ptr = m_vorbisComment.user_comments; // String currComment = ""; // m_songComments.clear(); // for (int j = 0; j < ptr.length; j++) // { // if (ptr[j] == null) // { // break; // } // currComment = (new String(ptr[j], 0, ptr[j].length - 1)).trim(); // m_songComments.add(currComment); // if (currComment.toUpperCase().startsWith("ARTIST")) // { // String artistLabelValue = currComment.substring(7); // } // else if (currComment.toUpperCase().startsWith("TITLE")) // { // String titleLabelValue = currComment.substring(6); // String miniDragLabel = currComment.substring(6); // } // if (TDebug.TraceAudioConverter) TDebug.out("Comment: " + currComment); // } // currComment = "Bitstream: " + m_vorbisInfo.getChannels() + " channel," + m_vorbisInfo.rate + "Hz"; // m_songComments.add(currComment); // if (TDebug.TraceAudioConverter) TDebug.out(currComment); if (TDebug.TraceAudioConverter) TDebug.out("Encoded by: " + m_vorbisComment.getVendor()); // m_songComments.add(currComment); // if (TDebug.TraceAudioConverter) TDebug.out(currComment); } /** Setup structures needed for vorbis decoding. Precondition: m_vorbisInfo has to be initialized completely (i.e. all three headers are read). */ private void setupVorbisStructures() { //$$fb field not used... //convsize = BUFFER_SIZE / m_vorbisInfo.getChannels(); m_vorbisDspState.initSynthesis(m_vorbisInfo); m_vorbisBlock.init(m_vorbisDspState); m_aPcmOut = new float[m_vorbisInfo.getChannels()][]; } /** Decode a packet of vorbis data. This method assumes that a packet is available in {@link #m_oggPacket m_oggPacket}. The content of this packet is run through the decoder. The resulting PCM data are written to the circular buffer. */ private void decodeDataPacket() { int nSamples; if (m_vorbisBlock.synthesis(m_oggPacket) == 0) { // test for success! m_vorbisDspState.blockIn(m_vorbisBlock); } while ((nSamples = m_vorbisDspState.pcmOut(m_aPcmOut)) > 0) { // convert floats to signed ints and // interleave for (int nChannel = 0; nChannel < m_vorbisInfo.getChannels(); nChannel++) { int pointer = nChannel * getSampleSizeInBytes(); for (int j = 0; j < nSamples; j++) { float fVal = m_aPcmOut[nChannel][j]; clipAndWriteSample(fVal, pointer); pointer += getFrameSize(); } } m_vorbisDspState.read(nSamples); getCircularBuffer().write(convbuffer, 0, getFrameSize() * nSamples); } } /** Scale and clip the sample and write it to convbuffer. */ private void clipAndWriteSample(float fSample, int nPointer) { int nSample; // TODO: check if clipping is necessary if (fSample > 1.0F) { fSample = 1.0F; } if (fSample < -1.0F) { fSample = -1.0F; } switch (getFormat().getSampleSizeInBits()) { case 16: nSample = (int) (fSample * 32767.0F); if (isBigEndian()) { convbuffer[nPointer++] = (byte) (nSample >> 8); convbuffer[nPointer] = (byte) (nSample & 0xFF); } else { convbuffer[nPointer++] = (byte) (nSample & 0xFF); convbuffer[nPointer] = (byte) (nSample >> 8); } break; case 24: nSample = (int) (fSample * 8388607.0F); if (isBigEndian()) { convbuffer[nPointer++] = (byte) (nSample >> 16); convbuffer[nPointer++] = (byte) ((nSample >>> 8) & 0xFF); convbuffer[nPointer] = (byte) (nSample & 0xFF); } else { convbuffer[nPointer++] = (byte) (nSample & 0xFF); convbuffer[nPointer++] = (byte) ((nSample >>> 8) & 0xFF); convbuffer[nPointer] = (byte) (nSample >> 16); } break; case 32: nSample = (int) (fSample * 2147483647.0F); if (isBigEndian()) { convbuffer[nPointer++] = (byte) (nSample >> 24); convbuffer[nPointer++] = (byte) ((nSample >>> 16) & 0xFF); convbuffer[nPointer++] = (byte) ((nSample >>> 8) & 0xFF); convbuffer[nPointer] = (byte) (nSample & 0xFF); } else { convbuffer[nPointer++] = (byte) (nSample & 0xFF); convbuffer[nPointer++] = (byte) ((nSample >>> 8) & 0xFF); convbuffer[nPointer++] = (byte) ((nSample >>> 16) & 0xFF); convbuffer[nPointer] = (byte) (nSample >> 24); } break; } } /** Read an ogg packet. This method does everything necessary to read an ogg packet. If needed, it calls {@link #readOggPage readOggPage()}, which, in turn, may read more data from the stream. The resulting packet is placed in {@link #m_oggPacket m_oggPacket} (for which the reference is not altered; is has to be initialized before). */ private void readOggPacket() throws IOException { while (true) { int result = m_oggStreamState.packetOut(m_oggPacket); if (result == 1) { return; } if (result == -1) { throw new IOException("can't read packet"); } readOggPage(); if (m_oggStreamState.pageIn(m_oggPage) < 0) { throw new IOException("can't read page of Ogg bitstream data"); } } } /** Read an ogg page. This method does everything necessary to read an ogg page. If needed, it reads more data from the stream. The resulting page is placed in {@link #m_oggPage m_oggPage} (for which the reference is not altered; is has to be initialized before). Note: this method doesn't deliver the page read to a StreamState object (which assembles pages to packets). This has to be done by the caller. */ private void readOggPage() throws IOException { while (true) { int result = m_oggSyncState.pageOut(m_oggPage); if (result == 1) { return; } // we need more data from the stream // TODO: call stream.read() directly int nBytes = readFromStream(m_abInputBuffer, 0, m_abInputBuffer.length); // TODO: This clause should become obsolete; readFromStream() should // propagate exceptions directly. if (nBytes == -1) { throw new EOFException(); } m_oggSyncState.write(m_abInputBuffer, nBytes); } } /** Read raw data from to ogg bitstream. Reads from {@link #m_oggBitStream m_oggBitStream} a specified number of bytes into a buffer, starting at a specified buffer index. @param buffer the where the read data should be put into. Its length has to be at least nStart + nLength. @param nStart @param nLength the number of bytes to read @return the number of bytes read (maybe 0) or -1 if there is no more data in the stream. */ private int readFromStream(byte[] buffer, int nStart, int nLength) throws IOException { return m_oggBitStream.read(buffer, nStart, nLength); } /** */ private int getSampleSizeInBytes() { return getFormat().getFrameSize() / getFormat().getChannels(); } /** . @return . */ private int getFrameSize() { return getFormat().getFrameSize(); } /** Returns if this stream (the decoded one) is big endian. @return true if this stream is big endian. */ private boolean isBigEndian() { return getFormat().isBigEndian(); } /** */ @Override public void close() throws IOException { super.close(); m_oggBitStream.close(); } } } /*** VorbisFormatConversionProvider.java ***/