Как мне заставить TTS перестать говорить НЕМЕДЛЕННО? - PullRequest
0 голосов
/ 02 апреля 2019

Я использую ISpVoice, чтобы произнести строку ввода. Теперь, хотя я использую теги SPF_ASYNC и SPF_PURGEBEFORESPEAK в методе Speak, tts не останавливается при вызове Pause, а продолжается до тех пор, пока tts не заканчивает слово.

Вот как я это делаю:

void speakSentence()
{
    pVoice->Pause();
    pVoice->Speak(L"This is a sentence.", SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
    pVoice->Resume();
}

Всякий раз, когда я пытаюсь вызвать эту функцию в середине слова «предложение», tts не делает паузу и вместо этого продолжает произносить слово до конца.

Из документации Microsoft:

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

Я попытался изменить границу предупреждения:

pVoice->SetAlertBoundary(SPEI_PHONEME);

и это не работает.

Существует NVDA Screen Reader, который решил эту проблему, но я не знаю, как они это сделали.

Есть ли способ решить мою проблему?

EDIT: Вот мой полный код. Я создаю небольшую программу для чтения с экрана, которая использует UIAutomation и MSAA. Программа может работать нестабильно при сравнении объектов пользовательского интерфейса, но в большинстве случаев она работает.

screeenreader.h:

#ifndef _SCREENREADER_H_
#define _SCREENREADER_H_

#define WIN32_LEAN_AND_MEAN
#ifndef UNICODE
#define UNICODE
#endif

#include <windows.h>

#include <memory>

#include "speechsynthesis.h"
#include "uiautomator.h"

class ScreenReader
{
public:
    explicit ScreenReader(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdShow);
    virtual ~ScreenReader();

    LRESULT CALLBACK MessageHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    int Exec();
private:
    void InitializeWindows();
    void InitRawInputDevices();
    bool IsMouseMove();
private:
    LPCWSTR m_applicationName;
    HINSTANCE m_hInstance;
    HINSTANCE m_hPrevInstance;
    PSTR m_pScmdline;
    int m_iCmdShow;

    HWND m_hWnd;

    SpeechSynthesis *m_pSpeech;
    UIAutomator *m_pAutomator;

    RAWINPUTDEVICE rid[2];

    LONG m_prevMouseX;
    LONG m_prevMouseY;

    BSTR currItem;
};

static ScreenReader *application;

static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

#endif

screenreader.cpp: В этой части я вызвал ISpVoice в разделе сообщений. При ScreenReader::MessageHandler() функции при IsMouseMove условии.

#include "screenreader.h"



ScreenReader::ScreenReader(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdShow)
{
    CoInitialize(NULL);
    m_pSpeech = new SpeechSynthesis;
    m_pAutomator = new UIAutomator;

    // Get current Cursor position.
    POINT pt;
    GetCursorPos(&pt);
    m_prevMouseX = pt.x;
    m_prevMouseY = pt.y;

    // Notify user the program is loading.
    m_pSpeech->Speak(L"Loading Rescan. Please wait.", SPF_DEFAULT, NULL);
    m_hInstance = hInstance;
    m_hPrevInstance = hPrevInstance;
    m_pScmdline = pScmdline;
    m_iCmdShow = iCmdShow;

    application = this;
    InitializeWindows();
    InitRawInputDevices();
}


ScreenReader::~ScreenReader()
{
    if (m_pSpeech != nullptr)
    {
        delete m_pSpeech;
        m_pSpeech = nullptr;
    }
    if (m_pAutomator != nullptr)
    {
        delete m_pAutomator;
        m_pAutomator = nullptr;
    }
    if (currItem != NULL)
    {
        SysFreeString(currItem);
        currItem = NULL;
    }
    CoUninitialize();
}

LRESULT CALLBACK ScreenReader::MessageHandler(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_INPUT:
    {
        UINT dwSize;
        GetRawInputData(
            (HRAWINPUT)lParam,
            RID_INPUT,
            NULL,
            &dwSize,
            sizeof(RAWINPUTHEADER)
        );
        std::unique_ptr<BYTE[]> lpb(new BYTE[dwSize]);
        if (!lpb)
            return 0;
        if (GetRawInputData(
            (HRAWINPUT)lParam,
            RID_INPUT,
            lpb.get(),
            &dwSize,
            sizeof(RAWINPUTHEADER)
        ) != dwSize)
            OutputDebugString(L"GetRawInputData does not return correct size!\n");

        RAWINPUT *raw = (RAWINPUT*)lpb.get();
        if (raw->header.dwType == RIM_TYPEKEYBOARD)
        {
            UINT mess = raw->data.keyboard.Message;
            UINT vKey = raw->data.keyboard.VKey;
            if (mess == WM_KEYDOWN)
            {
            }
        }
        else if (raw->header.dwType == RIM_TYPEMOUSE)
        {
            if (IsMouseMove())
            {
                BSTR item;
                HRESULT hr = m_pAutomator->GetUIAutomationItemNameAtMousePoint(&item);
                if (item == NULL)
                    return 0;
                if (currItem == NULL)
                    currItem = SysAllocString(item);
                if (wcscmp(currItem, item) != 0)
                {
                    m_pSpeech->Stop();
                    m_pSpeech->Speak(item);
                    if (currItem != NULL)
                        SysFreeString(currItem);
                    currItem = SysAllocString(item);
                }
                SysFreeString(item);
            }
        }
    }
        return 0;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
}

int ScreenReader::Exec()
{
    MSG msg;

    ShowWindow(m_hWnd, m_iCmdShow);

    // Tell the user that the program is ready.
    m_pSpeech->Speak(L"Rescan ready.", SPF_PURGEBEFORESPEAK);

    // The message loop
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

void ScreenReader::InitializeWindows()
{
    // Create Window class.
    WNDCLASSEX wc;

    m_applicationName = L"Rescan Screen Reader";

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = m_hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = m_applicationName;
    wc.hIconSm = wc.hIcon;

    // Register the window class.
    RegisterClassEx(&wc);

    m_hWnd = CreateWindowEx(
        WS_EX_OVERLAPPEDWINDOW,
        m_applicationName,
        L"Rescan Screen Reader",
        WS_CAPTION | WS_MINIMIZEBOX | WS_OVERLAPPED | WS_SYSMENU,
        (GetSystemMetrics(SM_CXSCREEN) - 500) / 2,
        (GetSystemMetrics(SM_CYSCREEN) - 300) / 2,
        500,
        300,
        NULL,
        NULL,
        m_hInstance,
        NULL
    );
}

void ScreenReader::InitRawInputDevices()
{
    // Initialize Keyboard
    rid[0].usUsagePage = 0x01;
    rid[0].usUsage = 0x06;
    rid[0].dwFlags = RIDEV_INPUTSINK;
    rid[0].hwndTarget = m_hWnd;
    // Initialize Mouse
    rid[1].usUsagePage = 0x01;
    rid[1].usUsage = 0x02;
    rid[1].dwFlags = RIDEV_INPUTSINK;
    rid[1].hwndTarget = m_hWnd;

    // Register RIDs
    RegisterRawInputDevices(rid, 2, sizeof(RAWINPUTDEVICE));
}

bool ScreenReader::IsMouseMove()
{
    POINT pt;
    GetCursorPos(&pt);
    bool result = !(m_prevMouseX == pt.x && m_prevMouseY == pt.y);
    m_prevMouseX = pt.x;
    m_prevMouseY = pt.y;
    return result;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_QUIT:
        PostQuitMessage(0);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return application->MessageHandler(hWnd, message, wParam, lParam);
    }
}

Я обернул ISpVoice в класс SpeechSynthesis.

speechsynthesis.h:

#ifndef _SPEECHSYNTHESIS_H_
#define _SPEECHSYNTHESIS_H_

#pragma warning(disable :  4996)

#define SPCAT_VOICES_ONECORE L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech_OneCore\\Voices"

#include <sapi.h>
#include <sphelper.h>
#include <atlbase.h>

class SpeechSynthesis
{
public:
    SpeechSynthesis();
    ~SpeechSynthesis();

    HRESULT Speak(LPCWSTR pwcs, DWORD dwFlags = SPF_PURGEBEFORESPEAK | SPF_ASYNC | SPF_IS_NOT_XML, ULONG *pulStreamNumber = NULL);
    HRESULT Resume();
    HRESULT Pause();
    HRESULT Stop();

    ISpVoice* getVoice();

private:
    CComPtr<ISpObjectToken> cpVoiceToken;
    CComPtr<IEnumSpObjectTokens> cpEnum;
    ISpVoice* pVoice;
    ULONG count;
};

#endif

speechsynthesis.cpp:

#include "speechsynthesis.h"



SpeechSynthesis::SpeechSynthesis()
{
    HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
    if (SUCCEEDED(hr))
        hr = SpEnumTokens(SPCAT_VOICES_ONECORE, NULL, NULL, &cpEnum);
    if (SUCCEEDED(hr))
        hr = cpEnum->GetCount(&count);
    if (SUCCEEDED(hr))
    {
        cpEnum->Item(1, &cpVoiceToken);
        pVoice->SetPriority(SPVPRIORITY::SPVPRI_ALERT);
        pVoice->SetAlertBoundary(SPEI_PHONEME);
        pVoice->SetOutput(NULL, TRUE);
        pVoice->SetVoice(cpVoiceToken);
    }
    if (FAILED(hr))
    {
        MessageBox(NULL, "A fatal error has occured", "Error Message", MB_ABORTRETRYIGNORE);
    }
}


SpeechSynthesis::~SpeechSynthesis()
{
    pVoice->Release();
}

HRESULT SpeechSynthesis::Speak(LPCWSTR pwcs, DWORD dwFlags, ULONG *pulStreamNumber)
{
    return pVoice->Speak(pwcs, dwFlags, pulStreamNumber);
}

HRESULT SpeechSynthesis::Resume()
{
    return pVoice->Resume();
}

HRESULT SpeechSynthesis::Pause()
{
    return pVoice->Pause();
}

HRESULT SpeechSynthesis::Stop()
{
    return Speak(NULL);
}

ISpVoice * SpeechSynthesis::getVoice()
{
    return pVoice;
}

uiautomator.h

#ifndef _UIAUTOMATOR_H_
#define _UIAUTOMATOR_H_

#include <windows.h>
#include <oleacc.h>
#include <uiautomation.h>

#pragma comment(lib, "oleacc.lib")

class UIAutomator
{
public:
    UIAutomator();
    ~UIAutomator();

    HRESULT GetItemNameAtMousePoint(BSTR *pStr);
    HRESULT GetUIAutomationItemNameAtMousePoint(BSTR *pStr);
private:
    HRESULT InitUIAutomation();

private:
    IUIAutomation *m_automation;
};

#endif

uiautomator.cpp

#include "uiautomator.h"



UIAutomator::UIAutomator()
{
    SetProcessDPIAware();
    HRESULT hr = InitUIAutomation();
}


UIAutomator::~UIAutomator()
{
}

HRESULT UIAutomator::GetItemNameAtMousePoint(BSTR * pStr)
{
    POINT pt;
    GetPhysicalCursorPos(&pt);
    VARIANT varItem;
    IAccessible *pAcc;
    HRESULT hr = AccessibleObjectFromPoint(pt, &pAcc, &varItem);
    if (SUCCEEDED(hr))
    {
        hr = pAcc->get_accName(varItem, pStr);
        VariantClear(&varItem);
        pAcc->Release();
    }
    return hr;
}

HRESULT UIAutomator::GetUIAutomationItemNameAtMousePoint(BSTR * pStr)
{
    CONTROLTYPEID id;
    POINT pt;
    IUIAutomationElement *elem;
    VARIANT val;
    GetCursorPos(&pt);
    HRESULT hr = m_automation->ElementFromPoint(pt, &elem);
    if (SUCCEEDED(hr))
    {
        hr = elem->get_CurrentControlType(&id);
        if (SUCCEEDED(hr))
        {
            if (id == UIA_PaneControlTypeId)
                GetItemNameAtMousePoint(pStr);
            else if (id == UIA_EditControlTypeId)
            {
                hr = elem->GetCurrentPropertyValue(UIA_ValueValuePropertyId, &val);
                if (SUCCEEDED(hr))
                {
                    *pStr = SysAllocString(val.bstrVal);
                    VariantClear(&val);
                }
            }
            else
            {
                hr = elem->get_CurrentName(pStr);
            }
        }
        elem->Release();
    }
    return hr;
}

HRESULT UIAutomator::InitUIAutomation()
{
    HRESULT hr = CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER,
        __uuidof(IUIAutomation), (void**)&m_automation);
    return hr;
}

main.cpp

#include "vld.h"
#include "screenreader.h"
#include <memory>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdShow)
{
    std::unique_ptr<ScreenReader> app(new ScreenReader(hInstance, hPrevInstance, pScmdline, iCmdShow));
    return app->Exec();
}

Если у вас нет времени на компиляцию, вот программа.

Если вы запустите его и наведите указатель мыши на окно программы, при выделении кнопок свертывания и закрытия появится задержка. Также иногда tts не останавливается сразу, когда вы наводите курсор на другой объект.

Сравните это с NVDA Screen Reader. Вы заметите большую разницу.

Ответы [ 2 ]

0 голосов
/ 04 апреля 2019

Я наконец получил это!

CComPtr<ISpAudio> audio;
CSpStreamFormat format;
format.AssignFormat(SPSF_11kHz8BitMono);

Инициализация аудио

SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOOUT, &audio);

Затем установите его формат и установите в качестве вывода значение pVoice.

audio->SetFormat(format.FormatId(), format.WaveFormatExPtr());
pVoice->SetOutput(audio, FALSE);

Теперь у меня есть доступ к аудиопотоку!

Теперь, чтобы немедленно остановить звук, позвоните:

audio->SetState(SPAS_STOP, 0);

Затем говорите снова, используя:

audio->SetState(SPAS_RUN, 0);
pVoice->Speak(L"This is a sentence", SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
0 голосов
/ 03 апреля 2019

У меня работает с настройкой или без SetAlertBoundary(SPEI_PHONEME).

Ниже приведен мой тестовый код, вы можете попробовать.

HRESULT hr = ::CoInitialize(nullptr);
if (FAILED(hr))
{
    return EXIT_FAILURE;
}
std::wstring text;

text = L"This is a sentence.";

CComPtr<ISpVoice> cpVoice;
// Create a SAPI voice
hr = cpVoice.CoCreateInstance(CLSID_SpVoice);
//cpVoice->SetAlertBoundary(SPEI_PHONEME);
// set the output to the default audio device
if (SUCCEEDED(hr))
{
    hr = cpVoice->SetOutput(NULL, TRUE);
}
// Speak the text
if (SUCCEEDED(hr))
{
    hr = cpVoice->Speak(text.c_str(), SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
}

text = L"The third type, a logarithm of the unsigned fold change, is undoubtedly the most tractable.";

Sleep(600);

hr = cpVoice->Pause();      
hr = cpVoice->Resume();
hr = cpVoice->Speak(text.c_str(), SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);

Sleep(10000);

::CoUninitialize();
if (SUCCEEDED(hr))
{
    return EXIT_SUCCESS;
}
return EXIT_FAILURE;
...