- 浏览: 7904 次
- 性别:
- 来自: 广州
最新评论
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音频处理、图形渲染、多线程编程以及Web技术的集成。对于初学者来说,这是一个很好的实践项目,能帮助他们全面理解Android应用开发的多个方面。在...
综上所述,通过`AudioRecord`和`AudioTrack`的配合,可以实现一个简单的Android录音机应用。在`AudioRecPlay`这个项目中,我们可以看到具体的代码实现,学习如何将理论知识应用到实践中。理解并掌握这两个类的使用,...
录音示波器是一种结合了录音功能与示波器特性的工具,主要面向电子工程爱好者、学生以及初学者,提供了一种简易的实验和学习环境。它不仅能够录制音频,还能实时显示音频信号的波形,帮助用户理解音频信号的变化规律...
android 对讲机&录音机类安卓源码包源代码合集(10个) Andorid录音+变声+转mp3 Android电子麦克风 AudioRecord 仿微信的录音功能 对讲机参考资料 开心网语音发送模块的录音功能 按住说话,开始录音,停止录音 神聊...
在这个"android 录音机 service 例子"中,我们将深入探讨如何结合Service和MediaRecorder来创建一个能够后台录制音频的应用。 1. **Service基础** Service组件是Android应用开发中的关键部分,它可以在没有用户...
android录音机
在Android开发中,创建一个能够显示声波动画的录音应用是一项有趣的挑战,它结合了多媒体处理、用户界面设计以及实时数据可视化。以下是对这个"Android录音的声波动画"项目的关键知识点的详细解释: 1. **Android ...
"android音频口示波器和波形发生器"项目是一个很好的实践,它涵盖了Android音频编程的关键知识点。让我们深入探讨一下这些技术要点。 首先,`示波器`是显示音频信号波形的工具,它可以帮助我们实时查看音频数据的...
在Android平台上,调用系统自带的录音机是开发者经常遇到的需求,这可以帮助用户方便地录制音频并集成到应用中。本文将深入探讨如何在Android应用中实现这一功能,并结合"AutoRecoder"这个示例来讲解相关知识点。 ...
【Android通过J2ME的录音功能实现简易示波器】是一种利用J2ME的多媒体应用编程接口(MMAPI)在Android设备上构建一个简单的模拟示波器的方法。虽然使用智能机可以实现实时读取麦克风输入流,提供更流畅的体验,但...
这个`Android 录音机Demo`应该是为了帮助开发者快速理解和实现音频录制的基本流程。下面我们将深入探讨Android录音的相关知识点。 1. **MediaRecorder类**:这是Android系统提供用于处理多媒体数据录制的核心类。...
在Android开发中,有时我们需要实现一些与音频相关的功能,例如录音和实时显示音频波形,以提供用户友好的界面。本篇文章将详细讲解如何在Android应用中创建一个录音分贝波浪图展示动画,主要涉及的技术包括录音、...
在Android平台上,开发一款带有录音效果的录音机应用是一项技术性的工作,涉及到多个技术层面,如音频处理、用户界面设计以及动画实现等。本项目"MiCode-SoundRecorder-15aa813"显然是一个关于Android录音机的源代码...
在Android平台上,开发一款录音机应用是一项常见的任务。本文将深入探讨如何实现一个具有暂停功能的Android录音机,包括其核心原理、关键步骤以及代码实现。这个录音机的特点是,当用户暂停时,会保存当前的录音数据...
本项目"仿三星android录音机界面"旨在提供一个高度还原三星原生录音机应用的UI设计,使得开发者和爱好者可以参考并应用于自己的项目中。下面我们将详细探讨这个项目的相关知识点。 首先,Android录音机应用的核心...
在Android平台上,开发一款原生录音机应用是一个常见的任务,涉及到多媒体处理、音频录制和播放等技术。Android SDK提供了一套完整的API,使得开发者能够轻松地实现这些功能。本篇文章将详细探讨Android原生录音机的...
【Android 录音机软件开发详解】 Android录音机软件是一种常见的移动应用,它允许用户录制、存储和播放音频。在毕业设计中,这样的项目能够帮助学生深入理解Android应用开发的基本原理,包括音频处理、UI设计、文件...
本文将深入探讨如何在Android上实现录音机功能,基于提供的"android录音机"项目,我们可以学习到以下几个关键知识点: 1. **录音API的使用**:Android提供了MediaRecorder类来处理音频录制。首先,我们需要实例化...
然而,除了基本的音乐播放和录音功能,声卡还能够被巧妙地利用来实现一些额外的科学应用,例如将电脑转变为一个虚拟示波器。这个概念是基于声卡的模拟数字转换(ADC)能力,它能够将声音信号转化为可处理的数字数据...