[Xamarin] 如何透過Xamarin.Forms的方式,製作Android上的語音辨識功能

Xamarin可以製作跨平台的App應用程式,當然也可以使用各平台底層的服務
在這篇文章中,會說明如何使用Android底層的Google語音辨識服務,將說出的語句辨識成為文字

由於用到的是Xamarin.Forms的開發方式,所以要採用DependencyService的方式來針對不同平台作出語音辨識的功能
iOS的語音辨識較為麻煩,需要採用第三方套件才能達成語音辨識的功能
所以在本篇文章中只會先說明Android該如何作出語音辨識,iOS的部份留待後續再來說明

1.先在Xamarin.Forms的可攜式專案中,建立一個SpeechToTextResult.cs的檔案檔案內容請加上下面的程式碼

public class SpeechToTextResult
{
    public string Text { get; set; }
}

這個類別庫主要是用來定義接收語音辨識完的結果,並放入至一個結果物件中

2.一樣在可攜式專案中,建立一個ISpeechToText.cs的類別檔

並在ISpeechToText.cs檔案中加上下面程式碼

public interface ISpeechToText
{
    Task<SpeechToTextResult> SpeechToTextAsync();
}

這邊主要是建立一個介面,以便讓不同平台去實作這樣的功能,所以在這裡定義了一個SpeechToTextAsync的副程式,並將結果回傳SpeechToTextResult的物件

3.建立一個View,並在畫面上加上一個Label與Button,當Button按下的時候,啟動語音辨識的功能xaml程式碼如下:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="AzureAD.Views.SpeechToTextView">
  <StackLayout>
    <Label Text="" VerticalOptions="Center" HorizontalOptions="Center" x:Name="lblText" />
    <Button Text="開始辨識" VerticalOptions="Center" HorizontalOptions="Center" x:Name="btnSpeech" Clicked="btnSpeech_Clicked" />
  </StackLayout>
</ContentPage>

C#程式碼如下:

ISpeechToText speech = DependencyService.Get<ISpeechToText>();

public SpeechToTextView()
{
    InitializeComponent();
}

/// <summary>
/// 點選開始辨識的動作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected async void btnSpeech_Clicked(object sender, EventArgs e)
{
    var SpeechResult = await speech.SpeechToTextAsync();
    if (SpeechResult != null)
        lblText.Text = SpeechResult.Text;
}

這個步驟主要是當按下Button時,去呼叫ISpeechToText這個介面,並將辨識的結果回傳至畫面上的Label中

4.接著就是Android平台中的重頭戲了,先在Android平台中,建立一個SpeechToTextService.cs的類別庫類別庫程式碼如下:

[assembly: Xamarin.Forms.Dependency(typeof(SpeechToTextService))]
namespace AzureAD.Droid.Services
{
    public class SpeechToTextService :ISpeechToText
    {

        private static TaskCompletionSource<SpeechToTextResult> objResult;

        public Task<SpeechToTextResult> SpeechToTextAsync()
        {
            string rec = PackageManager.FeatureMicrophone;

            if (rec == "android.hardware.microphone")
            {
                var voiceIntent = new Intent(RecognizerIntent.ActionRecognizeSpeech);
                voiceIntent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm);
                voiceIntent.PutExtra(RecognizerIntent.ExtraPrompt, "請說出指令");
                voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputCompleteSilenceLengthMillis, 1500);
                voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputPossiblyCompleteSilenceLengthMillis, 1500);
                voiceIntent.PutExtra(RecognizerIntent.ExtraSpeechInputMinimumLengthMillis, 15000);
                voiceIntent.PutExtra(RecognizerIntent.ExtraMaxResults, 1);
                voiceIntent.PutExtra(RecognizerIntent.ExtraLanguage, Java.Util.Locale.Default);
                var activity = (Activity)Forms.Context;
                activity.StartActivityForResult(voiceIntent, (int)Enums.ActiveResultCode.SpeechToText);
            }

            objResult = new TaskCompletionSource<SpeechToTextResult>();
            return objResult.Task;
        }

        /// <summary>
        /// 取得辨識結果的動作
        /// </summary>
        /// <param name="resultCode"></param>
        /// <param name="strSpeechText"></param>
        public static void OnResult(Result resultCode, Intent data)
        {
            if (resultCode == Result.Canceled)
            {
                objResult.TrySetResult(null);
                return;
            }

            if (resultCode != Result.Ok)
            {
                objResult.TrySetException(new Exception("Unexpected error"));
                return;
            }

            var matches = data.GetStringArrayListExtra(RecognizerIntent.ExtraResults);
            string strSpeechText = "No Speech";

            if (matches.Count != 0)
                strSpeechText = matches[0];

            SpeechToTextResult res = new SpeechToTextResult();
            res.Text = strSpeechText;
            objResult.TrySetResult(res);
        }
    }
}

這段程式碼分成兩個部份,上半段是實際執行語音辨識的動作,並定義一個新的SpeechToTextResult物件,下半段則是當語音辨識有結果時,將辨識的結果放至SpeechToTextResult物件中

5.最後在MainActivity.cs中,加上下面這段程式碼

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
    // 如果回傳的requestCode是Enums.ActiveResultCode.SpeechToText, 就將結果回傳至SpeechToText的OnResult
    AzureAD.Droid.Services.SpeechToTextService.OnResult(resultCode, data);
    base.OnActivityResult(requestCode, resultCode, data);
}

MainActivity.cs的程式碼主要是在執行,當辨識的過程得到結果後,會將事件傳至SpeechToTextService.OnResult的副程式中,這樣才能順利得到辨識的結果

下圖是執行的結果畫面

當按下開始辨識的按鈕之後,Android作業系統會開啟Google的語音辨識功能

最後得到辨識的結果,並放在最上方的Label中

語音變識的功能,以前並沒有太多的應用,但是隨著進近幾年機器人與AI智慧的發展,口語以及語音辨識的應用也會越來越多,並大量的應用在生活周遭上

參考資料:
Android Speech
Accessing Native Features with DependencyService

範例程式已放上GitHub,請參考下面網址內容:
https://github.com/madukapai/maduka-Xamarin