📄 fdmffingerprint.java
字号:
*
* @param fftOutputArray Complex array output by FFT
* @return array of band energies
*/
private double[] calculateBandEnergies(final Complex[] fftOutputArray) {
final double[] be = new double[NUM_BANDS];
int b = 0;
for (b = 0; b < NUM_BANDS; b++) {
int i = 0;
be[b] = 0.0;
for (i = frequency[b]; i < frequency[b + 1]; i++) {
be[b] += fftOutputArray[i].scale();
}
be[b] /= CHUNK_SAMPLES;
be[b] = Math.sqrt(be[b]);
}
return be;
}
/**
* This routine calculates some metrics on the band energies of a chunk. The metrics are written to arrays, with one array for each metric. The chunk argument
* is the position in the output arrays where the metrics should be stored.
*
* @param be band energies
*/
void chunkMetrics(final double[] be) {
final double energy;
double ratio = 0.0;
double twist = 0.0;
double lows = 0.0;
final double highs;
final double evens;
double odds = 0.0;
lows = be[0] + be[1];
highs = be[2] + be[3];
evens = be[0] + be[2];
odds = be[1] + be[3];
/* trap zeros */
lows = Math.abs(lows) < 0.001 ? 0.001 : lows;
odds = Math.abs(odds) < 0.001 ? 0.001 : odds;
energy = lows + highs;
ratio = highs / lows;
twist = evens / odds;
ratio = Math.abs(ratio) > 20.0 ? 20 : ratio;
twist = Math.abs(twist) > 20.0 ? 20 : twist;
energyBuffer[chunkPos] = new Complex(energy, 0.0);
ratioBuffer[chunkPos] = new Complex(ratio, 0.0);
twistBuffer[chunkPos] = new Complex(twist, 0.0);
}
/**
* This routine takes raw audio data and puts it into the data structure that FFT operates on.
*
* @param buffer a byte array
* @param in a Complex array that id passed to the FFT
* @param offs offset in the byte array. i.e. starting position
* @param length length of the byte array
* @param pos offset in the Complex array
* @return a Point with the positions of the fft Array and the byte array
*/
private Point convertAudioToComplex(final byte[] buffer, final Complex[] in, final int offs, final int length, final int pos) {
int j = offs;
int i = pos;
for (; i < CHUNK_SAMPLES && j < length; i++) {
int l = 0;
int r = 0;
l = buffer[j++] & 0xff;
l |= buffer[j++] << 8 & 0xff00;
r = buffer[j++] & 0xff;
r |= buffer[j++] << 8 & 0xff00;
in[i] = new Complex(l + r, 0.0);
}
return new Point(i, j);
}
/**
* @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#getClassName()
*/
@Override
public String getClassName() {
final StringBuffer sb = new StringBuffer();
sb.append(super.getClassName()).append("-");
switch (config.getWindowType()) {
default:
case WINDOW_RECTANGULAR:
sb.append("RECT");
break;
case WINDOW_HAMMING:
sb.append("HAMM");
break;
case WINDOW_HANNING:
sb.append("HANN");
break;
case WINDOW_RAISED_COSINE:
sb.append("RACO-").append(config.getAlpha());
break;
case WINDOW_BARTLETT:
sb.append("BART");
break;
case WINDOW_WELCH:
sb.append("WELC");
break;
case WINDOW_KAISER:
sb.append("KAIS-").append(config.getAlpha());
break;
}
return sb.toString();
}
/**
* @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#getSimilarityThreshhold()
*/
@Override
public float getSimilarityThreshhold() {
return config.getSimilarityThreshhold();
}
/**
* @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#makeFingerprintFromFile()
*/
@Override
protected void makeFingerprintFromFile() {
final File f = new File(getFile().getPath());
AudioFormat audioFormat = new AudioFormat(44100.0f, 16, 2, true, false);
try {
AudioInputStream convertedAIS = null;
final AudioInputStream inFileAIS = AudioSystem.getAudioInputStream(f);
if (audioFormat.equals(inFileAIS.getFormat())) {
convertedAIS = inFileAIS;
} else if (inFileAIS.getFormat().getEncoding() == AudioFormat.Encoding.PCM_SIGNED
|| inFileAIS.getFormat().getEncoding() == AudioFormat.Encoding.PCM_UNSIGNED) {
// hack for WAV files
audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100.0f, 16, 2, inFileAIS.getFormat().getFrameSize(), 44100.0f, false);
convertedAIS = AudioSystem.getAudioInputStream(audioFormat, inFileAIS);
} else if (AudioSystem.isConversionSupported(audioFormat, inFileAIS.getFormat())) {
convertedAIS = AudioSystem.getAudioInputStream(audioFormat, inFileAIS);
} else {
throw new Exception("Could not convert from [" + inFileAIS.getFormat().toString() + "] to [" + audioFormat.toString() + "]");
}
final DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
if (!AudioSystem.isLineSupported(info)) {
throw new Exception("audio type not supported.");
}
final SourceDataLine dataLine = (SourceDataLine)AudioSystem.getLine(info);
dataLine.open(audioFormat);
dataLine.start();
preRead();
final int bufferSize = (int)audioFormat.getSampleRate() * audioFormat.getFrameSize();
byte[] buffer = new byte[bufferSize];
int bytesRead = 0;
while ((bytesRead = convertedAIS.read(buffer, 0, bufferSize)) >= 0 && chunkPos < MAX_CHUNKS) {
update(buffer, bytesRead);
}
// release memory as soon as possible
buffer = null;
dataLine.drain();
dataLine.close();
postRead();
} catch (final Exception e) {
getFile().setStatus(Status.FILE_CORRUPT);
fingerprint = null;
log.warn("error while reading file [" + getFile().getPath() + "]", e);
}
}
/**
* @param fi AbstractFingerprint
* @return boolean
* @modelguid {3E6D5504-3A75-4444-BE5B-C23BB0DF4DE8}
*/
@Override
public float matches(final AbstractFingerprint fi) {
final FingerprintFile ff1 = getFile();
final FingerprintFile ff2 = fi.getFile();
if (ff1.getStatus() != Status.FILE_OK && ff1.getStatus() != Status.FILE_SIGNATURE_MISMATCH || ff2.getStatus() != Status.FILE_OK
&& ff2.getStatus() != Status.FILE_SIGNATURE_MISMATCH) {
return 0.0f;
}
if (ff1.getLength() == 0L && ff2.getLength() == 0L) {
return 100.0f;
}
if (fi instanceof FdmfFingerprint) {
final FdmfFingerprint pf = (FdmfFingerprint)fi;
// try to read the files
final byte[] a = getFingerprint();
final byte[] b = pf.getFingerprint();
if ((ff1.getStatus() == Status.FILE_OK || ff1.getStatus() == Status.FILE_SIGNATURE_MISMATCH)
&& (ff2.getStatus() == Status.FILE_OK || ff2.getStatus() == Status.FILE_SIGNATURE_MISMATCH)) {
if (a == null) {
log.error("File [" + ff1.toString() + "] read Ok, but fingerprint is null!");
ff1.setStatus(Status.FILE_CORRUPT);
return 0.0f;
}
if (b == null) {
log.error("File [" + ff2.toString() + "] read Ok, but fingerprint is null!");
ff2.setStatus(Status.FILE_CORRUPT);
return 0.0f;
}
final float c = (float)compare(a, b);
return c;
}
}
return 0.0f;
}
/**
* @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#postRead()
*/
@Override
protected void postRead() {
// calc fingerprint
fftInputArray = null;
if (chunkPos > 0) {
// cut the buffers to a power of 2
final int len = (int)Math.pow(2.0, ((int)(Math.log(chunkPos) / Math.log(2.0))));
Complex[] eb = new Complex[len];
Complex[] rb = new Complex[len];
Complex[] tb = new Complex[len];
System.arraycopy(energyBuffer, 0, eb, 0, len);
System.arraycopy(ratioBuffer, 0, rb, 0, len);
System.arraycopy(twistBuffer, 0, tb, 0, len);
// release memory
energyBuffer = null;
ratioBuffer = null;
twistBuffer = null;
final double[] wt = config.makeWindow(len);
// do fft
eb = FFT.fft2(FFT.window(eb, wt));
rb = FFT.fft2(FFT.window(rb, wt));
tb = FFT.fft2(FFT.window(tb, wt));
fingerprint = new byte[(eb.length + rb.length + tb.length >> 3)];
final byte[] l1 = quantize(eb);
System.arraycopy(l1, 0, fingerprint, 0, l1.length);
final byte[] l2 = quantize(rb);
System.arraycopy(l2, 0, fingerprint, l1.length, l2.length);
final byte[] l3 = quantize(tb);
System.arraycopy(l3, 0, fingerprint, l1.length + l2.length, l3.length);
}
}
/**
* @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#preRead()
*/
@Override
protected void preRead() {
started = true;
fftPos = 0;
fftInputArray = new Complex[CHUNK_SAMPLES];
chunkPos = 0;
energyBuffer = new Complex[MAX_CHUNKS];
ratioBuffer = new Complex[MAX_CHUNKS];
twistBuffer = new Complex[MAX_CHUNKS];
}
/**
* @see net.za.grasser.duplicate.fingerprint.AbstractFingerprint#update(byte[], int)
*/
@Override
protected void update(final byte[] pBuffer, final int pLength) {
int baPos = 0;
// find start of sound (ignore silence)
if (started) {
int i = 0;
for (; i < pLength; i++) {
if (pBuffer[i] != 0) {
baPos = i;
break;
}
}
if (i == pLength) {
return;
}
}
started = false;
Point ret = convertAudioToComplex(pBuffer, fftInputArray, baPos, pLength, fftPos);
while (baPos < pLength && chunkPos < MAX_CHUNKS) {
fftPos = ret.x;
baPos = ret.y;
if (fftPos == CHUNK_SAMPLES) {
chunkMetrics(calculateBandEnergies(FFT.fft2(FFT.window(fftInputArray, config.getWindow()))));
chunkPos++;
// don't forget the last couple of bytes
if (baPos < pLength) {
ret = convertAudioToComplex(pBuffer, fftInputArray, baPos, pLength, 0);
}
}
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -