登录社区:用户名: 密码: 忘记密码 网页功能:加入收藏 设为首页 网站搜索  

文档

下载

图书

论坛

安全

源码

硬件

游戏
首页 信息 空间 VB VC Delphi Java Flash 补丁 控件 安全 黑客 电子书 笔记本 手机 MP3 杀毒 QQ群 产品库 分类信息 编程网站
  立华软件园 - 安全技术中心 - 技术文档 - JAVA 技术文章 | 相关下载 | 电子图书 | 攻防录像 | 安全网站 | 在线论坛 | QQ群组 | 搜索   
 安全技术技术文档
  · 安全配制
  · 工具介绍
  · 黑客教学
  · 防火墙
  · 漏洞分析
  · 破解专题
  · 黑客编程
  · 入侵检测
 安全技术工具下载
  · 扫描工具
  · 攻击程序
  · 后门木马
  · 拒绝服务
  · 口令破解
  · 代理程序
  · 防火墙
  · 加密解密
  · 入侵检测
  · 攻防演示
 安全技术论坛
  · 安全配制
  · 工具介绍
  · 防火墙
  · 黑客入侵
  · 漏洞检测
  · 破解方法
 其他安全技术资源
  · 攻防演示动画
  · 电子图书
  · QQ群组讨论区
  · 其他网站资源
最新招聘信息

让Java说话!
发表日期:2004-07-30作者:[转贴] 出处:  

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

作者:Tony Loton
译者:Cocia Lin

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

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

    也许对现在来说,能用的到的是一个email朗读器,这也得谢谢JavaMail API.这样的程序将定期的检查你的电子邮箱,并且你的注意被一个声音“你有新的email,你要我给你朗读吗?”吸引。相近的,考虑语音提醒 ?C 当连接到你的日常管理程序时 ?C- 电脑大喊“不要忘了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”的含义。让我来解释。

    语音引擎依靠联结人的最小的语音单位的短声音例子来工作 ?C 在这里是英语。这些声音例子,叫做音体变位(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 ?C 被”|”标志分割的输入文本 ?C 在把他们一个一个通过声音输出通道输出。为了让它听起来更自然,合并每一个声音的结尾到下一个声音的开头:

/*
*这个方法使输入的单词发音
*/
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资源控制 ?C 使用getResource(..)方法 ?C 这不同于得到一个普通文件的控制权。

为了有一个语音一个语音的读出数据,转换到声音格式,实例化一个声音输出行(为什么他们叫它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 ?C
      // -- 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
为他的公司工作 ?C LOTONTech Limited ?C 提供软件解决方案,顾问,培训和技术写作服务。写作的小虫好像在这一年里始终叮咬着他,他为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
 


 


 

我来说两句】 【发送给朋友】 【加入收藏】 【返加顶部】 【打印本页】 【关闭窗口
中搜索 让Java说话!

 ■ [欢迎对本文发表评论]
用  户:  匿名发出:
您要为您所发的言论的后果负责,故请各位遵纪守法并注意语言文明。

最新招聘信息

关于我们 / 合作推广 / 给我留言 / 版权举报 / 意见建议 / 广告投放 / 友情链接  
Copyright ©2001-2006 Lihuasoft.net webmaster(at)lihuasoft.net
网站编程QQ群   京ICP备05001064号 页面生成时间:0.00196