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),暂停了就不写字节流和显示图像,按继续才开始。结束的时候就保持文件和把图像清空;
如有不懂的童鞋欢迎与我联系。
705

被折叠的 条评论
为什么被折叠?



