当前位置:天才代写 > tutorial > JAVA 教程 > 用Java实现网络语音信号传送

用Java实现网络语音信号传送

2017-11-11 08:00 星期六 所属: JAVA 教程 浏览:571

副标题#e#

一、引言

Java是一门适合于漫衍式计较情况、尤其是Internet措施设计的语言。这不只仅在于java具有很好的安详性和可移植性,还在于java为Internet编程提供了富厚的网络类库的支持。操作这些网络类库,可以轻松编写多种范例的网络通信措施。然而由于某些限制,Java在传输多媒体信息方面的应用不是很广,大部门的应用都会合在网络上传输语音等音频信号的方面。传输音频信号应用方案一般有两种,一是应用于数据广播的多对一传输,譬喻音频数据处事器向数个客户端发送音频数据信号,其最遍及的应用则是某些网上的IP电话,各人常常可以看到不少这种提供在线IP电话处事的网站的客户端都是利用的嵌在网页上的Java Applet措施,用来实现拨号、通话等等根基的网络电话成果; 第二种方案则是我们本日要涉及的部门,一对一的音频信号数据的传输。这种方案的应用范畴更广。各人都去过语音谈天室,大部门的语音谈天室的语音谈天成果的实现就是利用的Java技能,各人对这样网页的源代码阐明一下就可以发明这一点。

我曾开拓一个项目,涉及利用java来实此刻网络上传输语音数据。开拓中碰着不少问题,并且在互联网上发明关于java语音传输的资料较量少,寻找了很多天,最终从一个开放源代码的一个简朴的Answer Machine 演示措施中得到了办理问题的要领。本日我就把我在点对点传输音频信号方面的一些履历拿出来,与各人配合探讨这方面的问题。

二、存在的问题

在网络上传输音频的方面存在的问题主要可以归纳为以下几点:

1 两边之间的网络毗连

要举办频数据的传输,首先就是要成立数据连结。常用的通讯协议中,TCP较靠得住,所以用在不答允数据丢失的应用上。而UDP则较多应用于处理惩罚速度要求较快、数据传输靠得住性要求不是很高的应用上,如数据广播。通信协议的选择取决于我们所要做的应用的范例。奈何成立网络毗连,不变的吸收和发送音频信号的数据流是要害。

2 音频信号的收罗以及回放

在举办音频信号的收罗中我们必需思量到采样率的问题,声音信号的采样率有8Khz、16Khz、32Khz、44Khz等,每种数据采样虑发生的数据量都纷歧样,越高的采样率发生的数据量越大,所以我们要选择符合的采样率以适应网络的带宽。

3 音频数字信号的编码与解码。

假如把直吸收罗到的音频信号数据流在网络长举办传输,它所占有的带宽也是十分大的,以8Khz的采样率收罗14位的音频数据那么就有以下这样的一个式子:

14 bit * 8000/second=112,000 bits/second or112kbps

从中我们可以看出以这样的方法传输音频数据,每秒需要向网络中发送112kb的数据。所以。从节减带宽的角度思量,我们很有须要对这样的数据举办压缩。对多媒体信号的压缩我们有很多可以选择的名目,如mp2、mp3、GSM等等。同样,我们这里也存在一个对压缩名目举办选择的问题,思量到音频数据传输的实时性,对传输的音频数据质量的要求,以及各类压缩名目标压缩比率以及举办压缩息争压缩所要淹灭的系统资源等方面问题,选择符合的压缩名目就显得尤为重要。


#p#副标题#e#

三、办理的要领

下面就针对前面提出的问题接头一下办理的步伐。

1 两边之间的网络毗连

Java在这方面有其奇特的优势,Java提供了富厚的网络类库的支持,可以轻松编写多种范例的网络通信措施。在我下面的例子中我就利用了TCP/IP协议,通过Java的Socket类举办编程。

2 音频信号的收罗和回放以及音频数字信号的编码与解码

在办理这两个问题的时候,在网上很幸运地通过一些文章的先容,找到了Answer Machine 演示措施的源代码(由of jsresources.org的Florian Bomers 和Matthias Pfisterer编写,网址http://www.jsresources.org/apps/am.html)。在这个措施代码中,有几个办理我们问题所需要的类,并且作者将这些类封装的很好,我们根基不需要做什么窜改,只需要屏蔽个中的调试信息的输出就行了,更难堪的是它还封装了几种常见的音频名目。个中的GSM名目(Global System for Mobile Telecommunications)就是我们下面例子中回收的压缩名目,GSM名目可以将128kbps 的音频数据流 (16bit通过8k Hz的音频采样) 压缩为13kbps 的音频数据流,很是适合语音信号的传送,所以可谓是一石二鸟。

我阐明过这几个类的源代码,不得不服气它的作者,每个类的源代码都很精辟,各人可以本身阐明一下。好了下面就给各人讲讲这几个类,而且将它们用到的Java Sound API中的类和函数等一并做个简朴先容,让各人对Java Sound API中常用的类也有个大抵的相识。由于Java Sound API中的类较量多。限于篇幅无法对所有用到的类做详尽的表明,以下内容只是简朴提及了各个类的用途和利用类型,有关Java Sound API中类的详细先容请各人会见这里http://java.sun.com/j2se/1.4.2/docs/api/, 查找javax.sound.sampled的相关内容。

以下的提到几个文件是从Answer Machine 演示措施的源代码中提取出来的,由于是开放源代码的措施,各人在利用的时候请留意相关的民众协议。

① AMAudioFormat类(封装在AMAudioFormat.java文件中)

#p#分页标题#e#

AMAudioFormat类封装了CD、FM、TELEPHONE、GSM这四种质量的音频名目标参数,利用起来也很是简朴,这样我们在利用Java Sound API时就不消本身去写那些巨大的代码了,但为了大白Java Sound API的道理,我们需要对它的代码做一下阐明。它利用了Java Sound API中的AudioFormat这个类,这个类很是重要,在Java中对任何音频数据的利用都要实现通过它指定所需要利用的音频名目,AudioFormat类有一个嵌套的类AudioFormat.Encoding,实际上大部门对AudioFormat类的利用都是利用的这个嵌套的类。

AMAudioFormat类的重要要领:

名称:getLineAudioFormat

挪用名目:getLineAudioFormat(整型音频名目代号)

返回值: 按照通报音频名目代号生成的AudioFormat工具。

说道这里各人大概要问了,那么通过Java Sound API可以直接利用GSM名目吗?谜底是较量巨大,但同样有办理的步伐,作者在这里利用了别的的开源措施的类库-tritonus的GSM编码解码库。各人需要在这里www.tritonus.org/plugins.html下载tritonous_share.jar和tritonus_gsm.jar两个文件,并在AMAudioFormat类中引用,这样就完成了GSM名目标配置。需要汇报各人的是在对AMAudioFormat.java这个类举办编译后,我们的措施运行的时候就可以不需要tritonous_share.jar和tritonus_gsm.jar这两个文件的支持了。

#p#副标题#e#

② AudioCapture类(封装在AudioCapture.java文件中)

AudioCapture类封装了从音频硬件捕捉音频数据并自动编码为GSM音频压缩数据的进程,而且通过它的getAudioInputStream()要领提供应我们一个音频数据输入流,我们就可以直接将这个流发送到网络中。

AudioCapture 类的重要要领:

名称:getAudioInputStream

挪用名目:getAudioInputStream()

返回值:AudioInputStream工具

AudioCapture 类利用了Java Sound API中的AudioInputStream、AudioFormat、AudioSystem这几个类和TargetDataLine、LineListener接口。除了AudioFormat类我再简朴先容一下其他的类:

AudioInputStream 类是带有非凡音频名目和长度的InputStream类,它有两个结构要领,别离是AudioInputStream(InputStream stream, AudioFormat format,long length)和AudioInputStream(TargetData -Line line)。

TargetDataLine 接口是DataLine接口的一种,通过它就可以直接从音频硬件获取数据了,它有几个常用的要领,别离是:open(AudioFormat format)、void open(AudioFormat format, int bufferSize)、int read(byte[] b, int off, int len)。

AudioSystem 类是Java尺度音频系统的进口点,在AudioSystem 类中利用他的getLine() 要领建设TargetDataLine工具。

LineListener接口用来对线路状态改变的时间举办监听,他的重要的要领是update(LineEvent event)要领。

③ AudioPlayStream类(封装在AudioPlayStream.java文件中)

AudioPlayStream类与AudioCapture类恰好相反,它封装了GSM压缩音频数据的解码和音频信号的回放进程,提供应我们一个音频信号输出流。AudioCapture类用到的Java Sound API中的类它也根基都用到了,只是它利用了SourceDataLine接口而不是TargetDataLine接口

④ Debug类(封装在Debug.java文件中)

Debug类主要用来在调试时输出讯息,代码很少,厥后我把个中输出信息的语句都屏蔽了,对措施运行没有影响。

为了利便利用以上的几个类,我们需要对它们举办编译和打包,编译时需要配置相关的编译情况,以下是我们需要用到的呼吁行

set CLASSPATH=%CLASSPATH%;.;tritonus_gsm.jar;tritonus_share.jar
javac am\*.java am\audio\*.java
jar cmf packaging\manifest.mf am.jar am\*.class
am\audio\*.class

说明一下,我将以上提到的Java源码文件放在了am目次下,编译之后可以获得一个8k的am.jar文件,我们下一步所需要做的就是在我们的措施中引用这个包。

#p#副标题#e#

四、实例先容

有了以上的根基的先容,我就可以通过对我写的一个极为简朴的语音对讲软件代码的表明让各人更清楚地相识一下这几个模块的详细利用要领,各人可以从中得到开拓具有诸如网络电话、自动应答等成果的软件的雷同要领,用于语音数据的传输。

措施的布局:

整个措施分三层,浸染别离如下:

. 顶层: 用户界面

. 中间层: 节制层

. 底层: 传输层

#p#分页标题#e#

措施有两个主要的类: (表)

类名 描写
CallLink 网络传输层,用于吸收或发送音频数据。
VoiceSender 作为第二个启动的线程提供从音频硬件捕捉并编码好的数据给网络传输层。

措施的主类jphone利用了Runnable和ActionListener接口,主类除了根基的几个要领之外,还具有要领initAudioHardware()、ShowMSG、startPhone别离用于初始化AudioCapture类与AudioPlayStream类、显示措施状态和开始措施。主类jphone具有两个子类VoiceSender和CallLink。

子类VoiceSender同样利用了Runnable接口,它在措施中作为第二个启动的线程认真发送捕捉到的音频数据。CallLink子类就是认真成立scoket毗连,而且认真吸收或发送网络数据、监听网络毗连等成果的实现。它具有主要的要领是getInputStream()、getOutputStream()、listen()、open()、close()等。

为了让各人更清楚的相识措施的布局请各人看下面的类图。

用Java实现网络语音信号传送

措施的根基事情流程:

当措施启动时首先执行成立当前主类的实例,当按下呼唤按钮的时候执行startPhone()要领,startPhone()要领通过挪用initAudioHardware()要领成立AudioCapture工具和AudioPlayStream工具的实例PhoneMIC和PhoneSPK, 紧接着在成立CallLink子类的实例curCallLink来与具有方针IP地点的计较机举办scoket毗连后,startPhone()要领又将子类VoiceSender作为secondThread线程启动,然后又挪用run()要领。 run()要领通过已经成立的CallLink子类的实例curCallLink监听网络上的数据(也就是期待别人的呼唤),一旦有音频数据到来curCallLink 实例就为AudioPlayStream 工具PhoneSPK 提供网络传来的音频数据,而PhoneSPK在一个轮回中不绝的将音频数据转换为音频信号,完成雷同电话听筒的成果。

子类VoiceSender 就作为第二线程启动的时候,startPhone() 要领通报给它的参数是实例化的CallLink 子类curCallLink , 子类VoiceSender 通过实例化的AudioCapture 工具PhoneMIC 将音频信号压缩成GSM数据,并通过curCallLink 将音频数据发送到具有方针IP 地点的计较机上,完成雷同电话受话器的成果。

在这里实例化的CallLink 子类curCallLink 就相当于两个电话之间的电话线,这样通过我以上的表明各人对措施的道理就有一个或许的相识了吧。

个中的音频数据发送线程和音频数据吸收线程是同步的,不外思量到网络的因素,大概在声音的传输上有一些延迟,不外由于延迟较量小对实时听到对方的话语影响不大。

用Java实现网络语音信号传送

#p#副标题#e#

编写代码摘要:

在利用AudioCapture 类和AudioPlayStream 类的要领之前需要知道奈何初始化这两个类。在声明AudioCapture 工具的时候需要通报给它一个静态的整型值用于表达将音频信号压缩的方法,这个静态的整型常量可以是AMAudioFormat 类的以下四个值之一: FORMAT_CODE_CD 、FORMAT_CODE_FM 、FORMAT

_CODE_TELEPHONE 、FORMAT_CODE_GSM 。所以声明AudioCapture 工具就要用一下的形式:

private AudioCapture PhoneMIC null;
PhoneMIC new AudioCapture(AMAudioFormat.
FORMAT_CODE_GSM);

而声明AudioPlayStream 工具则差异,我们在初始化它的时候需要通报给它一个AudioFormat 工具,用于通知它我们所要播放音频的名目,这个AudioFormat 工具可以通过AMAudioFormat 类的getLineAudioFormat(名目参数值)要领得到,个中名目参数的取值和上面提到过的AMAudioFormat 的四个值沟通,所以声明AudioPlayStream 工具就要用以下的形式:

private AudioPlayStream PhoneSPK null;
AudioFormat format null;
format AMAudioFormat.getLineAudioFormat
(AMAudioFormat.FORMAT_CODE_GSM);
PhoneSPK new AudioPlayStream(format);

在这之后就可以利用AudioCapture 和AudioPlayStream 工具的open() 要领打开音频捕捉和音频回放通道完成它们的初始化了。—www.bianceng.cn。如以下的形式:

PhoneMIC.open();
PhoneSPK.open();

初始化完成之后要使AudioPlayStream 工具播放声音还需要以下进程,首先成立一个缓冲区(字节数组)用于存放从网络传来的音频数据流,然后执行AudioPlayStream 工具的start() 要领使AudioPlayStream

#p#分页标题#e#

工具开始声音的回放,这时执行一个while 轮回,在轮回中将音频流数据写入缓冲区,再利用AudioPlayStream工具的write()要领将缓冲区的数据还原成语音信号然后播放出来。如下面的例子:

boolean complete false;
byte[] gsmdata new byte[bufSize];
int numBytesRead 0;
......
PhoneSPK.start();
......
complete false;
while((!Thread.currentThread().interrupted()) )
{
 try
 {
  numBytesReadplaybackInputStream.read(gsmdata);
  if(numBytesRead == -1)
  {
   complete=true;
   break;
  }
  PhoneSPK.write(gsmdata, 0, numBytesRead);
 }
 catch (IOException e)
 {
  System.exit(1);
 }
}

#p#副标题#e#

个中complete 的值用于符号终止声音播放的异常原因。

雷同的,初始化完成之后要使AudioCapture 工具捕捉和压缩声音数据还需要其他的操纵,首先声明一个InputStream 工具,赋其值为AudioCapture 工具的getAudioInputStream() 要领的返回值,执行

AudioCapture 工具的start() 要领,然后在成立一个轮回,将通过InputStream 的read() 要领获得的数据发送到网络上。譬喻以下代码:

InputStream myIStream null;
myIStream PhoneMIC.getAudioInputStream();
......
while((!Thread.currentThread().interrupted()))
b = myIStream.read(compressedVoice,0, bufSize);
sendStream.write(compressedVoice,0,b);
......

通过利用CallLink 的几个要领,我们可以利便的传输和吸收音频数据流。以下是它的代码:

class CallLink
//利用套接字举办毗连
String ipAddr null;
Socket outSock = null;
ServerSocket inServSock null;
Socket inSock null;
CallLink(String inIP)
ipAddr inIP;
void open() throws IOException, UnknownHostException
{//打开网路毗连
if (ipAddr != null)
outSock=new Socket(ipAddr,TALK_PORT);
}
void listen() throws IOException
{// 监听,等待呼唤
inServSock new ServerSocket(TALK_PORT);
inSock inServSock.accept();
}
public InputStream getInputStream()throws IOException
{//返覆信频数据输入流
if (inSock != null)
return inSock.getInputStream();
else
return null;
}
publicOutputStreamgetOutputStream()throwsIOException
{//返覆信频数据输出流
if (outSock != null)
return outSock.getOutputStream();
else
return null;
}
void close() throws IOException
{//封锁网络毗连
inSock.close();
outSock.close();
}

措施的代码总体有366 行,限于篇幅,这里就纷歧一罗列了。

编译以及措施的利用要领:

运行和编译本措施需要加上特另外情况参数,为了利便利用可以成立以下内容的批处理惩罚文件:(假设措施所需要的包均在措施地址目次下的lib 文件夹中)

用于编译的批处理惩罚措施c.bat 的内容

javac -classpath .;lib\am.jar jphone.java

用于运行的批处理惩罚措施r.bat 的内容

java -classpath .;lib\am.jar jphone

启动时在A 计较机的IP 地点框内输入要举办毗连的计较机B 的IP 地点,在计较机B 的IP 地点框内输入要举办毗连的计较机A 的IP 地点,让后别离点击“拨出电话”按钮就可以举办毗连了。虽然别忘了接上麦克风和打开音箱电源,呵呵。

提醒各人,这里的IP 地点栏里预先存在的地点是127.0.0.1,也就是说,措施也可以和本身举办毗连,点击“拨出电话”按钮,等8 秒阁下敲敲你的麦克风,听到没有,是不是也有“嘣、嘣、嘣”的声音?

 

    关键字:

天才代写-代写联系方式