Как я могу найти аудиоформат выбранного голоса SpeechSynthesizer

В приложении C # для преобразования текста в речь я использую класс SpeechSynthesizer, у него есть событие с именем SpeakProgress, которое запускается для каждого произнесенного слова. Но для некоторых голосов параметр e.AudioPosition не синхронизируется с выходным аудиопотоком, и выходной волновой файл воспроизводится быстрее, чем показано в этой позиции (см. этот связанный вопрос).

В любом случае, я пытаюсь найти точную информацию о скорости передачи данных и другую информацию, относящуюся к выбранному голосу. Как я понял, если я смогу инициализировать волновой файл с этой информацией, проблема синхронизации будет решена. Однако, если я не могу найти такую ​​информацию в SupportedAudioFormat, я не знаю другого способа найти их. Например, голос "Microsoft David Desktop" не поддерживает формат в VoiceInfo, но кажется, что он поддерживает 16-битный формат PCM 16000 Гц.

Как найти аудиоформат выбранного голоса SpeechSynthesizer

 var formats = CurVoice.VoiceInfo.SupportedAudioFormats;

 if (formats.Count > 0)
 {
     var format = formats[0];
     reader.SetOutputToWaveFile(CurAudioFile, format);
 }
 else
 {
        var format = // How can I find it, if the audio hasn't provided it?           
        reader.SetOutputToWaveFile(CurAudioFile, format );
}

person Ahmad    schedule 08.12.2015    source источник


Ответы (2)


Обновление: этот ответ был изменен после расследования. Первоначально я предполагал по памяти, что SupportedAudioFormats, скорее всего, просто из (возможно, неправильно настроенных) данных реестра; расследование показало, что для меня в Windows 7 это определенно так, и в Windows 8 выполняется тщательное резервное копирование.

Проблемы с поддерживаемыми аудиоформатами

System.Speech является оболочкой почтенного COM-речевого API (SAPI), а некоторые голоса имеют 32-битный и 64-битный формат или могут быть неправильно сконфигурированы (в реестре 64-битной машины HKLM/Software/Microsoft/Speech/Voices против HKLM/Software/Wow6432Node/Microsoft/Speech/Voices.

Я указал ILSpy на System.Speech и его VoiceInfo класс, и я почти убежден, что SupportedAudioFormats исходит исключительно из данных реестра, поэтому при перечислении SupportedAudioFormats можно получить нулевые результаты, если любой из ваших движков TTS не зарегистрирован должным образом для вашего приложения. Целевая платформа (x86, Any или 64 bit), или если поставщик просто не предоставляет эту информацию в реестре.

Голоса могут по-прежнему поддерживать другие, дополнительные или меньшее количество форматов, поскольку это зависит от механизма речи (кода), а не реестра (данных). Так что это может быть выстрел в темноте. Стандартные голоса Windows часто в этом отношении в разы более последовательны, чем голоса третьих лиц, но они все равно не обязательно обеспечивают SupportedAudioFormats.

Трудный путь поиска этой информации

Я обнаружил, что все еще можно получить текущий формат текущего голоса, но это зависит от отражения для доступа к внутренним компонентам оболочек SAPI System.Speech.

Следовательно, это довольно хрупкий код! И я бы не рекомендовал использовать в продакшене.

Примечание. Приведенный ниже код требует, чтобы вы один раз вызывали Speak () для настройки; для принудительной установки без Speak () потребуется больше вызовов. Однако я могу позвонить Speak(""), чтобы ничего не сказать, и это прекрасно работает.

Выполнение:

[StructLayout(LayoutKind.Sequential)]
struct WAVEFORMATEX
{
    public ushort wFormatTag;
    public ushort nChannels;
    public uint nSamplesPerSec;
    public uint nAvgBytesPerSec;
    public ushort nBlockAlign;
    public ushort wBitsPerSample;
    public ushort cbSize;
}

WAVEFORMATEX GetCurrentWaveFormat(SpeechSynthesizer synthesizer)
{
    var voiceSynthesis = synthesizer.GetType()
                                    .GetProperty("VoiceSynthesizer", BindingFlags.Instance | BindingFlags.NonPublic)
                                    .GetValue(synthesizer, null);

    var ttsVoice = voiceSynthesis.GetType()
                                 .GetMethod("CurrentVoice", BindingFlags.Instance | BindingFlags.NonPublic)
                                 .Invoke(voiceSynthesis, new object[] { false });

    var waveFormat = (byte[])ttsVoice.GetType()
                                     .GetField("_waveFormat", BindingFlags.Instance | BindingFlags.NonPublic)
                                     .GetValue(ttsVoice);

    var pin = GCHandle.Alloc(waveFormat, GCHandleType.Pinned);
    var format = (WAVEFORMATEX)Marshal.PtrToStructure(pin.AddrOfPinnedObject(), typeof(WAVEFORMATEX));
    pin.Free();

    return format;
}

Использование:

SpeechSynthesizer s = new SpeechSynthesizer();
s.Speak("Hello");
var format = GetCurrentWaveFormat(s);
Debug.WriteLine($"{s.Voice.SupportedAudioFormats.Count} formats are claimed as supported.");
Debug.WriteLine($"Actual format: {format.nChannels} channel {format.nSamplesPerSec} Hz {format.wBitsPerSample} audio");

Чтобы проверить это, я переименовал раздел реестра Microsoft Anna AudioFormats в HKLM/Software/Wow6432Node/Microsoft/Speech/Voices/Tokens/MS-Anna-1033-20-Dsk/Attributes, в результате чего SpeechSynthesizer.Voice.SupportedAudioFormats не содержал никаких элементов при запросе. Ниже приведен результат в этой ситуации:

0 formats are claimed as supported.
Actual format: 1 channel 16000 Hz 16 audio
person El Zorko    schedule 17.12.2015
comment
Спасибо, однако, как я заметил, целевой платформой уже является x86. - person Ahmad; 17.12.2015
comment
@ Ахмад Интересно. Какое значение у вас есть для HKEY_LOCAL_MACHINE / Software / Microsoft / Speech / Voices / Tokens / (выбранный речевой движок) / Attributes / AudioFormats? На этом ПК (Win7 для Microsoft Anna) по умолчанию используется строка 18 REG_SZ. Если я переименую ключ AudioFormats, я не получу форматов при перечислении. Похоже на битовую маску (хотя и хранящуюся как REG_SZ), поскольку я могу настраивать различные биты, но некоторые комбинации недопустимы. Точно так же в HKLM / Software / Wow6432Node / Microsoft / Speech / Voices / и т. Д. Отличаются ли они? Интересно, проблема ли это установщика / реестра / голоса, не похоже на проблему с API. - person El Zorko; 18.12.2015
comment
Атрибут AudioFormats там отсутствует. Похоже, в Window 8.1 такого атрибута нет. - person Ahmad; 18.12.2015
comment
@ Ахмад Хм. Что ж, должно быть возможно извлечь это через отражение. Я обновлю свой ответ. - person El Zorko; 18.12.2015
comment
Спасибо большое, работает! Затем вы извлекаете формат после начала speak ?! - person Ahmad; 18.12.2015
comment
@Ahmad По сути, да, но дело не в этом - SpeechSynthesizer более убедительно, что он должен настроить движок TTS и подготовиться к выходу волны. Как только это будет сделано, формат, который он использовал для этого, кэшируется в _waveFormat. Немного поработав с ILSpy, можно заставить синтезатор делать это без вызова Speak (). Но в любом случае я подозреваю, что можно просто вызвать Speak () один раз, запросить формат и затем использовать, пока вы не измените движки или другие параметры. - person El Zorko; 18.12.2015
comment
До этого решения у меня был трюк, в котором я вызываю speak("please wait") один раз после SetOutputToDefaultAudioDevice, а затем после SetOutputToNull. Затем в обоих случаях я записываю параметр e.AudioPoision события SpeakProgress в переменные и, разделив их, вычисляю соотношение. Умножая его на 22050, я пытаюсь найти реальную частоту дискретизации. Какое из этих решений (ваш ответ и мой трюк) вы считаете более надежным в приложении? - person Ahmad; 18.12.2015
comment
Кстати, есть ли способ проверить, является ли вывод GetCurrentWaveFormat допустимым форматом? - person Ahmad; 18.12.2015
comment
@Ahmad Все только мое мнение, но я бы сказал, что это трудно назвать вашим первым вопросом; ваша эвристика не полагается на внутренние компоненты, что является большим плюсом, но отражение даст вам фактические значения, а не оценку. Будущие изменения .NET Framework могут сломать любое решение, основанное на отражении. Что касается достоверности формата волны, если Speak () завершится успешно, я был бы удивлен, если бы полученный формат был недействительным (в том смысле, что не отражал фактическое состояние выполнения), но для этого потребовался бы более обширный анализ Код SDK через ILSpy, чтобы прийти к окончательному ответу в любом случае. - person El Zorko; 18.12.2015
comment
На самом деле, я не имею в виду достоверность, я пытаюсь смешать ваше решение с моей эвристикой, я хочу знать, если GetCurrentWaveFormat как-то не удалось, тогда я переключаюсь на свою эвристику. есть способ проверить это? - person Ahmad; 18.12.2015
comment
Ах я вижу. В этом случае я мог бы обернуть отражающий код в try / catch, так как он, вероятно, сработает, если внутренние изменения изменятся, и будет, если Speak () не будет вызван. Я также мог бы утверждать / проверять, имеет ли формат разумно выглядящие структурные значения. Итак, wFormatTag = WAVE_FORMAT_PCM (1), что nSamplesPerSec имеет размер не менее 4K и не безумен, что wBitsPerSample равно 8 для безумия и тому подобное. Но я бы предпочел положиться на честное тестирование общих пользовательских конфигураций, движков и версий ОС, а не пытаться охватить все основы. - person El Zorko; 19.12.2015

Вы не можете получить эту информацию из кода. Вы можете только слушать все форматы (от плохого формата, например, 8 кГц до высококачественного, например, 48 кГц), и наблюдать, где он перестает улучшаться, что, я думаю, вы и сделали.

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

Для дополнительной информации:

Допустим, вы голосовая компания. Вы записали свой компьютерный голос с частотой 16 кГц, 16 бит, моно.

Пользователь может позволить вашему голосу говорить на 48 кГц, 32 бит, стерео. Речевой движок выполняет это преобразование. Речевому движку все равно, действительно ли он звучит лучше, он просто выполняет преобразование формата.

Допустим, пользователь хочет, чтобы ваш голос что-то говорил. Он просит сохранить файл как 48 кГц, 16 бит, стерео.

SAPI / System.Speech вызывает ваш голос с помощью этого метода:

STDMETHODIMP SpeechEngine::GetOutputFormat(const GUID * pTargetFormatId, const WAVEFORMATEX * pTargetWaveFormatEx,
GUID * pDesiredFormatId, WAVEFORMATEX ** ppCoMemDesiredWaveFormatEx)
{
    HRESULT hr = S_OK;

    //Here we need to return which format our audio data will be that we pass to the speech engine.
    //Our format (16 kHz, 16 bit, mono) will be converted to the format that the user requested. This will be done by the SAPI engine.

    enum SPSTREAMFORMAT sample_rate_at_which_this_voice_was_recorded = SPSF_16kHz16BitMono; //Here you tell the speech engine which format the data has that you will pass back. This way the engine knows if it should upsample you voice data or downsample to match the format that the user requested.

    hr = SpConvertStreamFormatEnum(sample_rate_at_which_this_voice_was_recorded, pDesiredFormatId, ppCoMemDesiredWaveFormatEx);

    return hr;
}

Это единственное место, где нужно «раскрыть», в каком формате записан ваш голос.

Все «Доступные форматы» скорее сообщают вам, какие преобразования может выполнять ваша звуковая карта / Windows.

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

person tmighty    schedule 03.04.2019