- 浏览: 8024 次
- 性别:
- 来自: 广州
-
最新评论
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),暂停了就不写字节流和显示图像,按继续才开始。结束的时候就保持文件和把图像清空;
如有不懂的童鞋欢迎与我联系。
- record.zip (231.6 KB)
- 下载次数: 154
相关推荐
在Android平台上开发一个示波器小程序,可以展示正弦和余弦信号,是将嵌入式计算和可视化技术应用于移动设备的一个典型实例。这个小程序利用Android系统的强大功能,为用户提供实时信号显示,使得用户能够在手机或...
"android音频口示波器和波形发生器"项目是一个很好的实践,它涵盖了Android音频编程的关键知识点。让我们深入探讨一下这些技术要点。 首先,`示波器`是显示音频信号波形的工具,它可以帮助我们实时查看音频数据的...
【Android通过J2ME的录音功能实现简易示波器】是一种利用J2ME的多媒体应用编程接口(MMAPI)在Android设备上构建一个简单的模拟示波器的方法。虽然使用智能机可以实现实时读取麦克风输入流,提供更流畅的体验,但...
在Android平台上,模拟信号示波器的开发是一个有趣且实用的技术挑战,它允许开发者利用手机的内置硬件,如麦克风,来捕获和显示模拟信号。这个示例代码旨在教你如何创建一个基本的Android模拟信号示波器,通过Audio...
在Android开发中,实现音频条形图效果可以增强用户界面的交互性和视觉效果,尤其是在音乐播放器、录音机等应用中。音频条形图通常用于表示音频信号的强度,如在音乐播放时动态显示波形。然而,一些应用场景可能不...
演示效果预览项目应用效果预览示波器-波形图(信号传输调制)版本说明当前版本V1.0.2增加了耳机Kong插入监听历史版本V1.0.1初始版本用法介绍build.gradle设置dependencies { compile 'com.zhouyou:rxaudio:1.0.2'}...
开发过程中,可能需要借助示波器或逻辑分析仪观察SPI信号,确保数据传输的准确性。同时,为了优化性能,需要考虑如何减少SPI传输次数,比如通过批量读写多个寄存器,或者利用硬件SPI控制器来减轻CPU负担。 总之,...