ad_qqread_mid_big">四、实例介绍
有了以上的基本的介绍,我就可以通过对我写的一个极为简单的语音对讲软件代码的解释让大家更清楚地了解一下这几个模块的具体使用方法,大家可以从中获得开发具有诸如网络电话、自动应答等功能的软件的类似方法,用于语音数据的传输。
程序的结构:
整个程序分三层,作用分别如下:
. 顶层: 用户界面
. 中间层: 控制层
. 底层: 传输层
程序有两个主要的类: (表)
| 类名 | 描述 | | calllink | 网络传输层,用于接收或发送音频数据。 | | voicesender | 作为第二个启动的线程提供从音频硬件捕获并编码好的数据给网络传输层。 |
程序的主类jphone使用了runnable和actionlistener接口,主类除了基本的几个方法之外,还具有方法initaudiohardware()、showmsg、startphone分别用于初始化audiocapture类与audioplaystream类、显示程序状态和开始程序。主类jphone具有两个子类voicesender和calllink。
子类voicesender同样使用了runnable接口,它在程序中作为第二个启动的线程负责发送捕获到的音频数据。calllink子类就是负责建立scoket连接,并且负责接收或发送网络数据、监听网络连接等功能的实现。它具有主要的方法是getinputstream()、getoutputstream()、listen()、open()、close()等。
为了让大家更清楚的了解程序的结构请大家看下面的类图。

程序的基本工作流程:
当程序启动时首先执行建立当前主类的实例,当按下呼叫按钮的时候执行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 就相当于两个电话之间的电话线,这样通过我以上的解释大家对程序的原理就有一个大概的了解了吧。
其中的音频数据发送线程和音频数据接收线程是同步的,不过考虑到网络的因素,可能在声音的传输上有一些延迟,不过由于延迟比较小对及时听到对方的话语影响不大。

编写代码摘要:
在使用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() 方法打开音频捕获和音频回放通道完成它们的初始化了。如以下的形式:
phonemic.open(); phonespk.open(); |
初始化完成之后要使audioplaystream 对象播放声音还需要以下过程,首先建立一个缓冲区(字节数组)用于存放从网络传来的音频数据流,然后执行audioplaystream 对象的start() 方法使audioplaystream 对象开始声音的回放,这时执行一个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); } } |
其中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 秒左右敲敲你的麦克风,听到没有,是不是也有“嘣、嘣、嘣”的声音?
|