この質問は通常、別の質問の一部として尋ねられますが、回答が長くなることが判明しました。他の場所にリンクできるように、ここで回答することにしました。
現時点ではJavaでオーディオサンプルを生成できる方法を知りませんが、将来的にそれが変更されれば、これがその場所になる可能性があります。JavaFX
例えば、このようなものがありますAudioSpectrumListener
ただし、サンプルに直接アクセスする方法はまだありません。
javax.sound.sampled
再生や録音に使用していますが、オーディオで何かを行いたいと思っています。
おそらく、それを視覚的に表示したり、何らかの方法で処理したりしたいと思うでしょう。
Java Sound でこれを行うには、オーディオ サンプル データにアクセスするにはどうすればよいですか?
参照:
- Java サウンド チュートリアル(正式)
- Java サウンド リソース(非公式)
ベストアンサー1
まあ、最も簡単な答えは、現時点では Java はプログラマー向けのサンプル データを生成できないということです。
信号処理を適用するには 2 つの方法があります。
オブジェクトを照会し、ユーザーの希望に応じてコントロールを設定することで、ミキサーまたはそのコンポーネント ラインでサポートされている任意の処理を使用できます
Control
。ミキサーとラインでサポートされている一般的なコントロールには、ゲイン、パン、およびリバーブ コントロールがあります。必要な種類の処理がミキサーまたはそのラインによって提供されていない場合は、プログラムでオーディオ バイトを直接操作し、必要に応じて操作することができます。
このページでは、最初の手法についてより詳しく説明します。2番目の手法には特別なAPIはない。
再生はjavax.sound.sampled
主にファイルとオーディオ デバイス間のブリッジとして機能します。バイトはファイルから読み込まれ、送信されます。
バイトが意味のあるオーディオサンプルであると想定しないでください。8ビットAIFFファイルでない限り、意味のあるものではありません。(一方、サンプルがは間違いなく8ビット符号付き、できるこれらを使って算術演算を行います。単に遊んでいるだけなら、8 ビットを使用することが、ここで説明した複雑さを回避する 1 つの方法です。
代わりに、私はAudioFormat.Encoding
そして、自分で解読する方法を説明してください。この回答はないエンコード方法については説明しませんが、下部の完全なコード例に含まれています。エンコードは、ほとんどの場合、デコード処理を逆に実行するだけです。
長い回答ですが、徹底した概要を説明したいと思います。
デジタルオーディオについて
一般的にデジタルオーディオについて説明するときは、線形パルスコード変調(LPCM)。
連続した音波が一定の間隔でサンプリングされ、振幅が何らかのスケールの整数に量子化されます。
ここに示すのは、4 ビットにサンプリングされ量子化された正弦波です。
(最も正の値である2の補数表現は、最も負の値より 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);
ここでfullScale
、 2 bitsPerSample - 1、すなわちですMath.pow(2, bitsPerSample-1)
。
byte
配列を意味のあるデータに強制変換するにはどうすればよいですか?
配列byte
にはサンプルフレームが分割されて一列に並んでいます。これは実際には非常に単純ですが、エンディアンbyte
これは各サンプル パケット内の の順序です。
ここに図があります。このサンプル (配列にパックされていますbyte
) は、10 進数値 9999 を保持します。
ビッグエンディアンとしての24ビットサンプル: バイト[i] バイト[i + 1] バイト[i + 2] ┌──────┐ ┌──────┐ ┌──────┐ 00000000 00100111 00001111 リトルエンディアンとしての24ビットサンプル: バイト[i] バイト[i + 1] バイト[i + 2] ┌──────┐ ┌──────┐ ┌──────┐ 00001111 00100111 00000000
これらは同じバイナリ値を保持しますが、byte
順序は逆になります。
- ビッグエンディアンでは、より重要な
byte
s がより重要でないbyte
s の前に来ます。 - リトルエンディアンでは、下位の
byte
s が上位の s の前に来ますbytes
。
WAVAファイルファイルはリトルエンディアン順で保存され、AIFF ファイルビッグエンディアン順で保存されます。エンディアンは以下から取得できます。AudioFormat.isBigEndian
。
を連結しbyte
て変数に入れるにはlong temp
、次のようにします。
byte
それぞれをマスク0xFF
( )とビット単位でAND演算して0b1111_1111
回避する符号拡張がbyte
自動的に昇格されます。 (char
、byte
および は、それらに対して演算が実行されるとshort
に昇格されます。) も参照してください。int
value & 0xff
Java では何を行いますか?- 各ビット
byte
を位置までシフトします。 - s をビット単位で OR します
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
メソッドを参照してください。)
連結が完了したのでbyte
、さらにいくつかの手順を実行してサンプルに変換できます。次の手順は、実際のエンコードによって異なります。
どうすればデコードできますかEncoding.PCM_SIGNED
?
2 の補数の符号は拡張する必要があります。つまり、最上位ビット (MSB) が 1 に設定されている場合、それより上のすべてのビットを 1 で埋めます。算術右シフト ( >>
) は、符号ビットが設定されている場合、自動的に埋めるので、通常は次のようにします。
int bitsToExtend = Long.SIZE - bitsPerSample;
float sample = (temp << bitsToExtend) >> bitsToExtend.
(Long.SIZE
は 64 です。temp
変数が でない場合はlong
、別のものを使用します。int temp
代わりに eg を使用する場合は、32 を使用します。)
これがどのように機能するかを理解するために、8 ビットを 16 ビットに符号拡張する図を示します。
11111111 はバイト値 -1 ですが、short の上位ビットは 0 です。 バイトの MSB を short の MSB 位置にシフトします。 0000 0000 1111 1111 << 8 ──────────────────── 1111 1111 0000 0000 それを後ろにシフトし、右シフトによって上位ビットがすべて 1 で埋められます。 これでショート値は -1 になりました。 1111 1111 0000 0000 >> 8 ──────────────────── 1111 1111 1111 1111
正の値 (MSB が 0 である値) は変更されません。これは算術右シフトの優れた特性です。
次に、サンプルを正規化する。いくつかの仮定。
コードが単純な場合は、明示的に符号拡張を記述する必要はないかもしれない。
byte
Javaは、ある整数型からより大きな型(例えば )に変換するときに自動的に符号拡張を行います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 bitsPerSample - 1は、符号付き値 0 に対応します。
- 2 bitsPerSampleの符号なし値は、最も正の符号付き値に対応します。
これは非常に簡単です。オフセットを減算するだけです。
float sample = temp - fullScale(bitsPerSample);
次に、サンプルを正規化する。いくつかの仮定。
どうすればデコードできますかEncoding.PCM_FLOAT
?
これは Java 7 以降の新機能です。
実際には、浮動小数点PCMはIEEE 32ビットまたはIEEE 64ビットのいずれかであり、すでに範囲に正規化されています±1.0
。サンプルはユーティリティメソッドを使用して取得できます。Float#intBitsToFloat
そしてDouble#longBitsToDouble
。
// 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
?
これらは圧縮電話などでよく使われる圧縮コーデックですjavax.sound.sampled
。SunのAu形式(ただし、このタイプのコンテナだけに限定されるわけではありません。たとえば、WAV にはこれらのエンコーディングを含めることができます。)
概念化できる法律そしてμ法則浮動小数点形式のようです。これらは PCM 形式ですが、値の範囲は非線形です。
解読には2つの方法があります。ここでは数式を使った方法を紹介します。バイナリを直接操作して解読することもできます。このブログ記事で説明されているしかし、見た目はより難解です。
どちらも、圧縮データは 8 ビットです。標準的には、A-law はデコード時に 13 ビット、μ-law はデコード時に 14 ビットですが、式を適用すると範囲が得られます±1.0
。
数式を適用する前に、次の 3 つのことを行う必要があります。
- データの整合性に関係する理由により、一部のビットは標準的に反転されて保存されます。
- これらは、2 の補数ではなく、符号と大きさとして保存されます。
- 数式では の範囲も想定されている
±1.0
ため、8 ビットの値をスケーリングする必要があります。
μ-lawの場合すべてのビット反転すると次のようになります。
temp ^= 0xffL; // 0xff == 0b1111_1111
~
( の上位ビットを反転したくないので は使用できないことに注意してくださいlong
。)
A-lawの場合、1ビットおきに反転すると次のようになります。
temp ^= 0x55L; // 0x55 == 0b0101_0101
(XORは反転に使用できます。ビットを設定、クリア、切り替えるにはどうすればいいですか?)
符号と大きさを 2 の補数に変換するには、次のようにします。
- 符号ビットが設定されているかどうかを確認します。
- そうであれば、符号ビットをクリアし、数値を反転します。
// 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)
);
A-law 式を Java に翻訳すると次のようになります。
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)