websocket+pcm-player实现pcm音频播放

//安装pcm-player
npm install pcm-player

//引入pcm-player
import PCMPlayer from 'pcm-player'

this.player.continue();  //播放音频
this.player.pause();  //暂停音频
this.player.volume();  //设置音量
this.player.destroy();  //销毁实例
// 创建实例
createPCMPlayer() {
  this.player = new PCMPlayer({
    encoding: "16bitInt", // 采样位数
    channels: 1, // 单通道
    sampleRate: 16000, // 采样率
    flushingTime: 1000, // pcm刷新间隔
    onstatechange: (node, event, type) => {}, // 播放状态变化事件
    onended: (node, event) => {}, // 播放结束事件
  });
},



initVoiceWebSocket() {
  if (typeof WebSocket === "undefined") {
    console.log("您的浏览器不支持socket");
  } else {
    const that = this;
    const wsUrl = `ws://192.168.1.1:8080/pcm?audioId=abcd1234`;
    // 实例化socket
    this.socket = new WebSocket(wsUrl);
    this.socket.onopen = function () {
      ws_heartCheck.start(); // 启动心跳
      console.log("指挥室拾音器WebSocket连接已建立");
      // 在连接建立后,可以发送消息到服务器
    };
    this.socket.onmessage = this.websocketonmessage;
    this.socket.onerror = this.websocketonerror;
    this.socket.onclose = this.websocketclose;
    // WebSocket心跳检测
    let ws_heartCheck = {
      timeout: 30000, // 30秒一次心跳
      timeoutObj: null, // 执行心跳的定时器
      serverTimeoutObj: null, // 服务器超时定时器
      // 启动方法
      start: function () {
        this.timeoutObj = setInterval(function () {
          // 这里发送一个心跳信息,后端收到后,返回一个消息,在onmessage拿到返回的心跳(信息)就说明连接正常
          that.socket.send(JSON.stringify({ funId: "voiceHeart", userId: "" }));
          // 如果超过一定时间还没重置,说明后端主动断开了
        }, this.timeout);
      },
      // 重置方法
      reset: function () {
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
        return this;
      },
    };
  }
}



async websocketonmessage(e) {
  const data = await e.data.arrayBuffer();
  const audioData = new Uint16Array(data);
  this.player.feed(audioData.buffer); // 将PCM音频数据写入pcm-player
  this.player.volume(3);  //设置音频音量大小
  this.player.continue();
},
websocketonerror(e) {
  //连接错误
  console.log("WebSocket连接发生错误");
  // 如果需要重连
  // setTimeout(() => {
  //   this.initVoiceWebSocket();
  // }, 2000);
},
websocketclose(e) {
  //连接关闭
  if (this.socket !== null) {
    this.socket.close();
  }
},
首先,我们需要添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency> <dependency> <groupId>javax.media</groupId> <artifactId>jmf</artifactId> <version>2.1.1e</version> </dependency> <dependency> <groupId>com.googlecode.jipes</groupId> <artifactId>jipes</artifactId> <version>1.4.0</version> </dependency> ``` 其中,`spring-boot-starter-websocket` 用于实现 WebSocket 通信,`spring-boot-starter-undertow` 用于提供 Web 服务,`jmf` 用于实现音频数据的解码和编码,`jipes` 用于进行音频数据的处理。 接着,我们定义一个 WebSocket 处理器类 `AudioWebSocketHandler`: ```java import javax.media.*; import javax.media.format.AudioFormat; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; @ServerEndpoint(value = "/audio") public class AudioWebSocketHandler { private static final Map<String, Player> players = new HashMap<>(); @OnOpen public void onOpen(Session session) { // 添加新的 Session 到 players 中 players.put(session.getId(), null); } @OnMessage public void onMessage(Session session, byte[] audioData, boolean last) throws IOException, NoPlayerException, CannotRealizeException, NoDataSourceException { // 获取对应的 Player Player player = players.get(session.getId()); if (player == null) { // 创建新的 Player player = Manager.createPlayer(new ByteArrayDataSource(new byte[0], "audio/basic")); player.realize(); player.start(); players.put(session.getId(), player); } // 将音频数据转化为 PCM 格式 AudioFormat format = new AudioFormat(AudioFormat.LINEAR, 8000, 16, 1); byte[] pcmData = JMFUtils.convertToPCM(audioData, format); // 将 PCM 数据写入 Player ByteBuffer buffer = ByteBuffer.wrap(pcmData); ((PushBufferStream) player.getStreams()[0]).write(buffer); // 如果是最后一帧音频数据,则关闭 Player if (last) { player.stop(); player.close(); players.remove(session.getId()); } } @OnClose public void onClose(Session session) { // 关闭对应的 Player Player player = players.get(session.getId()); if (player != null) { player.stop(); player.close(); } players.remove(session.getId()); } @OnError public void onError(Session session, Throwable throwable) { // 异常处理 throwable.printStackTrace(); } } ``` 在 `onOpen` 方法中,我们将新的 WebSocket 连接添加到 `players` 中,并初始化对应的 `Player` 对象。 在 `onMessage` 方法中,我们首先获取对应的 `Player` 对象。如果该对象不存在,则创建新的 `Player` 对象,并将其添加到 `players` 中。接着,我们将接收到的音频数据转化为 PCM 格式,并写入 `Player` 中。如果该音频数据是最后一帧,我们将关闭 `Player` 并将其从 `players` 中移除。 在 `onClose` 方法中,我们关闭对应的 `Player` 对象,并将其从 `players` 中移除。 在 `onError` 方法中,我们将异常信息打印出来。 接下来,我们创建一个工具类 `JMFUtils`,用于实现音频数据的转换: ```java import com.googlecode.jipes.AudioPipeline; import com.googlecode.jipes.SignalProcessingException; import com.googlecode.jipes.audio.AudioBuffer; import javax.media.format.AudioFormat; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; public class JMFUtils { public static byte[] convertToPCM(byte[] data, AudioFormat format) throws IOException { AudioPipeline pipeline = new AudioPipeline(); pipeline.add(new AudioBuffer(new ByteArrayInputStream(data), format)); pipeline.add(new AudioBufferConverter(format, new AudioFormat(AudioFormat.LINEAR, format.getSampleRate(), format.getSampleSizeInBits(), format.getChannels()), true)); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { pipeline.process(new AudioBuffer(outputStream, new AudioFormat(AudioFormat.LINEAR, format.getSampleRate(), format.getSampleSizeInBits(), format.getChannels()))); } catch (SignalProcessingException e) { e.printStackTrace(); } return outputStream.toByteArray(); } } ``` 在该工具类中,我们使用 `jipes` 库实现音频数据的转换。具体的转换过程为: 1. 创建 `AudioPipeline` 对象。 2. 将输入的音频数据添加到 `AudioPipeline` 中。 3. 添加一个 `AudioBufferConverter` 对象,将音频数据转化为 PCM 格式。 4. 将输出的 PCM 数据写入 `ByteArrayOutputStream` 中。 最后,我们需要在 `application.properties` 中添加以下配置: ```properties # WebSocket 相关配置 server.port=8080 server.servlet.context-path=/ # JMF 相关配置 javax.media.protocol.DataSource.protocolName=audio ``` 其中,`server.port` 用于配置服务器的端口号,`javax.media.protocol.DataSource.protocolName` 用于配置 JMF 的协议名。 至此,我们已经实现了使用 Spring Boot 开启多个 RTP 监听端口,并将接收到的 PCMA/PCMU 格式音频数据转化为 PCM 格式的功能。下面是一个 Web 页面的示例代码: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Audio Streaming</title> </head> <body> <input type="text" id="url" placeholder="Enter the RTP URL" value="rtp://127.0.0.1:8000/audio/pcma/8000"/> <button onclick="startStreaming()">Start Streaming</button> <button onclick="stopStreaming()">Stop Streaming</button> <br/><br/> <audio id="audio" controls autoplay></audio> <script> var websocket = null; var audioContext = new (window.AudioContext || window.webkitAudioContext)(); var source = null; function startStreaming() { var url = document.getElementById("url").value; websocket = new WebSocket("ws://" + window.location.hostname + ":8080" + window.location.pathname + "audio"); websocket.binaryType = "arraybuffer"; websocket.onopen = function () { websocket.send(JSON.stringify({url: url})); }; websocket.onmessage = function (event) { if (!source) { source = audioContext.createBufferSource(); audioContext.decodeAudioData(event.data, function (buffer) { source.buffer = buffer; source.connect(audioContext.destination); source.start(0); }); } else { audioContext.decodeAudioData(event.data, function (buffer) { var newSource = audioContext.createBufferSource(); newSource.buffer = buffer; newSource.connect(audioContext.destination); newSource.start(0); }); } }; } function stopStreaming() { if (websocket != null) { websocket.close(); websocket = null; } if (source != null) { source.stop(); source.disconnect(); source = null; } } </script> </body> </html> ``` 在该页面中,我们通过 WebSocket 与服务器进行通信,并可以输入 RTP URL 来启动音频流传输。我们使用 `AudioContext` 对象来播放音频数据。 最后,我们运行 Spring Boot 应用程序,并在浏览器中访问 Web 页面,即可启动音频流传输并播放音频数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值