Entrada de voz em DirectX

Nota

Este artigo diz respeito ao legado WinRT nativo APIs. Para novos projetos de aplicações nativas, recomendamos a utilização da API OpenXR.

Este artigo explica como implementar comandos de voz mais o reconhecimento de frases pequenas e de frase numa aplicação DirectX para Windows Mixed Reality.

Nota

Os fragmentos de código neste artigo utilizam C++/CX em vez de C++17-compliant C++/WinRT, que é usado no modelo de projeto holográfico C++. Os conceitos são equivalentes a um projeto C++/WinRT, mas é preciso traduzir o código.

Use SpeechRecognizer para reconhecimento contínuo da fala

Esta secção descreve como usar o reconhecimento contínuo da fala para ativar comandos de voz na sua aplicação. Este walk-through utiliza código da amostra HolographicVoiceInput. Quando a amostra estiver em funcionamento, diga o nome de um dos comandos de cor registados para alterar a cor do cubo giratório.

Primeiro, crie um novo Windows::Media::SpeechRecognition::SpeechRecognizer instance.

A partir de HolographicVoiceInputSampleMain::CreateSpeechConstraintsForCurrentState:

m_speechRecognizer = ref new SpeechRecognizer();

Crie uma lista de comandos de fala para o reconhecedor ouvir. Aqui, construímos um conjunto de comandos para mudar a cor de um holograma. Por conveniência, também criamos os dados que usaremos para os comandos mais tarde.

m_speechCommandList = ref new Platform::Collections::Vector<String^>();
   m_speechCommandData.clear();
   m_speechCommandList->Append(StringReference(L"white"));
   m_speechCommandData.push_back(float4(1.f, 1.f, 1.f, 1.f));
   m_speechCommandList->Append(StringReference(L"grey"));
   m_speechCommandData.push_back(float4(0.5f, 0.5f, 0.5f, 1.f));
   m_speechCommandList->Append(StringReference(L"green"));
   m_speechCommandData.push_back(float4(0.f, 1.f, 0.f, 1.f));
   m_speechCommandList->Append(StringReference(L"black"));
   m_speechCommandData.push_back(float4(0.1f, 0.1f, 0.1f, 1.f));
   m_speechCommandList->Append(StringReference(L"red"));
   m_speechCommandData.push_back(float4(1.f, 0.f, 0.f, 1.f));
   m_speechCommandList->Append(StringReference(L"yellow"));
   m_speechCommandData.push_back(float4(1.f, 1.f, 0.f, 1.f));
   m_speechCommandList->Append(StringReference(L"aquamarine"));
   m_speechCommandData.push_back(float4(0.f, 1.f, 1.f, 1.f));
   m_speechCommandList->Append(StringReference(L"blue"));
   m_speechCommandData.push_back(float4(0.f, 0.f, 1.f, 1.f));
   m_speechCommandList->Append(StringReference(L"purple"));
   m_speechCommandData.push_back(float4(1.f, 0.f, 1.f, 1.f));

Pode usar palavras fonéticas que podem não estar num dicionário para especificar comandos.

m_speechCommandList->Append(StringReference(L"SpeechRecognizer"));
   m_speechCommandData.push_back(float4(0.5f, 0.1f, 1.f, 1.f));

Para carregar a lista de comandos na lista de constrangimentos para o reconhecimento da fala, utilize um objeto SpeechRecognitionListConstraint.

SpeechRecognitionListConstraint^ spConstraint = ref new SpeechRecognitionListConstraint(m_speechCommandList);
   m_speechRecognizer->Constraints->Clear();
   m_speechRecognizer->Constraints->Append(spConstraint);
   create_task(m_speechRecognizer->CompileConstraintsAsync()).then([this](SpeechRecognitionCompilationResult^ compilationResult)
   {
       if (compilationResult->Status == SpeechRecognitionResultStatus::Success)
       {
           m_speechRecognizer->ContinuousRecognitionSession->StartAsync();
       }
       else
       {
           // Handle errors here.
       }
   });

Subscreva o evento ResultadoGerado sobre o DiscursoContinuousRegniçãodo Discurso . Este evento notifica a sua aplicação quando um dos seus comandos foi reconhecido.

m_speechRecognizer->ContinuousRecognitionSession->ResultGenerated +=
       ref new TypedEventHandler<SpeechContinuousRecognitionSession^, SpeechContinuousRecognitionResultGeneratedEventArgs^>(
           std::bind(&HolographicVoiceInputSampleMain::OnResultGenerated, this, _1, _2)
           );

O seu manipulador de eventos OnResultGenerated recebe dados de eventos numa instância do SpeechContinuousRecognitionResultGeneratedEventArgs. Se a confiança for maior do que o limiar definido, a sua aplicação deve ter em conta que o evento aconteceu. Guarde os dados do evento para que possa usá-lo num ciclo de atualização posterior.

Da HolographicVoiceInputSampleMain.cpp:

// Change the cube color, if we get a valid result.
   void HolographicVoiceInputSampleMain::OnResultGenerated(SpeechContinuousRecognitionSession ^sender, SpeechContinuousRecognitionResultGeneratedEventArgs ^args)
   {
       if (args->Result->RawConfidence > 0.5f)
       {
           m_lastCommand = args->Result->Text;
       }
   }

No nosso código de exemplo, mudamos a cor do cubo de holograma giratório de acordo com o comando do utilizador.

De HolographicVoiceInputSampleMain::Update:

// Check for new speech input since the last frame.
   if (m_lastCommand != nullptr)
   {
       auto command = m_lastCommand;
       m_lastCommand = nullptr;

       int i = 0;
       for each (auto& iter in m_speechCommandList)
       {
           if (iter == command)
           {
               m_spinningCubeRenderer->SetColor(m_speechCommandData[i]);
               break;
           }

           ++i;
       }
   }

Use o reconhecimento "one-shot"

Pode configurar um reconhecimento de discurso para ouvir frases ou frases que o utilizador fala. Neste caso, aplicamos um SpeechRecognitionTopicConstraint que diz ao reconhecimento da fala que tipo de entrada esperar. Aqui está um fluxo de trabalho de aplicativos para este cenário:

  1. A sua aplicação cria o SpeechRecognizer, fornece pedidos de UI e começa a ouvir um comando falado.
  2. O utilizador fala uma frase ou frase.
  3. O reconhecimento da fala do utilizador ocorre e um resultado é devolvido à app. Neste momento, a sua aplicação deve fornecer um aviso de UI para indicar que o reconhecimento ocorreu.
  4. Dependendo do nível de confiança a que pretende responder e do nível de confiança do resultado do reconhecimento da fala, a sua app pode processar o resultado e responder conforme apropriado.

Esta secção descreve como criar um SpeechRecognizer, compilar o constrangimento e ouvir a entrada da fala.

O código seguinte compila a restrição de tópico, que neste caso é otimizada para pesquisa web.

auto constraint = ref new SpeechRecognitionTopicConstraint(SpeechRecognitionScenario::WebSearch, L"webSearch");
   m_speechRecognizer->Constraints->Clear();
   m_speechRecognizer->Constraints->Append(constraint);
   return create_task(m_speechRecognizer->CompileConstraintsAsync())
       .then([this](task<SpeechRecognitionCompilationResult^> previousTask)
   {

Se a compilação for bem sucedida, podemos continuar com o reconhecimento da fala.

try
       {
           SpeechRecognitionCompilationResult^ compilationResult = previousTask.get();

           // Check to make sure that the constraints were in a proper format and the recognizer was able to compile it.
           if (compilationResult->Status == SpeechRecognitionResultStatus::Success)
           {
               // If the compilation succeeded, we can start listening for the user's spoken phrase or sentence.
               create_task(m_speechRecognizer->RecognizeAsync()).then([this](task<SpeechRecognitionResult^>& previousTask)
               {

O resultado é então devolvido à aplicação. Se estivermos confiantes o suficiente no resultado, podemos processar o comando. Este exemplo de código processa resultados com pelo menos confiança média.

try
                   {
                       auto result = previousTask.get();

                       if (result->Status != SpeechRecognitionResultStatus::Success)
                       {
                           PrintWstringToDebugConsole(
                               std::wstring(L"Speech recognition was not successful: ") +
                               result->Status.ToString()->Data() +
                               L"\n"
                               );
                       }

                       // In this example, we look for at least medium confidence in the speech result.
                       if ((result->Confidence == SpeechRecognitionConfidence::High) ||
                           (result->Confidence == SpeechRecognitionConfidence::Medium))
                       {
                           // If the user said a color name anywhere in their phrase, it will be recognized in the
                           // Update loop; then, the cube will change color.
                           m_lastCommand = result->Text;

                           PrintWstringToDebugConsole(
                               std::wstring(L"Speech phrase was: ") +
                               m_lastCommand->Data() +
                               L"\n"
                               );
                       }
                       else
                       {
                           PrintWstringToDebugConsole(
                               std::wstring(L"Recognition confidence not high enough: ") +
                               result->Confidence.ToString()->Data() +
                               L"\n"
                               );
                       }
                   }

Sempre que utilizar o reconhecimento de voz, procure exceções que possam indicar que o utilizador desligou o microfone nas definições de privacidade do sistema. Isto pode acontecer durante a inicialização ou reconhecimento.

catch (Exception^ exception)
                   {
                       // Note that if you get an "Access is denied" exception, you might need to enable the microphone
                       // privacy setting on the device and/or add the microphone capability to your app manifest.

                       PrintWstringToDebugConsole(
                           std::wstring(L"Speech recognizer error: ") +
                           exception->ToString()->Data() +
                           L"\n"
                           );
                   }
               });

               return true;
           }
           else
           {
               OutputDebugStringW(L"Could not initialize predefined grammar speech engine!\n");

               // Handle errors here.
               return false;
           }
       }
       catch (Exception^ exception)
       {
           // Note that if you get an "Access is denied" exception, you might need to enable the microphone
           // privacy setting on the device and/or add the microphone capability to your app manifest.

           PrintWstringToDebugConsole(
               std::wstring(L"Exception while trying to initialize predefined grammar speech engine:") +
               exception->Message->Data() +
               L"\n"
               );

           // Handle exceptions here.
           return false;
       }
   });

Nota

Existem vários DiscursosrecongniçãoScenarios predefinidos que podes usar para otimizar o reconhecimento da fala.

  • Para otimizar para ditado, use o cenário de ditado.

    // Compile the dictation topic constraint, which optimizes for speech dictation.
    auto dictationConstraint = ref new SpeechRecognitionTopicConstraint(SpeechRecognitionScenario::Dictation, "dictation");
    m_speechRecognizer->Constraints->Append(dictationConstraint);
    
  • Para pesquisas na web de fala, use a seguinte restrição de cenário específico da web.

    // Add a web search topic constraint to the recognizer.
    auto webSearchConstraint = ref new SpeechRecognitionTopicConstraint(SpeechRecognitionScenario::WebSearch, "webSearch");
    speechRecognizer->Constraints->Append(webSearchConstraint);
    
  • Utilize a restrição do formulário para preencher formulários. Neste caso, é melhor aplicar a sua própria gramática que está otimizada para preencher o formulário.

    // Add a form constraint to the recognizer.
    auto formConstraint = ref new SpeechRecognitionTopicConstraint(SpeechRecognitionScenario::FormFilling, "formFilling");
    speechRecognizer->Constraints->Append(formConstraint );
    
  • Pode fornecer a sua própria gramática no formato SRGS.

Utilizar reconhecimento contínuo

Para o cenário de ditado contínuo, consulte a amostra de código de fala Windows 10 UWP.

Lidar com a degradação da qualidade

As condições ambientais às vezes interferem com o reconhecimento da fala. Por exemplo, o quarto pode ser muito barulhento, ou o utilizador pode falar muito alto. Sempre que possível, a API de reconhecimento de voz fornece informações sobre as condições que causaram a degradação da qualidade. Esta informação é empurrada para a sua aplicação através de um evento WinRT. O exemplo a seguir mostra como subscrever este evento.

m_speechRecognizer->RecognitionQualityDegrading +=
       ref new TypedEventHandler<SpeechRecognizer^, SpeechRecognitionQualityDegradingEventArgs^>(
           std::bind(&HolographicVoiceInputSampleMain::OnSpeechQualityDegraded, this, _1, _2)
           );

Na nossa amostra de código, escrevemos as condições para a consola de depurar. Uma aplicação pode querer fornecer feedback ao utilizador através da UI, síntese de fala e outro método. Ou pode ter de se comportar de forma diferente quando a fala é interrompida por uma redução temporária da qualidade.

void HolographicSpeechPromptSampleMain::OnSpeechQualityDegraded(SpeechRecognizer^ recognizer, SpeechRecognitionQualityDegradingEventArgs^ args)
   {
       switch (args->Problem)
       {
       case SpeechRecognitionAudioProblem::TooFast:
           OutputDebugStringW(L"The user spoke too quickly.\n");
           break;

       case SpeechRecognitionAudioProblem::TooSlow:
           OutputDebugStringW(L"The user spoke too slowly.\n");
           break;

       case SpeechRecognitionAudioProblem::TooQuiet:
           OutputDebugStringW(L"The user spoke too softly.\n");
           break;

       case SpeechRecognitionAudioProblem::TooLoud:
           OutputDebugStringW(L"The user spoke too loudly.\n");
           break;

       case SpeechRecognitionAudioProblem::TooNoisy:
           OutputDebugStringW(L"There is too much noise in the signal.\n");
           break;

       case SpeechRecognitionAudioProblem::NoSignal:
           OutputDebugStringW(L"There is no signal.\n");
           break;

       case SpeechRecognitionAudioProblem::None:
       default:
           OutputDebugStringW(L"An error was reported with no information.\n");
           break;
       }
   }

Se não estiver a utilizar as aulas de árbitros para criar a sua aplicação DirectX, tem de cancelar a subscrição do evento antes de lançar ou recriar o seu reconhecimento de discurso. O HolographicSpeechPromptSample tem uma rotina para parar o reconhecimento e cancelar a subscrição de eventos.

Concurrency::task<void> HolographicSpeechPromptSampleMain::StopCurrentRecognizerIfExists()
   {
       return create_task([this]()
       {
           if (m_speechRecognizer != nullptr)
           {
               return create_task(m_speechRecognizer->StopRecognitionAsync()).then([this]()
               {
                   m_speechRecognizer->RecognitionQualityDegrading -= m_speechRecognitionQualityDegradedToken;

                   if (m_speechRecognizer->ContinuousRecognitionSession != nullptr)
                   {
                       m_speechRecognizer->ContinuousRecognitionSession->ResultGenerated -= m_speechRecognizerResultEventToken;
                   }
               });
           }
           else
           {
               return create_task([this]() { m_speechRecognizer = nullptr; });
           }
       });
   }

Use a síntese da fala para fornecer solicitações audíveis

As amostras de fala holográfica utilizam a síntese da fala para fornecer instruções sonoras ao utilizador. Esta secção mostra como criar uma amostra de voz sintetizada e, em seguida, reproduzi-la através das APIs áudio hrtf.

Recomendamos que forneça as suas próprias indicações de discurso quando solicitar a entrada da frase. As solicitações também podem ajudar a indicar quando os comandos de fala podem ser falados para um cenário de reconhecimento contínuo. O exemplo a seguir demonstra como usar um sintetizador de fala para o fazer. Também pode utilizar um clip de voz pré-gravado, uma UI visual ou outro indicador do que dizer, por exemplo, em cenários em que a solicitação não é dinâmica.

Primeiro, crie o objeto SpeechSynthesizer.

auto speechSynthesizer = ref new Windows::Media::SpeechSynthesis::SpeechSynthesizer();

Também precisa de uma corda que inclua o texto para sintetizar.

// Phrase recognition works best when requesting a phrase or sentence.
   StringReference voicePrompt = L"At the prompt: Say a phrase, asking me to change the cube to a specific color.";

A fala é sintetizada assíncronalmente através do SynthesizeTextToStreamAsync. Aqui, começamos uma tarefa assíncina para sintetizar o discurso.

create_task(speechSynthesizer->SynthesizeTextToStreamAsync(voicePrompt), task_continuation_context::use_current())
       .then([this, speechSynthesizer](task<Windows::Media::SpeechSynthesis::SpeechSynthesisStream^> synthesisStreamTask)
   {
       try
       {

A síntese da fala é enviada como um byte stream. Podemos usar esse fluxo byte para inicializar uma voz XAudio2. Para as nossas amostras de código holográfico, reproduzo-la como um efeito áudio HRTF.

Windows::Media::SpeechSynthesis::SpeechSynthesisStream^ stream = synthesisStreamTask.get();

           auto hr = m_speechSynthesisSound.Initialize(stream, 0);
           if (SUCCEEDED(hr))
           {
               m_speechSynthesisSound.SetEnvironment(HrtfEnvironment::Small);
               m_speechSynthesisSound.Start();

               // Amount of time to pause after the audio prompt is complete, before listening
               // for speech input.
               static const float bufferTime = 0.15f;

               // Wait until the prompt is done before listening.
               m_secondsUntilSoundIsComplete = m_speechSynthesisSound.GetDuration() + bufferTime;
               m_waitingForSpeechPrompt = true;
           }
       }

Tal como no reconhecimento da fala, a síntese da fala abre uma exceção se algo correr mal.

catch (Exception^ exception)
       {
           PrintWstringToDebugConsole(
               std::wstring(L"Exception while trying to synthesize speech: ") +
               exception->Message->Data() +
               L"\n"
               );

           // Handle exceptions here.
       }
   });

Ver também