1.语音采集:客户端程序,调用音频驱动,实时采集”麦克风(mic)”的语音数据,保存到内存中。
2. 语音传输:将内存中语音数据,通过TCP/IP协议传输到服务器端。
3. 语音播放:服务器接收音频数据,并实时播放。
4. 设计可操作图形界面
5. 作为可选的扩充功能1,实现端与端之间的双向通信。
ps:服务端界面显示了本地的IP地址。
客户端和服务的都有保存音频文件输入框
1。服务端代码:
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField; /*
* 双向通信的服务端.
*/
public class Server extends JFrame implements ActionListener {
private static String path;//接收文件的路径
private static String savepath;//保存文件的路径
JPanel jp1, jp2, jp3,jp4;
JLabel jl1 = null;
JButton captureBtn, stopBtn, playBtn, saveBtn,sendBtn,setButton;
//连接信息和录音信息
JLabel linkLabel=null;
JLabel rdLabel=null;
JLabel ipLabel=null;
//路径信息
JTextField repath,svpath;
//定义初始音频类
audioUtils server=null;
//获取本地IP
InetAddress ia=null;
Socket s=null;
// 构造函数
public Server() throws UnknownHostException {
// 组件初始化
jp1 = new JPanel();
jp2 = new JPanel();
jp3 = new JPanel();
jp4=new JPanel();
//文本框初始化
repath=new JTextField("输入接收录音文件保存的路径");
svpath=new JTextField("输入要保存的录音文件路径");
//定义连接和录音提示
linkLabel=new JLabel("未连接");
rdLabel=new JLabel("");
ipLabel=new JLabel("本地IP:"+ia.getLocalHost().getHostAddress());
// 定义字体
Font myFont = new Font("宋体", Font.BOLD, 30);
jl1 = new JLabel("实时通信系统服务端");
jl1.setFont(myFont);
captureBtn = new JButton("开始录音");
// 对开始录音按钮进行注册监听
captureBtn.addActionListener(this);
captureBtn.setActionCommand("captureBtn");
// 对停止录音进行注册监听
stopBtn = new JButton("停止录音");
stopBtn.addActionListener(this);
stopBtn.setActionCommand("stopBtn");
// 对播放录音进行注册监听
playBtn = new JButton("播放录音");
playBtn.addActionListener(this);
playBtn.setActionCommand("playBtn");
// 对保存录音进行注册监听
saveBtn = new JButton("保存录音");
saveBtn.addActionListener(this);
saveBtn.setActionCommand("saveBtn");
//对发送录音进行注册监听
sendBtn=new JButton("发送录音");
sendBtn.addActionListener(this);
sendBtn.setActionCommand("sendBtn");
//对设置ip,路径等监听
setButton=new JButton("保存设置");
setButton.addActionListener(this);
setButton.setActionCommand("setButton");
this.add(jp1, BorderLayout.NORTH);
this.add(jp2, BorderLayout.CENTER);
this.add(jp3, BorderLayout.SOUTH);
this.add(jp4,BorderLayout.NORTH);
jp3.setLayout(null);
jp3.setLayout(new GridLayout(1, 4, 10, 10));
jp2.add(rdLabel);
jp2.add(linkLabel);
jp2.add(ipLabel);
jp3.add(captureBtn);
jp3.add(stopBtn);
jp3.add(playBtn);
jp3.add(saveBtn);
jp3.add(sendBtn);
jp4.add(svpath);
jp4.add(repath);
jp4.add(setButton);
// 设置按钮的属性
this.captureBtn.setEnabled(false);
this.stopBtn.setEnabled(false);
this.playBtn.setEnabled(false);
this.saveBtn.setEnabled(false);
this.sendBtn.setEnabled(false);
// 设置窗口的属性
this.setSize(700, 500);
this.setTitle("服务端");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setVisible(true);
try {
server=new audioUtils();
//等待连接
ServerSocket ss=new ServerSocket(8888);
//阻塞方法,一直等待客户端接入
while(true){
//监听连接
s=ss.accept();
JOptionPane.showMessageDialog(null, "连接成功....");
System.out.println("已经连接");
linkLabel.setText("已连接:");
//开启一个接收文件的线程
server.recied(s, new File(path));
}
}
catch (Exception e) {
e.printStackTrace();
linkLabel.setText("未连接:");
}
}
//事件
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("captureBtn")) {
// 点击开始录音按钮后的动作
// 停止按钮可以启动
captureBtn.setEnabled(false);
stopBtn.setEnabled(true);
playBtn.setEnabled(false);
saveBtn.setEnabled(false);
sendBtn.setEnabled(false);
// 调用录音的方法
capture();
rdLabel.setText("正在录音..");
} else if (e.getActionCommand().equals("stopBtn")) {
// 点击停止录音按钮的动作
captureBtn.setEnabled(true);
stopBtn.setEnabled(false);
playBtn.setEnabled(true);
saveBtn.setEnabled(true);
if(s!=null)
sendBtn.setEnabled(true);
// 调用停止录音的方法
stop();
rdLabel.setText("完成录音..");
}
else if (e.getActionCommand().equals("playBtn")) {
// 调用播放录音的方法
play();
} else if (e.getActionCommand().equals("saveBtn")) {
// 调用保存录音的方法
rdLabel.setText("保存录音...");
save();
}else if(e.getActionCommand().equals("sendBtn")){
//调用发送录音的方法
rdLabel.setText("发送录音");
send();
}else if(e.getActionCommand().equals("setButton")){
path=repath.getText().trim();
savepath=svpath.getText().trim();
if(new File(savepath).isDirectory())
{
savepath+="/"+System.currentTimeMillis()+".mp3";
this.captureBtn.setEnabled(true);
}else{
svpath.setText("文件路径不存在!");
return ;
}
if(new File(path).isDirectory())
{
path+="/recied.mp3";
}else{
repath.setText("文件路径不存在!");
return ;
}
this.setButton.setEnabled(false);
JOptionPane.showMessageDialog(null, "设置成功");
}
}
//开始录音
public void capture() //录音代码
{ server.capture();
}
// 停止录音
public void stop() {
server.stop();
}
// 播放录音
public void play(){
server.play();
} // 保存录音
public void save() {
// 取得录音输入流
server.save(new File(savepath));
}
//发送录音到客户端(不保存到本地文件的发送)
public void send()
{
server.send();
}
public static void main(String []args){
try {
Server ss=new Server();
} catch (UnknownHostException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
2.客户端代码:
import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.*;
import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.regex.Matcher;
import java.util.regex.Pattern; public class Client extends JFrame implements ActionListener {
private static String IP;//服务端的IP地址
private static int port=8888;//端口号
private static String path;//接收文件的路径
private static String savepath;//保存文件的路径
//定义音频类
audioUtils client=null;
// 定义所需要的组件
JPanel jp1, jp2, jp3,jp4;
JLabel jl1 = null;
JButton captureBtn, stopBtn, playBtn, saveBtn,sendBtn,linkBtn,delinkBtn,setButton;
JTextField iptxt,repath,svpath;
JLabel linkLabel=null;
JLabel rdLabel=null;
private Socket s;
// 构造函数
public Client() {
//音频类初始化
client=new audioUtils();
//文本框初始化
iptxt=new JTextField("输入要连接的IP地址");
repath=new JTextField("输入接收录音文件保存的路径");
svpath=new JTextField("输入要保存的录音文件路径");
// 组件初始化
jp1 = new JPanel();
jp2 = new JPanel();
jp3 = new JPanel();
jp4=new JPanel();
//定义连接和录音提示
linkLabel=new JLabel("未连接");
rdLabel=new JLabel("");
// 定义字体
Font myFont = new Font("宋体", Font.BOLD, 30);
jl1 = new JLabel("实时通信系统客户端");
jl1.setFont(myFont);
captureBtn = new JButton("开始录音");
// 对开始录音按钮进行注册监听
captureBtn.addActionListener(this);
captureBtn.setActionCommand("captureBtn");
// 对停止录音进行注册监听
stopBtn = new JButton("停止录音");
stopBtn.addActionListener(this);
stopBtn.setActionCommand("stopBtn");
// 对播放录音进行注册监听
playBtn = new JButton("播放录音");
playBtn.addActionListener(this);
playBtn.setActionCommand("playBtn");
// 对保存录音进行注册监听
saveBtn = new JButton("保存录音");
saveBtn.addActionListener(this);
saveBtn.setActionCommand("saveBtn");
//对发送录音进行注册监听
sendBtn=new JButton("发送录音");
sendBtn.addActionListener(this);
sendBtn.setActionCommand("sendBtn");
//对连接录音进行注册监听
linkBtn=new JButton("连接");
linkBtn.addActionListener(this);
linkBtn.setActionCommand("linkBtn");
//对断开连接进行注册监听
delinkBtn=new JButton("断开连接");
delinkBtn.addActionListener(this);
delinkBtn.setActionCommand("delinkBtn");
//对设置ip,路径等监听
setButton=new JButton("保存设置");
setButton.addActionListener(this);
setButton.setActionCommand("setButton");
this.add(jp1, BorderLayout.NORTH);
this.add(jp2, BorderLayout.CENTER);
this.add(jp3, BorderLayout.SOUTH);
this.add(jp4,BorderLayout.NORTH);
jp3.setLayout(null);
jp3.setLayout(new GridLayout(1, 4, 10, 10));
jp4.add(iptxt);
jp4.add(svpath);
jp4.add(repath);
jp4.add(setButton);
jp2.add(rdLabel);
jp2.add(linkBtn);
jp2.add(delinkBtn);
jp2.add(linkLabel);
jp3.add(captureBtn);
jp3.add(stopBtn);
jp3.add(playBtn);
jp3.add(saveBtn);
jp3.add(sendBtn);
// 设置按钮的属性
this.linkBtn.setEnabled(false);
this.delinkBtn.setEnabled(false);
this.captureBtn.setEnabled(false);
this.stopBtn.setEnabled(false);
this.playBtn.setEnabled(false);
this.saveBtn.setEnabled(false);
this.sendBtn.setEnabled(false);
this.setButton.setEnabled(true);
// 设置窗口的属性
this.setSize(700, 500);
this.setTitle("客户端");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setVisible(true);
} public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("captureBtn")) {
// 点击开始录音按钮后的动作
// 停止按钮可以启动
captureBtn.setEnabled(false);
stopBtn.setEnabled(true);
playBtn.setEnabled(false);
saveBtn.setEnabled(false);
sendBtn.setEnabled(false);
// 调用录音的方法
capture();
rdLabel.setText("正在录音..");
} else if (e.getActionCommand().equals("stopBtn")) {
// 点击停止录音按钮的动作
captureBtn.setEnabled(true);
stopBtn.setEnabled(false);
playBtn.setEnabled(true);
saveBtn.setEnabled(true);
if(!linkBtn.isEnabled()){
sendBtn.setEnabled(true);
}
// 调用停止录音的方法
stop();
rdLabel.setText("完成录音..");
} else if (e.getActionCommand().equals("playBtn")) {
// 调用播放录音的方法
play();
} else if (e.getActionCommand().equals("saveBtn")) {
// 调用保存录音的方法
rdLabel.setText("保存录音..");
save();
}else if(e.getActionCommand().equals("sendBtn")){
rdLabel.setText("发送录音..");
send();
}
else if(e.getActionCommand().equals("linkBtn")){
linkBtn.setEnabled(false);
delinkBtn.setEnabled(true);
if(saveBtn.isEnabled())
sendBtn.setEnabled(true);
link();
if(s.isConnected())
linkLabel.setText("已连接:");
}
else if(e.getActionCommand().equals("delinkBtn")){
delinkBtn.setEnabled(false);
linkBtn.setEnabled(true);
sendBtn.setEnabled(false);
delink();
linkLabel.setText("未连接:");
}else if(e.getActionCommand().equals("setButton")){
//校验ip地址
Pattern p = Pattern.compile("^((25[0-5]|2[0-4]\\d|[1]{1}\\d{1}\\d{1}|[1-9]{1}\\d{1}|\\d{1})($|(?!\\.$)\\.)){4}$");
Matcher m = p.matcher(iptxt.getText());
boolean b = m.matches();
if(!b){
iptxt.setText("IP输入不合法");
return ;
}
IP=iptxt.getText().trim();
path=repath.getText().trim();
savepath=svpath.getText().trim();
if(new File(savepath).isDirectory())
{
savepath+="/"+System.currentTimeMillis()+".mp3";
this.captureBtn.setEnabled(true);
}else{
svpath.setText("文件路径不存在!");
return ;
}
if(new File(path).isDirectory())
{
path+="/recied.mp3";
}else{
repath.setText("文件路径不存在!");
return;
}
this.linkBtn.setEnabled(true);
this.setButton.setEnabled(false);
JOptionPane.showMessageDialog(null, "设置成功");
}
}
//连接到服务端
public void link()
{
//初始化一个套接字
try {
s=new Socket(IP,port);
//开启一个接收文件的线程
client.recied(s, new File(path));
} catch (UnknownHostException e) {
System.out.println("连接失败");
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
/*
* 断开连接
* (必须再连接之后再调用此方法,否则控制台会打印当前并无连接。)
* throws IOExcpetion e(关闭异常失败)
*/
public void delink() {
if(s.isClosed())System.out.println("当前并无连接");
try {
s.close();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
//录音
public void capture(){
client.capture();
}
// 停止录音
public void stop(){
client.stop();
}//播放录音(默认播放刚刚完成的录音)
public void play(){
client.play();
} // 保存录音
public void save(){
client.save(new File(savepath));
}
//发送录音到服务端(不保存到本地的发送)
public void send(){
client.send();
}
// public static void main(String[] args) {
// 创造一个实例
Client tc=new Client();
} }
3。音频工具类
//音频工具类。需要实例化。
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;import javax.sound.sampled.*;
import javax.swing.JOptionPane; class audioUtils
{
// 定义录音格式
private AudioFormat af = null;
// 定义目标数据行,可以从中读取音频数据,该 TargetDataLine 接口提供从目标数据行的缓冲区读取所捕获数据的方法。
private TargetDataLine td = null;
// 定义源数据行,源数据行是可以写入数据的数据行。它充当其混频器的源。应用程序将音频字节写入源数据行,这样可处理字节缓冲并将它们传递给混频器。
private SourceDataLine sd = null;
// 定义字节数组输入输出流
private ByteArrayInputStream bais = null;
private ByteArrayOutputStream baos = null;
// 定义音频输入流
private AudioInputStream ais = null;
// 定义停止录音的标志,来控制录音线程的运行
private Boolean stopflag = false;
private Socket s=null;
//默认的AudioFormat
private AudioFormat.Encoding encoding = AudioFormat.Encoding.
PCM_SIGNED ;
private float rate = 16000f;
private int sampleSize = 16;
private String signedString = "signed";
private boolean bigEndian = true;
private int channels = 2;
/*接收文件
* @param socket(套接字),file文件
*
*/
public void recied(Socket s,File file)
{
//开启一个接收文件的线程
recied r=new recied(s,file);
Thread t1=new Thread(r);
t1.start();
}
/*停止录音
*
*/
public void stop() {
stopflag = true;
//之前的源音频数据流要关了
if(td!=null)
{
td.close();
}
}
/*
* 播放语音(没流默认播放当前录音流)
*/
public void play(){
Thread t1=new Thread(new play(ais));
t1.start();
}
/*
* 有流播放传入的流
* @param ais(音频输入流)
*/
public void play(AudioInputStream ais){
Thread t1=new Thread(new play(ais));
t1.start();
}
/*
* 保存文件
* @param file(本地文件,若文件不存在会自动创建)
*/
public void save(File file) {
// 取得录音输入流
af = getAudioFormat();
byte audioData[] = baos.toByteArray();
bais = new ByteArrayInputStream(audioData);
ais = new AudioInputStream(bais, af, audioData.length
/ af.getFrameSize());
// 写入文件
try {
if (!file.exists()) {// 如果文件不存在,则创建该文件
file.createNewFile();
}
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, file);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流
try {
if (bais != null) {
bais.close();
}
if (ais != null) {
ais.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
* 开始录音
*/
public void capture() //录音代码
{
try {
af = getAudioFormat();
DataLine.Info info = new DataLine.Info(TargetDataLine.class,af);
td = (TargetDataLine)(AudioSystem.getLine(info));
td.open(af);
td.start();
Record record = new Record();
Thread t1 = new Thread(record);
t1.start();
} catch (LineUnavailableException ex) {
ex.printStackTrace();
return;
}
}
/*
* 发送录音(需要在连接后使用,否则会抛RuntimeException异常)
* throws
*/
public void send()
{
Thread t1=new Thread(new Send());
t1.start();
if(s.isClosed()){
throw new RuntimeException();
}
}
// 录音类,因为要用到MyRecord类中的变量,所以将其做成内部类
class Record implements Runnable // 录音线程
{
byte bts[] = new byte[10000]; public void run() {
baos = new ByteArrayOutputStream();
try {
System.out.println("客户端正在录音...");
stopflag = false;
while(stopflag != true)
{
int cnt = td.read(bts, 0, bts.length);
if(cnt > 0)
{
baos.write(bts, 0, cnt);
}
}
af = getAudioFormat();
byte audioData[] = baos.toByteArray();
bais = new ByteArrayInputStream(audioData);
ais = new AudioInputStream(bais, af, audioData.length
/ af.getFrameSize());
//查看数据
System.out.println("长度:"+ais.getFrameLength()+"最大"+ais.available());
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(baos != null)
{
baos.close();
}
if(bais!=null){
bais.close();
}
} catch (IOException e) {
e.printStackTrace();
}finally{
td.drain();
td.close();
}
}
}
}
// 设置AudioFormat的参数
public AudioFormat getAudioFormat() //录音格式
{
return new AudioFormat(encoding, rate, sampleSize, channels,(sampleSize / 8) * channels, rate, bigEndian);
}
/*
* 设置AduioFomat
*/
public void setAudioFormat
(AudioFormat.Encoding encoding,float rate,int sampleSize,String signedString,boolean bigEndian,int channels)
{
this.encoding = encoding;
this.rate = 16000f;
this. sampleSize = 16;
this.signedString = "signed";
this. bigEndian = true;
this. channels = 2;
}
//播放线程类
class play implements Runnable{
private AudioInputStream ais;
public play(AudioInputStream ais){
this.ais=ais;
}
@Override
public void run() {
// TODO 自动生成的方法存根
playAudio(ais);
}
//接收一个音频流
public void playAudio(AudioInputStream ais)
{
try{
AudioFormat baseFormat=ais.getFormat();
DataLine.Info info=new DataLine.Info(SourceDataLine.class, baseFormat);
SourceDataLine line=(SourceDataLine)AudioSystem.getLine(info);
System.out.println("正在播放录音");
line.open(baseFormat);
line.start();
int BUFFER_SIZE=4000*4;
int intBytes=0;
int outBytes;
byte[] audioData=new byte[BUFFER_SIZE];
while(intBytes!=-1)
{
intBytes=ais.read(audioData, 0, BUFFER_SIZE);
if(intBytes>=0)
outBytes=line.write(audioData, 0, intBytes);
}
}catch(Exception e)
{
e.printStackTrace();
}
finally{
try {
ais.close();
} catch (IOException e) {
// TODO 自动生成的 catch 块
System.out.println("关流失败");
}
}
}
}
//发送文件的线程类(不用保存到本地文件直接发送)
class Send implements Runnable{
public void run()
{
send();
}
private void send(){
try {
af = getAudioFormat();
byte audioData[] = baos.toByteArray();
bais = new ByteArrayInputStream(audioData);
ais = new AudioInputStream(bais, af, audioData.length
/ af.getFrameSize());
if(s!=null){
//将输入流写入服务端的输出流
s.getOutputStream().flush();
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, s.getOutputStream());
}
System.out.println("发送到服务端");
ais.close();
}
catch (UnknownHostException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
//收语音的线程,并播放的类
class recied implements Runnable{
InputStream in=null;
File file=null;
public recied(Socket socket,File file){
s=socket;
this.file=file;
}
@Override
public void run() {
// TODO 自动生成的方法存根
try{
while(true)
{
if (!file.exists()) {// 如果文件不存在,则创建该目录
file.createNewFile();
}
//获得客户端的读取流(阻塞)
in=s.getInputStream(); //这个网络流不允许读写头来回移动,也就不允许mark/reset机制需要buffer包起来。
BufferedInputStream bufin=new BufferedInputStream(in);
//获取到客户端传过来的输入流,并转化为录音输入流
ais = AudioSystem.getAudioInputStream(bufin);
//写入本地文件.
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, file);
System.out.println("收到录音..");
JOptionPane.showMessageDialog(null, "收到录音....");
AudioInputStream aui=AudioSystem.getAudioInputStream(file);
//播放收到的声音
play(aui);
}
}catch(Exception e){
e.getStackTrace();
}
}
}
}