You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用C#实现Philips Robust Hashing算法提取32位音频指纹?

Implementing Philips Robust Hashing (PRH) in C# for Audio Fingerprinting

Hey there! As someone who’s built audio fingerprinting tools before, I’ll walk you through implementing PRH (from Haitsma’s A Highly Robust Audio Fingerprinting System) in C# step by step. We’ll cover the core algorithm logic, code snippets, and key tips to avoid common pitfalls.

Core PRH Algorithm Overview

First, let’s recap the high-level flow to make sure we’re aligned:

  • Preprocess Audio: Convert to 8kHz mono, 16-bit PCM (PRH’s standard input format).
  • Frame the Signal: Split audio into overlapping frames with specific timing.
  • Spectral Analysis: Compute FFT for each frame, extract frequency band energies.
  • Differential Energy Calculation: Compute energy differences across consecutive frames.
  • Fingerprint Generation: Compare differential values across frequency bands to generate 32-bit binary fingerprints.

Step-by-Step Implementation

1. Setup Dependencies

We’ll use two libraries to simplify heavy lifting:

  • NAudio: For reading and converting audio files.
  • MathNet.Numerics: For FFT calculations.
    Install them via NuGet:
Install-Package NAudio
Install-Package MathNet.Numerics

2. Audio Preprocessing

First, convert any input audio (WAV, MP3, etc.) to 8kHz mono, 16-bit PCM. Here’s a helper method:

using NAudio.Wave;
using System.IO;
using System.Collections.Generic;

public float[] PreprocessAudio(string audioFilePath)
{
    using var audioFileReader = new AudioFileReader(audioFilePath);
    // Convert to 8kHz mono, 16-bit PCM
    using var resampler = new MediaFoundationResampler(audioFileReader, new WaveFormat(8000, 16, 1));
    resampler.ResamplerQuality = 60; // Balanced quality/speed

    // Read all samples as normalized float values [-1, 1]
    var samples = new List<float>();
    var buffer = new float[4096];
    int bytesRead;
    while ((bytesRead = resampler.Read(buffer, 0, buffer.Length)) > 0)
    {
        samples.AddRange(buffer.Take(bytesRead));
    }
    return samples.ToArray();
}

3. Frame the Audio Signal

PRH uses:

  • Frame length: 31.25ms = 250 samples (at 8kHz)
  • Frame shift: 10ms = 80 samples (170 samples overlap between frames)
  • Hamming window to reduce spectral leakage
using MathNet.Numerics.Signals;
using System.Collections.Generic;

public List<float[]> GetFramedSamples(float[] audioSamples)
{
    const int sampleRate = 8000;
    const float frameDurationMs = 31.25f;
    const float frameShiftMs = 10f;

    int frameSize = (int)(sampleRate * frameDurationMs / 1000);
    int frameShift = (int)(sampleRate * frameShiftMs / 1000);
    var frames = new List<float[]>();

    // Generate Hamming window
    var window = Window.Hamming(frameSize);

    for (int i = 0; i + frameSize <= audioSamples.Length; i += frameShift)
    {
        var frame = audioSamples.Skip(i).Take(frameSize).ToArray();
        // Apply window to the frame
        for (int j = 0; j < frameSize; j++)
        {
            frame[j] *= window[j];
        }
        frames.Add(frame);
    }
    return frames;
}

4. Compute Frequency Band Energies

Calculate the FFT for each frame, then split the 0-3kHz frequency range into 33 logarithmically spaced bands (as specified in Haitsma’s paper). Compute the energy (sum of squared magnitudes) for each band.

using MathNet.Numerics.IntegralTransforms;
using MathNet.Numerics;
using System.Collections.Generic;

public List<double[]> GetBandEnergies(List<float[]> frames)
{
    const int sampleRate = 8000;
    const int numBands = 33;
    var bandEnergies = new List<double[]>();

    foreach (var frame in frames)
    {
        // Convert frame to complex numbers for FFT
        var complexFrame = frame.Select(x => new Complex(x, 0)).ToArray();
        // Compute forward FFT (Matlab-compatible)
        Fourier.Forward(complexFrame, FourierOptions.Matlab);

        // Get max frequency bin for 3kHz
        int maxFreqBin = (int)(3000 * complexFrame.Length / sampleRate);
        // Generate logarithmic band edges
        var bandEdges = GenerateLogarithmicBands(0, maxFreqBin, numBands);

        // Calculate energy per band
        var energies = new double[numBands];
        for (int b = 0; b < numBands; b++)
        {
            int startBin = bandEdges[b];
            int endBin = bandEdges[b + 1];
            double energy = 0;
            for (int bin = startBin; bin < endBin; bin++)
            {
                energy += complexFrame[bin].MagnitudeSquared;
            }
            energies[b] = energy;
        }
        bandEnergies.Add(energies);
    }
    return bandEnergies;
}

// Helper to generate logarithmic band edges
private int[] GenerateLogarithmicBands(int minBin, int maxBin, int numBands)
{
    var edges = new int[numBands + 1];
    edges[0] = minBin;
    edges[numBands] = maxBin;

    // Logarithmic spacing to match human auditory perception
    double logMin = Math.Log10(minBin + 1);
    double logMax = Math.Log10(maxBin);
    double step = (logMax - logMin) / numBands;

    for (int i = 1; i < numBands; i++)
    {
        edges[i] = (int)Math.Pow(10, logMin + step * i) - 1;
    }
    return edges;
}

5. Calculate Differential Energies

PRH uses two differential values per band:

  • Δ1(t, i) = E(t, i) - E(t-1, i) (current vs previous frame)
  • Δ2(t, i) = E(t, i) - E(t-2, i) (current vs two frames prior)

We can only generate fingerprints once we have at least 3 frames.

using System.Collections.Generic;
using System;

public List<Tuple<double[], double[]>> GetDifferentialEnergies(List<double[]> bandEnergies)
{
    var diffs = new List<Tuple<double[], double[]>>();
    // Start from index 2 (need t-2, t-1, t frames)
    for (int t = 2; t < bandEnergies.Count; t++)
    {
        var delta1 = new double[bandEnergies[t].Length];
        var delta2 = new double[bandEnergies[t].Length];
        for (int i = 0; i < bandEnergies[t].Length; i++)
        {
            delta1[i] = bandEnergies[t][i] - bandEnergies[t-1][i];
            delta2[i] = bandEnergies[t][i] - bandEnergies[t-2][i];
        }
        diffs.Add(Tuple.Create(delta1, delta2));
    }
    return diffs;
}

6. Generate 32-bit Fingerprints

Compare differential values across 32 non-overlapping band pairs. For each pair (i,j), set a bit to 1 if both Δ1(i) > Δ1(j) AND Δ2(i) > Δ2(j); otherwise 0.

using System.Collections.Generic;

// 32 band pairs aligned with PRH specifications
private static readonly (int i, int j)[] BandPairs = new (int, int)[]
{
    (0,1),(2,3),(4,5),(6,7),(8,9),(10,11),(12,13),(14,15),
    (16,17),(18,19),(20,21),(22,23),(24,25),(26,27),(28,29),(30,31),
    (0,2),(1,3),(4,6),(5,7),(8,10),(9,11),(12,14),(13,15),
    (16,18),(17,19),(20,22),(21,23),(24,26),(25,27),(28,30),(29,31)
};

public List<uint> GenerateFingerprints(List<Tuple<double[], double[]>> differentialEnergies)
{
    var fingerprints = new List<uint>();
    foreach (var (delta1, delta2) in differentialEnergies)
    {
        uint fingerprint = 0;
        for (int bitIndex = 0; bitIndex < BandPairs.Length; bitIndex++)
        {
            var (i, j) = BandPairs[bitIndex];
            bool bitSet = (delta1[i] > delta1[j]) && (delta2[i] > delta2[j]);
            if (bitSet)
            {
                fingerprint |= (uint)1 << bitIndex;
            }
        }
        fingerprints.Add(fingerprint);
    }
    return fingerprints;
}

Key Guidance & Tips

  • Debugging: Start with short 10-30 second audio clips and print intermediate values (frame energies, deltas) to verify consistency. Double-check that your logarithmic band edges match the paper’s specs.
  • Performance: For large audio files, process frames in batches instead of loading the entire file into memory. Reuse complex arrays for FFT to reduce memory overhead.
  • Robustness: Test with distorted, compressed, or noisy versions of your audio to ensure fingerprints remain consistent. PRH is designed for robustness, but proper preprocessing is critical.
  • Paper Reference: Keep Haitsma’s paper handy—if you run into issues, cross-verify frame sizes, band spacing, and differential logic against the original specs.

内容的提问来源于stack exchange,提问作者yuno

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:43:03