Голосовой ввод в DirectX

Примечание

Эта статья относится к устаревшим собственным API WinRT. Для новых проектов собственных приложений рекомендуется использовать API OpenXR.

В этой статье объясняется, как реализовать голосовые команды, а также распознавание небольших фраз и предложений в приложении DirectX для Windows Mixed Reality.

Примечание

В фрагментах кода в этой статье используется C++/CX, а не C++17 C++/WinRT, который используется в шаблоне голографического проекта C++. Эти понятия эквивалентны для проекта C++/WinRT, но необходимо перевести код.

Использование SpeechRecognizer для непрерывного распознавания речи

В этом разделе описывается, как использовать непрерывное распознавание речи для включения голосовых команд в приложении. В этом пошаговом руководстве используется код из примера HolographicVoiceInput . При выполнении примера произнесите имя одной из зарегистрированных команд цвета, чтобы изменить цвет вращающегося куба.

Сначала создайте экземпляр Windows::Media::SpeechRecognition::SpeechRecognizer .

Из holographicVoiceInputSampleMain::CreateSpeechConstraintsForCurrentState:

m_speechRecognizer = ref new SpeechRecognizer();

Создайте список речевых команд для прослушивания распознавателя. Здесь мы создадим набор команд для изменения цвета голограммы. Для удобства мы также создадим данные, которые мы будем использовать для команд позже.

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

Для указания команд можно использовать фонетические слова, которые могут не находиться в словаре.

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

Чтобы загрузить список команд в список ограничений для распознавателя речи, используйте объект 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.
       }
   });

Подпишитесь на событие ResultGenerated в speechContinuousRecognitionSession распознавателя речи. Это событие уведомляет приложение о том, что одна из ваших команд была распознана.

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

Обработчик событий OnResultGenerated получает данные о событиях в экземпляре SpeechContinuousRecognitionResultGeneratedEventArgs . Если достоверность превышает пороговое значение, которое вы определили, приложение должно отметить, что произошло событие. Сохраните данные события, чтобы использовать их в последующем цикле обновления.

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

В нашем примере кода мы изменяем цвет вращающегося куба голограммы в соответствии с командой пользователя.

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

Использование единого распознавания

Вы можете настроить распознаватель речи для прослушивания фраз или предложений, которые произносит пользователь. В этом случае мы применяем SpeechRecognitionTopicConstraint , который сообщает распознавательу речи, какой тип входных данных следует ожидать. Ниже приведен рабочий процесс приложения для этого сценария.

  1. Приложение создает SpeechRecognizer, предоставляет запросы пользовательского интерфейса и начинает прослушивать произнесенные команды.
  2. Пользователь произносит фразу или предложение.
  3. Выполняется распознавание речи пользователя, и в приложение возвращается результат. На этом этапе приложение должно предоставить запрос пользовательского интерфейса, чтобы указать, что произошло распознавание.
  4. В зависимости от уровня достоверности, на который вы хотите ответить, и уровня достоверности результата распознавания речи, ваше приложение может обработать результат и ответить соответствующим образом.

В этом разделе описывается, как создать SpeechRecognizer, скомпилировать ограничение и прослушивать ввод речи.

Следующий код компилирует ограничение раздела, которое в данном случае оптимизировано для поиска в Интернете.

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

Если компиляция выполнена успешно, можно продолжить распознавание речи.

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

Затем результат возвращается в приложение. Если мы достаточно уверены в результатах, мы можем обработать команду . В этом примере кода результаты обрабатываются по крайней мере со средней достоверностью.

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

Всякий раз, когда вы используете распознавание речи, watch исключений, которые могут указывать на то, что пользователь отключил микрофон в параметрах конфиденциальности системы. Это может произойти во время инициализации или распознавания.

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

Примечание

Существует несколько предопределенных speechRecognitionScenario, которые можно использовать для оптимизации распознавания речи.

  • Чтобы оптимизировать диктовку, используйте сценарий диктовки.

    // Compile the dictation topic constraint, which optimizes for speech dictation.
    auto dictationConstraint = ref new SpeechRecognitionTopicConstraint(SpeechRecognitionScenario::Dictation, "dictation");
    m_speechRecognizer->Constraints->Append(dictationConstraint);
    
  • Для голосового поиска в Интернете используйте следующее ограничение сценария для конкретного веб-сайта.

    // Add a web search topic constraint to the recognizer.
    auto webSearchConstraint = ref new SpeechRecognitionTopicConstraint(SpeechRecognitionScenario::WebSearch, "webSearch");
    speechRecognizer->Constraints->Append(webSearchConstraint);
    
  • Используйте ограничение формы для заполнения форм. В этом случае лучше применить собственную грамматику, оптимизированную для заполнения формы.

    // Add a form constraint to the recognizer.
    auto formConstraint = ref new SpeechRecognitionTopicConstraint(SpeechRecognitionScenario::FormFilling, "formFilling");
    speechRecognizer->Constraints->Append(formConstraint );
    
  • Вы можете предоставить собственную грамматику в формате SRGS.

Использование непрерывного распознавания

Сценарий непрерывной диктовки см. в примере кода речи UWP для Windows 10.

Обработка снижения качества

Условия окружающей среды иногда мешают распознаванию речи. Например, комната может быть слишком шумной или пользователь может говорить слишком громко. По возможности API распознавания речи предоставляет сведения об условиях, вызвавших ухудшение качества. Эти сведения передаются в приложение с помощью события WinRT. В следующем примере показано, как подписаться на это событие.

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

В нашем примере кода мы записываем сведения об условиях в консоль отладки. Приложение может захотеть предоставить пользователю обратную связь с помощью пользовательского интерфейса, синтеза речи и другого метода. Или может потребоваться по-другому вести себя, когда речь прерывается временным снижением качества.

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

Если вы не используете ссылочные классы для создания приложения DirectX, необходимо отменить подписку на событие перед выпуском или воссоздаванием распознавателя речи. HolographicSpeechPromptSample имеет подпрограмму для остановки распознавания и отмены подписки на события.

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

Использование синтеза речи для предоставления звуковых подсказок

Голографические образцы речи используют синтез речи для предоставления пользователю звуковых инструкций. В этом разделе показано, как создать синтезированный образец голоса, а затем воспроизвести его с помощью API звука HRTF.

При запросе ввода фраз рекомендуется предоставлять собственные голосовые запросы. Запросы также помогают указать, когда речевые команды можно произносить для сценария непрерывного распознавания. В следующем примере показано, как использовать для этого синтезатор речи. Вы также можете использовать предварительно записанный голосовой клип, визуальный пользовательский интерфейс или другой индикатор того, что нужно сказать, например в сценариях, где запрос не является динамическим.

Сначала создайте объект SpeechSynthesizer.

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

Вам также потребуется строка, содержащая текст для синтеза.

// 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.";

Речь синтезируется асинхронно с помощью синтезаTextToStreamAsync. Здесь мы начинаем асинхронную задачу для синтеза речи.

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

Синтез речи отправляется в виде потока байтов. Мы можем использовать этот поток байтов для инициализации голоса XAudio2. Для наших примеров голографического кода мы воспроизводим его как звуковой эффект 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;
           }
       }

Как и в случае с распознаванием речи, синтез речи создает исключение, если что-то пойдет не так.

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

           // Handle exceptions here.
       }
   });

См. также раздел