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

Xamarin.iOS中Google Cloud Speech流式语音转文本遇gRPC及Sharpie导入问题

我之前也帮开发者解决过类似的Xamarin.iOS对接Google Speech API的问题,你遇到的gRPC兼容和Sharpie报错确实是这个场景下的常见坑,给你两种可行的解决方案,按需选择:

方案一:绕开gRPC,使用REST流式API(推荐)

官方的Google.Cloud.Speech.V1依赖的gRPC库在Xamarin.iOS上的原生支持一直有兼容性问题,切换到REST流式接口是最稳妥的方式,它同样支持实时音频传输和即时识别结果返回。

1. 准备Google Cloud认证

  • 先在Google Cloud Console开启Speech-to-Text服务,生成JSON格式的服务账号密钥,将密钥文件添加到Xamarin.iOS项目中,设置文件的生成操作为BundleResource。
  • 通过以下代码获取认证令牌:
    using Google.Apis.Auth.OAuth2;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    
    private async Task<string> GetGoogleAuthToken()
    {
        var credential = GoogleCredential.FromFile("your-service-account-key.json")
            .CreateScoped("https://www.googleapis.com/auth/cloud-platform");
        var token = await credential.UnderlyingCredential.GetAccessTokenForRequestAsync();
        return token;
    }
    

2. 实时音频捕获与流式传输

Xamarin.iOS可以用AVAudioEngine捕获音频,转换为Google要求的LINEAR16编码、16kHz采样率、单声道格式,然后分块发送到REST接口:

using AVFoundation;
using System.IO;
using System.Net.Http;

private AVAudioEngine _audioEngine;
private HttpClient _httpClient;
private StreamWriter _responseWriter;

public async Task StartStreamingRecognition()
{
    // 初始化HttpClient和认证
    var authToken = await GetGoogleAuthToken();
    _httpClient = new HttpClient();
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

    // 第一步:发送识别配置请求
    var configPayload = @"
    {
      ""config"": {
        ""encoding"": ""LINEAR16"",
        ""sampleRateHertz"": 16000,
        ""languageCode"": ""zh-CN"",
        ""enableAutomaticPunctuation"": true
      },
      ""audio"": {}
    }";
    var content = new StringContent(configPayload, System.Text.Encoding.UTF8, "application/json");
    var request = new HttpRequestMessage(HttpMethod.Post, "https://speech.googleapis.com/v1/speech:streamingRecognize")
    {
        Content = content
    };
    var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    response.EnsureSuccessStatusCode();

    // 准备音频流写入和响应读取
    var responseStream = await response.Content.ReadAsStreamAsync();
    _responseWriter = new StreamWriter(responseStream);
    var responseReader = new StreamReader(responseStream);

    // 初始化音频引擎捕获音频
    InitializeAudioEngine(responseReader);
}

private void InitializeAudioEngine(StreamReader responseReader)
{
    _audioEngine = new AVAudioEngine();
    var inputNode = _audioEngine.InputNode;
    var inputFormat = inputNode.InputFormatForBus(0);
    // 转换为目标音频格式
    var targetFormat = new AVAudioFormat(AVAudioCommonFormat.PCMFormatInt16, 16000, 1, true);
    var converter = new AVAudioConverter(inputFormat, targetFormat);

    inputNode.InstallTapOnBus(0, 4096, inputFormat, async (buffer, time) =>
    {
        var convertedBuffer = new AVAudioPcmBuffer(targetFormat, buffer.FrameLength);
        converter.ConvertToBuffer(convertedBuffer, buffer, out var error);
        if (error != null)
        {
            Console.WriteLine($"音频转换错误: {error.LocalizedDescription}");
            return;
        }

        // 将音频数据转为Base64并发送
        var audioBytes = convertedBuffer.GetChannelData(0).ToArray((int)(convertedBuffer.FrameLength * 2));
        var base64Audio = Convert.ToBase64String(audioBytes);
        var audioPayload = $"{{\"audio\": {{\"content\": \"{base64Audio}\"}}}}";
        
        await _responseWriter.WriteLineAsync(audioPayload);
        await _responseWriter.FlushAsync();

        // 读取并解析识别结果
        var resultJson = await responseReader.ReadLineAsync();
        if (!string.IsNullOrEmpty(resultJson))
        {
            ParseAndDisplayRecognitionResult(resultJson);
        }
    });

    _audioEngine.Prepare();
    _audioEngine.StartAndReturnError(out var startError);
    if (startError != null)
    {
        Console.WriteLine($"音频引擎启动失败: {startError.LocalizedDescription}");
    }
}

// 解析识别结果并展示的示例方法
private void ParseAndDisplayRecognitionResult(string resultJson)
{
    // 这里用JSON库(比如Newtonsoft.Json)解析resultJson,提取识别文本
    // 示例:
    var resultObj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(resultJson);
    var transcript = resultObj.results[0].alternatives[0].transcript;
    // 更新UI展示识别结果(注意在主线程操作)
    InvokeOnMainThread(() => {
        YourResultLabel.Text = transcript;
    });
}

3. 配置必要权限

在Info.plist中添加麦克风权限申请:

<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风进行实时语音识别</string>

方案二:修复gRPC与Sharpie报错(适合坚持用官方NuGet的场景)

如果一定要使用Google.Cloud.Speech.V1的gRPC实现,可以尝试以下步骤修复:

1. 更新依赖包与项目配置

  • 确保安装最新版本的Google.Cloud.Speech.V1、Grpc.Core和Grpc.Core.Api NuGet包。
  • 将Xamarin.iOS项目的目标框架设置为iOS 15+,并把链接器行为改为链接SDK程序集(避免gRPC原生库被误删)。

2. 手动处理gRPC原生绑定

"Sharpie import not found"报错是因为NuGet包中的Objective-C绑定代码依赖Sharpie生成的头文件,但Xamarin环境未正确配置。可以手动重新生成绑定:

  1. 从gRPC官方GitHub下载iOS原生库的头文件。
  2. 使用Sharpie工具生成绑定代码:
    sharpie bind -sdk iphoneos16.0 -output GrpcBindings -namespace Grpc.Core.Native *.h
    
  3. 将生成的绑定项目添加到解决方案,替换NuGet包中的gRPC绑定。

3. 初始化Speech客户端并流式识别

修复绑定后,可以使用官方SDK的流式API:

using Google.Cloud.Speech.V1;
using Grpc.Core;

private async Task StartGrpcStreamingRecognition()
{
    var credential = GoogleCredential.FromFile("your-service-account-key.json");
    var channel = new Channel(SpeechClient.DefaultEndpoint.ToString(), credential.ToChannelCredentials());
    var speechClient = SpeechClient.Create(channel);

    var streamingCall = speechClient.StreamingRecognize();
    // 发送配置
    await streamingCall.WriteAsync(new StreamingRecognizeRequest
    {
        Config = new RecognitionConfig
        {
            Encoding = RecognitionConfig.Types.AudioEncoding.Linear16,
            SampleRateHertz = 16000,
            LanguageCode = "zh-CN"
        }
    });

    // 捕获音频并发送(这里复用之前的AVAudioEngine捕获逻辑)
    InitializeAudioEngineForGrpc(streamingCall);

    // 读取识别结果
    await foreach (var response in streamingCall.ResponseStream.ReadAllAsync())
    {
        foreach (var result in response.Results)
        {
            foreach (var alternative in result.Alternatives)
            {
                InvokeOnMainThread(() => {
                    YourResultLabel.Text = alternative.Transcript;
                });
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:43:07