
本文解决 Java 调用 SoX play 命令播放网络电台时中途静音、进程假死的问题,核心在于正确处理子进程的标准 I/O 缓冲区,推荐使用 ProcessBuilder.inheritIO() 替代过时的 Runtime.exec()。
本文解决 java 调用 sox `play` 命令播放网络电台时中途静音、进程假死的问题,核心在于正确处理子进程的标准 i/o 缓冲区,推荐使用 `processbuilder.inheritio()` 替代过时的 `runtime.exec()`。
在 Raspberry Pi 4B(运行 Raspberry Pi OS)上通过 Java 调用 SoX 播放在线电台流(如 play --magic http://example.com/stream.mp3)时,常出现「播放 1–2 分钟后无声,但进程仍在运行、waitFor() 不返回」的现象。这并非 SoX 或流媒体本身故障,而是 Java 子进程管理中的典型 I/O 缓冲区阻塞问题。
问题根源:标准流缓冲区溢出
Runtime.getRuntime().exec() 启动的子进程会继承父进程的 stdin/stdout/stderr,但默认情况下 Java 不会自动消费其输出流。SoX 在播放过程中持续向 stdout(如进度信息)和 stderr(如解码日志)写入数据;当这些缓冲区填满(通常仅几 KB),SoX 会被内核挂起等待读取,导致音频中断——而 Java 端因未读取流,也卡在 waitFor(),形成死锁。
您观察到终端中命令可无限播放,正是因为 shell 自动处理了这些流;而 Java 程序若不显式处理,就会陷入此陷阱。
正确方案:使用 ProcessBuilder 并启用 inheritIO()
现代、安全且简洁的解决方案是改用 ProcessBuilder,并调用 inheritIO() —— 它让 SoX 直接复用 JVM 的控制台 I/O,避免缓冲区堆积:
import java.io.IOException;
public class SoxPlayer {
public static void playStream(String stationUrl) throws IOException, InterruptedException {
ProcessBuilder builder = new ProcessBuilder(
"play", "--magic", stationUrl
);
// 关键:将 SoX 的 stdout/stderr 直接映射到 Java 进程的控制台
builder.inheritIO();
// 设置环境变量(替代手动构造 env 数组)
builder.environment().put("AUDIODEV", "pcm.radsound");
Process process = builder.start();
// 如需异步播放,此处不应阻塞;若需同步等待结束,再调用 waitFor()
int exitCode = process.waitFor();
System.out.println("SoX exited with code: " + exitCode);
}
}
注意事项与最佳实践
- ✅ 务必使用 inheritIO():这是解决静音问题的核心,不可省略。
- ⚠️ 避免 waitFor() 阻塞主线程:对于长时间播放(如电台),建议在独立线程中调用 waitFor(),或完全异步处理,防止 UI 冻结。
- ? 确保 SoX 音频设备配置正确:AUDIODEV=pcm.radsound 需与您的 ALSA 设备名一致(可通过 aplay -L 查看可用设备)。
- ? Raspberry Pi 兼容性提示:确认已安装 SoX 及音频后端支持(如 sox libsox-fmt-all),并赋予 Java 进程对 /dev/snd/ 的访问权限(添加用户到 audio 组:sudo usermod -a -G audio pi)。
- ? 不要手动重定向流后再忽略:若使用 getInputStream()/getErrorStream(),必须主动读取(如用 BufferedReader 循环消费),否则仍会阻塞。
通过以上调整,SoX 将像在终端中一样稳定运行,实现小时级无中断流媒体播放。这一模式同样适用于 curl | play 等管道场景——只需确保整个管道链的 I/O 均被正确继承或消费。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











