📄 diphoneunitdatabase.java
字号:
/** * Portions Copyright 2003 Sun Microsystems, Inc. * Portions Copyright 1999-2001 Language Technologies Institute, * Carnegie Mellon University. * All Rights Reserved. Use is subject to license terms. * * See the file "license.terms" for information on usage and * redistribution of this file, and for a DISCLAIMER OF ALL * WARRANTIES. */package com.sun.speech.freetts.diphone;import com.sun.speech.freetts.relp.Sample;import com.sun.speech.freetts.relp.SampleInfo;import com.sun.speech.freetts.util.BulkTimer;import com.sun.speech.freetts.util.Utilities;import com.sun.speech.freetts.Unit;import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.InputStream;import java.io.InputStreamReader;import java.io.IOException;import java.net.MalformedURLException;import java.net.URL;import java.nio.ByteBuffer;import java.nio.channels.Channels;import java.nio.channels.FileChannel;import java.nio.channels.ReadableByteChannel;import java.nio.MappedByteBuffer;import java.util.HashMap;import java.util.Iterator;import java.util.LinkedHashMap;import java.util.Map;import java.util.NoSuchElementException;import java.util.StringTokenizer;import java.lang.ref.Reference;import java.lang.ref.SoftReference;import java.lang.ref.WeakReference;/** * Represents and manages the unit data for all diphones. The diphone * data set is stored in a set of data files. These data are loaded by this * class into internal data structures before diphone synthesis can * occur. * <p> *The diphone data set is one of the largest sets of data that * needs to be loaded by the synthesizer and therefore can add to the * overall startup time for any system using this database. For * certain applications, the startup time is a critical spec that * needs to be optimized, while for other applications, startup time * is inconsequential. This class provides settings (via system * properties) that control how the database is to be loaded so that * applications can tune for quick startup or optimal run time. * <p> * This class serves also as a testbed for comparing performance of * the traditional java binary I/O and the new io ( <code>java.nio </code>) * package. * <p> * <p> A diphone database can be loaded from a text data file, or a * binary datafile. The binary version loads significantly faster * than the text version. Additionally, a binary index can be * generated and used to reduce overall memory footprint. * <p> * <p> * A DiphoneUnitDatabase contains an array of frames, and an aray of * residuals. The frames are the samples of the wave, and the * residuals are for linear predictive coding use. This is called * "cst_sts" (a struct) in flite. * <p> * Note that if 'com.sun.speech.freetts.useNewIO' is set to true and * the input type is binary, than the JDK1.4+ new IO api is used to * load the database. * <p> * The system property * <pre> * com.sun.speech.freetts.diphone.UnitDatabase.cacheType * </pre> * * can be set to one of: * * <ul> * <li> preload: database is loaded at startup * <li> demand: database is loaded on demand * <li> hard: database is loaded on demand but cached * <li> soft: database is loaded on demand but cached with soft references * </ul> * * This <code> cacheType </code> setting controls how the database is * loaded. The default is to 'preload' the database. This setting * gives best runtime performance but with longer initial startup * cost. */public class DiphoneUnitDatabase { private String name; private int sampleRate; private int numChannels; private int residualFold = 1; private float lpcMin; private float lpcRange; private int lineCount = 0; private Diphone defaultDiphone; private Map diphoneMap = null; private Map diphoneIndex; private SampleInfo sampleInfo; private boolean useNewIO = Utilities.getProperty("com.sun.speech.freetts.useNewIO", "true").equals("true"); // cache can be 'preload' 'none', 'soft' or 'hard' private String cacheType = Utilities.getProperty( "com.sun.speech.freetts.diphone.UnitDatabase.cacheType", "preload"); private boolean useIndexing = !cacheType.equals("preload"); private boolean useCache = !cacheType.equals("demand"); private boolean useSoftCache = cacheType.equals("soft"); private final static int MAGIC = 0xFEEDFACE; private final static int INDEX_MAGIC = 0xFACADE; private final static int VERSION = 1; private final static int MAX_DB_SIZE = 4 * 1024 * 1024; private String indexName = null; private MappedByteBuffer mbb = null; private int defaultIndex = -1; /** * Creates the DiphoneUnitDatabase from the given input stream. * * @param url the location of the database * @param isBinary if <code>true</code> the database is in * binary format; otherwise it is in text format * * @throws IOException if there is trouble opening the DB */ public DiphoneUnitDatabase(URL url, boolean isBinary) throws IOException { if (!useIndexing || useCache) { diphoneMap = new LinkedHashMap(); } InputStream is = Utilities.getInputStream(url); indexName = getIndexName(url.toString()); if (isBinary) { loadBinary(is); } else { loadText(is); } is.close(); sampleInfo = new SampleInfo(sampleRate, numChannels, residualFold, lpcMin, lpcRange, 0.0f); } /** * Return the information about the sample data * for this database. * * @return the sample info */ SampleInfo getSampleInfo() { return sampleInfo; } /** * Returns the index name from the databaseName. * * @param databaseName the database name * * @return the index name or null if the database is not * a binary database. * * [[[ TODO the index should probably be incorporated into the * binary database ]]] */ private String getIndexName(String databaseName) { String indexName = null; if (databaseName.lastIndexOf(".") != -1) { indexName = databaseName.substring(0, databaseName.lastIndexOf(".")) + ".idx"; } return indexName; } /** * Loads the database from the given input stream. * * @param is the input stream */ private void loadText(InputStream is) { BufferedReader reader; String line; if (is == null) { throw new Error("Can't load diphone db file."); } reader = new BufferedReader(new InputStreamReader(is)); try { line = reader.readLine(); lineCount++; while (line != null) { if (!line.startsWith("***")) { parseAndAdd(line, reader); } line = reader.readLine(); } reader.close(); } catch (IOException e) { throw new Error(e.getMessage() + " at line " + lineCount); } finally { } } /** * Parses and process the given line. Used to process the text * form of the database. * * @param line the line to process * @param reader the source for the lines */ private void parseAndAdd(String line, BufferedReader reader) { try { StringTokenizer tokenizer = new StringTokenizer(line," "); String tag = tokenizer.nextToken(); if (tag.equals("NAME")) { name = tokenizer.nextToken(); } else if (tag.equals("SAMPLE_RATE")) { sampleRate = Integer.parseInt(tokenizer.nextToken()); } else if (tag.equals("NUM_CHANNELS")) { numChannels = Integer.parseInt(tokenizer.nextToken()); } else if (tag.equals("LPC_MIN")) { lpcMin = Float.parseFloat(tokenizer.nextToken()); } else if (tag.equals("COEFF_MIN")) { lpcMin = Float.parseFloat(tokenizer.nextToken()); } else if (tag.equals("COEFF_RANGE")) { lpcRange = Float.parseFloat(tokenizer.nextToken()); } else if (tag.equals("LPC_RANGE")) { lpcRange = Float.parseFloat(tokenizer.nextToken()); } else if (tag.equals("DIPHONE")) { String name = tokenizer.nextToken(); int start = Integer.parseInt(tokenizer.nextToken()); int mid = Integer.parseInt(tokenizer.nextToken()); int end = Integer.parseInt(tokenizer.nextToken()); int numSamples = (end - start); int midPoint = mid - start; if (numChannels <= 0) { throw new Error("Bad number of channels " + numChannels); } if (numSamples <= 0) { throw new Error("Bad number of samples " + numSamples); } Sample[] samples = new Sample[numSamples]; for (int i = 0; i < samples.length; i++) { samples[i] = new Sample(reader, numChannels); assert samples[i].getFrameData().length == numChannels; } Diphone diphone = new Diphone(name, samples, midPoint); add(diphone); } else { throw new Error("Unsupported tag " + tag); } } catch (NoSuchElementException nse) { throw new Error("Error parsing db " + nse.getMessage()); } catch (NumberFormatException nfe) { throw new Error("Error parsing numbers in db " + nfe.getMessage()); } } /** * Adds the given diphone to the DB. Diphones are kept in a map so * they can be accessed by name. * * @param diphone the diphone to add. */ private void add(Diphone diphone) { diphoneMap.put(diphone.getName(), diphone); if (defaultDiphone == null) { defaultDiphone = diphone; } } /** * Looks up the diphone with the given name. * * @param unitName the name of the diphone to look for * * @return the diphone or the defaultDiphone if not found. */ public Diphone getUnit(String unitName) { Diphone diphone = null; if (useIndexing) { diphone = getFromCache(unitName); if (diphone == null) { int index = getIndex(unitName); if (index != -1) { mbb.position(index); try { diphone = Diphone.loadBinary(mbb); if (diphone != null) { putIntoCache(unitName, diphone); } } catch (IOException ioe) { System.err.println("Can't load diphone " + unitName); diphone = null; } } } } else { diphone = (Diphone) diphoneMap.get(unitName); } if (diphone == null) { System.err.println("Can't find diphone " + unitName); diphone = defaultDiphone; } return diphone; } /** * Gets the named diphone from the cache. If we are using soft * caching, the reference may be a soft/weak reference so check to * see if the reference is still valid, if so return it; otherwise * invalidate it. Note that we have not had good success with weak * caches so far. The goal is to reduce the minimum required * memory footprint as far as possible while not compromising * performance. In small memory systems, the weak cache would * likely be reclaimed, giving us lower performance but with the * ability to still be able to run. In reality, the soft caches * did not help much. They just did not work correctly. * [[[ TODO: test weak/soft cache behavior with new versions of * the runtime to see if their behavior has improved ]]] * * @param name the name of the diphone * * @return the diphone or <code> null </code> if not in the cache */ private Diphone getFromCache(String name) { if (diphoneMap == null) { return null; } Diphone diphone = null; if (useSoftCache) { Reference ref = (Reference) diphoneMap.get(name); if (ref != null) { diphone = (Diphone) ref.get(); if (diphone == null) { diphoneMap.remove(name); } else { } } } else { diphone = (Diphone) diphoneMap.get(name); } return diphone; } /** * Puts the diphone in the cache. * * @param diphoneName the name of the diphone * @param diphone the diphone to put in the cache */ private void putIntoCache(String diphoneName, Diphone diphone) { if (diphoneMap == null) { return ; } if (useSoftCache) { diphoneMap.put(diphoneName, new WeakReference(diphone)); } else { diphoneMap.put(diphoneName, diphone); } } /** * Dumps the soft ref cache. */ private void dumpCacheSize() { int empty = 0; int full = 0; System.out.println("Entries: " + diphoneMap.size()); for (Iterator i = diphoneMap.values().iterator(); i.hasNext(); ) { Reference ref = (Reference) i.next(); if (ref.get() == null) { empty++; } else { full++; } } System.out.println(" empty: " + empty); System.out.println(" full: " + full); } /** * Returns the name of this DiphoneUnitDatabase. */ public String getName() { return name; } /** * Dumps the diphone database. */ public void dump() { System.out.println("Name " + name); System.out.println("SampleRate " + sampleRate); System.out.println("NumChannels " + numChannels); System.out.println("lpcMin " + lpcMin); System.out.println("lpcRange " + lpcRange); for (Iterator i = diphoneMap.values().iterator(); i.hasNext(); ) { Diphone diphone = (Diphone) i.next(); diphone.dump(); } } /** * Dumps a binary form of the database. * * @param path the path to dump the file to
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -