使用 Azure Speech Service 进行语音识别
作为基于云的 API,Azure 语音服务具备以下功能:
- 语音转文本功能会将音频文件或流转录为文本。
- 文本转语音功能会将输入文本转换为类似人工合成的语音。
- 语音翻译功能为语音转文本和语音转语音启用实时多语言翻译。
- 语音助理功能可以为应用程序创建类似人类的对话界面。
本文介绍如何使用 Azure 语音服务在示例 Xamarin.Forms 应用程序中实现语音转文本。 以下屏幕截图显示了 iOS 和 Android 上的示例应用程序:
创建 Azure 语音服务资源
Azure 语音服务是 Azure 认知服务的一部分,它为图像识别、语音识别和翻译以及 Bing 搜索等任务提供基于云的 API。 有关详细信息,请参阅“什么是 Azure 认知服务?”。
该示例项目需要在 Azure 门户中创建 Azure 认知服务资源。 可以为单个服务(例如语音服务)创建认知服务资源,也可以将其创建为多服务资源。 语音服务资源的创建步骤如下:
- 登录到 Azure 门户。
- 创建多服务或单服务资源。
- 获取资源的 API 密钥和区域信息。
- 更新示例 Constants.cs 文件。
有关创建资源的分步指南,请参阅“创建认知服务资源”。
使用语音服务配置应用
创建认知服务资源后,可以使用 Azure 资源中的区域和 API 密钥更新 Constants.cs 文件:
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>();
}
// ...
}
点击 transcribeButton
实例时,系统将调用 TranscribeClicked
方法:
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
该示例项目为 Android 定义了一个名为 AndroidMicrophoneService
的 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
该示例项目为 iOS 定义了一个名为 iOSMicrophoneService
的 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
示例项目为 UWP 定义了一个名为 UWPMicrophoneService
的 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
元素中显示该转录文本。
注意
Android 模拟器无法加载和初始化语音服务库。 对于 Android 平台,建议在物理设备上进行测试。