Reconocimiento de voz con el servicio de voz de Azure

Azure Speech Service es una API basada en la nube que ofrece la siguiente funcionalidad:

  • Conversión de voz a texto transcribe archivos de audio o secuencias a texto.
  • Texto a voz convierte el texto de entrada en voz sintetizada similar a la humana.
  • Traducción de voz permite la traducción en tiempo real y en varios idiomas tanto para la conversión de voz a texto como para la conversión de voz a voz.
  • Asistentes de voz pueden crear interfaces de conversación similares a las humanas para aplicaciones.

En este artículo se explica cómo se implementa la conversión de voz en texto en la aplicación de ejemplo Xamarin.Forms mediante Azure Speech Service. En las capturas de pantalla siguientes se muestra la aplicación de ejemplo en iOS y Android:

Capturas de pantalla de la aplicación de ejemplo en iOS y Android

Creación de un recurso de Azure Speech Service

Azure Speech Service forma parte de Azure Cognitive Services, que proporciona API basadas en la nube para tareas como el reconocimiento de imágenes, el reconocimiento de voz y la traducción, y la búsqueda de Bing. Para más información, consulte ¿Qué es Azure Cognitive Services?.

El proyecto de ejemplo requiere que se cree un recurso de Azure Cognitive Services en Azure Portal. Se puede crear un recurso de Cognitive Services para un único servicio, como Speech Service o como un recurso de varios servicios. Los pasos para crear un recurso de Speech Service son los siguientes:

  1. Inicie sesión en Azure Portal.
  2. Cree un recurso de varios servicios o de un solo servicio.
  3. Obtenga la información de la clave de API y la región del recurso.
  4. Actualice el archivo Constants.cs de ejemplo.

Para obtener una guía paso a paso para crear un recurso, consulte Creación de un recurso de Cognitive Services.

Nota:

Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar. Una vez que tenga una cuenta, se puede crear un recurso de un solo servicio en el nivel gratuito para probar el servicio.

Configuración de la aplicación con Speech Service

Después de crear un recurso de Cognitive Services, el archivo Constants.cs se puede actualizar con la región y la clave de API del recurso de Azure:

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

Instalación del paquete Speech Service de NuGet

La aplicación de ejemplo usa el paquete NuGet de Microsoft.CognitiveServices.Speech para conectarse al Azure Speech Service. Instale este paquete NuGet en el proyecto compartido y en cada proyecto de plataforma.

Crear una interfaz IMicrophoneService

Cada plataforma requiere permiso para acceder al micrófono. El proyecto de ejemplo proporciona una interfaz IMicrophoneService en el proyecto compartido y usa el Xamarin.FormsDependencyService para obtener implementaciones de plataforma de la interfaz.

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

Crear el diseño de página

El proyecto de ejemplo define un diseño de página básico en el archivo MainPage.xaml. Los elementos de diseño clave son un Button que inicia el proceso de transcripción, un Label que contiene el texto transcrito y un ActivityIndicator que se mostrará cuando la transcripción está en curso:

<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>

Implementación del servicio voz

El archivo de código subyacente de MainPage.xaml.cs contiene toda la lógica para enviar audio y recibir texto transcrito del Azure Speech Service.

El constructor MainPage obtiene una instancia de la interfaz IMicrophoneService de la DependencyService:

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

    public MainPage()
    {
        InitializeComponent();

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

    // ...
}

Se llama al método TranscribeClicked cuando se pulsa la instancia 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();
}

El método TranscribeClicked hace lo siguiente:

  1. Comprueba si la aplicación tiene acceso al micrófono y sale temprano si no lo hace.
  2. Crea una instancia de clase SpeechRecognizer si aún no existe.
  3. Detiene la transcripción continua si está en curso.
  4. Inserta una marca de tiempo e inicia la transcripción continua si no está en curso.
  5. Notifica a la aplicación que actualice su apariencia en función del nuevo estado de la aplicación.

El resto de los métodos MainPage de clase son asistentes para mostrar el estado de la aplicación:

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;
        }
    });
}

El UpdateTranscription método escribe el proporcionado newText string en el Label elemento denominado transcribedText. Obliga a que esta actualización se produzca en el subproceso de la interfaz de usuario para que se pueda llamar desde cualquier contexto sin causar excepciones. InsertDateTimeRecord escribe la fecha y hora actuales en la instancia transcribedText para marcar el inicio de una nueva transcripción. Por último, el método UpdateDisplayState actualiza los elementos Button y ActivityIndicator para reflejar si la transcripción está en curso o no.

Creación de servicios de micrófono de plataforma

La aplicación debe tener acceso de micrófono para recopilar datos de voz. La interfaz IMicrophoneService debe implementarse y registrarse con el DependencyService en cada plataforma para que funcione la aplicación.

Android

El proyecto de ejemplo define una implementación IMicrophoneService para Android denominada 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 tiene las siguientes características:

  1. El atributo Dependency registra la clase con DependencyService.
  2. El método GetPermissionAsync comprueba si se requieren permisos en función de la versión de Android SDK y llama a RequestMicPermissions si aún no se ha concedido el permiso.
  3. El método RequestMicPermissions usa la clase Snackbar para solicitar permisos del usuario si se requiere una justificación; de lo contrario, solicita directamente permisos de grabación de audio.
  4. Se llama al método OnRequestPermissionResult con un resultado de bool una vez que el usuario ha respondido a la solicitud de permisos.

La clase MainActivity se personaliza para actualizar la instancia AndroidMicrophoneService cuando se completan las solicitudes de permisos:

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;
        }
    }
}

La clase MainActivity define una referencia estática denominada Instance, que requiere el objeto AndroidMicrophoneService al solicitar permisos. Reemplaza el método OnRequestPermissionsResult para actualizar el objeto AndroidMicrophoneService cuando el usuario aprueba o deniega la solicitud de permisos.

Por último, la aplicación Android debe incluir el permiso para grabar audio en el archivo AndroidManifest.xml:

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

iOS

El proyecto de ejemplo define una implementación IMicrophoneService para iOS denominada iOSMicrophoneService:

[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 tiene las siguientes características:

  1. El atributo Dependency registra la clase con DependencyService.
  2. El método GetPermissionAsync llama a RequestMicPermissions para solicitar permisos del usuario del dispositivo.
  3. El método RequestMicPermissions usa la instancia AVAudioSession compartida para solicitar permisos de grabación.
  4. El método OnRequestPermissionResult actualiza la instancia TaskCompletionSource con el valor bool proporcionado.

Por último, la aplicación de iOS Info.plist debe incluir un mensaje que indique al usuario por qué la aplicación solicita acceso al micrófono. Edite el archivo Info.plist para incluir las siguientes etiquetas dentro del elemento <dict>:

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

UWP

El proyecto de ejemplo define una implementación IMicrophoneService para UWP denominada UWPMicrophoneService:

[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 tiene las siguientes características:

  1. El atributo Dependency registra la clase con DependencyService.
  2. El método GetPermissionAsync intenta inicializar una instancia MediaCapture. Si se produce un error, inicia una solicitud de usuario para habilitar el micrófono.
  3. El método OnRequestPermissionResult existe para satisfacer la interfaz, pero no es necesario para la implementación de UWP.

Por último, el Package.appxmanifest de UWP debe especificar que la aplicación use el micrófono. Haga doble clic en el archivo Package.appxmanifest y seleccione la opción Micrófono en la pestaña Funcionalidades de Visual Studio 2019:

Captura de pantalla del manifiesto en Visual Studio 2019

Prueba de la aplicación

Ejecute la aplicación y haga clic en el botón Transcribir. La aplicación debe solicitar acceso al micrófono y comenzar el proceso de transcripción. El ActivityIndicator se animará, mostrando que la transcripción está activa. Mientras habla, la aplicación transmitirá datos de audio al recurso de Azure Speech Services, que responderá con texto transcrito. El texto transcrito aparecerá en el elemento Label a medida que se recibe.

Nota:

Los emuladores de Android no se cargan e inicializan las bibliotecas del servicio de voz. Se recomienda realizar pruebas en un dispositivo físico para la plataforma Android.