Home | History | Annotate | Download | only in plugins
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.audio.plugins;
     34 
     35 import com.jme3.asset.AssetInfo;
     36 import com.jme3.asset.AssetLoader;
     37 import com.jme3.audio.AudioBuffer;
     38 import com.jme3.audio.AudioData;
     39 import com.jme3.audio.AudioKey;
     40 import com.jme3.audio.AudioStream;
     41 import com.jme3.audio.SeekableStream;
     42 import com.jme3.util.BufferUtils;
     43 import de.jarnbjo.ogg.EndOfOggStreamException;
     44 import de.jarnbjo.ogg.LogicalOggStream;
     45 import de.jarnbjo.ogg.PhysicalOggStream;
     46 import de.jarnbjo.vorbis.IdentificationHeader;
     47 import de.jarnbjo.vorbis.VorbisStream;
     48 import java.io.ByteArrayOutputStream;
     49 import java.io.IOException;
     50 import java.io.InputStream;
     51 import java.nio.ByteBuffer;
     52 import java.util.Collection;
     53 import java.util.logging.Level;
     54 import java.util.logging.Logger;
     55 
     56 public class OGGLoader implements AssetLoader {
     57 
     58 //    private static int BLOCK_SIZE = 4096*64;
     59 
     60     private PhysicalOggStream oggStream;
     61     private LogicalOggStream loStream;
     62     private VorbisStream vorbisStream;
     63 
     64 //    private CommentHeader commentHdr;
     65     private IdentificationHeader streamHdr;
     66 
     67     private static class JOggInputStream extends InputStream {
     68 
     69         private boolean endOfStream = false;
     70         protected final VorbisStream vs;
     71 
     72         public JOggInputStream(VorbisStream vs){
     73             this.vs = vs;
     74         }
     75 
     76         @Override
     77         public int read() throws IOException {
     78             return 0;
     79         }
     80 
     81         @Override
     82         public int read(byte[] buf) throws IOException{
     83             return read(buf,0,buf.length);
     84         }
     85 
     86         @Override
     87         public int read(byte[] buf, int offset, int length) throws IOException{
     88             if (endOfStream)
     89                 return -1;
     90 
     91             int bytesRead = 0, cnt = 0;
     92             assert length % 2 == 0; // read buffer should be even
     93 
     94             while (bytesRead <length) {
     95                 if ((cnt = vs.readPcm(buf, offset + bytesRead,length - bytesRead)) <= 0) {
     96                     System.out.println("Read "+cnt+" bytes");
     97                     System.out.println("offset "+offset);
     98                     System.out.println("bytesRead "+bytesRead);
     99                     System.out.println("buf length "+length);
    100                     for (int i = 0; i < bytesRead; i++) {
    101                        System.out.print(buf[i]);
    102                     }
    103                     System.out.println("");
    104 
    105 
    106                     System.out.println("EOS");
    107                     endOfStream = true;
    108                     break;
    109                 }
    110                 bytesRead += cnt;
    111            }
    112 
    113             swapBytes(buf, offset, bytesRead);
    114             return bytesRead;
    115 
    116         }
    117 
    118         @Override
    119         public void close() throws IOException{
    120             vs.close();
    121         }
    122 
    123     }
    124 
    125     private static class SeekableJOggInputStream extends JOggInputStream implements SeekableStream {
    126 
    127         private LogicalOggStream los;
    128         private float duration;
    129 
    130         public SeekableJOggInputStream(VorbisStream vs, LogicalOggStream los, float duration){
    131             super(vs);
    132             this.los = los;
    133             this.duration = duration;
    134         }
    135 
    136         public void setTime(float time) {
    137             System.out.println("--setTime--)");
    138             System.out.println("max granule : "+los.getMaximumGranulePosition());
    139             System.out.println("current granule : "+los.getTime());
    140             System.out.println("asked Time : "+time);
    141             System.out.println("new granule : "+(time/duration*los.getMaximumGranulePosition()));
    142             System.out.println("new granule2 : "+(time*vs.getIdentificationHeader().getSampleRate()));
    143 
    144 
    145 
    146             try {
    147                 los.setTime((long)(time*vs.getIdentificationHeader().getSampleRate()));
    148             } catch (IOException ex) {
    149                 Logger.getLogger(OGGLoader.class.getName()).log(Level.SEVERE, null, ex);
    150             }
    151         }
    152 
    153     }
    154 
    155     /**
    156      * Returns the total of expected OGG bytes.
    157      *
    158      * @param dataBytesTotal The number of bytes in the input
    159      * @return If the computed number of bytes is less than the number
    160      * of bytes in the input, it is returned, otherwise the number
    161      * of bytes in the input is returned.
    162      */
    163     private int getOggTotalBytes(int dataBytesTotal){
    164         // Vorbis stream could have more samples than than the duration of the sound
    165         // Must truncate.
    166         int numSamples;
    167         if (oggStream instanceof CachedOggStream){
    168             CachedOggStream cachedOggStream = (CachedOggStream) oggStream;
    169             numSamples = (int) cachedOggStream.getLastOggPage().getAbsoluteGranulePosition();
    170         }else{
    171             UncachedOggStream uncachedOggStream = (UncachedOggStream) oggStream;
    172             numSamples = (int) uncachedOggStream.getLastOggPage().getAbsoluteGranulePosition();
    173         }
    174 
    175         // Number of Samples * Number of Channels * Bytes Per Sample
    176         int totalBytes = numSamples * streamHdr.getChannels() * 2;
    177 
    178 //        System.out.println("Sample Rate: " + streamHdr.getSampleRate());
    179 //        System.out.println("Channels: " + streamHdr.getChannels());
    180 //        System.out.println("Stream Length: " + numSamples);
    181 //        System.out.println("Bytes Calculated: " + totalBytes);
    182 //        System.out.println("Bytes Available:  " + dataBytes.length);
    183 
    184         // Take the minimum of the number of bytes available
    185         // and the expected duration of the audio.
    186         return Math.min(totalBytes, dataBytesTotal);
    187     }
    188 
    189     private float computeStreamDuration(){
    190         // for uncached stream sources, the granule position is not known.
    191         if (oggStream instanceof UncachedOggStream)
    192             return -1;
    193 
    194         // 2 bytes(16bit) * channels * sampleRate
    195         int bytesPerSec = 2 * streamHdr.getChannels() * streamHdr.getSampleRate();
    196 
    197         // Don't know how many bytes are in input, pass MAX_VALUE
    198         int totalBytes = getOggTotalBytes(Integer.MAX_VALUE);
    199 
    200         return (float)totalBytes / bytesPerSec;
    201     }
    202 
    203     private ByteBuffer readToBuffer() throws IOException{
    204         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    205 
    206         byte[] buf = new byte[512];
    207         int read = 0;
    208 
    209         try {
    210             while ( (read = vorbisStream.readPcm(buf, 0, buf.length)) > 0){
    211                 baos.write(buf, 0, read);
    212             }
    213         } catch (EndOfOggStreamException ex){
    214         }
    215 
    216 
    217         byte[] dataBytes = baos.toByteArray();
    218         swapBytes(dataBytes, 0, dataBytes.length);
    219 
    220         int bytesToCopy = getOggTotalBytes( dataBytes.length );
    221 
    222         ByteBuffer data = BufferUtils.createByteBuffer(bytesToCopy);
    223         data.put(dataBytes, 0, bytesToCopy).flip();
    224 
    225         vorbisStream.close();
    226         loStream.close();
    227         oggStream.close();
    228 
    229         return data;
    230     }
    231 
    232     private static void swapBytes(byte[] b, int off, int len) {
    233         byte tempByte;
    234         for (int i = off; i < (off+len); i+=2) {
    235             tempByte = b[i];
    236             b[i] = b[i+1];
    237             b[i+1] = tempByte;
    238         }
    239     }
    240 
    241     private InputStream readToStream(boolean seekable,float streamDuration){
    242         if(seekable){
    243             return new SeekableJOggInputStream(vorbisStream,loStream,streamDuration);
    244         }else{
    245             return new JOggInputStream(vorbisStream);
    246         }
    247     }
    248 
    249     private AudioData load(InputStream in, boolean readStream, boolean streamCache) throws IOException{
    250         if (readStream && streamCache){
    251             oggStream = new CachedOggStream(in);
    252         }else{
    253             oggStream = new UncachedOggStream(in);
    254         }
    255 
    256         Collection<LogicalOggStream> streams = oggStream.getLogicalStreams();
    257         loStream = streams.iterator().next();
    258 
    259 //        if (loStream == null){
    260 //            throw new IOException("OGG File does not contain vorbis audio stream");
    261 //        }
    262 
    263         vorbisStream = new VorbisStream(loStream);
    264         streamHdr = vorbisStream.getIdentificationHeader();
    265 //        commentHdr = vorbisStream.getCommentHeader();
    266 
    267         if (!readStream){
    268             AudioBuffer audioBuffer = new AudioBuffer();
    269             audioBuffer.setupFormat(streamHdr.getChannels(), 16, streamHdr.getSampleRate());
    270             audioBuffer.updateData(readToBuffer());
    271             return audioBuffer;
    272         }else{
    273             AudioStream audioStream = new AudioStream();
    274             audioStream.setupFormat(streamHdr.getChannels(), 16, streamHdr.getSampleRate());
    275 
    276             // might return -1 if unknown
    277             float streamDuration = computeStreamDuration();
    278 
    279             audioStream.updateData(readToStream(oggStream.isSeekable(),streamDuration), streamDuration);
    280             return audioStream;
    281         }
    282     }
    283 
    284     public Object load(AssetInfo info) throws IOException {
    285         if (!(info.getKey() instanceof AudioKey)){
    286             throw new IllegalArgumentException("Audio assets must be loaded using an AudioKey");
    287         }
    288 
    289         AudioKey key = (AudioKey) info.getKey();
    290         boolean readStream = key.isStream();
    291         boolean streamCache = key.useStreamCache();
    292 
    293         InputStream in = null;
    294         try {
    295             in = info.openStream();
    296             AudioData data = load(in, readStream, streamCache);
    297             if (data instanceof AudioStream){
    298                 // audio streams must remain open
    299                 in = null;
    300             }
    301             return data;
    302         } finally {
    303             if (in != null){
    304                 in.close();
    305             }
    306         }
    307 
    308     }
    309 
    310 }
    311