Azure Speech Service を使用した音声認識
Azure 音声サービスは、次の機能を提供するクラウドベースの API です:
- 音声テキスト変換はは、オーディオ ファイルまたはストリームをテキストに変換します。
- テキスト読み上げは、入力テキストを人間のような合成音声に変換します。
- 音声翻訳を使用すると、音声からテキストへの翻訳と音声読み上げの両方に対して、リアルタイムの多言語翻訳が可能になります。
- 音声アシスタントは、アプリケーション用の人間のような会話インターフェイスを作成できます。
この記事では、Azure 音声サービスを使用してサンプル Xamarin.Forms アプリケーションで音声テキスト変換を実装する方法について説明します。 次のスクリーンショットは、iOS および Android 上のサンプル アプリケーションを示しています:
Azure 音声サービスリソースを作成する
Azure 音声サービスは Azure Cognitive Services の一部であり、画像認識、音声認識と翻訳、Bing 検索などのタスクにクラウドベースの API を提供します。 詳細については、「Azure Cognitive Services とは」を参照してください。
サンプル プロジェクトでは、Azure portal で Azure Cognitive Services リソースを作成する必要があります。 Cognitive Services リソースは、音声サービスなどの 1 つのサービスに対して作成することも、マルチサービス リソースとして作成することもできます。 音声サービス リソースを作成する手順は次のとおりです:
- Azure portal にサインインします。
- マルチサービスまたは単一サービス リソースを作成します。
- リソースの API キーとリージョン情報を取得します。
- サンプル 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
メソッドは以下を実行します。
- アプリケーションがマイクにアクセスできるかどうかを確認し、アクセスできない場合は早期に終了します。
- まだ存在しない場合
SpeechRecognizer
クラスのインスタンスを作成します。 - 進行中の場合は、継続的な文字起こしを停止します。
- タイムスタンプを挿入し、進行中でない場合は継続的な文字起こしを開始します。
- 新しいアプリケーションの状態に基づいて外観を更新するようにアプリケーションに通知します。
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
メソッドは、指定されたnewText
string
を transcribedText
という名前のLabel
要素に書き込みます。 これにより、UI スレッドでこの更新が強制的に実行されるため、例外を発生させずに任意のコンテキストから呼び出すことができます。 InsertDateTimeRecord
は、現在の日時を transcribedText
インスタンスに書き込み、新しい文字起こしの開始をマークします。 最後に、UpdateDisplayState
メソッドは、文字起こしが進行中かどうかを反映するように、 Button
と ActivityIndicator
要素を更新します。
プラットフォーム マイク サービスを作成する
アプリケーションは、音声データを収集するためのマイク アクセス権を持っている必要があります。 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
には、次の機能があります:
Dependency
属性は、クラスをDependencyService
に登録します。GetPermissionAsync
メソッドは、Android SDK のバージョンに基づいてアクセス許可が必要かどうかを確認し、アクセス許可がまだ付与されていない場合はRequestMicPermissions
を呼び出します。RequestMicPermissions
メソッドは、Snackbar
クラスを使用して、根拠が必要な場合はユーザーにアクセス許可を要求します。それ以外の場合は、直接オーディオ録音のアクセス許可を要求します。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
には、次の機能があります:
Dependency
属性は、クラスをDependencyService
に登録します。GetPermissionAsync
メソッドはRequestMicPermissions
を呼び出して、デバイス ユーザーにアクセス許可を要求します。RequestMicPermissions
メソッドは、共有AVAudioSession
インスタンスを使用して記録アクセス許可を要求します。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
には、次の機能があります:
Dependency
属性は、クラスをDependencyService
に登録します。GetPermissionAsync
メソッドは、MediaCapture
インスタンスの初期化を試みます。 これが失敗した場合は、マイクを有効にするユーザー要求が起動します。OnRequestPermissionResult
メソッドはインターフェイスを満たすために存在しますが、UWP の実装には必要ありません。
最後に、UWP Package.appxmanifest は、アプリケーションがマイクを使用することを指定する必要があります。 Package.appxmanifest ファイルをダブルクリックし、Visual Studio 2019 の [機能] タブで [マイク] オプションを選択します:
アプリケーションをテストする
アプリを実行し、[文字起こし] ボタンをクリックします。 アプリはマイクへのアクセスを要求し、文字起こしプロセスを開始する必要があります。 ActivityIndicator
がアニメーション化され、文字起こしがアクティブであることが示されます。 話すと、アプリはオーディオ データを Azure 音声サービス リソースにストリーミングし、文字起こしされたテキストで応答します。 文字起こしされたテキストは、受信時に Label
要素に表示されます。
Note
Android エミュレーターは、音声サービス ライブラリの読み込みと初期化に失敗します。 Android プラットフォームでは、物理デバイスでのテストをお勧めします。