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
相关产品推荐
相关产品推荐

