天天看点

实时语音通信

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();

                    }

                }

            }

}

继续阅读