Como uso dados de amostra de áudio do Java Sound?

Esta pergunta é geralmente feita como parte de outra pergunta, mas a resposta é longa. Decidi responder aqui para poder fazer o link em outro lugar.

Embora eu não esteja ciente de uma maneira que o Java possa produzir amostras de áudio para nós neste momento, se isso mudar no futuro, isso pode ser um lugar para isso. Eu sei que o JavaFX tem algumas coisas como esta, por exemplo AudioSpectrumListener , mas ainda não é uma maneira de acessar amostras diretamente.


Estou usando javax.sound.sampled para reprodução e / ou gravação, mas gostaria de fazer algo com o áudio.

Talvez eu gostaria de exibi-lo visualmente ou processá-lo de alguma forma.

Como access dados de amostra de áudio para fazer isso com o Java Sound?

Veja também:

  • Tutoriais de som Java (oficial)
  • Recursos de som Java (não oficiais)

Bem, a resposta mais simples é que, no momento, o Java não pode produzir dados de amostra para o programador.

Esta citação é do tutorial oficial :

Existem duas maneiras de aplicar o processamento de sinal:

  • Você pode usar qualquer processamento suportado pelo mixer ou por suas linhas componentes, consultando objects Control e, em seguida, definindo os controles como o usuário deseja. Controles típicos suportados por mixers e linhas incluem controles de ganho, pan e reverberação.

  • Se o tipo de processamento necessário não for fornecido pelo mixer ou por suas linhas, seu programa poderá operar diretamente nos bytes de áudio, manipulando-os conforme desejado.

Esta página discute a primeira técnica em maior detalhe, porque não há API especial para a segunda técnica .

A reprodução com javax.sound.sampled age principalmente como uma ponte entre o arquivo e o dispositivo de áudio. Os bytes são lidos do arquivo e enviados.

Não assuma que os bytes são amostras de áudio significativas! A menos que você tenha um arquivo AIFF de 8 bits, eles não são. (Por outro lado, se as amostras são definitivamente assinadas em 8 bits, você pode fazer aritmética com elas. Usar 8 bits é uma maneira de evitar a complexidade descrita aqui, se você estiver apenas brincando.)

Então, em vez disso, vou enumerar os tipos de AudioFormat.Encoding e descrever como decodificá-los você mesmo. Esta resposta não cobre como codificá-los, mas está incluída no exemplo de código completo na parte inferior. A codificação é basicamente o processo de decodificação ao contrário.

Esta é uma resposta longa, mas eu queria dar uma visão geral completa.


Um pouco sobre o áudio digital

Geralmente, quando o áudio digital é explicado, estamos nos referindo à LPCM ( Linear Pulse-Code Modulation ).

Uma onda sonora contínua é amostrada em intervalos regulares e as amplitudes são quantificadas para números inteiros de alguma escala.

É mostrada aqui uma onda senoidal amostrada e quantificada em 4 bits:

lpcm_graph

(Observe que o valor mais positivo na representação de complemento de dois é 1 a menos que o valor mais negativo. Esse é um detalhe menor a ser observado. Por exemplo, se você estiver recortando áudio e esquecer isso, os clipes positivos ficarão sobrecarregados.)

Quando temos áudio no computador, temos uma matriz dessas amostras. Uma matriz de amostra é o que queremos transformar a matriz de byte em.

Para decodificar amostras de PCM, não nos importamos muito com a taxa de amostragem ou o número de canais, então não vou falar muito sobre eles aqui. Os canais geralmente são intercalados, de modo que, se tivéssemos uma variedade deles, eles seriam armazenados assim:

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

Em outras palavras, para estéreo, as amostras na matriz apenas alternam entre esquerda e direita.


Algumas suposições

Todos os exemplos de código irão assumir as seguintes declarações:

  • byte[] bytes; A matriz de byte , lida a partir do AudioInputStream .
  • float[] samples; A matriz de amostra de saída que vamos preencher.
  • float sample; A amostra em que estamos trabalhando atualmente.
  • long temp; Um valor provisório usado para manipulação geral.
  • int i; A posição na matriz de byte que os dados da amostra atual são iniciados.

Vamos normalizar todas as amostras em nossa matriz float[] para o intervalo de -1f <= sample <= 1f . Todo o áudio de ponto flutuante que vi vem por aqui e é bastante conveniente.

Se o nosso áudio de origem ainda não vem assim (como por exemplo amostras inteiras), nós podemos normalizá-las nós mesmos usando o seguinte:

 sample = sample / fullScale(bitsPerSample); 

Onde fullScale é 2 bitsPerSample - 1 , ou seja, Math.pow(2, bitsPerSample-1) .


Como faço para coagir o array de byte em dados significativos?

A matriz de byte contém os frameworks de exemplo divididos e todos em uma linha. Isso é realmente muito direto, exceto por algo chamado endianness , que é a ordenação dos byte s em cada pacote de amostra.

Aqui está um diagrama. Esta amostra (compactada em uma matriz de byte ) contém o valor decimal 9999:

  Amostra de 24 bits como big-endian:

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

  Amostra de 24 bits como little-endian:

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

Eles possuem os mesmos valores binários; no entanto, os pedidos de byte são revertidos.

  • Em big-endian, os byte mais significativos vêm antes dos byte menos significativos.
  • Em little-endian, os byte menos significativos vêm antes dos bytes mais significativos.

Os arquivos WAV são armazenados em ordem little-endian e os arquivos AIFF são armazenados em ordem big-endian. Endianness pode ser obtido em AudioFormat.isBigEndian .

Para concatenar os byte s e colocá-los em nossa variável long temp , nós:

  1. Bit a bit E cada byte com a máscara 0xFF (que é 0b1111_1111 ) para evitar extensão de sinal quando o byte é automaticamente promovido. ( char , byte e short são promovidos para int quando a aritmética é executada neles.) Veja também O que o value & 0xff faz em Java?
  2. Bit deslocar cada byte para a posição.
  3. Bitwise OU o byte s juntos.

Aqui está um exemplo 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 a ordem de troca é invertida com base no endianness.

Isso também pode ser generalizado para um loop, que pode ser visto no código completo na parte inferior desta resposta. (Veja os methods packAnyBit e packAnyBit .)

Agora que temos os byte concatenados juntos, podemos dar mais alguns passos para transformá-los em uma amostra. Os próximos passos dependem da codificação real.

Como decodifico o Encoding.PCM_SIGNED ?

O sinal do complemento dos dois deve ser estendido. Isso significa que, se o bit mais significativo (MSB) for definido como 1, preencheremos todos os bits acima dele com 1s. O deslocamento à direita aritmético ( >> ) fará o preenchimento para nós automaticamente se o bit de sinal estiver definido, então eu normalmente faço assim:

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

(Onde Long.SIZE é 64. Se nossa variável temp não fosse long , Long.SIZE outra coisa. Se Long.SIZE , por exemplo, int temp , usaríamos 32.)

Para entender como isso funciona, aqui está um diagrama de extensão de sinal de 8 bits para 16 bits:

  11111111 é o valor de byte -1, mas os bits superiores do curto são 0.
  Mude o MSB do byte para a posição MSB do curto.

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

  Desloque de volta e o deslocamento direito preenche todos os bits superiores com 1s.
  Agora temos o valor curto de -1.

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

Valores positivos (que tinham 0 no MSB) são mantidos inalterados. Esta é uma boa propriedade do deslocamento direito aritmético.

Em seguida, normalize a amostra, conforme descrito em Algumas suposições .

Você pode não precisar escrever uma extensão de sinal explícita se seu código for simples

Java faz sign-extension automaticamente ao converter de um tipo integral para um tipo maior, por exemplo, byte para int . Se você souber que seu formato de input e saída está sempre assinado, poderá usar a extensão de sinal automática ao concatenar bytes na etapa anterior.

Lembre-se da seção acima ( Como eu coagi a matriz de bytes em dados significativos? ) Que usamos b & 0xFF para impedir que a extensão do sinal ocorra. Se você simplesmente remover o & 0xFF do byte mais alto, a extensão do sinal acontecerá automaticamente.

Por exemplo, o seguinte decodifica amostras de 16 bits assinadas, big-endian:

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

Como decodifico o Encoding.PCM_UNSIGNED ?

Nós o transformamos em um número assinado. Amostras não assinadas são simplesmente compensadas de forma que, por exemplo:

  • Um valor não assinado de 0 corresponde ao valor assinado mais negativo.
  • Um valor não assinado de 2 bitsPerSample - 1 corresponde ao valor assinado de 0.
  • Um valor não assinado de 2 bitsPerSample corresponde ao valor assinado mais positivo.

Então isso acaba sendo bem simples. Apenas subtraia o deslocamento:

 float sample = temp - fullScale(bitsPerSample); 

Em seguida, normalize a amostra, conforme descrito em Algumas suposições .

Como decodifico o Encoding.PCM_FLOAT ?

Isso é novo desde o Java 7.

Na prática, o PCM de ponto flutuante é tipicamente IEEE de 32 bits ou IEEE de 64 bits e já está normalizado no intervalo de ±1.0 . As amostras podem ser obtidas com os methods utilitários Float#intBitsToFloat e 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 

Como decodifico o Encoding.ULAW e o Encoding.ALAW ?

Estes são codecs de compactação de compression que são mais comuns em telefones e outros. Eles são suportados por javax.sound.sampled Eu suponho porque eles são usados ​​pelo formato Au da Sun. (No entanto, não se limita apenas a esse tipo de contêiner. Por exemplo, o WAV pode conter essas codificações.)

Você pode conceituar A-law e μ-law como se fossem um formato de ponto flutuante. Esses são formatos PCM, mas o intervalo de valores não é linear.

Existem duas maneiras de decodificá-las. Eu mostrarei o caminho que usa a fórmula matemática. Você também pode decodificá-los manipulando diretamente o binário descrito neste post, mas é mais esotérico.

Para ambos, os dados compactados são de 8 bits. Em geral, a lei A é de 13 bits quando decodificada e a lei μ é de 14 bits quando decodificada; No entanto, aplicando a fórmula produz um intervalo de ±1.0 .

Antes de poder aplicar a fórmula, existem três coisas a fazer:

  1. Alguns dos bits são invertidos de forma padronizada para armazenamento devido a razões que envolvem a integridade dos dados.
  2. Eles são armazenados como sinal e magnitude (em vez de complemento de dois).
  3. A fórmula também espera um intervalo de ±1.0 , portanto, o valor de 8 bits deve ser dimensionado.

Para a lei μ, todos os bits são invertidos, portanto:

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

(Note que não podemos usar ~ , porque não queremos inverter os bits altos do long .)

Para A-law, todos os outros bits são invertidos, portanto:

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

(XOR pode ser usado para fazer inversão. Veja Como você define, limpa e alterna um pouco? )

Para converter de sinal e magnitude em complemento de dois, nós:

  1. Verifique se o bit de sinal foi definido.
  2. Nesse caso, limpe o bit de sinal e anule o número.
 // 0x80 == 0b1000_0000 if ((temp & 0x80L) != 0) { temp ^= 0x80L; temp = -temp; } 

Em seguida, dimensione os números codificados, da mesma forma descrita em Algumas suposições :

 sample = temp / fullScale(8); 

Agora podemos aplicar a expansão.

A fórmula da lei μ traduzida para Java é então:

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

A fórmula A-law traduzida para Java é então:

 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; 

Aqui está o código de exemplo completo para a class 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; } }

É assim que você obtém os dados de amostra reais do som atualmente sendo reproduzido. A outra excelente resposta dirá o que os dados significam. Não tentei em outro sistema operacional que o meu Windows 10 YMMV. Para mim, ele puxa o atual dispositivo de gravação padrão do sistema. No Windows, defina como “Stereo Mix” em vez de “Microphone” para reproduzir o som. Você pode ter que alternar “Show Disabled Devices” para ver “Stereo Mix”.

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