최근 음성 합성 프로젝트를 진행하면서 PCM 형식의 오디오 파일을 MP3나 WAV 형식으로 변환해야 할 필요가 있었습니다. 이 과정을 기록해 둡니다.
Java를 이용한 PCM 오디오 파일을 MP3 형식으로 변환
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* PCM을 MP3로 변환하는 클래스
*
* @author 기술 개발자
* @since 2023-05-15
*/
public class AudioFormatConverter {
public static void main(String[] args) throws Exception {
//convertAudioFiles("audio/sample.pcm", "audio/sample.mp3");
convertPcmToMp3("test.pcm", "test.mp3");
}
/**
* PCM 파일을 MP3 형식으로 변환
*
* @param sourcePath 원본 PCM 파일 경로
* @param targetPath 대상 MP3 파일 경로
* @throws IOException 입출력 예외 처리
*/
public static String convertPcmToMp3(String sourcePath, String targetPath) throws IOException {
FileInputStream audioInput = new FileInputStream(sourcePath);
FileOutputStream audioOutput = new FileOutputStream(targetPath);
// 파일 크기 계산
byte[] buffer = new byte[4096];
int bytesRead = audioInput.read(buffer);
int pcmDataSize = 0;
while (bytesRead != -1) {
pcmDataSize += bytesRead;
bytesRead = audioInput.read(buffer);
}
audioInput.close();
// 헤더 정보 설정 (16비트 모노, 16000Hz)
AudioHeader headerInfo = new AudioHeader();
headerInfo.fileSize = pcmDataSize + (44 - 8);
headerInfo.formatHeaderLength = 16;
headerInfo.bitsPerSample = 16;
headerInfo.channelCount = 1;
headerInfo.formatTag = 0x0001;
headerInfo.sampleRate = 16000; // 표준 속도는 8000Hz, 여기서는 2배 빠른 속도
headerInfo.blockAlignment = (short) (headerInfo.channelCount * headerInfo.bitsPerSample / 8);
headerInfo.averageBytesPerSecond = headerInfo.blockAlignment * headerInfo.sampleRate;
headerInfo.dataHeaderLength = pcmDataSize;
byte[] headerData = headerInfo.getHeader();
// WAV 헤더는 44바이트여야 함
assert headerData.length == 44;
// 헤더 작성
audioOutput.write(headerData, 0, headerData.length);
// 데이터 스트림 작성
audioInput = new FileInputStream(sourcePath);
bytesRead = audioInput.read(buffer);
while (bytesRead != -1) {
audioOutput.write(buffer, 0, bytesRead);
bytesRead = audioInput.read(buffer);
}
audioInput.close();
audioOutput.close();
System.out.println("PCM에서 MP3 변환 완료!");
return "성공";
}
}
Java를 이용한 PCM 오디오 파일을 WAV 형식으로 변환
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* PCM을 WAV로 변환하는 클래스
*
* @author 기술 개발자
* @since 2023-05-15
*/
public class PcmToWavConverter {
public static void main(String[] args) throws Exception {
//convertAudioFiles("audio/sample.pcm", "audio/sample.wav");
convertPcmToWav("test.pcm", "test.wav");
}
/**
* PCM 파일을 WAV 형식으로 변환
*
* @param sourcePath 원본 PCM 파일 경로
* @param targetPath 대상 WAV 파일 경로
* @throws IOException 입출력 예외 처리
*/
public static void convertPcmToWav(String sourcePath, String targetPath) throws IOException {
FileInputStream audioInput = new FileInputStream(sourcePath);
FileOutputStream audioOutput = new FileOutputStream(targetPath);
// 파일 크기 계산
byte[] buffer = new byte[4096];
int bytesRead = audioInput.read(buffer);
int pcmDataSize = 0;
while (bytesRead != -1) {
pcmDataSize += bytesRead;
bytesRead = audioInput.read(buffer);
}
audioInput.close();
// 헤더 정보 설정 (16비트 스테레오, 8000Hz)
AudioHeader headerInfo = new AudioHeader();
headerInfo.fileSize = pcmDataSize + (44 - 8);
headerInfo.formatHeaderLength = 16;
headerInfo.bitsPerSample = 16;
headerInfo.channelCount = 2;
headerInfo.formatTag = 0x0001;
headerInfo.sampleRate = 8000;
headerInfo.blockAlignment = (short) (headerInfo.channelCount * headerInfo.bitsPerSample / 8);
headerInfo.averageBytesPerSecond = headerInfo.blockAlignment * headerInfo.sampleRate;
headerInfo.dataHeaderLength = pcmDataSize;
byte[] headerData = headerInfo.getHeader();
// WAV 헤더는 44바이트여야 함
assert headerData.length == 44;
// 헤더 작성
audioOutput.write(headerData, 0, headerData.length);
// 데이터 스트림 작성
audioInput = new FileInputStream(sourcePath);
bytesRead = audioInput.read(buffer);
while (bytesRead != -1) {
audioOutput.write(buffer, 0, bytesRead);
bytesRead = audioInput.read(buffer);
}
audioInput.close();
audioOutput.close();
System.out.println("변환 완료!");
}
}
<strong>AudioHeader 클래스<br></br></strong>
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* WAV 오디오 헤더 생성 클래스
*
* @author 기술 개발자
* @since 2023-05-15
*/
public class AudioHeader {
public final char riffIdentifier[] = {'R', 'I', 'F', 'F'};
public int fileSize;
public char waveTag[] = {'W', 'A', 'V', 'E'};
public char formatHeaderId[] = {'f', 'm', 't', ' '};
public int formatHeaderLength;
public short formatCode;
public short channelCount;
public int sampleRate;
public int averageBytesPerSecond;
public short blockAlignment;
public short bitsPerSample;
public char dataHeaderId[] = {'d', 'a', 't', 'a'};
public int dataHeaderLength;
public byte[] getHeader() throws IOException {
ByteArrayOutputStream headerStream = new ByteArrayOutputStream();
writeChars(headerStream, riffIdentifier);
writeInteger(headerStream, fileSize);
writeChars(headerStream, waveTag);
writeChars(headerStream, formatHeaderId);
writeInteger(headerStream, formatHeaderLength);
writeShort(headerStream, formatCode);
writeShort(headerStream, channelCount);
writeInteger(headerStream, sampleRate);
writeInteger(headerStream, averageBytesPerSecond);
writeShort(headerStream, blockAlignment);
writeShort(headerStream, bitsPerSample);
writeChars(headerStream, dataHeaderId);
writeInteger(headerStream, dataHeaderLength);
headerStream.flush();
byte[] headerBytes = headerStream.toByteArray();
headerStream.close();
return headerBytes;
}
private void writeShort(ByteArrayOutputStream stream, int value) throws IOException {
byte[] shortBytes = new byte[2];
shortBytes[1] = (byte) ((value << 16) >> 24);
shortBytes[0] = (byte) ((value << 24) >> 24);
stream.write(shortBytes);
}
private void writeInteger(ByteArrayOutputStream stream, int number) throws IOException {
byte[] intBytes = new byte[4];
intBytes[3] = (byte) (number >> 24);
intBytes[2] = (byte) ((number << 8) >> 24);
intBytes[1] = (byte) ((number << 16) >> 24);
intBytes[0] = (byte) ((number << 24) >> 24);
stream.write(intBytes);
}
private void writeChars(ByteArrayOutputStream stream, char[] characters) {
for (char c : characters) {
stream.write(c);
}
}
}