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

Node.js基于HTTP/2调用Amazon Transcribe Streaming无响应问题求助

解决Node.js HTTP/2调用Amazon Transcribe Streaming Service的问题

我来帮你梳理下你遇到的几个关键问题,以及对应的修复方案:

1. 端点拼写修正

首先确认端点的正确格式:https://transcribestreaming.us-east-1.amazonaws.com,注意是transcribestreaming(没有中间的连字符),你初期遇到的404错误就是因为端点拼写错误。

2. 核心问题:Event Stream编码与Content-Type

Amazon Transcribe Streaming要求请求体使用Event Stream Encoding,对应的Content-Type必须设置为application/vnd.amazon.eventstream,而不是application/json——这也是你遇到SerializationException和UnknownOperationException的核心原因。

Event Stream格式每个事件需要包含:

  • 4字节的总长度(不包含自身的长度字段)
  • 头部块(键值对结构,每个键值对包含键长度、键、值类型、值长度、值)
  • 数据块(JSON格式的AudioEvent)
  • 4字节的CRC校验(可选,但建议包含)

3. 修正后的完整代码示例

下面是调整后的代码,包含Event Stream的简单封装,以及正确的请求配置:

const http2 = require('http2');
const aws4 = require('aws4');
const crypto = require('crypto');

// 配置信息
const config = {
  accessKeyId: '<你的AWS Access Key>',
  secretAccessKey: '<你的AWS Secret Key>',
  region: 'us-east-1',
  endpoint: `https://transcribestreaming.${config.region}.amazonaws.com`,
  languageCode: 'en-US',
  mediaEncoding: 'pcm',
  sampleRate: 44100
};

// 封装Event Stream格式的AudioEvent
function wrapAudioEvent(audioChunk) {
  // 构造AudioEvent的JSON数据,PCM音频需转Base64
  const audioEventJson = JSON.stringify({
    AudioStream: {
      AudioEvent: {
        AudioChunk: audioChunk.toString('base64')
      }
    }
  });

  // 定义头部信息:指定内容类型为application/json
  const headers = [
    { key: ':content-type', value: 'application/json', type: 0 }
  ];

  // 计算头部块总长度
  let headerBlockLength = 0;
  headers.forEach(h => {
    headerBlockLength += 1 + Buffer.from(h.key).length + 1 + 4 + Buffer.from(h.value).length;
  });

  // 计算事件总长度:长度字段(4) + 头部块 + 数据 + CRC(4)
  const totalLength = 4 + headerBlockLength + audioEventJson.length + 4;

  // 构造头部块Buffer
  const headerBuffer = Buffer.alloc(headerBlockLength);
  let offset = 0;
  headers.forEach(h => {
    const keyBuf = Buffer.from(h.key);
    headerBuffer.writeUInt8(keyBuf.length, offset); // 键长度(1字节)
    offset += 1;
    keyBuf.copy(headerBuffer, offset); // 键内容
    offset += keyBuf.length;
    headerBuffer.writeUInt8(h.type, offset); // 值类型(0=字符串)
    offset += 1;
    const valueBuf = Buffer.from(h.value);
    headerBuffer.writeUInt32BE(valueBuf.length, offset); // 值长度(4字节)
    offset += 4;
    valueBuf.copy(headerBuffer, offset); // 值内容
    offset += valueBuf.length;
  });

  // 构造整个Event Stream块
  const eventBuffer = Buffer.alloc(totalLength);
  eventBuffer.writeUInt32BE(totalLength - 4, 0); // 总长度(不包含自身4字节)
  headerBuffer.copy(eventBuffer, 4); // 写入头部块
  Buffer.from(audioEventJson).copy(eventBuffer, 4 + headerBlockLength); // 写入数据块
  // 计算并写入CRC校验
  const crc = crypto.createHash('crc32').update(eventBuffer.slice(0, totalLength - 4)).digest();
  crc.copy(eventBuffer, totalLength - 4);

  return eventBuffer;
}

// 生成签名请求
const opts = {
  service: 'transcribe',
  region: config.region,
  path: '/stream-transcription',
  method: 'POST',
  headers: {
    'content-type': 'application/vnd.amazon.eventstream',
    'x-amz-target': 'com.amazonaws.transcribe.Transcribe.StartStreamTranscription',
    'x-amz-transcribe-language-code': config.languageCode,
    'x-amz-transcribe-media-encoding': config.mediaEncoding,
    'x-amz-transcribe-sample-rate': config.sampleRate.toString(),
    'x-amz-content-sha256': 'STREAMING-AWS4-HMAC-SHA256-EVENTS'
  }
};

const signedOpts = aws4.sign(opts, {
  accessKeyId: config.accessKeyId,
  secretAccessKey: config.secretAccessKey
});

// 建立HTTP/2连接
const client = http2.connect(config.endpoint);
client.on('error', (err) => {
  console.error('HTTP/2连接错误:', err);
});

// 发起请求
const req = client.request({
  ':method': 'POST',
  ':path': '/stream-transcription',
  'authorization': signedOpts.headers.Authorization,
  'content-type': 'application/vnd.amazon.eventstream',
  'x-amz-content-sha256': 'STREAMING-AWS4-HMAC-SHA256-EVENTS',
  'x-amz-target': 'com.amazonaws.transcribe.Transcribe.StartStreamTranscription',
  'x-amz-date': signedOpts.headers['X-Amz-Date'],
  'x-amz-transcribe-language-code': config.languageCode,
  'x-amz-transcribe-media-encoding': config.mediaEncoding,
  'x-amz-transcribe-sample-rate': config.sampleRate.toString(),
  ...(signedOpts.headers['x-amz-security-token'] ? { 'x-amz-security-token': signedOpts.headers['x-amz-security-token'] } : {})
});

// 处理响应
req.on('response', (headers) => {
  console.log('响应头部:', headers);
});

let responseData = '';
req.on('data', (chunk) => {
  responseData += chunk.toString();
  // 实际使用时需要解码返回的Event Stream格式数据,此处仅打印原始片段
  console.log('收到响应片段:', chunk.toString());
});

req.on('end', () => {
  console.log('响应结束:', responseData);
  client.close();
});

req.on('error', (err) => {
  console.error('请求错误:', err);
});

// 示例:发送音频数据(替换为你的原始PCM音频Buffer)
const audioBufferData = Buffer.from(/* 你的PCM音频数据 */);
const wrappedEvent = wrapAudioEvent(audioBufferData);
req.write(wrappedEvent);

// 音频流结束后调用end()
// req.end();

4. 关键注意事项

  • AudioChunk编码:原始PCM音频必须转成Base64字符串,放在AudioEvent的AudioChunk字段中。
  • Event Stream解码:Transcribe返回的响应也是Event Stream格式,你需要按照相同规范解码才能获取结构化的转录结果,上面的代码仅做了原始数据打印,实际使用时需补充解码逻辑。
  • 流式发送:如果是实时音频流,需要持续调用req.write(wrapAudioEvent(audioChunk))发送音频片段,在音频结束时调用req.end()。
  • 权限验证:确保你的AWS账号拥有transcribe:StartStreamTranscription权限,且Access Key和Secret Key配置正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 06:28:47