|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
package com.sun.media.sound; |
|
|
|
import java.io.IOException; |
|
import java.io.InputStream; |
|
import java.util.ArrayList; |
|
import java.util.Arrays; |
|
|
|
import javax.sound.sampled.AudioFormat; |
|
import javax.sound.sampled.AudioInputStream; |
|
import javax.sound.sampled.AudioSystem; |
|
import javax.sound.sampled.AudioFormat.Encoding; |
|
import javax.sound.sampled.spi.FormatConversionProvider; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final class AudioFloatFormatConverter extends FormatConversionProvider { |
|
|
|
private static class AudioFloatFormatConverterInputStream extends |
|
InputStream { |
|
private final AudioFloatConverter converter; |
|
|
|
private final AudioFloatInputStream stream; |
|
|
|
private float[] readfloatbuffer; |
|
|
|
private final int fsize; |
|
|
|
AudioFloatFormatConverterInputStream(AudioFormat targetFormat, |
|
AudioFloatInputStream stream) { |
|
this.stream = stream; |
|
converter = AudioFloatConverter.getConverter(targetFormat); |
|
fsize = ((targetFormat.getSampleSizeInBits() + 7) / 8); |
|
} |
|
|
|
public int read() throws IOException { |
|
byte[] b = new byte[1]; |
|
int ret = read(b); |
|
if (ret < 0) |
|
return ret; |
|
return b[0] & 0xFF; |
|
} |
|
|
|
public int read(byte[] b, int off, int len) throws IOException { |
|
|
|
int flen = len / fsize; |
|
if (readfloatbuffer == null || readfloatbuffer.length < flen) |
|
readfloatbuffer = new float[flen]; |
|
int ret = stream.read(readfloatbuffer, 0, flen); |
|
if (ret < 0) |
|
return ret; |
|
converter.toByteArray(readfloatbuffer, 0, ret, b, off); |
|
return ret * fsize; |
|
} |
|
|
|
public int available() throws IOException { |
|
int ret = stream.available(); |
|
if (ret < 0) |
|
return ret; |
|
return ret * fsize; |
|
} |
|
|
|
public void close() throws IOException { |
|
stream.close(); |
|
} |
|
|
|
public synchronized void mark(int readlimit) { |
|
stream.mark(readlimit * fsize); |
|
} |
|
|
|
public boolean markSupported() { |
|
return stream.markSupported(); |
|
} |
|
|
|
public synchronized void reset() throws IOException { |
|
stream.reset(); |
|
} |
|
|
|
public long skip(long n) throws IOException { |
|
long ret = stream.skip(n / fsize); |
|
if (ret < 0) |
|
return ret; |
|
return ret * fsize; |
|
} |
|
|
|
} |
|
|
|
private static class AudioFloatInputStreamChannelMixer extends |
|
AudioFloatInputStream { |
|
|
|
private final int targetChannels; |
|
|
|
private final int sourceChannels; |
|
|
|
private final AudioFloatInputStream ais; |
|
|
|
private final AudioFormat targetFormat; |
|
|
|
private float[] conversion_buffer; |
|
|
|
AudioFloatInputStreamChannelMixer(AudioFloatInputStream ais, |
|
int targetChannels) { |
|
this.sourceChannels = ais.getFormat().getChannels(); |
|
this.targetChannels = targetChannels; |
|
this.ais = ais; |
|
AudioFormat format = ais.getFormat(); |
|
targetFormat = new AudioFormat(format.getEncoding(), format |
|
.getSampleRate(), format.getSampleSizeInBits(), |
|
targetChannels, (format.getFrameSize() / sourceChannels) |
|
* targetChannels, format.getFrameRate(), format |
|
.isBigEndian()); |
|
} |
|
|
|
public int available() throws IOException { |
|
return (ais.available() / sourceChannels) * targetChannels; |
|
} |
|
|
|
public void close() throws IOException { |
|
ais.close(); |
|
} |
|
|
|
public AudioFormat getFormat() { |
|
return targetFormat; |
|
} |
|
|
|
public long getFrameLength() { |
|
return ais.getFrameLength(); |
|
} |
|
|
|
public void mark(int readlimit) { |
|
ais.mark((readlimit / targetChannels) * sourceChannels); |
|
} |
|
|
|
public boolean markSupported() { |
|
return ais.markSupported(); |
|
} |
|
|
|
public int read(float[] b, int off, int len) throws IOException { |
|
int len2 = (len / targetChannels) * sourceChannels; |
|
if (conversion_buffer == null || conversion_buffer.length < len2) |
|
conversion_buffer = new float[len2]; |
|
int ret = ais.read(conversion_buffer, 0, len2); |
|
if (ret < 0) |
|
return ret; |
|
if (sourceChannels == 1) { |
|
int cs = targetChannels; |
|
for (int c = 0; c < targetChannels; c++) { |
|
for (int i = 0, ix = off + c; i < len2; i++, ix += cs) { |
|
b[ix] = conversion_buffer[i]; |
|
} |
|
} |
|
} else if (targetChannels == 1) { |
|
int cs = sourceChannels; |
|
for (int i = 0, ix = off; i < len2; i += cs, ix++) { |
|
b[ix] = conversion_buffer[i]; |
|
} |
|
for (int c = 1; c < sourceChannels; c++) { |
|
for (int i = c, ix = off; i < len2; i += cs, ix++) { |
|
b[ix] += conversion_buffer[i]; |
|
} |
|
} |
|
float vol = 1f / ((float) sourceChannels); |
|
for (int i = 0, ix = off; i < len2; i += cs, ix++) { |
|
b[ix] *= vol; |
|
} |
|
} else { |
|
int minChannels = Math.min(sourceChannels, targetChannels); |
|
int off_len = off + len; |
|
int ct = targetChannels; |
|
int cs = sourceChannels; |
|
for (int c = 0; c < minChannels; c++) { |
|
for (int i = off + c, ix = c; i < off_len; i += ct, ix += cs) { |
|
b[i] = conversion_buffer[ix]; |
|
} |
|
} |
|
for (int c = minChannels; c < targetChannels; c++) { |
|
for (int i = off + c; i < off_len; i += ct) { |
|
b[i] = 0; |
|
} |
|
} |
|
} |
|
return (ret / sourceChannels) * targetChannels; |
|
} |
|
|
|
public void reset() throws IOException { |
|
ais.reset(); |
|
} |
|
|
|
public long skip(long len) throws IOException { |
|
long ret = ais.skip((len / targetChannels) * sourceChannels); |
|
if (ret < 0) |
|
return ret; |
|
return (ret / sourceChannels) * targetChannels; |
|
} |
|
|
|
} |
|
|
|
private static class AudioFloatInputStreamResampler extends |
|
AudioFloatInputStream { |
|
|
|
private final AudioFloatInputStream ais; |
|
|
|
private final AudioFormat targetFormat; |
|
|
|
private float[] skipbuffer; |
|
|
|
private SoftAbstractResampler resampler; |
|
|
|
private final float[] pitch = new float[1]; |
|
|
|
private final float[] ibuffer2; |
|
|
|
private final float[][] ibuffer; |
|
|
|
private float ibuffer_index = 0; |
|
|
|
private int ibuffer_len = 0; |
|
|
|
private final int nrofchannels; |
|
|
|
private float[][] cbuffer; |
|
|
|
private final int buffer_len = 512; |
|
|
|
private final int pad; |
|
|
|
private final int pad2; |
|
|
|
private final float[] ix = new float[1]; |
|
|
|
private final int[] ox = new int[1]; |
|
|
|
private float[][] mark_ibuffer = null; |
|
|
|
private float mark_ibuffer_index = 0; |
|
|
|
private int mark_ibuffer_len = 0; |
|
|
|
AudioFloatInputStreamResampler(AudioFloatInputStream ais, |
|
AudioFormat format) { |
|
this.ais = ais; |
|
AudioFormat sourceFormat = ais.getFormat(); |
|
targetFormat = new AudioFormat(sourceFormat.getEncoding(), format |
|
.getSampleRate(), sourceFormat.getSampleSizeInBits(), |
|
sourceFormat.getChannels(), sourceFormat.getFrameSize(), |
|
format.getSampleRate(), sourceFormat.isBigEndian()); |
|
nrofchannels = targetFormat.getChannels(); |
|
Object interpolation = format.getProperty("interpolation"); |
|
if (interpolation != null && (interpolation instanceof String)) { |
|
String resamplerType = (String) interpolation; |
|
if (resamplerType.equalsIgnoreCase("point")) |
|
this.resampler = new SoftPointResampler(); |
|
if (resamplerType.equalsIgnoreCase("linear")) |
|
this.resampler = new SoftLinearResampler2(); |
|
if (resamplerType.equalsIgnoreCase("linear1")) |
|
this.resampler = new SoftLinearResampler(); |
|
if (resamplerType.equalsIgnoreCase("linear2")) |
|
this.resampler = new SoftLinearResampler2(); |
|
if (resamplerType.equalsIgnoreCase("cubic")) |
|
this.resampler = new SoftCubicResampler(); |
|
if (resamplerType.equalsIgnoreCase("lanczos")) |
|
this.resampler = new SoftLanczosResampler(); |
|
if (resamplerType.equalsIgnoreCase("sinc")) |
|
this.resampler = new SoftSincResampler(); |
|
} |
|
if (resampler == null) |
|
resampler = new SoftLinearResampler2(); |
|
|
|
pitch[0] = sourceFormat.getSampleRate() / format.getSampleRate(); |
|
pad = resampler.getPadding(); |
|
pad2 = pad * 2; |
|
ibuffer = new float[nrofchannels][buffer_len + pad2]; |
|
ibuffer2 = new float[nrofchannels * buffer_len]; |
|
ibuffer_index = buffer_len + pad; |
|
ibuffer_len = buffer_len; |
|
} |
|
|
|
public int available() throws IOException { |
|
return 0; |
|
} |
|
|
|
public void close() throws IOException { |
|
ais.close(); |
|
} |
|
|
|
public AudioFormat getFormat() { |
|
return targetFormat; |
|
} |
|
|
|
public long getFrameLength() { |
|
return AudioSystem.NOT_SPECIFIED; |
|
} |
|
|
|
public void mark(int readlimit) { |
|
ais.mark((int) (readlimit * pitch[0])); |
|
mark_ibuffer_index = ibuffer_index; |
|
mark_ibuffer_len = ibuffer_len; |
|
if (mark_ibuffer == null) { |
|
mark_ibuffer = new float[ibuffer.length][ibuffer[0].length]; |
|
} |
|
for (int c = 0; c < ibuffer.length; c++) { |
|
float[] from = ibuffer[c]; |
|
float[] to = mark_ibuffer[c]; |
|
for (int i = 0; i < to.length; i++) { |
|
to[i] = from[i]; |
|
} |
|
} |
|
} |
|
|
|
public boolean markSupported() { |
|
return ais.markSupported(); |
|
} |
|
|
|
private void readNextBuffer() throws IOException { |
|
|
|
if (ibuffer_len == -1) |
|
return; |
|
|
|
for (int c = 0; c < nrofchannels; c++) { |
|
float[] buff = ibuffer[c]; |
|
int buffer_len_pad = ibuffer_len + pad2; |
|
for (int i = ibuffer_len, ix = 0; i < buffer_len_pad; i++, ix++) { |
|
buff[ix] = buff[i]; |
|
} |
|
} |
|
|
|
ibuffer_index -= (ibuffer_len); |
|
|
|
ibuffer_len = ais.read(ibuffer2); |
|
if (ibuffer_len >= 0) { |
|
while (ibuffer_len < ibuffer2.length) { |
|
int ret = ais.read(ibuffer2, ibuffer_len, ibuffer2.length |
|
- ibuffer_len); |
|
if (ret == -1) |
|
break; |
|
ibuffer_len += ret; |
|
} |
|
Arrays.fill(ibuffer2, ibuffer_len, ibuffer2.length, 0); |
|
ibuffer_len /= nrofchannels; |
|
} else { |
|
Arrays.fill(ibuffer2, 0, ibuffer2.length, 0); |
|
} |
|
|
|
int ibuffer2_len = ibuffer2.length; |
|
for (int c = 0; c < nrofchannels; c++) { |
|
float[] buff = ibuffer[c]; |
|
for (int i = c, ix = pad2; i < ibuffer2_len; i += nrofchannels, ix++) { |
|
buff[ix] = ibuffer2[i]; |
|
} |
|
} |
|
|
|
} |
|
|
|
public int read(float[] b, int off, int len) throws IOException { |
|
|
|
if (cbuffer == null || cbuffer[0].length < len / nrofchannels) { |
|
cbuffer = new float[nrofchannels][len / nrofchannels]; |
|
} |
|
if (ibuffer_len == -1) |
|
return -1; |
|
if (len < 0) |
|
return 0; |
|
int offlen = off + len; |
|
int remain = len / nrofchannels; |
|
int destPos = 0; |
|
int in_end = ibuffer_len; |
|
while (remain > 0) { |
|
if (ibuffer_len >= 0) { |
|
if (ibuffer_index >= (ibuffer_len + pad)) |
|
readNextBuffer(); |
|
in_end = ibuffer_len + pad; |
|
} |
|
|
|
if (ibuffer_len < 0) { |
|
in_end = pad2; |
|
if (ibuffer_index >= in_end) |
|
break; |
|
} |
|
|
|
if (ibuffer_index < 0) |
|
break; |
|
int preDestPos = destPos; |
|
for (int c = 0; c < nrofchannels; c++) { |
|
ix[0] = ibuffer_index; |
|
ox[0] = destPos; |
|
float[] buff = ibuffer[c]; |
|
resampler.interpolate(buff, ix, in_end, pitch, 0, |
|
cbuffer[c], ox, len / nrofchannels); |
|
} |
|
ibuffer_index = ix[0]; |
|
destPos = ox[0]; |
|
remain -= destPos - preDestPos; |
|
} |
|
for (int c = 0; c < nrofchannels; c++) { |
|
int ix = 0; |
|
float[] buff = cbuffer[c]; |
|
for (int i = c + off; i < offlen; i += nrofchannels) { |
|
b[i] = buff[ix++]; |
|
} |
|
} |
|
return len - remain * nrofchannels; |
|
} |
|
|
|
public void reset() throws IOException { |
|
ais.reset(); |
|
if (mark_ibuffer == null) |
|
return; |
|
ibuffer_index = mark_ibuffer_index; |
|
ibuffer_len = mark_ibuffer_len; |
|
for (int c = 0; c < ibuffer.length; c++) { |
|
float[] from = mark_ibuffer[c]; |
|
float[] to = ibuffer[c]; |
|
for (int i = 0; i < to.length; i++) { |
|
to[i] = from[i]; |
|
} |
|
} |
|
|
|
} |
|
|
|
public long skip(long len) throws IOException { |
|
if (len < 0) |
|
return 0; |
|
if (skipbuffer == null) |
|
skipbuffer = new float[1024 * targetFormat.getFrameSize()]; |
|
float[] l_skipbuffer = skipbuffer; |
|
long remain = len; |
|
while (remain > 0) { |
|
int ret = read(l_skipbuffer, 0, (int) Math.min(remain, |
|
skipbuffer.length)); |
|
if (ret < 0) { |
|
if (remain == len) |
|
return ret; |
|
break; |
|
} |
|
remain -= ret; |
|
} |
|
return len - remain; |
|
|
|
} |
|
|
|
} |
|
|
|
private final Encoding[] formats = {Encoding.PCM_SIGNED, |
|
Encoding.PCM_UNSIGNED, |
|
Encoding.PCM_FLOAT}; |
|
|
|
public AudioInputStream getAudioInputStream(Encoding targetEncoding, |
|
AudioInputStream sourceStream) { |
|
if (sourceStream.getFormat().getEncoding().equals(targetEncoding)) |
|
return sourceStream; |
|
AudioFormat format = sourceStream.getFormat(); |
|
int channels = format.getChannels(); |
|
Encoding encoding = targetEncoding; |
|
float samplerate = format.getSampleRate(); |
|
int bits = format.getSampleSizeInBits(); |
|
boolean bigendian = format.isBigEndian(); |
|
if (targetEncoding.equals(Encoding.PCM_FLOAT)) |
|
bits = 32; |
|
AudioFormat targetFormat = new AudioFormat(encoding, samplerate, bits, |
|
channels, channels * bits / 8, samplerate, bigendian); |
|
return getAudioInputStream(targetFormat, sourceStream); |
|
} |
|
|
|
public AudioInputStream getAudioInputStream(AudioFormat targetFormat, |
|
AudioInputStream sourceStream) { |
|
if (!isConversionSupported(targetFormat, sourceStream.getFormat())) |
|
throw new IllegalArgumentException("Unsupported conversion: " |
|
+ sourceStream.getFormat().toString() + " to " |
|
+ targetFormat.toString()); |
|
return getAudioInputStream(targetFormat, AudioFloatInputStream |
|
.getInputStream(sourceStream)); |
|
} |
|
|
|
public AudioInputStream getAudioInputStream(AudioFormat targetFormat, |
|
AudioFloatInputStream sourceStream) { |
|
|
|
if (!isConversionSupported(targetFormat, sourceStream.getFormat())) |
|
throw new IllegalArgumentException("Unsupported conversion: " |
|
+ sourceStream.getFormat().toString() + " to " |
|
+ targetFormat.toString()); |
|
if (targetFormat.getChannels() != sourceStream.getFormat() |
|
.getChannels()) |
|
sourceStream = new AudioFloatInputStreamChannelMixer(sourceStream, |
|
targetFormat.getChannels()); |
|
if (Math.abs(targetFormat.getSampleRate() |
|
- sourceStream.getFormat().getSampleRate()) > 0.000001) |
|
sourceStream = new AudioFloatInputStreamResampler(sourceStream, |
|
targetFormat); |
|
return new AudioInputStream(new AudioFloatFormatConverterInputStream( |
|
targetFormat, sourceStream), targetFormat, sourceStream |
|
.getFrameLength()); |
|
} |
|
|
|
public Encoding[] getSourceEncodings() { |
|
return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED, |
|
Encoding.PCM_FLOAT }; |
|
} |
|
|
|
public Encoding[] getTargetEncodings() { |
|
return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED, |
|
Encoding.PCM_FLOAT }; |
|
} |
|
|
|
public Encoding[] getTargetEncodings(AudioFormat sourceFormat) { |
|
if (AudioFloatConverter.getConverter(sourceFormat) == null) |
|
return new Encoding[0]; |
|
return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED, |
|
Encoding.PCM_FLOAT }; |
|
} |
|
|
|
public AudioFormat[] getTargetFormats(Encoding targetEncoding, |
|
AudioFormat sourceFormat) { |
|
if (AudioFloatConverter.getConverter(sourceFormat) == null) |
|
return new AudioFormat[0]; |
|
int channels = sourceFormat.getChannels(); |
|
|
|
ArrayList<AudioFormat> formats = new ArrayList<AudioFormat>(); |
|
|
|
if (targetEncoding.equals(Encoding.PCM_SIGNED)) |
|
formats.add(new AudioFormat(Encoding.PCM_SIGNED, |
|
AudioSystem.NOT_SPECIFIED, 8, channels, channels, |
|
AudioSystem.NOT_SPECIFIED, false)); |
|
if (targetEncoding.equals(Encoding.PCM_UNSIGNED)) |
|
formats.add(new AudioFormat(Encoding.PCM_UNSIGNED, |
|
AudioSystem.NOT_SPECIFIED, 8, channels, channels, |
|
AudioSystem.NOT_SPECIFIED, false)); |
|
|
|
for (int bits = 16; bits < 32; bits += 8) { |
|
if (targetEncoding.equals(Encoding.PCM_SIGNED)) { |
|
formats.add(new AudioFormat(Encoding.PCM_SIGNED, |
|
AudioSystem.NOT_SPECIFIED, bits, channels, channels |
|
* bits / 8, AudioSystem.NOT_SPECIFIED, false)); |
|
formats.add(new AudioFormat(Encoding.PCM_SIGNED, |
|
AudioSystem.NOT_SPECIFIED, bits, channels, channels |
|
* bits / 8, AudioSystem.NOT_SPECIFIED, true)); |
|
} |
|
if (targetEncoding.equals(Encoding.PCM_UNSIGNED)) { |
|
formats.add(new AudioFormat(Encoding.PCM_UNSIGNED, |
|
AudioSystem.NOT_SPECIFIED, bits, channels, channels |
|
* bits / 8, AudioSystem.NOT_SPECIFIED, true)); |
|
formats.add(new AudioFormat(Encoding.PCM_UNSIGNED, |
|
AudioSystem.NOT_SPECIFIED, bits, channels, channels |
|
* bits / 8, AudioSystem.NOT_SPECIFIED, false)); |
|
} |
|
} |
|
|
|
if (targetEncoding.equals(Encoding.PCM_FLOAT)) { |
|
formats.add(new AudioFormat(Encoding.PCM_FLOAT, |
|
AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4, |
|
AudioSystem.NOT_SPECIFIED, false)); |
|
formats.add(new AudioFormat(Encoding.PCM_FLOAT, |
|
AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4, |
|
AudioSystem.NOT_SPECIFIED, true)); |
|
formats.add(new AudioFormat(Encoding.PCM_FLOAT, |
|
AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8, |
|
AudioSystem.NOT_SPECIFIED, false)); |
|
formats.add(new AudioFormat(Encoding.PCM_FLOAT, |
|
AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8, |
|
AudioSystem.NOT_SPECIFIED, true)); |
|
} |
|
|
|
return formats.toArray(new AudioFormat[formats.size()]); |
|
} |
|
|
|
public boolean isConversionSupported(AudioFormat targetFormat, |
|
AudioFormat sourceFormat) { |
|
if (AudioFloatConverter.getConverter(sourceFormat) == null) |
|
return false; |
|
if (AudioFloatConverter.getConverter(targetFormat) == null) |
|
return false; |
|
if (sourceFormat.getChannels() <= 0) |
|
return false; |
|
if (targetFormat.getChannels() <= 0) |
|
return false; |
|
return true; |
|
} |
|
|
|
public boolean isConversionSupported(Encoding targetEncoding, |
|
AudioFormat sourceFormat) { |
|
if (AudioFloatConverter.getConverter(sourceFormat) == null) |
|
return false; |
|
for (int i = 0; i < formats.length; i++) { |
|
if (targetEncoding.equals(formats[i])) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
} |