You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Socket实时音频流:实现服务器到客户端音频传输反转

实现服务器采集音频并推送至客户端播放的方案

没问题!我来帮你把现有的音频流逻辑完全反转,让服务器负责采集本地音频并通过UDP发送给客户端,客户端接收后直接播放。咱们基于你现有的代码做针对性修改:

核心逻辑反转说明

原来的流程是:客户端采集 → UDP发送 → 服务器接收 → 服务器播放
现在要改成:服务器采集 → UDP发送 → 客户端接收 → 客户端播放
核心改动就是把「采集逻辑」从客户端移到服务器,把「播放逻辑」从服务器移到客户端,同时调整UDP的发送/接收方向。


修改后的服务器代码(负责采集+发送)

import java.io.ByteArrayOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;

public class VUServer {
    boolean stopAudioCapture = false;
    ByteArrayOutputStream byteOutputStream;
    AudioFormat adFormat;
    TargetDataLine targetDataLine;
    // 记录客户端的地址和端口,用于推送音频
    private InetAddress clientAddress;
    private int clientPort;

    private AudioFormat getAudioFormat() {
        float sampleRate = 16000.0F;
        int sampleInbits = 16;
        int channels = 1;
        boolean signed = true;
        boolean bigEndian = false;
        return new AudioFormat(sampleRate, sampleInbits, channels, signed, bigEndian);
    }

    public static void main(String args[]) {
        new VUServer().runVOIP();
    }

    public void runVOIP() {
        try {
            // 先创建UDP Socket监听客户端的连接请求
            DatagramSocket serverSocket = new DatagramSocket(9786);
            byte[] handshakeBuffer = new byte[10];
            DatagramPacket handshakePacket = new DatagramPacket(handshakeBuffer, handshakeBuffer.length);
            // 等待客户端发送握手包,获取客户端地址
            serverSocket.receive(handshakePacket);
            clientAddress = handshakePacket.getAddress();
            clientPort = handshakePacket.getPort();
            System.out.println("已连接客户端:" + clientAddress.getHostAddress() + ":" + clientPort);

            // 启动音频采集线程
            captureAudio(serverSocket);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void captureAudio(DatagramSocket serverSocket) {
        try {
            adFormat = getAudioFormat();
            DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, adFormat);
            targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
            targetDataLine.open(adFormat);
            targetDataLine.start();
            Thread captureThread = new Thread(new CaptureThread(serverSocket));
            captureThread.start();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }
    }

    class CaptureThread extends Thread {
        private DatagramSocket socket;
        byte tempBuffer[] = new byte[10000];

        public CaptureThread(DatagramSocket socket) {
            this.socket = socket;
        }

        public void run() {
            stopAudioCapture = false;
            try {
                while (!stopAudioCapture) {
                    int cnt = targetDataLine.read(tempBuffer, 0, tempBuffer.length);
                    if (cnt > 0) {
                        // 把采集到的音频发送给客户端
                        DatagramPacket sendPacket = new DatagramPacket(tempBuffer, cnt, clientAddress, clientPort);
                        socket.send(sendPacket);
                    }
                }
                targetDataLine.close();
                socket.close();
            } catch (Exception e) {
                System.out.println("CaptureThread::run()" + e);
                System.exit(0);
            }
        }
    }
}

修改后的客户端代码(负责接收+播放)

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import javax.sound.sampled.*;

public class VUClient extends JFrame {
    boolean stopAudioPlay = false;
    AudioFormat adFormat;
    SourceDataLine sourceLine;
    DatagramSocket clientSocket;

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

    public VUClient() {
        final JButton connect = new JButton("连接服务器");
        final JButton stop = new JButton("停止播放");
        connect.setEnabled(true);
        stop.setEnabled(false);

        connect.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                connect.setEnabled(false);
                stop.setEnabled(true);
                connectAndPlayAudio();
            }
        });
        getContentPane().add(connect);

        stop.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                connect.setEnabled(true);
                stop.setEnabled(false);
                stopAudioPlay = true;
                if (sourceLine != null) {
                    sourceLine.stop();
                    sourceLine.close();
                }
                if (clientSocket != null) {
                    clientSocket.close();
                }
            }
        });
        getContentPane().add(stop);

        getContentPane().setLayout(new FlowLayout());
        setTitle("服务器音频播放客户端");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(400, 100);
        getContentPane().setBackground(Color.white);
        setVisible(true);
    }

    private void connectAndPlayAudio() {
        try {
            adFormat = getAudioFormat();
            // 初始化播放线路
            DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, adFormat);
            sourceLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
            sourceLine.open(adFormat);
            sourceLine.start();

            // 创建UDP Socket并发送握手包给服务器
            clientSocket = new DatagramSocket();
            InetAddress serverAddress = InetAddress.getByName("127.0.0.1");
            byte[] handshakeData = "CONNECT".getBytes();
            DatagramPacket handshakePacket = new DatagramPacket(handshakeData, handshakeData.length, serverAddress, 9786);
            clientSocket.send(handshakePacket);

            // 启动接收音频线程
            Thread playThread = new Thread(new PlayThread());
            playThread.start();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }
    }

    private AudioFormat getAudioFormat() {
        float sampleRate = 16000.0F;
        int sampleInbits = 16;
        int channels = 1;
        boolean signed = true;
        boolean bigEndian = false;
        return new AudioFormat(sampleRate, sampleInbits, channels, signed, bigEndian);
    }

    class PlayThread extends Thread {
        byte tempBuffer[] = new byte[10000];

        public void run() {
            stopAudioPlay = false;
            try {
                while (!stopAudioPlay) {
                    DatagramPacket receivePacket = new DatagramPacket(tempBuffer, tempBuffer.length);
                    clientSocket.receive(receivePacket);
                    // 直接播放接收到的音频数据
                    sourceLine.write(receivePacket.getData(), 0, receivePacket.getLength());
                }
            } catch (Exception e) {
                System.out.println("PlayThread::run()" + e);
            }
        }
    }
}

关键改动点说明

  1. 服务器侧:

    • 新增了握手逻辑,先接收客户端的连接请求以获取客户端的IP和端口,确保音频能精准推送
    • 把原来客户端的CaptureThread移到服务器,负责采集本地音频并通过UDP发送给客户端
    • 移除了原来服务器的播放逻辑,专注于采集和发送
  2. 客户端侧:

    • 移除了原来的采集逻辑,新增了UDP接收线程,负责接收服务器推送的音频
    • 把原来服务器的播放逻辑移到客户端,用SourceDataLine直接播放接收到的音频数据
    • 新增了连接/停止按钮,替代原来的采集/播放按钮,符合新的业务逻辑
  3. 通用优化:

    • 避免了每次接收音频都创建新的SourceDataLine,复用同一条线路播放,减少资源浪费
    • 修正了UDP数据包的长度问题,发送时只发送实际采集到的字节数(cnt),避免发送空数据

现在你可以先启动服务器,再启动客户端点击「连接服务器」,就能听到服务器采集的音频在客户端播放啦!

内容的提问来源于stack exchange,提问作者yitow53

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 18:47:40