¿Cómo uso los datos de muestra de audio de Java Sound?

Esta pregunta generalmente se hace como parte de otra pregunta, pero resulta que la respuesta es larga. Decidí responderlo aquí para poder vincularlo a otro sitio.

Aunque no conozco la forma en que Java pueda producir muestras de audio para nosotros en este momento, si eso cambia en el futuro, este puede ser un lugar para ello. Sé que JavaFX tiene algunas cosas como esta, por ejemplo AudioSpectrumListener , pero todavía no es una forma de acceder directamente a las muestras.


Estoy usando javax.sound.sampled para la reproducción y / o grabación, pero me gustaría hacer algo con el audio.

Tal vez me gustaría mostrarlo visualmente o procesarlo de alguna manera.

¿Cómo accedo a los datos de muestra de audio para hacer eso con Java Sound?

Ver también:

  • Tutoriales de Java Sound (Oficial)
  • Recursos de sonido de Java (no oficial)

Bueno, la respuesta más simple es que, por el momento, Java no puede producir datos de muestra para el progtwigdor.

Esta cita es del tutorial oficial :

Hay dos formas de aplicar el procesamiento de señal:

  • Puede usar cualquier procesamiento admitido por el mezclador o sus líneas componentes, preguntando por objetos de Control y luego configurando los controles como lo desee el usuario. Los controles típicos soportados por mezcladores y líneas incluyen controles de ganancia, panoramización y reverberación.

  • Si el mezclador o sus líneas no proporcionan el tipo de procesamiento que necesita, su progtwig puede operar directamente en los bytes de audio, manipulándolos como desee.

Esta página discute la primera técnica en mayor detalle, porque no hay una API especial para la segunda técnica .

La reproducción con javax.sound.sampled actúa principalmente como un puente entre el archivo y el dispositivo de audio. Los bytes se leen desde el archivo y se envían.

¡No suponga que los bytes son muestras de audio significativas! A menos que tengas un archivo AIFF de 8 bits, no lo son. (Por otro lado, si las muestras están definitivamente firmadas con 8 bits, puede hacer operaciones aritméticas con ellas. Usar 8 bits es una forma de evitar la complejidad descrita aquí, si solo está jugando).

Entonces, en su lugar, enumeraré los tipos de AudioFormat.Encoding y describiré cómo decodificarlos usted mismo. Esta respuesta no cubrirá cómo codificarlos, pero se incluye en el ejemplo de código completo en la parte inferior. La encoding es principalmente el proceso de deencoding en reversa.

Esta es una respuesta larga, pero quería dar una descripción completa.


Un poco sobre audio digital

En general, cuando se explica el audio digital, nos referimos a la modulación de código de pulso lineal (LPCM).

Una onda de sonido continua se muestrea a intervalos regulares y las amplitudes se cuantifican en enteros de alguna escala.

Aquí se muestra una onda sinusoidal muestreada y cuantizada a 4 bits:

lpcm_graph

(Tenga en cuenta que el valor más positivo en la representación de complemento de dos es 1 menos que el valor más negativo. Este es un detalle menor que debe tenerse en cuenta. Por ejemplo, si está recortando audio y olvida esto, los clips positivos se desbordarán).

Cuando tenemos audio en la computadora, tenemos una matriz de estas muestras. Una matriz de muestra es a lo que queremos convertir la matriz de byte .

Para decodificar muestras PCM, no nos importa mucho la frecuencia de muestreo o la cantidad de canales, por lo que no voy a decir mucho sobre ellos aquí. Los canales suelen estar intercalados, de modo que si tuviéramos una matriz de ellos, se almacenarían así:

 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) ... 

En otras palabras, para estéreo, las muestras en la matriz solo se alternan entre la izquierda y la derecha.


Algunas suposiciones

Todos los ejemplos de código asumirán las siguientes declaraciones:

  • byte[] bytes; La matriz de byte , leída de AudioInputStream .
  • float[] samples; La matriz de muestra de salida que vamos a llenar.
  • float sample; La muestra en la que estamos trabajando actualmente.
  • long temp; Un valor provisional utilizado para la manipulación general.
  • int i; La posición en la matriz de byte donde comienzan los datos de la muestra actual.

Normalizaremos todas las muestras en nuestra matriz float[] al rango de -1f <= sample <= 1f . Todo el audio de punto flotante que he visto viene de esta manera y es bastante conveniente.

Si nuestro audio de origen no viene así (como lo es, por ejemplo, muestras enteras), podemos normalizarlas usando lo siguiente:

 sample = sample / fullScale(bitsPerSample); 

Donde fullScale es 2 bitsPerSample - 1 , es decir, Math.pow(2, bitsPerSample-1) .


¿Cómo forzar la matriz de byte a datos significativos?

La matriz de byte contiene los marcos de muestra divididos y todo en una línea. Esto es realmente sencillo, excepto por algo llamado endianness , que es el orden de los byte en cada paquete de muestra.

Aquí hay un diagtwig. Esta muestra (empaquetada en una matriz de byte ) contiene el valor decimal 9999:

  Muestra de 24 bits como big-endian:

  bytes [i] bytes [i + 1] bytes [i + 2]
  ┌──────┐ ┌──────┐ ┌──────┐
  00000000 00100111 00001111

  Muestra de 24 bits como little-endian:

  bytes [i] bytes [i + 1] bytes [i + 2]
  ┌──────┐ ┌──────┐ ┌──────┐
  00001111 00100111 00000000 

Ellos tienen los mismos valores binarios; sin embargo, las órdenes de byte están invertidas.

  • En big-endian, los byte más significativos vienen antes que los byte menos significativos.
  • En little-endian, los byte menos significativos vienen antes que los bytes más significativos.

Los archivos WAV se almacenan en orden little-endian y los archivos AIFF se almacenan en orden big-endian. Endianness se puede obtener de AudioFormat.isBigEndian .

Para concatenar los byte y ponerlos en nuestra variable long temp , nosotros:

  1. Bitwise Y cada byte con la máscara 0xFF (que es 0b1111_1111 ) para evitar la extensión de signo cuando el byte se promueve automáticamente. ( char , byte y short se promueven a int cuando se realiza una aritmética en ellos). Consulte también ¿Qué hace value & 0xff en Java?
  2. Bit cambia cada byte a la posición.
  3. Bitwise O los byte juntos.

Aquí hay un ejemplo de 24 bits:

 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) ); } 

Observe que el orden de desplazamiento se invierte en función del endianness.

Esto también se puede generalizar a un bucle, que se puede ver en el código completo en la parte inferior de esta respuesta. (Consulte los métodos unpackAnyBit y packAnyBit ).

Ahora que hemos concatenado los byte , podemos dar algunos pasos más para convertirlos en una muestra. Los siguientes pasos dependen de la encoding real.

¿Cómo decodizo Encoding.PCM_SIGNED ?

El signo del complemento de los dos debe ser extendido. Esto significa que si el bit más significativo (MSB) se establece en 1, llenaremos todos los bits que están sobre él con 1s. El desplazamiento a la derecha aritmético ( >> ) nos rellenará automáticamente si el bit de signo está configurado, por lo que generalmente lo hago de esta manera:

 int bitsToExtend = Long.SIZE - bitsPerSample; float sample = (temp << bitsToExtend) >> bitsToExtend. 

(Donde Long.SIZE es 64. Si nuestra variable temp no fuera long , Long.SIZE algo más. Si Long.SIZE por ejemplo, int temp lugar, usaríamos 32.)

Para comprender cómo funciona esto, aquí hay un diagtwig de extensión de signos de 8 bits a 16 bits:

  11111111 es el valor de byte -1, pero los bits superiores del short son 0.
  Cambie el MSB del byte a la posición MSB del corto.

  0000 0000 1111 1111
  << 8
  ───────────────────
  1111 1111 0000 0000

  Desplácelo hacia atrás y el desplazamiento a la derecha llena todos los bits superiores con 1s.
  Ahora tenemos el valor corto de -1.

  1111 1111 0000 0000
  >> 8
  ───────────────────
  1111 1111 1111 1111 

Los valores positivos (que tenían un 0 en el MSB) no se modifican. Esta es una propiedad agradable del desplazamiento a la derecha aritmético.

Luego, normalice la muestra, como se describe en Algunas suposiciones .

Es posible que no necesite escribir la extensión de signo explícita si su código es simple

Java hace una extensión de signo automáticamente al convertir de un tipo integral a un tipo más grande, por ejemplo byte a int . Si sabe que su formato de entrada y salida siempre está firmado, puede usar la extensión de signo automática al concatenar bytes en el paso anterior.

Recuerde en la sección anterior ( ¿Cómo forzar la matriz de bytes a datos significativos? ) Que utilizamos b & 0xFF para evitar que se produzca la extensión de la señal. Si solo quita el & 0xFF del byte más alto, la extensión del signo se producirá automáticamente.

Por ejemplo, las siguientes decodificaciones firmadas, big-endian, muestras de 16 bits:

 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 // ... } 

¿Cómo decodifico Encoding.PCM_UNSIGNED ?

Lo convertimos en un número firmado. Las muestras sin firmar se compensan simplemente de modo que, por ejemplo:

  • Un valor sin signo de 0 corresponde al valor con signo más negativo.
  • Un valor sin signo de 2 bits Por Muestra - 1 corresponde al valor con signo de 0.
  • Un valor sin signo de 2 bits Por Muestra corresponde al valor firmado más positivo.

Así que esto resulta ser bastante simple. Solo resta el desplazamiento:

 float sample = temp - fullScale(bitsPerSample); 

Luego, normalice la muestra, como se describe en Algunas suposiciones .

¿Cómo decodizo Encoding.PCM_FLOAT ?

Esto es nuevo desde Java 7.

En la práctica, PCM de punto flotante es típicamente IEEE de 32 bits o IEEE de 64 bits y ya está normalizado en el rango de ±1.0 . Las muestras se pueden obtener con los métodos de utilidad Float#intBitsToFloat y 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 

¿Cómo decodifico Encoding.ULAW y Encoding.ALAW ?

Estos son códecs de compresión comparativos que son más comunes en teléfonos y tal. Están respaldados por javax.sound.sampled , supongo, porque son utilizados por el formato Au de Sun. (Sin embargo, no se limita a este tipo de contenedor. Por ejemplo, WAV puede contener estas codificaciones).

Puede conceptualizar la ley A y la ley μ como si fueran un formato de coma flotante. Estos son formatos PCM, pero el rango de valores no es lineal.

Hay dos formas de decodificarlos. Mostraré el camino que usa la fórmula matemática. También puede decodificarlos manipulando el binario directamente, que se describe en esta publicación de blog, pero tiene un aspecto más esotérico.

Para ambos, los datos comprimidos son de 8 bits. La ley A estándar es de 13 bits cuando se decodifica y la ley μ es de 14 bits cuando se decodifica; sin embargo, al aplicar la fórmula se obtiene un rango de ±1.0 .

Antes de poder aplicar la fórmula, hay tres cosas que hacer:

  1. Algunos de los bits están invertidos de forma estándar para su almacenamiento debido a razones que involucran la integridad de los datos.
  2. Se almacenan como signo y magnitud (en lugar del complemento de dos).
  3. La fórmula también espera un rango de ±1.0 , por lo que el valor de 8 bits debe escalarse.

Para la ley μ todos los bits están invertidos, entonces:

 temp ^= 0xffL; // 0xff == 0b1111_1111 

(Tenga en cuenta que no podemos usar ~ , porque no queremos invertir los bits altos del long ).

Para la ley A, todos los demás bits están invertidos, entonces:

 temp ^= 0x55L; // 0x55 == 0b0101_0101 

(XOR se puede usar para hacer inversión. Consulte ¿Cómo se establece, se borra y se alterna un poco? )

Para convertir de signo y magnitud a complemento de dos, nosotros:

  1. Verifique si el bit de signo se configuró.
  2. Si es así, borre el bit de signo y niegue el número.
 // 0x80 == 0b1000_0000 if ((temp & 0x80L) != 0) { temp ^= 0x80L; temp = -temp; } 

Luego escale los números codificados, de la misma forma que se describe en Algunas suposiciones :

 sample = temp / fullScale(8); 

Ahora podemos aplicar la expansión.

La fórmula de la ley μ traducida a Java es entonces:

 sample = (float) ( signum(sample) * (1.0 / 255.0) * (pow(256.0, abs(sample)) - 1.0) ); 

La fórmula de la ley A traducida a Java es entonces:

 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; 

Aquí está el código de ejemplo completo para la clase SimpleAudioConversion .

 package mcve.audio; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioFormat.Encoding; import static java.lang.Math.*; /** * 

Performs simple audio format conversion.

* *

Example usage:

* *
{@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); * }}

* * @author Radiodef * @see Overview on Stack Overflow */ 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). *

* 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. *

* 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; } }

Así es como obtienes los datos de muestra reales del sonido que se está reproduciendo actualmente. La otra respuesta excelente le dirá qué significan los datos. No lo he probado en otro sistema operativo que no sea mi máquina con Windows 10 YMMV. Para mí, extrae el dispositivo de grabación predeterminado del sistema actual. En Windows, configúrelo en “Mezcla estéreo” en lugar de “Micrófono” para obtener sonido de reproducción. Es posible que deba alternar entre “Mostrar dispositivos deshabilitados” para ver “Mezcla estéreo”.

 import javax.sound.sampled.*; public class SampleAudio { private static long extendSign(long temp, int bitsPerSample) { int extensionBits = 64 - bitsPerSample; return (temp << extensionBits) >> extensionBits; } public static void main(String[] args) throws LineUnavailableException { float sampleRate = 8000; int sampleSizeBits = 16; int numChannels = 1; // Mono AudioFormat format = new AudioFormat(sampleRate, sampleSizeBits, numChannels, true, true); TargetDataLine tdl = AudioSystem.getTargetDataLine(format); tdl.open(format); tdl.start(); if (!tdl.isOpen()) { System.exit(1); } byte[] data = new byte[(int)sampleRate*10]; int read = tdl.read(data, 0, (int)sampleRate*10); if (read > 0) { for (int i = 0; i < read-1; i = i + 2) { long val = ((data[i] & 0xffL) << 8L) | (data[i + 1] & 0xffL); long valf = extendSign(val, 16); System.out.println(i + "\t" + valf); } } tdl.close(); } }