如何将音频逐块流式传输到浏览器?

问题描述 投票:0回答:1

我正在尝试制作一个网络广播电台,我想经常自由地更换歌曲和叠加声音。我想对音频进行速率限制,以便可以在发送之前更改提要。如果可能的话,我也想提供连续的内容

到目前为止,我们对 websockets 的尝试已经接近成功,但质量有点混乱:

这是代码:

server.js

const express     = require('express');
const app         = express()
const http        = require('http')
const server      = http.createServer(app)
const { Server }  = require("socket.io")
const io          = new Server(server)
const fs          = require('fs')

const SRC_PATH      = 'src.wav'
const PACKET_SIZE   = 6400
let   PACKET = 0

function getpacket(socket){
    const file_descriptor     = fs.openSync(SRC_PATH, 'r', null)
    const read_offset         = PACKET * PACKET_SIZE
    const buffer              = Buffer.alloc(PACKET_SIZE)
    const buffer_write_offset = 0
    const num_bytes_to_read   = PACKET_SIZE
    const num_bytes_read      = fs.readSync(file_descriptor, buffer, buffer_write_offset, num_bytes_to_read, read_offset)
    fs.closeSync(file_descriptor)
    console.log(`Sending packet ${PACKET}`)
    socket.emit("data", buffer)
    PACKET++
}

app.use('/', express.static('.'))

io.on('connection', (socket) => {
    console.log("connected...")
    socket.on("get", ()=>{getpacket(socket)})
})

server.listen(3000, () => {
  console.log('listening on *:3000');
})

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Testing</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
</head>

<body>
    <div onclick="listen()">Click to Listen</div>

    <script>
    const socketio     = io()
    const SAMPLE_RATE  = 32000 // samples/second
    
    async function listen(){
        // Set up the new audio context
        const audioContext = new AudioContext()

        socketio.once("data", (arrayBuff)=>{
            const buffer = new Uint8Array(arrayBuff)
            addTobuffer(buffer, audioContext)
        })

        requestData()
    }

    function requestData(){
        socketio.emit("get")
    }

    async function addTobuffer(data, audioContext){
        // Set up the new audio source
        const audioSource  = await audioContext.createBufferSource()
        // create audio buffer from data,
        const audioBuffer  = await createAudioBuffer(audioContext,data) 
        // Asign the data buffer to the audioSource
        audioSource.buffer = audioBuffer
        // Connect the audio source to the audio context
        audioSource.connect(audioContext.destination)

        audioSource.start(0)
        // wait until just before the end and then get more data
        const packetLength    = (data.length/SAMPLE_RATE)*1000-10
        await new Promise(resolve=>setTimeout(resolve,packetLength))

        socketio.once("data", (arrayBuff)=>{
            const buffer = new Uint8Array(arrayBuff)
            addTobuffer(buffer, audioContext)
        })
        requestData()
    }

    async function createAudioBuffer(audioContext,data){
        /* uint8 pcm to float */
        const number_of_channels = 1
        const number_of_bytes    = data.length
        const audioBuffer        = audioContext.createBuffer(number_of_channels, number_of_bytes, SAMPLE_RATE)
        const nowBuffering       = audioBuffer.getChannelData(0)
        for (let index=0; index<number_of_bytes;index++){
            const thirtytwofloat = new Float32Array(1)
            thirtytwofloat[0]    = (data[index]-(255/2))/255
            nowBuffering[index]  = thirtytwofloat[0]
        }
        return audioBuffer
    }

    </script>

</body>
</html>

并生成格式奇怪的 PCM WAV:

ffmpeg -i src.mp3 -ar 32000 -ac 1 -acodec pcm_u8 src.wav

有没有办法获得更清晰的音频输出?

javascript node.js streaming audio-streaming web-audio-api
1个回答
5
投票

好的,所以看起来像许多广播电台,如 Today's Hits (可以在 howler.js 广播示例中找到),使用 live 流媒体标准

流式传输音频

首先,它们发送一条 http 消息“内容类型:音频/mpeg”,以及一些其他消息,例如 Transfer-Encoding,以向浏览器发出流即将到来的信号并保持与服务器的连接。如果我们查看 safari 上开发者控制台的网络选项卡并检查请求,我们可以看到 http 消息

根据 MDN 文档 有几种标准化的流媒体格式,wav 目前看起来不是其中之一,但 mp3 是

这是一个工作示例...
服务器.js

const EventEmitter = require('events')
const schedule     = require('node-schedule')
const express      = require('express')
const http         = require("http")
const fs           = require('fs')

const app          = express()

const SAMPLE_SIZE = 32000                         // samples/sec
const PACKET_SIZE = SAMPLE_SIZE                   // 1 second worth of data
const UPDATE_TIME = '* * * * * *'                 // every second
const PATH        = "./src.mp3"
let   PACKET_NUM  = 0
const eventEmitter= new EventEmitter ()

async function getpacket(req,res){
  const file_descriptor     = fs.openSync(PATH, 'r', null)
  const read_offset         = PACKET_NUM * PACKET_SIZE
  const buffer              = Buffer.alloc(PACKET_SIZE)
  const buffer_write_offset = 0
  const num_bytes_to_read   = PACKET_SIZE
  const num_bytes_read      = fs.readSync(file_descriptor, buffer, buffer_write_offset, num_bytes_to_read, read_offset)
  fs.closeSync(file_descriptor)
  console.log(`Sending packet ${PACKET_NUM} to ${req.socket.remoteAddress}`) // safari sometimes requests two streams at the same time
  res.write(buffer)
}

app.get("/", (req,res)=>{
  res.sendFile("index.html",{root: '.'})
})

app.get("/src.mp3", async (req,res)=>{
    res.writeHead(200,"OK",{"Content-Type":"audio/mpeg"})

    const updateHandler = () =>{ getpacket(req,res) }
    eventEmitter.on("update", updateHandler) // On update event, send another packet

    req.socket.on("close",()=>{
      eventEmitter.removeListener("update",updateHandler)
      console.log(`Client ${req.socket.remoteAddress} disconected from server`)
    })
})

// This creates a schedule to make an update event on every second
schedule.scheduleJob(UPDATE_TIME, function(){
  PACKET_NUM+=1
  eventEmitter.emit("update")
})

const server = http.createServer(app)
server.listen(3000)

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Testing</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>

<body>
    <audio controls id="music"></audio>

    <div onclick="listen()">Click to Listen</div>
    <div onclick="stop()">Click to Stop</div>

    <script>    
    async function listen(){
       let music = document.getElementById('music')
       music.src="/src.mp3"
       music.play()
    }

    async function stop(){
       let music = document.getElementById('music')
       music.pause()
       music.src=""
    }
    </script>

</body>
</html>

并从 wav 文件生成 mp3 文件

ffmpeg -i src.wav -ab 128k src.mp3

如果 server.js 在您的计算机上运行,您可以访问 http://localhost:3000/src.mp3 查看实时流,然后访问 http://localhost:3000/ 查看示例浏览器支持

可以使用 ffmpeg 实时更新 src.mp3

软件包和有用的资源

实时编辑的一些 ffmpeg 见解

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.