Noções básicas da API JAVA SOUND

Olá Habr! Apresento a você a tradução do artigo “Java Sound, Getting Started, Part 1, Playback” .

Som em JAVA, parte um, o começo. Tocando som



Este é o começo de uma série de oito lições que o familiarizarão totalmente com a API Java Sound.

O que é som na percepção humana? Essa é a sensação que experimentamos quando uma mudança na pressão do ar é transmitida para as pequenas áreas sensoriais dentro de nossos ouvidos.

E o principal objetivo da criação da API de som é fornecer meios para escrever código, o que ajudará a transferir ondas de pressão para os ouvidos do sujeito certo, na hora certa.

Tipos de som em Java:

  1. A API Java Sound suporta dois tipos principais de áudio (som).
  2. Som digitalizado e gravado diretamente como um arquivo
  3. Grave como um arquivo MIDI. Muito distante, mas semelhante à notação musical, onde os instrumentos musicais são tocados na sequência desejada.

Esses tipos são bastante diferentes em sua essência e vamos nos concentrar no primeiro, já que na maioria dos casos estamos lidando com som que precisa ser digitalizado para gravar de uma fonte externa para um arquivo ou vice-versa para reproduzir o som anteriormente gravado nesse arquivo.

Pré-visualização


A API Java Sound é baseada no conceito de linhas e mixers.

Seguinte:
Descreveremos as características físicas e elétricas da representação analógica do som aplicada a um mixer de áudio .

Voltaremos ao cenário da banda de rock iniciante, que usa seis microfones e dois alto-falantes estéreo nesse caso. Precisamos disso para entender o funcionamento do mixer de áudio.

A seguir, analisamos vários temas Java Sound para programação, como linhas, mixers, formatos para dados de áudio e muito mais.

Vamos entender as relações existentes entre os objetos SourceDataLine, Clip, Mixer, AudioFormat e criar um programa simples que reproduz áudio.

Abaixo, apresentamos um exemplo deste programa, que você pode usar para gravar e reproduzir o som gravado.

No futuro, forneceremos uma explicação completa do código do programa usado para esse fim. Mas de maneira alguma completamente nesta lição.

Exemplo de código e consideração


Características físicas e elétricas do som analógico

O objetivo de nossa lição é apresentar o básico da programação Java usando a API Java Sound.

A API Java Sound é baseada no conceito de um mixer de áudio, que é um dispositivo comumente usado para reproduzir som em quase qualquer lugar: de shows de rock a ouvir CDs em casa. Porém, antes de iniciar uma explicação detalhada da operação do mixer de áudio, será útil se familiarizar com as características físicas e elétricas do próprio som analógico.

Veja a Fig. 1



Vasya Pupyrkin faz um discurso.

Esta figura mostra Vasya fazendo um discurso usando um sistema conhecido como endereço amplo. Esse sistema normalmente inclui um microfone, amplificador e alto-falante. O objetivo deste sistema é fortalecer a voz de Vasya para que ele possa ser ouvido mesmo em uma grande multidão.

Oscile no ar

Resumidamente, quando Vasya fala, suas cordas vocais fazem com que as partículas de ar vibrem em sua laringe. Isso leva ao surgimento de ondas sonoras, que, por sua vez, fazem a membrana do microfone vibrar e depois se transformam em vibrações elétricas de amplitude muito pequena que simulam exatamente as vibrações sonoras do original de Vasya. Um amplificador, como o próprio nome indica, amplifica essas vibrações elétricas. Então eles chegam ao alto-falante, que realiza a transformação inversa de vibrações elétricas amplificadas em ondas sonoras muito amplificadas, mas que ainda assim repetem exatamente as mesmas ondas geradas nas cordas vocais de Vasya Pupyrkin.

Microfone dinâmico

Agora vamos ver a Fig. 2, que mostra um diagrama esquemático de um microfone chamado dinâmico.


Fig. 2 circuito de microfone dinâmico

Vibrações sonoras afetam a membrana

A pressão das vibrações sonoras atua sobre uma membrana flexível dentro do microfone. Isso faz com que a membrana vibre, enquanto as vibrações repetem as vibrações das ondas sonoras.

Bobina móvel

Uma bobina de fio fino é presa à membrana do microfone. À medida que a membrana oscila, a bobina também faz movimentos alternativos no campo magnético do núcleo, feito de um forte ímã permanente. E como Faraday também estabeleceu, uma corrente elétrica surge na bobina.

Um sinal elétrico segue o formato das ondas sonoras.

Assim, a partir de uma corrente muito fraca induzida na bobina, é obtido um sinal elétrico alternado, repetindo a forma de ondas sonoras que atuam na membrana do microfone. Além disso, este sinal na forma de uma tensão alternada é alimentado à entrada do amplificador da Fig. 1

Altifalante

De fato, o princípio de operação do alto-falante repete o dispositivo de um microfone dinâmico, ligado apenas na direção oposta. (Naturalmente, neste caso, os fios do enrolamento são muito mais grossos e a membrana é muito maior para garantir a operação com um sinal amplificado)




As oscilações da membrana do alto-falante afetam as partículas de ar e criam ondas sonoras poderosas. O formato dessas ondas repete exatamente o formato das ondas sonoras de intensidade muito mais baixa, criadas pelas cordas vocais de Vasya. Mas a intensidade das novas ondas agora é suficiente para garantir que as vibrações sonoras de Vasya cheguem aos ouvidos das pessoas, mesmo nas fileiras de trás de uma grande multidão.

Concerto de rock

A essa altura, você deve estar se perguntando o que tudo isso tem a ver com a Java Sound API? Mas espere um pouco mais, estamos liderando o caminho para o básico do mixer de áudio.

O circuito descrito acima foi bastante simples. Consistia em Vasya Pupyrkin, um microfone, um amplificador e um alto-falante. Agora considere o circuito com a Fig. 4, que apresenta o palco preparado para o show de rock do grupo musical iniciante.



Seis microfones e dois alto-falantes

Na Fig. 4 seis microfones estão localizados no palco. Dois alto-falantes (alto-falantes) estão localizados nas laterais do palco. Quando o show começa, os artistas cantam ou tocam música em cada um dos seis microfones. Conseqüentemente, teremos seis sinais elétricos, que devem ser amplificados individualmente e depois alimentados aos dois alto-falantes. Além disso, os artistas podem usar vários efeitos especiais de som, por exemplo, reverb, que também precisarão ser convertidos em sinais elétricos antes de aplicá-los aos alto-falantes.

Dois alto-falantes nas laterais do palco foram projetados para criar o efeito do som estéreo. Ou seja, o sinal elétrico proveniente do microfone localizado no palco à direita deve cair no alto-falante localizado também à direita. Da mesma forma, o sinal do microfone para a esquerda deve ser alimentado ao alto-falante localizado à esquerda da cena. Mas os sinais elétricos de outros microfones localizados perto do centro do palco já devem ser transmitidos aos dois alto-falantes em proporções apropriadas. E dois microfones no centro devem transmitir o sinal para os dois alto-falantes igualmente.

Mixer de áudio

A tarefa discutida acima é realizada apenas por um dispositivo eletrônico chamado mixer de áudio.

Linha de áudio (canal)

Embora o autor não seja especialista em mixers de áudio, em seu humilde entendimento, um mixador de áudio típico tem a capacidade de receber na entrada um certo número de sinais elétricos independentes uns dos outros, cada um dos quais representa o sinal de som ou a linha (canal) original.

(O conceito de canal de áudio se tornará muito importante quando começarmos a entender a API Java Sound em detalhes.

Processamento independente de cada canal de áudio

De qualquer forma, o mixer de áudio padrão tem a capacidade de amplificar cada linha de áudio independentemente dos outros canais. Além disso, o mixer geralmente tem a capacidade de impor efeitos especiais ao som, como, por exemplo, reverberação para qualquer uma das linhas de áudio. No final, o mixer, como o próprio nome indica, pode misturar todos os sinais elétricos individuais nos canais de saída conforme definido, de modo a controlar a contribuição de cada linha de áudio para os canais de saída (esse controle é geralmente chamado de pan ou pan - distribuição no espaço).

Retornando ao som estéreo

Assim, no diagrama da Fig. 4, o engenheiro de som do mixer de áudio tem a capacidade de combinar sinais de seis microfones para obter dois sinais de saída, cada um dos quais é transmitido ao seu alto-falante.

Para uma operação bem-sucedida, o sinal de cada microfone deve ser fornecido na proporção apropriada, dependendo da localização física do microfone no palco. (Ao alterar o panorama, um engenheiro de som qualificado pode alterar a contribuição de cada microfone, se necessário, se, por exemplo, o vocalista principal se mover pelo palco durante um concerto).

Hora de voltar ao mundo da programação

Voltemos agora do mundo físico para o mundo da programação. De acordo com a Sun: “O Java Sound não envolve nenhuma configuração especial de hardware; Ele foi projetado para permitir que vários componentes de áudio sejam instalados no sistema e disponibilizados ao usuário por meio da API. O Java Sound suporta a funcionalidade padrão de entrada e saída de uma placa de som (por exemplo, para gravar e reproduzir arquivos de áudio), além da capacidade de misturar vários fluxos de áudio ”

Misturadores e canais

Como já mencionado, a Java Sound API é baseada no conceito de mixers e canais. Se você passar do mundo físico para o mundo da programação, a Sun escreverá o seguinte sobre o mixer:

“Um mixer é um dispositivo de áudio com um ou mais canais. Mas o mixer que realmente mistura o sinal de áudio deve ter vários canais de entrada de fontes de origem e pelo menos um canal de destino de saída ".

Linhas de entrada podem ser instâncias de classes com objetos SourceDataLine e linhas de saída podem ser objetos TargetDataLine. O mixer também pode receber som pré-gravado e em loop como uma entrada, definindo seus canais de origem de entrada como instâncias de objetos de classe que implementam a interface Clip.

Interface de linha de canal.

A Sun relata o seguinte na interface Line: “ Line é um elemento de um pipeline de áudio digital, como uma porta de áudio de entrada ou saída, mixer ou caminho de áudio de ou para um mixer. Os dados de áudio que passam pelo canal podem ser mono ou multicanal (por exemplo, estéreo). ... Um canal pode ter controles, como ganho, pan e reverb. ”

Juntando os termos

Portanto, as citações acima da Sun denotam os seguintes termos

Sourcedataline
Targetgetataline
Port
Clip
Controles

Fig. 5 mostra um exemplo do uso desses termos para criar um programa simples de saída de áudio.



Script do programa

Do ponto de vista do software 5 mostra um objeto Mixer obtido com um objeto Clip e dois objetos SourceDataLine.

O que é Clip

Clip é um objeto na entrada do mixer, cujo conteúdo não muda com o tempo. Em outras palavras, você carrega os dados de áudio no objeto Clipe antes de reproduzi-los. O conteúdo de áudio do objeto Clipe pode ser reproduzido uma ou mais vezes. Você pode fazer um loopback do clipe e, em seguida, o conteúdo será reproduzido repetidamente.

Fluxo de entrada

O objeto SourceDataLine, por outro lado, é um objeto de fluxo na entrada do misturador. Um objeto desse tipo pode receber um fluxo de dados de áudio e enviá-lo para o mixer em tempo real. Os dados de áudio necessários podem ser obtidos de várias fontes, como arquivos de áudio, conexão de rede ou buffer de memória.

Diferentes tipos de canais

Portanto, os objetos Clip e SourceDataLine podem ser considerados como canais de entrada para o objeto Mixer. Cada um desses canais de entrada pode ter seus próprios: panorâmica, ganho e reverb.

Reproduzir conteúdo de áudio

Em um sistema tão simples, o Mixer lê dados das linhas de entrada, usa o controle para misturar sinais de entrada e fornece saída para um ou mais canais de saída, como alto-falante, saída de linha, fone de ouvido e assim por diante.

A Listagem 11 mostra um programa simples que captura dados de áudio de uma porta de microfone, armazena esses dados na memória e os reproduz pela porta do alto-falante.

Discutiremos apenas captura e reprodução. A maior parte do programa acima é criar uma janela e uma interface gráfica para o usuário, para que seja possível controlar a gravação e a reprodução. Não discutiremos esta parte como indo além da meta. Mas então consideraremos a captura e reprodução de dados. Discutiremos a perda nesta lição e a captura na próxima. Ao longo do caminho, ilustraremos o uso do canal de áudio com a API Java Sound.

Os dados capturados são armazenados em um objeto ByteArrayOutputStream.

Um trecho de código fornece a leitura de dados de áudio de um microfone e o armazenamento como um objeto ByteArrayOutputStream.

O método, chamado playAudio, que começa na Listagem 1, reproduz os dados de áudio que foram capturados e armazenados no objeto ByteArrayOutputStream.

private void playAudio() { try{ byte audioData[] = byteArrayOutputStream. toByteArray(); InputStream byteArrayInputStream = new ByteArrayInputStream( audioData); 

Listagem 1

Começamos com o código padrão.

O snippet de programa na Listagem 1 ainda não está relacionado ao Java Sound.

Seu objetivo é:

  • Converta dados salvos anteriormente em uma matriz do tipo byte.
  • Obtenha o fluxo de entrada para uma matriz de dados de bytes.

Precisamos disso para disponibilizar dados de áudio para reprodução posterior.

Vá para a API de som

A linha de código na Listagem 2 já está relacionada à API Java Sound.

  AudioFormat audioFormat = getAudioFormat(); 

Listagem 2

Aqui, abordaremos brevemente o tópico, que será discutido em detalhes na próxima lição.

Dois formatos independentes

Na maioria das vezes, estamos lidando com dois formatos independentes para dados de áudio.

Formato de arquivo (qualquer) que contém dados de áudio (em nosso programa, ainda não o é, pois os dados são armazenados na memória)

O formato dos dados de áudio enviados é por si só.

O que é um formato de áudio?

Aqui está o que a Sun escreve sobre isso:

“Cada canal de dados tem seu próprio formato de áudio associado ao seu fluxo de dados. O formato (uma instância do AudioFormat) determina a ordem dos bytes do fluxo de áudio. Os parâmetros de formato podem ser o número de canais, a frequência de amostragem, o bit de quantização, o método de codificação, etc. Os métodos usuais de codificação podem ser a modulação linear por código de pulso do PCM e suas variantes. ”

Sequência de bytes

Os dados de áudio de origem são uma sequência de bytes de dados binários. Existem várias opções de como você pode organizar e interpretar essa sequência. Não começaremos a lidar com todas essas opções em detalhes, mas discutiremos um pouco o formato de áudio que usamos aqui em nosso programa.

Pequena digressão

Aqui deixamos o método playAudio por enquanto e observamos o método getAudioFormat da Listagem 2.

O método getAudioFormat completo é mostrado na Listagem 3.

  private AudioFormat getAudioFormat(){ float sampleRate = 8000.0F; int sampleSizeInBits = 16; int channels = 1; boolean signed = true; boolean bigEndian = false; return new AudioFormat( sampleRate, sampleSizeInBits, channels, signed, bigEndian); }//end getAudioFormat 

Listagem 3

Além de declarar variáveis ​​inicializadas, o código na Listagem 3 contém uma expressão executável.

Objeto AudioFormat

O método getAudioFormat cria e retorna uma instância de um objeto da classe AudioFormat. Aqui está o que a Sun escreve sobre esta classe:

“A classe AudioFormat define a ordem específica dos dados em um fluxo de áudio. Voltando aos campos do objeto AudioFormat, você pode obter informações sobre como interpretar corretamente os bits em um fluxo de dados binários. ”

Usamos o construtor mais simples

A classe AudioFormat possui dois tipos de construtores (usaremos o mais trivial). Os seguintes parâmetros são necessários para este construtor:

  • Taxa de amostragem ou taxa de amostragem por segundo (valores disponíveis: 8000, 11025, 16000, 22050 e 44100 amostras por segundo)
  • Profundidade de bits dos dados (8 e 16 bits por contagem estão disponíveis)
  • Número de canais (um canal para mono e dois para estéreo)
  • Dados assinados ou não assinados usados ​​no fluxo (por exemplo, o valor varia de 0 a 255 ou de -127 a +127)
  • A ordem dos bytes de big endian ou little endian. (se você estiver transmitindo valores de 16 bits por fluxo de bytes, é importante saber qual byte vem primeiro - baixo ou alto, pois existem duas opções).

Como você pode ver na Listagem 3, no nosso caso, usamos os seguintes parâmetros para uma instância do objeto AudioFormat.

  • 8000 amostras por segundo
  • 16 tamanho dos dados
  • dados significativos
  • Ordem little-endian

Por padrão, os dados são codificados pelo PCM linear.

O construtor que usamos cria uma instância do objeto AudioFormat usando modulação linear por código de pulso e os parâmetros indicados acima (retornaremos ao PCM linear e outros métodos de codificação nas lições a seguir)

Voltar ao método playAudio novamente

Agora que entendemos como o formato de dados de áudio funciona no som Java, voltemos ao método playAudio. Assim que queremos reproduzir os dados de áudio disponíveis, precisamos de um objeto da classe AudioInputStream. Temos uma instância disso na Listagem 4.

  audioInputStream = new AudioInputStream( byteArrayInputStream, audioFormat, audioData.length/audioFormat. getFrameSize()); 

Listagem 4

Parâmetros para o construtor AudioInputStream

  • O construtor para a classe AudioInputStream requer os três parâmetros a seguir:
  • O fluxo no qual a instância do objeto AudioInputStream será baseada (como vemos para esse fim, usamos a instância do objeto ByteArrayInputStream criada anteriormente)
  • O formato de dados de áudio para este fluxo (para esse fim, já criamos uma instância do objeto AudioFormat)
  • O tamanho do quadro (quadro) para os dados neste fluxo (veja a descrição abaixo)
  • Os dois primeiros parâmetros são claros no código da Listagem 4. No entanto, o terceiro parâmetro não é tão óbvio por si só.

Obter tamanho do quadro

Como podemos ver na Listagem 4, o valor do terceiro parâmetro é criado usando cálculos. Este é apenas um dos atributos do formato de áudio que não mencionamos anteriormente e é chamado de quadro.

O que é um quadro?

Para um PCM linear simples usado em nosso programa, o quadro contém um conjunto de amostras para todos os canais em um determinado momento.

Assim, o tamanho do quadro é igual ao tamanho da contagem em bytes vezes o número de canais.

Como você deve ter adivinhado, um método chamado getFrameSize retorna o tamanho do quadro em bytes.

Cálculo do tamanho do quadro

Assim, o comprimento dos dados de áudio em um quadro pode ser calculado dividindo o número total de bytes na sequência de dados de áudio pelo número de bytes em um quadro. Este cálculo é usado para o terceiro parâmetro na Listagem 4.

Obtendo um objeto SourceDataLine

A próxima parte do programa que discutiremos é um sistema simples de saída de áudio. Como podemos ver no diagrama da Figura 5, para resolver esse problema, precisamos de um objeto SourceDataLine.

Existem várias maneiras de obter uma instância do objeto SourceDataLine, todas muito complicadas. O código na Listagem 5 recupera e armazena uma referência a uma instância do objeto SourceDataLine.

(Observe que esse código não apenas instancia o objeto SourceDataLine. Ele o obtém de maneira indireta.)

  DataLine.Info dataLineInfo = new DataLine.Info( SourceDataLine.class, audioFormat); sourceDataLine = (SourceDataLine) AudioSystem.getLine( dataLineInfo); 

Listagem 5

O que é um objeto SourceDataLine?

Sobre isso, a Sun escreve o seguinte:

“SourceDataLine é um canal de dados no qual os dados podem ser gravados. Funciona como uma entrada para um mixer. Um aplicativo grava uma sequência de bytes em um SourceDataLine, que armazena em buffer os dados e os entrega ao seu misturador. O mixer pode transmitir os dados que processa para o próximo estágio, por exemplo, para a porta de saída.

Observe que a convenção de nomenclatura para esse emparelhamento reflete o relacionamento entre o canal e seu mixer. ”

Método GetLine para a classe AudioSystem

Uma maneira de obter uma instância do objeto SourceDataLine é chamar o método estático getLine da classe AudioSystem (teremos muito a relatar sobre isso nas próximas lições).

O método getLine requer um parâmetro de entrada do tipo Line.Info e retorna um objeto Line que corresponda à descrição no objeto Line.Info já definido.

Outra digressão curta

A Sun relata as seguintes informações sobre o objeto Line.Info:

“O canal possui seu próprio objeto de informação (uma instância do Line.Info), que mostra qual mixer (se houver) envia os dados de áudio misturados como saída diretamente para o canal e qual mixer (se houver) recebe os dados de áudio como entrada diretamente do canal. Variedades de Line podem corresponder às subclasses de Line.Info, que permitem especificar outros tipos de parâmetros relacionados a tipos específicos de canais ”

Objeto DataLine.Info

A primeira expressão na Listagem 5 cria uma nova instância do objeto DataLine.Info, que é um formulário especial (subclasse) do objeto Line.Info.

Existem vários construtores sobrecarregados para a classe DataLine.Info. Nós escolhemos o mais fácil de usar. Este construtor requer dois parâmetros.

Objeto de classe

O primeiro parâmetro é Class, que representa a classe que definimos como SourceDataLine.class

O segundo parâmetro determina o formato de dados desejado para o canal. Utilizamos uma instância do objeto AudioFormat, que já foi definida anteriormente.

Onde já estamos?

Infelizmente, ainda não temos o objeto SourceDataLine mais necessário. Até o momento, temos um objeto que fornece apenas informações sobre o objeto SourceDataLine de que precisamos.

Obtendo um objeto SourceDataLine

A segunda expressão na Listagem 5 finalmente cria e armazena a instância de SourceDataLine de que precisamos. Isso acontece chamando o método estático getLine da classe AudioSystem e passando dataLineInfo como parâmetro. (Na próxima lição, veremos como obter o objeto Line, trabalhando diretamente com o objeto Mixer).

O método getLine retorna uma referência a um objeto do tipo Line, que é o pai do SourceDataLine. Portanto, é necessário um downcast aqui antes que o valor de retorno seja salvo como SourceDataLine.

Vamos nos preparar para usar o objeto SourceDataLine

Depois de obter uma instância do objeto SourceDataLine, precisamos prepará-la para abertura e execução, conforme mostrado na Listagem 6.

  sourceDataLine.open(audioFormat); sourceDataLine.start(); 

Listagem 6

Método de abertura

Como você pode ver na Listagem 6, enviamos o objeto AudioFormat ao método de abertura para o objeto SourceDataLine.

Segundo a Sun, este é um método:

"Abre uma linha (canal) com um formato definido anteriormente, permitindo que ele receba todos os recursos do sistema de que precisa e esteja em condições de trabalho"

Estado de descoberta

Há pouco mais que Sun escreve sobre ele neste tópico.

“Abrir e fechar o canal afeta a distribuição dos recursos do sistema. A abertura bem-sucedida do canal garante que todos os recursos necessários sejam fornecidos ao canal.

A abertura do mixer, que possui portas de entrada e saída para dados de áudio, inclui, entre outras coisas, o hardware da plataforma na qual ocorre o trabalho e a inicialização dos componentes de software necessários.

A abertura de um canal, que é uma rota para dados de áudio de ou para um mixer, inclui tanto a inicialização quanto o recebimento de recursos ilimitados do mixer. Em outras palavras, o mixer possui um número finito de canais; portanto, vários aplicativos com suas próprias necessidades de canal (e até mesmo um aplicativo) devem compartilhar corretamente os recursos do mixer) ”

Chame o método start em um canal

De acordo com a Sun, chamar o método start para um canal significa o seguinte:

“O canal pode usar linhas de E / S. Se for feita uma tentativa de usar uma linha já em operação, o método não fará nada. Mas, depois que o buffer de dados estiver vazio, a linha retoma a E / S inicial, iniciando no primeiro quadro que não conseguiu processar após o carregamento completo do buffer. ”

No nosso caso, é claro, o canal não parou. Desde que o lançamos pela primeira vez.

Agora temos quase tudo o que precisamos

Nesse ponto, recebemos todos os recursos de áudio necessários para reproduzir os dados de áudio que gravamos e armazenados anteriormente na instância do objeto ByteArrayOutputStream. (Lembre-se de que esse objeto existe apenas na RAM do computador).

Começamos fluxos

Criaremos e iniciaremos o fluxo para reproduzir o áudio. O código na Listagem 7 cria e inicia esse encadeamento.

(Não confunda a chamada para o método start neste encadeamento com a chamada para o método start no objeto SourceDataLine da Listagem 6. Essas são operações completamente diferentes)

 Thread playThread = new Thread(new PlayThread()); playThread.start(); } catch (Exception e) { System.out.println(e); System.exit(0); }//end catch }//end playAudio 

Listagem 7

Código despretensioso

O snippet do programa na Listagem 7, embora muito simples, mostra um exemplo de programação multithread em Java. Se você não o entende, deve se familiarizar com este tópico em tópicos especializados para aprender Java.

Uma vez iniciado o fluxo, ele funcionará até que todos os dados de áudio pré-gravados sejam reproduzidos até o fim.

Novo objeto de thread

O código na Listagem 7 cria uma instância do objeto Thread da classe PlayThread. Essa classe é definida como uma classe interna em nosso programa. Sua descrição começa na Listagem 8.

 class PlayThread extends Thread{ byte tempBuffer[] = new byte[10000]; 

Listagem 8

O método run na classe Thread

Exceto para declarar uma variável tempBuffer (que se refere a uma matriz de bytes), uma definição completa dessa classe é apenas uma definição do método run. Como você já deve saber, chamar o método start em um objeto Thread faz com que o método run deste objeto seja executado

O método de execução para esse encadeamento começa na Listagem 9.

 public void run(){ try{ int cnt; //  //    -1 // while((cnt = audioInputStream. read(tempBuffer, 0, tempBuffer.length)) != -1){ if(cnt > 0){ //   //    //    //   . sourceDataLine.write( tempBuffer, 0, cnt); }//end if }//end while 

Listagem 9

A primeira parte do fragmento de programa no método run

O método run contém duas partes importantes, a primeira das quais é mostrada na Listagem 9.

Em suma, um loop é usado aqui para ler dados de áudio de um AudioInputStream e passá-los para um SourceDataLine.

Os dados enviados para o objeto SourceDataLine são automaticamente transferidos para a saída de áudio padrão. Pode ser um alto-falante do computador embutido ou uma saída de linha. (Aprenderemos a determinar os dispositivos de som necessários nas lições a seguir). A variável cnt e o buffer de dados tempBuffer são usados ​​para controlar o fluxo de dados entre as operações de leitura e gravação.

Lendo dados do AudioInputStream

O ciclo de leitura do objeto AudioInputStream lê o número máximo especificado de bytes de dados do AudioInputStream e coloca sua matriz de bytes.

Valor de retorno

Além disso, esse método retorna o número total de bytes lidos, ou -1, se o final da sequência gravada foi atingido. O número de bytes lidos é armazenado na variável cnt.

Loop de gravação SourceDataLine

Se o número de bytes lidos for maior que zero, haverá uma transição para o ciclo de gravação de dados no SourceDataLine. Nesse loop, os dados de áudio entram no mixer. Os bytes são lidos da matriz de bytes de acordo com seus índices e gravados no buffer do canal.

Quando o fluxo de entrada seca

Quando o loop de leitura retorna -1, isso significa que todos os dados de áudio gravados anteriormente terminaram e um controle adicional é passado para o fragmento do programa na Listagem 10.

  sourceDataLine.drain(); sourceDataLine.close(); }catch (Exception e) { System.out.println(e); System.exit(0); }//end catch }//end run }//   PlayThread 

Listagem 10

Bloquear e esperar

O código na Listagem 10 chama o método de drenagem no objeto SourceDataLine para que o programa possa bloquear e aguardar o buffer interno esvaziar no SourceDataLine. Quando o buffer está vazio, significa que a próxima parte inteira é entregue à saída de som do computador.

Fechando SourceDataLine

Em seguida, o programa chama o método close para fechar o canal, mostrando assim que todos os recursos do sistema usados ​​pelo canal agora estão livres. A Sun relata o seguinte fechamento de canal:

“Fechar o canal sinaliza que todos os recursos envolvidos nesse canal podem ser liberados. Para liberar recursos, o aplicativo deve fechar os canais, se eles já estão envolvidos ou não, e quando o aplicativo termina. Supõe-se que os misturadores compartilhem recursos do sistema e possam ser fechados e abertos repetidamente. Outros canais podem ou não suportar a reabertura após o fechamento. Em geral, os mecanismos para abertura de linhas variam de acordo com diferentes subtipos. ”

E agora o fim da história

Então, aqui, explicamos como nosso programa usa a API Java Sound para garantir a entrega de dados de áudio da memória interna do computador para a placa de som.

Execute o programa

Agora você pode compilar e executar o programa da Listagem 11, que termina no final de nossa lição.

Capturar e reproduzir dados de áudio

O programa demonstra a capacidade de gravar dados de um microfone e reproduzi-los através da placa de som do seu computador. Instruções para usá-lo são muito simples.

Execute o programa. A GUI simples, mostrada na Figura 6, deve aparecer na tela.



  • Clique no botão Capturar e grave qualquer som no microfone.
  • Clique no botão Parar para parar a gravação.
  • Clique no botão Reproduzir para reproduzir a gravação através da saída de som do seu computador.

Se você não ouvir nada, tente aumentar a sensibilidade do microfone ou o volume do alto-falante.

O programa salva um registro na memória do computador, portanto, tenha cuidado. Se você tentar salvar muitos dados de áudio, poderá ficar sem memória RAM.

Conclusão

  • Descobrimos que a API Java Sound é baseada no conceito de canais e mixers.
  • Recebemos informações iniciais sobre as características físicas e elétricas do som analógico, para que pudéssemos entender o dispositivo do mixer de áudio.
  • Utilizamos um cenário de show de rock amador usando seis microfones e dois alto-falantes estéreo para descrever a possibilidade de usar um mixer de áudio.
  • Discutimos vários tópicos de programação do Java Sound, incluindo mixers, canais, formato de dados e muito mais.
  • Explicamos o relacionamento geral entre os objetos SourceDataLine, Clip, Mixer, AudioFormat e portas em um programa simples para a saída de dados de áudio.
  • Nós nos familiarizamos com um programa que nos permite gravar inicialmente e depois reproduzir dados de áudio.
  • Recebemos uma explicação detalhada do código usado para reproduzir dados de áudio gravados anteriormente na memória do computador.

O que vem a seguir?

Neste tutorial, descobrimos que a API Java Sound é baseada no conceito de mixers e canais. No entanto, o código que discutimos não incluía misturadores explicitamente. A classe AudioSystem nos forneceu métodos estáticos que possibilitam gravar programas de processamento de áudio sem acessar diretamente os mixers. Em outras palavras, esses métodos estáticos nos afastam os misturadores.

Na próxima lição, apresentamos um código de captura de dados modificado em comparação com o apresentado nesta lição. A nova versão usará explicitamente os misturadores para mostrar como usá-los quando você realmente precisar deles.

 import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import javax.sound.sampled.*; public class AudioCapture01 extends JFrame{ boolean stopCapture = false; ByteArrayOutputStream byteArrayOutputStream; AudioFormat audioFormat; TargetDataLine targetDataLine; AudioInputStream audioInputStream; SourceDataLine sourceDataLine; public static void main( String args[]){ new AudioCapture01(); }//end main public AudioCapture01(){ final JButton captureBtn = new JButton("Capture"); final JButton stopBtn = new JButton("Stop"); final JButton playBtn = new JButton("Playback"); captureBtn.setEnabled(true); stopBtn.setEnabled(false); playBtn.setEnabled(false); captureBtn.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ captureBtn.setEnabled(false); stopBtn.setEnabled(true); playBtn.setEnabled(false); //  //   //   Stop captureAudio(); } } ); getContentPane().add(captureBtn); stopBtn.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ captureBtn.setEnabled(true); stopBtn.setEnabled(false); playBtn.setEnabled(true); //  //    stopCapture = true; } } ); getContentPane().add(stopBtn); playBtn.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ //  //    playAudio(); } } ); getContentPane().add(playBtn); getContentPane().setLayout( new FlowLayout()); setTitle("Capture/Playback Demo"); setDefaultCloseOperation( EXIT_ON_CLOSE); setSize(250,70); setVisible(true); } //    //     //   ByteArrayOutputStream private void captureAudio(){ try{ //    audioFormat = getAudioFormat(); DataLine.Info dataLineInfo = new DataLine.Info( TargetDataLine.class, audioFormat); targetDataLine = (TargetDataLine) AudioSystem.getLine( dataLineInfo); targetDataLine.open(audioFormat); targetDataLine.start(); //     //    //   //    Thread captureThread = new Thread( new CaptureThread()); captureThread.start(); } catch (Exception e) { System.out.println(e); System.exit(0); } } //    // ,    //  ByteArrayOutputStream private void playAudio() { try{ //  //  byte audioData[] = byteArrayOutputStream. toByteArray(); InputStream byteArrayInputStream = new ByteArrayInputStream( audioData); AudioFormat audioFormat = getAudioFormat(); audioInputStream = new AudioInputStream( byteArrayInputStream, audioFormat, audioData.length/audioFormat. getFrameSize()); DataLine.Info dataLineInfo = new DataLine.Info( SourceDataLine.class, audioFormat); sourceDataLine = (SourceDataLine) AudioSystem.getLine( dataLineInfo); sourceDataLine.open(audioFormat); sourceDataLine.start(); //    //     //     //      Thread playThread = new Thread(new PlayThread()); playThread.start(); } catch (Exception e) { System.out.println(e); System.exit(0); } } //     //  AudioFormat private AudioFormat getAudioFormat(){ float sampleRate = 8000.0F; //8000,11025,16000,22050,44100 int sampleSizeInBits = 16; //8,16 int channels = 1; //1,2 boolean signed = true; //true,false boolean bigEndian = false; //true,false return new AudioFormat( sampleRate, sampleSizeInBits, channels, signed, bigEndian); } //===================================// //    //    class CaptureThread extends Thread{ byte tempBuffer[] = new byte[10000]; public void run(){ byteArrayOutputStream = new ByteArrayOutputStream(); stopCapture = false; try{ while(!stopCapture){ int cnt = targetDataLine.read( tempBuffer, 0, tempBuffer.length); if(cnt > 0){ //     byteArrayOutputStream.write( tempBuffer, 0, cnt); } } byteArrayOutputStream.close(); }catch (Exception e) { System.out.println(e); System.exit(0); } } } //===================================// //   //     class PlayThread extends Thread{ byte tempBuffer[] = new byte[10000]; public void run(){ try{ int cnt; //     -1 while((cnt = audioInputStream. read(tempBuffer, 0, tempBuffer.length)) != -1){ if(cnt > 0){ //    //   //    //    sourceDataLine.write( tempBuffer, 0, cnt); } } sourceDataLine.drain(); sourceDataLine.close(); }catch (Exception e) { System.out.println(e); System.exit(0); } } } //===================================// }//end outer class AudioCapture01.java 

Listagem 11

Source: https://habr.com/ru/post/pt434424/


All Articles