【问题标题】:Unity freezes for 2 seconds while Microsoft Azure Text-To-Speech processes inputUnity 在 Microsoft Azure Text-To-Speech 处理输入时冻结 2 秒
【发布时间】:2020-02-29 01:30:14
【问题描述】:

我正在使用带有 Unity 的 Microsoft Azure Text To Speech。它的工作原理是当按下按钮从文本输入中产生语音时,整个应用程序冻结大约 2 秒,然后输出声音,游戏恢复正常。我认为此冻结是由于 Azure 正在处理 TTS?下面是代码。

public void ButtonClick()
    {
        // Creates an instance of a speech config with specified subscription key and service region.
        // Replace with your own subscription key and service region (e.g., "westus").
        var config = SpeechConfig.FromSubscription("[redacted]", "westus");

        // Creates a speech synthesizer.
        // Make sure to dispose the synthesizer after use!
        using (var synthsizer = new SpeechSynthesizer(config, null))
        {
            lock (threadLocker)
            {
                waitingForSpeak = true;
            }

            // Starts speech synthesis, and returns after a single utterance is synthesized.
            var result = synthsizer.SpeakTextAsync(inputField.text).Result;

            // Checks result.
            string newMessage = string.Empty;
            if (result.Reason == ResultReason.SynthesizingAudioCompleted)
            {
                // Since native playback is not yet supported on Unity yet (currently only supported on Windows/Linux Desktop),
                // use the Unity API to play audio here as a short term solution.
                // Native playback support will be added in the future release.
                var sampleCount = result.AudioData.Length / 2;
                var audioData = new float[sampleCount];
                for (var i = 0; i < sampleCount; ++i)
                {
                    audioData[i] = (short)(result.AudioData[i * 2 + 1] << 8 | result.AudioData[i * 2]) / 32768.0F;
                }

                // The default output audio format is 16K 16bit mono
                var audioClip = AudioClip.Create("SynthesizedAudio", sampleCount, 1, 16000, false);
                audioClip.SetData(audioData, 0);
                audioSource.clip = audioClip;
                audioSource.Play();

                newMessage = "Speech synthesis succeeded!";
            }
            else if (result.Reason == ResultReason.Canceled)
            {
                var cancellation = SpeechSynthesisCancellationDetails.FromResult(result);
                newMessage = $"CANCELED:\nReason=[{cancellation.Reason}]\nErrorDetails=[{cancellation.ErrorDetails}]\nDid you update the subscription info?";
            }

            lock (threadLocker)
            {
                message = newMessage;
                waitingForSpeak = false;
            }
        }
    }

    void Start()
    {
        if (inputField == null)
        {
            message = "inputField property is null! Assign a UI InputField element to it.";
            UnityEngine.Debug.LogError(message);
        }
        else if (speakButton == null)
        {
            message = "speakButton property is null! Assign a UI Button to it.";
            UnityEngine.Debug.LogError(message);
        }
        else
        {
            // Continue with normal initialization, Text, InputField and Button objects are present.
            inputField.text = "Enter text you wish spoken here.";
            message = "Click button to synthesize speech";
            speakButton.onClick.AddListener(ButtonClick);
            //ButtonClick();
        }
    }

我希望 TTS 在按下 TTS 按钮时不会冻结整个应用程序,因此当按下 TTS 按钮时应用程序是可用的。任何帮助将不胜感激。

【问题讨论】:

    标签: c# azure unity3d text-to-speech microsoft-cognitive


    【解决方案1】:

    当您执行synthesizer.SpeakTextAsync(inputField.text).Result; 时,它会阻塞直到任务完成。相反,尝试调用Task&lt;SpeechSynthesisResult&gt; task = synthesizer.SpeakTextAsync(inputField.text);,然后设置一个协程,直到task.IsCompleted() 为真,然后执行代码中的其余过程

    这里有一个部分(未经测试的)解决方案可以帮助您入门。我将变量从 synthsizer 更改为 synthesizer,并删除了所有锁定,因为协程在主线程上按顺序发生,因此不需要锁定:

    public void ButtonClick()
    {
        if (waitingForSpeak) return;
    
        waitingForSpeak = true;
    
        // Creates an instance of a speech config with specified subscription
        // key and service region.
        // Replace with your own subscription key and service region (e.g., "westus").
        SpeechConfig config = SpeechConfig.FromSubscription("[redacted]", "westus");
    
        // Creates a speech synthesizer.
        // Make sure to dispose the synthesizer after use!       
        SpeechSynthesizer synthesizer = new SpeechSynthesizer(config, null));
    
        // Starts speech synthesis, and returns after a single utterance is synthesized.
        Task<SpeechSynthesisResult> task = synthesizer.SpeakTextAsync(inputField.text);
    
        StartCoroutine(CheckSynthesizer(task, config, synthesizer));
    }
    
    private IEnumerator CheckSynthesizer(Task<SpeechSynthesisResult> task, 
            SpeechConfig config, 
            SpeechSynthesizer synthesizer)
    {
        yield return new WaitUntil(() => task.IsCompleted());
    
        var result = task.Result;
    
        // Checks result.
        string newMessage = string.Empty;
        if (result.Reason == ResultReason.SynthesizingAudioCompleted)
        {
            // Since native playback is not yet supported on Unity yet (currently
            // only supported on Windows/Linux Desktop),
            // use the Unity API to play audio here as a short term solution.
            // Native playback support will be added in the future release.
            var sampleCount = result.AudioData.Length / 2;
            var audioData = new float[sampleCount];
            for (var i = 0; i < sampleCount; ++i)
            {
                audioData[i] = (short)(result.AudioData[i * 2 + 1] << 8 
                        | result.AudioData[i * 2]) / 32768.0F;
            }
    
            // The default output audio format is 16K 16bit mono
            var audioClip = AudioClip.Create("SynthesizedAudio", sampleCount, 
                    1, 16000, false);
            audioClip.SetData(audioData, 0);
            audioSource.clip = audioClip;
            audioSource.Play();
    
            newMessage = "Speech synthesis succeeded!";
        }
        else if (result.Reason == ResultReason.Canceled)
        {
            var cancellation = SpeechSynthesisCancellationDetails.FromResult(result);
            newMessage = $"CANCELED:\nReason=[{cancellation.Reason}]\n"+
                         $"ErrorDetails=[{cancellation.ErrorDetails}]\n"+"
                         "Did you update the subscription info?";
        }
    
        message = newMessage;
        waitingForSpeak = false;
        synthesizer.Dispose();
    }
    
    void Start()
    {
        if (inputField == null)
        {
            message = "inputField property is null! Assign a UI InputField element to it.";
            UnityEngine.Debug.LogError(message);
        }
        else if (speakButton == null)
        {
            message = "speakButton property is null! Assign a UI Button to it.";
            UnityEngine.Debug.LogError(message);
        }
        else
        {
            // Continue with normal initialization, Text, InputField and Button 
            // objects are present.
            inputField.text = "Enter text you wish spoken here.";
            message = "Click button to synthesize speech";
            speakButton.onClick.AddListener(ButtonClick);
            //ButtonClick();
        }
    }
    

    作为对 cme​​ts 的回应,这里是另一种方法的开始,您可以尝试将数据复制包装在一个任务中并在该任务完成之前产生:

    private IEnumerator CheckSynthesizer(Task<SpeechSynthesisResult> task, 
            SpeechConfig config, 
            SpeechSynthesizer synthesizer)
    {
        yield return new WaitUntil(() => task.IsCompleted());
    
        var result = task.Result;
    
        // Checks result.
        string newMessage = string.Empty;
        if (result.Reason == ResultReason.SynthesizingAudioCompleted)
        {
            // Since native playback is not yet supported on Unity yet (currently
            // only supported on Windows/Linux Desktop),
            // use the Unity API to play audio here as a short term solution.
            // Native playback support will be added in the future release.
    
            Task copyTask = Task.Factory.StartNew( () => 
            {
                var sampleCount = result.AudioData.Length / 2;
                var audioData = new float[sampleCount];
                for (var i = 0; i < sampleCount; ++i)
                {
                    audioData[i] = (short)(result.AudioData[i * 2 + 1] << 8 
                            | result.AudioData[i * 2]) / 32768.0F;
                }
    
                // The default output audio format is 16K 16bit mono
                var audioClip = AudioClip.Create("SynthesizedAudio", sampleCount, 
                        1, 16000, false);
                audioClip.SetData(audioData, 0);
                audioSource.clip = audioClip;
                audioSource.Play();
            });
    
            yield return new WaitUntil(() => copyTask.IsCompleted());
    
            newMessage = "Speech synthesis succeeded!";
        }
        else if (result.Reason == ResultReason.Canceled)
        {
            var cancellation = SpeechSynthesisCancellationDetails.FromResult(result);
            newMessage = $"CANCELED:\nReason=[{cancellation.Reason}]\n"+
                         $"ErrorDetails=[{cancellation.ErrorDetails}]\n"+"
                         "Did you update the subscription info?";
        }
    
        message = newMessage;
        waitingForSpeak = false;
        synthesizer.Dispose();
    }
    

    【讨论】:

    • 谢谢!为将 AudioData 字节数组转换为 audioDatafloat 数组的 for 循环创建一个 Task 是否也有意义?仍然有一点点冻结,我认为这可能是 for 循环也导致了一些延迟。这样它会等待 for 循环完成,然后再执行协程中的剩余代码?
    • 嗯,值得一试,创建该任务,然后让步,直到该任务完成,就像您与另一个任务一样。
    • 下面是 for 循环的任务。这个对吗? private Task&lt;float[]&gt; longForLoop(SpeechSynthesisResult result) { var sampleCount = result.AudioData.Length / 2; var audioData = new float[sampleCount]; for (var i = 0; i &lt; sampleCount; ++i) { audioData[i] = (short)(result.AudioData[i * 2 + 1] &lt;&lt; 8 | result.AudioData[i * 2]) / 32768.0F; } return Task.FromResult(audioData); }
    • 这有点超出我的专业知识,但我继续编辑我的答案,并试图包括一个开始。如果您对此有任何大问题,我会开始 another question,确保在问题中包含代码并描述运行时发生的延迟。
    猜你喜欢
    • 1970-01-01
    • 2019-05-03
    • 2021-01-31
    • 1970-01-01
    • 2016-04-03
    • 1970-01-01
    • 1970-01-01
    • 2019-11-14
    • 1970-01-01
    相关资源
    最近更新 更多