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();
}
}
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;
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();
}
public void play(AudioInputStream ais){
Thread t1=new Thread(new play(ais));
t1.start();
}
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;
}
}
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);
}
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();
}
}
}
}