Last active
December 10, 2021 03:00
-
-
Save charneykaye/1d67f4996469752572cce8d8d2f5979a to your computer and use it in GitHub Desktop.
Java class for parsing audio data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.charneykaye; | |
import javax.sound.sampled.AudioFormat; | |
import java.nio.ByteBuffer; | |
import java.nio.ByteOrder; | |
/** | |
Utilities for converting back and forth between a `double` and various `byte[]` for different bit-rates | |
a double is a single value for a channel of a frame of some audio. | |
@link https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html | |
*/ | |
@SuppressWarnings("CommentedOutCode") | |
public enum AudioSample { | |
// sample types | |
U8, // unsigned 8-bit integer | |
S8, // signed 8-bit integer | |
F32LSB, // 32-bit floating point, LSB order | |
F32MSB, // 32-bit floating point, MSB order | |
F64LSB, // 64-bit floating point, LSB order | |
F64MSB, // 64-bit floating point, MSB order | |
S16LSB, // 16-bit signed integer, LSB order | |
S16MSB, // 16-bit signed integer, MSB order | |
S24LSB, // 24-bit signed integer, LSB order | |
S24MSB, // 24-bit signed integer, MSB order | |
S32LSB, // 32-bit signed integer, LSB order | |
S32MSB, // 32-bit signed integer, MSB order | |
U16LSB, // 16-bit unsigned integer, LSB order | |
U16MSB; // 16-bit unsigned integer, MSB order | |
public static final int SIGNED_16BIT_MAX = 0x8000; | |
public static final int SIGNED_24BIT_MAX = 0x800000; | |
public static final int SIGNED_32BIT_MAX = 0x80000000; | |
public static final int SIGNED_8BIT_MAX = 0x80; | |
public static final int UNSIGNED_16BIT_MAX = 0xffff; | |
public static final int UNSIGNED_8BIT_MAX = 0xff; | |
// format encoding types | |
private static final AudioFormat.Encoding PCM_UNSIGNED = AudioFormat.Encoding.PCM_UNSIGNED; | |
private static final AudioFormat.Encoding PCM_SIGNED = AudioFormat.Encoding.PCM_SIGNED; | |
private static final AudioFormat.Encoding PCM_FLOAT = AudioFormat.Encoding.PCM_FLOAT; | |
/** | |
Convert output values into a ByteBuffer | |
@param fmt to write | |
@param samples output to convert | |
@return byte buffer of stream | |
*/ | |
public static ByteBuffer byteBufferOf(AudioFormat fmt, double[][] samples) throws Exception { | |
ByteBuffer outputBytes = ByteBuffer.allocate(samples.length * fmt.getFrameSize()); | |
for (double[] sample : samples) | |
for (double v : sample) | |
outputBytes.put(AudioSample.toBytes(v, AudioSample.typeOfOutput(fmt))); | |
return outputBytes; | |
} | |
/** | |
Get the proprietary (to this class) type for output audio | |
which can be used later to quickly build sample bytes from `double` values | |
@param format of audio from which to extract proprietary sample format | |
@return proprietary sample format | |
@throws Exception if format is unsupported | |
*/ | |
public static AudioSample typeOfOutput(AudioFormat format) throws Exception { | |
return typeOf(format, true); | |
} | |
/** | |
Get the proprietary (to this class) type for input audio | |
which can be used later to quickly build sample bytes from `double` values | |
@param format of audio from which to extract proprietary sample format | |
@return proprietary sample format | |
@throws Exception if format is unsupported | |
*/ | |
public static AudioSample typeOfInput(AudioFormat format) throws Exception { | |
return typeOf(format, false); | |
} | |
/** | |
Get the proprietary (to this class) type | |
which can be used later to quickly build sample bytes from `double` values | |
@param format of audio from which to extract proprietary sample format | |
@param isOutput whether this format will be used for output (which affects rules) | |
@return proprietary sample format | |
@throws Exception if format is unsupported | |
*/ | |
private static AudioSample typeOf(AudioFormat format, boolean isOutput) throws Exception { | |
// switch based on frame size (bytes) and encoding | |
AudioFormat.Encoding encoding = format.getEncoding(); | |
int sampleSizeInBits = format.getSampleSizeInBits(); | |
switch (sampleSizeInBits) { | |
case 8: | |
if (!isOutput && encoding.equals(PCM_UNSIGNED)) { | |
return U8; | |
} else if (encoding.equals((PCM_SIGNED))) { | |
return S8; | |
} else { | |
throw new Exception("Unsupported 8-bit " + (isOutput ? "output " : "") + "encoding: " + encoding); | |
} | |
case 16: | |
if (!isOutput && encoding.equals(PCM_UNSIGNED)) { | |
return format.isBigEndian() ? U16MSB : U16LSB; | |
} else if (encoding.equals((PCM_SIGNED))) { | |
return format.isBigEndian() ? S16MSB : S16LSB; | |
} else { | |
throw new Exception("Unsupported 16-bit " + (isOutput ? "output " : "") + "encoding: " + encoding); | |
} | |
case 24: | |
if (!isOutput && encoding.equals(PCM_SIGNED)) { | |
return format.isBigEndian() ? S24MSB : S24LSB; | |
} else { | |
throw new Exception("Unsupported 24-bit " + (isOutput ? "output " : "") + "encoding: " + encoding); | |
} | |
case 32: | |
if (encoding.equals(PCM_SIGNED)) { | |
return format.isBigEndian() ? S32MSB : S32LSB; | |
} else if (encoding.equals((PCM_FLOAT))) { | |
return format.isBigEndian() ? F32MSB : F32LSB; | |
} else { | |
throw new Exception("Unsupported 32-bit " + (isOutput ? "output " : "") + "encoding: " + encoding); | |
} | |
case 64: | |
if (encoding.equals(PCM_FLOAT)) { | |
return format.isBigEndian() ? F64MSB : F64LSB; | |
} else { | |
throw new Exception("Unsupported 64-bit " + (isOutput ? "output " : "") + "encoding: " + encoding); | |
} | |
default: | |
throw new Exception("Unsupported " + (isOutput ? "output " : "") + " sample size: " + sampleSizeInBits + " bits"); | |
} | |
} | |
/** | |
Convert a `double` value to output bytes based on its proprietary sample type | |
@param value to convert | |
@param type of sample | |
@return output bytes | |
*/ | |
public static byte[] toBytes(double value, AudioSample type) { | |
return switch (type) { | |
case S8 -> toBytesS8(value); | |
case S16LSB -> toBytesS16LSB(value); | |
case S16MSB -> toBytesS16MSB(value); | |
case S32LSB -> toBytesS32LSB(value); | |
case S32MSB -> toBytesS32MSB(value); | |
case F32LSB -> toBytesF32LSB(value); | |
case F32MSB -> toBytesF32MSB(value); | |
case F64LSB -> toBytesF64LSB(value); | |
case F64MSB -> toBytesF64MSB(value); | |
default -> new byte[0]; | |
}; | |
} | |
/** | |
Convert input bytes to a `double` value based on its proprietary sample type | |
@param value to convert | |
@param type of sample | |
@return value | |
*/ | |
public static double fromBytes(byte[] value, AudioSample type) { | |
return switch (type) { | |
case U8 -> fromBytesU8(value); | |
case S8 -> fromBytesS8(value); | |
case U16LSB -> fromBytesU16LSB(value); | |
case U16MSB -> fromBytesU16MSB(value); | |
case S16LSB -> fromBytesS16LSB(value); | |
case S16MSB -> fromBytesS16MSB(value); | |
case S24LSB -> fromBytesS24LSB(value); | |
case S24MSB -> fromBytesS24MSB(value); | |
case S32LSB -> fromBytesS32LSB(value); | |
case S32MSB -> fromBytesS32MSB(value); | |
case F32LSB -> fromBytesF32LSB(value); | |
case F32MSB -> fromBytesF32MSB(value); | |
case F64LSB -> fromBytesF64LSB(value); | |
case F64MSB -> fromBytesF64MSB(value); | |
}; | |
} | |
/** | |
to bytes encoded as 8-bit signed int LSB | |
@param value to encode | |
@return encoded bytes | |
*/ | |
private static byte[] toBytesS8(double value) { | |
return new byte[]{(byte) (SIGNED_8BIT_MAX * value)}; | |
} | |
/** | |
to bytes encoded as 16-bit signed int LSB | |
@param value to encode | |
@return encoded bytes | |
*/ | |
private static byte[] toBytesS16LSB(double value) { | |
return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort((short) (SIGNED_16BIT_MAX * value)).array(); | |
} | |
/** | |
to bytes encoded as 16-bit signed int MSB | |
@param value to encode | |
@return encoded bytes | |
*/ | |
private static byte[] toBytesS16MSB(double value) { | |
return ByteBuffer.allocate(2).order(ByteOrder.BIG_ENDIAN).putShort((short) (SIGNED_16BIT_MAX * value)).array(); | |
} | |
/** | |
to bytes encoded as 32-bit signed int LSB | |
@param value to encode | |
@return encoded bytes | |
*/ | |
private static byte[] toBytesS32LSB(double value) { | |
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt((int) (SIGNED_32BIT_MAX * value)).array(); | |
} | |
/** | |
to bytes encoded as 32-bit signed int MSB | |
@param value to encode | |
@return encoded bytes | |
*/ | |
private static byte[] toBytesS32MSB(double value) { | |
return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt((int) (SIGNED_32BIT_MAX * value)).array(); | |
} | |
/** | |
to bytes encoded as 32-bit float LSB | |
@param value to encode | |
@return encoded bytes | |
*/ | |
private static byte[] toBytesF32LSB(double value) { | |
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putFloat((float) value).array(); | |
} | |
/** | |
to bytes encoded as 32-bit float MSB | |
@param value to encode | |
@return encoded bytes | |
*/ | |
private static byte[] toBytesF32MSB(double value) { | |
return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat((float) value).array(); | |
} | |
/** | |
to bytes encoded as 64-bit float LSB | |
@param value to encode | |
@return encoded bytes | |
*/ | |
private static byte[] toBytesF64LSB(double value) { | |
return ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putDouble(value).array(); | |
} | |
/** | |
to bytes encoded as 64-bit float MSB | |
@param value to encode | |
@return encoded bytes | |
*/ | |
private static byte[] toBytesF64MSB(double value) { | |
return ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putDouble(value).array(); | |
} | |
/** | |
from bytes encoded as 8-bit unsigned int LSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesU8(byte[] sample) { | |
return (double) (ByteBuffer.wrap(sample) | |
.order(ByteOrder.LITTLE_ENDIAN) | |
.get() & UNSIGNED_8BIT_MAX) / (double) SIGNED_8BIT_MAX - 1; | |
} | |
/** | |
from bytes encoded as 8-bit signed int LSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesS8(byte[] sample) { | |
return (double) ByteBuffer.wrap(sample) | |
.order(ByteOrder.LITTLE_ENDIAN) | |
.get() / (double) SIGNED_8BIT_MAX; | |
} | |
/** | |
from bytes encoded as 16-bit unsigned int LSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesU16LSB(byte[] sample) { | |
return (double) (ByteBuffer.wrap(sample) | |
.order(ByteOrder.LITTLE_ENDIAN) | |
.getShort() & UNSIGNED_16BIT_MAX) / (double) SIGNED_16BIT_MAX - 1; | |
} | |
/** | |
from bytes encoded as 16-bit unsigned int MSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesU16MSB(byte[] sample) { | |
return (double) (ByteBuffer.wrap(sample) | |
.order(ByteOrder.BIG_ENDIAN) | |
.getShort() & UNSIGNED_16BIT_MAX) / (double) SIGNED_16BIT_MAX - 1; | |
} | |
/** | |
from bytes encoded as 16-bit signed int LSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesS16LSB(byte[] sample) { | |
return (double) ByteBuffer.wrap(sample) | |
.order(ByteOrder.LITTLE_ENDIAN) | |
.getShort() / (double) SIGNED_16BIT_MAX; | |
} | |
/** | |
from bytes encoded as 16-bit signed int MSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesS16MSB(byte[] sample) { | |
return (double) ByteBuffer.wrap(sample) | |
.order(ByteOrder.BIG_ENDIAN) | |
.getShort() / (double) SIGNED_16BIT_MAX; | |
} | |
/** | |
from bytes encoded as 24-bit signed int LSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesS24LSB(byte[] sample) { | |
return (double) ((sample[2]) << 16 | (sample[1] & 0xFF) << 8 | (sample[0] & 0xFF)) / (double) SIGNED_24BIT_MAX; | |
/* | |
return (double) ByteBuffer.wrap(sample) | |
.order(ByteOrder.LITTLE_ENDIAN) | |
.getShort() / (double) 0x8000; | |
*/ | |
} | |
/** | |
from bytes encoded as 24-bit signed int MSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesS24MSB(byte[] sample) { | |
return (double) ((sample[0]) << 16 | (sample[1] & 0xFF) << 8 | (sample[2] & 0xFF)) / (double) SIGNED_24BIT_MAX; | |
/* | |
return (double) ByteBuffer.wrap(sample) | |
.order(ByteOrder.BIG_ENDIAN) | |
.getShort() / (double) 0x800000; | |
*/ | |
} | |
/** | |
from bytes encoded as 32-bit signed int LSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesS32LSB(byte[] sample) { | |
return (double) ByteBuffer.wrap(sample) | |
.order(ByteOrder.LITTLE_ENDIAN) | |
.getInt() / (double) SIGNED_32BIT_MAX; | |
} | |
/** | |
from bytes encoded as 32-bit signed int MSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesS32MSB(byte[] sample) { | |
return (double) ByteBuffer.wrap(sample) | |
.order(ByteOrder.BIG_ENDIAN) | |
.getInt() / (double) SIGNED_32BIT_MAX; | |
} | |
/** | |
from bytes encoded as 32-bit float LSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesF32LSB(byte[] sample) { | |
return ByteBuffer.wrap(sample) | |
.order(ByteOrder.LITTLE_ENDIAN) | |
.getFloat(); | |
} | |
/** | |
from bytes encoded as 32-bit float MSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesF32MSB(byte[] sample) { | |
return ByteBuffer.wrap(sample) | |
.order(ByteOrder.BIG_ENDIAN) | |
.getFloat(); | |
} | |
/** | |
from bytes encoded as 64-bit float LSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesF64LSB(byte[] sample) { | |
return ByteBuffer.wrap(sample) | |
.order(ByteOrder.LITTLE_ENDIAN) | |
.getDouble(); | |
} | |
/** | |
from bytes encoded as 64-bit float MSB | |
@param sample to decode | |
@return value | |
*/ | |
private static double fromBytesF64MSB(byte[] sample) { | |
return ByteBuffer.wrap(sample) | |
.order(ByteOrder.BIG_ENDIAN) | |
.getDouble(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment