Azure Speech Service を使用した音声認識

Download Sampleサンプルのダウンロード

Azure 音声サービスは、次の機能を提供するクラウドベースの API です:

  • 音声テキスト変換はは、オーディオ ファイルまたはストリームをテキストに変換します。
  • テキスト読み上げは、入力テキストを人間のような合成音声に変換します。
  • 音声翻訳を使用すると、音声からテキストへの翻訳と音声読み上げの両方に対して、リアルタイムの多言語翻訳が可能になります。
  • 音声アシスタントは、アプリケーション用の人間のような会話インターフェイスを作成できます。

この記事では、Azure 音声サービスを使用してサンプル Xamarin.Forms アプリケーションで音声テキスト変換を実装する方法について説明します。 次のスクリーンショットは、iOS および Android 上のサンプル アプリケーションを示しています:

Screenshots of the sample application on iOS and Android

Azure 音声サービスリソースを作成する

Azure 音声サービスは Azure Cognitive Services の一部であり、画像認識、音声認識と翻訳、Bing 検索などのタスクにクラウドベースの API を提供します。 詳細については、「Azure Cognitive Services とは」を参照してください。

サンプル プロジェクトでは、Azure portal で Azure Cognitive Services リソースを作成する必要があります。 Cognitive Services リソースは、音声サービスなどの 1 つのサービスに対して作成することも、マルチサービス リソースとして作成することもできます。 音声サービス リソースを作成する手順は次のとおりです:

  1. Azure portal にサインインします。
  2. マルチサービスまたは単一サービス リソースを作成します。
  3. リソースの API キーとリージョン情報を取得します。
  4. サンプル Constants.cs ファイルを更新します。

リソースを作成するための詳細なガイドについては、「Cognitive Services リソースの作成」を参照してください。

Note

Azure サブスクリプションをお持ちでない場合は、開始する前に無料アカウントを作成してください。 アカウントを作成したら、Free レベルで単一サービス リソースを作成して、サービスを試すことができます。

音声サービスを使用してアプリを構成する

Cognitive Services リソースを作成した後、 Constants.cs ファイルを Azure リソースのリージョンと API キーで更新できます:

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

NuGet Speech Service パッケージをインストールする

サンプル アプリケーションでは、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 メソッドは、指定された newTextstringtranscribedText という名前の Label 要素に書き込みます。 これにより、UI スレッドでこの更新が強制的に実行されるため、例外を発生させずに任意のコンテキストから呼び出すことができます。 InsertDateTimeRecord は、現在の日時を transcribedText インスタンスに書き込み、新しい文字起こしの開始をマークします。 最後に、UpdateDisplayState メソッドは、文字起こしが進行中かどうかを反映するように、 ButtonActivityIndicator 要素を更新します。

プラットフォーム マイク サービスを作成する

アプリケーションは、音声データを収集するためのマイク アクセス権を持っている必要があります。 IMicrophoneService インターフェイスは、アプリケーションを機能させるために、各プラットフォーム上の DependencyService に実装して登録する必要があります。

Android

サンプル プロジェクトでは、AndroidMicrophoneService と呼ばれる Android の IMicrophoneService 実装を定義します:

[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. RequestMicPermissions メソッドは、Snackbar クラスを使用して、根拠が必要な場合はユーザーにアクセス許可を要求します。それ以外の場合は、直接オーディオ録音のアクセス許可を要求します。
  4. OnRequestPermissionResult メソッドは、ユーザーがアクセス許可要求に応答すると、bool 結果で呼び出されます。

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

サンプル プロジェクトでは、iOSMicrophoneService と呼ばれる iOS の IMicrophoneService 実装を定義します:

[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

サンプル プロジェクトでは、UWPMicrophoneService と呼ばれる UWP の IMicrophoneService 実装を定義します:

[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 要素に表示されます。

Note

Android エミュレーターは、音声サービス ライブラリの読み込みと初期化に失敗します。 Android プラットフォームでは、物理デバイスでのテストをお勧めします。