如何在Java中保存经Sonic算法处理的音频为WAV格式
Got it, let's tackle this WAV saving issue with Sonic. Since playback works fine, your processed PCM audio data is valid— the problem is almost certainly missing a proper WAV file header (which defines critical metadata like sample rate, bit depth, and channel count) or using the wrong approach for writing the data to disk.
Let's break down the fix step by step:
1. Confirm Sonic's Output Audio Parameters
First, note the key parameters of the audio Sonic is producing— these need to match exactly what we put in the WAV header:
- Sample rate (e.g., 44100 Hz)
- Bit depth (e.g., 16 bits)
- Channel count (1 for mono, 2 for stereo)
Sonic typically preserves the input audio's parameters, so you can reuse the values from your source audio file.
2. Create a Reusable WAV Writer Utility
WAV files require a specific binary header structure before the raw PCM data. Here's a utility class to handle header generation and file writing:
import java.io.FileOutputStream; import java.io.IOException; public class WavFileWriter { // WAV header constants private static final int RIFF_HEADER_TOTAL_SIZE = 44; private static final byte[] RIFF_MARKER = {'R', 'I', 'F', 'F'}; private static final byte[] WAVE_MARKER = {'W', 'A', 'V', 'E'}; private static final byte[] FMT_SUBCHUNK_MARKER = {'f', 'm', 't', ' '}; private static final byte[] DATA_SUBCHUNK_MARKER = {'d', 'a', 't', 'a'}; private static final int PCM_AUDIO_FORMAT = 1; private final int sampleRate; private final int bitDepth; private final int channelCount; public WavFileWriter(int sampleRate, int bitDepth, int channelCount) { this.sampleRate = sampleRate; this.bitDepth = bitDepth; this.channelCount = channelCount; } public void writeWavFile(byte[] processedPcmData, String outputFilePath) throws IOException { long totalFileSize = RIFF_HEADER_TOTAL_SIZE + processedPcmData.length; long byteRate = sampleRate * channelCount * (bitDepth / 8); int blockAlign = channelCount * (bitDepth / 8); try (FileOutputStream fos = new FileOutputStream(outputFilePath)) { // Write RIFF chunk header fos.write(RIFF_MARKER); writeLittleEndianInt(fos, (int) (totalFileSize - 8)); fos.write(WAVE_MARKER); // Write fmt subchunk fos.write(FMT_SUBCHUNK_MARKER); writeLittleEndianInt(fos, 16); // Size of PCM format data writeLittleEndianShort(fos, (short) PCM_AUDIO_FORMAT); writeLittleEndianShort(fos, (short) channelCount); writeLittleEndianInt(fos, sampleRate); writeLittleEndianInt(fos, (int) byteRate); writeLittleEndianShort(fos, (short) blockAlign); writeLittleEndianShort(fos, (short) bitDepth); // Write data subchunk fos.write(DATA_SUBCHUNK_MARKER); writeLittleEndianInt(fos, processedPcmData.length); fos.write(processedPcmData); // Write raw processed PCM data } } // Helper to write little-endian int (WAV uses little-endian byte order) private void writeLittleEndianInt(FileOutputStream fos, int value) throws IOException { fos.write(value & 0xFF); fos.write((value >> 8) & 0xFF); fos.write((value >> 16) & 0xFF); fos.write((value >> 24) & 0xFF); } // Helper to write little-endian short private void writeLittleEndianShort(FileOutputStream fos, short value) throws IOException { fos.write(value & 0xFF); fos.write((value >> 8) & 0xFF); } }
3. Integrate with Your Sonic Processing Code
Assuming you're capturing all processed PCM data from Sonic (collect it incrementally if processing in chunks), use the utility to save the file:
public class Main { public static void main(String[] args) { // Match these to your source audio and Sonic's output int sampleRate = 44100; int bitDepth = 16; int channelCount = 1; // Mono, adjust to 2 for stereo // Your existing Sonic setup Sonic sonic = new Sonic(sampleRate, channelCount); sonic.setSpeed(1.5); // Example speed adjustment sonic.setPitch(1.2); // Example pitch adjustment // --- Process your audio here --- // Feed input audio bytes to Sonic, collect all output bytes // For example, if processing incrementally: ByteArrayOutputStream processedDataStream = new ByteArrayOutputStream(); // (Your code to read input audio, feed to Sonic, and write output to processedDataStream) byte[] processedPcmData = processedDataStream.toByteArray(); // Save to WAV WavFileWriter writer = new WavFileWriter(sampleRate, bitDepth, channelCount); try { writer.writeWavFile(processedPcmData, "processed_output.wav"); System.out.println("WAV file saved successfully!"); } catch (IOException e) { e.printStackTrace(); } } }
Why Your Previous Attempts Failed
- ByteArrayInputStream: If you wrote raw PCM data directly from this stream without adding the WAV header, media players wouldn't know how to decode the data (no metadata about sample rate, etc.). This leads to partial or distorted playback like "single words".
- TargetDataLine: This class is designed for capturing live audio input (like from a microphone), not writing pre-processed audio data to a file. It's the wrong tool for this use case.
Key Tips
- Make sure you collect all processed bytes from Sonic before writing— don't write partial chunks. Using
ByteArrayOutputStreamto accumulate data incrementally is a safe approach. - Double-check that the parameters passed to
WavFileWritermatch exactly what Sonic is producing. Mismatched values (e.g., wrong sample rate) will break playback even if the file saves successfully.
内容的提问来源于stack exchange,提问作者Hasitha Jayawardana

