Principes de base de l'API JAVA SOUND

Bonjour, Habr! Je vous présente la traduction de l'article «Java Sound, Getting Started, Part 1, Playback» .

Sound in JAVA, Part One, The Beginning. Jouer du son



C'est le début d'une série de huit leçons qui vous familiariseront pleinement avec l'API Java Sound.

Qu'est-ce que le son dans la perception humaine? C'est le sentiment que nous ressentons lorsqu'un changement de pression atmosphérique est transmis aux minuscules zones sensorielles à l'intérieur de nos oreilles.

Et l'objectif principal de la création de l'API Sound est de vous fournir des moyens d'écrire du code, ce qui aidera à transférer les ondes de pression aux oreilles du bon sujet au bon moment.

Types de sons en Java:

  1. L'API Java Sound prend en charge deux principaux types d'audio (son).
  2. Son numérisé et enregistré directement sous forme de fichier
  3. Enregistrez en tant que fichier MIDI. Très éloigné, mais similaire à la notation musicale, où les instruments de musique sont joués dans la séquence souhaitée.

Ces types sont très différents dans leur essence et nous nous concentrerons sur le premier, car dans la plupart des cas, nous avons affaire à du son qui doit être numérisé pour enregistrer à partir d'une source externe dans un fichier ou vice versa pour reproduire le son précédemment enregistré à partir d'un tel fichier.

Aperçu


L'API Java Sound est basée sur le concept de lignes et de mélangeurs.

Suivant:
Nous décrirons les caractéristiques physiques et électriques de la représentation analogique du son appliquée à un mélangeur audio .

Nous allons nous tourner vers le scénario du groupe de rock débutant, qui utilise dans ce cas six microphones et deux haut-parleurs stéréo. Nous en avons besoin pour comprendre le fonctionnement du mélangeur audio.

Ensuite, nous examinons un certain nombre de thèmes Java Sound pour la programmation, tels que les lignes, les mélangeurs, les formats de données audio, etc.

Nous allons comprendre les relations existant entre les objets SourceDataLine, Clip, Mixer, AudioFormat et créer un programme simple qui reproduit l'audio.

Ci-dessous, nous donnons un exemple de ce programme, que vous pouvez utiliser pour enregistrer puis lire le son enregistré.

À l'avenir, nous fournirons une explication complète du code de programme utilisé à cet effet. Mais en aucun cas complètement dans cette leçon.

Exemple de code et considération


Caractéristiques physiques et électriques du son analogique

Le but de notre leçon est de vous présenter les bases de la programmation Java à l'aide de l'API Java Sound.

L'API Java Sound est basée sur le concept d'une table de mixage audio, qui est un appareil couramment utilisé pour jouer du son presque partout: des concerts de rock à l'écoute de CD à la maison. Mais avant de vous lancer dans une explication détaillée du fonctionnement du mélangeur audio, il sera utile de vous familiariser avec les caractéristiques physiques et électriques du son analogique lui-même.

Regardez la Fig. 1



Vasya Pupyrkin pousse un discours.

Cette figure montre Vasya prononçant un discours en utilisant un système connu sous le nom de large adresse. Un tel système comprend généralement un microphone, un amplificateur et un haut-parleur. Le but de ce système est de renforcer la voix de Vasya afin qu'il puisse être entendu même dans une grande foule.

Vaciller dans l'air

En bref, lorsque Vasya parle, ses cordes vocales font vibrer les particules d'air dans son larynx. Cela conduit à l'émergence d'ondes sonores qui, à leur tour, font vibrer la membrane du microphone puis se transforment en vibrations électriques de très petite amplitude qui simulent exactement les vibrations sonores de l'original de Vasya. Un amplificateur, comme son nom l'indique, amplifie ces vibrations électriques. Ensuite, ils atteignent le haut-parleur, qui effectue la transformation inverse des vibrations électriques amplifiées en ondes sonores très amplifiées, mais qui répètent néanmoins exactement les mêmes ondes générées dans les cordes vocales de Vasya Pupyrkin.

Microphone dynamique

Regardons maintenant la Fig. 2, qui montre un diagramme schématique d'un microphone appelé dynamique.


Fig. 2 Circuit microphone dynamique

Les vibrations sonores affectent la membrane

La pression des vibrations sonores agit sur une membrane flexible à l'intérieur du microphone. Cela fait vibrer la membrane, tandis que les vibrations de la membrane répètent les vibrations des ondes sonores.

Bobine mobile

Une bobine de fil mince est fixée à la membrane du microphone. Lorsque la membrane oscille, la bobine effectue également des mouvements alternatifs dans le champ magnétique du noyau constitué d'un aimant permanent puissant. Et comme Faraday l'a également établi, un courant électrique apparaît dans la bobine.

Un signal électrique suit la forme des ondes sonores.

Ainsi, à partir d'un très faible courant induit dans la bobine, un signal électrique alternatif est obtenu, répétant la forme d'ondes sonores qui agissent sur la membrane du microphone. En outre, ce signal sous la forme d'une tension alternative est appliqué à l'entrée de l'amplificateur de la Fig. 1.

Haut-parleur

En fait, le principe de fonctionnement de l'enceinte répète l'appareil d'un microphone dynamique, uniquement allumé en sens inverse. (Naturellement, dans ce cas, les fils de bobinage sont beaucoup plus épais et la membrane est beaucoup plus grande pour assurer un fonctionnement avec un signal amplifié)




Les oscillations de la membrane du haut-parleur affectent les particules d'air et créent de puissantes ondes sonores. La forme de ces ondes répète exactement la forme des ondes sonores d'intensité beaucoup plus faible créées par les cordes vocales de Vasya. Mais l'intensité des nouvelles vagues est maintenant suffisante pour garantir que les vibrations sonores de Vasya atteignent les oreilles des personnes debout, même dans les rangées arrière d'une grande foule.

Concert de rock

À ce stade, vous vous demandez peut-être ce que tout cela a à voir avec l'API Java Sound. Mais attendez un peu plus longtemps, nous ouvrons la voie aux bases du mélangeur audio.

Le circuit décrit ci-dessus était assez simple. Il était composé de Vasya Pupyrkin, d'un microphone, d'un amplificateur et d'un haut-parleur. Considérons maintenant le circuit avec la Fig. 4, qui présente la scène préparée pour le concert de rock du groupe de musique débutant.



Six microphones et deux haut-parleurs

Dans la Fig. 4 six microphones sont situés sur la scène. Deux haut-parleurs (haut-parleurs) sont situés sur les côtés de la scène. Au début du concert, les interprètes chantent ou jouent de la musique dans chacun des six microphones. En conséquence, nous aurons six signaux électriques, qui doivent être amplifiés individuellement puis envoyés aux deux haut-parleurs. En plus de cela, les artistes peuvent utiliser divers effets spéciaux sonores, par exemple la réverbération, qui devront également être convertis en signaux électriques avant de les appliquer aux haut-parleurs.

Deux haut-parleurs sur les côtés de la scène sont conçus pour créer l'effet d'un son stéréo. Autrement dit, le signal électrique provenant du microphone situé sur la scène à droite doit tomber dans le haut-parleur situé également à droite. De même, le signal provenant du microphone à gauche doit être envoyé au haut-parleur situé à gauche de la scène. Mais les signaux électriques provenant d'autres microphones situés plus près du centre de la scène doivent déjà être transmis aux deux haut-parleurs dans des proportions appropriées. Et deux microphones en plein centre devraient transmettre leur signal aux deux haut-parleurs de manière égale.

Mixeur audio

La tâche discutée ci-dessus n'est exécutée que par un appareil électronique appelé mélangeur audio.

Ligne audio (canal)

Bien que l'auteur ne soit pas un expert des mélangeurs audio, à sa connaissance humble, un mélangeur audio typique a la capacité de recevoir à l'entrée un certain nombre de signaux électriques indépendants les uns des autres, chacun représentant le signal ou la ligne sonore d'origine (canal).

(Le concept de canal audio deviendra très important lorsque nous commencerons à comprendre l'API Java Sound en détail.

Traitement indépendant de chaque canal audio

Dans tous les cas, le mélangeur audio standard a la capacité d'amplifier chaque ligne audio indépendamment des autres autres canaux. De plus, la table de mixage a généralement la possibilité d'imposer des effets spéciaux sonores, tels que, par exemple, la réverbération à l'une des lignes audio. Au final, la table de mixage, comme son nom l'indique, peut mélanger tous les signaux électriques individuels dans les canaux de sortie tels qu'ils seront définis, afin de contrôler la contribution de chaque ligne audio aux canaux de sortie (ce contrôle est généralement appelé pan ou pan - distribution dans l'espace).

Retour au son stéréo

Ainsi, dans le schéma de la Fig. 4, l'ingénieur du son de la table de mixage audio a la capacité de combiner les signaux de six microphones pour obtenir deux signaux de sortie, chacun étant transmis à son haut-parleur.

Pour un fonctionnement réussi, le signal de chaque microphone doit être fourni dans la proportion appropriée, en fonction de l'emplacement physique du microphone sur la scène. (En modifiant le panoramique, un ingénieur du son qualifié peut modifier la contribution de chaque microphone si nécessaire, si, par exemple, le chanteur principal se déplace sur la scène pendant un concert).

Il est temps de retourner dans le monde de la programmation

Revenons maintenant du monde physique au monde de la programmation. Selon Sun: «Java Sound n'implique aucune configuration matérielle spéciale; Il est conçu pour permettre à divers composants audio d'être installés sur le système et mis à la disposition de l'utilisateur via l'API. Java Sound prend en charge les fonctionnalités d'entrée et de sortie standard d'une carte son (par exemple, pour l'enregistrement et la lecture de fichiers audio), ainsi que la possibilité de mélanger plusieurs flux audio »

Mixeurs et canaux

Comme déjà mentionné, l'API Java Sound est construite sur le concept de mélangeurs et de canaux. Si vous passez du monde physique au monde de la programmation, Sun écrit ce qui suit concernant le mélangeur:

«Un mélangeur est un appareil audio avec un ou plusieurs canaux. Mais le mélangeur qui mixe vraiment le signal audio doit avoir plusieurs canaux d'entrée de sources sources et au moins un canal cible de sortie. "

Les lignes d'entrée peuvent être des instances de classes avec des objets SourceDataLine et les lignes de sortie peuvent être des objets TargetDataLine. Le mélangeur peut également recevoir un son préenregistré et en boucle en entrée, définissant ses canaux de source d'entrée comme des instances d'objets de classe qui implémentent l'interface Clip.

Interface de ligne de canal.

Sun rapporte les informations suivantes à partir de l'interface de ligne: «La ligne est un élément d'un pipeline audio numérique tel qu'un port audio d'entrée ou de sortie, un mélangeur ou un chemin audio vers ou depuis un mélangeur. Les données audio passant par le canal peuvent être mono ou multicanaux (par exemple, stéréo). ... Un canal peut avoir des contrôles, tels que le gain, le panoramique et la réverbération. »

Rassembler les termes

Ainsi, les citations ci-dessus de Sun dénotaient les termes suivants

Sourcedataline
Targetgetataline
Port
Clip
Contrôles

Fig. 5 montre un exemple d'utilisation de ces termes pour construire un programme de sortie audio simple.



Script de programme

D'un point de vue logiciel 5 montre un objet Mixer obtenu avec un objet Clip et deux objets SourceDataLine.

Qu'est-ce que Clip

Clip est un objet à l'entrée du mélangeur dont le contenu ne change pas avec le temps. En d'autres termes, vous chargez les données audio dans l'objet Clip avant de les lire. Le contenu audio de l'objet Clip peut être lu une ou plusieurs fois. Vous pouvez boucler le clip, puis le contenu sera lu encore et encore.

Flux d'entrée

L'objet SourceDataLine, d'autre part, est un objet de flux à l'entrée du mélangeur. Un objet de ce type peut recevoir un flux de données audio et l'envoyer au mélangeur en temps réel. Les données audio nécessaires peuvent être obtenues à partir de diverses sources, telles que des fichiers audio, une connexion réseau ou une mémoire tampon.

Différents types de canaux

Ainsi, les objets Clip et SourceDataLine peuvent être considérés comme des canaux d'entrée pour l'objet Mixer. Chacun de ces canaux d'entrée peut avoir le sien: panoramique, gain et réverbération.

Lire du contenu audio

Dans un système aussi simple, Mixer lit les données des lignes d'entrée, utilise le contrôle pour mélanger les signaux d'entrée et fournit une sortie à un ou plusieurs canaux de sortie, tels qu'un haut-parleur, une sortie de ligne, une prise casque, etc.

Le listing 11 montre un programme simple qui capture des données audio à partir d'un port de microphone, stocke ces données en mémoire, puis les lit via le port de haut-parleur.

Nous discuterons uniquement de la capture et de la lecture. La plupart du programme ci-dessus consiste à créer une fenêtre et une interface graphique pour l'utilisateur afin qu'il soit possible de contrôler l'enregistrement et la lecture. Nous ne discuterons pas de cette partie comme allant au-delà de l'objectif. Mais ensuite, nous considérerons la capture et la lecture des données. Nous discuterons de la perte dans cette leçon et de la capture dans la prochaine. En cours de route, nous illustrerons l'utilisation du canal audio avec l'API Java Sound.

Les données capturées sont stockées dans un objet ByteArrayOutputStream.

Un extrait de code permet de lire des données audio à partir d'un microphone et de les stocker en tant qu'objet ByteArrayOutputStream.

La méthode, appelée playAudio, qui commence dans le Listing 1, lit les données audio qui ont été capturées et stockées dans l'objet ByteArrayOutputStream.

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

Listing 1

Nous commençons avec le code standard.

L'extrait de programme du Listing 1 n'est en fait pas encore lié à Java Sound.

Son objectif est de:

  • Convertissez les données précédemment enregistrées en un tableau d'octets de type.
  • Obtenez le flux d'entrée pour un tableau de données d'octets.

Nous en avons besoin pour rendre les données audio disponibles pour une lecture ultérieure.

Accédez à l'API Sound

La ligne de code du Listing 2 est déjà liée à l'API Java Sound.

  AudioFormat audioFormat = getAudioFormat(); 

Listing 2

Ici, nous abordons brièvement le sujet, qui sera discuté en détail dans la prochaine leçon.

Deux formats indépendants

Le plus souvent, nous avons affaire à deux formats indépendants pour les données audio.

Format de fichier, (tout) qui contient des données audio (dans notre programme, il ne l'est pas encore, car les données sont stockées en mémoire)

Le format des données audio soumises est en lui-même.

Qu'est-ce qu'un format audio?

Voici ce que Sun écrit à ce sujet:

«Chaque canal de données a son propre format audio associé à son flux de données. Le format (une instance d'AudioFormat) détermine l'ordre des octets du flux audio. Les paramètres de format peuvent être le nombre de canaux, la fréquence d'échantillonnage, le bit de quantification, la méthode de codage, etc. Les méthodes de codage habituelles peuvent être la modulation linéaire par impulsions codées du PCM et de ses variantes. »

Séquence d'octets

Les données audio source sont une séquence d'octets de données binaires. Il existe différentes options pour organiser et interpréter cette séquence. Nous ne commencerons pas à traiter toutes ces options en détail, mais nous discuterons un peu du format audio que nous utilisons ici dans notre programme.

Petite digression

Ici, nous laissons la méthode playAudio pour l'instant et regardons la méthode getAudioFormat du Listing 2.

La méthode getAudioFormat complète est présentée dans le Listing 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 

Listing 3

En plus de déclarer des variables initialisées, le code du Listing 3 contient une expression exécutable.

Objet AudioFormat

La méthode getAudioFormat crée et renvoie une instance d'un objet de la classe AudioFormat. Voici ce que Sun écrit sur cette classe:

«La classe AudioFormat définit l'ordre spécifique des données dans un flux audio. En ce qui concerne les champs de l'objet AudioFormat, vous pouvez obtenir des informations sur la façon d'interpréter correctement les bits dans un flux de données binaires. »

Nous utilisons le constructeur le plus simple

La classe AudioFormat a deux types de constructeurs (nous prendrons le plus trivial). Les paramètres suivants sont requis pour ce constructeur:

  • Taux d'échantillonnage ou taux d'échantillonnage par seconde (valeurs disponibles: 8000, 11025, 16000, 22050 et 44100 échantillons par seconde)
  • Profondeur de bits des données (8 et 16 bits par comptage sont disponibles)
  • Nombre de canaux (un canal pour mono et deux pour stéréo)
  • Données signées ou non signées utilisées dans le flux (par exemple, la valeur varie de 0 à 255 ou de -127 à +127)
  • L'ordre des octets de Big-endian ou little-endian. (si vous transmettez un flux d'octets de valeurs 16 bits, il est important de savoir quel octet vient en premier - bas ou haut, car il y a les deux options).

Comme vous pouvez le voir dans le Listing 3, dans notre cas, nous avons utilisé les paramètres suivants pour une instance de l'objet AudioFormat.

  • 8000 échantillons par seconde
  • 16 taille des données
  • données importantes
  • Ordre du petit-boutiste

Par défaut, les données sont codées par PCM linéaire.

Le constructeur que nous avons utilisé crée une instance de l'objet AudioFormat en utilisant la modulation linéaire par impulsions codées et les paramètres indiqués ci-dessus (Nous reviendrons sur PCM linéaire et d'autres méthodes d'encodage dans les leçons suivantes)

Retour à la méthode playAudio à nouveau

Maintenant que nous comprenons comment fonctionne le format de données audio en son Java, revenons à la méthode playAudio. Dès que nous voulons lire les données audio disponibles, nous avons besoin d'un objet de classe AudioInputStream. Nous en obtenons une instance dans le Listing 4.

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

Listing 4

Paramètres du constructeur AudioInputStream

  • Le constructeur de la classe AudioInputStream requiert les trois paramètres suivants:
  • Le flux sur lequel l'instance de l'objet AudioInputStream sera basée (comme nous le voyons à cet effet, nous utilisons l'instance de l'objet ByteArrayInputStream créé précédemment)
  • Le format de données audio pour ce flux (à cet effet, nous avons déjà créé une instance de l'objet AudioFormat)
  • La taille de la trame (trame) pour les données de ce flux (voir la description ci-dessous)
  • Les deux premiers paramètres ressortent clairement du code du Listing 4. Cependant, le troisième paramètre n'est pas si évident en soi.

Obtenir la taille du cadre

Comme nous pouvons le voir dans le Listing 4, la valeur du troisième paramètre est créée à l'aide de calculs. Ce n'est qu'un des attributs du format audio que nous n'avons pas mentionné auparavant, et il est appelé une trame.

Qu'est-ce qu'un cadre?

Pour un PCM linéaire simple utilisé dans notre programme, la trame contient un ensemble d'échantillons pour tous les canaux à un moment donné.

Ainsi, la taille de trame est égale à la taille du compte en octets multipliée par le nombre de canaux.

Comme vous l'avez peut-être deviné, une méthode appelée getFrameSize renvoie la taille de la trame en octets.

Calcul de la taille du cadre

Ainsi, la longueur des données audio dans une trame peut être calculée en divisant le nombre total d'octets dans la séquence de données audio par le nombre d'octets dans une trame. Ce calcul est utilisé pour le troisième paramètre du Listing 4.

Obtention d'un objet SourceDataLine

La prochaine partie du programme dont nous allons discuter est un simple système de sortie audio. Comme nous pouvons le voir sur le diagramme de la figure 5, pour résoudre ce problème, nous avons besoin d'un objet SourceDataLine.

Il existe plusieurs façons d'obtenir une instance de l'objet SourceDataLine, toutes très délicates. Le code du Listing 5 récupère et stocke une référence à une instance de l'objet SourceDataLine.

(Notez que ce code n'instancie pas seulement l'objet SourceDataLine. Il l'obtient de manière plutôt détournée.)

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

Listing 5

Qu'est-ce qu'un objet SourceDataLine?

À ce sujet, Sun écrit ce qui suit:

«SourceDataLine est un canal de données dans lequel les données peuvent être écrites. Il fonctionne comme une entrée pour un mélangeur. Une application écrit une séquence d'octets dans une SourceDataLine, qui met en mémoire tampon les données et les remet à son mélangeur. Le mélangeur peut transmettre les données qu'il traite pour l'étape suivante, par exemple, au port de sortie.

Notez que la convention de dénomination de ce couplage reflète la relation entre le canal et son mélangeur. »

Méthode GetLine pour la classe AudioSystem

Une façon d'obtenir une instance de l'objet SourceDataLine consiste à appeler la méthode statique getLine à partir de la classe AudioSystem (nous aurons beaucoup à dire à ce sujet dans les prochaines leçons).

La méthode getLine nécessite un paramètre d'entrée de type Line.Info et retourne un objet Line qui correspond à la description de l'objet Line.Info déjà défini.

Une autre petite digression

Sun rapporte les informations suivantes sur l'objet Line.Info:

«Le canal a son propre objet d'information (une instance de Line.Info), qui indique quel mélangeur (le cas échéant) envoie les données audio mixées en sortie directement au canal, et quel mélangeur (le cas échéant) reçoit les données audio en entrée directement du canal. Les variétés de Line peuvent correspondre à des sous-classes de Line.Info, ce qui vous permet de spécifier d'autres types de paramètres liés à des types de canaux spécifiques »

Objet DataLine.Info

La première expression du Listing 5 crée une nouvelle instance de l'objet DataLine.Info, qui est une forme spéciale (sous-classe) de l'objet Line.Info.

Il existe plusieurs constructeurs surchargés pour la classe DataLine.Info. Nous avons choisi le plus simple à utiliser. Ce constructeur nécessite deux paramètres.

Objet de classe

Le premier paramètre est Class, qui représente la classe que nous avons définie comme SourceDataLine.class

Le deuxième paramètre détermine le format de données souhaité pour le canal. Nous utilisons pour cela une instance de l'objet AudioFormat, qui a déjà été définie précédemment.

Où en sommes-nous déjà?

Malheureusement, nous n'avons toujours pas l'objet SourceDataLine le plus requis. Jusqu'à présent, nous avons un objet qui fournit uniquement des informations sur l'objet SourceDataLine dont nous avons besoin.

Obtention d'un objet SourceDataLine

La deuxième expression du Listing 5 crée et stocke enfin l'instance de SourceDataLine dont nous avons besoin. Cela se produit en appelant la méthode statique getLine de la classe AudioSystem et en transmettant dataLineInfo en tant que paramètre. (Dans la prochaine leçon, nous verrons comment obtenir un objet Line, en travaillant directement avec l'objet Mixer).

La méthode getLine renvoie une référence à un objet de type Line, qui est le parent de SourceDataLine. Par conséquent, une conversion descendante est requise ici avant que la valeur de retour ne soit enregistrée en tant que SourceDataLine.

Préparons-nous à utiliser l'objet SourceDataLine

Une fois que nous obtenons une instance de l'objet SourceDataLine, nous devons le préparer pour l'ouverture et l'exécution, comme indiqué dans le Listing 6.

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

Listing 6

Méthode d'ouverture

Comme vous pouvez le voir dans le Listing 6, nous avons envoyé l'objet AudioFormat à la méthode d'ouverture de l'objet SourceDataLine.

Selon Sun, il s'agit d'une méthode:

«Ouvre une ligne (canal) avec un format préalablement défini, lui permettant de recevoir toutes les ressources système dont il a besoin et d'être en état de fonctionnement (de travail)»

État de découverte

Il n'y a pas grand-chose de plus que Sun écrit sur lui dans ce fil.

«L'ouverture et la fermeture du canal affectent la distribution des ressources système. Une ouverture réussie du canal garantit que toutes les ressources nécessaires sont fournies au canal.

L'ouverture du mélangeur, qui a ses ports d'entrée et de sortie pour les données audio, comprend, entre autres, l'utilisation du matériel de la plate-forme sur laquelle le travail et l'initialisation des composants logiciels nécessaires ont lieu.

L'ouverture d'un canal, qui est une route pour les données audio vers ou depuis une console de mixage, comprend à la fois l'initialisation et la réception de ressources de mixage nullement illimitées. En d'autres termes, le mélangeur a un nombre fini de canaux, donc plusieurs applications avec leurs propres besoins en canaux (et parfois même une application) doivent partager correctement les ressources du mélangeur) »

Appeler la méthode de démarrage sur un canal

Selon Sun, appeler la méthode de démarrage d'un canal signifie ce qui suit:

«Le canal est autorisé à utiliser des lignes d'E / S. Si une tentative est faite pour utiliser une ligne déjà opérationnelle, la méthode ne fait rien. Mais une fois que le tampon de données est vide, la ligne reprend le démarrage des E / S, en commençant par la première trame qu'elle n'a pas réussi à traiter après le chargement complet du tampon. »

Dans notre cas, bien sûr, la chaîne ne s'est pas arrêtée. Depuis que nous l'avons lancé pour la première fois.

Maintenant, nous avons presque tout ce dont nous avons besoin

À ce stade, nous avons reçu toutes les ressources audio dont nous avons besoin pour lire les données audio que nous avons précédemment enregistrées et stockées dans une instance de l'objet ByteArrayOutputStream. (Rappelez-vous que cet objet n'existe que dans la RAM de l'ordinateur).

Nous commençons les flux

Nous allons créer et démarrer le flux pour lire l'audio. Le code du Listing 7 crée et démarre ce thread.

(Ne confondez pas l'appel à la méthode start dans ce thread avec l'appel à la méthode start dans l'objet SourceDataLine du Listing 6. Ce sont des opérations complètement différentes)

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

Listing 7

Code sans prétention

L'extrait du programme du Listing 7, bien que très simple, montre un exemple de programmation multi-thread en Java. Si vous ne le comprenez pas, vous devez vous familiariser avec ce sujet dans des sujets spécialisés pour apprendre Java.

Une fois le flux démarré, il fonctionnera jusqu'à ce que toutes les données audio préenregistrées aient été lues jusqu'à la fin.

Nouvel objet de thread

Le code du Listing 7 crée une instance de l'objet Thread de la classe PlayThread. Cette classe est définie comme une classe interne dans notre programme. Sa description commence dans l'extrait 8.

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

Listing 8

La méthode run dans la classe Thread

À l'exception de la déclaration d'une variable tempBuffer (qui fait référence à un tableau d'octets), une définition complète de cette classe n'est qu'une définition de la méthode d'exécution. Comme vous devez déjà le savoir, l'appel de la méthode start sur un objet Thread provoque l'exécution de la méthode run de cet objet

La méthode d'exécution de ce thread commence dans le Listing 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 

Listing 9

La première partie du fragment de programme dans la méthode run

La méthode run contient deux parties importantes, dont la première est présentée dans le Listing 9.

En résumé, une boucle est utilisée ici pour lire les données audio d'un AudioInputStream et les transmettre à une SourceDataLine.

Les données envoyées à l'objet SourceDataLine sont automatiquement transférées vers la sortie audio par défaut. Il peut s'agir d'un haut-parleur d'ordinateur intégré ou d'une sortie ligne. (Nous apprendrons à déterminer les appareils sonores nécessaires dans les leçons suivantes). La variable cnt et le tampon de données tempBuffer sont utilisés pour contrôler le flux de données entre les opérations de lecture et d'écriture.

Lecture de données depuis AudioInputStream

Le cycle de lecture de l'objet AudioInputStream lit le nombre maximal spécifié d'octets de données de AudioInputStream et place leur tableau d'octets.

Valeur de retour

De plus, cette méthode renvoie le nombre total d'octets lus, ou -1, si la fin de la séquence enregistrée a été atteinte. Le nombre d'octets lus est stocké dans la variable cnt.

Boucle d'écriture SourceDataLine

Si le nombre d'octets lus est supérieur à zéro, il y a alors une transition vers le cycle d'écriture des données dans SourceDataLine. Dans cette boucle, les données audio entrent dans la console de mixage. Les octets sont lus à partir du tableau d'octets conformément à leurs indices et écrits dans le tampon de canal.

Lorsque le flux d'entrée sèche

Lorsque la boucle de lecture renvoie -1, cela signifie que toutes les données audio précédemment enregistrées sont terminées et qu'un contrôle supplémentaire est transmis au fragment de programme dans le listing 10.

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

Listing 10

Verrouillez et attendez

Le code du Listing 10 appelle la méthode drain sur l'objet SourceDataLine afin que le programme puisse bloquer et attendre que le tampon interne se vide dans SourceDataLine. Lorsque le tampon est vide, cela signifie que la totalité de la portion suivante est délivrée à la sortie audio de l'ordinateur.

Fermeture de SourceDataLine

Ensuite, le programme appelle la méthode close pour fermer le canal, montrant ainsi que toutes les ressources système utilisées par le canal sont désormais libres. Sun rapporte la fermeture de canal suivante:

«La fermeture du canal signale que toutes les ressources impliquées pour ce canal peuvent être libérées. Pour libérer des ressources, l'application doit fermer les canaux, qu'ils soient déjà impliqués ou pas déjà, ainsi que la fin de l'application. On suppose que les mélangeurs partagent les ressources du système et peuvent être fermés et ouverts à plusieurs reprises. D'autres canaux peuvent ou non prendre en charge la réouverture après leur fermeture. En général, les mécanismes d'ouverture des lignes varient selon les différents sous-types. »

Et maintenant la fin de l'histoire

Nous avons donc expliqué ici comment notre programme utilise l'API Java Sound afin d'assurer la livraison des données audio de la mémoire interne de l'ordinateur à la carte son.

Exécutez le programme

Vous pouvez maintenant compiler et exécuter le programme à partir du Listing 11, qui couronne la fin de notre leçon.

Capturez et lisez des données audio

Le programme démontre la capacité d'enregistrer des données à partir d'un microphone et de les lire via la carte son de votre ordinateur. Les instructions d'utilisation sont très simples.

Exécutez le programme. L'interface graphique simple, qui est illustrée à la figure 6, devrait apparaître à l'écran.



  • Cliquez sur le bouton Capturer et enregistrez tous les sons dans le microphone.
  • Cliquez sur le bouton Arrêter pour arrêter l'enregistrement.
  • Cliquez sur le bouton Lecture pour lire l'enregistrement via la sortie audio de votre ordinateur.

Si vous n’entendez rien, essayez d’augmenter la sensibilité du microphone ou le volume du haut-parleur.

Le programme enregistre un enregistrement dans la mémoire de l'ordinateur, alors soyez prudent. Si vous essayez d'enregistrer trop de données audio, vous risquez de manquer de RAM.

Conclusion

  • Nous avons découvert que l'API Java Sound est basée sur le concept de canaux et de mélangeurs.
  • Nous avons obtenu les informations initiales sur les caractéristiques physiques et électriques du son analogique, afin de comprendre ensuite le dispositif du mélangeur audio.
  • Nous avons utilisé un scénario de concert de rock amateur utilisant six microphones et deux haut-parleurs stéréo pour décrire la possibilité d'utiliser un mélangeur audio.
  • Nous avons discuté d'un certain nombre de sujets de programmation Java Sound, notamment les mélangeurs, les canaux, le format des données, etc.
  • Nous avons expliqué la relation générale entre les objets SourceDataLine, Clip, Mixer, AudioFormat et les ports dans un programme simple de sortie de données audio.
  • Nous nous sommes familiarisés avec un programme qui nous permet initialement d'enregistrer puis de lire des données audio.
  • Nous avons reçu une explication détaillée du code utilisé pour lire les données audio enregistrées précédemment dans la mémoire de l'ordinateur.

Et ensuite?

Dans ce didacticiel, nous avons découvert que l'API Java Sound est basée sur le concept de mélangeurs et de canaux. Cependant, le code dont nous avons discuté n'incluait pas explicitement les mélangeurs. La classe AudioSystem nous a fourni des méthodes statiques qui permettent d'écrire des programmes de traitement audio sans accéder directement aux mélangeurs. En d'autres termes, ces méthodes statiques nous éloignent des mélangeurs.

Dans la leçon suivante, nous présentons un code de capture de données modifié par rapport à celui présenté dans cette leçon. La nouvelle version utilisera explicitement des mélangeurs pour vous montrer comment les utiliser lorsque vous en avez vraiment besoin.

 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 

Listing 11

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


All Articles