好吧,最简单的答案是,目前Java无法为程序员生成示例数据。
这句话来自官方教程:
有两种方法可以应用信号处理:
本页更详细地讨论了第一种技术,因为第二种技术没有特殊的 API。
播放 方式很大程度上充当文件和音频设备之间的桥梁。从文件中读入字节并发送出去。javax.sound.sampled
不要以为字节是有意义的音频样本!除非您碰巧有8位AIFF文件,否则它们不是。(另一方面,如果样本肯定是8位有符号的,则可以使用它们进行算术运算。使用 8 位是避免此处描述的复杂性的一种方法,如果您只是在玩。
因此,我将枚举 AudioFormat.Encoding
的类型,并描述如何自己解码它们。这个答案不会涵盖如何对它们进行编码,但它包含在底部的完整代码示例中。编码大多只是反向解码过程。
这是一个很长的答案,但我想给出一个全面的概述。
关于数字音频的一点点
通常,在解释数字音频时,我们指的是线性脉冲编码调制(LPCM)。
连续声波以固定的间隔进行采样,振幅被量化为某种比例的整数。
这里显示的是一个正弦波,经过采样并量化为4位:
(请注意,二的补码表示中最正的值比最负的值小 1。这是一个需要注意的小细节。例如,如果您正在剪辑音频并忘记了这一点,则正剪辑将溢出。
当我们在计算机上有音频时,我们有这些样本的数组。示例数组是我们想要将数组转换为的数组。byte
要解码PCM样本,我们不太关心采样率或通道数,所以我在这里不会说太多。通道通常是交错的,因此,如果我们有一个数组,它们将像这样存储:
Index 0: Sample 0 (Left Channel)
Index 1: Sample 0 (Right Channel)
Index 2: Sample 1 (Left Channel)
Index 3: Sample 1 (Right Channel)
Index 4: Sample 2 (Left Channel)
Index 5: Sample 2 (Right Channel)
...
换句话说,对于立体声,数组中的样本只是在左和右之间交替。
一些假设
所有代码示例都将假定以下声明:
-
byte[] bytes;
从 中读取的数组。byte
AudioInputStream
-
float[] samples;
我们要填充的输出示例数组。
-
float sample;
我们当前正在处理的示例。
-
long temp;
用于常规操作的临时值。
-
int i;
数组中当前样本数据开始的位置。byte
我们将数组中的所有样本规范化为 的范围。我见过的所有浮点音频都是以这种方式进行的,非常方便。float[]
-1f <= sample <= 1f
如果我们的源音频还没有像这样(例如整数样本),我们可以使用以下方法自己规范化它们:
sample = sample / fullScale(bitsPerSample);
其中 2位每个样本 - 1,即 .fullScale
Math.pow(2, bitsPerSample-1)
如何将数组强制放入有意义的数据?byte
该数组包含拆分在一行中的示例帧。这实际上非常简单,除了称为字节序的东西,这是每个样本包中s的顺序。byte
byte
这是一个图表。此示例(打包到数组中)保存十进制值 9999:byte
24-bit sample as big-endian:
bytes[i] bytes[i + 1] bytes[i + 2]
┌──────┐ ┌──────┐ ┌──────┐
00000000 00100111 00001111
24-bit sample as little-endian:
bytes[i] bytes[i + 1] bytes[i + 2]
┌──────┐ ┌──────┐ ┌──────┐
00001111 00100111 00000000
它们具有相同的二进制值;但是,订单是相反的。byte
- 在大端序中,越显著的 s 先于不太显著的 s。
byte
byte
- 在小端序中,s 越不显著,越显著。
byte
bytes
WAV 文件以小端顺序存储,AIFF 文件以大端顺序存储。字节序可以从AudioFormat.isBigEndian
获得。
为了连接 s 并将它们放入我们的变量中,我们:byte
long temp
- 按位 AND 每个都带有掩码(即 ),以避免在自动升级时进行符号扩展。(,并在对它们执行算术运算时升级为。另请参阅
价值与0xff
在 Java 中的作用?byte
0xFF
0b1111_1111
byte
char
byte
short
int
- 位将每个位移入到位置。
byte
- 按位或 s 在一起。
byte
下面是一个 24 位示例:
long temp;
if (isBigEndian) {
temp = (
((bytes[i ] & 0xffL) << 16)
| ((bytes[i + 1] & 0xffL) << 8)
| (bytes[i + 2] & 0xffL)
);
} else {
temp = (
(bytes[i ] & 0xffL)
| ((bytes[i + 1] & 0xffL) << 8)
| ((bytes[i + 2] & 0xffL) << 16)
);
}
请注意,平移顺序是根据字节序反转的。
这也可以推广到一个循环,这可以在这个答案底部的完整代码中看到。(请参阅 和 方法。unpackAnyBit
packAnyBit
现在我们已经将 s 连接在一起,我们可以再采取一些步骤将它们转换为样本。后续步骤取决于实际编码。byte
如何解码?Encoding.PCM_SIGNED
两者的补号必须延长。这意味着,如果最高有效位 (MSB) 设置为 1,我们将用 1 填充其上方的所有位。如果设置了符号位,算术右移()将自动为我们进行填充,因此我通常这样做:>>
int bitsToExtend = Long.SIZE - bitsPerSample;
float sample = (temp << bitsToExtend) >> bitsToExtend.
(其中是 64。如果我们的变量不是 ,我们将使用其他变量。如果我们使用例如 相反,我们将使用32。Long.SIZE
temp
long
int temp
要了解其工作原理,下面是将 8 位到 16 位进行符号扩展的示意图:
11111111 is the byte value -1, but the upper bits of the short are 0.
Shift the byte's MSB in to the MSB position of the short.
0000 0000 1111 1111
<< 8
───────────────────
1111 1111 0000 0000
Shift it back and the right-shift fills all the upper bits with 1s.
We now have the short value of -1.
1111 1111 0000 0000
>> 8
───────────────────
1111 1111 1111 1111
正值(MSB 中为 0 的值)保持不变。这是算术右移的一个很好的属性。
然后归一化样本,如一些假设中所述。
如果您的代码很简单,则可能不需要编写显式符号扩展
Java 在从一个整数类型转换为更大的类型(例如,转换为 ) 时会自动对扩展进行符号扩展。如果您知道输入和输出格式始终是有符号的,则可以在前面的步骤中串联字节时使用自动符号扩展。byte
int
回想一下上面的部分(如何将字节数组强制转换为有意义的数据?),我们曾经用它来防止发生符号扩展。如果您只是从最高位置删除 ,则符号扩展名将自动发生。b & 0xFF
& 0xFF
byte
例如,以下解码有符号、大端、16 位样本:
for (int i = 0; i < bytes.length; i++) {
int sample = (bytes[i] << 8) // high byte is sign-extended
| (bytes[i + 1] & 0xFF); // low byte is not
// ...
}
如何解码?Encoding.PCM_UNSIGNED
我们将其转换为签名号码。无符号样本只是偏移,例如:
- 无符号值 0 对应于最负的有符号值。
- 无符号值 2位PerSample - 1 对应于有符号值 0。
- 无符号值 2位PerSample 对应于最正的有符号值。
所以事实证明这很简单。只需减去偏移量:
float sample = temp - fullScale(bitsPerSample);
然后归一化样本,如一些假设中所述。
如何解码?Encoding.PCM_FLOAT
这是自 Java 7 以来的新内容。
在实践中,浮点 PCM 通常是 IEEE 32 位或 IEEE 64 位,并且已经规范化为 的范围。这些样本可以使用实用程序方法 Float#intBitsToFloat
和 Double#longBitsToDouble
获得。±1.0
// IEEE 32-bit
float sample = Float.intBitsToFloat((int) temp);
// IEEE 64-bit
double sampleAsDouble = Double.longBitsToDouble(temp);
float sample = (float) sampleAsDouble; // or just use double for arithmetic
如何解码 和 ?Encoding.ULAW
Encoding.ALAW
这些是在电话等中更常见的压缩压缩编解码器。它们受到我的假设支持,因为它们被Sun的Au格式使用。(但是,它不仅限于这种类型的容器。例如,WAV 可以包含这些编码。javax.sound.sampled
您可以将A定律和μ定律概念化,就像它们是浮点格式一样。这些是PCM格式,但值的范围是非线性的。
有两种方法可以解码它们。我将展示使用数学公式的方法。您也可以通过直接操作二进制文件来解码它们,这在这篇博客文章中有所描述,但它看起来更深奥。
对于两者,压缩数据都是 8 位的。标准A-law在解码时为13位,μ定律在解码时为14位;但是,应用该公式将产生一个范围。±1.0
在应用公式之前,有三件事要做:
- 由于涉及数据完整性的原因,某些位被标准地反转以进行存储。
- 它们被存储为符号和大小(而不是二的补码)。
- 该公式还期望范围为 ,因此必须缩放 8 位值。
±1.0
对于μ律,所有位都是倒置的,因此:
temp ^= 0xffL; // 0xff == 0b1111_1111
(请注意,我们不能使用 ,因为我们不想反转 的高位。~
long
对于A-law,其他每个位都是颠倒的,所以:
temp ^= 0x55L; // 0x55 == 0b0101_0101
(XOR可用于进行反转。请参阅如何设置、清除和切换?)
为了从符号和量级转换为二的补码,我们:
- 检查是否已设置符号位。
- 如果是这样,请清除符号位并否定该数字。
// 0x80 == 0b1000_0000
if ((temp & 0x80L) != 0) {
temp ^= 0x80L;
temp = -temp;
}
然后缩放编码的数字,与“一些假设”中描述的方式相同:
sample = temp / fullScale(8);
现在我们可以应用扩展。
翻译成Java的μ法公式是:
sample = (float) (
signum(sample)
*
(1.0 / 255.0)
*
(pow(256.0, abs(sample)) - 1.0)
);
然后,翻译成Java的A-law公式是:
float signum = signum(sample);
sample = abs(sample);
if (sample < (1.0 / (1.0 + log(87.7)))) {
sample = (float) (
sample * ((1.0 + log(87.7)) / 87.7)
);
} else {
sample = (float) (
exp((sample * (1.0 + log(87.7))) - 1.0) / 87.7
);
}
sample = signum * sample;
下面是该类的完整示例代码。SimpleAudioConversion
package mcve.audio;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioFormat.Encoding;
import static java.lang.Math.*;
/**
* <p>Performs simple audio format conversion.</p>
*
* <p>Example usage:</p>
*
* <pre>{@code AudioInputStream ais = ... ;
* SourceDataLine line = ... ;
* AudioFormat fmt = ... ;
*
* // do setup
*
* for (int blen = 0; (blen = ais.read(bytes)) > -1;) {
* int slen;
* slen = SimpleAudioConversion.decode(bytes, samples, blen, fmt);
*
* // do something with samples
*
* blen = SimpleAudioConversion.encode(samples, bytes, slen, fmt);
* line.write(bytes, 0, blen);
* }}</pre>
*
* @author Radiodef
* @see <a href="http://stackoverflow.com/a/26824664/2891664">Overview on Stack Overflow</a>
*/
public final class SimpleAudioConversion {
private SimpleAudioConversion() {}
/**
* Converts from a byte array to an audio sample float array.
*
* @param bytes the byte array, filled by the AudioInputStream
* @param samples an array to fill up with audio samples
* @param blen the return value of AudioInputStream.read
* @param fmt the source AudioFormat
*
* @return the number of valid audio samples converted
*
* @throws NullPointerException if bytes, samples or fmt is null
* @throws ArrayIndexOutOfBoundsException
* if bytes.length is less than blen or
* if samples.length is less than blen / bytesPerSample(fmt.getSampleSizeInBits())
*/
public static int decode(byte[] bytes,
float[] samples,
int blen,
AudioFormat fmt) {
int bitsPerSample = fmt.getSampleSizeInBits();
int bytesPerSample = bytesPerSample(bitsPerSample);
boolean isBigEndian = fmt.isBigEndian();
Encoding encoding = fmt.getEncoding();
double fullScale = fullScale(bitsPerSample);
int i = 0;
int s = 0;
while (i < blen) {
long temp = unpackBits(bytes, i, isBigEndian, bytesPerSample);
float sample = 0f;
if (encoding == Encoding.PCM_SIGNED) {
temp = extendSign(temp, bitsPerSample);
sample = (float) (temp / fullScale);
} else if (encoding == Encoding.PCM_UNSIGNED) {
temp = unsignedToSigned(temp, bitsPerSample);
sample = (float) (temp / fullScale);
} else if (encoding == Encoding.PCM_FLOAT) {
if (bitsPerSample == 32) {
sample = Float.intBitsToFloat((int) temp);
} else if (bitsPerSample == 64) {
sample = (float) Double.longBitsToDouble(temp);
}
} else if (encoding == Encoding.ULAW) {
sample = bitsToMuLaw(temp);
} else if (encoding == Encoding.ALAW) {
sample = bitsToALaw(temp);
}
samples[s] = sample;
i += bytesPerSample;
s++;
}
return s;
}
/**
* Converts from an audio sample float array to a byte array.
*
* @param samples an array of audio samples to encode
* @param bytes an array to fill up with bytes
* @param slen the return value of the decode method
* @param fmt the destination AudioFormat
*
* @return the number of valid bytes converted
*
* @throws NullPointerException if samples, bytes or fmt is null
* @throws ArrayIndexOutOfBoundsException
* if samples.length is less than slen or
* if bytes.length is less than slen * bytesPerSample(fmt.getSampleSizeInBits())
*/
public static int encode(float[] samples,
byte[] bytes,
int slen,
AudioFormat fmt) {
int bitsPerSample = fmt.getSampleSizeInBits();
int bytesPerSample = bytesPerSample(bitsPerSample);
boolean isBigEndian = fmt.isBigEndian();
Encoding encoding = fmt.getEncoding();
double fullScale = fullScale(bitsPerSample);
int i = 0;
int s = 0;
while (s < slen) {
float sample = samples[s];
long temp = 0L;
if (encoding == Encoding.PCM_SIGNED) {
temp = (long) (sample * fullScale);
} else if (encoding == Encoding.PCM_UNSIGNED) {
temp = (long) (sample * fullScale);
temp = signedToUnsigned(temp, bitsPerSample);
} else if (encoding == Encoding.PCM_FLOAT) {
if (bitsPerSample == 32) {
temp = Float.floatToRawIntBits(sample);
} else if (bitsPerSample == 64) {
temp = Double.doubleToRawLongBits(sample);
}
} else if (encoding == Encoding.ULAW) {
temp = muLawToBits(sample);
} else if (encoding == Encoding.ALAW) {
temp = aLawToBits(sample);
}
packBits(bytes, i, temp, isBigEndian, bytesPerSample);
i += bytesPerSample;
s++;
}
return i;
}
/**
* Computes the block-aligned bytes per sample of the audio format,
* using Math.ceil(bitsPerSample / 8.0).
* <p>
* Round towards the ceiling because formats that allow bit depths
* in non-integral multiples of 8 typically pad up to the nearest
* integral multiple of 8. So for example, a 31-bit AIFF file will
* actually store 32-bit blocks.
*
* @param bitsPerSample the return value of AudioFormat.getSampleSizeInBits
* @return The block-aligned bytes per sample of the audio format.
*/
public static int bytesPerSample(int bitsPerSample) {
return (int) ceil(bitsPerSample / 8.0); // optimization: ((bitsPerSample + 7) >>> 3)
}
/**
* Computes the largest magnitude representable by the audio format,
* using Math.pow(2.0, bitsPerSample - 1). Note that for two's complement
* audio, the largest positive value is one less than the return value of
* this method.
* <p>
* The result is returned as a double because in the case that
* bitsPerSample is 64, a long would overflow.
*
* @param bitsPerSample the return value of AudioFormat.getBitsPerSample
* @return the largest magnitude representable by the audio format
*/
public static double fullScale(int bitsPerSample) {
return pow(2.0, bitsPerSample - 1); // optimization: (1L << (bitsPerSample - 1))
}
private static long unpackBits(byte[] bytes,
int i,
boolean isBigEndian,
int bytesPerSample) {
switch (bytesPerSample) {
case 1: return unpack8Bit(bytes, i);
case 2: return unpack16Bit(bytes, i, isBigEndian);
case 3: return unpack24Bit(bytes, i, isBigEndian);
default: return unpackAnyBit(bytes, i, isBigEndian, bytesPerSample);
}
}
private static long unpack8Bit(byte[] bytes, int i) {
return bytes[i] & 0xffL;
}
private static long unpack16Bit(byte[] bytes,
int i,
boolean isBigEndian) {
if (isBigEndian) {
return (
((bytes[i ] & 0xffL) << 8)
| (bytes[i + 1] & 0xffL)
);
} else {
return (
(bytes[i ] & 0xffL)
| ((bytes[i + 1] & 0xffL) << 8)
);
}
}
private static long unpack24Bit(byte[] bytes,
int i,
boolean isBigEndian) {
if (isBigEndian) {
return (
((bytes[i ] & 0xffL) << 16)
| ((bytes[i + 1] & 0xffL) << 8)
| (bytes[i + 2] & 0xffL)
);
} else {
return (
(bytes[i ] & 0xffL)
| ((bytes[i + 1] & 0xffL) << 8)
| ((bytes[i + 2] & 0xffL) << 16)
);
}
}
private static long unpackAnyBit(byte[] bytes,
int i,
boolean isBigEndian,
int bytesPerSample) {
long temp = 0;
if (isBigEndian) {
for (int b = 0; b < bytesPerSample; b++) {
temp |= (bytes[i + b] & 0xffL) << (
8 * (bytesPerSample - b - 1)
);
}
} else {
for (int b = 0; b < bytesPerSample; b++) {
temp |= (bytes[i + b] & 0xffL) << (8 * b);
}
}
return temp;
}
private static void packBits(byte[] bytes,
int i,
long temp,
boolean isBigEndian,
int bytesPerSample) {
switch (bytesPerSample) {
case 1: pack8Bit(bytes, i, temp);
break;
case 2: pack16Bit(bytes, i, temp, isBigEndian);
break;
case 3: pack24Bit(bytes, i, temp, isBigEndian);
break;
default: packAnyBit(bytes, i, temp, isBigEndian, bytesPerSample);
break;
}
}
private static void pack8Bit(byte[] bytes, int i, long temp) {
bytes[i] = (byte) (temp & 0xffL);
}
private static void pack16Bit(byte[] bytes,
int i,
long temp,
boolean isBigEndian) {
if (isBigEndian) {
bytes[i ] = (byte) ((temp >>> 8) & 0xffL);
bytes[i + 1] = (byte) ( temp & 0xffL);
} else {
bytes[i ] = (byte) ( temp & 0xffL);
bytes[i + 1] = (byte) ((temp >>> 8) & 0xffL);
}
}
private static void pack24Bit(byte[] bytes,
int i,
long temp,
boolean isBigEndian) {
if (isBigEndian) {
bytes[i ] = (byte) ((temp >>> 16) & 0xffL);
bytes[i + 1] = (byte) ((temp >>> 8) & 0xffL);
bytes[i + 2] = (byte) ( temp & 0xffL);
} else {
bytes[i ] = (byte) ( temp & 0xffL);
bytes[i + 1] = (byte) ((temp >>> 8) & 0xffL);
bytes[i + 2] = (byte) ((temp >>> 16) & 0xffL);
}
}
private static void packAnyBit(byte[] bytes,
int i,
long temp,
boolean isBigEndian,
int bytesPerSample) {
if (isBigEndian) {
for (int b = 0; b < bytesPerSample; b++) {
bytes[i + b] = (byte) (
(temp >>> (8 * (bytesPerSample - b - 1))) & 0xffL
);
}
} else {
for (int b = 0; b < bytesPerSample; b++) {
bytes[i + b] = (byte) ((temp >>> (8 * b)) & 0xffL);
}
}
}
private static long extendSign(long temp, int bitsPerSample) {
int bitsToExtend = Long.SIZE - bitsPerSample;
return (temp << bitsToExtend) >> bitsToExtend;
}
private static long unsignedToSigned(long temp, int bitsPerSample) {
return temp - (long) fullScale(bitsPerSample);
}
private static long signedToUnsigned(long temp, int bitsPerSample) {
return temp + (long) fullScale(bitsPerSample);
}
// mu-law constant
private static final double MU = 255.0;
// A-law constant
private static final double A = 87.7;
// natural logarithm of A
private static final double LN_A = log(A);
private static float bitsToMuLaw(long temp) {
temp ^= 0xffL;
if ((temp & 0x80L) != 0) {
temp = -(temp ^ 0x80L);
}
float sample = (float) (temp / fullScale(8));
return (float) (
signum(sample)
*
(1.0 / MU)
*
(pow(1.0 + MU, abs(sample)) - 1.0)
);
}
private static long muLawToBits(float sample) {
double sign = signum(sample);
sample = abs(sample);
sample = (float) (
sign * (log(1.0 + (MU * sample)) / log(1.0 + MU))
);
long temp = (long) (sample * fullScale(8));
if (temp < 0) {
temp = -temp ^ 0x80L;
}
return temp ^ 0xffL;
}
private static float bitsToALaw(long temp) {
temp ^= 0x55L;
if ((temp & 0x80L) != 0) {
temp = -(temp ^ 0x80L);
}
float sample = (float) (temp / fullScale(8));
float sign = signum(sample);
sample = abs(sample);
if (sample < (1.0 / (1.0 + LN_A))) {
sample = (float) (sample * ((1.0 + LN_A) / A));
} else {
sample = (float) (exp((sample * (1.0 + LN_A)) - 1.0) / A);
}
return sign * sample;
}
private static long aLawToBits(float sample) {
double sign = signum(sample);
sample = abs(sample);
if (sample < (1.0 / A)) {
sample = (float) ((A * sample) / (1.0 + LN_A));
} else {
sample = (float) ((1.0 + log(A * sample)) / (1.0 + LN_A));
}
sample *= sign;
long temp = (long) (sample * fullScale(8));
if (temp < 0) {
temp = -temp ^ 0x80L;
}
return temp ^ 0x55L;
}
}