首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 音頻波形圖繪製方案:從 PCM 數據到可視化元件實作

     編輯:Java 相關知識

需求澄清

原文件內容為 Perl 變數作用域範例,與標題「Java 顯示聲響波形圖」完全不符。本文提供正確的 Java 音頻波形繪製方案,涵蓋 PCM 數據讀取、波形計算、Swing/JavaFX 繪製、大數據優化。

核心流程

  1. 解碼音頻:MP3/WAV → PCM (signed 16-bit little-endian) 陣列。
  2. 降採樣/峰值提取:螢幕像素有限(如 1000px),需將幾十萬採樣點壓縮為每像素一個 min/max 對。
  3. 繪製:對稱繪製正負振幅,或單向填充面積圖。

依賴庫推薦

  • TarsosDSP:純 Java 音頻解碼、特徵提取(Maven: be.tarsos:dsp:2.5)。
  • JLayer / MP3SPI:舊款 MP3 解碼。
  • JavaFX Media:內建解碼,但格式支援有限。
  • FFmpeg + ProcessBuilder:最強相容性,解碼為 WAV 再讀取。

完整 Swing 波形面板實作

package com.example.audio;

import javax.sound.sampled.*;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Path2D;
import java.io.File;
import java.io.IOException;

public class WaveformPanel extends JPanel {

    private short[] pcmData;      // 原始 PCM 數據
    private int[] minMaxCache;    // 降採樣後的 [min, max] 陣列,長度 = 寬度 * 2
    private float zoom = 1.0f;    // 水平縮放
    private int startSample = 0;  // 可見區域起始採樣點

    public void loadAudio(File file) throws UnsupportedAudioFileException, IOException {
        try (AudioInputStream ais = AudioSystem.getAudioInputStream(file)) {
            AudioFormat fmt = ais.getFormat();
            // 強制轉為 16-bit PCM
            if (fmt.getEncoding() != AudioFormat.Encoding.PCM_SIGNED || fmt.getSampleSizeInBits() != 16) {
                ais = AudioSystem.getAudioInputStream(
                    new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, fmt.getSampleRate(), 16,
                        fmt.getChannels(), fmt.getChannels() * 2, fmt.getSampleRate(), false), ais);
            }
            byte[] bytes = ais.readAllBytes();
            pcmData = new short[bytes.length / 2];
            for (int i = 0; i < pcmData.length; i++) {
                pcmData[i] = (short) ((bytes[i*2+1] << 8) | (bytes[i*2] & 0xFF)); // little-endian
            }
            buildCache();
            repaint();
        }
    }

    /** 建立降採樣快取:每像素對應一組 min/max */
    private void buildCache() {
        if (pcmData == null) return;
        int w = Math.max(1, getWidth());
        minMaxCache = new int[w * 2];
        int samplesPerPx = Math.max(1, pcmData.length / w);
        for (int px = 0; px < w; px++) {
            int start = px * samplesPerPx;
            int end = Math.min(start + samplesPerPx, pcmData.length);
            short min = 32767, max = -32768;
            for (int i = start; i < end; i++) {
                short v = pcmData[i];
                if (v < min) min = v;
                if (v > max) max = v;
            }
            minMaxCache[px*2] = min;
            minMaxCache[px*2+1] = max;
        }
    }

    @Override protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (minMaxCache == null) return;

        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(new Color(30, 144, 255)); // DodgerBlue

        int h = getHeight();
        int mid = h / 2;
        float scale = (h * 0.45f) / 32768f; // 留 10% 邊距

        Path2D.Float path = new Path2D.Float();
        int w = getWidth();

        // 上半部(正振幅)
        path.moveTo(0, mid);
        for (int px = 0; px < w; px++) {
            int max = minMaxCache[px*2+1];
            float y = mid - max * scale;
            path.lineTo(px, y);
        }
        // 下半部(負振幅,鏡像)
        for (int px = w-1; px >= 0; px--) {
            int min = minMaxCache[px*2];
            float y = mid - min * scale; // min 為負,y > mid
            path.lineTo(px, y);
        }
        path.closePath();
        g2.fill(path);
        g2.draw(path);

        // 中央軸
        g2.setColor(Color.GRAY);
        g2.drawLine(0, mid, w, mid);
    }

    @Override public Dimension getPreferredSize() {
        return new Dimension(800, 200);
    }

    // 主程式測試
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame f = new JFrame("Audio Waveform");
            WaveformPanel panel = new WaveformPanel();
            f.add(panel);
            f.pack();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setLocationRelativeTo(null);
            f.setVisible(true);

            // 載入測試檔案
            try { panel.loadAudio(new File("test.wav")); }
            catch (Exception e) { e.printStackTrace(); }
        });
    }
}

大檔案優化策略

策略說明
記憶體映射用 FileChannel.map() 讀取大檔,避免全量載入堆內存。
多解析度快取預計算多層級 min/max 金字塔(如 1x, 2x, 4x...),縮放時 O(1) 切換。
虛擬化繪製僅繪製可見區域採樣點,配合 AffineTransform 平移縮放。
背景執行緒解碼SwingWorker 或 JavaFX Task 非阻塞載入,載入中顯示進度條。

JavaFX 版本優勢

  • 硬體加速渲染(Canvas / Path),大數據流暢度遠超 Swing。
  • 內建 AudioSpectrumListener 可即時繪製頻譜/波形。
  • CSS 樣式化、動畫原生支援。
// JavaFX 簡易波形繪製片段
Canvas canvas = new Canvas(800, 200);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.DODGERBLUE);
for (int px = 0; px < width; px++) {
    double max = cache[px*2+1] / 32768.0;
    double min = cache[px*2] / 32768.0;
    double mid = height/2;
    gc.fillRect(px, mid - max*mid*0.9, 1, (max-min)*mid*0.9);
}

常見格式支援對照

格式Java SoundTarsosDSPFFmpeg
WAV (PCM)✅ 原生✅✅
MP3❌ 需 MP3SPI✅✅
AAC/M4A❌❌✅
FLAC❌❌✅
OGG Vorbis❌✅✅
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved