9fee0ec4ca
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7101 a1c6a512-1295-4272-9138-f99709370657
696 lines
25 KiB
Java
696 lines
25 KiB
Java
/*
|
|
* FloatSampleTools.java
|
|
*
|
|
* This file is part of Tritonus: http://www.tritonus.org/
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2000,2004 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.share.sampled;
|
|
|
|
import java.util.*;
|
|
import javax.sound.sampled.*;
|
|
import org.tritonus.share.TDebug;
|
|
|
|
/**
|
|
* Utility functions for handling data in normalized float arrays.
|
|
* Each sample is linear in the range of [-1.0f, +1.0f].
|
|
* <p>
|
|
* Currently, the following bit sizes are supported:
|
|
* <ul>
|
|
* <li>8-bit
|
|
* <li>16-bit
|
|
* <li>packed 24-bit (stored in 3 bytes)
|
|
* <li>32-bit
|
|
* </ul>
|
|
* 8-bit data can be unsigned or signed. All other data is only
|
|
* supported in signed encoding.
|
|
*
|
|
* @see FloatSampleBuffer
|
|
* @author Florian Bomers
|
|
*/
|
|
|
|
public class FloatSampleTools {
|
|
|
|
/** default number of bits to be dithered: 0.7f */
|
|
public static final float DEFAULT_DITHER_BITS = 0.7f;
|
|
|
|
private static Random random = null;
|
|
|
|
// sample width (must be in order !)
|
|
static final int F_8=1;
|
|
static final int F_16=2;
|
|
static final int F_24=3;
|
|
static final int F_32=4;
|
|
static final int F_SAMPLE_WIDTH_MASK=F_8 | F_16 | F_24 | F_32;
|
|
|
|
// format bit-flags
|
|
static final int F_SIGNED=8;
|
|
static final int F_BIGENDIAN=16;
|
|
|
|
// supported formats
|
|
static final int CT_8S=F_8 | F_SIGNED;
|
|
static final int CT_8U=F_8;
|
|
static final int CT_16SB=F_16 | F_SIGNED | F_BIGENDIAN;
|
|
static final int CT_16SL=F_16 | F_SIGNED;
|
|
static final int CT_24SB=F_24 | F_SIGNED | F_BIGENDIAN;
|
|
static final int CT_24SL=F_24 | F_SIGNED;
|
|
static final int CT_32SB=F_32 | F_SIGNED | F_BIGENDIAN;
|
|
static final int CT_32SL=F_32 | F_SIGNED;
|
|
|
|
// ////////////////////////////// initialization /////////////////////////////// //
|
|
|
|
/** prevent instanciation */
|
|
private FloatSampleTools() {
|
|
}
|
|
|
|
|
|
// /////////////////// FORMAT / FORMAT TYPE /////////////////////////////////// //
|
|
|
|
/**
|
|
* only allow "packed" samples -- currently no support for 18, 20, 24_32 bits.
|
|
* @throws IllegalArgumentException
|
|
*/
|
|
static void checkSupportedSampleSize(int ssib, int channels, int frameSize) {
|
|
if ((ssib*channels) != frameSize * 8) {
|
|
throw new IllegalArgumentException("unsupported sample size: "+ssib
|
|
+" stored in "+(frameSize/channels)+" bytes.");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the formatType code from the given format.
|
|
* @throws IllegalArgumentException
|
|
*/
|
|
static int getFormatType(AudioFormat format) {
|
|
boolean signed = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);
|
|
if (!signed &&
|
|
!format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) {
|
|
throw new IllegalArgumentException
|
|
("unsupported encoding: only PCM encoding supported.");
|
|
}
|
|
if (!signed && format.getSampleSizeInBits() != 8) {
|
|
throw new IllegalArgumentException
|
|
("unsupported encoding: only 8-bit can be unsigned");
|
|
}
|
|
checkSupportedSampleSize(format.getSampleSizeInBits(),
|
|
format.getChannels(),
|
|
format.getFrameSize());
|
|
|
|
int formatType = getFormatType(format.getSampleSizeInBits(),
|
|
signed, format.isBigEndian());
|
|
return formatType;
|
|
}
|
|
|
|
/**
|
|
* @throws IllegalArgumentException
|
|
*/
|
|
static int getFormatType(int ssib, boolean signed, boolean bigEndian) {
|
|
int bytesPerSample=ssib/8;
|
|
int res=0;
|
|
if (ssib==8) {
|
|
res=F_8;
|
|
} else if (ssib==16) {
|
|
res=F_16;
|
|
} else if (ssib==24) {
|
|
res=F_24;
|
|
} else if (ssib==32) {
|
|
res=F_32;
|
|
}
|
|
if (res==0) {
|
|
throw new IllegalArgumentException
|
|
("FloatSampleBuffer: unsupported sample size of "
|
|
+ssib+" bits per sample.");
|
|
}
|
|
if (!signed && bytesPerSample>1) {
|
|
throw new IllegalArgumentException
|
|
("FloatSampleBuffer: unsigned samples larger than "
|
|
+"8 bit are not supported");
|
|
}
|
|
if (signed) {
|
|
res|=F_SIGNED;
|
|
}
|
|
if (bigEndian && (ssib!=8)) {
|
|
res|=F_BIGENDIAN;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int getSampleSize(int formatType) {
|
|
switch (formatType & F_SAMPLE_WIDTH_MASK) {
|
|
case F_8: return 1;
|
|
case F_16: return 2;
|
|
case F_24: return 3;
|
|
case F_32: return 4;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Return a string representation of this format
|
|
*/
|
|
static String formatType2Str(int formatType) {
|
|
String res=""+formatType+": ";
|
|
switch (formatType & F_SAMPLE_WIDTH_MASK) {
|
|
case F_8:
|
|
res+="8bit";
|
|
break;
|
|
case F_16:
|
|
res+="16bit";
|
|
break;
|
|
case F_24:
|
|
res+="24bit";
|
|
break;
|
|
case F_32:
|
|
res+="32bit";
|
|
break;
|
|
}
|
|
res+=((formatType & F_SIGNED)==F_SIGNED)?" signed":" unsigned";
|
|
if ((formatType & F_SAMPLE_WIDTH_MASK)!=F_8) {
|
|
res+=((formatType & F_BIGENDIAN)==F_BIGENDIAN)?
|
|
" big endian":" little endian";
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
// /////////////////// BYTE 2 FLOAT /////////////////////////////////// //
|
|
|
|
private static final float twoPower7=128.0f;
|
|
private static final float twoPower15=32768.0f;
|
|
private static final float twoPower23=8388608.0f;
|
|
private static final float twoPower31=2147483648.0f;
|
|
|
|
private static final float invTwoPower7=1/twoPower7;
|
|
private static final float invTwoPower15=1/twoPower15;
|
|
private static final float invTwoPower23=1/twoPower23;
|
|
private static final float invTwoPower31=1/twoPower31;
|
|
|
|
|
|
/**
|
|
* Conversion function to convert an interleaved byte array to
|
|
* a List of interleaved float arrays. The float arrays will contain normalized
|
|
* samples in the range [-1.0f, +1.0f]. The input array
|
|
* provides bytes in the format specified in <code>format</code>.
|
|
* <p>
|
|
* Only PCM formats are accepted. The method will convert all
|
|
* byte values from
|
|
* <code>input[inByteOffset]</code> to
|
|
* <code>input[inByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
|
|
* to floats from
|
|
* <code>output(n)[outOffset]</code> to
|
|
* <code>output(n)[outOffset + frameCount - 1]</code>
|
|
*
|
|
* @param input the audio data in an byte array
|
|
* @param inByteOffset index in input where to start the conversion
|
|
* @param output list of float[] arrays which receive the converted audio data.
|
|
* if the list does not contain enough elements, or individual float arrays
|
|
* are not large enough, they are created.
|
|
* @param outOffset the start offset in <code>output</code>
|
|
* @param frameCount number of frames to be converted
|
|
* @param format the input format. Only packed PCM is allowed
|
|
* @throws IllegalArgumentException if one of the parameters is out of bounds
|
|
*
|
|
* @see #byte2floatInterleaved(byte[],int,float[],int,int,AudioFormat)
|
|
*/
|
|
public static void byte2float(byte[] input, int inByteOffset,
|
|
List<float[]> output, int outOffset, int frameCount,
|
|
//List output, int outOffset, int frameCount,
|
|
AudioFormat format) {
|
|
for (int channel = 0; channel < format.getChannels(); channel++) {
|
|
float[] data;
|
|
if (output.size() < channel) {
|
|
data = new float[frameCount + outOffset];
|
|
output.add(data);
|
|
} else {
|
|
data = output.get(channel);
|
|
if (data.length < frameCount + outOffset) {
|
|
data = new float[frameCount + outOffset];
|
|
output.set(channel, data);
|
|
}
|
|
}
|
|
|
|
byte2floatGeneric(input, inByteOffset, format.getFrameSize(),
|
|
data, outOffset,
|
|
frameCount, format);
|
|
inByteOffset += format.getFrameSize() / format.getChannels();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Conversion function to convert an interleaved byte array to
|
|
* an interleaved float array. The float array will contain normalized
|
|
* samples in the range [-1.0f, +1.0f]. The input array
|
|
* provides bytes in the format specified in <code>format</code>.
|
|
* <p>
|
|
* Only PCM formats are accepted. The method will convert all
|
|
* byte values from
|
|
* <code>input[inByteOffset]</code> to
|
|
* <code>input[inByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
|
|
* to floats from
|
|
* <code>output[outOffset]</code> to
|
|
* <code>output[outOffset + (frameCount * format.getChannels()) - 1]</code>
|
|
*
|
|
* @param input the audio data in an byte array
|
|
* @param inByteOffset index in input where to start the conversion
|
|
* @param output the float array that receives the converted audio data
|
|
* @param outOffset the start offset in <code>output</code>
|
|
* @param frameCount number of frames to be converted
|
|
* @param format the input format. Only packed PCM is allowed
|
|
* @throws IllegalArgumentException if one of the parameters is out of bounds
|
|
*
|
|
* @see #byte2float(byte[],int,List,int,int,AudioFormat)
|
|
*/
|
|
public static void byte2floatInterleaved(byte[] input, int inByteOffset,
|
|
float[] output, int outOffset, int frameCount,
|
|
AudioFormat format) {
|
|
|
|
byte2floatGeneric(input, inByteOffset, format.getFrameSize() / format.getChannels(),
|
|
output, outOffset, frameCount * format.getChannels(),
|
|
format);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Generic conversion function to convert a byte array to
|
|
* a float array.
|
|
* <p>
|
|
* Only PCM formats are accepted. The method will convert all
|
|
* bytes from
|
|
* <code>input[inByteOffset]</code> to
|
|
* <code>input[inByteOffset + (sampleCount * (inByteStep - 1)]</code>
|
|
* to samples from
|
|
* <code>output[outOffset]</code> to
|
|
* <code>output[outOffset+sampleCount-1]</code>.
|
|
* <p>
|
|
* The <code>format</code>'s channel count is ignored.
|
|
* <p>
|
|
* For mono data, set <code>inByteOffset</code> to <code>format.getFrameSize()</code>.<br>
|
|
* For converting interleaved input data, multiply <code>sampleCount</code>
|
|
* by the number of channels and set inByteStep to
|
|
* <code>format.getFrameSize() / format.getChannels()</code>.
|
|
*
|
|
* @param sampleCount number of samples to be written to output
|
|
* @param inByteStep how many bytes advance for each output sample in <code>output</code>.
|
|
* @throws IllegalArgumentException if one of the parameters is out of bounds
|
|
*
|
|
* @see #byte2floatInterleaved(byte[],int,float[],int,int,AudioFormat)
|
|
* @see #byte2float(byte[],int,List,int,int,AudioFormat)
|
|
*/
|
|
static void byte2floatGeneric(byte[] input, int inByteOffset, int inByteStep,
|
|
float[] output, int outOffset, int sampleCount,
|
|
AudioFormat format) {
|
|
int formatType = getFormatType(format);
|
|
|
|
byte2floatGeneric(input, inByteOffset, inByteStep,
|
|
output, outOffset, sampleCount,
|
|
formatType);
|
|
}
|
|
|
|
|
|
/**
|
|
* Central conversion function from
|
|
* a byte array to a normalized float array. In order to accomodate
|
|
* interleaved and non-interleaved
|
|
* samples, this method takes inByteStep as parameter which
|
|
* can be used to flexibly convert the data.
|
|
* <p>
|
|
* E.g.:<br>
|
|
* mono->mono: inByteStep=format.getFrameSize()<br>
|
|
* interleaved_stereo->interleaved_stereo: inByteStep=format.getFrameSize()/2,
|
|
* sampleCount*2<br>
|
|
* stereo->2 mono arrays:<br>
|
|
* ---inByteOffset=0, outOffset=0, inByteStep=format.getFrameSize()<br>
|
|
* ---inByteOffset=format.getFrameSize()/2, outOffset=1, inByteStep=format.getFrameSize()<br>
|
|
*/
|
|
static void byte2floatGeneric(byte[] input, int inByteOffset, int inByteStep,
|
|
float[] output, int outOffset, int sampleCount,
|
|
int formatType) {
|
|
//if (TDebug.TraceAudioConverter) {
|
|
// TDebug.out("FloatSampleTools.byte2floatGeneric, formatType="
|
|
// +formatType2Str(formatType));
|
|
//}
|
|
int endCount = outOffset + sampleCount;
|
|
int inIndex = inByteOffset;
|
|
for (int outIndex = outOffset; outIndex < endCount; outIndex++, inIndex+=inByteStep) {
|
|
// do conversion
|
|
switch (formatType) {
|
|
case CT_8S:
|
|
output[outIndex]=
|
|
((float) input[inIndex])*invTwoPower7;
|
|
break;
|
|
case CT_8U:
|
|
output[outIndex]=
|
|
((float) ((input[inIndex] & 0xFF)-128))*invTwoPower7;
|
|
break;
|
|
case CT_16SB:
|
|
output[outIndex]=
|
|
((float) ((input[inIndex]<<8)
|
|
| (input[inIndex+1] & 0xFF)))*invTwoPower15;
|
|
break;
|
|
case CT_16SL:
|
|
output[outIndex]=
|
|
((float) ((input[inIndex+1]<<8)
|
|
| (input[inIndex] & 0xFF)))*invTwoPower15;
|
|
break;
|
|
case CT_24SB:
|
|
output[outIndex]=
|
|
((float) ((input[inIndex]<<16)
|
|
| ((input[inIndex+1] & 0xFF)<<8)
|
|
| (input[inIndex+2] & 0xFF)))*invTwoPower23;
|
|
break;
|
|
case CT_24SL:
|
|
output[outIndex]=
|
|
((float) ((input[inIndex+2]<<16)
|
|
| ((input[inIndex+1] & 0xFF)<<8)
|
|
| (input[inIndex] & 0xFF)))*invTwoPower23;
|
|
break;
|
|
case CT_32SB:
|
|
output[outIndex]=
|
|
((float) ((input[inIndex]<<24)
|
|
| ((input[inIndex+1] & 0xFF)<<16)
|
|
| ((input[inIndex+2] & 0xFF)<<8)
|
|
| (input[inIndex+3] & 0xFF)))*invTwoPower31;
|
|
break;
|
|
case CT_32SL:
|
|
output[outIndex]=
|
|
((float) ((input[inIndex+3]<<24)
|
|
| ((input[inIndex+2] & 0xFF)<<16)
|
|
| ((input[inIndex+1] & 0xFF)<<8)
|
|
| (input[inIndex] & 0xFF)))*invTwoPower31;
|
|
break;
|
|
default:
|
|
throw new IllegalArgumentException
|
|
("unsupported format="+formatType2Str(formatType));
|
|
}
|
|
}
|
|
}
|
|
|
|
// /////////////////// FLOAT 2 BYTE /////////////////////////////////// //
|
|
|
|
private static byte quantize8(float sample, float ditherBits) {
|
|
if (ditherBits!=0) {
|
|
sample+=random.nextFloat()*ditherBits;
|
|
}
|
|
if (sample>=127.0f) {
|
|
return (byte) 127;
|
|
} else if (sample<=-128.0f) {
|
|
return (byte) -128;
|
|
} else {
|
|
return (byte) (sample<0?(sample-0.5f):(sample+0.5f));
|
|
}
|
|
}
|
|
|
|
private static int quantize16(float sample, float ditherBits) {
|
|
if (ditherBits!=0) {
|
|
sample+=random.nextFloat()*ditherBits;
|
|
}
|
|
if (sample>=32767.0f) {
|
|
return 32767;
|
|
} else if (sample<=-32768.0f) {
|
|
return -32768;
|
|
} else {
|
|
return (int) (sample<0?(sample-0.5f):(sample+0.5f));
|
|
}
|
|
}
|
|
|
|
private static int quantize24(float sample, float ditherBits) {
|
|
if (ditherBits!=0) {
|
|
sample+=random.nextFloat()*ditherBits;
|
|
}
|
|
if (sample>=8388607.0f) {
|
|
return 8388607;
|
|
} else if (sample<=-8388608.0f) {
|
|
return -8388608;
|
|
} else {
|
|
return (int) (sample<0?(sample-0.5f):(sample+0.5f));
|
|
}
|
|
}
|
|
|
|
private static int quantize32(float sample, float ditherBits) {
|
|
if (ditherBits!=0) {
|
|
sample+=random.nextFloat()*ditherBits;
|
|
}
|
|
if (sample>=2147483647.0f) {
|
|
return 2147483647;
|
|
} else if (sample<=-2147483648.0f) {
|
|
return -2147483648;
|
|
} else {
|
|
return (int) (sample<0?(sample-0.5f):(sample+0.5f));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Conversion function to convert a non-interleaved float audio data to
|
|
* an interleaved byte array. The float arrays contains normalized
|
|
* samples in the range [-1.0f, +1.0f]. The output array
|
|
* will receive bytes in the format specified in <code>format</code>.
|
|
* Exactly <code>format.getChannels()</code> channels are converted
|
|
* regardless of the number of elements in <code>input</code>. If <code>input</code>
|
|
* does not provide enough channels, an </code>IllegalArgumentException<code> is thrown.
|
|
* <p>
|
|
* Only PCM formats are accepted. The method will convert all
|
|
* samples from <code>input(n)[inOffset]</code> to
|
|
* <code>input(n)[inOffset + frameCount - 1]</code>
|
|
* to byte values from <code>output[outByteOffset]</code> to
|
|
* <code>output[outByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
|
|
* <p>
|
|
* Dithering should be used when the output resolution is significantly
|
|
* lower than the original resolution. This includes if the original
|
|
* data was 16-bit and it is now converted to 8-bit, or if the
|
|
* data was generated in the float domain. No dithering need to be used
|
|
* if the original sample data was in e.g. 8-bit and the resulting output
|
|
* data has a higher resolution. If dithering is used, a sensitive value
|
|
* is DEFAULT_DITHER_BITS.
|
|
*
|
|
* @param input a List of float arrays with the input audio data
|
|
* @param inOffset index in the input arrays where to start the conversion
|
|
* @param output the byte array that receives the converted audio data
|
|
* @param outByteOffset the start offset in <code>output</code>
|
|
* @param frameCount number of frames to be converted.
|
|
* @param format the output format. Only packed PCM is allowed
|
|
* @param ditherBits if 0, do not dither. Otherwise the number of bits to be dithered
|
|
* @throws IllegalArgumentException if one of the parameters is out of bounds
|
|
*
|
|
* @see #DEFAULT_DITHER_BITS
|
|
* @see #float2byteInterleaved(float[],int,byte[],int,int,AudioFormat,float)
|
|
*/
|
|
//public static void float2byte(List<float[]> input, int inOffset,
|
|
public static void float2byte(List input, int inOffset,
|
|
byte[] output, int outByteOffset,
|
|
int frameCount,
|
|
AudioFormat format, float ditherBits) {
|
|
for (int channel = 0; channel < format.getChannels(); channel++) {
|
|
float[] data = (float[]) input.get(channel);
|
|
float2byteGeneric(data, inOffset,
|
|
output, outByteOffset, format.getFrameSize(),
|
|
frameCount, format, ditherBits);
|
|
outByteOffset += format.getFrameSize() / format.getChannels();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Conversion function to convert an interleaved float array to
|
|
* an interleaved byte array. The float array contains normalized
|
|
* samples in the range [-1.0f, +1.0f]. The output array
|
|
* will receive bytes in the format specified in <code>format</code>.
|
|
* <p>
|
|
* Only PCM formats are accepted. The method will convert all
|
|
* samples from <code>input[inOffset]</code> to
|
|
* <code>input[inOffset + (frameCount * format.getChannels()) - 1]</code>
|
|
* to byte values from <code>output[outByteOffset]</code> to
|
|
* <code>output[outByteOffset + (frameCount * format.getFrameSize()) - 1]</code>
|
|
* <p>
|
|
* Dithering should be used when the output resolution is significantly
|
|
* lower than the original resolution. This includes if the original
|
|
* data was 16-bit and it is now converted to 8-bit, or if the
|
|
* data was generated in the float domain. No dithering need to be used
|
|
* if the original sample data was in e.g. 8-bit and the resulting output
|
|
* data has a higher resolution. If dithering is used, a sensitive value
|
|
* is DEFAULT_DITHER_BITS.
|
|
*
|
|
* @param input the audio data in normalized samples
|
|
* @param inOffset index in input where to start the conversion
|
|
* @param output the byte array that receives the converted audio data
|
|
* @param outByteOffset the start offset in <code>output</code>
|
|
* @param frameCount number of frames to be converted.
|
|
* @param format the output format. Only packed PCM is allowed
|
|
* @param ditherBits if 0, do not dither. Otherwise the number of bits to be dithered
|
|
* @throws IllegalArgumentException if one of the parameters is out of bounds
|
|
*
|
|
* @see #DEFAULT_DITHER_BITS
|
|
* @see #float2byte(List,int,byte[],int,int,AudioFormat,float)
|
|
*/
|
|
public static void float2byteInterleaved(float[] input, int inOffset,
|
|
byte[] output, int outByteOffset,
|
|
int frameCount,
|
|
AudioFormat format, float ditherBits) {
|
|
float2byteGeneric(input, inOffset,
|
|
output, outByteOffset, format.getFrameSize() / format.getChannels(),
|
|
frameCount * format.getChannels(),
|
|
format, ditherBits);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Generic conversion function to convert a float array to
|
|
* a byte array.
|
|
* <p>
|
|
* Only PCM formats are accepted. The method will convert all
|
|
* samples from <code>input[inOffset]</code> to
|
|
* <code>input[inOffset+sampleCount-1]</code>
|
|
* to byte values from <code>output[outByteOffset]</code> to
|
|
* <code>output[outByteOffset + (sampleCount * (outByteStep - 1)]</code>.
|
|
* <p>
|
|
* The <code>format</code>'s channel count is ignored.
|
|
* <p>
|
|
* For mono data, set <code>outByteOffset</code> to <code>format.getFrameSize()</code>.<br>
|
|
* For converting interleaved input data, multiply <code>sampleCount</code>
|
|
* by the number of channels and set outByteStep to
|
|
* <code>format.getFrameSize() / format.getChannels()</code>.
|
|
*
|
|
* @param sampleCount number of samples in input to be converted.
|
|
* @param outByteStep how many bytes advance for each input sample in <code>input</code>.
|
|
* @throws IllegalArgumentException if one of the parameters is out of bounds
|
|
*
|
|
* @see #float2byteInterleaved(float[],int,byte[],int,int,AudioFormat,float)
|
|
* @see #float2byte(List,int,byte[],int,int,AudioFormat,float)
|
|
*/
|
|
static void float2byteGeneric(float[] input, int inOffset,
|
|
byte[] output, int outByteOffset, int outByteStep,
|
|
int sampleCount,
|
|
AudioFormat format, float ditherBits) {
|
|
int formatType = getFormatType(format);
|
|
|
|
float2byteGeneric(input, inOffset,
|
|
output, outByteOffset, outByteStep,
|
|
sampleCount,
|
|
formatType, ditherBits);
|
|
}
|
|
|
|
|
|
/**
|
|
* Central conversion function from normalized float array to
|
|
* a byte array. In order to accomodate interleaved and non-interleaved
|
|
* samples, this method takes outByteStep as parameter which
|
|
* can be used to flexibly convert the data.
|
|
* <p>
|
|
* E.g.:<br>
|
|
* mono->mono: outByteStep=format.getFrameSize()<br>
|
|
* interleaved stereo->interleaved stereo: outByteStep=format.getFrameSize()/2, sampleCount*2<br>
|
|
* 2 mono arrays->stereo:<br>
|
|
* ---inOffset=0, outByteOffset=0, outByteStep=format.getFrameSize()<br>
|
|
* ---inOffset=1, outByteOffset=format.getFrameSize()/2, outByteStep=format.getFrameSize()<br>
|
|
*/
|
|
static void float2byteGeneric(float[] input, int inOffset,
|
|
byte[] output, int outByteOffset, int outByteStep,
|
|
int sampleCount, int formatType, float ditherBits) {
|
|
//if (TDebug.TraceAudioConverter) {
|
|
// TDebug.out("FloatSampleBuffer.float2byteGeneric, formatType="
|
|
// +"formatType2Str(formatType));
|
|
//}
|
|
|
|
if (inOffset < 0
|
|
|| inOffset + sampleCount > input.length
|
|
|| sampleCount < 0) {
|
|
throw new IllegalArgumentException("invalid input index: "
|
|
+"input.length="+input.length
|
|
+" inOffset="+inOffset
|
|
+" sampleCount="+sampleCount);
|
|
}
|
|
if (outByteOffset < 0
|
|
|| outByteOffset + (sampleCount * outByteStep) > output.length
|
|
|| outByteStep < getSampleSize(formatType)) {
|
|
throw new IllegalArgumentException("invalid output index: "
|
|
+"output.length="+output.length
|
|
+" outByteOffset="+outByteOffset
|
|
+" sampleCount="+sampleCount
|
|
+" format="+formatType2Str(formatType));
|
|
}
|
|
|
|
if (ditherBits!=0.0f && random==null) {
|
|
// create the random number generator for dithering
|
|
random=new Random();
|
|
}
|
|
int endSample = inOffset + sampleCount;
|
|
int iSample;
|
|
int outIndex = outByteOffset;
|
|
for (int inIndex = inOffset;
|
|
inIndex < endSample;
|
|
inIndex++, outIndex+=outByteStep) {
|
|
// do conversion
|
|
switch (formatType) {
|
|
case CT_8S:
|
|
output[outIndex]=quantize8(input[inIndex]*twoPower7, ditherBits);
|
|
break;
|
|
case CT_8U:
|
|
output[outIndex]=(byte) (quantize8((input[inIndex]*twoPower7), ditherBits)+128);
|
|
break;
|
|
case CT_16SB:
|
|
iSample=quantize16(input[inIndex]*twoPower15, ditherBits);
|
|
output[outIndex]=(byte) (iSample >> 8);
|
|
output[outIndex+1]=(byte) (iSample & 0xFF);
|
|
break;
|
|
case CT_16SL:
|
|
iSample=quantize16(input[inIndex]*twoPower15, ditherBits);
|
|
output[outIndex+1]=(byte) (iSample >> 8);
|
|
output[outIndex]=(byte) (iSample & 0xFF);
|
|
break;
|
|
case CT_24SB:
|
|
iSample=quantize24(input[inIndex]*twoPower23, ditherBits);
|
|
output[outIndex]=(byte) (iSample >> 16);
|
|
output[outIndex+1]=(byte) ((iSample >>> 8) & 0xFF);
|
|
output[outIndex+2]=(byte) (iSample & 0xFF);
|
|
break;
|
|
case CT_24SL:
|
|
iSample=quantize24(input[inIndex]*twoPower23, ditherBits);
|
|
output[outIndex+2]=(byte) (iSample >> 16);
|
|
output[outIndex+1]=(byte) ((iSample >>> 8) & 0xFF);
|
|
output[outIndex]=(byte) (iSample & 0xFF);
|
|
break;
|
|
case CT_32SB:
|
|
iSample=quantize32(input[inIndex]*twoPower31, ditherBits);
|
|
output[outIndex]=(byte) (iSample >> 24);
|
|
output[outIndex+1]=(byte) ((iSample >>> 16) & 0xFF);
|
|
output[outIndex+2]=(byte) ((iSample >>> 8) & 0xFF);
|
|
output[outIndex+3]=(byte) (iSample & 0xFF);
|
|
break;
|
|
case CT_32SL:
|
|
iSample=quantize32(input[inIndex]*twoPower31, ditherBits);
|
|
output[outIndex+3]=(byte) (iSample >> 24);
|
|
output[outIndex+2]=(byte) ((iSample >>> 16) & 0xFF);
|
|
output[outIndex+1]=(byte) ((iSample >>> 8) & 0xFF);
|
|
output[outIndex]=(byte) (iSample & 0xFF);
|
|
break;
|
|
default:
|
|
throw new IllegalArgumentException
|
|
("unsupported format="+formatType2Str(formatType));
|
|
}
|
|
}
|
|
}
|
|
}
|