Androboiceってアプリを作ったときのメモ

前回の続き。

で、Androidでどうやって録音するか。

AudioRecordクラスでは前回の音データが取得できる。以下コード。

// サンプルレート 8kHz
int SAMPLE_RATE = 8000;
// フラグ
isRecoding = true;
// プロセスの優先度を上げる
android.os.Process.setThreadPriority(
          android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
// バッファサイズを求める サンプルレート8kHz  モノラル 16ビット/サンプル
int audioBuf = AudioRecord.getMinBufferSize(SAMPLE_RATE,
                     AudioFormat.CHANNEL_CONFIGURATION_MONO,
                     AudioFormat.ENCODING_PCM_16BIT) * 2;
// レコーダの取得  サンプルレート8kHz  モノラル 16ビット/サンプル
AudioRecord audioRecord =  new AudioRecord(MediaRecorder.AudioSource.MIC,
                           SAMPLE_RATE,
                           AudioFormat.CHANNEL_CONFIGURATION_MONO,
                           AudioFormat.ENCODING_PCM_16BIT,
                           audioBuf);
// バッファ
byte[] buffer = new byte[audioBuf];
// 録音用ファイル取得  SDカード状態チェック略
File recFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/rec.raw");
recFile.createNewFile();
// ファイル書き込み  例外処理略
FileOutputStream out =  new FileOutputStream(recFile);
// 録音開始
audioRecord.startRecording();
// フラグが落ちるまでループ  例外処理略
while(isRecoding) {
	// バッファを読み込んで書き込む
	audioRecord.read(buffer, 0, audioBuf);
}
// 終了処理 例外処理略
audioRecord.stop();
out.close();
out = null;

実際はスレッドなんかで実行してフラグで録音ループを抜ける。これでSDカードにrec.rawってファイルができる。
ファイルの中身はサンプルレート8000Hz、1サンプルあたり16ビット、モノラルのバイトデータ。ヘッダ情報のないWindowsのwavファイルのデータチャンクのところ。これにRIFFとかfmtチャンクを加えればwavファイルの出来上がり。
逆に言うと、サンプルレート8000Hz、1サンプルあたり16ビット、モノラルのwavファイルの先頭からヘッダ情報の40バイトをちぎったのと同じデータなのだ。

んで、1サンプル16ビット=2バイトずつデータが並んでいて、16ビットなので65536段階の強弱の信号が記録されている。
16ビットでサンプルした場合は、符号付き(-32768 ~ +32767)であらわすらしい。 無音は 0。
8ビットでサンプルした場合は、符号なし(0~ +255)無音は128であらわす。

8ビットの場合はそのままJavaのbyte型の変数の配列に1サンプルずつデータが入っているが、
16ビットでサンプルした場合はエンディアンに気をつけないといけない。
取得できるデータはwavファイルと同じリトルエンディアンであるらしく、前8ビットには下の桁、後ろ8ビットには上の桁(2進数的に)
が収まっている。

byte[] buff = new buff[2];
リトルエンディアン 16bitの1
前8ビット    後ろ8ビット
00000001  00000000        // 2進数
0x0100                    // 16進数
buff[0]  == 0x01
buff[1]  == 0x00

リトルエンディアン 16bitの32767
11111111  01111111       //  2進数
0xFF7F                    // 16進数
buff[0]  == 0xFF
buff[1]  == 0xF7

リトルエンディアン 16bitの-32768
00000000 10000000        //  2進数
0x0080                     // 16進数
buff[0]  == 0x00
buff[1]  == 0x80

このようにバイナリエディタなんかでダンプしただけではパッと見て分かりにくいデータなのだ。
これが分かるまで苦労した。バイナリデータは触ったことなかったし。
Windowsの電卓ソフトの関数電卓モードを活用させてもらった。

んで、上のサンプルで保存したrec.rawファイルにヘッダをつけてwavファイルにする。以下コード


// WAVヘッダをつけて保存
// 各種例外処理略 チェック処理略
public void addWavHeader() {
	// 録音したファイル
	File recFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/rec.raw");
	// WAVファイル
	File wavFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/rec.wav");
	// ストリーム
    FileInputStream in = new FileInputStream(recFile);
	FileOutputStream out = new FileOutputStream(wavFile);

	// ヘッダ作成  サンプルレート8kHz
	byte[] header = createHeader(8000, (int)recFile.length());
	// ヘッダの書き出し
	out.write(header);

	// 録音したファイルのバイトデータ読み込み
	int n = 0,offset = 0;
	byte[] buffer = new byte[(int)recFile.length()];
	while (offset < buffer.length
			&& (n = in.read(buffer, offset, buffer.length - offset)) >= 0) {
		offset += n;
	}
	// バイトデータ書き込み
	out.write(buffer);

	// 終了
	in.close();
	out.close();
}

// Wavファイルのヘッダを作成する(PCM16ビット モノラル)
// sampleRate  サンプルレート
// datasize データサイズ
// これなんかもっとキレイに書けると思うが 。。 Ringroidのソースなんかキレイかも
public static byte[] createHeader(int sampleRate, int datasize) {
	byte[] byteRIFF = {'R', 'I', 'F', 'F'};
	byte[] byteFilesizeSub8 = intToBytes((datasize + 36));	// ファイルサイズ-8バイト数
	byte[] byteWAVE = {'W', 'A', 'V', 'E'};
	byte[] byteFMT_ = {'f', 'm', 't', ' '};
	byte[] byte16bit = intToBytes(16);					// fmtチャンクのバイト数
	byte[] byteSamplerate = intToBytes(sampleRate);		// サンプルレート
	byte[] byteBytesPerSec = intToBytes(sampleRate * 2);	// バイト/秒 = サンプルレート x 1チャンネル x 2バイト
	byte[] bytePcmMono = {0x01, 0x00, 0x01, 0x00};		// フォーマットID 1 =リニアPCM  ,  チャンネル 1 = モノラル
	byte[] byteBlockBit = {0x02, 0x00, 0x10, 0x00};		// ブロックサイズ2バイト サンプルあたりのビット数16ビット
	byte[] byteDATA = {'d', 'a', 't', 'a'};
	byte[] byteDatasize = intToBytes(datasize);			// データサイズ

	ByteArrayOutputStream out = new ByteArrayOutputStream();
	try {
		out.write(byteRIFF);
		out.write(byteFilesizeSub8);
		out.write(byteWAVE);
		out.write(byteFMT_);
		out.write(byte16bit);
		out.write(bytePcmMono);
		out.write(byteSamplerate);
		out.write(byteBytesPerSec);
		out.write(byteBlockBit);
		out.write(byteDATA);
		out.write(byteDatasize);

	} catch (IOException e) {
		return out.toByteArray();
	}

	return out.toByteArray();
}
// int型32ビットデータをリトルエンディアンのバイト配列にする
public static byte[] intToBytes(int value) {
	byte[] bt = new byte[4];
	bt[0] = (byte)(value & 0x000000ff);
	bt[1] = (byte)((value & 0x0000ff00) >> 8);
	bt[2] = (byte)((value & 0x00ff0000) >> 16);
	bt[3] = (byte)((value & 0xff000000) >> 24);
	return bt;
}

これでSDカードにrec.wavってファイルができるはず。あとは標準の音楽ソフトかパソコンで再生すればOK牧場。
WAVファイルを保存してゴニョゴニョできるソフトはそんなに多くないので、うまくやれば人気のアプリになりそう。
3gppとちがって加工しやすいし。3gpのフォーマット読んだだけで心が折れそうになったし・・。

PCでWAVファイルとかゴニョゴニョしてた人たちには大したことないかもしれないが、苦労した。
良い勉強になった。
はて、次はAudioTrackで再生の予定。

Related posts:

  1. Androidで音声処理1
  2. HT-03Aでプレビューの映像データをビットマップデータに変換して保存する
  3. HT-03Aでプレビューの映像データをビットマップデータに変換するところ補足

関連記事はYARPP関連記事プラグインによって表示されています。