使用 RecordRTC.js 库流式传输音频

RecordRTC.js 是一个方便的 JavaScript 库,可让您直接从浏览器录制音频和视频。通过使用 WebRTC 技术,它能让捕捉音频、视频甚至屏幕录制等媒体流变得超级简单。

您可以将录音保存为 WAV、WebM 和 MP3 等多种格式,使其成为满足不同需求的多功能工具。其用户友好的 API 简化了启动、停止和保存录音的过程,使其成为希望将录音功能集成到 Web 应用程序中的开发人员的绝佳选择。

前端代码

"use client";
import React, { useState, useRef } from "react";
import RecordRTC from "recordrtc";
import { v4 as uuidv4 } from "uuid";

const Mic = () => {
  const [isRecording, setIsRecording] = useState(false);
  const recorderRef = useRef<RecordRTC | null>(null);
  const wsRef = useRef<WebSocket | null>(null);

  const startRecording = () => {
    const sessionId = uuidv4();
    const messageId = uuidv4();
    const userId = uuidv4();

    navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
      const ws = new WebSocket("ws://localhost:8080");
      ws.onopen = () => {
        ws.send(
          JSON.stringify({
            type: "start_audio",
            session_id: sessionId,
            message_id: messageId,
            user_id: userId,
          })
        );

        const recorder = new RecordRTC(stream, {
          type: "audio",
          recorderType: RecordRTC.StereoAudioRecorder,
          mimeType: "audio/wav",
          timeSlice: 100,
          sampleRate: 48000,
          numberOfAudioChannels: 2,
          ondataavailable: (blob) => {
            console.log("ondataavailable", blob);
            const reader = new FileReader();
            reader.onloadend = () => {
              const base64data = reader.result;
              ws.send(
                JSON.stringify({
                  type: "stream_audio",
                  audio_data: base64data,
                })
              );
            };
            reader.readAsDataURL(blob);
          },
        });

        recorder.startRecording();
        recorderRef.current = recorder;
        wsRef.current = ws;
        setIsRecording(true);
      };
    });
  };

  const stopRecording = () => {
    if (recorderRef.current && wsRef.current) {
      recorderRef.current.stopRecording(() => {
        wsRef.current?.send(
          JSON.stringify({
            type: "stop_audio",
          })
        );
        wsRef.current?.close();
        setIsRecording(false);
      });
    }
  };

  return (
    <div>
      <button onClick={isRecording ? stopRecording : startRecording}>
        {isRecording ? "Stop Recording" : "Start Recording"}
      </button>
    </div>
  );
};

export default Mic;

在前端,我使用了 Nextjs 和应用路由器,并创建了一个名为 Mic 的简单组件。创建了RecordRTC一个新的实例来处理来自媒体流的音频录制。RecordRTC实例的配置通过各种属性和回调函数详细说明。

type: "audio":指定录音类型为音频。

recorderType: RecordRTC.StereoAudioRecorder:表示使用StereoAudioRecorder录制立体声音频。本录音机适合录制高品质音频。

mimeType: "audio/wav":设置录制音频的 MIME 类型为 WAV,这是一种常见的未压缩音频格式。

timeSlice: 100:将录音机配置为每 100 毫秒发出音频数据块。这对于实时流式传输非常有用,因为它允许应用程序频繁处理较小的音频数据块。

sampleRate: 48000:指定录音的采样率。48,000 Hz 的采样率是高质量录音的标准,可捕捉各种音频频率。

numberOfAudioChannels: 2:将音频通道数设置为 2,表示音频将以立体声录制。

ondataavailable每次有新的音频数据块可用时(每 100 毫秒,由 timeSlice定义)都会调用此函数。回调处理流式传输方面,读取二进制音频数据,将其编码为 Base64,并通过 WebSocket 发送。

后端代码

此后端代码使用 Node.js 设置 WebSocket 服务器来处理音频流。服务器从客户端以块的形式接收音频数据,将这些块临时存储为 WAV 文件,然后使用 SoX(Sound eXchange)将它们连接成单个音频文件。

const fs = require("fs");
const path = require("path");
const WebSocket = require("ws");
const { exec } = require("child_process");

const wss = new WebSocket.Server({ port: 8080 });

wss.on("connection", (ws) => {
  ws.audioDataArray = []; // Initialize an array to store audio data
  ws.tempFiles = []; // Initialize an array to store temp file paths

  ws.on("message", (message) => {
    const data = JSON.parse(message);
    if (data.type === "start_audio") {
      ws.sessionId = data.session_id;
      ws.messageId = data.message_id;
      ws.userId = data.user_id;
      console.log(`Started audio stream for session: ${ws.sessionId}`);
    } else if (data.type === "stop_audio") {
      console.log(`Stopped audio stream for session: ${ws.sessionId}`);

      // Create a temporary directory to store the chunks
      const tempDir = path.join(__dirname, "temp");
      if (!fs.existsSync(tempDir)) {
        fs.mkdirSync(tempDir);
      }

      // Write each chunk to a temporary file
      ws.audioDataArray.forEach((buffer, index) => {
        const tempFilePath = path.join(tempDir, `chunk-${index}.wav`);
        fs.writeFileSync(tempFilePath, buffer);
        ws.tempFiles.push(tempFilePath);
      });

      // Concatenate the chunks using SoX
      const combinedFilePath = path.join(
        __dirname,
        "uploads",
        `${ws.userId}-${ws.sessionId}-${ws.messageId}-combined.wav`
      );
      const soxCommand = `sox ${ws.tempFiles.join(" ")} ${combinedFilePath}`;

      exec(soxCommand, (error, stdout, stderr) => {
        if (error) {
          console.error(`Error executing sox: ${error}`);
          return;
        }
        console.log(`Combined audio saved to ${combinedFilePath}`);
        console.log(stdout);
        console.error(stderr);

        // Clean up temporary files
        ws.tempFiles.forEach((filePath) => {
          fs.unlinkSync(filePath);
        });
      });
    } else if (data.type === "stream_audio") {
      console.log("Received data:", data); // Log the entire data object
      if (typeof data.audio_data === "string") {
        const audioData = Buffer.from(data.audio_data.split(",")[1], "base64");
        console.log(`Received audio data chunk of length: ${audioData.length}`);
        ws.audioDataArray.push(audioData); // Store audio data in the array
      } else {
        console.error("Invalid audio data format");
      }
    }
  });
});

我之所以选择 SoX 来处理音频块,是因为当我尝试连接数据块并处理音频文件时,在每个数据块之后都会遇到咔哒声。我将进一步研究这个问题,并在今后更新文章。

作者:Kusal Kalinga

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论