JAVA SOUND API-Grundlagen

Hallo Habr! Ich präsentiere Ihnen die Übersetzung des Artikels „Java Sound, Erste Schritte, Teil 1, Wiedergabe“ .

Sound in JAVA, Teil Eins, Der Anfang. Sound abspielen



Dies ist der Beginn einer Reihe von acht Lektionen, die Sie mit der Java Sound-API vertraut machen.

Was ist Klang in der menschlichen Wahrnehmung? Dies ist das Gefühl, das wir erleben, wenn eine Änderung des Luftdrucks auf die winzigen sensorischen Bereiche in unseren Ohren übertragen wird.

Das Hauptziel bei der Erstellung der Sound-API besteht darin, Ihnen Mittel zum Schreiben von Code zur Verfügung zu stellen, mit deren Hilfe Druckwellen zur richtigen Zeit auf die Ohren des richtigen Motivs übertragen werden können.

Arten von Sound in Java:

  1. Die Java Sound API unterstützt zwei Haupttypen von Audio (Sound).
  2. Ton digitalisiert und direkt als Datei aufgezeichnet
  3. Aufnahme als MIDI-Datei. Sehr weit entfernt, aber ähnlich der Notenschrift, bei der Musikinstrumente in der gewünschten Reihenfolge gespielt werden.

Diese Typen unterscheiden sich in ihrem Wesen erheblich, und wir werden uns auf den ersten konzentrieren, da es sich in den meisten Fällen um Ton handelt, der entweder digitalisiert werden muss, um von einer externen Quelle in eine Datei aufzunehmen, oder umgekehrt, um zuvor aus einer solchen Datei aufgenommenen Ton wiederzugeben.

Vorschau


Die Java Sound API basiert auf dem Konzept von Linien und Mischern.

Weiter:
Wir werden die physikalischen und elektrischen Eigenschaften der analogen Klangdarstellung beschreiben, die auf einen Audiomischer angewendet wird.

Wir wenden uns dem Szenario der beginnenden Rockband zu, die in diesem Fall sechs Mikrofone und zwei Stereolautsprecher verwendet. Wir brauchen dies, um die Funktionsweise des Audiomischers zu verstehen.

Als Nächstes betrachten wir eine Reihe von Java Sound-Themen für die Programmierung, z. B. Linien, Mixer, Formate für Audiodaten und mehr.

Wir werden die Beziehungen zwischen den Objekten SourceDataLine, Clip, Mixer und AudioFormat verstehen und ein einfaches Programm erstellen, das Audio wiedergibt.

Im Folgenden finden Sie ein Beispiel für dieses Programm, mit dem Sie den aufgenommenen Ton aufnehmen und dann wiedergeben können.

In Zukunft werden wir den zu diesem Zweck verwendeten Programmcode vollständig erläutern. Aber keineswegs vollständig in dieser Lektion.

Codebeispiel und Überlegung


Physikalische und elektrische Eigenschaften von analogem Klang

In unserer Lektion möchten wir Ihnen die Grundlagen der Java-Programmierung mithilfe der Java Sound-API vorstellen.

Die Java Sound API basiert auf dem Konzept eines Audio-Mixers, der häufig zum Abspielen von Sound fast überall verwendet wird: von Rockkonzerten bis zum Hören von CDs zu Hause. Bevor Sie jedoch eine ausführliche Erläuterung der Funktionsweise des Audiomischers vornehmen, sollten Sie sich mit den physikalischen und elektrischen Eigenschaften des analogen Klangs selbst vertraut machen.

Schauen Sie sich Abb. 1



Vasya Pupyrkin drückt eine Rede.

Diese Abbildung zeigt, wie Vasya eine Rede mit einem System hält, das als Wide-Address-System bekannt ist. Ein solches System umfasst typischerweise ein Mikrofon, einen Verstärker und einen Lautsprecher. Der Zweck dieses Systems ist es, Vasyas Stimme zu stärken, so dass er auch in einer großen Menge gehört werden kann.

Wackeln in der Luft

Kurz gesagt, wenn Vasya spricht, lassen seine Stimmbänder Luftpartikel in seinem Kehlkopf vibrieren. Dies führt zur Entstehung von Schallwellen, die wiederum dazu führen, dass die Mikrofonmembran vibriert und sich dann in elektrische Schwingungen mit sehr kleiner Amplitude verwandelt, die genau die Schallschwingungen von Vasyas Original simulieren. Ein Verstärker verstärkt, wie der Name schon sagt, diese elektrischen Schwingungen. Dann gelangen sie zum Lautsprecher, der die inverse Umwandlung verstärkter elektrischer Schwingungen in sehr verstärkte Schallwellen durchführt, aber dennoch genau die gleichen Wellen wiederholt, die in Vasya Pupyrkins Stimmbändern erzeugt werden.

Dynamisches Mikrofon

Schauen wir uns nun Abb. 2, die ein schematisches Diagramm eines Mikrofons zeigt, das als dynamisch bezeichnet wird.


Abb. 2 Dynamische Mikrofonschaltung

Schallschwingungen beeinflussen die Membran

Der Druck der Schallschwingungen wirkt auf eine flexible Membran im Mikrofon. Dies bewirkt, dass die Membran vibriert, während die Vibrationen der Membran die Vibrationen von Schallwellen wiederholen.

Bewegliche Spule

Eine Spule aus dünnem Draht ist an der Mikrofonmembran angebracht. Wenn die Membran schwingt, macht die Spule auch Hin- und Herbewegungen im Magnetfeld des Kerns, der aus einem starken Permanentmagneten besteht. Und wie auch Faraday feststellte, entsteht in der Spule ein elektrischer Strom.

Ein elektrisches Signal folgt der Form von Schallwellen.

Somit wird aus einem sehr schwachen Strom, der in der Spule induziert wird, ein elektrisches Wechselsignal erhalten, das die Form von Schallwellen wiederholt, die auf die Mikrofonmembran wirken. Ferner wird dieses Signal in Form einer Wechselspannung dem Eingang des Verstärkers aus Fig. 1 zugeführt. 1.

Lautsprecher

Tatsächlich wiederholt das Funktionsprinzip des Lautsprechers das Gerät eines dynamischen Mikrofons, das nur in die entgegengesetzte Richtung eingeschaltet ist. (In diesem Fall sind die Wicklungsdrähte natürlich viel dicker und die Membran ist viel größer, um den Betrieb mit einem verstärkten Signal sicherzustellen.)




Schwingungen der Lautsprechermembran wirken sich auf Luftpartikel aus und erzeugen starke Schallwellen. Die Form dieser Wellen wiederholt genau die Form von Schallwellen mit viel geringerer Intensität, die durch Vasyas Stimmbänder erzeugt werden. Aber die Intensität der neuen Wellen reicht jetzt aus, um sicherzustellen, dass die Schallschwingungen von Vasya die Ohren von Menschen erreichen, die selbst in den hinteren Reihen einer großen Menge stehen.

Rockkonzert

Zu diesem Zeitpunkt fragen Sie sich vielleicht, was dies alles mit der Java Sound API zu tun hat. Aber warten Sie etwas länger, wir führen Sie zu den Grundlagen des Audiomischers.

Die oben beschriebene Schaltung war recht einfach. Es bestand aus Vasya Pupyrkin, einem Mikrofon, einem Verstärker und einem Lautsprecher. Betrachten Sie nun die Schaltung mit Abb. 4, die die Bühne präsentiert, die für das Rockkonzert der beginnenden Musikgruppe vorbereitet wurde.



Sechs Mikrofone und zwei Lautsprecher

In Abb. 4 Sechs Mikrofone befinden sich auf der Bühne. An den Seiten der Bühne befinden sich zwei Lautsprecher. Zu Beginn des Konzerts singen oder spielen die Darsteller in jedem der sechs Mikrofone Musik. Dementsprechend werden wir sechs elektrische Signale haben, die einzeln verstärkt und dann beiden Lautsprechern zugeführt werden müssen. Darüber hinaus können Interpreten verschiedene Sound-Spezialeffekte verwenden, z. B. Hall, die ebenfalls in elektrische Signale umgewandelt werden müssen, bevor sie an die Lautsprecher angelegt werden.

Zwei Lautsprecher an den Seiten der Bühne erzeugen den Effekt von Stereoklang. Das heißt, das elektrische Signal, das vom rechts auf der Bühne befindlichen Mikrofon kommt, sollte in den ebenfalls rechts befindlichen Lautsprecher fallen. Ebenso sollte das Signal vom Mikrofon links dem Lautsprecher links von der Szene zugeführt werden. Elektrische Signale von anderen Mikrofonen, die sich näher an der Bühnenmitte befinden, sollten jedoch bereits in angemessenen Anteilen an beide Lautsprecher übertragen werden. Und zwei Mikrofone direkt in der Mitte sollten ihr Signal gleichermaßen an beide Lautsprecher übertragen.

Audiomischer

Die oben diskutierte Aufgabe wird nur von einem elektronischen Gerät ausgeführt, das als Audiomischer bezeichnet wird.

Audio-Leitung (Kanal)

Obwohl der Autor kein Experte für Audiomischer ist, hat ein typischer Audiomischer nach seinem bescheidenen Verständnis die Fähigkeit, am Eingang eine bestimmte Anzahl von voneinander unabhängigen elektrischen Signalen zu empfangen, von denen jedes das ursprüngliche Tonsignal oder die ursprüngliche Leitung (Kanal) darstellt.

(Das Konzept eines Audiokanals wird sehr wichtig, wenn wir beginnen, die Java Sound-API im Detail zu verstehen.

Unabhängige Verarbeitung jedes Audiokanals

In jedem Fall kann der Standard-Audiomischer jede Audiolinie unabhängig von den anderen anderen Kanälen verstärken. Außerdem kann der Mixer normalerweise Sound-Spezialeffekte wie z. B. Hall auf eine der Audio-Linien übertragen. Am Ende kann der Mischer, wie der Name schon sagt, alle einzelnen elektrischen Signale in den Ausgangskanälen so mischen, wie er eingestellt wird, um den Beitrag jeder Audioleitung zu den Ausgangskanälen zu steuern. (Dieser Regler wird normalerweise als Pan oder Pan bezeichnet. Verteilung im Raum).

Zurück zum Stereoton

So ist im Diagramm mit Abb. In 4 hat der Toningenieur des Audiomischers die Fähigkeit, Signale von sechs Mikrofonen zu kombinieren, um zwei Ausgangssignale zu erhalten, von denen jedes zu seinem Lautsprecher übertragen wird.

Für einen erfolgreichen Betrieb muss das Signal von jedem Mikrofon in einem angemessenen Verhältnis geliefert werden, abhängig von der physischen Position des Mikrofons auf der Bühne. (Durch Ändern des Schwenks kann ein qualifizierter Tontechniker bei Bedarf den Beitrag jedes Mikrofons ändern, wenn sich beispielsweise der Sänger während eines Konzerts auf der Bühne bewegt.)

Zeit, in die Welt der Programmierung zurückzukehren

Kehren wir nun von der physischen Welt in die Welt der Programmierung zurück. Laut Sun: „Java Sound beinhaltet keine spezielle Hardwarekonfiguration. Es ermöglicht die Installation verschiedener Audiokomponenten auf dem System und die Bereitstellung für den Benutzer über die API. Java Sound unterstützt die Standard-Ein- und Ausgabefunktionen einer Soundkarte (z. B. zum Aufnehmen und Abspielen von Audiodateien) sowie die Möglichkeit, mehrere Audiostreams zu mischen. “

Mischer und Kanäle

Wie bereits erwähnt, basiert die Java Sound API auf dem Konzept von Mixern und Kanälen. Wenn Sie von der physischen Welt in die Programmierwelt wechseln, schreibt Sun Folgendes zum Mixer:

„Ein Mixer ist ein Audiogerät mit einem oder mehreren Kanälen. Der Mixer, der das Audiosignal wirklich mischt, muss jedoch mehrere Eingangskanäle von Quellquellen und mindestens einen Ausgangszielkanal haben. "

Eingabezeilen können Instanzen von Klassen mit SourceDataLine-Objekten sein, und Ausgabezeilen können TargetDataLine-Objekte sein. Der Mixer kann auch aufgezeichneten und geloopten Sound als Eingang empfangen und seine Eingangsquellenkanäle als Instanzen von Klassenobjekten definieren, die die Clip-Schnittstelle implementieren.

Kanalleitungsschnittstelle.

Sun meldet Folgendes über die Line-Schnittstelle: „ Line ist ein Element einer digitalen Audio-Pipeline, z. B. ein Audioeingang oder -ausgang, ein Mixer oder ein Audiopfad zu oder von einem Mixer. Die Audiodaten, die durch den Kanal geleitet werden, können ein- oder mehrkanalig sein (z. B. Stereo). ... Ein Kanal kann Steuerelemente wie Gain, Pan und Reverb haben. “

Begriffe zusammenfügen

Die obigen Zitate von Sun bezeichneten also die folgenden Begriffe

Sourcedataline
Targetgetataline
Hafen
Clip
Kontrollen

Abb. 5 zeigt ein Beispiel für die Verwendung dieser Begriffe zum Erstellen eines einfachen Audioausgabeprogramms.



Programm Skript

Aus Sicht der Software 5 zeigt ein Mixer-Objekt, das mit einem Clip-Objekt und zwei SourceDataLine-Objekten erhalten wurde.

Was ist Clip?

Clip ist ein Objekt am Eingang des Mischpults, dessen Inhalt sich mit der Zeit nicht ändert. Mit anderen Worten, Sie laden die Audiodaten in das Clip-Objekt, bevor Sie es abspielen. Der Audioinhalt des Clip-Objekts kann ein- oder mehrmals abgespielt werden. Sie können den Clip zurückschleifen und dann wird der Inhalt immer wieder abgespielt.

Eingabestream

Das SourceDataLine-Objekt ist dagegen ein Stream-Objekt am Eingang des Mixers. Ein Objekt dieses Typs kann einen Strom von Audiodaten empfangen und in Echtzeit an den Mixer senden. Die erforderlichen Audiodaten können aus verschiedenen Quellen wie Audiodateien, Netzwerkverbindung oder Speicherpuffer bezogen werden.

Verschiedene Arten von Kanälen

Somit können die Objekte Clip und SourceDataLine als Eingangskanäle für das Mixer-Objekt betrachtet werden. Jeder dieser Eingangskanäle kann seinen eigenen haben: Pan, Gain und Reverb.

Audioinhalte abspielen

In einem solch einfachen System liest der Mixer Daten von den Eingangsleitungen, verwendet die Steuerung zum Mischen der Eingangssignale und liefert die Ausgabe an einen oder mehrere Ausgangskanäle, wie z. B. einen Lautsprecher, einen Leitungsausgang, eine Kopfhörerbuchse usw.

Listing 11 zeigt ein einfaches Programm, das Audiodaten von einem Mikrofonanschluss erfasst, diese Daten im Speicher speichert und dann über den Lautsprecheranschluss wiedergibt.

Wir werden nur die Aufnahme und Wiedergabe diskutieren. Der größte Teil des oben genannten Programms besteht darin, ein Fenster und eine grafische Oberfläche für den Benutzer zu erstellen, damit die Aufnahme und Wiedergabe gesteuert werden kann. Wir werden diesen Teil nicht als über das Ziel hinausgehend erörtern. Aber dann werden wir die Erfassung und Wiedergabe von Daten betrachten. Wir werden in dieser Lektion über das Verlieren sprechen und in der nächsten festhalten. Auf dem Weg werden wir die Verwendung des Audiokanals mit der Java Sound API veranschaulichen.

Die erfassten Daten werden in einem ByteArrayOutputStream-Objekt gespeichert.

Ein Code-Snippet-Snippet ermöglicht das Lesen von Audiodaten von einem Mikrofon und das Speichern als ByteArrayOutputStream-Objekt.

Die Methode mit dem Namen playAudio, die in Listing 1 beginnt, spielt die Audiodaten ab, die im ByteArrayOutputStream-Objekt erfasst und gespeichert wurden.

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

Listing 1

Wir beginnen mit dem Standardcode.

Das Programm-Snippet in Listing 1 ist noch nicht mit Java Sound verwandt.

Sein Zweck ist:

  • Konvertieren Sie zuvor gespeicherte Daten in ein Array vom Typ Byte.
  • Ruft den Eingabestream für ein Byte-Datenarray ab.

Wir benötigen dies, um Audiodaten für die spätere Wiedergabe verfügbar zu machen.

Gehen Sie zur Sound-API

Die Codezeile in Listing 2 bezieht sich bereits auf die Java Sound API.

  AudioFormat audioFormat = getAudioFormat(); 

Listing 2:

Hier gehen wir kurz auf das Thema ein, das in der nächsten Lektion ausführlich besprochen wird.

Zwei unabhängige Formate

Meist handelt es sich um zwei unabhängige Formate für Audiodaten.

Dateiformat (beliebig), das Audiodaten enthält (in unserem Programm noch nicht, da die Daten im Speicher gespeichert sind)

Das Format der übermittelten Audiodaten ist an sich.

Was ist ein Audioformat?

Hier ist, was Sun darüber schreibt:

„Jeder Datenkanal hat ein eigenes Audioformat, das seinem Datenstrom zugeordnet ist. Das Format (eine Instanz von AudioFormat) bestimmt die Bytereihenfolge des Audiostreams. Die Formatparameter können die Anzahl der Kanäle, die Abtastfrequenz, das Quantisierungsbit, das Codierungsverfahren usw. sein. Die üblichen Codierungsverfahren können die lineare Pulscodemodulation des PCM und seiner Varianten sein. “

Bytefolge

Die Quell-Audiodaten sind eine Bytefolge von Binärdaten. Es gibt verschiedene Möglichkeiten, wie Sie diese Sequenz organisieren und interpretieren können. Wir werden uns nicht im Detail mit all diesen Optionen befassen, aber wir werden ein wenig auf das Audioformat eingehen, das wir hier in unserem Programm verwenden.

Kleiner Exkurs

Hier verlassen wir zunächst die playAudio-Methode und sehen uns die getAudioFormat-Methode aus Listing 2 an.

Die vollständige getAudioFormat-Methode ist in Listing 3 dargestellt.

  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:

Zusätzlich zur Deklaration initialisierter Variablen enthält der Code in Listing 3 einen ausführbaren Ausdruck.

AudioFormat-Objekt

Die Methode getAudioFormat erstellt eine Instanz eines Objekts der AudioFormat-Klasse und gibt sie zurück. Folgendes schreibt Sun über diese Klasse:

„Die AudioFormat-Klasse definiert die spezifische Reihenfolge der Daten in einem Audiostream. Wenn Sie sich den Feldern des AudioFormat-Objekts zuwenden, erhalten Sie Informationen zur korrekten Interpretation von Bits in einem binären Datenstrom. “

Wir verwenden den einfachsten Konstruktor

Die AudioFormat-Klasse verfügt über zwei Arten von Konstruktoren (wir werden den trivialsten nehmen). Die folgenden Parameter sind für diesen Konstruktor erforderlich:

  • Abtastrate oder Abtastrate pro Sekunde (Verfügbare Werte: 8000, 11025, 16000, 22050 und 44100 Abtastungen pro Sekunde)
  • Bittiefe der Daten (8 und 16 Bit pro Zählung sind verfügbar)
  • Anzahl der Kanäle (ein Kanal für Mono und zwei für Stereo)
  • Signierte oder nicht signierte Daten, die im Stream verwendet werden (z. B. variiert der Wert zwischen 0 und 255 oder zwischen -127 und +127).
  • Die Bytereihenfolge von Big-Endian oder Little-Endian. (Wenn Sie einen Byte-Stream mit 16-Bit-Werten übertragen, ist es wichtig zu wissen, welches Byte zuerst kommt - niedrig oder hoch, da es beide Optionen gibt).

Wie Sie in Listing 3 sehen können, haben wir in unserem Fall die folgenden Parameter für eine Instanz des AudioFormat-Objekts verwendet.

  • 8000 Proben pro Sekunde
  • 16 Datengröße
  • signifikante Daten
  • Little-Endian-Ordnung

Standardmäßig werden Daten von linearem PCM codiert.

Der von uns verwendete Konstruktor erstellt eine Instanz des AudioFormat-Objekts unter Verwendung der linearen Pulscodemodulation und der oben angegebenen Parameter (wir werden in den folgenden Lektionen auf lineares PCM und andere Codierungsmethoden zurückkommen).

Zurück zur playAudio-Methode

Nachdem wir nun verstanden haben, wie das Audiodatenformat in Java-Sound funktioniert, kehren wir zur playAudio-Methode zurück. Sobald wir die verfügbaren Audiodaten abspielen wollen, benötigen wir ein Objekt der Klasse AudioInputStream. Wir erhalten eine Instanz davon in Listing 4.

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

Listing 4:

Parameter für den AudioInputStream-Konstruktor

  • Der Konstruktor für die AudioInputStream-Klasse erfordert die folgenden drei Parameter:
  • Der Stream, auf dem die Instanz des AudioInputStream-Objekts basiert (wie wir zu diesem Zweck sehen, verwenden wir die zuvor erstellte Instanz des ByteArrayInputStream-Objekts).
  • Das Audiodatenformat für diesen Stream (zu diesem Zweck haben wir bereits eine Instanz des AudioFormat-Objekts erstellt)
  • Die Größe des Frames (Frames) für die Daten in diesem Stream (siehe Beschreibung unten)
  • Die ersten beiden Parameter sind aus dem Code in Listing 4 ersichtlich. Der dritte Parameter ist jedoch an sich nicht so offensichtlich.

Holen Sie sich die Rahmengröße

Wie wir aus Listing 4 sehen können, wird der Wert des dritten Parameters mithilfe von Berechnungen erstellt. Dies ist nur eines der Attribute des Audioformats, die wir zuvor nicht erwähnt haben, und es wird als Frame bezeichnet.

Was ist ein Rahmen?

Für ein einfaches lineares PCM, das in unserem Programm verwendet wird, enthält der Frame eine Reihe von Samples für alle Kanäle zu einem bestimmten Zeitpunkt.

Somit ist die Rahmengröße gleich der Größe der Anzahl in Bytes mal der Anzahl von Kanälen.

Wie Sie vielleicht vermutet haben, gibt eine Methode namens getFrameSize die Frame-Größe in Bytes zurück.

Berechnung der Rahmengröße

Somit kann die Länge von Audiodaten in einem Rahmen berechnet werden, indem die Gesamtzahl der Bytes in der Audiodatensequenz durch die Anzahl der Bytes in einem Rahmen dividiert wird. Diese Berechnung wird für den dritten Parameter in Listing 4 verwendet.

Abrufen eines SourceDataLine-Objekts

Der nächste Teil des Programms, den wir diskutieren werden, ist ein einfaches Audioausgabesystem. Wie wir aus dem Diagramm in Abb. 5 sehen können, benötigen wir zur Lösung dieses Problems ein SourceDataLine-Objekt.

Es gibt verschiedene Möglichkeiten, eine Instanz des SourceDataLine-Objekts abzurufen, die alle sehr schwierig sind. Der Code in Listing 5 ruft einen Verweis auf eine Instanz des SourceDataLine-Objekts ab und speichert ihn.

(Beachten Sie, dass dieser Code nicht nur das SourceDataLine-Objekt instanziiert. Er wird auf ziemlich umständliche Weise abgerufen.)

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

Listing 5:

Was ist ein SourceDataLine-Objekt?

Darüber schreibt Sun Folgendes:

„SourceDataLine ist ein Datenkanal, in den Daten geschrieben werden können. Es dient als Eingang für einen Mixer. Eine Anwendung schreibt eine Byte-Sequenz in eine SourceDataLine, die die Daten puffert und an ihren Mixer liefert. Der Mischer kann die Daten, die er für die nächste Stufe verarbeitet, beispielsweise an den Ausgangsport übertragen.

Beachten Sie, dass die Namenskonvention für diese Paarung die Beziehung zwischen dem Kanal und seinem Mixer widerspiegelt. “

GetLine-Methode für die AudioSystem-Klasse

Eine Möglichkeit, eine Instanz des SourceDataLine-Objekts abzurufen, besteht darin, die statische getLine-Methode aus der AudioSystem-Klasse aufzurufen (wir werden in den nächsten Lektionen viel darüber zu berichten haben).

Die Methode getLine erfordert einen Eingabeparameter vom Typ Line.Info und gibt ein Line-Objekt zurück, das der Beschreibung im bereits definierten Line.Info-Objekt entspricht.

Noch ein kurzer Exkurs

Sun meldet die folgenden Informationen zum Line.Info-Objekt:

„Der Kanal verfügt über ein eigenes Informationsobjekt (eine Instanz von Line.Info), das anzeigt, welcher Mixer (falls vorhanden) die gemischten Audiodaten als Ausgabe direkt an den Kanal sendet und welcher Mixer (falls vorhanden) die Audiodaten als Eingabe direkt vom Kanal empfängt. Linienvarianten können Unterklassen von Line.Info entsprechen, mit denen Sie andere Parametertypen angeben können, die sich auf bestimmte Kanaltypen beziehen. “

DataLine.Info-Objekt

Der erste Ausdruck in Listing 5 erstellt eine neue Instanz des DataLine.Info-Objekts, bei der es sich um eine spezielle Form (Unterklasse) des Line.Info-Objekts handelt.

Es gibt mehrere überladene Konstruktoren für die DataLine.Info-Klasse. Wir haben die am einfachsten zu verwendende ausgewählt. Dieser Konstruktor benötigt zwei Parameter.

Klassenobjekt

Der erste Parameter ist Class, der die Klasse darstellt, die wir als SourceDataLine.class definiert haben

Der zweite Parameter bestimmt das gewünschte Datenformat für den Kanal. Wir verwenden dafür eine Instanz des AudioFormat-Objekts, die bereits zuvor definiert wurde.

Wo sind wir schon

Leider haben wir immer noch nicht das am meisten benötigte SourceDataLine-Objekt. Bisher haben wir ein Objekt, das nur Informationen über das von uns benötigte SourceDataLine-Objekt bereitstellt.

Abrufen eines SourceDataLine-Objekts

Der zweite Ausdruck in Listing 5 erstellt und speichert schließlich die benötigte Instanz von SourceDataLine. Dies geschieht durch Aufrufen der statischen Methode getLine der AudioSystem-Klasse und Übergabe von dataLineInfo als Parameter. (In der nächsten Lektion werden wir uns ansehen, wie Sie das Line-Objekt erhalten, indem Sie direkt mit dem Mixer-Objekt arbeiten.)

Die Methode getLine gibt einen Verweis auf ein Objekt vom Typ Line zurück, das das übergeordnete Element von SourceDataLine ist. Daher ist hier ein Downcast erforderlich, bevor der Rückgabewert als SourceDataLine gespeichert wird.

Machen wir uns bereit, das SourceDataLine-Objekt zu verwenden

Sobald wir eine Instanz des SourceDataLine-Objekts erhalten haben, müssen wir es für das Öffnen und Ausführen vorbereiten, wie in Listing 6 gezeigt.

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

Listing 6:

Öffnungsmethode

Wie Sie in Listing 6 sehen können, haben wir das AudioFormat-Objekt an die Öffnungsmethode für das SourceDataLine-Objekt gesendet.

Laut Sun ist dies eine Methode:

"Öffnet eine Leitung (Kanal) mit einem zuvor definierten Format, sodass er alle Systemressourcen empfangen kann, die er benötigt, und sich in einem funktionierenden Zustand befindet."

Erkennungsstatus

Es gibt wenig mehr, was Sun in diesem Thread über ihn schreibt.

„Das Öffnen und Schließen des Kanals wirkt sich auf die Verteilung der Systemressourcen aus. Durch das erfolgreiche Öffnen des Kanals wird sichergestellt, dass dem Kanal alle erforderlichen Ressourcen zur Verfügung gestellt werden.

Das Öffnen des Mischpults, dessen Eingangs- und Ausgangsanschlüsse für Audiodaten vorhanden sind, umfasst unter anderem die Verwendung der Hardware der Plattform, auf der die Arbeit und Initialisierung der erforderlichen Softwarekomponenten stattfindet.

Das Öffnen eines Kanals, der eine Route für Audiodaten zu oder von einem Mixer darstellt, umfasst sowohl das Initialisieren als auch das Empfangen keineswegs unbegrenzter Mixerressourcen. Mit anderen Worten, der Mischer hat eine begrenzte Anzahl von Kanälen, sodass mehrere Anwendungen mit ihren eigenen Kanalanforderungen (und manchmal sogar eine Anwendung) die Mischerressourcen korrekt teilen müssen. “

Rufen Sie die Startmethode auf einem Kanal auf

Laut Sun bedeutet das Aufrufen der Startmethode für einen Kanal Folgendes:

„Der Kanal darf E / A-Leitungen verwenden. Wenn versucht wird, eine bereits funktionierende Leitung zu verwenden, führt die Methode nichts aus. Nachdem der Datenpuffer leer ist, wird die Zeile mit dem Starten der E / A fortgesetzt, beginnend mit dem ersten Frame, den sie nach dem vollständigen Laden des Puffers nicht verarbeiten konnte. “

In unserem Fall hat der Kanal natürlich nicht aufgehört. Seit wir es zum ersten Mal gestartet haben.

Jetzt haben wir fast alles was wir brauchen

Zu diesem Zeitpunkt haben wir alle Audioressourcen erhalten, die wir zur Wiedergabe der Audiodaten benötigen, die wir zuvor aufgezeichnet und in der ByteArrayOutputStream-Objektinstanz gespeichert haben. (Denken Sie daran, dass dieses Objekt nur im RAM des Computers vorhanden ist.)

Wir beginnen zu fließen

Wir werden den Stream erstellen und starten, um das Audio abzuspielen. Der Code in Listing 7 erstellt und startet diesen Thread.

(Verwechseln Sie den Aufruf der Startmethode in diesem Thread nicht mit dem Aufruf der Startmethode im SourceDataLine-Objekt aus Listing 6. Dies sind völlig unterschiedliche Vorgänge.)

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

Listing 7:

Unprätentiöser Code

Der Ausschnitt des Programms in Listing 7 ist zwar sehr einfach, zeigt jedoch ein Beispiel für die Multithread-Programmierung in Java. Wenn Sie es nicht verstehen, sollten Sie sich mit diesem Thema in speziellen Themen zum Erlernen von Java vertraut machen.

Sobald der Stream gestartet ist, funktioniert er, bis alle aufgezeichneten Audiodaten bis zum Ende abgespielt wurden.

Neues Thread-Objekt

Der Code in Listing 7 erstellt eine Instanz des Thread-Objekts der PlayThread-Klasse. Diese Klasse ist in unserem Programm als innere Klasse definiert. Ihre Beschreibung beginnt in Listing 8.

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

Listing 8:

Die Ausführungsmethode in der Thread-Klasse

Mit Ausnahme der Deklaration einer tempBuffer-Variablen (die sich auf ein Array von Bytes bezieht) ist eine vollständige Definition dieser Klasse nur eine Definition der Ausführungsmethode. Wie Sie bereits wissen sollten, führt das Aufrufen der Startmethode für ein Thread-Objekt dazu, dass die Ausführungsmethode dieses Objekts ausgeführt wird

Die Ausführungsmethode für diesen Thread beginnt in 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:

Der erste Teil des Programmfragments in der Ausführungsmethode

Die Ausführungsmethode enthält zwei wichtige Teile, von denen der erste in Listing 9 dargestellt ist.

Insgesamt wird hier eine Schleife verwendet, um Audiodaten aus einem AudioInputStream zu lesen und an eine SourceDataLine zu übergeben.

An das SourceDataLine-Objekt gesendete Daten werden automatisch an die Standard-Audioausgabe übertragen. Es kann sich um einen eingebauten Computerlautsprecher oder einen Leitungsausgang handeln. (In den folgenden Lektionen lernen wir, die erforderlichen Audiogeräte zu ermitteln.) Die cnt-Variable und der tempBuffer-Datenpuffer werden verwendet, um den Datenfluss zwischen Lese- und Schreibvorgängen zu steuern.

Lesen von Daten aus AudioInputStream

Der Lesezyklus vom AudioInputStream-Objekt liest die angegebene maximale Anzahl von Datenbytes aus dem AudioInputStream und platziert deren Bytearray.

Rückgabewert

Ferner gibt diese Methode die Gesamtzahl der gelesenen Bytes oder -1 zurück, wenn das Ende der aufgezeichneten Sequenz erreicht wurde. Die Anzahl der gelesenen Bytes wird in der Variablen cnt gespeichert.

SourceDataLine-Schreibschleife

Wenn die Anzahl der gelesenen Bytes größer als Null ist, erfolgt ein Übergang zum Zyklus des Schreibens von Daten in SourceDataLine. In dieser Schleife gelangen die Audiodaten in den Mixer. Bytes werden gemäß ihren Indizes aus dem Bytearray gelesen und in den Kanalpuffer geschrieben.

Wenn der Eingangsstrom trocknet

Wenn die Leseschleife -1 zurückgibt, bedeutet dies, dass alle zuvor aufgezeichneten Audiodaten beendet wurden und die weitere Steuerung an das Programmfragment in Listing 10 übergeben wird.

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

Listing 10:

Sperren und warten

Der Code in Listing 10 ruft die Drain-Methode für das SourceDataLine-Objekt auf, damit das Programm blockieren und warten kann, bis der interne Puffer in SourceDataLine leer ist. Wenn der Puffer leer ist, bedeutet dies, dass der gesamte nächste Teil an die Tonausgabe des Computers geliefert wird.

SourceDataLine schließen

Anschließend ruft das Programm die Methode close auf, um den Kanal zu schließen, wodurch angezeigt wird, dass alle vom Kanal verwendeten Systemressourcen jetzt frei sind. Sun meldet die folgende Kanalschließung:

„Das Schließen des Kanals signalisiert, dass alle für diesen Kanal beteiligten Ressourcen freigegeben werden können. Um Ressourcen freizugeben, muss die Anwendung die Kanäle schließen, unabhängig davon, ob sie bereits beteiligt sind oder nicht, sowie wenn die Anwendung endet. Es wird davon ausgegangen, dass Mischer Systemressourcen gemeinsam nutzen und wiederholt geschlossen und geöffnet werden können. Andere Kanäle unterstützen möglicherweise die Wiedereröffnung, nachdem sie geschlossen wurden. Im Allgemeinen variieren die Mechanismen zum Öffnen von Linien je nach Subtyp. “

Und jetzt das Ende der Geschichte

Daher haben wir hier erklärt, wie unser Programm die Java Sound API verwendet, um die Übertragung von Audiodaten aus dem internen Speicher des Computers auf die Soundkarte sicherzustellen.

Führen Sie das Programm aus

Jetzt können Sie das Programm aus Listing 11 kompilieren und ausführen, was das Ende unserer Lektion krönt.

Erfassen und Abspielen von Audiodaten

Das Programm demonstriert die Fähigkeit, Daten von einem Mikrofon aufzunehmen und über die Soundkarte Ihres Computers abzuspielen. Anweisungen zur Verwendung sind sehr einfach.

Führen Sie das Programm aus. Die einfache GUI GUI, die in Abbildung 6 dargestellt ist, sollte auf dem Bildschirm angezeigt werden.



  • Klicken Sie auf die Schaltfläche Aufnehmen und nehmen Sie alle Töne auf dem Mikrofon auf.
  • Klicken Sie auf die Schaltfläche Stopp, um die Aufnahme zu beenden.
  • Klicken Sie auf die Schaltfläche Wiedergabe, um die Aufnahme über die Tonausgabe Ihres Computers abzuspielen.

Wenn Sie nichts hören, erhöhen Sie die Mikrofonempfindlichkeit oder die Lautsprecherlautstärke.

Das Programm speichert eine Aufzeichnung im Speicher des Computers. Seien Sie also vorsichtig. Wenn Sie versuchen, zu viele Audiodaten zu speichern, ist möglicherweise nicht mehr genügend Arbeitsspeicher vorhanden.

Fazit

  • Wir haben herausgefunden, dass die Java Sound API auf dem Konzept von Kanälen und Mixern basiert.
  • Wir haben die ersten Informationen über die physikalischen und elektrischen Eigenschaften des analogen Klangs erhalten, um dann das Gerät des Audiomischers zu verstehen.
  • Wir haben ein Amateur-Rockkonzertszenario mit sechs Mikrofonen und zwei Stereolautsprechern verwendet, um die Möglichkeit der Verwendung eines Audiomischers zu beschreiben.
  • Wir haben eine Reihe von Java Sound-Programmierthemen besprochen, darunter Mixer, Kanäle, Datenformat und mehr.
  • Wir haben die allgemeine Beziehung zwischen den Objekten SourceDataLine, Clip, Mixer, AudioFormat und Ports in einem einfachen Programm zur Ausgabe von Audiodaten erläutert.
  • Wir haben ein Programm kennengelernt, mit dem wir zunächst Audiodaten aufnehmen und dann wiedergeben können.
  • Wir haben eine detaillierte Erklärung des Codes erhalten, der zum Abspielen von Audiodaten verwendet wird, die zuvor im Speicher des Computers aufgezeichnet wurden.

Was weiter?

In diesem Tutorial haben wir herausgefunden, dass die Java Sound API auf dem Konzept von Mixern und Kanälen basiert. Der besprochene Code enthielt jedoch keine expliziten Mixer. Die AudioSystem-Klasse stellte uns statische Methoden zur Verfügung, mit denen Audioverarbeitungsprogramme geschrieben werden können, ohne direkt auf die Mixer zuzugreifen. Mit anderen Worten, diese statischen Methoden nehmen uns Mischer weg.

In der nächsten Lektion präsentieren wir einen modifizierten Datenerfassungscode im Vergleich zu dem in dieser Lektion vorgestellten. In der neuen Version werden explizit Mixer verwendet, um Ihnen zu zeigen, wie Sie sie verwenden können, wenn Sie sie wirklich benötigen.

 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/de434424/


All Articles