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

Azure Speech SDK ConversationTranscriber 前后端音频流转配置问题

实时音频流适配Azure认知语音服务的可行方案

针对你遇到的前端MediaRecorder输出格式与Azure ConversationTranscriber不兼容的问题,以下是两种高效的解决思路,无需依赖FFmpeg实时转码:

一、优先方案:WebM/OPUS → 提取OPUS裸流直接适配OGG/OPUS格式

Azure认知语音服务原生支持OGG容器的OPUS编码,而前端MediaRecorder生成的WebM/OPUS只是容器不同,编码本身完全一致。只需在后端解析WebM分片,提取OPUS裸流即可直接发送给服务。

前端代码(采集并发送WebM/OPUS分片)

// 强制单声道、16kHz采样率(Azure兼容参数)
const constraints = {
  audio: {
    channelCount: 1,
    sampleRate: 16000,
    echoCancellation: true
  }
};

const mediaStream = await navigator.mediaDevices.getUserMedia(constraints);
// 指定WebM/OPUS格式
const recorder = new MediaRecorder(mediaStream, { mimeType: 'audio/webm;codecs=opus' });
// 每100ms发送一次音频分片
recorder.start(100);

const webSocket = new WebSocket('ws://your-backend-url');

recorder.ondataavailable = (e) => {
  if (e.data.size > 0 && webSocket.readyState === WebSocket.OPEN) {
    webSocket.send(e.data);
  }
};

后端代码(解析WebM提取OPUS裸流)

使用pywebm库解析WebM分片,提取音频帧后喂给ConversationTranscriber:

import io
import pywebm
from azure.cognitiveservices.speech import (
    AudioStreamFormat, PullAudioInputStream,
    PullAudioInputStreamCallback, SpeechConfig,
    ConversationTranscriber
)

# 配置Azure语音服务
speech_config = SpeechConfig(subscription="YOUR_AZURE_KEY", region="YOUR_REGION")
# 设置为OGG/OPUS格式(16kHz,单声道)
audio_format = AudioStreamFormat.get_ogg_opus_format(16000, 1)

class WebMOpusStream(PullAudioInputStreamCallback):
    def __init__(self, websocket):
        self.websocket = websocket
        self.opus_buffer = b""

    def read(self, buffer: memoryview) -> int:
        # 从WebSocket接收WebM数据
        if not self.opus_buffer:
            webm_data = self.websocket.recv()
            # 解析WebM,提取OPUS音频帧
            reader = pywebm.WebMReader(io.BytesIO(webm_data))
            for frame in reader.iter_frames():
                if frame.track_type == "audio":
                    self.opus_buffer += frame.data

        # 将OPUS数据写入输出buffer
        write_len = min(len(self.opus_buffer), len(buffer))
        buffer[:write_len] = self.opus_buffer[:write_len]
        self.opus_buffer = self.opus_buffer[write_len:]
        return write_len

# 初始化WebSocket连接(示例用websockets库)
import websockets
import asyncio

async def handle_client(websocket):
    stream_callback = WebMOpusStream(websocket)
    audio_stream = PullAudioInputStream(audio_format, stream_callback)
    transcriber = ConversationTranscriber(speech_config, audio_stream=audio_stream)

    # 绑定转录回调
    def on_transcribed(evt):
        if evt.result.text.strip():
            print(f"转录结果: {evt.result.text}")

    transcriber.transcribed.connect(on_transcribed)
    await transcriber.start_transcribing_async()

    # 保持连接
    await websocket.wait_closed()
    await transcriber.stop_transcribing_async()

# 启动WebSocket服务
start_server = websockets.serve(handle_client, "0.0.0.0", 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

二、备选方案:前端采集原生PCM → 后端实时适配WAV/PCM格式

如果不想处理WebM容器,直接用WebAudio API采集16位单声道16kHz的PCM裸流,后端只需按Azure要求的参数配置即可直接接收(无需额外添加WAV头,Azure支持指定参数的裸PCM)。

前端代码(采集原生PCM)

const constraints = {
  audio: { channelCount: 1, sampleRate: 16000 }
};
const mediaStream = await navigator.mediaDevices.getUserMedia(constraints);
const audioContext = new AudioContext({ sampleRate: 16000 });
const source = audioContext.createMediaStreamSource(mediaStream);
// 4096帧的缓冲区,平衡延迟与性能
const processor = audioContext.createScriptProcessor(4096, 1, 1);

source.connect(processor);
processor.connect(audioContext.destination);

const webSocket = new WebSocket('ws://your-backend-url');

processor.onaudioprocess = (e) => {
  if (webSocket.readyState !== WebSocket.OPEN) return;
  
  // 获取Float32格式的PCM数据,转换为16位整数
  const float32Data = e.inputBuffer.getChannelData(0);
  const int16Data = new Int16Array(float32Data.length);
  for (let i = 0; i < float32Data.length; i++) {
    // 归一化到-32768到32767范围
    const sample = Math.max(-1, Math.min(1, float32Data[i]));
    int16Data[i] = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
  }
  
  webSocket.send(int16Data.buffer);
};

后端代码(处理PCM流)

直接使用Azure的PCM格式配置,无需额外转码:

from azure.cognitiveservices.speech import (
    AudioStreamFormat, PullAudioInputStream,
    PullAudioInputStreamCallback, SpeechConfig,
    ConversationTranscriber
)
import websockets
import asyncio

speech_config = SpeechConfig(subscription="YOUR_AZURE_KEY", region="YOUR_REGION")
# 配置PCM格式:16kHz采样率,16位深度,单声道
audio_format = AudioStreamFormat.get_pcm_format(16000, 16, 1)

class PCMAudioStream(PullAudioInputStreamCallback):
    def __init__(self, websocket):
        self.websocket = websocket
        self.pcm_buffer = b""

    def read(self, buffer: memoryview) -> int:
        if not self.pcm_buffer:
            self.pcm_buffer = self.websocket.recv()
        
        write_len = min(len(self.pcm_buffer), len(buffer))
        buffer[:write_len] = self.pcm_buffer[:write_len]
        self.pcm_buffer = self.pcm_buffer[write_len:]
        return write_len

async def handle_client(websocket):
    stream_callback = PCMAudioStream(websocket)
    audio_stream = PullAudioInputStream(audio_format, stream_callback)
    transcriber = ConversationTranscriber(speech_config, audio_stream=audio_stream)

    def on_transcribed(evt):
        if evt.result.text.strip():
            print(f"转录结果: {evt.result.text}")

    transcriber.transcribed.connect(on_transcribed)
    await transcriber.start_transcribing_async()
    await websocket.wait_closed()
    await transcriber.stop_transcribing_async()

start_server = websockets.serve(handle_client, "0.0.0.0", 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

关键注意事项

  • 参数一致性:前端采集和后端配置必须统一使用单声道、16kHz采样率、16位深度,这是Azure认知语音服务最稳定兼容的参数组合。
  • 分片大小:前端发送的音频分片建议控制在100-200ms范围内,避免过大导致延迟,过小增加网络开销。
  • 异常处理:需添加WebSocket断开、音频流中断等异常捕获逻辑,保证转录服务的稳定性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 19:28:21