Entrada de voz no DirectX
Nota
Este artigo está relacionado com as APIs nativas do WinRT legadas. Para novos projetos de aplicações nativas, recomendamos a utilização da API OpenXR.
Este artigo explica como implementar comandos de voz e o reconhecimento de frases e expressões pequenas numa aplicação DirectX para Windows Mixed Reality.
Nota
Os fragmentos de código neste artigo utilizam C++/CX em vez de C++17 compatíveis com C++/WinRT, que é utilizado no modelo de projeto holográfico C++. Os conceitos são equivalentes para um projeto C++/WinRT, mas tem de traduzir o código.
Utilizar SpeechRecognizer para reconhecimento de voz contínuo
Esta secção descreve como utilizar o reconhecimento de voz contínuo para ativar comandos de voz na sua aplicação. Estas instruções utilizam código do exemplo HolographicVoiceInput . Quando o exemplo estiver em execução, fale o nome de um dos comandos de cor registados para alterar a cor do cubo giratório.
Primeiro, crie uma nova instância Windows::Media::SpeechRecognition::SpeechRecognizer .
De HolographicVoiceInputSampleMain::CreateSpeechConstraintsForCurrentState:
m_speechRecognizer = ref new SpeechRecognizer();
Crie uma lista de comandos de voz para o reconhecedor escutar. Aqui, criamos um conjunto de comandos para alterar a cor de um holograma. Para sua comodidade, também criamos os dados que iremos utilizar 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 utilizar 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 para a lista de restrições do reconhecedor de voz, 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 ResultGenerated no SpeechContinuousRecognitionSession do reconhecedor de voz. Este evento notifica a sua aplicação quando um dos seus comandos tiver sido reconhecido.
m_speechRecognizer->ContinuousRecognitionSession->ResultGenerated +=
ref new TypedEventHandler<SpeechContinuousRecognitionSession^, SpeechContinuousRecognitionResultGeneratedEventArgs^>(
std::bind(&HolographicVoiceInputSampleMain::OnResultGenerated, this, _1, _2)
);
O processador de eventos OnResultGenerated recebe dados de eventos numa instância SpeechContinuousRecognitionResultGeneratedEventArgs . Se a confiança for superior ao limiar que definiu, a aplicação deverá ter em atenção que o evento ocorreu. Guarde os dados do evento para que possa utilizá-lo num ciclo de atualização posterior.
Em 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, alteramos a cor do cubo holograma giratório de acordo com o comando do utilizador.
A partir 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;
}
}
Utilizar o reconhecimento "one-shot"
Pode configurar um reconhecedor de voz para escutar expressões ou frases que o utilizador fale. Neste caso, aplicamos um SpeechRecognitionTopicConstraint que indica ao reconhecedor de voz que tipo de entrada esperar. Eis um fluxo de trabalho de aplicação para este cenário:
- A sua aplicação cria o SpeechRecognizer, fornece pedidos de IU e começa a escutar um comando falado.
- O utilizador fala uma expressão ou frase.
- O reconhecimento da voz do utilizador ocorre e é devolvido um resultado à aplicação. Neste momento, a sua aplicação deve fornecer um pedido de IU para indicar que ocorreu o reconhecimento.
- Dependendo do nível de confiança ao qual pretende responder e do nível de confiança do resultado do reconhecimento de voz, a sua aplicação pode processar o resultado e responder conforme adequado.
Esta secção descreve como criar um SpeechRecognizer, compilar a restrição e ouvir entradas de voz.
O código seguinte compila a restrição de tópico, que neste caso está otimizada para pesquisa na 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 de voz.
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)
{
Em seguida, o resultado é devolvido à aplicação. Se tivermos confiança 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, watch para exceções que possam indicar que o utilizador desativou 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 SpeechRecognitionScenarios predefinidos que pode utilizar para otimizar o reconhecimento de voz.
Para otimizar o ditado, utilize 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 voz, utilize 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 de formulário para preencher formulários. Neste caso, é melhor aplicar a sua própria gramática 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 o reconhecimento contínuo
Para o cenário de ditado contínuo, veja o exemplo de código de voz Windows 10 UWP.
Lidar com a degradação da qualidade
Por vezes, as condições ambientais interferem com o reconhecimento de voz. Por exemplo, a sala pode ser demasiado ruidosa 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. Estas informações são enviadas para a sua aplicação através de um evento WinRT. O exemplo seguinte mostra como subscrever este evento.
m_speechRecognizer->RecognitionQualityDegrading +=
ref new TypedEventHandler<SpeechRecognizer^, SpeechRecognitionQualityDegradingEventArgs^>(
std::bind(&HolographicVoiceInputSampleMain::OnSpeechQualityDegraded, this, _1, _2)
);
No nosso exemplo de código, escrevemos as informações das condições na consola de depuração. Uma aplicação poderá querer fornecer feedback ao utilizador através da IU, da sintetização de voz e de outro método. Ou poderá ter de se comportar de forma diferente quando a voz é 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 classes ref para criar a sua aplicação DirectX, tem de anular a subscrição do evento antes de lançar ou recriar o reconhecedor de voz. O HolographicSpeechPromptSample tem uma rotina para parar o reconhecimento e anular 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; });
}
});
}
Utilizar a síntese de voz para fornecer avisos sonoros
Os exemplos de voz holográfica utilizam a síntese de voz para fornecer instruções audíveis ao utilizador. Esta secção mostra como criar um exemplo de voz sintetizado e reproduzi-lo novamente através das APIs de áudio HRTF.
Recomendamos que forneça os seus próprios pedidos de voz quando pedir a entrada da expressão. Os pedidos também podem ajudar a indicar quando os comandos de voz podem ser falados para um cenário de reconhecimento contínuo. O exemplo seguinte demonstra como utilizar um sintetizador de voz para o fazer. Também pode utilizar um clip de voz pré-gravado, uma IU visual ou outro indicador do que dizer, por exemplo, em cenários em que o pedido não é dinâmico.
Primeiro, crie o objeto SpeechSynthesizer.
auto speechSynthesizer = ref new Windows::Media::SpeechSynthesis::SpeechSynthesizer();
Também precisa de uma cadeia 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 voz é sintetizada de forma assíncrona através de SynthesizeTextToStreamAsync. Aqui, iniciamos uma tarefa assíncrona para sintetizar a voz.
create_task(speechSynthesizer->SynthesizeTextToStreamAsync(voicePrompt), task_continuation_context::use_current())
.then([this, speechSynthesizer](task<Windows::Media::SpeechSynthesis::SpeechSynthesisStream^> synthesisStreamTask)
{
try
{
A síntese de voz é enviada como um fluxo de bytes. Podemos utilizar esse fluxo de bytes para inicializar uma voz XAudio2. Para os nossos exemplos de código holográfico, reproduzemo-lo como um efeito de á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 acontece com o reconhecimento de voz, a síntese de voz gera 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.
}
});