Pregunta

I'm messing around with SAPI on Windows, and I've noticed that the audio quality of voices is quite underwhelming. By comparing the audio quality of a simple test program and various qualities that eSpeak provides, I've concluded that the default quality is somewhere around 16kHz 16 Bit Mono.

Simple SAPI test program

#include <string>
#include <iostream>

#include <Windows.h>
#include <sapi.h>


#define _CHECK_HR(hr, debug_str)   \
    if(FAILED(hr)) {    \
        std::cout << debug_str << ": " << std::hex << "0x" << hr << std::dec << std::endl;   \
        goto check_failure;   \
    }

#define CHECK_HR(expr, debug_str)  \
    _CHECK_HR(expr, debug_str);

#define SAFE_RELEASE(obj)   \
    if(obj != NULL) {   \
        obj->Release(); \
        obj = NULL; \
    }


int main()
{
    ISpVoice* voice = NULL;

    CHECK_HR(CoInitialize(NULL), "CoInitialize");

    CHECK_HR(CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (LPVOID*)&voice), "voice = CoCreateInstance");
    CHECK_HR(voice->Speak(TEXT("This is a simple test."), 0, NULL), "voice->Speak");

    std::cout << "No errors!" << std::endl;

check_failure:
    SAFE_RELEASE(voice);
    CoUninitialize();
}

Naturally, I've tried consulting the SAPI documentation, but haven't found out how to change the format. ISpVoice doesn't have a method which sets the format, but it has a SetOuput method, which takes:

either a stream, audio device, or an object token for an output audio device

My next step was creating an IAudioClient, with the format provided by SpConvertStreamFormatEnum, and setting its IAudioRenderClient as the voice's output. The attempt failed because I couldn't initialize IAudioClient.

Trying to set an audio stream as a voice's output

#include <string>
#include <iostream>

#include <Windows.h>
#include <Mmdeviceapi.h>
#include <Audioclient.h>
#include <audiopolicy.h>
#include <sapi.h>
#include <sphelper.h>


#define _CHECK_HR(hr, debug_str)   \
    if(FAILED(hr)) {    \
        std::cout << debug_str << ": " << std::hex << "0x" << hr << std::dec << std::endl;   \
        goto check_failure;   \
    }

#define CHECK_HR(expr, debug_str)  \
    _CHECK_HR(expr, debug_str);

#define SAFE_RELEASE(obj)   \
    if(obj != NULL) {   \
        obj->Release(); \
        obj = NULL; \
    }

#define SAFE_FREE(obj)   \
    if(obj != NULL) {   \
        CoTaskMemFree(obj); \
        obj = NULL; \
    }


int main()
{
    ISpVoice* voice = NULL;
    IMMDeviceEnumerator* device_enumerator = NULL;
    IMMDevice* audio_device = NULL;
    WAVEFORMATEX *audio_format = NULL;
    GUID format_guid;
    IAudioClient* audio_client = NULL;
    IAudioRenderClient* audio_render_client = NULL;

    CHECK_HR(CoInitialize(NULL), "CoInitialize");

    CHECK_HR(CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (LPVOID*)&voice), "CoCreateInstance");

    CHECK_HR(CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&device_enumerator)), "CoCreateInstance");
    CHECK_HR(device_enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &audio_device), "device_enumerator->GetDefaultAudioEndpoint");

    CHECK_HR(audio_device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, reinterpret_cast<void**>(&audio_client)), "audio_device->Activate");
    CHECK_HR(SpConvertStreamFormatEnum(SPSF_48kHz16BitStereo, &format_guid, &audio_format), "SpConvertStreamFormatEnum");
    CHECK_HR(audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE, 0, 0, audio_format, NULL), "audio_client->Initialize");
    CHECK_HR(audio_client->Start(), "audio_client->Start");
    CHECK_HR(audio_client->GetService(__uuidof(IAudioRenderClient), reinterpret_cast<void**>(&audio_render_client)), "audio_client->GetService");

    CHECK_HR(voice->SetOutput(audio_render_client, FALSE), "voice->SetOutput");
    CHECK_HR(voice->Speak(TEXT("This is a test."), 0, NULL), "voice->Speak");

    std::cout << "No errors!" << std::endl;

check_failure:
    SAFE_RELEASE(device_enumerator);
    SAFE_RELEASE(audio_device);
    SAFE_FREE(audio_format);
    SAFE_RELEASE(audio_client);
    SAFE_RELEASE(audio_render_client);
    CoUninitialize();
}

Besides that, I've poked around SAPI Audio Interfaces, finding a bunch of other interfaces and implementations, none of which seem particularly useful for this task. I feel like I'm running in circles here.

The question: How can I change the audio format of a voice as eSpeak's TTSApp does? TTSApp

¿Fue útil?

Solución

Try:

ATL::CComPtr<ISpVoice> voice;
voice.CoCreateInstance(CLSID_SpVoice);

CSpStreamFormat format;
format.AssignFormat(SPSF_44kHz16BitMono);

ATL::CComPtr<ISpAudio> audio;
SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOOUT, &audio);
audio->SetFormat(format.FormatId(), format.WaveFormatExPtr());

voice->SetOutput(audio, FALSE);

NOTE: This does not include any error handling, so your code will need to check HRESULT return codes and object/pointer validity.

Also note that eSpeak's native output format is 16-bit 22050Hz mono.

For a C version, you will need to handle the COM object lifetime yourself, and look at what CSpStreamFormat is doing in the AssignFormat, FormatId and WaveFormatExPtr methods.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top