这是一个典型的跨平台实时语音通信系统,结合您的技术栈选择,我为您提供完整的技术方案:
一、系统架构设计
Windows端 (SpringBoot + Vue) Linux工控机端
↑↓ WebSocket/SIP/RTP ↑↓
└───────── 语音服务器 ──────────┘
二、技术选型方案
方案A:WebRTC + 信令服务器(推荐)
// Windows端:SpringBoot + WebRTC
@Configuration
public class WebRTCConfig {
@Bean
public PeerConnectionFactory peerConnectionFactory() {
PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
.builder(context).createInitializationOptions());
return PeerConnectionFactory.builder().createPeerConnectionFactory();
}
}
方案B:SIP + RTP(适合工业环境)
// SIP协议栈集成
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
</dependency>
<dependency>
<groupId>javax.sip</groupId>
<artifactId>jain-sip-api</artifactId>
<version>1.3.0-91</version>
</dependency>
三、Windows端实现(SpringBoot + Vue)
1. SpringBoot后端核心代码
@RestController
@CrossOrigin
@RequestMapping("/api/audio")
public class AudioController {
// WebSocket端点
@MessageMapping("/audio-stream")
public void handleAudioStream(byte[] audioData, @Header("sessionId") String sessionId) {
audioService.relayAudio(sessionId, audioData);
}
// 获取设备列表
@GetMapping("/devices")
public List<AudioDevice> getAudioDevices() {
return audioService.getAvailableDevices();
}
}
2. 音频处理服务
@Service
public class AudioService {
// 使用Java Sound API捕获音频
public AudioInputStream captureAudio() {
AudioFormat format = new AudioFormat(16000, 16, 1, true, false);
TargetDataLine microphone = AudioSystem.getTargetDataLine(format);
// 音频编码(Opus/G.711)
byte[] audioData = encodeAudio(rawData, "G.711");
return new AudioInputStream(microphone);
}
// WebSocket音频传输
@Autowired
private SimpMessagingTemplate messagingTemplate;
public void sendAudioToClient(String sessionId, byte[] audioData) {
messagingTemplate.convertAndSendToUser(
sessionId,
"/queue/audio",
audioData
);
}
}
3. Vue前端组件
<template>
<div class="audio-intercom">
<div class="device-selector">
<select v-model="selectedInput">
<option v-for="device in inputDevices" :value="device.id">
{{ device.label }}
</option>
</select>
<select v-model="selectedOutput">
<option v-for="device in outputDevices" :value="device.id">
{{ device.label }}
</option>
</select>
</div>
<button @click="toggleRecording" :class="{ active: isRecording }">
{{ isRecording ? '停止对讲' : '开始对讲' }}
</button>
<audio ref="audioPlayer" autoplay></audio>
</div>
</template>
<script>
export default {
data() {
return {
ws: null,
mediaRecorder: null,
audioChunks: [],
isRecording: false,
inputDevices: [],
outputDevices: []
};
},
async mounted() {
await this.initAudioDevices();
this.connectWebSocket();
},
methods: {
async initAudioDevices() {
const devices = await navigator.mediaDevices.enumerateDevices();
this.inputDevices = devices.filter(d => d.kind === 'audioinput');
this.outputDevices = devices.filter(d => d.kind === 'audiooutput');
},
connectWebSocket() {
this.ws = new WebSocket(`ws://${location.host}/api/audio/ws`);
this.ws.onmessage = (event) => {
this.playAudio(event.data);
};
},
async toggleRecording() {
if (!this.isRecording) {
await this.startRecording();
} else {
this.stopRecording();
}
},
async startRecording() {
const stream = await navigator.mediaDevices.getUserMedia({
audio: { deviceId: this.selectedInput }
});
this.mediaRecorder = new MediaRecorder(stream, {
mimeType: 'audio/webm;codecs=opus'
});
this.mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
this.sendAudioData(event.data);
}
};
this.mediaRecorder.start(100); // 每100ms发送一次
this.isRecording = true;
},
sendAudioData(audioData) {
const reader = new FileReader();
reader.onload = () => {
this.ws.send(reader.result);
};
reader.readAsArrayBuffer(audioData);
},
playAudio(audioData) {
const blob = new Blob([audioData], { type: 'audio/webm' });
const url = URL.createObjectURL(blob);
this.$refs.audioPlayer.src = url;
}
}
};
</script>
四、Linux工控机端实现
1. Python + GStreamer方案(推荐)
# audio_client.py
import pyaudio
import socket
import threading
class LinuxAudioClient:
def __init__(self, server_ip):
self.CHUNK = 1024
self.FORMAT = pyaudio.paInt16
self.CHANNELS = 1
self.RATE = 16000
# 连接服务器
self.ws = websocket.WebSocketApp(
f"ws://{server_ip}:8080/api/audio/ws",
on_message=self.on_message
)
def start(self):
# 启动音频捕获线程
capture_thread = threading.Thread(target=self.capture_audio)
capture_thread.start()
# 启动播放线程
play_thread = threading.Thread(target=self.play_audio)
play_thread.start()
def capture_audio(self):
p = pyaudio.PyAudio()
stream = p.open(
format=self.FORMAT,
channels=self.CHANNELS,
rate=self.RATE,
input=True,
frames_per_buffer=self.CHUNK
)
while True:
data = stream.read(self.CHUNK)
self.ws.send(data, opcode=websocket.ABNF.OPCODE_BINARY)
2. C++方案(性能更高)
// audio_capture.cpp
#include <alsa/asoundlib.h>
class AlsaAudio {
public:
void captureAndSend() {
snd_pcm_t *capture_handle;
snd_pcm_open(&capture_handle, "default", SND_PCM_STREAM_CAPTURE, 0);
while(running) {
snd_pcm_readi(capture_handle, buffer, frames);
// 通过WebSocket发送到服务器
send_to_server(buffer, frames * 2);
}
}
};
五、信令服务器(SpringBoot实现)
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(audioHandler(), "/audio/ws")
.setAllowedOrigins("*");
}
@Bean
public WebSocketHandler audioHandler() {
return new AudioWebSocketHandler();
}
}
@Component
public class AudioWebSocketHandler extends TextWebSocketHandler {
private ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) {
String sessionId = session.getId();
sessions.put(sessionId, session);
// 发送设备信息
session.sendMessage(new TextMessage("CONNECTED:" + sessionId));
}
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
// 转发音频数据给其他设备
byte[] audioData = message.getPayload().array();
sessions.forEach((id, targetSession) -> {
if (!id.equals(session.getId()) && targetSession.isOpen()) {
try {
targetSession.sendMessage(new BinaryMessage(audioData));
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
六、完整部署配置
1. application.yml
server:
port: 8080
servlet:
context-path: /
audio:
config:
sample-rate: 16000
channels: 1
codec: opus # 或 g711
buffer-size: 2048
websocket:
allowed-origins: "*"
buffer-size: 65536
2. Docker部署
# Windows服务端
FROM openjdk:11-jre
COPY target/audio-intercom.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
# Linux客户端
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y \
python3 python3-pip alsa-utils pulseaudio
COPY audio_client.py /app/
CMD ["python3", "/app/audio_client.py"]
七、关键优化建议
-
音频编码选择:
- 网络好:Opus(动态码率,20-256kbps)
- 网络差:G.711(64kbps,工业常用)
- 延迟敏感:G.722.1(低延迟)
-
抗丢包处理:
// 添加前向纠错
public byte[] addFEC(byte[] audioData) {
// 使用冗余包或纠错码
return fecEncoder.encode(audioData);
}
- QoS保障:
- 使用UDP + 重传机制
- Jitter Buffer平滑处理
- 自适应码率调整
八、测试方案
@SpringBootTest
public class AudioTest {
@Test
public void testAudioLatency() {
// 测试端到端延迟
long startTime = System.currentTimeMillis();
audioService.sendAndReceive();
long latency = System.currentTimeMillis() - startTime;
assertTrue(latency < 200); // 工业要求<200ms
}
}
这个方案的优势:
- 跨平台兼容:WebRTC标准支持
- 工业级稳定:支持断线重连、自动重试
- 可扩展性强:可扩展到多设备对讲
- 维护简单:统一的技术栈
您可以根据实际需求调整音频编码、传输协议和部署方式。需要进一步优化的话,请告诉我具体的使用场景和性能要求。