앱 내에서 녹음 파일은 초기에 .pcm 형식으로 저장되었습니다. 사용에 아무런 문제도 없었지만 최근 웹뷰에서 오디오 파일을 로드할 때 호환성 문제가 발생했습니다. 이에 .pcm 형식 파일을 .wav 형식으로 전환할 필요가 생겼습니다.
PCM
PCM(Pulse Code Modulation - 펄스 코드 모듈레이션)은 음성과 같은 아날로그 신호를 상징화된 펄스 세트로 전환하는 방법입니다. PCM 신호는 [1], [0]와 같은 상징으로 구성된 디지털 신호로, 압축이나 인코딩을 거치지 않습니다. 이는 전송 시스템의 잡음 및 왜곡으로부터 보호받는다는 장점이 있습니다. 또한 폭넓은 dynamic range를 제공하며 품질이 우수한 음질을 얻을 수 있습니다. 간단히 말해 PCM은 압축되지 않은 오디오 형식입니다.
WAV
WAV는 WAVE라는 약자로, .wav가 그 확장명입니다. 이는 손실없는 오디오 파일 형식으로 RIFF(Resource Interchange File Format) 규격을 따릅니다. 모든 WAV 파일은 하나의 파일 헤더를 가지고 있습니다. 이 헤더는 오디오 스트림의 인코딩 매개변수를 포함합니다. WAV는 PCM 외에도几乎所有 ACM 규격에 따른 인코딩 방법을 사용할 수 있습니다.
PCM과 WAV의 관계 PCM은 WAV 파일 내의 오디오 데이터의 한 가지 인코딩 방법입니다. PCM에 파일 헤더를 추가하면 WAV 형식으로 전환할 수 있습니다. 그러나 WAV는 PCM 외에도 다른 인코딩 방법을 사용할 수 있습니다.
PcmToWavUtil
package com.hgb.mytest;
import android.media.AudioFormat;
import android.media.AudioRecord;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* PCM 형식의 오디오를 WAV 형식으로 전환하는 유틸리티 클래스
*/
public class PcmToWavUtil {
private int bufferSize; // 버퍼 크기
private int sampleRate = 8000; // 8000|16000
private int channel = AudioFormat.CHANNEL_IN_STEREO; // 스테레오
private int encoding = AudioFormat.ENCODING_PCM_16BIT;
public PcmToWavUtil() {
this.bufferSize = AudioRecord.getMinBufferSize(sampleRate, channel, encoding);
}
/**
* @param sampleRate 샘플레이트
* @param channel 채널 수
* @param encoding 오디오 데이터 형식
*/
public PcmToWavUtil(int sampleRate, int channel, int encoding) {
this.sampleRate = sampleRate;
this.channel = channel;
this.encoding = encoding;
this.bufferSize = AudioRecord.getMinBufferSize(sampleRate, channel, encoding);
}
/**
* PCM 파일을 WAV 파일로 전환
*
* @param inFilename 원본 파일 경로
* @param outFilename 대상 파일 경로
*/
public void pcmToWav(String inFilename, String outFilename) {
FileInputStream in;
FileOutputStream out;
long totalAudioLen;
long totalDataLen;
long byteRate;
int channels = 2;
byte[] data = new byte[bufferSize];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
byteRate = 16 * sampleRate * channels / 8;
writeWaveFileHeader(out, totalAudioLen, totalDataLen,
sampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* WAV 파일 헤더 쓰기
*/
private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
long totalDataLen, long sampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
header[0] = 'R'; // RIFF/WAVE 헤더
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W'; // WAVE
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 'fmt ' chunk 크기
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // 형식 = 1
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (sampleRate & 0xff);
header[25] = (byte) ((sampleRate >> 8) & 0xff);
header[26] = (byte) ((sampleRate >> 16) & 0xff);
header[27] = (byte) ((sampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
header[32] = (byte) (2 * 16 / 8); // block align
header[33] = 0;
header[34] = 16; // 샘플당 비트 수
header[35] = 0;
header[36] = 'd'; // data chunk
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
}
** демо 예제 **
휴대폰의 내장 카드 /sdcard/yxck/treamentRecord/ 디렉토리에 있는 123.pcm 파일을 123.wav로 전환합니다.
public class MainActivity extends AppCompatActivity {
private PcmToWavUtil pcmToWavUtil = new PcmToWavUtil();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final String path = "/sdcard/yxck/treamentRecord/123.pcm";
final String outpath = path.replace(".pcm", ".wav");
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
pcmToWavUtil.pcmToWav(path, outpath);
}
});
}
}
** 결과 **
PCM를 WAV로 성공적으로 변환되었습니다.