如何在 javascript 网络客户端中播放 PCM 流(通过 UDP)?

How to play a PCM stream (over UDP) in a javascript web client?

我正在编写一个 javascript 网络客户端,我想在我的客户端中播放来自 UDP 流(原始 RTP)的编码为 PCM 的音频(我可以更改流的目标主机和端口自由)。怎么做到的?

我阅读了有关 Howler 的信息,但找不到任何 UDP 侦听选项或 PCM 支持。我也查看了 WebRTC,但它似乎不支持 PCM(如果我没看错的话)。

非常感谢!

所以我终于做到了! 我写了一个 python 服务器来将 UDP 转换为 websocket。 我使用 Quart 编写它,它就像一个异步烧瓶,以使 websocket 和 UDP 选择更容易:

import json
import struct
import socket
import asyncio
from quart import Quart, websocket

app = Quart(__name__)


class PcmServerProtocol:
    def __init__(self, queue: asyncio.Queue):
        self._queue = queue

    def connection_made(self, transport):
        self.transport = transport

    def datagram_received(self, data, addr):
        pcm_chunks = struct.unpack(
            # Convert the data to pulses
            "<" + "h" * (len(data) // struct.calcsize("h")),
            data
        )
        self._queue.put_nowait(json.dumps(pcm_chunks))


async def sending(queue: asyncio.Queue):
    while True:
        data = await queue.get()
        await websocket.send(data)


@app.websocket("/sound")
async def sound():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(("127.0.0.1", 15000))
    pcm_queue = asyncio.Queue()
    streamer = asyncio.create_task(sending(pcm_queue))
    pcm_receiver = asyncio.create_task(
        asyncio.get_event_loop().create_datagram_endpoint(
            lambda: PcmServerProtocol(pcm_queue), sock=sock
        )
    )
    await asyncio.gather(streamer, pcm_receiver)


if __name__ == "__main__":
    app.run()

现在我们只需要连接一个网络套接字并播放来自 javascript 的声音 为此,我使用了网络音频 API:

let audioCtx = new AudioContext();
let ws = new WebSocket('ws://' + document.domain + ':' + location.port + '/sound');
ws.onmessage = function (event) {
    let pcm_chunks = JSON.parse(event.data);
    let node = audioCtx.createBufferSource();
    let buffer = audioCtx.createBuffer(1, pcm_chunks.length, 44100);
    let data = buffer.getChannelData(0);
    for (let i = 0; i < pcm_chunks.length; i++) {
        // Normalize to between -1.0 and 1.0, my PCM was signed 16bit
        data[i] = pcm_chunks[i] / 32768;
    }
    node.buffer = buffer;
    node.loop = false;
    node.connect(audioCtx.destination);
    node.start(0);
};

我发现的一个缺点是通过网络进行操作会使声音滞后,即使在本地主机上设置它时效果很好。

显然,非常欢迎您发表评论并提出其他想法。


编辑:

我实际上找到了使用 vlc(或 cvlc)的更好解决方案。看来 vlc 可以获取 rtp 流并通过多种操作重新流式传输它。

cvlc rtp://source_ip:source_port --sout '#transcode{acodec=mp3}:standard{access=http,mux=mp3,dst=dest_ip:dest_port}'

启动那个进程时,客户端可以是最简单的HTML5音频标签:

<audio controls autoplay>
    <source src="http://dest_ip:dest_port" type="audio/mp3">
</audio>