溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

讓Java說(shuō)話! (轉(zhuǎn))

發(fā)布時(shí)間:2020-08-10 04:50:11 來(lái)源:ITPUB博客 閱讀:146 作者:worldblog 欄目:編程語(yǔ)言
讓Java說(shuō)話! (轉(zhuǎn))[@more@]

  讓Java說(shuō)話!
 為你的Java 1.3 應(yīng)用程序和Applet添加說(shuō)話能力
概要
這篇文章中,Tony Loton展示了不使用硬件和本地調(diào)用的,少于150行Java代碼實(shí)現(xiàn)一個(gè)簡(jiǎn)單的語(yǔ)音引擎。此外,他提供了一個(gè)小zip文件,里面包含了使Java應(yīng)用程序說(shuō)話說(shuō)需要的東西—僅僅用來(lái)娛樂或別的真正的應(yīng)用程序。如果你剛剛接觸Java Sound api,這篇文章將是一個(gè)很好的介紹。(1800字)

作者:Tony Loton
譯者:Cocia Lin

  為什么要使你的程序說(shuō)話呢?首先,為了娛樂,這很適合象游戲這樣的娛樂程序。并且還有很多嚴(yán)肅的應(yīng)用領(lǐng)域。我想這雖然不是可視化界面的天生缺點(diǎn),也是聲音可用之處-- 或者過(guò)分一點(diǎn) – 可以使你的眼睛離開你正在做的事情。

  最近,我曾經(jīng)應(yīng)用一些技術(shù)在web上獲得HTML和XML信息的工作[請(qǐng)看 "Access the World's Biggest Database with Web DataBase Connectivity" (JavaWorld, March 2001)]。這讓我將那個(gè)工作和我的這個(gè)想法結(jié)合來(lái)創(chuàng)建一個(gè)說(shuō)話的Web瀏覽器。這樣的一個(gè)瀏覽器可以使你聽到你喜歡的網(wǎng)站上的信息摘錄 – 新聞標(biāo)題,例如 – 就象在外邊溜狗或開車上班的途中收聽收音機(jī)一樣。當(dāng)然,以現(xiàn)在的科技水平,你必須帶上你的筆記本電腦和移動(dòng)電話,但這些不切實(shí)際的設(shè)想在不久的將來(lái),隨著應(yīng)用Java技術(shù)的智能電話的出現(xiàn)而變成現(xiàn)實(shí),例如Nokia 9210(在美國(guó)叫9290).

  也許對(duì)現(xiàn)在來(lái)說(shuō),能用的到的是一個(gè)eMail朗讀器,這也得謝謝JavaMail API.這樣的程序?qū)⒍ㄆ诘臋z查你的電子郵箱,并且你的注意被一個(gè)聲音“你有新的email,你要我給你朗讀嗎?”吸引。相近的,考慮語(yǔ)音提醒 – 當(dāng)連接到你的日常管理程序時(shí) –- 電腦大喊“不要忘了10分鐘后你和老板的會(huì)議!”

  回到這些想法,或者你有更好的自己的想法,我們繼續(xù)。我將演示怎樣將我提供的zip文件添加的我們的工作中,這樣,如果你覺得這些東西太難了,你就可以直接安裝運(yùn)行而跳過(guò)實(shí)現(xiàn)細(xì)節(jié)。

測(cè)試語(yǔ)音引擎
  為了使用這個(gè)語(yǔ)音引擎,你需要將jw-0817-javatalk.zip添加到你的classpath,在命令行方式或Java程序中使用com.lotontech.speech.Talker。

命令行方式,象下面這樣運(yùn)行,輸入:


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

在Java程序中,簡(jiǎn)單的包含著兩行代碼:


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

這里,你可能想知道命令行方式或sayPhoneWord(..)方法中的字符串格式”h|e|l|oo”的含義。讓我來(lái)解釋。

  語(yǔ)音引擎依靠聯(lián)結(jié)人的最小的語(yǔ)音單位的短聲音例子來(lái)工作 – 在這里是英語(yǔ)。這些聲音例子,叫做音體變位(allophone),是一個(gè),兩個(gè),或三個(gè)字母標(biāo)識(shí)符的標(biāo)志。有些標(biāo)識(shí)符是明顯的,有些是不明顯的,你能從語(yǔ)音學(xué)里看到這樣的”hello”的表示。

h --發(fā)音你能想到
e --發(fā)音你能想到
l --發(fā)音你能想到,但注意,我將兩個(gè) “l(fā)” 變?yōu)橐粋€(gè)”l”
oo -- “hello”的發(fā)音,不是”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 -- 變調(diào)b
dd --變調(diào)d
ggg -- 變調(diào)g
hh --變調(diào)h
ll --變調(diào)l
nn --變調(diào)n
rr -- 變調(diào)r
tt -- 變調(diào)t
yy --變調(diào)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 -- 變調(diào) u
wh -- 例如 where
zh -- 例如 Asian
人說(shuō)話的每一個(gè)句子都有單詞的升調(diào)和降調(diào)的變化。這個(gè)音調(diào)使說(shuō)話聽起來(lái)自然,富有感情,并且可以從句子語(yǔ)調(diào)確定這是疑問(wèn)句。如果你聽過(guò)Stephen Hawking的人造聲音,你就能夠理解我所說(shuō)的了。考慮這兩個(gè)句子:

It is fake -- f|aa|k
Is it fake? -- f|AA|k
你也許猜想,使用升調(diào)的方法是用大寫字母。你要實(shí)際感受一下,我的提示是你要注意聽元音字母

  這是你使用這個(gè)軟件需要知道的全部了,但是如果你對(duì)引擎罩下面的東西感興趣,那么繼續(xù)往下讀。

實(shí)現(xiàn)語(yǔ)音引擎

  語(yǔ)音引擎僅僅需要一個(gè)類來(lái)實(shí)現(xiàn),包含四個(gè)方法。它使用j2se1.3的Java Sound API。我不想提供一個(gè)全面的Java Sound API教程,你將通過(guò)例子學(xué)習(xí)。你將發(fā)現(xiàn)不是有很多需要你來(lái)做,并且說(shuō)明能告訴你需要知道的。

這里是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;
}

如果你從命令行運(yùn)行程序,下面的main(..)方法將作為一個(gè)入口服務(wù)。它取得命令行的第一個(gè)參數(shù),如果有一個(gè)參數(shù),將傳遞給sayPhoneWord(…)方法:


/*
*這個(gè)方法在命令行對(duì)一個(gè)指定的單詞發(fā)音
*/
public static void main(String args[])
{
  Talker player=new Talker();
  if (args.length>0) player.sayPhoneWord(args[0]);
  System.exit(0);
}

上面,SayPhoneWord(…)方法被main(…)方法調(diào)用,或者它被Java程序或Applet直接調(diào)用。它看起來(lái)比它本身難理解。本質(zhì)上,它簡(jiǎn)單的一步一步解釋單詞的語(yǔ)音變位allophone – 被”|”標(biāo)志分割的輸入文本 – 在把他們一個(gè)一個(gè)通過(guò)聲音輸出通道輸出。為了讓它聽起來(lái)更自然,合并每一個(gè)聲音的結(jié)尾到下一個(gè)聲音的開頭:

/*
*這個(gè)方法使輸入的單詞發(fā)音
*/
public void sayPhoneWord(String word)
{
// 為上一個(gè)聲音設(shè)置一個(gè)字節(jié)數(shù)組
  byte[] previousSound=null;

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

為語(yǔ)音單位構(gòu)造一個(gè)文件名
  String thisPhoneFile=st.nextToken();
  thisPhoneFile="/allophones/"+thisPhoneFile+".au";


  從文件中獲得數(shù)據(jù)
  byte[] thisSound=getSound(thisPhoneFile);

  if (previousSound!=null)
  {
 

  合并上一個(gè)語(yǔ)音和現(xiàn)在的這個(gè)
  int mergeCount=0;
  if (previousSound.length>=500 && thisSound.length>=500)
  mergeCount=500;
  for (int i=0; i  {
  previousSound[previousSound.length-mergeCount+i]
  =(byte)((previousSound[previousSound.length
  -mergeCount+i]+thisSound[i])/2);
  }


  播放前一個(gè)音符
  playSound(previousSound);


  切割當(dāng)前的音符作為前一個(gè)音符
  byte[] newSound=new byte[thisSound.length-mergeCount];
  for (int ii=0; ii  newSound[ii]=thisSound[ii+mergeCount];
  previousSound=newSound;
  }
  else
  previousSound=thisSound;
  }


  //播放最終聲音和刷新聲音通道
  playSound(previousSound);
  drain();
}

在sayPhoneWord()結(jié)尾,你看到它調(diào)用playSound(..)來(lái)輸出單獨(dú)的聲音例子,并且調(diào)用drain(..)來(lái)刷新聲音通道。這里是playSound(..)的代碼:


/*
*播放一個(gè)聲音
*/
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) {}
}

現(xiàn)在,如果你回頭看看sayPhoneWord(..)方法,你將發(fā)現(xiàn)還有一個(gè)方法我們還沒有提到:getSound(..).


getSound(..)從事先錄制好的au文件中讀出聲音的字節(jié)數(shù)據(jù)。當(dāng)我說(shuō)文件時(shí),指的是我提供的zip文件里的資源。我強(qiáng)調(diào)這點(diǎn)差別,因?yàn)槟愕玫絁AR資源控制 – 使用getResource(..)方法 – 這不同于得到一個(gè)普通文件的控制權(quán)。

為了有一個(gè)語(yǔ)音一個(gè)語(yǔ)音的讀出數(shù)據(jù),轉(zhuǎn)換到聲音格式,實(shí)例化一個(gè)聲音輸出行(為什么他們叫它SourceDateLine,我不知道),組合這些字節(jié)數(shù)據(jù),我在下面代碼中提供給你說(shuō)明:


/*
*這個(gè)方法從文件中讀出單獨(dú)的語(yǔ)音并且構(gòu)造一個(gè)字節(jié)矢量
*/
private byte[] getSound(String fileName)
{
  try
  {
  URL url=Talker.class.getResource(fileName);
  AudioInputStream stream = AudioSystem.getAudioInputStream(url);

  AudioFormat format = stream.getFormat();

 

轉(zhuǎn)換一個(gè)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.");
  }

 

打開資源數(shù)據(jù)行(輸出行output line)
  line = (SourceDataLine) AudioSystem.getLine(outInfo);
  line.open(format, 50000);
  line.start();
  }

//一些尺寸計(jì)算
  int frameSizeInBytes = format.getFrameSize();
  int bufferLengthInFrames = line.getBufferSize() / 8;
  int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;

  byte[] data=new byte[bufferLengthInBytes];

  //讀出數(shù)據(jù)字節(jié)并計(jì)算
  int numBytesRead = 0;
  if ((numBytesRead = stream.read(data)) != -1)
  {
  int numBytesRemaining = numBytesRead;
  }

 

//裁剪字節(jié)數(shù)組到正確尺寸
  byte[] newData=new byte[numBytesRead];
  for (int i=0; i  newData[i]=data[i];

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

好了,就這么多。一個(gè)150行的語(yǔ)音合成器代碼,包括說(shuō)明。但這沒有完全結(jié)束。

文本到語(yǔ)音(Text-to-speech)的轉(zhuǎn)換
用語(yǔ)音學(xué)方法表示單詞可能太乏味,所以,如果你想創(chuàng)建一個(gè)象我介紹一樣的應(yīng)用程序,你要提供原始文本。

研究過(guò)這個(gè)題目后,我在zip文件中提供一個(gè)實(shí)驗(yàn)性的文本到語(yǔ)音的轉(zhuǎn)換類。當(dāng)你運(yùn)行它后,將輸出給你你想要的語(yǔ)音表示。

在命令行模式,運(yùn)行text-to-speech轉(zhuǎn)換器:


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

你看到的輸出類似下面這樣:


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

或者,象這樣運(yùn)行它:


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

如果你想知道它是怎樣工作的,我將告訴你我的方法很簡(jiǎn)單,應(yīng)用通常的順序的一套文本替換規(guī)則。有幾個(gè)例子規(guī)則,你可能喜歡應(yīng)用精神上的,順序的方式,這些例子是”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”的順序?qū)⑹沁@樣:


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)

你應(yīng)該看到,包含ant的want被用幾種不同的方式朗讀。你也應(yīng)該看到對(duì)于unique來(lái)說(shuō)的特殊情況,應(yīng)該被讀為y|ou..而不是u|n….

電腦里的精靈,對(duì)你說(shuō)話
這篇文章提供一個(gè)可以使用Java 1.3運(yùn)行的簡(jiǎn)便的語(yǔ)音引擎。如果你研究這些代碼,你可以得到一些關(guān)于JavaSound API播放音頻片斷的有用方法。要想使這個(gè)引擎真的能用,你要思考文本到語(yǔ)音的轉(zhuǎn)換方法,這真的是我的一個(gè)主要想法。在這個(gè)引擎中,你要想出大量的文本轉(zhuǎn)換規(guī)則,還要應(yīng)用一些好的優(yōu)先順序。我希望你有比我強(qiáng)的毅力。

最后,你可能還記得我說(shuō)過(guò)的Nokia 9210。我有一部,它支持Java,我決定用Java使它說(shuō)話。我也想使applet(Java2的以前版本)在瀏覽器中說(shuō)話。這些技術(shù)依靠J2SE 1.3聲音引擎,現(xiàn)在是可用的。一個(gè)不同的方法是需要的,依靠簡(jiǎn)單的Java AudioClip 接口。不像你想象的那樣簡(jiǎn)單,但我在其上工作。

關(guān)于作者
Tony Loton
為他的公司工作 – LOTONTech Limited – 提供軟件解決方案,顧問(wèn),培訓(xùn)和技術(shù)寫作服務(wù)。寫作的小蟲好像在這一年里始終叮咬著他,他為John Wiley & Sons 和 Wrox Press出版社寫書。

關(guān)于譯者

Cocia Lin(cocia@163.com)是程序員。它擁有學(xué)士學(xué)位,現(xiàn)在專攻Java相關(guān)技術(shù),剛剛開始在計(jì)算機(jī)領(lǐng)域折騰。

相關(guān)資源

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):
db.html">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
 


 


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI