You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Xamarin Android基于WebSocket协议的微软认知语音连续识别问题

解决Xamarin Android上Azure认知语音服务WebSocket连续识别的消息发送问题

嘿,我之前帮好几个Xamarin开发者搞定过类似的Azure语音WebSocket识别难题,咱们一步步来拆解你遇到的瓶颈——核心问题大概率出在握手请求头的配置消息发送的顺序/格式上,这也是官方WebSocket文档里最容易被忽略的细节。

首先,确认WebSocket握手的路径与请求头

Azure语音的WebSocket连接不是随便连个地址就行,必须在握手阶段就把关键参数传对:

  • URL格式wss://<你的订阅区域>.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=<目标语言>
    比如中文简体就是language=zh-CN,区域要和你Azure订阅的语音服务区域完全一致(比如eastasia、westus等)。
  • 必须携带的请求头
    • Authorization: Bearer <你的Bearer Token>(要先调用Azure的token接口获取,有效期10分钟)
    • X-Microsoft-OutputFormat: 指定识别结果的返回格式,比如simple(仅文本)或detailed(带置信度)
    • Content-Type: application/json
    • Origin: 可以填你的App包名或者任意合法域名(比如https://your-app-domain.com

我见过很多开发者在这里踩坑:要么token过期了,要么区域不匹配,要么漏了某个请求头,导致连接成功后发消息被服务器拒绝。

然后,消息发送的正确顺序

连接成功后,不能直接发音频,必须按以下步骤来:

1. 先发送启动识别的文本消息(JSON格式)

这是告诉服务器“我要开始发音频了,按这个配置来识别”,格式必须严格符合要求:

{
  "context": {
    "system": {
      "version": "1.0.0",
      "name": "XamarinSpeechClient",
      "build": "Android"
    }
  },
  "config": {
    "format": "audio/wav; codecs=audio/pcm; samplerate=16000",
    "language": "zh-CN",
    "profanity": "masked"
  }
}
  • format必须和你实际发送的音频参数完全一致:16kHz采样率、单声道、16位PCM,这个是硬要求,不匹配的话服务器会直接断开。
  • profanity可选,用来处理脏话,可选值masked(替换为*)、removed(删除)、raw(保留)。

在Xamarin里用ClientWebSocket发送这个消息的代码示例:

using System.Text.Json;
using System.Net.WebSockets;

// 构建启动消息
var startPayload = new
{
    context = new
    {
        system = new
        {
            version = "1.0.0",
            name = "XamarinSpeechClient",
            build = "Android"
        }
    },
    config = new
    {
        format = "audio/wav; codecs=audio/pcm; samplerate=16000",
        language = "zh-CN",
        profanity = "masked"
    }
};
var startMessage = JsonSerializer.Serialize(startPayload);
var messageBytes = Encoding.UTF8.GetBytes(startMessage);

// 发送文本消息
await webSocket.SendAsync(
    new ArraySegment<byte>(messageBytes),
    WebSocketMessageType.Text,
    endOfMessage: true,
    CancellationToken.None);

2. 分帧发送麦克风的PCM音频数据

接下来要捕获麦克风的音频,必须转成16kHz、单声道、16位PCM格式,然后按20ms一帧的大小发送(也就是640字节,计算方式:16000采样率 × 16位/8 × 1声道 × 0.02秒 = 640字节)。

Xamarin Android里用AudioRecord捕获音频并发送的示例:

using Android.Media;

// 配置音频捕获参数
var sampleRate = 16000;
var channel = ChannelIn.Mono;
var encoding = Encoding.Pcm16bit;
var bufferSize = AudioRecord.GetMinBufferSize(sampleRate, channel, encoding);

using var audioRecord = new AudioRecord(
    MediaRecorder.AudioSource.Mic,
    sampleRate,
    channel,
    encoding,
    bufferSize);

audioRecord.StartRecording();
var audioBuffer = new byte[640]; // 20ms帧大小

// 持续发送音频直到连接关闭
while (webSocket.State == WebSocketState.Open)
{
    var readBytes = audioRecord.Read(audioBuffer, 0, audioBuffer.Length);
    if (readBytes > 0)
    {
        await webSocket.SendAsync(
            new ArraySegment<byte>(audioBuffer, 0, readBytes),
            WebSocketMessageType.Binary,
            endOfMessage: true,
            CancellationToken.None);
    }
    // 控制帧率,避免发送过快
    await Task.Delay(20);
}

audioRecord.Stop();
audioRecord.Release();

3. 结束识别时发送停止信号

如果要停止识别,发送一个{"eof": true}的文本消息,告诉服务器“我发完了”,然后再关闭WebSocket连接:

var stopMessage = Encoding.UTF8.GetBytes("{\"eof\": true}");
await webSocket.SendAsync(
    new ArraySegment<byte>(stopMessage),
    WebSocketMessageType.Text,
    endOfMessage: true,
    CancellationToken.None);

await webSocket.CloseAsync(
    WebSocketCloseStatus.NormalClosure,
    "识别结束",
    CancellationToken.None);

常见问题排查

  • Token过期:Bearer Token有效期只有10分钟,过期后要重新获取,别一直用同一个token。
  • 音频格式不匹配:一定要确保捕获的音频是16kHz、单声道、16位PCM,很多开发者不小心用了44.1kHz的采样率,直接导致识别失败。
  • 帧大小不合适:不要一次性发超大的音频帧,服务器对帧大小有限制,20ms一帧是官方推荐的最优解。
  • 连接超时:如果长时间没有音频输入,要定期发送空的二进制帧(比如每10秒发一次),避免服务器因为无数据断开连接。

内容的提问来源于stack exchange,提问作者Vincent Elbert Budiman

火山引擎 最新活动