Androidで録音

android録音アプリ開発

androidで録音できるようになったのでメモ

○権限追加
 androidで録音をする場合以下2つの権限を追加する。

 <uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

○録音
アンドロイドで録音処理を行う場合、MediaRecorderとAudioRecordの2つのクラスが用意されている。
 MediaRecorder:ファイルへの書き込みが前提になっている。
 AudioRecord:音声の入力情報を取得してリアルタイムの処理を行える。ファイルへの保存を行う場合は、自前でフォーマットに合わせた書き込みを行わなければ行けない。

音声認識を処理を行う場合はAudioRecordクラスのリアルタイム処理が処理が向いている。


○MediaRecordクラスを使った録音
以下がサンプルになる。音声認識の処理を行えるよう無圧縮状態のWAVファイルで保存している。

//録音
private MediaRecorder mediarecorder; //録音用のメディアレコーダークラス
static final String filePath = "/sdcard/sampleWav.wav"; //録音用のファイルパス

private void startMediaRecord(){
    try{
    	File mediafile = new File(filePath);
        if(mediafile.exists()) {
        	//ファイルが存在する場合は削除する
            mediafile.delete();
        }
        mediafile = null;
        mediarecorder = new MediaRecorder();
        //マイクからの音声を録音する
        mediarecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        //ファイルへの出力フォーマット DEFAULTにするとwavが扱えるはず
        mediarecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
        //音声のエンコーダーも合わせてdefaultにする
        mediarecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
        //ファイルの保存先を指定
        mediarecorder.setOutputFile(filePath);
        //録音の準備をする
        mediarecorder.prepare();
        //録音開始
        mediarecorder.start();
    } catch(Exception e){
        e.printStackTrace();
    }
}

//停止
private void stopRecord(){
	if(mediarecorder == null){
		Toast.makeText(MainActivity.this, "mediarecorder = null", Toast.LENGTH_SHORT).show();
	}else{
		try{
    		//録音停止
    		mediarecorder.stop();
    		mediarecorder.reset();
		}catch (Exception e){
			e.printStackTrace();
		}
	}
}

○AudioRecordを使った録音
AudioRecordクラスは音声の入力情報を取得することができ、リアルタイムの音声処理に向いている。自前でWAVE形式のファイル書き込み用クラスを用意し、AudioRecordクラスのイベント発生時に書き込みを行うだけでファイルを作成できるようにした。WAVE形式のファイルのフォーマットについては"http://www.kk.iij4u.or.jp/~kondo/wave/"が参考になる。

以下WAVE形式でファイルを保存するためのクラスになる。このクラスを使用してAudioRecordの情報を書き込むだけでWAVEファイルが作成できる。

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class MyWaveFile {
	private final int FILESIZE_SEEK = 4;
	private final int DATASIZE_SEEK = 40;

	private RandomAccessFile raf; //リアルタイム処理なのでランダムアクセスファイルクラスを使用する
	private File recFile; //録音後の書き込み、読み込みようファイル
	private String fileName = "/sdcard/teruuuSound.wav"; //録音ファイルのパス
	private byte[] RIFF = {'R','I','F','F'}; //wavファイルリフチャンクに書き込むチャンクID用
	private int fileSize = 36;
	private byte[] WAVE = {'W','A','V','E'}; //WAV形式でRIFFフォーマットを使用する
	private byte[] fmt = {'f','m','t',' '}; //fmtチャンク スペースも必要
	private int fmtSize = 16; //fmtチャンクのバイト数
	private byte[] fmtID = {1, 0}; // フォーマットID リニアPCMの場合01 00 2byte
	private short chCount = 1; //チャネルカウント モノラルなので1 ステレオなら2にする
	private int bytePerSec = SoundDefine.SAMPLING_RATE * (fmtSize / 8) * chCount; //データ速度
	private short blockSize = (short) ((fmtSize / 8) * chCount); //ブロックサイズ (Byte/サンプリングレート * チャンネル数)
	private short bitPerSample = 16; //サンプルあたりのビット数 WAVでは8bitか16ビットが選べる
	private byte[] data = {'d', 'a', 't', 'a'}; //dataチャンク
	private int dataSize = 0; //波形データのバイト数


	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(SoundDefine.SAMPLING_RATE)); //サンプリング周波数
			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();
		}

	}


	 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;

	 }

	// 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;
	 }


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

それからWAVEファイルクラスを使ってAudioRecordクラスから取得した情報を書き込む処理は以下になる。

AudioRecord audioRecord; //録音用のオーディオレコードクラス
final static int SAMPLING_RATE = 44100; //オーディオレコード用サンプリング周波数
private int bufSize;//オーディオレコード用バッファのサイズ
private short[] shortData; //オーディオレコード用バッファ
private MyWaveFile wav1 = new MyWaveFile();

//AudioRecordの初期化
private void initAudioRecord(){
	wav1.createFile(SoundDefine.filePath);
	// AudioRecordオブジェクトを作成
	bufSize = android.media.AudioRecord.getMinBufferSize(SAMPLING_RATE,
				 AudioFormat.CHANNEL_IN_MONO,
				 AudioFormat.ENCODING_PCM_16BIT);
	audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
	   SAMPLING_RATE,
	   AudioFormat.CHANNEL_IN_MONO,
	   AudioFormat.ENCODING_PCM_16BIT,
	   bufSize);

	shortData = new short[bufSize / 2];

	// コールバックを指定
	audioRecord.setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() {
		// フレームごとの処理
		@Override
		public void onPeriodicNotification(AudioRecord recorder) {
			// TODO Auto-generated method stub
			audioRecord.read(shortData, 0, bufSize / 2); // 読み込む
			wav1.addBigEndianData(shortData); // ファイルに書き出す
		}

		@Override
		public void onMarkerReached(AudioRecord recorder) {
			// TODO Auto-generated method stub

		}
	});
	// コールバックが呼ばれる間隔を指定
	audioRecord.setPositionNotificationPeriod(bufSize / 2);
}

/**
 * オーディオレコード用のボタン初期化
 */
private void initAudioRecordButton(){
	startButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
        	startAudioRecord();
        }
    });

    stopButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
        	stopAudioRecord();
        }
    });

    playButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
        	playRecord();
        }
    });
}

private void startAudioRecord(){
	audioRecord.startRecording();
	audioRecord.read(shortData, 0, bufSize/2);
}

//オーディオレコードを停止する
private void stopAudioRecord(){
	audioRecord.stop();
}