概要

 wavファイルを扱うためのクラスを作成した。2014/09/17 littleEndianShortメソッドを追記。


背景と目的

 以前作成したwavファイルを再生するアプリに続き、録音アプリを作成しようとしている。だが、録音しながらwavファイルを作成するための処理は、そこそこ複雑で、今後もよく使いそうなので専用のクラスを作ることにした。


詳細

1.構想

 扱えるファイルの仕様は、とりあえずサンプリング周波数44.1kHz、16ビット、モノラルのリニアPCMに限る。PCMデータは、後からどんどん追加して伸ばしていけるようにする。

2.実装
2.1 フィールド

 rafは、RandomAccessFileクラスのオブジェクト。ファイルの任意の位置に任意のデータを書き込むためにこのクラスを使う。recFileは、wavファイルのオブジェクト。その他は、wavファイルのヘッダの各要素に対応する。

 private final int FILESIZE_SEEK = 4;
 private final int DATASIZE_SEEK = 40;
 
 private RandomAccessFile raf;
 private File recFile;
 private String fileName = "test.wav";
 private byte[] RIFF = {'R','I','F','F'};
 private int fileSize = 36;
 private byte[] WAVE = {'W','A','V','E'};
 private byte[] fmt = {'f','m','t',' '};
 private int fmtSize = 16;
 private byte[] fmtID = {1, 0}; // 2byte
 private short chCount = 1;
 private int sampleRate = 44100;
 private int bytePerSec = 44100 * 2;
 private short blockSize = 2;
 private short bitPerSample = 16;
 private byte[] data = {'d', 'a', 't', 'a'};
 private int dataSize = 0;
2.2 コンストラクタ

 コンストラクタは、デフォルトだけにする。ソースコードは省略

2.3 createFileメソッド

 wavファイルを作成するためのメソッド。引数としてファイル名を与えて、新規ファイルを作成する。該当するファイルがあれば削除してから作成する。また、PCMデータのないヘッダだけを書き込んでおく。

public void createFile(String fName){
  
  fileName = fName;
  
  // ファイルを作成
  recFile = new File(fileName);
  if(recFile.exists()){
   recFile.delete();
  }
  try {
   recFile.createNewFile();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

  try {
   raf = new RandomAccessFile(recFile, "rw");
  } catch (FileNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
  // wavのヘッダを書き込み
  try {
   raf.seek(0);
   raf.write(RIFF);
   raf.write(littleEndianInteger(fileSize));
   raf.write(WAVE);
   raf.write(fmt);
   raf.write(littleEndianInteger(fmtSize));
   raf.write(fmtID);
   raf.write(littleEndianShort(chCount));
   raf.write(littleEndianInteger(sampleRate));
   raf.write(littleEndianInteger(bytePerSec));
   raf.write(littleEndianShort(blockSize));
   raf.write(littleEndianShort(bitPerSample));
   raf.write(data);
   raf.write(littleEndianInteger(dataSize));
   
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }

 }
 // int型をリトルエンディアンのbyte配列に変更
 private byte[] littleEndianInteger(int i){
  
  byte[] buffer = new byte[4];
  
  buffer[0] = (byte) i;
  buffer[1] = (byte) (i >> 8);
  buffer[2] = (byte) (i >> 16);
  buffer[3] = (byte) (i >> 24);
  
  return buffer;
  
 }
2.3 addBigEndianDataメソッド

 PCMデータをwavファイルに追記するメソッド。引数として与えるのはビッグエンディアンのshort型配列データとして、それをリトルエンディアンに並び替えて追記する。

// PCMデータを追記するメソッド
 public void addBigEndianData(short[] shortData){

  // ファイルにデータを追記
  try {
   raf.seek(raf.length());
   raf.write(littleEndianShorts(shortData));
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
  // ファイルサイズを更新
  updateFileSize();
  
  // データサイズを更新
  updateDataSize();
  
 }
 // ファイルサイズを更新
 private void updateFileSize(){
  
  fileSize = (int) (recFile.length() - 8);
  byte[] fileSizeBytes = littleEndianInteger(fileSize);
  try {
   raf.seek(FILESIZE_SEEK);
   raf.write(fileSizeBytes);
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }
 // データサイズを更新
 private void updateDataSize(){
  
  dataSize = (int) (recFile.length() - 44);
  byte[] dataSizeBytes = littleEndianInteger(dataSize);
  try {
   raf.seek(DATASIZE_SEEK);
   raf.write(dataSizeBytes);
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }
 // short型変数をリトルエンディアンのbyte配列に変更
	private byte[] littleEndianShort(short s){
		
		byte[] buffer = new byte[2];

		buffer[0] = (byte) s;
		buffer[1] = (byte) (s >> 8);

		return buffer;
		
	}
 // short型配列をリトルエンディアンのbyte配列に変更
 private byte[] littleEndianShorts(short[] s){
  
  byte[] buffer = new byte[s.length * 2];
  int i;
  
  for(i = 0; i < s.length; i++){
   buffer[2 * i] = (byte) s[i];
   buffer[2 * i + 1] = (byte) (s[i] >> 8);
  }

  return buffer;
  
 }
2.4 closeメソッド

 rafを閉じる。

 // ファイルを閉じる
 public void close(){
  
  try {
   raf.close();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
3.動作確認

 以下のようなテスト用プログラムで、動作確認したところwavファイルが作成されSoundEngineFreeで正しく波形が表示された。問題なさそうだ。

public class testWaveFile {

 public static void main(String[] args){
  // TODO Auto-generated constructor stub
  
  WaveFile wav1 = new WaveFile();
  int i;
  short[] d = new short[44];
  for(i=0;i

まとめと今後の課題

 wavファイルを扱うメソッドを作成できた。機能は非常に限られているが、今後徐々に追加すればよいので、とりあえずこれを使って録音アプリを作りたい。