### 利用java socket和sampled实现点对点即时语音通信

基本思路

​ 利用javax.sound.sampled 包中的方法可以很方便的实现,获取拾音器音频输入的内容,和写入音频输出的混频器中。结合socket可以实现点对点语音通话。

需要用到的类

AudioFormat类

​ 是在声音流中指定特定数据安排的类。通过检查以音频格式存储的信息,可以发现在二进制声音数据中解释位的方式。每个数据行都有与其数据流相关的音频格式。源(回放)数据行的音频格式指示数据行期望接收输出的数据类型。对于目标(捕获)数据行,音频格式指定可以从该行读取的数据种类。当然,声音文件也有音频格式。AudioFileFormat

类封装 AudioFormat 以及其他特定于文件的信息

TargetDataLine类

​ 目标数据行是可以从中读取音频数据的某种类型的 DataLine。最常见的示例是从音频捕获设备获取其数据的数据行。(该设备被实现为写入目标数据行的混频器。)

SourceDataLine类

​ 源数据行是可以写入数据的数据行。它充当其混频器的源。应用程序将音频字节写入源数据行,这样可处理字节缓冲并将它们传递给混频器。混频器可以将这些样本与取自其他源的样本混合起来,然后将该混合物传递到输出端口之类的目标(它可表示声卡上的音频输出设备)

AudioSystem类

AudioSystem
类充当取样音频系统资源的入口点。此类允许查询和访问安装在系统上的混频器。AudioSystem
包括许多在不同格式间转换音频数据的方法,以及在音频文件和流之间进行转换的方法。它还提供不用显式处理混频器即可直接从
AudioSystem 获得 Line 的方法

TargetDataLine td = (TargetDataLine)(AudioSystem.getLine(info));

Obtains a line that matches the description in the specified Line.Info object. 

If a DataLine is requested, and info is an instance of DataLine.Info specifying at least one fully qualified audio format, the last one will be used as the default format of the returned DataLine. 

If system properties javax.sound.sampled.Clip, javax.sound.sampled.Port, javax.sound.sampled.SourceDataLine and javax.sound.sampled.TargetDataLine are defined or they are defined in the file "sound.properties", they are used to retrieve default lines. For details, refer to the class description. If the respective property is not set, or the mixer requested in the property is not installed or does not provide the requested line, all installed mixers ar  e queried for the requested line type. A Line will be returned from the first mixer providing the requested line type.

具体实现

1、获取目标数据行,这里是从音频捕获设备(拾音器)获取其数据的数据行

​ 在本应用中从音频捕获设备(拾音器)获取其数据的数据行,会发送给对方作为对方的音频源数据。

// 1.获取音频流数据
// af为AudioFormat也就是音频格式
AudioFormat af = getAudioFormat();
DataLine.Info info = new DataLine.Info(TargetDataLine.class, af);
// 这里的td实际上是
TargetDataLine targetDataLine = (TargetDataLine) (AudioSystem.getLine(info));
// 打开具有指定格式的行,这样可使行获得所有所需的系统资源并变得可操作。
targetDataLine.open(af);
// 允许某一数据行执行数据 I/O
targetDataLine.start();

//将数据读取到bts中
int len = targetDataLine.read(bts, 0, bts.length);

2、获取源数据行 ,将音频字节写入源数据行,这样可处理字节缓冲并将它们传递给混频器。混频器可以将这些样本与取自其他源的样本混合起来,然后将该混合物传递到输出端口之类的目标(它可表示声卡上的音频输出设备)

​ 在本应用中对方发送过来的数据 会写入到源数据行中,输出到音频输出设备。

// 2.从音频流获取数据
dataLineInfo = new DataLine.Info(SourceDataLine.class, af);
sd = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
// 打开具有指定格式的行,这样可使行获得所有所需的系统资源并变得可操作。
sd.open(af);
// 允许某一数据行执行数据 I/O
sd.start();
//2.向源数据行中写入数据,写入后会自动播放 
sourceDataLine.write(bts, 0, len);              

3.利用socket实现通信

​ 在socket 中服务端 和 客户端 的数据传递时一致

需要注意的是 在client类和server类的run方法的while循环中 ,要先进行写、再进行读操作 ,不然socket的读取流会阻塞
这里写图片描述

代码实现
AudioUtils类 这是一个工具类

主要用来初始化 和获取SourceDataLine和TargetDataLine

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.DataLine.Info;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;

public class AudioUtils {

    private static AudioFormat af;
    private static Info info;
    private static TargetDataLine td;
    private static Info dataLineInfo;
    private static SourceDataLine sd;

    /**
     * 获取音频流数据(从拾音器)
     * 
     * @return TargetDataLine
     * @throws LineUnavailableException
     */
    public static TargetDataLine getTargetDataLine() throws LineUnavailableException {
        if (td != null) {
            return td;
        } else {
                // 1.获取音频流数据
                // af为AudioFormat也就是音频格式
                af = getAudioFormat();
                info = new DataLine.Info(TargetDataLine.class, af);
                // 这里的td实际上是
                td = (TargetDataLine) (AudioSystem.getLine(info));
                // 打开具有指定格式的行,这样可使行获得所有所需的系统资源并变得可操作。
                td.open(af);
                // 允许某一数据行执行数据 I/O
                td.start();
            return td;
        }

    }
    /**
     * 获取混编器 写入数据会自动播放
     * 
     * @return SourceDataLine
     * @throws LineUnavailableException
     */
    public static SourceDataLine getSourceDataLine() throws LineUnavailableException {
        if (sd != null) {
            return sd;
        } else {
                // 2.从音频流获取数据
                dataLineInfo = new DataLine.Info(SourceDataLine.class, af);
                sd = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
                // 打开具有指定格式的行,这样可使行获得所有所需的系统资源并变得可操作。
                sd.open(af);
                // 允许某一数据行执行数据 I/O
                sd.start();

            return sd;
        }
    }

    /**
     * 设置AudioFormat的参数
     * 
     * @return AudioFormat
     */
    public static AudioFormat getAudioFormat() {
        AudioFormat.Encoding encoding = AudioFormat.Encoding.PCM_SIGNED;
        float rate = 8000f;
        int sampleSize = 16;
        String signedString = "signed";
        boolean bigEndian = true;
        int channels = 1;
        return new AudioFormat(encoding, rate, sampleSize, channels, (sampleSize / 8) * channels, rate, bigEndian);
    }

    /**
     * 关闭资源
     */
    public static void close() {
        if (td != null) {
            td.close();
        }
        if (sd != null) {
            sd.close();
        }

    }
}
Server类
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;

/**
 * 主要实现局域网通讯中服务端的功能
 * 
 * @author Administrator
 */
public class Server {
    private OutputStream out;
    private InputStream in;
    private ServerSocket serverSocket;
    private Socket socket;
    //private int counter = 1;
    private byte[] bos=new byte[2024];
    //private static ByteArrayOutputStream baos;
    private  byte[] bis=new byte[2024];

    public Server() {

        startServer();
    }

    private void startServer() {
        try {
            serverSocket = new ServerSocket(9000, 20);
                // 等待连接
                System.out.println("服务端:等待连接");
                socket = serverSocket.accept();
                out = socket.getOutputStream();
                // out.flush();
                System.out.println("服务端:连接成功");
                // 保持通讯
                in = socket.getInputStream();

                TargetDataLine targetDataLine = AudioUtils.getTargetDataLine();

                SourceDataLine sourceDataLine = AudioUtils.getSourceDataLine();
                while (true) {
                    System.out.println("server:");

                    /**
                     * 这里一定要先发再收  不然socket的读取流会阻塞
                     */

                    //获取音频流
                    int writeLen = targetDataLine.read(bos,0,bos.length);
                    //发
                    if (bos != null) {
                        //向对方发送拾音器获取到的音频
                        System.out.println("rerver 发");
                        out.write(bos,0,writeLen);
                    }

                    //收
                    int readLen = in.read(bis);
                    if (bis != null) {
                        //播放对方发送来的音频
                        System.out.println("rerver 收");
                        sourceDataLine.write(bis, 0, readLen);
                    }
                }


        } catch (Exception ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static void main(String args[]) {
        new Server();
    }

}
client类
package 及时通信;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;

/**
 *
 * @author Administrator
 */
public class Client {
    private OutputStream out;
    private InputStream in;
    private Socket socket;
    private byte[] bos=new byte[2024];
    //private static ByteArrayOutputStream baos;
    private static byte[] bis=new byte[2024];

    public Client() {
        startClient();
    }

    private void startClient() {
        try {
            //这里需要根据自己的ip修改
            socket = new Socket("192.168.43.52", 9000);

            out = socket.getOutputStream();
            System.out.println("客户端:连接成功");
            // 保持通讯
            in = socket.getInputStream();

            TargetDataLine targetDataLine = AudioUtils.getTargetDataLine();

            SourceDataLine sourceDataLine = AudioUtils.getSourceDataLine();
            while (true) {
                System.out.println("Client:");

                    //获取音频流
                    int writeLen = targetDataLine.read(bos,0,bos.length);
                    //发
                    if (bos != null) {
                        //向对方发送拾音器获取到的音频
                        System.out.println("Client 发");
                        out.write(bos,0,writeLen);
                    }
                    //收
                    int readLen = in.read(bis);
                    if (bis != null) {
                        //播放对方发送来的音频
                        System.out.println("Client 收");
                        sourceDataLine.write(bis, 0, readLen);
                    }

            }

        } catch (Exception ex) {
            Logger.getLogger(Client.class.getName())
                    .log(Level.SEVERE, null, ex);
        }
    }

    public static void main(String args[]) {
        new Client();
    }
}
Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐