You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实时语音转写与语音生成方案选型及AWS代码故障排查

实时语音转录方案选择与AWS SDK代码问题排查

一、SDK选择建议

  • 现成npm包:如果项目优先级是快速落地、减少配置成本,优先选维护活跃的成熟包(比如react-speech-recognition、assemblyai官方SDK)。这类包已经封装了麦克风权限、音频流处理、服务端通信逻辑,可靠性可以通过GitHub星标、最近更新时间、社区issue解决情况判断,适合不需要深度绑定AWS生态的场景。
  • AWS SDK(Transcribe + Polly):适合需要和AWS其他服务集成、对数据隐私/合规有严格要求的场景。但注意:前端直接暴露IAM密钥是严重安全风险,必须通过Cognito身份池或后端代理请求;浏览器端音频流格式转换、权限处理确实繁琐,需要严格对照AWS文档的参数要求。

二、你的AWS SDK代码问题与修复

核心问题点

  1. 密钥暴露风险:前端硬编码ACCESS_KEY_ID和SECRET_ACCESS_KEY会导致密钥泄露,任何人都能通过浏览器控制台获取并滥用你的AWS资源,必须立即修改。
  2. 音频流编码错误:MicrophoneStream输出的是Float32Array格式的PCM数据,你用TextEncoder编码成字符串是错误的,AWS要求的是16位有符号整数的PCM字节数组。
  3. 请求方式错误:TranscribeStreamingClient需要配合StartStreamTranscriptionCommand发送请求,并且音频流要包装成异步迭代器,不能每次data事件都单独发送请求。
  4. 事件监听逻辑错误:直接在客户端实例上绑定on("data")会导致多次点击按钮时重复监听,应该针对单次请求绑定回调。
  5. 采样率不匹配:麦克风实际采样率可能不是44100,需要动态获取并匹配AWS请求参数。

修复后的代码

import React, { useState } from "react";
import { TranscribeStreamingClient, StartStreamTranscriptionCommand } from "@aws-sdk/client-transcribe-streaming";
import { MicrophoneStream } from "microphone-stream";
// 注意:禁止硬编码永久密钥,改用Cognito身份池获取临时凭证
import { COGNITO_IDENTITY_POOL_ID } from "./transcribeGlobal.js";
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";

const region = "us-east-1";
const languageCode = "en-US";

// 使用Cognito身份池获取临时凭证,规避密钥泄露风险
const transcribeClient = new TranscribeStreamingClient({
  region,
  credentials: fromCognitoIdentityPool({
    clientConfig: { region },
    identityPoolId: COGNITO_IDENTITY_POOL_ID,
  }),
});

function TranscribeClientSpeech() {
  const [transcription, setTranscription] = useState("");
  const [isStreaming, setIsStreaming] = useState(false);
  let micStream = null;

  // 将Float32Array格式的麦克风数据转换为AWS要求的16位PCM字节数组
  const convertFloat32ToInt16 = (float32Array) => {
    const int16Array = new Int16Array(float32Array.length);
    for (let i = 0; i < float32Array.length; i++) {
      const val = Math.max(-1, Math.min(1, float32Array[i]));
      int16Array[i] = val < 0 ? val * 0x8000 : val * 0x7fff;
    }
    return int16Array.buffer;
  };

  const startTranscription = async () => {
    if (isStreaming) return;
    setIsStreaming(true);

    try {
      const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
      // 动态获取麦克风实际采样率,避免参数不匹配
      const sampleRate = mediaStream.getAudioTracks()[0].getSettings().sampleRate || 44100;
      micStream = new MicrophoneStream(mediaStream, { sampleRate });

      // 将麦克风流包装为异步迭代器,符合AWS API输入要求
      const audioStream = async function* () {
        for await (const chunk of micStream) {
          yield {
            AudioEvent: {
              AudioChunk: convertFloat32ToInt16(chunk),
            },
          };
        }
      };

      const command = new StartStreamTranscriptionCommand({
        LanguageCode: languageCode,
        MediaEncoding: "pcm",
        SampleRateHertz: sampleRate,
        AudioStream: audioStream(),
      });

      // 发送请求并遍历响应流处理转录结果
      const response = await transcribeClient.send(command);
      for await (const event of response.TranscriptResultStream) {
        if (event.TranscriptEvent) {
          const results = event.TranscriptEvent.Transcript.Results;
          // 仅拼接非部分结果(完整的转录片段)
          if (results.length > 0 && !results[0].IsPartial) {
            const newTranscript = results[0].Alternatives[0].Transcript;
            setTranscription(prev => prev + " " + newTranscript);
          }
        }
      }
    } catch (err) {
      console.error("转录错误:", err);
      setIsStreaming(false);
    }
  };

  const stopTranscription = () => {
    if (micStream) {
      micStream.stop();
      micStream = null;
    }
    setIsStreaming(false);
  };

  return (
    <div>
      <h1>实时转录</h1>
      <p>{transcription}</p>
      {!isStreaming ? (
        <button onClick={startTranscription}>开始转录</button>
      ) : (
        <button onClick={stopTranscription}>停止转录</button>
      )}
    </div>
  );
}

export default TranscribeClientSpeech;

关键修改说明

  • 凭证安全:改用fromCognitoIdentityPool从Cognito身份池获取临时凭证,彻底避免硬编码永久密钥的风险。
  • 音频格式转换:新增convertFloat32ToInt16函数,将麦克风输出的Float32Array转换为AWS Transcribe要求的16位PCM字节数组。
  • 异步迭代器包装:将麦克风流包装成异步迭代器,符合AWS Streaming API的输入规范。
  • 响应流处理:使用for await...of遍历响应流,区分部分结果和最终结果,避免重复拼接无效内容。
  • 采样率动态匹配:从麦克风设备获取实际采样率,确保和请求参数一致,避免因格式不匹配导致转录失败。

内容的提问来源于stack exchange,提问作者JustDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 17:12:48