1、 功能需求

1. 軟件運行平台为android平台

 

2、 性能要求

1. 應用軟件可以适應不同android硬件平台

2. 要求提供源碼,最好封裝为函數庫。

3. 支持平台:Android1.6 Android2.0 Android2.1 Android2.2(及以上平台)

4. 支持的手機分辨率:320x240 320x480 480x800  (具有自适應性)

5. 可以根據重力感應切換屏幕方向

6. 要求聲音的實時波形顯示

7. 錄音的時間長度顯示

8. 文件名自動按照系統時間保存,如20120601.amr

9. 在可以列出曆史錄音列表(文件列表)

 

 

 

 

 

 

 

關鍵代碼如下:

 

 

package com.android.record;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

import com.android.record.Draw.DrawThread;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.SurfaceView;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.Chronometer;

/**
 * class name:TestAudioRecord<BR>
 * class description:用AudioRecord來進行錄音<BR>
 * PS: <BR>
 * 
 * @version 1.00 2012/07/17
 * @author CODYY)liangqinsheng
 */
public class SoundRecord extends Activity {
    // 音頻獲取源
    private int audioSource = MediaRecorder.AudioSource.MIC;
    // 設置音頻采样率,44100是目前的標准,但是某些設備仍然支持22050,16000,11025
    private static int sampleRateInHz = 8000;
    // 設置音頻的錄制的聲道CHANNEL_IN_STEREO为雙聲道,CHANNEL_CONFIGURATION_MONO为單聲道
    private static int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
    // 音頻數據格式:PCM 16位每個样本。保證設備支持。PCM 8位每個样本。不一定能得到設備支持。
    private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    // 緩沖區字節大小
    private int bufferSizeInBytes = 0;
    private Button mRecordButton;
    private Button mStopButton;
    private Button mPauseButton;
    private Button mListButton;
    private AudioRecord audioRecord;
    private boolean isRecord = false;// 設置正在錄制的狀態
    // AudioName裸音頻數據文件
    private static final String AudioName = "/sdcard/record_sound/love.3gpp";
    // NewAudioName可播放的音頻文件
    private static String NewAudioName = "/sdcard/record_sound/new.wav";

    // private ArrayList<String> recordFiles;
    private SimpleDateFormat sdf;

    private int mPause = 0;
    private Chronometer chronometer;
    private long passTime;
    Paint mPaint;
    SurfaceView sfv;
    public ArrayList<short[]> inBuf = new ArrayList<short[]>();
    public int baseLine = 0;
    public int rateX = 16;
    public int rateY = 5;

    public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	getWindow().setFormat(PixelFormat.TRANSLUCENT);// 讓界面橫屏
	requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉界面標題
	getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
		WindowManager.LayoutParams.FLAG_FULLSCREEN);
	// 重新設置界面大小
	setContentView(R.layout.main);
	sdf = new SimpleDateFormat("yyMMddhhmmss");
	sfv = (SurfaceView) this.findViewById(R.id.surfaceView);
	mPaint = new Paint();
	mPaint.setColor(Color.GREEN);// 畫筆为綠色
	mPaint.setStrokeWidth(1);// 設置畫筆粗細
	baseLine = sfv.getHeight();
	init();
    }

    private void init() {
	mRecordButton = (Button) this.findViewById(R.id.recordButton);
	mStopButton = (Button) this.findViewById(R.id.stopButton);
	mPauseButton = (Button) this.findViewById(R.id.pauseButton);
	mListButton = (Button) this.findViewById(R.id.listButton);
	mRecordButton.setOnClickListener(new TestAudioListener());
	mStopButton.setOnClickListener(new TestAudioListener());
	mPauseButton.setOnClickListener(new TestAudioListener());
	mListButton.setOnClickListener(new TestAudioListener());
	chronometer = (Chronometer) findViewById(R.id.chronometer);
	creatAudioRecord();
    }

    private void creatAudioRecord() {
	// 獲得緩沖區字節大小
	bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,
		channelConfig, audioFormat);
	// 創建AudioRecord對象
	audioRecord = new AudioRecord(audioSource, sampleRateInHz,
		channelConfig, audioFormat, bufferSizeInBytes);
    }

    class TestAudioListener implements OnClickListener {
	@Override
	public void onClick(View v) {
	    if (v == mRecordButton) {
		mPause = 0;
		startRecord();
		// 開始記時
		chronometer.setFormat(null);
		chronometer.setBase(SystemClock.elapsedRealtime());
		chronometer.start();
		new DrawThread(sfv, mPaint).start();
	    }
	    if (v == mStopButton) {
		stopRecord();
		chronometer.stop();
		inBuf.clear();// 清除
	    }
	    if (v == mPauseButton) {
		if (mPause == 1) {
		    mPause = 0;
		    chronometer.setBase(chronometer.getBase()
			    + (SystemClock.elapsedRealtime() - passTime));
		    chronometer.start();
		} else {
		    mPause = 1;
		    chronometer.stop();
		    passTime = SystemClock.elapsedRealtime();
		}
	    }
	    if (v == mListButton) {
		Intent intent = new Intent(SoundRecord.this, FileList.class);
		startActivity(intent);
	    }
	}
    }

    private void startRecord() {
	NewAudioName = "/sdcard/record_sound/" + sdf.format(new Date()) + ".wav";
	if (audioRecord == null)
	    audioRecord = new AudioRecord(audioSource, sampleRateInHz,
		    channelConfig, audioFormat, bufferSizeInBytes);
	audioRecord.startRecording();
	// 讓錄制狀態为true
	isRecord = true;
	// 開启音頻文件寫入線程
	new Thread(new AudioRecordThread()).start();
    }

    private void stopRecord() {
	close();
    }

    private void close() {
	if (audioRecord != null) {
	    System.out.println("stopRecord");
	    isRecord = false;// 停止文件寫入
	    audioRecord.stop();
	    audioRecord.release();// 釋放資源
	    audioRecord = null;
	}
    }

    class AudioRecordThread implements Runnable {
	@Override
	public void run() {

	    writeDateTOFile();// 往文件中寫入裸數據isRecord
	    copyWaveFile(AudioName, NewAudioName);// 给裸數據加上頭文件
	}
    }

    /**
     * 這裏將數據寫入文件,但是並不能播放,因为AudioRecord獲得的音頻是原始的裸音頻, 如果需要播放就必須加入一些格式或者編碼的頭信息。
     */
    private void writeDateTOFile() {
	// new一個byte數組用來存一些字節數據,大小为緩沖區大小
	byte[] audiodata = new byte[bufferSizeInBytes];
	FileOutputStream fos = null;
	int readsize = 0;
	try {
	    File file = new File(AudioName);
	    if (file.exists()) {
		file.delete();
	    }
	    fos = new FileOutputStream(file);// 建立一個可存取字節的文件
	} catch (Exception e) {
	    e.printStackTrace();
	}
	while (isRecord == true) {
	    readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
    	    if(mPause == 0) {
		short[] tmpBuf = new short[readsize / 4];
		for (int i = 0, ii = 0; i < tmpBuf.length; i++, ii = i * 4) {
		    tmpBuf[i] = audiodata[ii];
		}
		synchronized (inBuf) {//
		    inBuf.add(tmpBuf);// 添加數據
		}
	    }
	    if (AudioRecord.ERROR_INVALID_OPERATION != readsize) {
		try {
		    if (mPause == 0 && fos != null)
			fos.write(audiodata);
		} catch (IOException e) {
		    e.printStackTrace();
		}
	    }
	}
	try {
	    fos.close();// 關閉寫入流
	} catch (IOException e) {
	    e.printStackTrace();
	}
    }

    /**
     * 這裏得到可播放的音頻文件
     * 
     * @param inFilename
     * @param outFilename
     */
    private void copyWaveFile(String inFilename, String outFilename) {
	FileInputStream in = null;
	FileOutputStream out = null;
	long totalAudioLen = 0;
	long totalDataLen = totalAudioLen + 36;
	long longSampleRate = sampleRateInHz;
	int channels = 2;
	long byteRate = 16 * sampleRateInHz * channels / 8;
	byte[] data = new byte[bufferSizeInBytes];
	try {
	    in = new FileInputStream(inFilename);
	    out = new FileOutputStream(outFilename);
	    totalAudioLen = in.getChannel().size();
	    totalDataLen = totalAudioLen + 36;
	    int j = 0;
	    WriteWaveFileHeader(out, totalAudioLen, totalDataLen,
		    longSampleRate, channels, byteRate);
	    while (in.read(data) != -1) {
		out.write(data);
		System.out.println("j:" + j);
		j++;
		// out.flush();
	    }

	    in.close();
	    out.close();
	} catch (FileNotFoundException e) {
	    e.printStackTrace();
	} catch (IOException e) {
	    e.printStackTrace();
	}
    }

    /**
     * 44字節的wav頭文件信息
     */
    private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen,
	    long totalDataLen, long longSampleRate, int channels, long byteRate)
	    throws IOException {
	byte[] header = new byte[44];
	header[0] = 'R'; // RIFF/WAVE header
	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';
	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; // 4 bytes: size of 'fmt ' chunk
	header[17] = 0;
	header[18] = 0;
	header[19] = 0;
	header[20] = 1; // format = 1
	header[21] = 0;
	header[22] = (byte) channels;
	header[23] = 0;
	header[24] = (byte) (longSampleRate & 0xff);
	header[25] = (byte) ((longSampleRate >> 8) & 0xff);
	header[26] = (byte) ((longSampleRate >> 16) & 0xff);
	header[27] = (byte) ((longSampleRate >> 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; // bits per sample
	header[35] = 0;
	header[36] = 'd';
	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);
    }

    @Override
    protected void onDestroy() {
	close();
	super.onDestroy();
    }

	class DrawThread extends Thread {
		private int oldX = 0;// 上次繪制的X坐標
		private int oldY = 0;// 上次繪制的Y坐標
		private SurfaceView sfv;// 畫板
		private int X_index = 0;// 當前畫圖所在屏幕X軸的坐標
		private Paint mPaint;// 畫筆
		public DrawThread(SurfaceView sfv, Paint mPaint) {
			this.sfv = sfv;
			this.mPaint = mPaint;
		}
		public void run() {
			while (isRecord) {
				ArrayList<short[]> buf = new ArrayList<short[]>();
				synchronized (inBuf) {
					if (inBuf.size() == 0)
						continue;
					buf = (ArrayList<short[]>) inBuf.clone();// 保存
					inBuf.clear();// 清除
				}
				for (int i = 0; i < buf.size(); i++) {
					short[] tmpBuf = buf.get(i);
					SimpleDraw(X_index, tmpBuf, rateY, baseLine);// 把緩沖區數據畫出來
					X_index = X_index + tmpBuf.length;
					if (X_index > sfv.getWidth()) {
						X_index = 0;
					}
				}
			}
		}
		/**
		 * 繪制指定區域
		 * 
		 * @param start
		 *            X軸開始的位置(全屏)
		 * @param buffer
		 *            緩沖區
		 * @param rate
		 *            Y軸數據縮小的比例
		 * @param baseLine
		 *            Y軸基線
		 */
		void SimpleDraw(int start, short[] buffer, int rate, int baseLine) {
			if (start == 0)
				oldX = 0;
			Canvas canvas = sfv.getHolder().lockCanvas(
					new Rect(start, 0, start + buffer.length, sfv.getHeight()));// 關鍵:獲取畫布
			canvas.drawColor(Color.BLACK);// 清除背景
			int y;
			for (int i = 0; i < buffer.length; i++) {// 有多少畫多少
				int x = i + start;
				y = buffer[i] / rate + baseLine;// 調節縮小比例,調節基准線
				canvas.drawLine(oldX, oldY, x, y, mPaint);
				oldX = x;
				oldY = y;
			}
			sfv.getHolder().unlockCanvasAndPost(canvas);// 解锁畫布,提交畫好的圖像
		}
	}

}

 畫圖類:package com.android.record;

import java.util.ArrayList;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.AudioRecord;
import android.view.SurfaceView;
public class Draw {
	public ArrayList<short[]> inBuf = new ArrayList<short[]>();
	private boolean isRecording = false;// 線程控制標記
	/**
	 * X軸縮小的比例
	 */
	public int rateX = 4;
	/**
	 * Y軸縮小的比例
	 */
	public int rateY = 4;
	/**
	 * Y軸基線
	 */
	public int baseLine = 0;
	/**
	 * 初始化
	 */
	public void init(int rateX, int rateY, int baseLine) {
		this.rateX = rateX;
		this.rateY = rateY;
		this.baseLine = baseLine;
	}
	/**
	 * 開始
	 * 
	 * @param recBufSize
	 *            AudioRecord的MinBufferSize
	 */
	public void Start(AudioRecord audioRecord, int recBufSize, SurfaceView sfv,
			Paint mPaint) {
		isRecording = true;
//		new RecordThread(audioRecord, recBufSize).start();// 開始繪制線程
		new DrawThread(sfv, mPaint).start();// 開始繪制線程
	}
	/**
	 * 停止
	 */
	public void Stop() {
		isRecording = false;
		inBuf.clear();// 清除
	}
	/**
	 * 負責從MIC保存數據到inBuf
	 * 
	 * @author GV
	 * 
	 */
	class RecordThread extends Thread {
		private int recBufSize;
		private AudioRecord audioRecord;
		public RecordThread(AudioRecord audioRecord, int recBufSize) {
			this.audioRecord = audioRecord;
			this.recBufSize = recBufSize;
		}
		public void run() {
			try {
				short[] buffer = new short[recBufSize];
				while (isRecording) {
					// 從MIC保存數據到緩沖區
					int bufferReadResult = audioRecord.read(buffer, 0,
							recBufSize);
					short[] tmpBuf = new short[bufferReadResult / rateX];
					for (int i = 0, ii = 0; i < tmpBuf.length; i++, ii = i
							* rateX) {
						tmpBuf[i] = buffer[ii];
					}
					synchronized (inBuf) {//
						inBuf.add(tmpBuf);// 添加數據
					}
				}
			} catch (Throwable t) {
			}
		}
	};
	/**
	 * 負責繪制inBuf中的數據
	 * 
	 * @author GV
	 * 
	 */
	class DrawThread extends Thread {
		private int oldX = 0;// 上次繪制的X坐標
		private int oldY = 0;// 上次繪制的Y坐標
		private SurfaceView sfv;// 畫板
		private int X_index = 0;// 當前畫圖所在屏幕X軸的坐標
		private Paint mPaint;// 畫筆
		public DrawThread(SurfaceView sfv, Paint mPaint) {
			this.sfv = sfv;
			this.mPaint = mPaint;
		}
		public void run() {
			while (isRecording) {
				ArrayList<short[]> buf = new ArrayList<short[]>();
				synchronized (inBuf) {
					if (inBuf.size() == 0)
						continue;
					buf = (ArrayList<short[]>) inBuf.clone();// 保存
					inBuf.clear();// 清除
				}
				for (int i = 0; i < buf.size(); i++) {
					short[] tmpBuf = buf.get(i);
					SimpleDraw(X_index, tmpBuf, rateY, baseLine);// 把緩沖區數據畫出來
					X_index = X_index + tmpBuf.length;
					if (X_index > sfv.getWidth()) {
						X_index = 0;
					}
				}
			}
		}
		/**
		 * 繪制指定區域
		 * 
		 * @param start
		 *            X軸開始的位置(全屏)
		 * @param buffer
		 *            緩沖區
		 * @param rate
		 *            Y軸數據縮小的比例
		 * @param baseLine
		 *            Y軸基線
		 */
		void SimpleDraw(int start, short[] buffer, int rate, int baseLine) {
			if (start == 0)
				oldX = 0;
			Canvas canvas = sfv.getHolder().lockCanvas(
					new Rect(start, 0, start + buffer.length, sfv.getHeight()));// 關鍵:獲取畫布
			canvas.drawColor(Color.BLACK);// 清除背景
			int y;
			for (int i = 0; i < buffer.length; i++) {// 有多少畫多少
				int x = i + start;
				y = buffer[i] / rate + baseLine;// 調節縮小比例,調節基准線
				canvas.drawLine(oldX, oldY, x, y, mPaint);
				oldX = x;
				oldY = y;
			}
			sfv.getHolder().unlockCanvasAndPost(canvas);// 解锁畫布,提交畫好的圖像
		}
	}
}
 完整的項目見附件.
 
關鍵技術點:
1.使用AudioRecord 進行錄音;
2.錄音寫文件與現實示波同時進行;
2個線程同步,主要數據只有一份,所以得拷貝一份给2個線程用,不然的話錄音會短截;
3.主要控制好字節流(轉化成數字)在畫布上的顯示,需要多次調試才達到效果;
4.暫停與進行錄音需要一個線程控制,如果按了暫停,就設置線程裏面有一個開關(例如mPause),暫停了就不寫字節流和顯示圖像,按繼續才開始。結束的時候就保持文件和把圖像清空;
 
 
創作者介紹
創作者 shadow 的頭像
shadow

資訊園

shadow 發表在 痞客邦 留言(0) 人氣()