共用方式為


使用 Azure 語音服務進行語音辨識

Azure 語音服務是雲端式 API,可提供下列功能:

  • 語音轉換文字 會轉譯音訊檔案或串流到文字。
  • 文字到語音 轉換會將輸入文字轉換成類似人為的合成語音。
  • 語音翻譯 可為語音轉換文字和語音轉換語音啟用即時、多語言翻譯。
  • 語音助理 可以為應用程式建立類似人類的對話介面。

本文說明如何使用 Azure 語音服務,在範例 Xamarin.Forms 應用程式中實作語音轉換文字。 下列螢幕快照顯示 iOS 和 Android 上的範例應用程式:

iOS 和 Android 上範例應用程式的螢幕快照

建立 Azure 語音服務資源

Azure 語音服務是 Azure 認知服務的一部分,可為影像辨識、語音辨識、語音辨識和翻譯和 Bing 搜尋等工作提供雲端式 API。 如需詳細資訊,請參閱 什麼是 Azure 認知服務?

範例專案需要在您的 Azure 入口網站 中建立 Azure 認知服務資源。 您可以為單一服務建立認知服務資源,例如語音服務或多重服務資源。 建立語音服務資源的步驟如下:

  1. 登入您的 Azure 入口網站
  2. 建立多服務或單一服務資源。
  3. 取得資源的 API 金鑰和區域資訊。
  4. 更新範例 Constants.cs 檔案。

如需建立資源的逐步指南,請參閱 建立認知服務資源

注意

如果您沒有 Azure 訂用帳戶,請在開始前建立免費帳戶。 擁有帳戶之後,即可在免費層建立單一服務資源,以試用服務。

使用語音服務設定您的應用程式

建立認知服務資源之後, Constants.cs 檔案可以使用來自 Azure 資源的區域和 API 金鑰來更新:

public static class Constants
{
    public static string CognitiveServicesApiKey = "YOUR_KEY_GOES_HERE";
    public static string CognitiveServicesRegion = "westus";
}

安裝 NuGet 語音服務套件

範例應用程式會使用 Microsoft.CognitiveServices.Speech NuGet 套件來連線到 Azure 語音服務。 在共享專案和每個平台專案中安裝此 NuGet 套件。

建立 IMicrophoneService 介面

每個平臺都需要存取麥克風的許可權。 範例專案提供 IMicrophoneService 共享專案中的介面,並使用 Xamarin.FormsDependencyService 取得介面的平台實作。

public interface IMicrophoneService
{
    Task<bool> GetPermissionAsync();
    void OnRequestPermissionResult(bool isGranted);
}

建立版面配置

範例專案會在 MainPage.xaml 檔案中定義基本版面配置。 主要版面配置元素是 Button 啟動轉譯程式的 、 Label 要包含轉譯文字的 ,以及在 ActivityIndicator 轉譯進行時顯示 :

<ContentPage ...>
    <StackLayout>
        <Frame ...>
            <ScrollView x:Name="scroll"
                        ...>
                <Label x:Name="transcribedText"
                       ... />
            </ScrollView>
        </Frame>

        <ActivityIndicator x:Name="transcribingIndicator"
                           IsRunning="False" />
        <Button x:Name="transcribeButton"
                ...
                Clicked="TranscribeClicked"/>
    </StackLayout>
</ContentPage>

實作語音服務

MainPage.xaml.cs程式代碼後置檔案包含從 Azure 語音服務傳送音訊和接收轉譯文字的所有邏輯。

MainPage構函式會從 DependencyService取得介面的IMicrophoneService實例:

public partial class MainPage : ContentPage
{
    SpeechRecognizer recognizer;
    IMicrophoneService micService;
    bool isTranscribing = false;

    public MainPage()
    {
        InitializeComponent();

        micService = DependencyService.Resolve<IMicrophoneService>();
    }

    // ...
}

TranscribeClicked 選 實例時會 transcribeButton 呼叫 方法:

async void TranscribeClicked(object sender, EventArgs e)
{
    bool isMicEnabled = await micService.GetPermissionAsync();

    // EARLY OUT: make sure mic is accessible
    if (!isMicEnabled)
    {
        UpdateTranscription("Please grant access to the microphone!");
        return;
    }

    // initialize speech recognizer
    if (recognizer == null)
    {
        var config = SpeechConfig.FromSubscription(Constants.CognitiveServicesApiKey, Constants.CognitiveServicesRegion);
        recognizer = new SpeechRecognizer(config);
        recognizer.Recognized += (obj, args) =>
        {
            UpdateTranscription(args.Result.Text);
        };
    }

    // if already transcribing, stop speech recognizer
    if (isTranscribing)
    {
        try
        {
            await recognizer.StopContinuousRecognitionAsync();
        }
        catch(Exception ex)
        {
            UpdateTranscription(ex.Message);
        }
        isTranscribing = false;
    }

    // if not transcribing, start speech recognizer
    else
    {
        Device.BeginInvokeOnMainThread(() =>
        {
            InsertDateTimeRecord();
        });
        try
        {
            await recognizer.StartContinuousRecognitionAsync();
        }
        catch(Exception ex)
        {
            UpdateTranscription(ex.Message);
        }
        isTranscribing = true;
    }
    UpdateDisplayState();
}

TranscribeClicked 方法會執行下列動作:

  1. 檢查應用程式是否能夠存取麥克風,並在未存取時提早結束。
  2. 如果類別尚未存在,請建立 類別的 SpeechRecognizer 實例。
  3. 如果進行中,則會停止連續轉譯。
  4. 插入時間戳,並在未進行時開始連續轉譯。
  5. 通知應用程式根據新的應用程式狀態更新其外觀。

類別方法的 MainPage 其餘部分是顯示應用程式狀態的協助程式:

void UpdateTranscription(string newText)
{
    Device.BeginInvokeOnMainThread(() =>
    {
        if (!string.IsNullOrWhiteSpace(newText))
        {
            transcribedText.Text += $"{newText}\n";
        }
    });
}

void InsertDateTimeRecord()
{
    var msg = $"=================\n{DateTime.Now.ToString()}\n=================";
    UpdateTranscription(msg);
}

void UpdateDisplayState()
{
    Device.BeginInvokeOnMainThread(() =>
    {
        if (isTranscribing)
        {
            transcribeButton.Text = "Stop";
            transcribeButton.BackgroundColor = Color.Red;
            transcribingIndicator.IsRunning = true;
        }
        else
        {
            transcribeButton.Text = "Transcribe";
            transcribeButton.BackgroundColor = Color.Green;
            transcribingIndicator.IsRunning = false;
        }
    });
}

方法UpdateTranscription會將 提供的 newTextstring 寫入名為transcribedText的專案Label。 它會強制在 UI 線程上執行此更新,以便從任何內容呼叫它,而不會造成例外狀況。 會將 InsertDateTimeRecord 目前的日期和時間寫入 實例, transcribedText 以標記新轉譯的開頭。 最後, UpdateDisplayState 方法會更新 ButtonActivityIndicator 元素,以反映是否正在進行轉譯。

建立平台麥克風服務

應用程式必須具有麥克風存取權,才能收集語音數據。 IMicrophoneService介面必須在每個平台上實作並註冊DependencyService,應用程式才能運作。

Android

範例專案會 IMicrophoneService 定義 Android 的實作,稱為 AndroidMicrophoneService

[assembly: Dependency(typeof(AndroidMicrophoneService))]
namespace CognitiveSpeechService.Droid.Services
{
    public class AndroidMicrophoneService : IMicrophoneService
    {
        public const int RecordAudioPermissionCode = 1;
        private TaskCompletionSource<bool> tcsPermissions;
        string[] permissions = new string[] { Manifest.Permission.RecordAudio };

        public Task<bool> GetPermissionAsync()
        {
            tcsPermissions = new TaskCompletionSource<bool>();

            if ((int)Build.VERSION.SdkInt < 23)
            {
                tcsPermissions.TrySetResult(true);
            }
            else
            {
                var currentActivity = MainActivity.Instance;
                if (ActivityCompat.CheckSelfPermission(currentActivity, Manifest.Permission.RecordAudio) != (int)Permission.Granted)
                {
                    RequestMicPermissions();
                }
                else
                {
                    tcsPermissions.TrySetResult(true);
                }

            }

            return tcsPermissions.Task;
        }

        public void OnRequestPermissionResult(bool isGranted)
        {
            tcsPermissions.TrySetResult(isGranted);
        }

        void RequestMicPermissions()
        {
            if (ActivityCompat.ShouldShowRequestPermissionRationale(MainActivity.Instance, Manifest.Permission.RecordAudio))
            {
                Snackbar.Make(MainActivity.Instance.FindViewById(Android.Resource.Id.Content),
                        "Microphone permissions are required for speech transcription!",
                        Snackbar.LengthIndefinite)
                        .SetAction("Ok", v =>
                        {
                            ((Activity)MainActivity.Instance).RequestPermissions(permissions, RecordAudioPermissionCode);
                        })
                        .Show();
            }
            else
            {
                ActivityCompat.RequestPermissions((Activity)MainActivity.Instance, permissions, RecordAudioPermissionCode);
            }
        }
    }
}

AndroidMicrophoneService具有下列功能:

  1. 屬性 Dependency 會向 DependencyService註冊 類別。
  2. GetPermissionAsync方法會根據 Android SDK 版本檢查是否需要許可權,如果尚未授與許可權,則會呼叫 RequestMicPermissions
  3. 如果需要理由,此方法 RequestMicPermissionsSnackbar 使用 類別向使用者要求許可權,否則會直接要求音頻錄製許可權。
  4. 一旦使用者回應許可權要求,就會 OnRequestPermissionResultbool 結果呼叫 方法。

類別 MainActivity 會自定義為在許可權要求完成時更新 AndroidMicrophoneService 實例:

public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    IMicrophoneService micService;
    internal static MainActivity Instance { get; private set; }

    protected override void OnCreate(Bundle savedInstanceState)
    {
        Instance = this;
        // ...
        micService = DependencyService.Resolve<IMicrophoneService>();
    }
    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
    {
        // ...
        switch(requestCode)
        {
            case AndroidMicrophoneService.RecordAudioPermissionCode:
                if (grantResults[0] == Permission.Granted)
                {
                    micService.OnRequestPermissionResult(true);
                }
                else
                {
                    micService.OnRequestPermissionResult(false);
                }
                break;
        }
    }
}

類別 MainActivity 會定義稱為 Instance的靜態參考,這個參考在要求許可權時,物件需要 AndroidMicrophoneService 這個參考。 它會覆寫 OnRequestPermissionsResult 方法,以在使用者核准或拒絕許可權要求時更新 AndroidMicrophoneService 物件。

最後,Android 應用程式必須包含在AndroidManifest.xml檔案中錄製音訊的許可權:

<manifest ...>
    ...
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>

iOS

範例項目會 IMicrophoneService 定義名為 iOSMicrophoneService的 iOS 實作:

[assembly: Dependency(typeof(iOSMicrophoneService))]
namespace CognitiveSpeechService.iOS.Services
{
    public class iOSMicrophoneService : IMicrophoneService
    {
        TaskCompletionSource<bool> tcsPermissions;

        public Task<bool> GetPermissionAsync()
        {
            tcsPermissions = new TaskCompletionSource<bool>();
            RequestMicPermission();
            return tcsPermissions.Task;
        }

        public void OnRequestPermissionResult(bool isGranted)
        {
            tcsPermissions.TrySetResult(isGranted);
        }

        void RequestMicPermission()
        {
            var session = AVAudioSession.SharedInstance();
            session.RequestRecordPermission((granted) =>
            {
                tcsPermissions.TrySetResult(granted);
            });
        }
    }
}

iOSMicrophoneService具有下列功能:

  1. 屬性 Dependency 會向 DependencyService註冊 類別。
  2. 方法 GetPermissionAsync 會呼叫 RequestMicPermissions 以向裝置使用者要求許可權。
  3. RequestMicPermissions方法會使用共用AVAudioSession實例來要求錄製許可權。
  4. 方法會OnRequestPermissionResult使用提供的 bool 值來更新 TaskCompletionSource 實例。

最後,iOS 應用程式 Info.plist 必須包含訊息,告知使用者應用程式為何要求存取麥克風。 編輯 Info.plist 檔案,以在 元素中包含 <dict> 下列標籤:

<plist>
    <dict>
        ...
        <key>NSMicrophoneUsageDescription</key>
        <string>Voice transcription requires microphone access</string>
    </dict>
</plist>

UWP

範例項目會 IMicrophoneService 定義名為 UWPMicrophoneService的 UWP 實作:

[assembly: Dependency(typeof(UWPMicrophoneService))]
namespace CognitiveSpeechService.UWP.Services
{
    public class UWPMicrophoneService : IMicrophoneService
    {
        public async Task<bool> GetPermissionAsync()
        {
            bool isMicAvailable = true;
            try
            {
                var mediaCapture = new MediaCapture();
                var settings = new MediaCaptureInitializationSettings();
                settings.StreamingCaptureMode = StreamingCaptureMode.Audio;
                await mediaCapture.InitializeAsync(settings);
            }
            catch(Exception ex)
            {
                isMicAvailable = false;
            }

            if(!isMicAvailable)
            {
                await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-microphone"));
            }

            return isMicAvailable;
        }

        public void OnRequestPermissionResult(bool isGranted)
        {
            // intentionally does nothing
        }
    }
}

UWPMicrophoneService具有下列功能:

  1. 屬性 Dependency 會向 DependencyService註冊 類別。
  2. 方法 GetPermissionAsync 會嘗試初始化 MediaCapture 實例。 如果失敗,它會啟動使用者要求來啟用麥克風。
  3. OnRequestPermissionResult方法的存在是為了滿足 介面,但 UWP 實作並非必要。

最後,UWP Package.appxmanifest 必須指定應用程式使用麥克風。 按兩下 Package.appxmanifest 檔案,然後選取 Visual Studio 2019 中 [功能] 索引卷標上的 [麥克風] 選項:

Visual Studio 2019 中指令清單的螢幕快照

測試應用程式

執行應用程式,然後按兩下 [ 轉譯] 按鈕。 應用程式應該要求麥克風存取,並開始轉譯程式。 會 ActivityIndicator 以動畫顯示轉譯為使用中狀態。 當您說話時,應用程式會將音訊數據串流至 Azure 語音服務資源,以轉譯的文字回應。 謄寫的文字會在收到時出現在 Label 專案中。

注意

Android 模擬器無法載入和初始化語音服務連結庫。 建議針對Android平臺在實體裝置上進行測試。