通过 socket.io 流式传输实时音频

     2023-05-06     52

关键词:

【中文标题】通过 socket.io 流式传输实时音频【英文标题】:Stream realtime audio over socket.io 【发布时间】:2021-11-15 03:24:57 【问题描述】:

如何使用 socket.io 将实时音频从一个客户端流式传输到可能的多个客户端?

我已经到了可以在同一个标​​签中录制音频和播放音频的地步。

这是我目前的代码:

$(document).ready(function () 
    var socket = io("ws://127.0.0.1:4385");

    if (navigator.mediaDevices) 
        console.log('getUserMedia supported.');

        var constraints =  audio: true ;

        navigator.mediaDevices.getUserMedia(constraints)
            .then(function (stream) 

                let ctx = new AudioContext();
                let source = ctx.createMediaStreamSource(stream);
                let destination = ctx.createMediaStreamDestination();
                source.connect(ctx.destination);
            )
            .catch(function (err) 
                console.log('The following error occurred: ' + err);
            )
    
);

如何将该音频流发送到我的 socket.io 服务器,然后再发送回另一个客户端?

我听说过 WebRTC,但我不想要点对点解决方案,因为如果有多个客户端想要收听音频,这会给客户端带来负担。

必须有一种方法可以检索原始音频数据并将其发送到我的 socket.io 服务器,然后再将其发送回想要收听它的客户端。

【问题讨论】:

【参考方案1】:

经过多次反复试验,我找到了一个令我满意的解决方案。 这是客户端javascript。服务器端 socket.io 服务器只是将数据转发给正确的客户端,应该是微不足道的。

里面也有一些前端的东西。无视就好。

main.js

var socket;
var ctx;
var playbackBuffers = ;
var audioWorkletNodes = ;
var isMuted = true;

$(document).ready(function () 

    $('#login-form').on('submit', function (e) 
        e.preventDefault();
        $('#login-view').hide();
        $('#content-view').show();
        connectToVoiceServer($('#username').val());
        createAudioContext();

        $('#mute-toggle').click(function () 
            isMuted = !isMuted;
            if (isMuted) 
                $('#mute-toggle').html('<i class="bi bi-mic-mute"></i>');
             else 
                $('#mute-toggle').html('<i class="bi bi-mic"></i>');
            
        );

        if (navigator.mediaDevices) 
            setupRecordWorklet();
         else 
            // TODO: Display warning can not access microphone
        
    );
);

function setupRecordWorklet() 
    navigator.mediaDevices.getUserMedia( audio: true )
        .then(async function (stream) 
            await ctx.audioWorklet.addModule('./js/record-processor.js');
            let src = ctx.createMediaStreamSource(stream);

            const processor = new AudioWorkletNode(ctx, 'record-processor');

            let recordBuffer;
            processor.port.onmessage = (e) => 
                if (e.data.eventType === 'buffer') 
                    recordBuffer = new Float32Array(e.data.buffer);
                
                if (e.data.eventType === 'data' && !isMuted) 
                    socket.volatile.emit('voice',  id: socket.id, buffer: recordBuffer.slice(e.data.start, e.data.end).buffer );
                
            
            src.connect(processor);
        )
        .catch(function (err) 
            console.log('The following error occurred: ' + err);
        );

    socket.on('voice', data => 
        if (playbackBuffers[data.id]) 
            let buffer = new Float32Array(data.buffer);
            playbackBuffers[data.id].buffer.set(buffer, playbackBuffers[data.id].cursor);
            playbackBuffers[data.id].cursor += buffer.length;
            playbackBuffers[data.id].cursor %= buffer.length * 4;
        
    );


function createAudioContext() 
    ctx = new AudioContext();


function connectToVoiceServer(username) 
    socket = io("wss://example.com",  query: `username=$username` );

    socket.on("connect", function () 

    );

    socket.on('user:connect', function (user) 
        addUser(user.id, user.username);
    );

    socket.on('user:disconnect', function (id) 
        removeUser(id);
    );

    socket.on('user:list', function (users) 
        users.forEach(function (user) 
            addUser(user.id, user.username);
        );
    );


function addUser(id, username) 
    $('#user-list').append(`<li id="$id" class="list-group-item text-truncate">$username</li>`);
    addUserAudio(id);


function removeUser(id) 
    $('#' + id).remove();
    removeUserAudio(id);


async function addUserAudio(id) 
    await ctx.audioWorklet.addModule('./js/playback-processor.js');
    audioWorkletNodes[id] = new AudioWorkletNode(ctx, 'playback-processor');

    audioWorkletNodes[id].port.onmessage = (e) => 
        if (e.data.eventType === 'buffer') 
            playbackBuffers[id] =  cursor: 0, buffer: new Float32Array(e.data.buffer) ;
        
    

    audioWorkletNodes[id].connect(ctx.destination);


function removeUserAudio(id) 
    audioWorkletNodes[id].disconnect();
    audioWorkletNodes[id] = undefined;
    playbackBuffers[id] = undefined;


record-processor.js

class RecordProcessor extends AudioWorkletProcessor 

    constructor() 
        super();
        this._cursor = 0;
        this._bufferSize = 8192 * 4;
        this._sharedBuffer = new SharedArrayBuffer(this._bufferSize);
        this._sharedView = new Float32Array(this._sharedBuffer);
        this.port.postMessage(
            eventType: 'buffer',
            buffer: this._sharedBuffer
        );
    

    process(inputs, outputs) 

        for (let i = 0; i < inputs[0][0].length; i++) 
            this._sharedView[(i + this._cursor) % this._sharedView.length] = inputs[0][0][i];
        

        if (((this._cursor + inputs[0][0].length) % (this._sharedView.length / 4)) === 0) 
            this.port.postMessage(
                eventType: 'data',
                start: this._cursor - this._sharedView.length / 4 + inputs[0][0].length,
                end: this._cursor + inputs[0][0].length
            );
        

        this._cursor += inputs[0][0].length;
        this._cursor %= this._sharedView.length;

        return true;
    


registerProcessor('record-processor', RecordProcessor);

playback-processor.js

class PlaybackProcessor extends AudioWorkletProcessor 

    constructor() 
        super();
        this._cursor = 0;
        this._bufferSize = 8192 * 4;
        this._sharedBuffer = new SharedArrayBuffer(this._bufferSize);
        this._sharedView = new Float32Array(this._sharedBuffer);
        this.port.postMessage(
            eventType: 'buffer',
            buffer: this._sharedBuffer
        );
    

    process(inputs, outputs) 

        for (let i = 0; i < outputs[0][0].length; i++) 
            outputs[0][0][i] = this._sharedView[i + this._cursor];
            this._sharedView[i + this._cursor] = 0;
        

        this._cursor += outputs[0][0].length;
        this._cursor %= this._sharedView.length;

        return true;
    


registerProcessor('playback-processor', PlaybackProcessor);

注意事项:

    我正在使用 SharedArrayBuffers 读取/写入 AudioWorklet。为了使它们正常工作,您的服务器必须提供带有标题的网页:Cross-Origin-Opener-Policy=same-originCross-Origin-Embedder-Policy=require-corp 这将传输未压缩的非交错 IEEE754 32 位线性 PCM 音频。因此通过网络传输的数据将是巨大的。必须添加压缩! 假设发送方和接收方的采样率相同

【讨论】:

如何实时流式传输音频文件

...。代理音频工作正常(音频源实时连接并发送音频,然后通过HTTP传输到客户端),但是当我尝试流式传输音频文件时,它会快速通过-客户端最终将整个音频文件包含在其中他们的本地缓冲区。我希望他们在本地缓冲区 查看详情

流式传输实时音频

...】:我希望在android设备上具有实时音频流功能,该功能通过设备的MIC捕获音频并将其发送到服务器。我知道在录制后发送音频文件,但在实时情况下我需要帮助。可能可以通过不断地向服务器发送字节数组来完成。如果是这样... 查看详情

Qt 通过 TCP 套接字实时流式传输音频

】Qt通过TCP套接字实时流式传输音频【英文标题】:QtStreamAudioOverTCPSocketInRealtime【发布时间】:2014-10-1908:20:09【问题描述】:我试图弄清楚如何通过Qt中的TCP套接字实时传输音频。我使用的是客户端上的QAudioInput和服务器上的QAudio... 查看详情

通过 socket.io 1.0 实时音频

】通过socket.io1.0实时音频【英文标题】:Liveaudioviasocket.io1.0【发布时间】:2014-09-1212:25:05【问题描述】:来自socket.io网站二进制流从1.0开始,可以来回发送任何blob:图像、音频、视频。我现在想知道,如果这不能成为我最近想... 查看详情

节点(套接字)实时音频流/广播

...706:35:40【问题描述】:请问,是否有任何简单的方法可以通过NODE.js和可能的SOCKET.IO将媒体文件(ogg、mp3、spx..)从服务器流式传输(广播)到客户端(浏览器)?我必须在服务器端录制音频输入,然后才能为许多客户端实时播放... 查看详情

在 node.js 中使用 socket.io 通过 webrtc 广播实时音频

】在node.js中使用socket.io通过webrtc广播实时音频【英文标题】:Broadcastingliveaudiothroughwebrtcusingsocket.ioinnode.js【发布时间】:2015-08-1119:02:59【问题描述】:我正在尝试使用webrtc通过getUserMedia()获取音频并使用socket.io将其发送到服务器... 查看详情

如何使用 node.js 和 socket.io 通过 WebSockets 流式传输 MP3 数据?

】如何使用node.js和socket.io通过WebSockets流式传输MP3数据?【英文标题】:HowtostreamMP3dataviaWebSocketswithnode.jsandsocket.io?【发布时间】:2012-02-0407:13:22【问题描述】:我在使用node.js和socket.io通过WebSocket传输MP3数据时遇到问题。一切似... 查看详情

socket.io 流式传输二进制数据

...器,我需要向客户端发送一个字节缓冲区。我知道这可以通过首先将字节缓冲区转换为base64并将其发送,然后将其转换回客户端来完成。但我想知道是否有更优雅的方式将字节流发送到客户端。【问题讨论】 查看详情

将实时 Android 音频流式传输到服务器

】将实时Android音频流式传输到服务器【英文标题】:StreamLiveAndroidAudiotoServer【发布时间】:2013-02-2707:15:42【问题描述】:我目前正在尝试将实时麦克风音频从Android设备流式传输到Java程序。我开始在两个安卓设备之间发送实时音... 查看详情

使用 Flash 流式传输实时音频

】使用Flash流式传输实时音频【英文标题】:StreamingliveaudiowithFlash【发布时间】:2010-10-2301:30:48【问题描述】:有人联系我,希望我在德克萨斯州埃尔帕索建立一个专注于本地音乐场景的互联网广播电台。我查看了各种选项,但... 查看详情

具有不同长度的音频文件的 HTTP 实时流式传输

】具有不同长度的音频文件的HTTP实时流式传输【英文标题】:HTTPLiveStreamingwithaudiofilesofvaryinglengths【发布时间】:2011-08-1302:33:07【问题描述】:我正在尝试使用Apple的HTTPLiveStreaming协议将音频流式传输到iOS和Safari客户端。与HTTPLive... 查看详情

Socket.io 没有流式传输到多个连接

】Socket.io没有流式传输到多个连接【英文标题】:Socket.ionotstreamingtomultipleconnections【发布时间】:2012-10-3112:04:52【问题描述】:我正在尝试将推文流式传输到客户端并且一切正常,只要只有一个连接。如果我在浏览器的一个选项... 查看详情

使用 FFMPEG 到网络音频 api 的实时流式传输

】使用FFMPEG到网络音频api的实时流式传输【英文标题】:LivestreamingusingFFMPEGtowebaudioapi【发布时间】:2014-02-0721:16:16【问题描述】:我正在尝试使用node.js+ffmpeg将音频流式传输到仅使用网络音频api在LAN中连接的浏览器。不使用元素... 查看详情

从 URL 实时流式传输音频的 RadioKit 替代方案

】从URL实时流式传输音频的RadioKit替代方案【英文标题】:RadioKitAlternativeForLiveStreamingAudioFromURL【发布时间】:2012-09-2815:36:31【问题描述】:我正在阅读RadioKit我喜欢他们使用的东西,它适用于我的ShoutCast流,但它的价格比每个应... 查看详情

ExoPlayer 无法播放 Adob​​e 实时流编码器流式传输的音频/视频 (RTMP)

】ExoPlayer无法播放Adob​​e实时流编码器流式传输的音频/视频(RTMP)【英文标题】:ExoPlayercan\'tplayaudio/videostreamed(RTMP)byAdobelivestreamencoder【发布时间】:2019-02-2408:04:25【问题描述】:我正在使用AdobeLiveStreamEncoder在服务器端流式传输... 查看详情

将通过 HTTP 流式传输的 wav 实时转换为 mp3

】将通过HTTP流式传输的wav实时转换为mp3【英文标题】:ConvertwavstreamedoverHTTPtomp3,inreal-time【发布时间】:2014-06-1316:53:22【问题描述】:背景:我正在使用一项服务,该服务返回MIME类型为audio/wav的数据。我需要为此音频提供播放机... 查看详情

通过 NodeJS 流式传输音频的最快方法

】通过NodeJS流式传输音频的最快方法【英文标题】:FastestwaytostreamaudioviaNodeJS【发布时间】:2018-01-2014:59:24【问题描述】:我所有的音频文件都来自外部来源,即GoogleCloudStorage。并且所有文件都有一个公共URL,无需授权令牌即可... 查看详情

GStreamer:通过网络流式传输 vorbis 编码的音频

】GStreamer:通过网络流式传输vorbis编码的音频【英文标题】:GStreamer:Streamvorbis-encodedaudioovernetwork【发布时间】:2012-12-1722:22:42【问题描述】:我想编写一个通过网络传输音频和视频的应用程序。作为一个简单的开始,我想尝试... 查看详情