다음을 통해 공유


Cortana를 통해 음성 명령으로 포그라운드 앱 활성화

Warning

이 기능은 Windows 10 2020년 5월 업데이트(버전 2004, 코드명 “20H1”)부터 더 이상 지원되지 않습니다.

Cortana가 최신적인 생산성 환경을 어떻게 변화시키고 있는지 알아보려면 Microsoft 365의 Cortana를 참조하세요.

Cortana 내에서 음성 명령을 사용하여 시스템 기능에 액세스하는 것 외에도 앱의 기능과 함께 Cortana 확장할 수도 있습니다. 음성 명령을 사용하면 앱이 포그라운드 및 앱 내에서 실행되는 작업 또는 명령으로 활성화될 수 있습니다.

앱이 포그라운드의 음성 명령을 처리하면 포커스를 받고 Cortana가 해제됩니다. 원하는 경우 앱을 활성화하고 백그라운드 작업으로 명령을 실행할 수 있습니다. 이 경우 Cortana는 포커스를 유지하고 앱은Cortana 캔버스와 Cortana 음성을 통해 모든 피드백과 결과를 반환합니다.

특정 연락처에 메시지를 보내는 것과 같이 추가 컨텍스트 또는 사용자 입력이 필요한 음성 명령은 포그라운드 앱에서 가장 잘 처리할 수 있지만, 예정된 여행 나열과 같은 기본 명령은 백그라운드 앱을 통해 Cortana에서 처리할 수 있습니다.

음성 명령을 사용하여 백그라운드에서 앱을 활성화하려면 음성 명령을 사용하여 Cortana에서 백그라운드 앱 활성화 명령을 참조하세요.

참고 항목

음성 명령은 VCD(음성 명령 정의) 파일에 정의된 특정 의도가 있는 단일 발화로 Cortana를 통해 설치된 앱으로 전달됩니다.

VCD 파일은 각각 고유한 의도를 가진 하나 이상의 음성 명령을 정의합니다.

음성 명령 정의는 복잡성이 다를 수 있습니다. 제한된 단일 발화에서 더 유연한 자연어 발화 컬렉션에 이르기까지 모든 것을 지원할 수 있으며 모두 동일한 의도를 나타냅니다.

포그라운드 앱 기능을 보여주기 위해 Cortana 음성 명령 샘플에서 Adventure Works라는 여행 계획 및 관리 앱을 사용합니다.

Cortana 없이 새로운 Adventure Works 여행을 만들려면 앱을 실행하고 새로운 여행 페이지로 이동합니다. 기존 여행을 보려면 앱을 시작하고 예정된 여행 페이지로 이동하여 여행을 선택합니다.

Cortana를 통해 음성 명령을 사용하여 대신 “Adventure Works가 여행 추가” 또는 “Adventure Works에 여행 추가”라고 말하여 앱을 시작하고 새 여행 페이지로 이동할 수 있습니다. 차례로 “Adventure Works, 내 런던 여행 보여줘”라고 말하면 앱이 시작되고 여기에 표시된 여행 세부 정보 페이지로 이동합니다.

포그라운드 앱을 시작하는 Cortana의 스크린샷

음성 명령 기능을 추가하고 음성 또는 키보드 입력을 사용하여 앱과 Cortana 통합하는 기본 단계는 다음과 같습니다.

  1. VCD 파일을 만듭니다. 사용자가 앱을 활성화할 때 작업을 시작하거나 명령을 호출하기 위해 말할 수 있는 모든 음성 명령을 정의하는 XML 문서입니다. VCD 요소 및 특성 v1.2를 참조하세요.
  2. 앱이 실행될 때 VCD 파일에 명령 집합을 등록합니다.
  3. 음성으로 활성화 명령, 앱 내 탐색, 명령 실행을 처리합니다.

필수 조건

UWP(유니버설 Windows 플랫폼) 앱을 처음 개발하는 경우 다음 항목을 살펴보고 여기서 설명하는 기술을 숙지하세요.

사용자 환경 참고 자료

앱을 Cortana와 통합하는 방법에 대한 정보는 Cortana 디자인 가이드라인을 참조하고 유용하고 매력적인 음성 지원 앱을 디자인하는 데 유용한 팁은 음성 상호 작용을 참조하세요.

Visual Studio에서 프로젝트로 새 솔루션 만들기

  1. Microsoft Visual Studio 2015를 시작합니다.

    Visual Studio 2015 시작 페이지가 나타납니다.

  2. 파일 메뉴에서 >프로젝트를 선택합니다.

    새 프로젝트 대화 상자가 나타납니다. 대화 상자의 왼쪽 창에서 표시할 템플릿 유형을 선택할 수 있습니다.

  3. 왼쪽 창에서 설치된 > 템플릿 > Visual C# > Windows를 확장한 다음, 유니버설 템플릿 그룹을 선택합니다. 대화 상자의 가운데 창에는 UWP(유니버설 Windows 플랫폼) 앱용 프로젝트 템플릿 목록이 표시됩니다.

  4. 가운데 창에서 빈 앱(유니버설 Windows) 템플릿을 선택합니다.

    빈 앱 템플릿은 컴파일과 실행은 가능하지만 사용자 인터페이스 컨트롤이나 데이터는 포함되지 않은 최소한의 UWP 앱을 만듭니다. 이 자습서를 진행하는 동안 앱에 컨트롤을 추가합니다.

  5. 이름 텍스트 상자에 프로젝트 이름을 입력합니다. 이 예제에서는 “AdventureWorks”를 사용합니다.

  6. 확인을 클릭하여 프로젝트를 만듭니다.

    Microsoft Visual Studio에서 프로젝트를 만들고 솔루션 탐색기에 표시합니다.

프로젝트에 이미지 자산 추가 및 앱 매니페스트에서 지정

UWP 앱에서는 특정 설정 및 디바이스 기능(고대비, 유효 픽셀, 로케일 등)에 따라 가장 적합한 이미지를 자동으로 선택할 수 있습니다. 이미지를 제공하고 다른 리소스 버전에 대해 앱 프로젝트 내에서 적절한 명명 규칙과 폴더 구성을 사용하는지 확인해야 합니다. 권장 리소스 버전을 제공하지 않으면 사용자의 기본 설정, 기능, 디바이스 유형, 위치에 따라 접근성, 지역화, 이미지 품질이 저하될 수 있습니다.

고대비 및 스케일링 요소의 이미지 리소스에 대한 자세한 내용은 타일 및 아이콘 자산에 대한 지침을 참조하세요.

한정자를 사용하여 리소스 이름을 지정합니다. 리소스 한정자는 특정 버전의 리소스를 사용해야 하는 컨텍스트를 식별하는 폴더 및 파일 이름 한정자입니다.

표준 명명 규칙은 foldername/qualifiername-value[_qualifiername-value]/filename.qualifiername-value[_qualifiername-value].ext입니다. 예를 들어 루트 폴더와 파일 이름 images/logo.png만 사용하여 코드에서 참조할 수 있는 images/logo.scale-100_contrast-white.png가 있습니다. 한정자를 사용하여 리소스 이름을 지정하는 방법을 참조하세요.

현재 지역화되거나 다중 해상도 리소스를 제공할 계획이 없더라도 문자열 리소스 파일(예: en-US\resources.resw)의 기본 언어와 이미지의 기본 스케일링 요소(예: logo.scale-100.png)를 표시하는 것이 좋습니다. 그러나 최소한 100, 200, 400 스케일링 요소에 대한 자산을 제공하는 것이 좋습니다.

Important

Cortana 캔버스의 제목 영역에 사용되는 앱 아이콘은 “Package.appxmanifest” 파일에 지정된 Square44x44Logo 아이콘입니다.

VCD 파일 만들기

  1. Visual Studio에서 기본 프로젝트 이름을 마우스 오른쪽 단추로 클릭하고 새 항목> 추가를 선택합니다. XML 파일을 추가합니다.
  2. VCD 파일의 이름(예: “AdventureWorksCommands.xml”)을 입력하고 추가를 클릭합니다.
  3. 솔루션 탐색기에서 VCD 파일을 선택합니다.
  4. 속성 창에서 작업 빌드콘텐츠로 설정한 다음, 출력 디렉터리에 복사최신 버전인 경우 복사로 설정합니다.

VCD 파일 편집

https://schemas.microsoft.com/voicecommands/1.2를 가리키는 xmlns 특성이 있는 VoiceCommands 요소를 추가합니다.

  1. 앱에서 지원하는 각 언어에 대해 앱에서 지원하는 음성 명령을 포함하는 CommandSet 요소를 만듭니다.

    각기 다른 xml:lang 특성을 가진 여러 CommandSet 요소를 선언할 수 있으므로 앱이 다른 시장에서 사용될 수 있습니다. 예를 들어 미국 앱에는 영어를 위해 CommandSet와 스페인어를 위해 CommandSet가 있을 수 있습니다.

    주의

    앱을 활성화하고 음성 명령을 사용하여 작업을 시작하려면 앱이 사용자의 디바이스에 표시된 음성 언어와 일치하는 언어로 CommandSet를 포함하는 VCD 파일을 등록해야 합니다. 음성 언어는 설정 > 시스템 > 음성 > 음성 언어에 있습니다.

  2. 지원하려는 각 명령에 대해 명령 요소를 추가합니다. VCD 파일에 선언된 각 명령에는 다음 정보가 포함되어야 합니다.

    • 애플리케이션이 런타임에 음성 명령을 식별하는 데 사용하는 AppName 특성입니다.
    • 사용자가 명령을 호출하는 방법을 설명하는 구가 포함된 Example 요소입니다. Cortana에서는 사용자가 “What can I say?”, “도움말”을 말하거나 자세히 보기를 탭할 때 이 예제를 보여 줍니다.
    • 앱이 명령으로 인식하는 단어 또는 구를 포함하는 ListenFor 요소입니다. 각 ListenFor 요소는 명령과 관련된 특정 단어를 포함하는 하나 이상의 PhraseList 요소에 대한 참조를 포함할 수 있습니다.

참고 항목

ListenFor 요소는 프로그래밍 방식으로 수정하면 안 됩니다. 그러나 ListenFor 요소와 연결된 PhraseList 요소는 프로그래밍 방식으로 수정할 수 있습니다. 애플리케이션은 사용자가 앱을 사용할 때 생성된 데이터 세트를 기반으로 런타임 시 PhraseList의 콘텐츠를 수정해야 합니다. Cortana VCD 구 목록을 동적으로 수정을 참조하세요.

애플리케이션이 시작될 때 표시하고 말할 Cortana에 대한 텍스트를 포함하는 Feedback 요소입니다.

Navigate 요소는 음성 명령이 앱을 포그라운드로 활성화함을 나타냅니다. 이 예에서 showTripToDestination 명령은 포그라운드 작업입니다.

VoiceCommandService 요소는 음성 명령이 백그라운드에서 앱을 활성화함을 나타냅니다. 이 요소의 Target 특성 값은 package.appxmanifest 파일에 있는 uap:AppService 요소의 Name 특성 값과 일치해야 합니다. 이 예에서 whenIsTripToDestinationcancelTripToDestination 명령은 앱 서비스의 이름을 “AdventureWorksVoiceCommandService”로 지정하는 백그라운드 작업입니다.

자세한 내용은 VCD 요소 및 특성 v1.2 참조를 확인합니다.

다음은 Adventure Works 앱의 en-us 음성 명령을 정의하는 VCD 파일의 일부입니다.

<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="https://schemas.microsoft.com/voicecommands/1.2">
  <CommandSet xml:lang="en-us" Name="AdventureWorksCommandSet_en-us">
    <AppName> Adventure Works </AppName>
    <Example> Show trip to London </Example>

    <Command Name="showTripToDestination">
      <Example> Show trip to London </Example>
      <ListenFor RequireAppName="BeforeOrAfterPhrase"> show [my] trip to {destination} </ListenFor>
      <ListenFor RequireAppName="ExplicitlySpecified"> show [my] {builtin:AppName} trip to {destination} </ListenFor>
      <Feedback> Showing trip to {destination} </Feedback>
      <Navigate />
    </Command>

    <Command Name="whenIsTripToDestination">
      <Example> When is my trip to Las Vegas?</Example>
      <ListenFor RequireAppName="BeforeOrAfterPhrase"> when is [my] trip to {destination}</ListenFor>
      <ListenFor RequireAppName="ExplicitlySpecified"> when is [my] {builtin:AppName} trip to {destination} </ListenFor>
      <Feedback> Looking for trip to {destination}</Feedback>
      <VoiceCommandService Target="AdventureWorksVoiceCommandService"/>
    </Command>
    
    <Command Name="cancelTripToDestination">
      <Example> Cancel my trip to Las Vegas </Example>
      <ListenFor RequireAppName="BeforeOrAfterPhrase"> cancel [my] trip to {destination}</ListenFor>
      <ListenFor RequireAppName="ExplicitlySpecified"> cancel [my] {builtin:AppName} trip to {destination} </ListenFor>
      <Feedback> Cancelling trip to {destination}</Feedback>
      <VoiceCommandService Target="AdventureWorksVoiceCommandService"/>
    </Command>

    <PhraseList Label="destination">
      <Item>London</Item>
      <Item>Las Vegas</Item>
      <Item>Melbourne</Item>
      <Item>Yosemite National Park</Item>
    </PhraseList>
  </CommandSet>

VCD 명령 설치

VCD를 설치하려면 앱을 한 번 실행해야 합니다.

참고 항목

음성 명령 데이터는 앱 설치 간에 보존되지 않습니다. 앱의 음성 명령 데이터가 그대로 유지되도록 하려면 앱을 시작하거나 활성화할 때마다 VCD 파일을 초기화하거나 VCD가 현재 설치되어 있는지 여부를 나타내는 설정을 유지하는 것이 좋습니다.

“app.xaml.cs” 파일에서 다음을 수행합니다.

  1. 다음 using 지시문을 추가합니다.

    using Windows.Storage;
    
  2. “OnLaunched” 메서드를 async 한정자로 표시합니다.

    protected async override void OnLaunched(LaunchActivatedEventArgs e)
    
  3. Onlaunched 처리기에서 InstallCommandDefinitionsFromStorageFileAsync를 호출하여 시스템에서 인식해야 하는 음성 명령을 등록합니다.

Adventure Works 샘플에서는 먼저 StorageFile 개체를 정의합니다.

그런 다음, GetFileAsync를 호출하여 “AdventureWorksCommands.xml” 파일로 초기화합니다.

그런 다음, 이 StorageFile 개체는 InstallCommandDefinitionsFromStorageFileAsync에 전달됩니다.

try
{
  // Install the main VCD. 
  StorageFile vcdStorageFile = 
  await Package.Current.InstalledLocation.GetFileAsync(
  @"AdventureWorksCommands.xml");

  await Windows.ApplicationModel.VoiceCommands.VoiceCommandDefinitionManager.
InstallCommandDefinitionsFromStorageFileAsync(vcdStorageFile);

  // Update phrase list.
  ViewModel.ViewModelLocator locator = App.Current.Resources["ViewModelLocator"] as ViewModel.ViewModelLocator;
  if(locator != null)
  {
     await locator.TripViewModel.UpdateDestinationPhraseList();
  }
}
catch (Exception ex)
{
  System.Diagnostics.Debug.WriteLine("Installing Voice Commands Failed: " + ex.ToString());
}

활성화 및 음성 명령 실행 처리

앱이 한 번 이상 실행되고 음성 명령 집합이 설치된 후 앱이 후속 음성 명령 활성화에 응답하는 방법을 지정합니다.

  1. 앱이 음성 명령으로 활성화되었는지 확인합니다.

    Application.OnActivated 이벤트를 재정의하고 IActivatedEventArgs.KindVoiceCommand인지 확인합니다.

  2. 명령의 이름과 말한 내용을 확인합니다.

    IActivatedEventArgs에서 VoiceCommandActivatedEventArgs 개체에 대한 참조를 가져오고 SpeechRecognitionResult 개체의 Result 속성을 쿼리합니다.

    사용자가 말한 내용을 확인하려면 Text의 값이나 SpeechRecognitionSemanticInterpretation 사전에서 인식된 구의 의미 체계 속성을 확인합니다.

  3. 원하는 페이지로 이동하는 등 앱에서 적절한 작업을 수행합니다.

이 예에서는 3단계: VCD 파일 편집에서 VCD를 다시 참조합니다.

음성 명령에 대한 음성 인식 결과를 수신한 후 RulePath 배열의 첫 번째 값에서 명령 이름을 가져옵니다. VCD 파일이 둘 이상의 가능한 음성 명령을 정의했으므로 VCD의 명령 이름과 값을 비교하여 적절한 조치를 취해야 합니다.

애플리케이션에 대한 가장 일반적인 작업은 음성 명령 컨텍스트와 관련된 콘텐츠가 있는 페이지로 이동하는 것입니다. 이 예에서는 TripPage 페이지로 이동하여 음성 명령 값, 명령 입력 방법, 인식된 “대상” 구(해당되는 경우)를 전달합니다. 또는 해당 페이지로 이동할 때 앱이 SpeechRecognitionResult에 탐색 매개 변수를 보낼 수 있습니다.

commandMode 키를 사용하여 SpeechRecognitionSemanticInterpretation.Properties 사전에서 앱을 실행한 음성 명령이 실제로 음성인지 또는 텍스트로 입력되었는지 확인할 수 있습니다. 해당 키의 값은 “음성” 또는 “텍스트” 중 하나입니다. 키 값이 “음성”이면 앱에서 음성 합성(Windows.Media.SpeechSynthesis)을 사용하여 사용자에게 음성 피드백을 제공하는 것이 좋습니다.

SpeechRecognitionSemanticInterpretation.Properties를 사용하여 ListenFor 요소의 PhraseList 또는 PhraseTopic 제약 조건에서 말한 콘텐츠를 찾습니다. 사전 키는 PhraseList 또는 PhraseTopic 요소의 Label 특성 값입니다. 여기서는 {destination} 구 값에 액세스하는 방법을 보여 줍니다.

/// <summary>
/// Entry point for an application activated by some means other than normal launching. 
/// This includes voice commands, URI, share target from another app, and so on. 
/// 
/// NOTE:
/// A previous version of the VCD file might remain in place 
/// if you modify it and update the app through the store. 
/// Activations might include commands from older versions of your VCD. 
/// Try to handle these commands gracefully.
/// </summary>
/// <param name="args">Details about the activation method.</param>
protected override void OnActivated(IActivatedEventArgs args)
{
    base.OnActivated(args);

    Type navigationToPageType;
    ViewModel.TripVoiceCommand? navigationCommand = null;

    // Voice command activation.
    if (args.Kind == ActivationKind.VoiceCommand)
    {
        // Event args can represent many different activation types. 
        // Cast it so we can get the parameters we care about out.
        var commandArgs = args as VoiceCommandActivatedEventArgs;

        Windows.Media.SpeechRecognition.SpeechRecognitionResult speechRecognitionResult = commandArgs.Result;

        // Get the name of the voice command and the text spoken. 
        // See VoiceCommands.xml for supported voice commands.
        string voiceCommandName = speechRecognitionResult.RulePath[0];
        string textSpoken = speechRecognitionResult.Text;

        // commandMode indicates whether the command was entered using speech or text.
        // Apps should respect text mode by providing silent (text) feedback.
        string commandMode = this.SemanticInterpretation("commandMode", speechRecognitionResult);
        
        switch (voiceCommandName)
        {
            case "showTripToDestination":
                // Access the value of {destination} in the voice command.
                string destination = this.SemanticInterpretation("destination", speechRecognitionResult);

                // Create a navigation command object to pass to the page. 
                navigationCommand = new ViewModel.TripVoiceCommand(
                    voiceCommandName,
                    commandMode,
                    textSpoken,
                    destination);

                // Set the page to navigate to for this voice command.
                navigationToPageType = typeof(View.TripDetails);
                break;
            default:
                // If we can't determine what page to launch, go to the default entry point.
                navigationToPageType = typeof(View.TripListView);
                break;
        }
    }
    // Protocol activation occurs when a card is clicked within Cortana (using a background task).
    else if (args.Kind == ActivationKind.Protocol)
    {
        // Extract the launch context. In this case, we're just using the destination from the phrase set (passed
        // along in the background task inside Cortana), which makes no attempt to be unique. A unique id or 
        // identifier is ideal for more complex scenarios. We let the destination page check if the 
        // destination trip still exists, and navigate back to the trip list if it doesn't.
        var commandArgs = args as ProtocolActivatedEventArgs;
        Windows.Foundation.WwwFormUrlDecoder decoder = new Windows.Foundation.WwwFormUrlDecoder(commandArgs.Uri.Query);
        var destination = decoder.GetFirstValueByName("LaunchContext");

        navigationCommand = new ViewModel.TripVoiceCommand(
                                "protocolLaunch",
                                "text",
                                "destination",
                                destination);

        navigationToPageType = typeof(View.TripDetails);
    }
    else
    {
        // If we were launched via any other mechanism, fall back to the main page view.
        // Otherwise, we'll hang at a splash screen.
        navigationToPageType = typeof(View.TripListView);
    }

    // Repeat the same basic initialization as OnLaunched() above, taking into account whether
    // or not the app is already active.
    Frame rootFrame = Window.Current.Content as Frame;

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active.
    if (rootFrame == null)
    {
        // Create a frame to act as the navigation context and navigate to the first page.
        rootFrame = new Frame();
        App.NavigationService = new NavigationService(rootFrame);

        rootFrame.NavigationFailed += OnNavigationFailed;

        // Place the frame in the current window.
        Window.Current.Content = rootFrame;
    }

    // Since we're expecting to always show a details page, navigate even if 
    // a content frame is in place (unlike OnLaunched).
    // Navigate to either the main trip list page, or if a valid voice command
    // was provided, to the details page for that trip.
    rootFrame.Navigate(navigationToPageType, navigationCommand);

    // Ensure the current window is active
    Window.Current.Activate();
}

/// <summary>
/// Returns the semantic interpretation of a speech result. 
/// Returns null if there is no interpretation for that key.
/// </summary>
/// <param name="interpretationKey">The interpretation key.</param>
/// <param name="speechRecognitionResult">The speech recognition result to get the semantic interpretation from.</param>
/// <returns></returns>
private string SemanticInterpretation(string interpretationKey, SpeechRecognitionResult speechRecognitionResult)
{
  return speechRecognitionResult.SemanticInterpretation.Properties[interpretationKey].FirstOrDefault();
}