Android平台使用JTransform库实现PCM数据频谱柱状图可视化咨询
Got it, let's walk through how to turn your PCM data into frequency band data for that bar chart using JTransforms—this is exactly the kind of thing I’ve tinkered with for audio visualizers before. Here's a step-by-step breakdown with code examples to make it concrete:
First, you need to get the library into your project. If you're using Maven, add this dependency to your pom.xml:
<dependency> <groupId>com.github.wendykierp</groupId> <artifactId>JTransforms</artifactId> <version>3.1</version> </dependency>
If you're not using a build tool, just download the JAR from the Maven repository and add it to your project's classpath.
PCM data is usually stored as a byte array (16-bit signed is the most common format). JTransforms' FFT works with double arrays, so you’ll need to convert and normalize your PCM samples:
- For 16-bit PCM, each sample is 2 bytes. Combine them into a signed short, then normalize to the range
[-1.0, 1.0]by dividing by32768.0(the max value of a 16-bit signed short).
Here’s a helper method for this conversion:
public static double[] pcmBytesToDoubles(byte[] pcmBytes) { int sampleCount = pcmBytes.length / 2; double[] normalizedSamples = new double[sampleCount]; for (int i = 0; i < sampleCount; i++) { // Combine two bytes into a signed short (little-endian, adjust if your PCM is big-endian) short rawSample = (short) ((pcmBytes[2*i] & 0xFF) | (pcmBytes[2*i+1] << 8)); // Normalize to [-1.0, 1.0] normalizedSamples[i] = rawSample / 32768.0; } return normalizedSamples; }
JTransforms uses a DoubleFFT_1D class for 1-dimensional FFTs. You’ll need to:
- Choose a sample window size (common choices are 1024, 2048, or 4096—bigger sizes give better frequency resolution but slower updates).
- Convert your normalized PCM samples into a complex array (JTransforms expects real and imaginary parts interleaved:
[real0, imag0, real1, imag1, ...]). - Run the forward FFT.
Example code:
// Configuration (adjust these to match your audio setup) int sampleWindowSize = 4096; double sampleRate = 44100; // e.g., 44.1kHz for CD-quality audio // Get your normalized PCM data (make sure it's exactly sampleWindowSize long—pad or truncate if needed) double[] pcmSamples = pcmBytesToDoubles(yourPcmByteArray); // Initialize FFT transformer DoubleFFT_1D fft = new DoubleFFT_1D(sampleWindowSize); // Prepare complex input array (imaginary parts start at 0) double[] complexInput = new double[2 * sampleWindowSize]; System.arraycopy(pcmSamples, 0, complexInput, 0, sampleWindowSize); // Execute FFT fft.complexForward(complexInput);
The FFT output gives you complex numbers for each frequency bin. To get a readable amplitude value, calculate the magnitude of each complex number (sqrt(real² + imag²)). Also, since FFT results are symmetric, you only need the first half of the bins (the second half is the mirror of the first).
Optional: Convert magnitudes to decibels (dB) for a more natural visualization (this matches how human hearing perceives loudness):
double[] magnitudes = new double[sampleWindowSize / 2]; double[] dbMagnitudes = new double[magnitudes.length]; for (int i = 0; i < sampleWindowSize / 2; i++) { double real = complexInput[2 * i]; double imag = complexInput[2 * i + 1]; // Calculate raw magnitude magnitudes[i] = Math.sqrt(real * real + imag * imag); // Convert to dB (normalize by window size to account for FFT scaling) dbMagnitudes[i] = 20 * Math.log10(magnitudes[i] / sampleWindowSize); // Clip very quiet values to avoid -infinity (adjust -60 to your needs) if (dbMagnitudes[i] < -60) { dbMagnitudes[i] = -60; } }
A bar chart usually doesn’t show every single frequency bin—you’ll want to group bins into logical bands (like octave bands or custom ranges). For example, you might use bands like 20-60Hz, 60-200Hz, up to 6kHz-20kHz.
Here’s how to calculate the average magnitude for each band:
// Define your desired frequency bands (customize these to match your chart) String[] bandLabels = {"20-60Hz", "60-200Hz", "200-600Hz", "600-2kHz", "2kHz-6kHz", "6kHz-20kHz"}; double[] bandEdges = {20, 60, 200, 600, 2000, 6000, 20000}; // Calculate Hz per bin (frequency resolution) double binFrequencyStep = sampleRate / sampleWindowSize; double[] bandAmplitudes = new double[bandLabels.length]; for (int bandIdx = 0; bandIdx < bandLabels.length; bandIdx++) { double startFreq = bandEdges[bandIdx]; double endFreq = bandEdges[bandIdx + 1]; // Find the first and last bin in this band int startBin = (int) Math.floor(startFreq / binFrequencyStep); int endBin = (int) Math.ceil(endFreq / binFrequencyStep); // Clamp bins to valid range (0 to sampleWindowSize/2 - 1) startBin = Math.max(0, startBin); endBin = Math.min(magnitudes.length - 1, endBin); // Calculate average amplitude for the band double total = 0; int binCount = 0; for (int bin = startBin; bin <= endBin; bin++) { total += dbMagnitudes[bin]; // Use magnitudes if you don't want dB binCount++; } bandAmplitudes[bandIdx] = binCount > 0 ? total / binCount : 0; }
Now you have two arrays ready for plotting:
bandLabels: The x-axis labels for your bars (e.g., "20-60Hz")bandAmplitudes: The y-axis values (dB or raw magnitude) for each bar
You can use any Java plotting library (JavaFX Charts, Swing's JFreeChart, etc.) to render these into a bar chart. For example, with JavaFX:
// Quick JavaFX example (simplified) CategoryAxis xAxis = new CategoryAxis(); xAxis.setCategories(FXCollections.observableArrayList(bandLabels)); NumberAxis yAxis = new NumberAxis(); yAxis.setLabel("Amplitude (dB)"); BarChart<String, Number> barChart = new BarChart<>(xAxis, yAxis); XYChart.Series<String, Number> series = new XYChart.Series<>(); series.setName("Frequency Spectrum"); for (int i = 0; i < bandLabels.length; i++) { series.getData().add(new XYChart.Data<>(bandLabels[i], bandAmplitudes[i])); } barChart.getData().add(series);
A few quick tips to refine your visualization:
- Use a window function (like Hann) on your PCM samples before FFT to reduce spectral leakage. JTransforms has built-in support for this, or you can apply it manually.
- Adjust the sample window size: bigger windows give more precise frequency data but slower updates; smaller windows are more responsive but less detailed.
- Normalize the y-axis so your bars don’t go off-screen (e.g., set the range from -60dB to 0dB if using dB).
内容的提问来源于stack exchange,提问作者Robin

