您好,登錄后才能下訂單哦!
讓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
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
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
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):
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
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。