Базы данныхИнтернетКомпьютерыОперационные системыПрограммированиеСетиСвязьРазное
Поиск по сайту:
Подпишись на рассылку:

Назад в раздел

Говорящая Java!

div.main {margin-left: 20pt; margin-right: 20pt} Говорящая Java!

Добавьте голосовые возможности в ваши апплеты и приложения на Java 1.3  Содержание
В этой статье Тони Лотон (Tony Loton) показывает как реализовать простой голосовой движок используя менее 150 строк кода на Java, без дополнительного оборудования и без вывода нативных библиотек. Далее он предоставляет небольшой zip файл, содержащий все необходимое для того чтобы заставить ваше Java приложение говорить -- просто забавы ради или для более серьезных задач. И, если вы впервые сталкиваетесь с Java Sound API, эта статья может послужить неплохим введением. (1,800 слов в англ. оригинале) Автор Tony Loton

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

Недавно я работал над технологией сбора HTML и XML информации из Web [см. "Доступ к крупнейшей в мире базе данных при помощи связанных баз данных в Web  (Access the World's Biggest Database with Web DataBase Connectivity)" (JavaWorld, Март 2001)]. Так получилось что я смог объединить эти две идеи воедино и создать говорящий Web броузер. Такой броузер может быть полезен для прослушивания различных фрагментов информации с ваших любимых сайтов -- например, заголовков новостей. Это все равно что слушать радио выгуливая собаку или по пути к месту работы. Конечно, сегодня для этого вам придется иметь при себе портативный компьютер с подключенным мобильным телефоном, но в ближайшем будущем эта ситуация может измениться с появлением поддерживающих Java "интеллектуальных" телефонов, таких как Nokia 9210 (9290 в США).

Что касается текущих перспектив, то удачным применением может стать использования для чтения электронной почты, что также возможно благодаря JavaMail API. Это приложение может периодически проверять ваш почтовый ящик и время от времени заставляет вас вздрогнуть от неожиданности, когда голос из ниоткуда произносит: "Пришла новая почта. Хотите чтобы я прочитал ее вам?" Или, продолжая развивать эту тему, его можно подключить к вашему ежедневнику, чтобы он выкрикивал: "Не забудь о встрече с боссом через 10 минут!"

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

Тестирование речевого движка
Для использования речевого движка вам потребуется включить файл jw-0817-javatalk.zip в ваш CLASSPATH и запустить com.lotontech.speech.Talker класс из командной строки или из  Java программы.

Для запуска из командной строки наберите:


java com.lotontech.speech.Talker "h|e|l|oo"

Для запуска из программы Java, просто включите две строки кода:


com.lotontech.speech.Talker talker=new com.lotontech.speech.Talker();
talker.sayPhoneWord("h|e|l|oo");

На этом месте у вас возможно возникли вопросы по поводу формата строки "h|e|l|oo", передаваемую через командную строку или передаваемую в качестве параметра методу sayPhoneWord(...). Позвольте мне объяснить.

Речевой движок работает путем объединения коротких звуковых фрагментов, составляющих человеческую (в данном случае английскую) речь. Эти звуковые фрагменты, называемые allophones (аллофоны), имеют одно-, двух- или трехбуквенное обозначения. Некоторые идентификаторы очевидны, а некоторые не очень, как вы и могли убедиться на примере фонетического представления слова "hello".

h -- звучит так как вы и ожидали

e -- звучит так как вы и ожидали

l -- звучит так как вы и ожидали, но обратите внимание на то что я сократил двойное "l" до одиночного

oo -- подходит для слова "hello," но не для слов "bot" и "too"

Вот перечень допустимых allophones:

a -- как в слове cat

b -- как в слове cab

c -- как в слове cat

d -- как в слове dot

e -- как в слове bet

f -- как в слове frog

g -- как в слове frog

h -- как в слове hog

i -- как в слове pig

j -- как в слове jig

k -- как в слове keg

l -- как в слове leg

m -- как в слове met

n -- как в слове begin

o -- как в слове not

p -- как в слове pot

r -- как в слове rot

s -- как в слове sat

t -- как в слове sat

u -- как в слове put

v -- как в слове have

w -- как в слове wet

y -- как в слове yet

z -- как в слове  zoo

aa -- как в слове fake

ay -- как в слове hay

ee -- как в слове bee

ii -- как в слове high

oo -- как в слове go

bb -- вариация b с другим акцентом

dd -- вариация d с другим акцентом

ggg -- вариация g с другим акцентом

hh -- вариация h с другим акцентом

ll -- вариация l с другим акцентом

nn -- вариация n с другим акцентом

rr -- вариация r с другим акцентом

tt -- вариация t с другим акцентом

yy -- вариация y с другим акцентом

ar -- как в слове car

aer -- как в слове care

ch -- как в слове which

ck -- как в слове check

ear -- как в слове beer

er -- как в слове later

err -- как в слове later (более протяжно)

ng -- как в слове feeding

or -- как в слове law

ou -- как в слове zoo

ouu -- как в слове zoo (более протяжно)

ow -- как в слове cow

oy -- как в слове boy

sh -- как в слове shut

th -- как в слове thing

dth -- как в слове this

uh -- вариация произношения u

wh -- как в слове where

zh -- как в слове Asian

В человеческой речи тональность слов повышается и понижается на протяжении всего произносимого предложения. Эта интонация позволяет речи звучать более натурально, более эмоционально, и дает возможность отличать вопрос от утверждения. Если вы когда-нибудь слышали синтетический голос Стивена Хокингса (Stephen Hawking's), тогда вы понимаете о чем я говорю. Рассмотрим следующие два предложения:

It is fake -- f|aa|k

Is it fake? -- f|AA|k

Как вы могли догадаться, для повышения интонации используются заглавные буквы. Все что от вас требуется - немного поэкспериментировать и ознакомиться с моими рекомендациями по имитации протяжных гласных звуков.

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

Реализация речевого движка
Для речевого движка требуется всего лишь один класс с четырьмя методами. Он использует Java Sound API, включенный в состав J2SE 1.3. Я не буду приводить здесь руководство по использованию Java Sound API, но вы сможете научиться всему необходимому рассмотрев приведенный ниже пример. Вы увидите что в этом нет ничего сложного, а комментарии предоставят вам всю необходимую информацию.

Так выглядит описание класса Talker:


package com.lotontech.speech;

import javax.sound.sampled.*;
import java.io.*;
import java.util.*;
import java.net.*;

public class Talker
{
  private SourceDataLine line=null;
}

Если вы запускаете Talker из командной строки, то входной точкой программы является приведенный ниже метод main(...). Он передает первый параметр командной строки (если он есть) методу sayPhoneWord(...):


/*
* Этот метод проговаривает фонетическое слово, указанное в командной строке.
*/
public static void main(String args[])
{
  Talker player=new Talker();
  if (args.length>0) player.sayPhoneWord(args[0]);
  System.exit(0);
}

Метод sayPhoneWord(...) вызывается в main(...), рассмотренном выше или может быть вызван непосредственно из вашего Java приложения или поддерживающего plug-in апплета. Это выглядит сложнее чем есть на самом деле. По сути дела он обрабатывает allophones, разделенные символами "|" в вводимом тексте, и проигрывает  их одно за другим через канал вывода звука. Для того чтобы звучание было более естественным, я объединяю конец каждого звукового фрагмента с началом следующего:


/*
* Этот метод проговаривает заданное фонетическое слово.
*/
public void sayPhoneWord(String word)
{
  // -- Создание фиктивного массива байтов для предыдущего звука --
  byte[] previousSound=null;

  // -- Разбиение строки ввода на отдельные allophones --
  StringTokenizer st=new StringTokenizer(word,"|",false);
  while (st.hasMoreTokens())
  {
    // -- Формирует имя файла для allophone --
    String thisPhoneFile=st.nextToken();
    thisPhoneFile="/allophones/"+thisPhoneFile+".au";

    // -- Получение данных из файла --
    byte[] thisSound=getSound(thisPhoneFile);

    if (previousSound!=null)
    {
      // -- Объединение предыдущего allophone с текущим (если это возможно) --
      int mergeCount=0;
      if (previousSound.length>=500 && thisSound.length>=500)
        mergeCount=500;
      for (int i=0; i<mergeCount;i++)
      {
        previousSound[previousSound.length-mergeCount+i]
         =(byte)((previousSound[previousSound.length
         -mergeCount+i]+thisSound[i])/2);
      }

      // -- Воспроизведение предыдущего allophone --
      playSound(previousSound);

      // -- Определяет текущий усеченный allophone как предыдущий --
      byte[] newSound=new byte[thisSound.length-mergeCount];
      for (int ii=0; ii<newSound.length; ii++)
        newSound[ii]=thisSound[ii+mergeCount];
      previousSound=newSound;
    }
    else
      previousSound=thisSound;
  }

  // -- Проигрывает последний звук и освобождает звуковой канал --
  playSound(previousSound);
  drain();
}

Как видите, в конце метода sayPhoneWord() осуществляется вызов метода playSound(...) для воспроизведения отдельного звукового фрагмента (allophone), а затем следует вызов метода drain(...) для очистки звукового канала. Вот код метода playSound(...):


/*
* Этот метод воспроизводит звуковой фрагмент.
*/
private void playSound(byte[] data)
{
  if (data.length>0) line.write(data, 0, data.length);
}

И для метода drain(...):


/*
* Этот метод очищает звуковой канал.
*/
private void drain()
{
  if (line!=null) line.drain();
  try {Thread.sleep(100);} catch (Exception e) {}
}

Теперь, если вы вернетесь к методу sayPhoneWord(...), вы увидите что остался один метод, который мы еще не рассмотрели: getSound(...).

getSound(...) считывает ранее записанный звуковой фрагмент как битовые данные из au файла. Когда я говорю "файл", я имею ввиду ресурс, представленный в файле supplied.zip. Я обращаю на это внимание, поскольку доступ к JAR ресурсу (используя метод getResource(...)) отличается от доступа к файлу.

Чтобы разобраться во всех этапах работы метода: чтения данных, преобразования формата звука, формирования линии вывода звука (почему они назвали метод SourceDataLine? я не знаю) и сборки байтов данных, я сопроводил код соответствующими комментариями:


/*
* Этот метод считывает файл с отдельным allophone и
* формирует вектор байтов.
*/
private byte[] getSound(String fileName)
{
  try
  {
    URL url=Talker.class.getResource(fileName);
    AudioInputStream stream = AudioSystem.getAudioInputStream(url);

    AudioFormat format = stream.getFormat();

    // -- Преобразует ALAW/ULAW звук в PCM для проигрывателя --
    if ((format.getEncoding() == AudioFormat.Encoding.ULAW) ||
     (format.getEncoding() == AudioFormat.Encoding.ALAW))
    {
      AudioFormat tmpFormat = new AudioFormat(
       AudioFormat.Encoding.PCM_SIGNED,
       format.getSampleRate(),
       format.getSampleSizeInBits() * 2,
       format.getChannels(),
       format.getFrameSize() * 2,
       format.getFrameRate(),
       true);

      stream = AudioSystem.getAudioInputStream(tmpFormat, stream);
      format = tmpFormat;
    }

    DataLine.Info info = new DataLine.Info(
     Clip.class,
     format,
     ((int) stream.getFrameLength() * format.getFrameSize()));

    if (line==null)
    {
      // -- Строка вывода еще не сформирована --
      // -- Можно найти подходящую строку? --
      DataLine.Info outInfo = new DataLine.Info(SourceDataLine.class,
       format);
      if (!AudioSystem.isLineSupported(outInfo))
      {
        System.out.println("Использование строки " + outInfo + " недопустимо.");
        throw new Exception("Использование строки " + outInfo + " недопустимо.");
      }

      // -- Открыть строку исходных данных (строку вывода) --
      line = (SourceDataLine) AudioSystem.getLine(outInfo);
      line.open(format, 50000);
      line.start();
    }

    // -- Вычисление размера --
    int frameSizeInBytes = format.getFrameSize();
    int bufferLengthInFrames = line.getBufferSize() / 8;
    int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;

    byte[] data=new byte[bufferLengthInBytes];

    // -- Чтение байтов данных и их учет --
    int numBytesRead = 0;
    if ((numBytesRead = stream.read(data)) != -1)
    {
      int numBytesRemaining = numBytesRead;
    }

    // -- Усечение массива байтов до допустимого размера --
    byte[] newData=new byte[numBytesRead];
    for (int i=0; i<numBytesRead;i++)
      newData[i]=data[i];

    return newData;
  }
  catch (Exception e)
  {
    return new byte[0];
  }
}

Вот так. Синтезатор речи содержит около 150 строк кода, включая комментарии. Но это еще не все.

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

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

Вы можете запустить конвертер из командной строки:


java com.lotontech.speech.Converter "hello there"

В результате вы получите что-то вроде:


hello -> h|e|l|oo
there -> dth|aer

Или, запустив его следующим образом:


java com.lotontech.speech.Converter "I like to read JavaWorld"

вы увидите (и услышите):


i -> ii
like -> l|ii|k
to -> t|ouu
read -> r|ee|a|d
java -> j|a|v|a
world -> w|err|l|d

Если вам интересно как он работает, скажу вам что мой подход предельно прост и заключается в применении нескольких правил замены символов, следующих в определенном порядке. Вот несколько таких правил, которые вы можете мысленно проверить на таких словах как "ant," "want," "wanted," "unwanted," и "unique":

Замените "*unique*" на "|y|ou|n|ee|k|"

Замените "*want*" на "|w|o|n|t|"

Замените "*a*" на "|a|"

Замените "*e*" на "|e|"

Замените "*d*" на "|d|"

Замените "*n*" на "|n|"

Замените "*u*" на "|u|"

Замените "*t*" на "|t|"

Слову "unwanted" соответствует следующая последовательность преобразований:


unwanted
un[|w|o|n|t|]ed (правило 2)
[|u|][|n|][|w|o|n|t|][|e|][|d|] (правила 4, 5, 6, 7)
u|n|w|o|n|t|e|d (с удалением лишних символов)

Как вы видите, слова в которых присутствуют буквы wont проговариваются иначе чем слова, содержащие буквы ant. Также обратите внимание на частное правило произношения слова unique, имеющее приоритет над остальными правилами, чтобы оно произносилось как y|ou...а не как u|n....

Призрак из машины обращается к вам
Эта статья представляет готовый к использованию, удобный речевой движок для использования в ваших приложениях на Java 1.3. Если вы взглянете на код, то он также послужит вам полезным введением в JavaSound API для воспроизведения аудиоклипов. Для того чтобы он был действительно полезен вы должны подумать над идеей преобразования текста в речь, поскольку это является фундаментом для всех приложений, связанных с проговариванием слов. В предложенном мною подходе вам придется придумать огромное количество правил замены букв и правильное расположение их по иерархии. Надеюсь что у вас окажется больше выдержки чем у меня!

И, в завершении, вы помните мое упоминание телефона Nokia 9210 во введении. У меня есть такой. Он поддерживает Java и я решил заставить его говорить используя Java. Я также решил реализовать речь вне стандартных апплетов (до выхода Java 2), выполняемых в броузере. Тогда как методика, рассмотренная в этой статье, допустима для этих задач, возникают проблемы с технологией, поскольку она базируется на звуковом движке J2SE 1.3. Требуется другой подход, основанный на использовании простого интерфейса Java AudioClip. Это не так просто как вам могло бы показаться, но я  работаю над этим.

О авторе
Тони Лотон (Tony Loton) работает в собственной компании -- LOTONtech Limited, занимающейся разработкой программных решений, консультациями, обучением и разработкой технической документации. В этом году на него напала литературная чесотка и Тони принял участие в написании книги для John Wiley & Sons и Wrox Press.



  • Главная
  • Новости
  • Новинки
  • Скрипты
  • Форум
  • Ссылки
  • О сайте




  • Emanual.ru – это сайт, посвящённый всем значимым событиям в IT-индустрии: новейшие разработки, уникальные методы и горячие новости! Тонны информации, полезной как для обычных пользователей, так и для самых продвинутых программистов! Интересные обсуждения на актуальные темы и огромная аудитория, которая может быть интересна широкому кругу рекламодателей. У нас вы узнаете всё о компьютерах, базах данных, операционных системах, сетях, инфраструктурах, связях и программированию на популярных языках!
     Copyright © 2001-2024
    Реклама на сайте