让Java说话!

news/2024/11/29 0:46:37/

                                       让Java说话!
            为你的Java 1.3 应用程序和Applet添加说话能力
概要
这篇文章中,Tony Loton展示了不使用硬件和本地调用的,少于150行Java代码实现一个简单的语音引擎。此外,他提供了一个小zip文件,里面包含了使Java应用程序说话说需要的东西—仅仅用来娱乐或别的真正的应用程序。如果你刚刚接触Java Sound API,这篇文章将是一个很好的介绍。(1800字)

作者:Tony Loton
译者:Cocia Lin

     为什么要使你的程序说话呢?首先,为了娱乐,这很适合象游戏这样的娱乐程序。并且还有很多严肃的应用领域。我想这虽然不是可视化界面的天生缺点,也是声音可用之处-- 或者过分一点 – 可以使你的眼睛离开你正在做的事情。

      最近,我曾经应用一些技术在Web上获得HTML和XML信息的工作[请看 "Access the World's Biggest Database with Web DataBase Connectivity" (JavaWorld, March 2001)]。这让我将那个工作和我的这个想法结合来创建一个说话的Web浏览器。这样的一个浏览器可以使你听到你喜欢的网站上的信息摘录 – 新闻标题,例如 – 就象在外边溜狗或开车上班的途中收听收音机一样。当然,以现在的科技水平,你必须带上你的笔记本电脑和移动电话,但这些不切实际的设想在不久的将来,随着应用Java技术的智能电话的出现而变成现实,例如Nokia 9210(在美国叫9290).

    也许对现在来说,能用的到的是一个email朗读器,这也得谢谢JavaMail API.这样的程序将定期的检查你的电子邮箱,并且你的注意被一个声音“你有新的email,你要我给你朗读吗?”吸引。相近的,考虑语音提醒 – 当连接到你的日常管理程序时 –- 电脑大喊“不要忘了10分钟后你和老板的会议!”

    回到这些想法,或者你有更好的自己的想法,我们继续。我将演示怎样将我提供的zip文件添加的我们的工作中,这样,如果你觉得这些东西太难了,你就可以直接安装运行而跳过实现细节。

测试语音引擎
    为了使用这个语音引擎,你需要将jw-0817-javatalk.zip添加到你的classpath,在命令行方式或Java程序中使用com.lotontech.speech.Talker。

命令行方式,象下面这样运行,输入:


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");

这里,你可能想知道命令行方式或sayPhoneWord(..)方法中的字符串格式”h|e|l|oo”的含义。让我来解释。

    语音引擎依靠联结人的最小的语音单位的短声音例子来工作 – 在这里是英语。这些声音例子,叫做音体变位(allophone),是一个,两个,或三个字母标识符的标志。有些标识符是明显的,有些是不明显的,你能从语音学里看到这样的”hello”的表示。

h --发音你能想到
e --发音你能想到
l --发音你能想到,但注意,我将两个 “l” 变为一个”l”
oo -- “hello”的发音,不是”bot”的,也不是”too”的
这里列出了能用到的音体变(allophone):

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 (longer sound)
ng -- 例如 feeding
or -- 例如 law
ou -- 例如 zoo
ouu -- 例如 zoo (longer sound)
ow -- 例如 cow
oy -- 例如 boy
sh -- 例如 shut
th -- 例如 thing
dth -- 例如 this
uh -- 变调 u
wh -- 例如 where
zh -- 例如 Asian
人说话的每一个句子都有单词的升调和降调的变化。这个音调使说话听起来自然,富有感情,并且可以从句子语调确定这是疑问句。如果你听过Stephen Hawking的人造声音,你就能够理解我所说的了。考虑这两个句子:

It is fake -- f|aa|k
Is it fake? -- f|AA|k
你也许猜想,使用升调的方法是用大写字母。你要实际感受一下,我的提示是你要注意听元音字母

  这是你使用这个软件需要知道的全部了,但是如果你对引擎罩下面的东西感兴趣,那么继续往下读。

实现语音引擎

    语音引擎仅仅需要一个类来实现,包含四个方法。它使用J2SE1.3的Java Sound API。我不想提供一个全面的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;
}

如果你从命令行运行程序,下面的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程序或Applet直接调用。它看起来比它本身难理解。本质上,它简单的一步一步解释单词的语音变位allophone – 被”|”标志分割的输入文本 – 在把他们一个一个通过声音输出通道输出。为了让它听起来更自然,合并每一个声音的结尾到下一个声音的开头:

/*
*这个方法使输入的单词发音
*/
public void sayPhoneWord(String word)
{
// 为上一个声音设置一个字节数组
  byte[] previousSound=null;

//分割输入字符串
  StringTokenizer st=new StringTokenizer(word,"|",false);
  while (st.hasMoreTokens())
  {
 

为语音单位构造一个文件名
    String thisPhoneFile=st.nextToken();
    thisPhoneFile="/allophones/"+thisPhoneFile+".au";


     从文件中获得数据
    byte[] thisSound=getSound(thisPhoneFile);

    if (previousSound!=null)
    {
 

   合并上一个语音和现在的这个
      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);
      }

 


     播放前一个音符
      playSound(previousSound);

 


     切割当前的音符作为前一个音符
      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(..)来输出单独的声音例子,并且调用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文件中读出声音的字节数据。当我说文件时,指的是我提供的zip文件里的资源。我强调这点差别,因为你得到JAR资源控制 – 使用getResource(..)方法 – 这不同于得到一个普通文件的控制权。

为了有一个语音一个语音的读出数据,转换到声音格式,实例化一个声音输出行(为什么他们叫它SourceDateLine,我不知道),组合这些字节数据,我在下面代码中提供给你说明:


/*
*这个方法从文件中读出单独的语音并且构造一个字节矢量
*/
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)
    {
      // -- Output line not instantiated yet –
      // -- Can we find a suitable kind of line? --
      DataLine.Info outInfo = new DataLine.Info(SourceDataLine.class,
       format);
      if (!AudioSystem.isLineSupported(outInfo))
      {
        System.out.println("Line matching " + outInfo + " not supported.");
        throw new Exception("Line matching " + outInfo + " not supported.");
      }

     

打开资源数据行(输出行output line)
      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行的语音合成器代码,包括说明。但这没有完全结束。

文本到语音(Text-to-speech)的转换
用语音学方法表示单词可能太乏味,所以,如果你想创建一个象我介绍一样的应用程序,你要提供原始文本。

研究过这个题目后,我在zip文件中提供一个实验性的文本到语音的转换类。当你运行它后,将输出给你你想要的语音表示。

在命令行模式,运行text-to-speech转换器:


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 (rule 2)
[|u|][|n|][|w|o|n|t|][|e|][|d|] (rules 4, 5, 6, 7)
u|n|w|o|n|t|e|d (with surplus characters removed)

你应该看到,包含ant的want被用几种不同的方式朗读。你也应该看到对于unique来说的特殊情况,应该被读为y|ou..而不是u|n….

电脑里的精灵,对你说话
这篇文章提供一个可以使用Java 1.3运行的简便的语音引擎。如果你研究这些代码,你可以得到一些关于JavaSound API播放音频片断的有用方法。要想使这个引擎真的能用,你要思考文本到语音的转换方法,这真的是我的一个主要想法。在这个引擎中,你要想出大量的文本转换规则,还要应用一些好的优先顺序。我希望你有比我强的毅力。

最后,你可能还记得我说过的Nokia 9210。我有一部,它支持Java,我决定用Java使它说话。我也想使applet(Java2的以前版本)在浏览器中说话。这些技术依靠J2SE 1.3声音引擎,现在是可用的。一个不同的方法是需要的,依靠简单的Java AudioClip 接口。不像你想象的那样简单,但我在其上工作。

 

 

关于作者
Tony Loton
为他的公司工作 – LOTONTech Limited – 提供软件解决方案,顾问,培训和技术写作服务。写作的小虫好像在这一年里始终叮咬着他,他为John Wiley & Sons 和 Wrox Press出版社写书。

关于译者

Cocia Lin(cocia@163.com)是程序员。它拥有学士学位,现在专攻Java相关技术,刚刚开始在计算机领域折腾。

相关资源

You'll find the speech engine and related source code in the jw-0817-javatalk.zip file:
http://www.javaworld.com/jw-08-2001/javatalk/jw-0817-javatalk.zip
Go to java.sun.com's "Java Sound API" page for documentation, download information, and FAQ:
http://java.sun.com/products/java-media/sound/
To see how the speech engine will combine with Webpages to build my talking browser, read "Access the World's Biggest Database with Web Database Connectivity," Tony Loton (JavaWorld, March 2001):
http://www.javaworld.com/javaworld/jw-03-2001/jw-0316-webdb.html
In "Make an EJB from Any Java Class with Java Reflection" (JavaWorld, December 2000), Tony Loton explains how to turn any Java class into an EJB, and any single-tier Java application into an enterprise application:
http://www.javaworld.com/jw-12-2000/jw-1215-anyclass.html
"Program Multimedia with JMF," Budi Kurniawan (JavaWorld):
Part 1: Go multimedia by learning how the Java Media Framework compares to your stereo system (April 2001)
Part 2: Jump into Java Media Framework's important classes and interfaces (May 2001)
"Add MP3 capabilities to Java Sound with SPI," Dan Becker (JavaWorld, November 2000):
http://www.javaworld.com/jw-11-2000/jw-1103-mp3.html
Sign up for the JavaWorld This Week free weekly email newsletter to learn what's new at JavaWorld:
http://www.idg.net/jw-subscribe
You'll find a wealth of IT-related articles from our sister publications at IDG.net
 


 


http://www.ppmy.cn/news/824478.html

相关文章

让Java说话! (转)

让Java说话&#xff01; (转)[more] 让Java说话&#xff01; 为你的Java 1.3 应用程序和Applet添加说话能力概要这篇文章中&#xff0c;Tony Loton展示了不使用硬件和本地调用的&#xff0c;少于150行Java代码实现一个简单的语音引擎。此外&#xff0c;他提供了一个小zip文件&a…

德语发音快速学

元音[a:] [a] 长元音[a:]发音时口自然张大&#xff0c;舌自然平放&#xff0c;舌尖近下齿龈&#xff0c;舌后部稍微抬起&#xff0c;在口腔前部区域形成发音。 字母出现形式 a 例如 Abend, Name, Tag, da aa 例如 …

日语语法笔记【翻译】

0.0 不知道可不可以在这里保存自己的东西啊。因为比较喜欢SegmentFault的配色。 日语语法笔记 Keith Smillie 内容清单 语序名词代词指示词和疑问词助词动词 介绍尊敬形的现在时和过去时表达『是』、『在』等存在意义『desho』的用法基本形的现在时和过去时动名词 / 『-te』的形…

地道英语的窍门

摘自《英语国际音标闪电入门》 1. 辅音连读略2. 音节划分单词的发音由音节构成&#xff0c;音节的划分通常以元音为依据&#xff08;一个单词有几个元音就有几个音节&#xff09;元音与元音之间辅音的归属是决定一个音节结束和下一个音节开始的重要点1.元音间只有一个辅音&…

四步练就地道英语发音

1. 准确发音 —— 嘴巴动起来 要想准确发音&#xff0c;请记住两个关键&#xff1a;元音要饱满;辅音要干脆。 元音一定要发饱满、到位&#xff0c;特别是双元音。但是很多童鞋往往会把双元音发成单元音&#xff0c;比如&#xff0c;把/ai/time读成[tem]&#xff0c;/ei/make读成…

IT工程师学会说话

目录 导语学习资料练声练声原则保护嗓子练习方法共鸣发声口腔共鸣鼻腔共鸣胸腔共鸣 《21天掌握当众讲话诀窍》 笔记总述伸手不打笑脸人眼神很重要站如松气沉丹田眉飞色舞&#xff0c;手舞足蹈没有内容&#xff0c;前面的都是花架子 张继娅Mooc 笔记声调上声 语流音变轻声 导语 …

English音标(全)与单词家园

英语音标与单词&#xff0c;无异于中文的拼音与偏旁&#xff0c;是基础中的基础&#xff0c;学习英语的第一步&#xff0c;个人认为十分重要&#xff0c;必须学习牢靠&#xff0c;这些基础影响到你的听说读写的质量&#xff0c;其中英语较中文易学&#xff0c;但是规则比较多。…

【渝粤题库】陕西师范大学200551 英语语音

《语音》作业 I.写出下列单词音标 1.pit 2.beak 3.tap 4.did 5.cab 6.garden 7.cheap 8.judge 9.food 10.vast   11.thank 12.than 13.safe  14.zebra 15.vision 16.batter 17.barter 18.bot 19.bought 20.better 21.Bert 22.bit 23.tap 24.tarp 25.top 26.today 27.Dad 28.d…