JAVA SOUND API基础

哈Ha! 我向您介绍了文章“ Java声音,入门,第1部分,播放”的翻译

JAVA中的声音,第一部分,开始。 播放声音



这是一系列八个课程的开始,这些课程将使您完全熟悉Java Sound API。

人类感知的声音是什么? 当气压的变化传递到我们耳朵内部的微小感觉区域时,我们会感受到这种感觉。

创建Sound API的主要目的是为您提供编写代码的方法,这将有助于在正确的时间将压力波传递到正确的对象的耳朵。

Java中的声音类型:

  1. Java Sound API支持两种主要类型的音频(声音)。
  2. 声音数字化并直接记录为文件
  3. 录制为MIDI文件。 距离很远,但类似于乐谱,乐器以所需的顺序演奏。

这些类型在本质上有很大不同,我们将专注于第一种类型,因为在大多数情况下,我们处理的声音要么需要数字化才能从外部源录制到文件,反之亦然,以再现以前从此类文件录制的声音。

预览版


Java Sound API基于线路和混音器的概念

下一个:
我们将描述应用于混音器的声音模拟表示的物理和电气特性。

我们将转到开始摇滚乐队的场景,在这种情况下,它将使用六个麦克风和两个立体声扬声器。 我们需要它来了解音频混合器的操作。

接下来,我们来看许多用于编程的Java Sound主题,例如行,混音器,音频数据格式等等。

我们将了解对象SourceDataLine,Clip,Mixer,AudioFormat之间的关系,并创建一个播放音频的简单程序。

下面我们给出该程序的示例,您可以使用它来录制然后播放录制的声音。

将来,我们将提供用于此目的程序代码的完整说明。 但是在本课中绝不是完全。

代码示例和注意事项


模拟声音的物理和电气特性

本课的目的是向您介绍使用Java Sound API进行Java编程的基础知识。

Java Sound API基于混音器的概念,混音器是在几乎任何地方播放声音的常用设备:从摇滚音乐会到在家听CD。 但是在开始详细说明混音器的操作之前,熟悉模拟声音本身的物理和电气特性将很有用。

看图。 1个



Vasya Pupyrkin发表讲话。

该图显示了Vasya使用称为宽地址的语音系统进行演讲的能力。 这样的系统通常包括麦克风,放大器和扬声器。 该系统的目的是增强Vasya的声音,以便即使在人群中也能听到他的声音。

在空中摇晃

简而言之,当Vasya讲话时,他的声带会导致空气颗粒在其喉中振动。 这会导致出现声波,进而使麦克风膜振动,然后转变成非常小的振幅的电振动,从而精确地模拟了Vasya最初产生的声振动。 顾名思义,放大器会放大这些电振动。 然后,他们到达扬声器,扬声器将放大的电子振动逆变换为放大的声波,但仍精确地重复了Vasya Pupyrkin声带中产生的相同波。

动态麦克风

现在让我们看一下图。 参照图2,其示出了称为动态的麦克风的示意图。


2动态麦克风电路

声音振动会影响膜

声音振动的压力作用在麦克风内部的柔性膜上。 这导致膜振动,而膜的振动重复声波的振动。

动圈

一圈细线连接到麦克风膜。 随着膜片的振动,线圈也会在由强永磁体制成的铁芯的磁场中进行往复运动。 而且正如法拉第所建立的那样,线圈中会产生电流。

电信号遵循声波的形状。

因此,从线圈中感应的非常弱的电流中,获得了交变的电信号,从而重复了作用在麦克风膜片上的声波形式。 此外,该交流电压形式的信号从图2馈送到放大器的输入。 1。

扩音器

实际上,扬声器的工作原理重复了动态麦克风的装置,只是沿相反的方向打开。 (自然地,在这种情况下,绕组线要粗得多,而膜要大得多,以确保在放大信号下工作)




扬声器膜片的振动会影响空气颗粒并产生强大的声波。 这些波的形状正好重复了Vasya声带产生的强度低得多的声波的形状。 但是现在,新波的强度足以确保Vasya发出的声音振动到达站立的人的耳朵,即使是在大批人群的后排。

摇滚音乐会

到此时,您可能想知道,这与Java Sound API有什么关系? 但是,请稍等片刻,我们将带您进入混音器的基础知识。

上述电路非常简单。 它由Vasya Pupyrkin,一个麦克风,一个放大器和一个扬声器组成。 现在考虑图。 图4展示了为开始的音乐团体的摇滚音乐会准备的舞台。



六个麦克风和两个扬声器

在图 舞台上有4个6个麦克风。 舞台的两侧有两个扬声器(扬声器)。 音乐会开始时,表演者会用六个麦克风中的每个唱歌或播放音乐。 因此,我们将有六个电信号,必须将其分别放大,然后馈送到两个扬声器。 除此之外,表演者还可以使用各种声音特殊效果,例如混响,在将其应用于扬声器之前,还需要将其转换为电信号。

舞台两侧的两个扬声器旨在产生立体声效果。 即,来自位于舞台右侧的麦克风的电信号应落入也位于右侧的扬声器中。 类似地,从麦克风到左侧的信号应馈送到位于场景左侧的扬声器。 但是,来自更靠近舞台中央的其他麦克风的电信号应该已经以适当的比例传输到两个扬声器。 中间的两个麦克风应将其信号均等地传输到两个扬声器。

调音台

上面讨论的任务仅由称为音频混合器的电子设备执行。

音频线(声道)

尽管作者不是音频混音器的专家,但基于他的拙见,典型的音频混音器具有在输入端接收一定数量彼此独立的电信号的能力,每个电信号代表原始的声音信号或线路(通道)。

(当我们开始详细了解Java Sound API时,音频通道的概念将变得非常重要。

独立处理每个音频通道

在任何情况下,标准音频混音器都有能力独立于其他通道来放大每个音频线。 而且,混音器通常具有施加声音特殊效果的能力,例如对任何音频线进行混响。 最后,调音台,顾名思义,可以混合将要设置的输出通道中的所有单个电信号,从而控制每条音频线对输出通道的贡献。(这种控制通常称为pan或pan-空间分布)。

返回立体声

因此,在具有图的图中。 如图4所示,混音器的声音工程师能够组合来自六个麦克风的信号以获得两个输出信号,每个输出信号都传输到其扬声器。

为了成功操作,必须根据舞台上麦克风的物理位置,以适当比例提供来自每个麦克风的信号。 (通过改变声像,合格的声音工程师可以根据需要更改每个麦克风的声响,例如,如果主唱在音乐会期间在舞台上四处移动)。

是时候回到编程世界了

现在让我们从物理世界回到编程世界。 Sun表示: “ Java Sound不涉及任何特殊的硬件配置; 它旨在允许将各种音频组件安装在系统上,并通过API供用户使用。 Java Sound支持声卡的标准输入和输出功能(例如,用于记录和播放音频文件),以及混合多个音频流的功能”

混合器和通道

如前所述,Java Sound API建立在混合器和通道的概念上。 如果您从物理世界转到编程世界,那么Sun会写关于混合器的以下内容:

调音台是具有一个或多个通道的音频设备。 但是真正混合音频信号的混合器必须具有源信号源的多个输入通道和至少一个输出目标通道。”

输入行可以是带有SourceDataLine对象的类的实例,而输出行可以是TargetDataLine对象。 混音器还可以接收预录和循环播放的声音作为输入,将其输入源通道定义为实现Clip接口的类对象的实例。

通道线接口。

Sun从Line界面报告以下内容:“ Line是数字音频管道的元素,例如输入或输出音频端口,混音器或混音器的音频路径。 通过通道的音频数据可以是单声道或多声道(例如,立体声)。 ...通道可以具有控制,例如增益,声相和混响。”

将条款放在一起

因此,Sun的上述引用表示以下术语

源数据线
Targetgetataline
港口

控制项

图5显示了使用这些术语构建简单的音频输出程序的示例。



程序脚本

从软件角度 图5显示了通过一个Clip对象和两个SourceDataLine对象获得的Mixer对象。

什么是剪辑

剪辑是调音台输入端的对象,其内容不会随时间变化。 换句话说,您在播放音频数据之前将其加载到Clip对象中。 Clip对象的音频内容可以被播放一次或多次。 您可以环回剪辑,然后内容将一次又一次地播放。

输入流

另一方面,SourceDataLine对象是混合器输入处的流对象。 这种类型的对象可以接收音频数据流,并将其实时发送到混合器。 可以从各种来源获得必要的音频数据,例如音频文件,网络连接或内存缓冲区。

不同类型的渠道

因此,Clip和SourceDataLine对象可以视为Mixer对象的输入通道。 这些输入通道中的每一个都可以有自己的:声像,增益和混响。

播放音频内容

在这样一个简单的系统中,混音器从输入线路读取数据,使用控制来混合输入信号,并将输出提供给一个或多个输出通道,例如扬声器,线路输出,耳机插孔等。

清单11显示了一个简单的程序,该程序从麦克风端口捕获音频数据,将其存储在内存中,然后通过扬声器端口播放。

我们将只讨论捕获和回放。 上面的大多数程序都是为用户创建一个窗口和一个图形界面,以便可以控制记录和回放。 我们不会讨论超出目标的这一部分。 但是接下来我们将考虑数据的捕获和回放。 我们将在本课中讨论失败,然后在下一课中介绍。 在此过程中,我们将说明音频通道与Java Sound API的结合使用。

捕获的数据存储在ByteArrayOutputStream对象中。

代码段提供了从麦克风读取音频数据并将其存储为ByteArrayOutputStream对象的功能。

从清单1开始的名为playAudio的方法将播放捕获的音频数据并将其存储在ByteArrayOutputStream对象中。

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

清单1

我们从标准代码开始。

清单1中的程序片段实际上与Java Sound无关。

其目的是:

  • 将先前保存的数据转换为字节类型的数组。
  • 获取字节数据数组的输入流。

我们需要这样做才能使音频数据可供以后播放。

转到声音API

清单2中的代码行已经与Java Sound API相关。

  AudioFormat audioFormat = getAudioFormat(); 

清单2

在这里,我们简要讨论该主题,下一课将对此进行详细讨论。

两种独立格式

大多数情况下,我们正在处理两种独立的音频数据格式。

包含音频数据的文件格式(任何)(在我们的程序中尚未,因为数据存储在内存中)

提交的音频数据的格式本身。

什么是音频格式?

这是Sun关于它的内容:

每个数据通道都有自己的音频格式和数据流。 格式(AudioFormat的实例)确定音频流的字节顺序。 格式参数可以是通道数,采样频率,量化位,编码方法等。通常的编码方法可以是PCM及其变体的线性脉冲编码调制。”

字节顺序

源音频数据是二进制数据的字节序列。 您可以使用多种选项来组织和解释此序列。 我们不会开始详细讨论所有这些选项,但是我们将讨论程序中使用的音频格式。

小题外话

在这里,我们暂时保留playAudio方法,并查看清单2中的getAudioFormat方法。

清单3中显示了完整的getAudioFormat方法。

  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 

清单3

除了声明初始化变量之外,清单3中的代码还包含一个可执行表达式。

AudioFormat对象

getAudioFormat方法创建并返回AudioFormat类的对象的实例。 这是Sun关于此类的内容:

“ AudioFormat类定义音频流中数据的特定顺序。 转到AudioFormat对象的字段,您可以获得有关如何正确解释二进制数据流中的位的信息。”

我们使用最简单的构造函数

AudioFormat类有两种构造函数(我们将采用最简单的一种)。 此构造函数需要以下参数:

  • 每秒采样率或采样率(可用值:每秒8000、11025、16000、22050和44100个采样)
  • 数据的位深度(每个计数有8位和16位可用)
  • 声道数(单声道一个,立体声声道两个)
  • 流中使用的带符号或无符号数据(例如,值从0到255或从-127到+127变化)
  • 大端或小端的字节顺序。 (如果要传输16位值的字节流,则必须知道哪个字节先出现-低或高,因为这两个选项都很重要)。

如清单3所示,在本例中,我们将以下参数用于AudioFormat对象的实例。

  • 每秒8000个样本
  • 16个数据大小
  • 重要数据
  • 小端顺序

默认情况下,数据由线性PCM编码。

我们使用的构造函数使用线性脉冲编码调制和上面指示的参数创建了AudioFormat对象的实例(在以下课程中,我们将返回线性PCM和其他编码方法)

再次回到playAudio方法

现在,我们了解了音频数据格式如何以Java声音工作,让我们回到playAudio方法。 只要我们要播放可用的音频数据,就需要一个AudioInputStream类的对象。 我们在清单4中获得了它的一个实例。

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

清单4

AudioInputStream构造函数的参数

  • AudioInputStream类的构造函数需要以下三个参数:
  • AudioInputStream对象的实例所基于的流(为此我们看到,我们使用之前创建的ByteArrayInputStream对象的实例)
  • 此流的音频数据格式(为此,我们已经创建了AudioFormat对象的实例)
  • 此流中数据的帧大小(请参见以下说明)
  • 清单4中的代码清楚地表明了前两个参数。但是,第三个参数本身并不那么明显。

获取框架大小

从清单4中可以看到,第三个参数的值是使用计算创建的。 这只是我们之前未提到的音频格式的属性之一,称为帧。

什么是镜框?

对于我们程序中使用的简单线性PCM,帧包含给定时间所有通道的一组样本。

因此,帧大小等于以字节为单位的计数大小乘以通道数。

您可能已经猜到了,名为getFrameSize的方法返回以字节为单位的帧大小。

框架尺寸计算

因此,可以通过将音频数据序列中的字节总数除以一帧中的字节数来计算一帧中的音频数据的长度。 此计算用于清单4中的第三个参数。

获取SourceDataLine对象

我们将讨论的程序的下一部分是一个简单的音频输出系统。 从图5的图中可以看出,要解决此问题,我们需要一个SourceDataLine对象。

有几种方法可以获取SourceDataLine对象的实例,所有这些都非常棘手。 清单5中的代码检索并存储对SourceDataLine对象的实例的引用。

(请注意,此代码不仅实例化SourceDataLine对象,而且还以一种round回的方式获取它。)

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

清单5

什么是SourceDataLine对象?

关于此,Sun编写了以下内容:

“ SourceDataLine是可以写入数据的数据通道。 它用作混音器的输入。 应用程序将字节序列写入SourceDataLine,后者对数据进行缓冲并将其传递到其混合器。 混频器可以将其为下一级处理的数据传输到例如输出端口。

请注意,该配对的命名约定反映了通道及其混频器之间的关系。”

AudioSystem类的GetLine方法

获取SourceDataLine对象的实例的一种方法是从AudioSystem类调用静态getLine方法(在下一课中,我们将对此进行大量报告)。

getLine方法需要类型为Line.Info的输入参数,并返回与已经定义的Line.Info对象中的描述相匹配的Line对象。

另一个简短的题外话

Sun报告有关Line.Info对象的以下信息:

“通道具有自己的信息对象(Line.Info的实例),它显示哪个混合器(如果有)将混合的音频数据作为输出直接发送到该通道,以及哪个混合器(如果有的话)直接从该通道接收音频数据作为输入。 Line的各种可以对应Line.Info的子类,它允许您指定与特定类型的通道相关的其他类型的参数”

DataLine.Info对象

清单5中的第一个表达式创建DataLine.Info对象的新实例,这是Line.Info对象的一种特殊形式(子类)。

DataLine.Info类有几个重载的构造函数。 我们选择了最容易使用的。 该构造函数需要两个参数。

类对象

第一个参数是Class,它表示我们定义为SourceDataLine.class的类

第二个参数确定通道的所需数据格式。 我们为其使用了AudioFormat对象的实例,该实例已在前面定义。

我们已经在哪里了?

不幸的是,我们仍然没有最需要的SourceDataLine对象。 到目前为止,我们有一个仅提供有关所需SourceDataLine对象信息的对象。

获取SourceDataLine对象

清单5中的第二个表达式最终创建并存储了我们所需的SourceDataLine实例。 这是通过调用AudioSystem类的静态方法getLine并将dataLineInfo作为参数传递而发生的。 (在下一课中,我们将研究如何直接与Mixer对象一起使用Line对象)。

getLine方法返回对Line类型对象的引用,该对象是SourceDataLine的父级。 因此,在这里需要向下转换,然后再将返回值另存为SourceDataLine。

准备使用SourceDataLine对象

一旦获得SourceDataLine对象的实例,就需要为打开和运行它做准备,如清单6所示。

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

清单6

开启方式

从清单6中可以看到,我们将AudioFormat对象发送到SourceDataLine对象的open方法。

根据Sun的说法,这是一种方法:

“以先前定义的格式打开一条线路(通道),使他可以接收他需要的任何系统资源并且处于工作(工作)状态”

发现状态

Sun在此主题中只写了一些关于他的文章。

“打开和关闭通道会影响系统资源的分配。 成功打开通道可确保将所有必要的资源提供给通道。

混音器的开口(其具有音频数据的输入和输出端口)除其他外,包括使用平台的硬件,在该平台上进行必要的软件组件的工作和初始化。

打开一个通道,该通道是音频数据进出混音器的路径,包括初始化它和接收无限的混音器资源。 换句话说,混频器具有有限数量的通道,因此几个具有各自通道需求的应用程序(有时甚至是一个应用程序)必须正确共享混频器资源)”

在通道上调用start方法

根据Sun的说法,调用通道的start方法意味着:

“该通道允许使用I / O线。 如果尝试使用已经运行的线路,则该方法不执行任何操作。 但是,在数据缓冲区为空之后,该行从缓冲区完全加载后无法管理的第一帧开始,重新开始I / O。”

当然,在我们的情况下,通道并没有停止。 自从我们首次推出以来。

现在我们几乎有了所需的一切

至此,我们收到了播放先前记录并存储在ByteArrayOutputStream对象实例中的音频数据所需的所有音频资源。 (请记住,该对象仅存在于计算机的RAM中)。

我们开始流动

我们将创建并启动流以播放音频。 清单7中的代码创建并启动了该线程。

(不要将此线程中对start方法的调用与清单6中SourceDataLine对象中对start方法的调用相混淆。这是完全不同的操作)

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

清单7

朴实的代码

清单7中的程序片段虽然非常简单,但却显示了Java中的多线程编程示例。 如果您不理解它,则应该在学习Java的专门主题中熟悉本主题。

流启动后,它将一直工作到所有预录制的音频数据都播放完为止。

新线程对象

清单7中的代码创建了PlayThread类的Thread对象的实例。 此类在我们的程序中定义为内部类。 其描述始于清单8。

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

清单8

Thread类中的run方法

除了声明tempBuffer变量(指的是字节数组)以外,此类的完整定义只是run方法的定义。 如您所知,在Thread对象上调用start方法会使该对象的run方法执行

此线程的run方法从清单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 

清单9

run方法中程序片段的第一部分

run方法包含两个重要部分,清单9中显示了第一部分。

总而言之,这里使用循环从AudioInputStream读取音频数据并将其传递到SourceDataLine。

发送到SourceDataLine对象的数据将自动传输到默认音频输出。 它可以是内置计算机扬声器或线路输出。 (在接下来的课程中,我们将学习确定必要的声音设备)。 cnt变量和tempBuffer数据缓冲区用于控制读写操作之间的数据流。

从AudioInputStream读取数据

AudioInputStream对象的读取周期从AudioInputStream读取指定的最大字节数据数,并将其字节数组放置。

返回值

此外,如果已到达记录序列的末尾,则此方法返回读取的字节总数或-1。 读取的字节数存储在cnt变量中。

SourceDataLine写循环

如果读取的字节数大于零,则过渡到将数据写入SourceDataLine的周期。 在此循环中,音频数据进入混频器。 根据字节的索引从字节数组中读取字节,并将其写入通道缓冲区。

当输入流干燥时

当读取循环返回-1时,这意味着所有先前记录的音频数据都已结束,并且进一步的控制权传递给清单10中的程序片段。

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

清单10

锁定并等待

清单10中的代码调用SourceDataLine对象上的rain方法,以便程序可以阻塞并等待内部缓冲区在SourceDataLine中为空。 当缓冲区为空时,这意味着整个下一部分将被传送到计算机的声音输出。

关闭SourceDataLine

然后,程序调用close方法关闭通道,从而表明该通道使用的所有系统资源现在都是空闲的。 Sun报告了以下通道关闭:

关闭频道表示该频道涉及的所有资源都可以释放。 为了释放资源,应用程序必须关闭通道,无论它们是否已经使用,以及应用程序何时结束。 假定混合器共享系统资源,并且可以重复关闭和打开。 其他通道关闭后可能支持也可能不支持重新打开。 通常,打开线的机制根据不同的子类型而有所不同。”

现在故事的结局

因此,在这里我们对程序如何使用Java Sound API进行了说明,以确保音频数据从计算机的内部存储器传递到声卡。

运行程序

现在,您可以编译并运行清单11中的程序,这将结束本课。

捕获和播放音频数据

该程序演示了从麦克风记录数据并通过计算机的声卡播放数据的能力。 使用说明非常简单。

运行程序。 如图6所示的简单GUI GUI应该出现在屏幕上。



  • 单击捕获按钮,然后将所有声音记录到麦克风。
  • 单击停止按钮停止录制。
  • 单击“播放”按钮以通过计算机的声音输出播放录音。

如果您听不到任何声音,请尝试增加麦克风的灵敏度或扬声器的音量。

该程序将记录保存在计算机内存中,因此请小心。 如果尝试保存过多的音频数据,则可能会用完RAM。

结论

  • 我们发现Java Sound API基于通道和混合器的概念。
  • 我们获得了有关模拟声音的物理和电气特性的初始信息,以便随后了解混音器的设备。
  • 我们使用了一个业余摇滚音乐会场景,该场景使用六个麦克风和两个立体声扬声器来描述使用混音器的可能性。
  • 我们讨论了许多Java Sound编程主题,包括混合器,通道,数据格式等。
  • 我们在一个用于输出音频数据的简单程序中解释了对象SourceDataLine,Clip,Mixer,AudioFormat与端口之间的一般关系。
  • 我们熟悉了一个程序,该程序使我们可以首先记录然后播放音频数据。
  • 我们收到了有关播放以前存储在计算机内存中的音频数据的代码的详细说明。

接下来是什么?

在本教程中,我们发现Java Sound API基于混合器和通道的概念。 但是,我们讨论的代码没有明确包含混合器。 AudioSystem类为我们提供了静态方法,使您无需直接访问混音器就可以编写音频处理程序。 换句话说,这些静态方法使调音台远离我们。

在下一课中,我们将提供与本课相比的修改后的数据捕获代码。新版本将明确使用混音器,向您展示在真正需要它们时如何使用它们。

 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 

清单11

Source: https://habr.com/ru/post/zh-CN434424/


All Articles