如何在两台Raspberry Pi3间不使用Socket,通过Java实现语音收发?
嘿,这个需求挺有意思的——不用Socket直接编码,在两台Raspberry Pi3之间用Java实现语音收发,咱们可以试试几个封装好的方案,完全不用自己写Socket逻辑,下面给你拆解几个实用的:
方案1:基于MQTT消息队列(最省心的网络方案)
MQTT是轻量级的消息协议,现成的客户端库已经把底层的Socket通信封装得明明白白,你只需要关注语音的录制、发送和接收播放就行。
准备工作
搭建MQTT Broker:在其中一台Pi或者你手头的其他设备上装Mosquitto(树莓派直接敲命令):
sudo apt update && sudo apt install mosquitto mosquitto-clients装完后Broker默认启动,两台Pi能ping通的话就能用。
引入Java依赖:如果用Maven,在
pom.xml里加Paho客户端依赖:<dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse.paho.client.mqttv3</artifactId> <version>1.2.5</version> </dependency>
发送端代码(录制并发布语音)
这个代码会录制10秒语音,转成字节数组后发布到pi/voice主题:
import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttMessage; import javax.sound.sampled.*; import java.io.ByteArrayOutputStream; public class VoiceSender { private static final String BROKER_URL = "tcp://[你的BrokerIP]:1883"; private static final String TOPIC = "pi/voice"; private static final int RECORD_SECONDS = 10; private static final AudioFormat FORMAT = new AudioFormat(44100, 16, 1, true, true); public static void main(String[] args) throws Exception { // 初始化MQTT客户端 MqttClient client = new MqttClient(BROKER_URL, MqttClient.generateClientId()); MqttConnectOptions options = new MqttConnectOptions(); options.setCleanSession(true); client.connect(options); // 录制语音 TargetDataLine line = AudioSystem.getTargetDataLine(FORMAT); line.open(FORMAT); line.start(); System.out.println("开始录制..."); byte[] buffer = new byte[line.getBufferSize() / 5]; int bytesRead; ByteArrayOutputStream out = new ByteArrayOutputStream(); long endTime = System.currentTimeMillis() + RECORD_SECONDS * 1000; while (System.currentTimeMillis() < endTime) { bytesRead = line.read(buffer, 0, buffer.length); out.write(buffer, 0, bytesRead); } line.stop(); line.close(); System.out.println("录制结束,发送语音..."); // 发布消息 MqttMessage message = new MqttMessage(out.toByteArray()); message.setQos(1); // 确保消息到达 client.publish(TOPIC, message); client.disconnect(); } }
接收端代码(订阅并播放语音)
订阅pi/voice主题,收到字节流后直接播放:
import org.eclipse.paho.client.mqttv3.*; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import javax.sound.sampled.*; public class VoiceReceiver { private static final String BROKER_URL = "tcp://[你的BrokerIP]:1883"; private static final String TOPIC = "pi/voice"; private static final AudioFormat FORMAT = new AudioFormat(44100, 16, 1, true, true); public static void main(String[] args) throws Exception { MqttClient client = new MqttClient(BROKER_URL, "VoiceReceiver", new MemoryPersistence()); MqttConnectOptions options = new MqttConnectOptions(); options.setCleanSession(true); client.connect(options); // 订阅主题并设置回调 client.subscribe(TOPIC, (topic, message) -> { try { System.out.println("收到语音,开始播放..."); // 播放语音 SourceDataLine line = AudioSystem.getSourceDataLine(FORMAT); line.open(FORMAT); line.start(); line.write(message.getPayload(), 0, message.getPayload().length); line.drain(); line.stop(); line.close(); System.out.println("播放结束"); } catch (Exception e) { e.printStackTrace(); } }); // 保持程序运行 Thread.sleep(Long.MAX_VALUE); } }
方案2:基于共享目录(无网络依赖,适合近距离)
如果两台Pi在同一个局域网,也可以用SMB共享目录:发送端把录制好的语音文件写到共享目录,接收端监听目录变化,有新文件就播放。
准备工作
在Pi1上搭建SMB共享:
- 安装Samba:
sudo apt install samba - 编辑配置文件:
sudo nano /etc/samba/smb.conf,在末尾加:[pi_shared] path = /home/pi/voice_shared writeable = yes guest ok = no create mask = 0644 directory mask = 0755 - 创建共享目录:
mkdir /home/pi/voice_shared - 设置Samba用户密码:
sudo smbpasswd -a pi - 重启Samba服务:
sudo systemctl restart smbd
- 安装Samba:
在Pi2上挂载共享目录:
sudo mount -t cifs //[Pi1_IP]/pi_shared /mnt/voice_shared -o username=pi,password=你的密码想要开机自动挂载,把这条命令加到
/etc/fstab里就行。
发送端代码(录制到共享目录)
import javax.sound.sampled.*; import java.io.File; import java.io.FileOutputStream; public class FileVoiceSender { private static final String SHARED_DIR = "/mnt/voice_shared"; private static final int RECORD_SECONDS = 10; private static final AudioFormat FORMAT = new AudioFormat(44100, 16, 1, true, true); public static void main(String[] args) throws Exception { TargetDataLine line = AudioSystem.getTargetDataLine(FORMAT); line.open(FORMAT); line.start(); System.out.println("开始录制..."); byte[] buffer = new byte[line.getBufferSize() / 5]; int bytesRead; File outputFile = new File(SHARED_DIR, "voice_" + System.currentTimeMillis() + ".pcm"); FileOutputStream out = new FileOutputStream(outputFile); long endTime = System.currentTimeMillis() + RECORD_SECONDS * 1000; while (System.currentTimeMillis() < endTime) { bytesRead = line.read(buffer, 0, buffer.length); out.write(buffer, 0, bytesRead); } line.stop(); line.close(); out.close(); System.out.println("录制完成,文件已保存到共享目录"); } }
接收端代码(监听目录并播放)
用Java的WatchService监听目录变化:
import javax.sound.sampled.*; import java.io.File; import java.io.FileInputStream; import java.nio.file.*; public class FileVoiceReceiver { private static final String SHARED_DIR = "/mnt/voice_shared"; private static final AudioFormat FORMAT = new AudioFormat(44100, 16, 1, true, true); public static void main(String[] args) throws Exception { Path dir = Paths.get(SHARED_DIR); WatchService watchService = FileSystems.getDefault().newWatchService(); dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE); System.out.println("监听共享目录中..."); while (true) { WatchKey key = watchService.take(); for (WatchEvent<?> event : key.pollEvents()) { if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) { File voiceFile = dir.resolve((Path) event.context()).toFile(); // 等文件写完再读取 Thread.sleep(1000); System.out.println("发现新文件:" + voiceFile.getName() + ",开始播放"); // 播放文件 FileInputStream in = new FileInputStream(voiceFile); SourceDataLine line = AudioSystem.getSourceDataLine(FORMAT); line.open(FORMAT); line.start(); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { line.write(buffer, 0, bytesRead); } line.drain(); line.stop(); line.close(); in.close(); System.out.println("播放结束"); } } key.reset(); } } }
方案3:基于蓝牙RFCOMM(无网络,适合近距离)
Pi3自带蓝牙模块,用RFCOMM协议可以模拟串口通信,Java用BlueCove库就能实现,完全不用自己写Socket代码。
准备工作
- 开启并配对两台Pi的蓝牙:
- 开启蓝牙:
sudo bluetoothctl power on - 用
bluetoothctl搜索并配对对方的Pi,记得信任设备:trust [对方蓝牙MAC]
- 开启蓝牙:
- 引入BlueCove依赖:Maven依赖如下(注意树莓派用的是ARM架构,可能需要对应版本):
<dependency> <groupId>com.intel.bluetooth</groupId> <artifactId>bluecove</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>com.intel.bluetooth</groupId> <artifactId>bluecove-gpl</artifactId> <version>2.1.1</version> </dependency>
蓝牙服务端代码(接收语音)
import javax.bluetooth.*; import javax.microedition.io.Connector; import javax.microedition.io.StreamConnection; import javax.microedition.io.StreamConnectionNotifier; import javax.sound.sampled.*; import java.io.InputStream; public class BluetoothVoiceServer { private static final UUID UUID = new UUID("0000110100001000800000805F9B34FB", false); private static final AudioFormat FORMAT = new AudioFormat(44100, 16, 1, true, true); public static void main(String[] args) throws Exception { // 初始化蓝牙服务 LocalDevice localDevice = LocalDevice.getLocalDevice(); localDevice.setDiscoverable(DiscoveryAgent.GIAC); String url = "btspp://localhost:" + UUID + ";name=VoiceServer"; StreamConnectionNotifier notifier = (StreamConnectionNotifier) Connector.open(url); System.out.println("蓝牙服务已启动,等待连接..."); StreamConnection connection = notifier.acceptAndOpen(); System.out.println("客户端已连接"); // 接收并播放语音 InputStream in = connection.openInputStream(); SourceDataLine line = AudioSystem.getSourceDataLine(FORMAT); line.open(FORMAT); line.start(); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { line.write(buffer, 0, bytesRead); } line.drain(); line.stop(); line.close(); in.close(); connection.close(); notifier.close(); } }
蓝牙客户端代码(发送语音)
import javax.bluetooth.RemoteDevice; import javax.microedition.io.Connector; import javax.microedition.io.StreamConnection; import javax.sound.sampled.*; import java.io.OutputStream; public class BluetoothVoiceClient { private static final UUID UUID = new UUID("0000110100001000800000805F9B34FB", false); private static final String REMOTE_MAC = "[对方Pi的蓝牙MAC地址]"; private static final int RECORD_SECONDS = 10; private static final AudioFormat FORMAT = new AudioFormat(44100, 16, 1, true, true); public static void main(String[] args) throws Exception { // 连接蓝牙设备 RemoteDevice remoteDevice = RemoteDevice.getRemoteDevice(REMOTE_MAC); String url = "btspp://" + REMOTE_MAC + ":" + UUID + ";authenticate=false;encrypt=false;master=false"; StreamConnection connection = (StreamConnection) Connector.open(url); System.out.println("已连接到蓝牙服务端"); // 录制并发送语音 TargetDataLine line = AudioSystem.getTargetDataLine(FORMAT); line.open(FORMAT); line.start(); System.out.println("开始录制..."); OutputStream out = connection.openOutputStream(); byte[] buffer = new byte[line.getBufferSize() / 5]; int bytesRead; long endTime = System.currentTimeMillis() + RECORD_SECONDS * 1000; while (System.currentTimeMillis() < endTime) { bytesRead = line.read(buffer, 0, buffer.length); out.write(buffer, 0, bytesRead); } line.stop(); line.close(); out.close(); connection.close(); System.out.println("发送完成"); } }
注意事项
- 所有方案里的音频格式要统一,比如示例里用的是44100Hz、16bit、单声道、PCM编码,不然播放会出现杂音或者无法播放的情况。
- 树莓派上运行Java程序时,要确保有音频权限:可以用
sudo usermod -a -G audio pi把pi用户加到audio组。 - 蓝牙方案如果遇到连接问题,检查两台Pi的蓝牙是否配对成功,是否信任了对方设备。
- 共享目录方案要确保挂载稳定,避免出现读写权限问题。
内容的提问来源于stack exchange,提问作者mbcode
相关产品推荐
相关产品推荐

