private void trackListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { trackFingerprintListBox.Items.Clear(); if (trackListBox.SelectedItems.Count > 0) { AudioTrack audioTrack = (AudioTrack)trackListBox.SelectedItem; Dictionary <SubFingerprintHash, object> hashFilter = new Dictionary <SubFingerprintHash, object>(); // helper structure to filter out duplicate hashes foreach (SubFingerprintHash hash in store.AudioTracks[audioTrack]) { if (store.CollisionMap.GetValues(hash).Count > 1 && !hashFilter.ContainsKey(hash)) { // only add hash to the list if it points to at least two different audio tracks List <SubFingerprintLookupEntry> entries = store.CollisionMap.GetValues(hash); SubFingerprintLookupEntry firstEntry = entries[0]; for (int x = 1; x < entries.Count; x++) { if (entries[x].AudioTrack != firstEntry.AudioTrack) { trackFingerprintListBox.Items.Add(hash); hashFilter.Add(hash, null); break; } } } } } }
public Fingerprint GetFingerprint(SubFingerprintLookupEntry entry) { int indexOffset = 0; if (store[entry.AudioTrack].Count - entry.Index < fingerprintSize) { indexOffset = Math.Min(indexOffset, -fingerprintSize + store[entry.AudioTrack].Count - entry.Index); } return(new Fingerprint(store[entry.AudioTrack], entry.Index + indexOffset, fingerprintSize)); }
private void ShowFingerprints(SubFingerprintLookupEntry sfp1, SubFingerprintLookupEntry sfp2) { Fingerprint fp1 = store.GetFingerprint(sfp1); Fingerprint fp2 = store.GetFingerprint(sfp2); Fingerprint fpDifference = fp1.Difference(fp2); fingerprintView1.Fingerprint = fp1; fingerprintView2.Fingerprint = fp2; fingerprintView3.Fingerprint = fpDifference; berLabel.Content = Fingerprint.CalculateBER(fp1, fp2); }
public List <Match> FindMatches(SubFingerprintHash hash) { List <Match> matches = new List <Match>(); List <SubFingerprintLookupEntry> entries = collisionMap.GetValues(hash); //Debug.WriteLine("Finding matches..."); // compare each track with each other int cycle = 1; for (int x = 0; x < entries.Count; x++) { SubFingerprintLookupEntry entry1 = entries[x]; for (int y = cycle; y < entries.Count; y++) { SubFingerprintLookupEntry entry2 = entries[y]; if (entry1.AudioTrack != entry2.AudioTrack) // don't compare tracks with themselves //Debug.WriteLine("Comparing " + entry1.AudioTrack.Name + " with " + entry2.AudioTrack.Name + ":"); { if (store[entry1.AudioTrack].Count - entry1.Index < fingerprintSize || store[entry2.AudioTrack].Count - entry2.Index < fingerprintSize) { // the end of at least one track has been reached and there are not enough hashes left // to do a fingerprint comparison continue; } // sum up the bit errors List <SubFingerprintHash> track1Hashes = store[entry1.AudioTrack]; List <SubFingerprintHash> track2Hashes = store[entry2.AudioTrack]; uint bitErrors = 0; for (int s = 0; s < fingerprintSize; s++) { SubFingerprintHash track1Hash = track1Hashes[entry1.Index + s]; SubFingerprintHash track2Hash = track2Hashes[entry2.Index + s]; if (track1Hash.Value == 0 || track2Hash.Value == 0) { bitErrors = (uint)fingerprintSize * 32; break; } // skip fingerprints with hashes that are zero, since it is probably from // a track section with silence // by setting the bitErrors to the maximum, the match will not be added bitErrors += track1Hash.HammingDistance(track2Hash); } float bitErrorRate = bitErrors / (float)(fingerprintSize * 32); // sub-fingerprints * 32 bits //Debug.WriteLine("BER: " + bitErrorRate + " <- " + (bitErrorRate < threshold ? "MATCH!!!" : "no match")); if (bitErrorRate < threshold) { matches.Add(new Match { Similarity = 1 - bitErrorRate, Track1 = entry1.AudioTrack, Track1Time = SubFingerprintIndexToTimeSpan(entry1.Index), Track2 = entry2.AudioTrack, Track2Time = SubFingerprintIndexToTimeSpan(entry2.Index), Source = matchSourceName }); } } } cycle++; } //Debug.WriteLine("finished"); return(matches); }
public List <Match> FindMatches(SubFingerprintHash hash) { List <Match> matches = new List <Match>(); List <SubFingerprintLookupEntry> entries = collisionMap.GetValues(hash); for (int x = 0; x < entries.Count; x++) { SubFingerprintLookupEntry entry1 = entries[x]; for (int y = x; y < entries.Count; y++) { SubFingerprintLookupEntry entry2 = entries[y]; if (entry1.AudioTrack != entry2.AudioTrack) // don't compare tracks with themselves { var store1 = store[entry1.AudioTrack]; var store2 = store[entry2.AudioTrack]; List <SubFingerprintHash> hashes1 = store1.hashes; List <SubFingerprintHash> hashes2 = store2.hashes; int index1 = entry1.Index; int index2 = entry2.Index; TrackStore.IndexEntry indexEntry1, indexEntry2; int numTried = 0; // count of hashes tried to match int numMatched = 0; // count of hashes matched int frameCount = 0; // count over how many actual frames hashes were matched (an index in the store is the equivalent of a frame in the generator) bool matchFound = false; // Iterate through sequential frames TrackStore.IndexEntry indexEntryNone = new TrackStore.IndexEntry(); while (true) { indexEntry1 = store1.index.ContainsKey(index1) ? store1.index[index1] : indexEntryNone; indexEntry2 = store2.index.ContainsKey(index2) ? store2.index[index2] : indexEntryNone; // Hash collision // The union of the two ranges is the total number of distinct hashes // The intersection of the two ranges is the total number of similar hashes // NOTE The following block calculates the number of matches (the intersection) // of the two hash lists with the Zipper intersection algorithm and relies // on the hash list sorting in the fingerprint generator. // Other approaches tried which are slower: // - n*m element by element comparison (seven though the amount of elements is reasonably small) // - concatenating the two ranges (LINQ), sorting them, and linearly iterating over, counting the duplicates (sort is slow) // - using a hash set for collision detection (hash set insertion and lookup are costly) int i = indexEntry1.index; int i_e = indexEntry1.index + indexEntry1.length; int j = indexEntry2.index; int j_e = indexEntry2.index + indexEntry2.length; int intersectionCount = 0; // Count intersecting hashes of a frame with the Zipper algorithm while (i < i_e && j < j_e) { if (hashes1[i] < hashes2[j]) { i++; } else if (hashes2[j] < hashes1[i]) { j++; } else { intersectionCount++; i++; j++; } } numMatched += intersectionCount; numTried += indexEntry1.length + indexEntry2.length - intersectionCount; // Determine the next indices to check for collisions int nextIndex1Increment = 0; if (hashes1.Count > i_e) { do { nextIndex1Increment++; } while (!store1.index.ContainsKey(index1 + nextIndex1Increment)); } int nextIndex2Increment = 0; if (hashes2.Count > j_e) { do { nextIndex2Increment++; } while (!store2.index.ContainsKey(index2 + nextIndex2Increment)); } int nextIndexIncrement = Math.Min(nextIndex1Increment, nextIndex2Increment); index1 += nextIndexIncrement; index2 += nextIndexIncrement; frameCount += nextIndexIncrement; // Match detection // This approach trades the hash matching rate with time, i.e. the rate required // for a match drops with time, by using an exponentially with time decaying threshold. // The idea is that a high matching rate after a short time is equivalent to a low matching // rate after a long time. The difficulty is to to parameterize it in such a way, that a // match is detected as fast as possible, while detecting a no-match isn't delayed too far // as it takes a lot of processing time. // NOTE The current parameters are just eyeballed, there's a lot of influence on processing speed and matching rate here double rate = 1d / numTried * numMatched; if (frameCount >= matchingMaxFrames || rate < thresholdReject[frameCount]) { break; // exit condition } else if (frameCount > matchingMinFrames && rate > thresholdAccept[frameCount]) { matchFound = true; break; } if (nextIndexIncrement == 0) { // We reached the end of a hash list break; // Break the while loop } } if (matchFound) { matches.Add(new Match { Similarity = 1f / numTried * numMatched, Track1 = entry1.AudioTrack, Track1Time = SubFingerprintIndexToTimeSpan(entry1.Index), Track2 = entry2.AudioTrack, Track2Time = SubFingerprintIndexToTimeSpan(entry2.Index), Source = matchSourceName }); } } } } return(matches); }