讓Java說話! (轉)

worldblog發表於2007-12-09
讓Java說話! (轉)[@more@]

  讓說話!
 為你的Java 1.3 應用和Applet新增說話能力
概要
這篇文章中,Tony Loton展示了不使用和本地的,少於150行Java程式碼實現一個簡單的語音引擎。此外,他提供了一個小zip,裡面包含了使Java應用程式說話說需要的東西—僅僅用來娛樂或別的真正的應用程式。如果你剛剛接觸Java Sound ,這篇文章將是一個很好的介紹。(1800字)

作者:Tony Loton
譯者:Cocia Lin

  為什麼要使你的程式說話呢?首先,為了娛樂,這很適合象遊戲這樣的娛樂程式。並且還有很多嚴肅的應用領域。我想這雖然不是視覺化介面的天生缺點,也是可用之處-- 或者過分一點 – 可以使你的眼睛離開你正在做的事情。

  最近,我曾經應用一些技術在上獲得HTML和資訊的工作[請看 "Access the World's Biggest Database with Web DataBase Connectivity" (JavaWorld, March 2001)]。這讓我將那個工作和我的這個想法結合來建立一個說話的Web。這樣的一個瀏覽器可以使你聽到你喜歡的網站上的資訊摘錄 – 新聞標題,例如 – 就象在外邊溜狗或開車上班的途中收聽收音機一樣。當然,以現在的科技水平,你必須帶上你的筆記本和行動電話,但這些不切實際的設想在不久的將來,隨著應用Java技術的智慧電話的出現而變成現實,例如Nokia 9210(在美國叫9290).

  也許對現在來說,能用的到的是一個e朗讀器,這也得謝謝JavaMail API.這樣的程式將定期的檢查你的電子,並且你的注意被一個聲音“你有新的,你要我給你朗讀嗎?”吸引。相近的,考慮語音提醒 – 當連線到你的日常管理程式時 –- 電腦大喊“不要忘了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.sayPhone("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
你也許猜想,使用升調的方法是用大寫字母。你要實際感受一下,我的提示是你要注意聽母音字母

  這是你使用這個需要知道的全部了,但是如果你對引擎罩下面的東西感興趣,那麼繼續往下讀。

實現語音引擎

  語音引擎僅僅需要一個類來實現,包含四個方法。它使用1.3的Java Sound API。我不想提供一個全面的Java Sound API教程,你將透過例子學習。你將發現不是有很多需要你來做,並且說明能告訴你需要知道的。

這裡是Talker類的基本定義:


package com.lotontech.speech;

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

public class Talker
{
  private DataLine line=null;
}

如果你從命令列執行程式,下面的main(..)方法將作為一個入口服務。它取得命令列的第一個引數,如果有一個引數,將傳遞給sayPhoneWord(…)方法:


/*
*這個方法在命令列對一個指定的單詞發音
*/
public static void main(String args[])
{
  Talker =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  {
  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[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  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 AudioCl介面。不像你想象的那樣簡單,但我在其上工作。

 

 

關於作者
Tony Loton
為他的公司工作 – LOTONTech Limited – 提供軟體解決方案,顧問,培訓和技術寫作服務。寫作的小蟲好像在這一年裡始終叮咬著他,他為John Wiley & Sons 和 Wrox Press出版社寫書。

關於譯者

Cocia Lin()是程式設計師。它擁有學士學位,現在專攻Java相關技術,剛剛開始在領域折騰。

相關資源

You'll find the speech engine and related source code in the jw-0817-javatalk.file:

Go to java.sun.com's "Java Sound API" page for documentation, information, and FAQ:

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):
.html">
In "Make an 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:

"Program Multimedia with JMF," Budi Kurniawan (JavaWorld):
Part 1: Go multimedia by learning how the Java Media compares to your stereo system (April 2001)
Part 2: Jump into Java Media Framework's important classes and interfaces (May 2001)
"Add capabilities to Java Sound with SPI," Dan Becker (JavaWorld, November 2000):

Sign up for the JavaWorld This Week free weekly email newsletter to learn what's new at JavaWorld:

You'll find a wealth of IT-related articles from our sister publications at IDG.net
 


 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990043/,如需轉載,請註明出處,否則將追究法律責任。

相關文章