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

Download Sample 下載範例

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

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

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

Screenshots of the sample application on iOS and 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 中 [功能] 索引卷標上的 [麥克風] 選項:

Screenshot of the manifest in Visual Studio 2019

測試應用程式

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

注意

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