Upgrade OpenAL bits to LWJGL 3.

I didn't find a good way to determine whether OpenAL had already been
initialized. So if we need this whole "sharing AL with something else in the
same JVM" business, we'll need to find another way to do it.
This commit is contained in:
Michael Bayne
2025-08-08 10:34:03 -07:00
parent af792182c1
commit 8fdc7431de
10 changed files with 1061 additions and 37 deletions
+3 -9
View File
@@ -32,15 +32,9 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.lwjgl.lwjgl</groupId> <groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId> <artifactId>lwjgl-openal</artifactId>
<version>2.9.3</version> <version>3.3.6</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.lwjgl.lwjgl</groupId>
<artifactId>lwjgl_util</artifactId>
<version>2.9.3</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
@@ -22,7 +22,7 @@ package com.threerings.openal;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.lwjgl.openal.AL10; import org.lwjgl.openal.AL10;
import org.lwjgl.util.WaveData; import com.threerings.openal.util.WaveData;
/** /**
* Contains data for a single sampled sound. * Contains data for a single sampled sound.
@@ -19,9 +19,6 @@
package com.threerings.openal; package com.threerings.openal;
import java.nio.FloatBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL10; import org.lwjgl.openal.AL10;
/** /**
@@ -89,9 +86,13 @@ public class Listener
public void setOrientation (float ax, float ay, float az, float ux, float uy, float uz) public void setOrientation (float ax, float ay, float az, float ux, float uy, float uz)
{ {
if (_ax != ax || _ay != ay || _az != az || _ux != ux || _uy != uy || _uz != uz) { if (_ax != ax || _ay != ay || _az != az || _ux != ux || _uy != uy || _uz != uz) {
_vbuf.put(_ax = ax).put(_ay = ay).put(_az = az); _vbuf[0] = (_ax = ax);
_vbuf.put(_ux = ux).put(_uy = uy).put(_uz = uz).rewind(); _vbuf[1] = (_ay = ay);
AL10.alListener(AL10.AL_ORIENTATION, _vbuf); _vbuf[2] = (_az = az);
_vbuf[3] = (_ux = ux);
_vbuf[4] = (_uy = uy);
_vbuf[5] = (_uz = uz);
AL10.alListenerfv(AL10.AL_ORIENTATION, _vbuf);
} }
} }
@@ -115,5 +116,5 @@ public class Listener
protected float _ax, _ay, _az = -1f, _ux, _uy = 1f, _uz; protected float _ax, _ay, _az = -1f, _ux, _uy = 1f, _uz;
/** A buffer for floating point values. */ /** A buffer for floating point values. */
protected FloatBuffer _vbuf = BufferUtils.createFloatBuffer(6); protected float[] _vbuf = new float[6];
} }
@@ -34,7 +34,7 @@ import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem; import javax.sound.sampled.AudioSystem;
import org.lwjgl.openal.AL10; import org.lwjgl.openal.AL10;
import org.lwjgl.util.WaveData; import com.threerings.openal.util.WaveData;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@@ -23,11 +23,15 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.nio.ByteBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10; import org.lwjgl.openal.AL10;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.ALC10;
import org.lwjgl.openal.ALC;
import org.lwjgl.openal.ALCCapabilities;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@@ -77,8 +81,13 @@ public class SoundManager
*/ */
public void shutdown () public void shutdown ()
{ {
if (isInitialized() && !_sharingAL) { if (_alcContext != 0L) {
AL.destroy(); ALC10.alcDestroyContext(_alcContext);
_alcContext = 0L;
}
if (_alcDevice != 0L) {
ALC10.alcCloseDevice(_alcDevice);
_alcDevice = 0L;
} }
} }
@@ -201,11 +210,20 @@ public class SoundManager
_rqueue = rqueue; _rqueue = rqueue;
// initialize the OpenAL sound system // initialize the OpenAL sound system
if (AL.isCreated()) {
_sharingAL = true;
} else {
try { try {
AL.create("", 44100, 15, false); _alcDevice = ALC10.alcOpenDevice((ByteBuffer)null);
if (_alcDevice == 0) {
log.warning("Failed to create AL device.");
// don't start the background loading thread
return;
}
ALCCapabilities deviceCaps = ALC.createCapabilities(_alcDevice);
_alcContext = ALC10.alcCreateContext(_alcDevice, (IntBuffer)null);
ALC10.alcMakeContextCurrent(_alcContext);
AL.createCapabilities(deviceCaps);
// AL.create("", 44100, 15, false);
} catch (Exception e) { } catch (Exception e) {
log.warning("Failed to initialize sound system.", e); log.warning("Failed to initialize sound system.", e);
// don't start the background loading thread // don't start the background loading thread
@@ -218,7 +236,6 @@ public class SoundManager
// don't start the background loading thread // don't start the background loading thread
return; return;
} }
}
// configure our LRU map with a removal observer // configure our LRU map with a removal observer
_clips.setRemovalObserver(new LRUHashMap.RemovalObserver<String, ClipBuffer>() { _clips.setRemovalObserver(new LRUHashMap.RemovalObserver<String, ClipBuffer>() {
@@ -415,8 +432,8 @@ public class SoundManager
} }
}; };
/** Whether or not we're sharing an AL context that someone else created. */ protected long _alcDevice;
protected boolean _sharingAL; protected long _alcContext;
/** Used to get back from the background thread to our "main" thread. */ /** Used to get back from the background thread to our "main" thread. */
protected RunQueue _rqueue; protected RunQueue _rqueue;
@@ -21,7 +21,7 @@ package com.threerings.openal;
import java.io.IOException; import java.io.IOException;
import org.lwjgl.util.WaveData; import com.threerings.openal.util.WaveData;
/** /**
* Loads clips via the class loader using LWJGL's {@link WaveData} utility * Loads clips via the class loader using LWJGL's {@link WaveData} utility
@@ -0,0 +1,256 @@
/*
* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.threerings.openal.util;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.DataInputStream;
import java.net.URL;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.sound.sampled.spi.AudioFileReader;
/**
* Abstract File Reader class.
*
* @author Jan Borgersen
*/
abstract class SunFileReader extends AudioFileReader {
// buffer size for temporary input streams
protected static final int bisBufferSize = 4096;
/**
* Constructs a new SunFileReader object.
*/
SunFileReader() {
}
// METHODS TO IMPLEMENT AudioFileReader
/**
* Obtains the audio file format of the input stream provided. The stream must
* point to valid audio file data. In general, audio file providers may
* need to read some data from the stream before determining whether they
* support it. These parsers must
* be able to mark the stream, read enough data to determine whether they
* support the stream, and, if not, reset the stream's read pointer to its original
* position. If the input stream does not support this, this method may fail
* with an IOException.
* @param stream the input stream from which file format information should be
* extracted
* @return an <code>AudioFileFormat</code> object describing the audio file format
* @throws UnsupportedAudioFileException if the stream does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
* @see InputStream#markSupported
* @see InputStream#mark
*/
abstract public AudioFileFormat getAudioFileFormat(InputStream stream) throws UnsupportedAudioFileException, IOException;
/**
* Obtains the audio file format of the URL provided. The URL must
* point to valid audio file data.
* @param url the URL from which file format information should be
* extracted
* @return an <code>AudioFileFormat</code> object describing the audio file format
* @throws UnsupportedAudioFileException if the URL does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
*/
abstract public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException;
/**
* Obtains the audio file format of the File provided. The File must
* point to valid audio file data.
* @param file the File from which file format information should be
* extracted
* @return an <code>AudioFileFormat</code> object describing the audio file format
* @throws UnsupportedAudioFileException if the File does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
*/
abstract public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException;
/**
* Obtains an audio stream from the input stream provided. The stream must
* point to valid audio file data. In general, audio file providers may
* need to read some data from the stream before determining whether they
* support it. These parsers must
* be able to mark the stream, read enough data to determine whether they
* support the stream, and, if not, reset the stream's read pointer to its original
* position. If the input stream does not support this, this method may fail
* with an IOException.
* @param stream the input stream from which the <code>AudioInputStream</code> should be
* constructed
* @return an <code>AudioInputStream</code> object based on the audio file data contained
* in the input stream.
* @throws UnsupportedAudioFileException if the stream does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
* @see InputStream#markSupported
* @see InputStream#mark
*/
abstract public AudioInputStream getAudioInputStream(InputStream stream) throws UnsupportedAudioFileException, IOException;
/**
* Obtains an audio stream from the URL provided. The URL must
* point to valid audio file data.
* @param url the URL for which the <code>AudioInputStream</code> should be
* constructed
* @return an <code>AudioInputStream</code> object based on the audio file data pointed
* to by the URL
* @throws UnsupportedAudioFileException if the URL does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
*/
abstract public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException;
/**
* Obtains an audio stream from the File provided. The File must
* point to valid audio file data.
* @param file the File for which the <code>AudioInputStream</code> should be
* constructed
* @return an <code>AudioInputStream</code> object based on the audio file data pointed
* to by the File
* @throws UnsupportedAudioFileException if the File does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
*/
abstract public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException;
// HELPER METHODS
/**
* rllong
* Protected helper method to read 64 bits and changing the order of
* each bytes.
* @param DataInputStream
* @return 32 bits swapped value.
* @exception IOException
*/
final int rllong(DataInputStream dis) throws IOException {
int b1, b2, b3, b4 ;
int i = 0;
i = dis.readInt();
b1 = ( i & 0xFF ) << 24 ;
b2 = ( i & 0xFF00 ) << 8;
b3 = ( i & 0xFF0000 ) >> 8;
b4 = ( i & 0xFF000000 ) >>> 24;
i = ( b1 | b2 | b3 | b4 );
return i;
}
/**
* big2little
* Protected helper method to swap the order of bytes in a 32 bit int
* @param int
* @return 32 bits swapped value
*/
final int big2little(int i) {
int b1, b2, b3, b4 ;
b1 = ( i & 0xFF ) << 24 ;
b2 = ( i & 0xFF00 ) << 8;
b3 = ( i & 0xFF0000 ) >> 8;
b4 = ( i & 0xFF000000 ) >>> 24;
i = ( b1 | b2 | b3 | b4 );
return i;
}
/**
* rlshort
* Protected helper method to read 16 bits value. Swap high with low byte.
* @param DataInputStream
* @return the swapped value.
* @exception IOException
*/
final short rlshort(DataInputStream dis) throws IOException {
short s=0;
short high, low;
s = dis.readShort();
high = (short)(( s & 0xFF ) << 8) ;
low = (short)(( s & 0xFF00 ) >>> 8);
s = (short)( high | low );
return s;
}
/**
* big2little
* Protected helper method to swap the order of bytes in a 16 bit short
* @param int
* @return 16 bits swapped value
*/
final short big2littleShort(short i) {
short high, low;
high = (short)(( i & 0xFF ) << 8) ;
low = (short)(( i & 0xFF00 ) >>> 8);
i = (short)( high | low );
return i;
}
/** Calculates the frame size for PCM frames.
* Note that this method is appropriate for non-packed samples.
* For instance, 12 bit, 2 channels will return 4 bytes, not 3.
* @param sampleSizeInBits the size of a single sample in bits
* @param channels the number of channels
* @return the size of a PCM frame in bytes.
*/
static final int calculatePCMFrameSize(int sampleSizeInBits, int channels) {
return ((sampleSizeInBits + 7) / 8) * channels;
}
}
@@ -0,0 +1,257 @@
/*
* Copyright (c) 2002-2008 LWJGL Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'LWJGL' nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.threerings.openal.util;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import org.lwjgl.openal.AL10;
import static com.threerings.openal.Log.log;
/**
*
* Utitlity class for loading wavefiles.
*
* @author Brian Matzon <brian@matzon.dk>
* @version $Revision$
* $Id$
*/
public class WaveData {
/** actual wave data */
public final ByteBuffer data;
/** format type of data */
public final int format;
/** sample rate of data */
public final int samplerate;
/**
* Creates a new WaveData
*
* @param data actual wavedata
* @param format format of wave data
* @param samplerate sample rate of data
*/
private WaveData(ByteBuffer data, int format, int samplerate) {
this.data = data;
this.format = format;
this.samplerate = samplerate;
}
/**
* Disposes the wavedata
*/
public void dispose() {
data.clear();
}
/**
* Creates a WaveData container from the specified url
*
* @param path URL to file
* @return WaveData containing data, or null if a failure occured
*/
public static WaveData create(URL path) {
try {
// due to an issue with AudioSystem.getAudioInputStream
// and mixing unsigned and signed code
// we will use the reader directly
WaveFileReader wfr = new WaveFileReader();
return create(wfr.getAudioInputStream(new BufferedInputStream(path.openStream())));
} catch (Exception e) {
log.warning("Unable to create from: " + path + ", " + e.getMessage());
return null;
}
}
/**
* Creates a WaveData container from the specified in the classpath
*
* @param path path to file (relative, and in classpath)
* @return WaveData containing data, or null if a failure occured
*/
public static WaveData create(String path) {
return create(Thread.currentThread().getContextClassLoader().getResource(path));
}
/**
* Creates a WaveData container from the specified inputstream
*
* @param is InputStream to read from
* @return WaveData containing data, or null if a failure occured
*/
public static WaveData create(InputStream is) {
try {
return create(
AudioSystem.getAudioInputStream(is));
} catch (Exception e) {
log.warning("Unable to create from inputstream, " + e.getMessage());
return null;
}
}
/**
* Creates a WaveData container from the specified bytes
*
* @param buffer array of bytes containing the complete wave file
* @return WaveData containing data, or null if a failure occured
*/
public static WaveData create(byte[] buffer) {
try {
return create(
AudioSystem.getAudioInputStream(
new BufferedInputStream(new ByteArrayInputStream(buffer))));
} catch (Exception e) {
log.warning("Unable to create from byte array, " + e.getMessage());
return null;
}
}
/**
* Creates a WaveData container from the specified ByetBuffer.
* If the buffer is backed by an array, it will be used directly,
* else the contents of the buffer will be copied using get(byte[]).
*
* @param buffer ByteBuffer containing sound file
* @return WaveData containing data, or null if a failure occured
*/
public static WaveData create(ByteBuffer buffer) {
try {
byte[] bytes = null;
if(buffer.hasArray()) {
bytes = buffer.array();
} else {
bytes = new byte[buffer.capacity()];
buffer.get(bytes);
}
return create(bytes);
} catch (Exception e) {
log.warning("Unable to create from ByteBuffer, " + e.getMessage());
return null;
}
}
/**
* Creates a WaveData container from the specified stream
*
* @param ais AudioInputStream to read from
* @return WaveData containing data, or null if a failure occured
*/
public static WaveData create(AudioInputStream ais) {
//get format of data
AudioFormat audioformat = ais.getFormat();
// get channels
int channels = 0;
if (audioformat.getChannels() == 1) {
if (audioformat.getSampleSizeInBits() == 8) {
channels = AL10.AL_FORMAT_MONO8;
} else if (audioformat.getSampleSizeInBits() == 16) {
channels = AL10.AL_FORMAT_MONO16;
} else {
assert false : "Illegal sample size";
}
} else if (audioformat.getChannels() == 2) {
if (audioformat.getSampleSizeInBits() == 8) {
channels = AL10.AL_FORMAT_STEREO8;
} else if (audioformat.getSampleSizeInBits() == 16) {
channels = AL10.AL_FORMAT_STEREO16;
} else {
assert false : "Illegal sample size";
}
} else {
assert false : "Only mono or stereo is supported";
}
//read data into buffer
ByteBuffer buffer = null;
try {
int available = ais.available();
if(available <= 0) {
available = ais.getFormat().getChannels() * (int) ais.getFrameLength() * ais.getFormat().getSampleSizeInBits() / 8;
}
byte[] buf = new byte[ais.available()];
int read = 0, total = 0;
while ((read = ais.read(buf, total, buf.length - total)) != -1
&& total < buf.length) {
total += read;
}
buffer = convertAudioBytes(buf, audioformat.getSampleSizeInBits() == 16, audioformat.isBigEndian() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
} catch (IOException ioe) {
return null;
}
//create our result
WaveData wavedata =
new WaveData(buffer, channels, (int) audioformat.getSampleRate());
//close stream
try {
ais.close();
} catch (IOException ioe) {
}
return wavedata;
}
private static ByteBuffer convertAudioBytes(byte[] audio_bytes, boolean two_bytes_data, ByteOrder order) {
ByteBuffer dest = ByteBuffer.allocateDirect(audio_bytes.length);
dest.order(ByteOrder.nativeOrder());
ByteBuffer src = ByteBuffer.wrap(audio_bytes);
src.order(order);
if (two_bytes_data) {
ShortBuffer dest_short = dest.asShortBuffer();
ShortBuffer src_short = src.asShortBuffer();
while (src_short.hasRemaining())
dest_short.put(src_short.get());
} else {
while (src.hasRemaining())
dest.put(src.get());
}
dest.rewind();
return dest;
}
}
@@ -0,0 +1,124 @@
/*
* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.threerings.openal.util;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
/**
* WAVE file format class.
*
* @author Jan Borgersen
*/
final class WaveFileFormat extends AudioFileFormat {
/**
* Wave format type.
*/
private final int waveType;
//$$fb 2001-07-13: added management of header size in this class
//$$fb 2002-04-16: Fix for 4636355: RIFF audio headers could be _more_ spec compliant
private static final int STANDARD_HEADER_SIZE = 28;
//$$fb 2002-04-16: Fix for 4636355: RIFF audio headers could be _more_ spec compliant
/**
* fmt_ chunk size in bytes
*/
private static final int STANDARD_FMT_CHUNK_SIZE = 16;
// magic numbers
static final int RIFF_MAGIC = 1380533830;
static final int WAVE_MAGIC = 1463899717;
static final int FMT_MAGIC = 0x666d7420; // "fmt "
static final int DATA_MAGIC = 0x64617461; // "data"
// encodings
static final int WAVE_FORMAT_UNKNOWN = 0x0000;
static final int WAVE_FORMAT_PCM = 0x0001;
static final int WAVE_FORMAT_ADPCM = 0x0002;
static final int WAVE_FORMAT_ALAW = 0x0006;
static final int WAVE_FORMAT_MULAW = 0x0007;
static final int WAVE_FORMAT_OKI_ADPCM = 0x0010;
static final int WAVE_FORMAT_DIGISTD = 0x0015;
static final int WAVE_FORMAT_DIGIFIX = 0x0016;
static final int WAVE_IBM_FORMAT_MULAW = 0x0101;
static final int WAVE_IBM_FORMAT_ALAW = 0x0102;
static final int WAVE_IBM_FORMAT_ADPCM = 0x0103;
static final int WAVE_FORMAT_DVI_ADPCM = 0x0011;
static final int WAVE_FORMAT_SX7383 = 0x1C07;
WaveFileFormat( AudioFileFormat aff ) {
this( aff.getType(), aff.getByteLength(), aff.getFormat(), aff.getFrameLength() );
}
WaveFileFormat(AudioFileFormat.Type type, int lengthInBytes, AudioFormat format, int lengthInFrames) {
super(type,lengthInBytes,format,lengthInFrames);
AudioFormat.Encoding encoding = format.getEncoding();
if( encoding.equals(AudioFormat.Encoding.ALAW) ) {
waveType = WAVE_FORMAT_ALAW;
} else if( encoding.equals(AudioFormat.Encoding.ULAW) ) {
waveType = WAVE_FORMAT_MULAW;
} else if( encoding.equals(AudioFormat.Encoding.PCM_SIGNED) ||
encoding.equals(AudioFormat.Encoding.PCM_UNSIGNED) ) {
waveType = WAVE_FORMAT_PCM;
} else {
waveType = WAVE_FORMAT_UNKNOWN;
}
}
int getWaveType() {
return waveType;
}
int getHeaderSize() {
return getHeaderSize(getWaveType());
}
static int getHeaderSize(int waveType) {
//$$fb 2002-04-16: Fix for 4636355: RIFF audio headers could be _more_ spec compliant
// use dynamic format chunk size
return STANDARD_HEADER_SIZE + getFmtChunkSize(waveType);
}
static int getFmtChunkSize(int waveType) {
//$$fb 2002-04-16: Fix for 4636355: RIFF audio headers could be _more_ spec compliant
// add 2 bytes for "codec specific data length" for non-PCM codecs
int result = STANDARD_FMT_CHUNK_SIZE;
if (waveType != WAVE_FORMAT_PCM) {
result += 2; // WORD for "codec specific data length"
}
return result;
}
}
@@ -0,0 +1,375 @@
/*
* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.threerings.openal.util;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
/**
* WAVE file reader.
*
* @author Kara Kytle
* @author Jan Borgersen
* @author Florian Bomers
*/
public final class WaveFileReader extends SunFileReader {
private static final int MAX_READ_LENGTH = 12;
/**
* Obtains the audio file format of the input stream provided. The stream must
* point to valid audio file data. In general, audio file providers may
* need to read some data from the stream before determining whether they
* support it. These parsers must
* be able to mark the stream, read enough data to determine whether they
* support the stream, and, if not, reset the stream's read pointer to its original
* position. If the input stream does not support this, this method may fail
* with an IOException.
* @param stream the input stream from which file format information should be
* extracted
* @return an <code>AudioFileFormat</code> object describing the audio file format
* @throws UnsupportedAudioFileException if the stream does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
* @see InputStream#markSupported
* @see InputStream#mark
*/
public AudioFileFormat getAudioFileFormat(InputStream stream) throws UnsupportedAudioFileException, IOException {
// fix for 4489272: AudioSystem.getAudioFileFormat() fails for InputStream, but works for URL
AudioFileFormat aff = getFMT(stream, true);
// the following is not strictly necessary - but was implemented like that in 1.3.0 - 1.4.1
// so I leave it as it was. May remove this for 1.5.0
stream.reset();
return aff;
}
/**
* Obtains the audio file format of the URL provided. The URL must
* point to valid audio file data.
* @param url the URL from which file format information should be
* extracted
* @return an <code>AudioFileFormat</code> object describing the audio file format
* @throws UnsupportedAudioFileException if the URL does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
*/
public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException {
InputStream urlStream = url.openStream(); // throws IOException
AudioFileFormat fileFormat = null;
try {
fileFormat = getFMT(urlStream, false);
} finally {
urlStream.close();
}
return fileFormat;
}
/**
* Obtains the audio file format of the File provided. The File must
* point to valid audio file data.
* @param file the File from which file format information should be
* extracted
* @return an <code>AudioFileFormat</code> object describing the audio file format
* @throws UnsupportedAudioFileException if the File does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
*/
public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException {
AudioFileFormat fileFormat = null;
FileInputStream fis = new FileInputStream(file); // throws IOException
// part of fix for 4325421
try {
fileFormat = getFMT(fis, false);
} finally {
fis.close();
}
return fileFormat;
}
/**
* Obtains an audio stream from the input stream provided. The stream must
* point to valid audio file data. In general, audio file providers may
* need to read some data from the stream before determining whether they
* support it. These parsers must
* be able to mark the stream, read enough data to determine whether they
* support the stream, and, if not, reset the stream's read pointer to its original
* position. If the input stream does not support this, this method may fail
* with an IOException.
* @param stream the input stream from which the <code>AudioInputStream</code> should be
* constructed
* @return an <code>AudioInputStream</code> object based on the audio file data contained
* in the input stream.
* @throws UnsupportedAudioFileException if the stream does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
* @see InputStream#markSupported
* @see InputStream#mark
*/
public AudioInputStream getAudioInputStream(InputStream stream) throws UnsupportedAudioFileException, IOException {
// getFMT leaves the input stream at the beginning of the audio data
AudioFileFormat fileFormat = getFMT(stream, true); // throws UnsupportedAudioFileException, IOException
// we've got everything, and the stream is at the
// beginning of the audio data, so return an AudioInputStream.
return new AudioInputStream(stream, fileFormat.getFormat(), fileFormat.getFrameLength());
}
/**
* Obtains an audio stream from the URL provided. The URL must
* point to valid audio file data.
* @param url the URL for which the <code>AudioInputStream</code> should be
* constructed
* @return an <code>AudioInputStream</code> object based on the audio file data pointed
* to by the URL
* @throws UnsupportedAudioFileException if the URL does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
*/
public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException {
InputStream urlStream = url.openStream(); // throws IOException
AudioFileFormat fileFormat = null;
try {
fileFormat = getFMT(urlStream, false);
} finally {
if (fileFormat == null) {
urlStream.close();
}
}
return new AudioInputStream(urlStream, fileFormat.getFormat(), fileFormat.getFrameLength());
}
/**
* Obtains an audio stream from the File provided. The File must
* point to valid audio file data.
* @param file the File for which the <code>AudioInputStream</code> should be
* constructed
* @return an <code>AudioInputStream</code> object based on the audio file data pointed
* to by the File
* @throws UnsupportedAudioFileException if the File does not point to valid audio
* file data recognized by the system
* @throws IOException if an I/O exception occurs
*/
public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException {
FileInputStream fis = new FileInputStream(file); // throws IOException
AudioFileFormat fileFormat = null;
// part of fix for 4325421
try {
fileFormat = getFMT(fis, false);
} finally {
if (fileFormat == null) {
fis.close();
}
}
return new AudioInputStream(fis, fileFormat.getFormat(), fileFormat.getFrameLength());
}
//--------------------------------------------------------------------
private AudioFileFormat getFMT(InputStream stream, boolean doReset) throws UnsupportedAudioFileException, IOException {
// assumes sream is rewound
int bytesRead;
int nread = 0;
int fmt;
int length = 0;
int wav_type = 0;
short channels;
long sampleRate;
long avgBytesPerSec;
short blockAlign;
int sampleSizeInBits;
AudioFormat.Encoding encoding = null;
DataInputStream dis = new DataInputStream( stream );
if (doReset) {
dis.mark(MAX_READ_LENGTH);
}
int magic = dis.readInt();
int fileLength = rllong(dis);
int waveMagic = dis.readInt();
int totallength;
if (fileLength <= 0) {
fileLength = AudioSystem.NOT_SPECIFIED;
totallength = AudioSystem.NOT_SPECIFIED;
} else {
totallength = fileLength + 8;
}
if ((magic != WaveFileFormat.RIFF_MAGIC) || (waveMagic != WaveFileFormat.WAVE_MAGIC)) {
// not WAVE, throw UnsupportedAudioFileException
if (doReset) {
dis.reset();
}
throw new UnsupportedAudioFileException("not a WAVE file");
}
// find and read the "fmt" chunk
// we break out of this loop either by hitting EOF or finding "fmt "
while(true) {
try {
fmt = dis.readInt();
nread += 4;
if( fmt==WaveFileFormat.FMT_MAGIC ) {
// we've found the 'fmt' chunk
break;
} else {
// else not 'fmt', skip this chunk
length = rllong(dis);
nread += 4;
if (length % 2 > 0) length++;
nread += dis.skipBytes(length);
}
} catch (EOFException eof) {
// we've reached the end of the file without finding the 'fmt' chunk
throw new UnsupportedAudioFileException("Not a valid WAV file");
}
}
// Read the format chunk size.
length = rllong(dis);
nread += 4;
// This is the nread position at the end of the format chunk
int endLength = nread + length;
// Read the wave format data out of the format chunk.
// encoding.
wav_type = rlshort(dis); nread += 2;
if (wav_type == WaveFileFormat.WAVE_FORMAT_PCM)
encoding = AudioFormat.Encoding.PCM_SIGNED; // if 8-bit, we need PCM_UNSIGNED, below...
else if ( wav_type == WaveFileFormat.WAVE_FORMAT_ALAW )
encoding = AudioFormat.Encoding.ALAW;
else if ( wav_type == WaveFileFormat.WAVE_FORMAT_MULAW )
encoding = AudioFormat.Encoding.ULAW;
else {
// we don't support any other WAVE formats....
throw new UnsupportedAudioFileException("Not a supported WAV file");
}
// channels
channels = rlshort(dis); nread += 2;
if (channels <= 0) {
throw new UnsupportedAudioFileException("Invalid number of channels");
}
// sample rate.
sampleRate = rllong(dis); nread += 4;
// this is the avgBytesPerSec
avgBytesPerSec = rllong(dis); nread += 4;
// this is blockAlign value
blockAlign = rlshort(dis); nread += 2;
// this is the PCM-specific value bitsPerSample
sampleSizeInBits = (int)rlshort(dis); nread += 2;
if (sampleSizeInBits <= 0) {
throw new UnsupportedAudioFileException("Invalid bitsPerSample");
}
// if sampleSizeInBits==8, we need to use PCM_UNSIGNED
if ((sampleSizeInBits==8) && encoding.equals(AudioFormat.Encoding.PCM_SIGNED))
encoding = AudioFormat.Encoding.PCM_UNSIGNED;
// skip any difference between the length of the format chunk
// and what we read
// if the length of the chunk is odd, there's an extra pad byte
// at the end. i've never seen this in the fmt chunk, but we
// should check to make sure.
if (length % 2 != 0) length += 1;
// $$jb: 07.28.99: endLength>nread, not length>nread.
// This fixes #4257986
if (endLength > nread)
nread += dis.skipBytes(endLength - nread);
// we have a format now, so find the "data" chunk
// we break out of this loop either by hitting EOF or finding "data"
// $$kk: if "data" chunk precedes "fmt" chunk we are hosed -- can this legally happen?
nread = 0;
while(true) {
try{
int datahdr = dis.readInt();
nread+=4;
if (datahdr == WaveFileFormat.DATA_MAGIC) {
// we've found the 'data' chunk
break;
} else {
// else not 'data', skip this chunk
int thisLength = rllong(dis); nread += 4;
if (thisLength % 2 > 0) thisLength++;
nread += dis.skipBytes(thisLength);
}
} catch (EOFException eof) {
// we've reached the end of the file without finding the 'data' chunk
throw new UnsupportedAudioFileException("Not a valid WAV file");
}
}
// this is the length of the data chunk
int dataLength = rllong(dis); nread += 4;
// now build the new AudioFileFormat and return
AudioFormat format = new AudioFormat(encoding,
(float)sampleRate,
sampleSizeInBits, channels,
calculatePCMFrameSize(sampleSizeInBits, channels),
(float)sampleRate, false);
return new WaveFileFormat(AudioFileFormat.Type.WAVE,
totallength,
format,
dataLength / format.getFrameSize());
}
}