基于android的实时音频频谱仪
前一段实习,本来打算做c++,到了公司发现没啥项目,于是乎转行做了android,写的第一个程序竟然要我处理信号,咱可是一心搞计算机的,没接触过信号的东西,什么都没接触过,于是乎, 找各种朋友,各种熟人,现在想想,专注语言是不对的,语言就是一工具,关键还是业务,算法。好了,废话不多说,上程序,注释都很详细,应该能看懂。
分析声音,其实很简单,就是运用傅里叶变换,将声音信号由时域转化到频域(程序用的是快速傅里叶变换,比较简单),为啥要这样,好处多多,不细讲,公司里的用处是为了检测手机发出声音的信号所在的频率集中范围。
第一个类,复数的计算,用到加减乘,很简单。
- package com.mobao360.sunshine;
- //复数的加减乘运算
- public class Complex {
- public double real;
- public double image;
- //三个构造函数
- public Complex() {
- // TODO Auto-generated constructor stub
- this.real = 0;
- this.image = 0;
- }
- public Complex(double real, double image){
- this.real = real;
- this.image = image;
- }
- public Complex(int real, int image) {
- Integer integer = real;
- this.real = integer.floatValue();
- integer = image;
- this.image = integer.floatValue();
- }
- public Complex(double real) {
- this.real = real;
- this.image = 0;
- }
- //乘法
- public Complex cc(Complex complex) {
- Complex tmpComplex = new Complex();
- tmpComplex.real = this.real * complex.real - this.image * complex.image;
- tmpComplex.image = this.real * complex.image + this.image * complex.real;
- return tmpComplex;
- }
- //加法
- public Complex sum(Complex complex) {
- Complex tmpComplex = new Complex();
- tmpComplex.real = this.real + complex.real;
- tmpComplex.image = this.image + complex.image;
- return tmpComplex;
- }
- //减法
- public Complex cut(Complex complex) {
- Complex tmpComplex = new Complex();
- tmpComplex.real = this.real - complex.real;
- tmpComplex.image = this.image - complex.image;
- return tmpComplex;
- }
- //获得一个复数的值
- public int getIntValue(){
- int ret = 0;
- ret = (int) Math.round(Math.sqrt(this.real*this.real - this.image*this.image));
- return ret;
- }
- }
这个类是有三个功能,第一,采集数据;第二,进行快速傅里叶计算;第三,绘图。
采集数据用AudioRecord类,网上讲解这个类的蛮多的,搞清楚构造类的各个参数就可以。
绘图用的是SurfaceView Paint Canvas三个类,本人也是参考网络达人的代码
- package com.mobao360.sunshine;
- import java.util.ArrayList;
- import java.lang.Short;
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.graphics.DashPathEffect;
- import android.graphics.Paint;
- import android.graphics.Path;
- import android.graphics.PathEffect;
- import android.graphics.Rect;
- import android.media.AudioRecord;
- import android.util.Log;
- import android.view.SurfaceView;
- public class AudioProcess {
- public static final float pi= (float) 3.1415926;
- //应该把处理前后处理后的普线都显示出来
- private ArrayList<short[]> inBuf = new ArrayList<short[]>();//原始录入数据
- private ArrayList<int[]> outBuf = new ArrayList<int[]>();//处理后的数据
- private boolean isRecording = false;
- Context mContext;
- private int shift = 30;
- public int frequence = 0;
- private int length = 256;
- //y轴缩小的比例
- public int rateY = 21;
- //y轴基线
- public int baseLine = 0;
- //初始化画图的一些参数
- public void initDraw(int rateY, int baseLine,Context mContext, int frequence){
- this.mContext = mContext;
- this.rateY = rateY;
- this.baseLine = baseLine;
- this.frequence = frequence;
- }
- //启动程序
- public void start(AudioRecord audioRecord, int minBufferSize, SurfaceView sfvSurfaceView) {
- isRecording = true;
- new RecordThread(audioRecord, minBufferSize).start();
- new DrawThread(sfvSurfaceView).start();
- }
- //停止程序
- public void stop(SurfaceView sfvSurfaceView){
- isRecording = false;
- inBuf.clear();
- }
- //录音线程
- class RecordThread extends Thread{
- private AudioRecord audioRecord;
- private int minBufferSize;
- public RecordThread(AudioRecord audioRecord,int minBufferSize){
- this.audioRecord = audioRecord;
- this.minBufferSize = minBufferSize;
- }
- public void run(){
- try{
- short[] buffer = new short[minBufferSize];
- audioRecord.startRecording();
- while(isRecording){
- int res = audioRecord.read(buffer, 0, minBufferSize);
- synchronized (inBuf){
- inBuf.add(buffer);
- }
- //保证长度为2的幂次数
- length=up2int(res);
- short[]tmpBuf = new short[length];
- System.arraycopy(buffer, 0, tmpBuf, 0, length);
- Complex[]complexs = new Complex[length];
- int[]outInt = new int[length];
- for(int i=0;i < length; i++){
- Short short1 = tmpBuf[i];
- complexs[i] = new Complex(short1.doubleValue());
- }
- fft(complexs,length);
- for (int i = 0; i < length; i++) {
- outInt[i] = complexs[i].getIntValue();
- }
- synchronized (outBuf) {
- outBuf.add(outInt);
- }
- }
- audioRecord.stop();
- }catch (Exception e) {
- // TODO: handle exception
- Log.i("Rec E",e.toString());
- }
- }
- }
- //绘图线程
- class DrawThread extends Thread{
- //画板
- private SurfaceView sfvSurfaceView;
- //当前画图所在屏幕x轴的坐标
- //画笔
- private Paint mPaint;
- private Paint tPaint;
- private Paint dashPaint;
- public DrawThread(SurfaceView sfvSurfaceView) {
- this.sfvSurfaceView = sfvSurfaceView;
- //设置画笔属性
- mPaint = new Paint();
- mPaint.setColor(Color.BLUE);
- mPaint.setStrokeWidth(2);
- mPaint.setAntiAlias(true);
- tPaint = new Paint();
- tPaint.setColor(Color.YELLOW);
- tPaint.setStrokeWidth(1);
- tPaint.setAntiAlias(true);
- //画虚线
- dashPaint = new Paint();
- dashPaint.setStyle(Paint.Style.STROKE);
- dashPaint.setColor(Color.GRAY);
- Path path = new Path();
- path.moveTo(0, 10);
- path.lineTo(480,10);
- PathEffect effects = new DashPathEffect(new float[]{5,5,5,5},1);
- dashPaint.setPathEffect(effects);
- }
- @SuppressWarnings("unchecked")
- public void run() {
- while (isRecording) {
- ArrayList<int[]>buf = new ArrayList<int[]>();
- synchronized (outBuf) {
- if (outBuf.size() == 0) {
- continue;
- }
- buf = (ArrayList<int[]>)outBuf.clone();
- outBuf.clear();
- }
- //根据ArrayList中的short数组开始绘图
- for(int i = 0; i < buf.size(); i++){
- int[]tmpBuf = buf.get(i);
- SimpleDraw(tmpBuf, rateY, baseLine);
- }
- }
- }
- /**
- * 绘制指定区域
- *
- * @param start
- * X 轴开始的位置(全屏)
- * @param buffer
- * 缓冲区
- * @param rate
- * Y 轴数据缩小的比例
- * @param baseLine
- * Y 轴基线
- */
- private void SimpleDraw(int[] buffer, int rate, int baseLine){
- Canvas canvas = sfvSurfaceView.getHolder().lockCanvas(
- new Rect(0, 0, buffer.length,sfvSurfaceView.getHeight()));
- canvas.drawColor(Color.BLACK);
- canvas.drawText("幅度值", 0, 3, 2, 15, tPaint);
- canvas.drawText("原点(0,0)", 0, 7, 5, baseLine + 15, tPaint);
- canvas.drawText("频率(HZ)", 0, 6, sfvSurfaceView.getWidth() - 50, baseLine + 30, tPaint);
- canvas.drawLine(shift, 20, shift, baseLine, tPaint);
- canvas.drawLine(shift, baseLine, sfvSurfaceView.getWidth(), baseLine, tPaint);
- canvas.save();
- canvas.rotate(30, shift, 20);
- canvas.drawLine(shift, 20, shift, 30, tPaint);
- canvas.rotate(-60, shift, 20);
- canvas.drawLine(shift, 20, shift, 30, tPaint);
- canvas.rotate(30, shift, 20);
- canvas.rotate(30, sfvSurfaceView.getWidth()-1, baseLine);
- canvas.drawLine(sfvSurfaceView.getWidth() - 1, baseLine, sfvSurfaceView.getWidth() - 11, baseLine, tPaint);
- canvas.rotate(-60, sfvSurfaceView.getWidth()-1, baseLine);
- canvas.drawLine(sfvSurfaceView.getWidth() - 1, baseLine, sfvSurfaceView.getWidth() - 11, baseLine, tPaint);
- canvas.restore();
- //tPaint.setStyle(Style.STROKE);
- for(int index = 64; index <= 512; index = index + 64){
- canvas.drawLine(shift + index, baseLine, shift + index, 40, dashPaint);
- String str = String.valueOf(frequence / 1024 * index);
- canvas.drawText( str, 0, str.length(), shift + index - 15, baseLine + 15, tPaint);
- }
- int y;
- for(int i = 0; i < buffer.length; i = i + 1){
- y = baseLine - buffer[i] / rateY ;
- canvas.drawLine(2*i + shift, baseLine, 2*i +shift, y, mPaint);
- }
- sfvSurfaceView.getHolder().unlockCanvasAndPost(canvas);
- }
- }
- /**
- * 向上取最接近iint的2的幂次数.比如iint=320时,返回256
- * @param iint
- * @return
- */
- private int up2int(int iint) {
- int ret = 1;
- while (ret<=iint) {
- ret = ret << 1;
- }
- return ret>>1;
- }
- //快速傅里叶变换
- public void fft(Complex[] xin,int N)
- {
- int f,m,N2,nm,i,k,j,L;//L:运算级数
- float p;
- int e2,le,B,ip;
- Complex w = new Complex();
- Complex t = new Complex();
- N2 = N / 2;//每一级中蝶形的个数,同时也代表m位二进制数最高位的十进制权值
- f = N;//f是为了求流程的级数而设立的
- for(m = 1; (f = f / 2) != 1; m++); //得到流程图的共几级
- nm = N - 2;
- j = N2;
- /******倒序运算——雷德算法******/
- for(i = 1; i <= nm; i++)
- {
- if(i < j)//防止重复交换
- {
- t = xin[j];
- xin[j] = xin[i];
- xin[i] = t;
- }
- k = N2;
- while(j >= k)
- {
- j = j - k;
- k = k / 2;
- }
- j = j + k;
- }
- /******蝶形图计算部分******/
- for(L=1; L<=m; L++) //从第1级到第m级
- {
- e2 = (int) Math.pow(2, L);
- //e2=(int)2.pow(L);
- le=e2+1;
- B=e2/2;
- for(j=0;j<B;j++) //j从0到2^(L-1)-1
- {
- p=2*pi/e2;
- w.real = Math.cos(p * j);
- //w.real=Math.cos((double)p*j); //系数W
- w.image = Math.sin(p*j) * -1;
- //w.imag = -sin(p*j);
- for(i=j;i<N;i=i+e2) //计算具有相同系数的数据
- {
- ip=i+B; //对应蝶形的数据间隔为2^(L-1)
- t=xin[ip].cc(w);
- xin[ip] = xin[i].cut(t);
- xin[i] = xin[i].sum(t);
- }
- }
- }
- }
- }
主程序
- package com.mobao360.sunshine;
- import java.util.ArrayList;
- import android.app.Activity;
- import android.app.AlertDialog;
- import android.content.Context;
- import android.content.DialogInterface;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.SurfaceView;
- import android.view.View;
- import android.widget.AdapterView;
- import android.widget.ArrayAdapter;
- import android.widget.Button;
- import android.widget.Spinner;
- import android.widget.TextView;
- import android.widget.Toast;
- import android.widget.ZoomControls;
- import android.media.AudioFormat;
- import android.media.AudioRecord;
- import android.media.MediaRecorder;
- public class AudioMaker extends Activity {
- /** Called when the activity is first created. */
- static int frequency = 8000;//分辨率
- static final int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
- static final int audioEncodeing = AudioFormat.ENCODING_PCM_16BIT;
- static final int yMax = 50;//Y轴缩小比例最大值
- static final int yMin = 1;//Y轴缩小比例最小值
- int minBufferSize;//采集数据需要的缓冲区大小
- AudioRecord audioRecord;//录音
- AudioProcess audioProcess = new AudioProcess();//处理
- Button btnStart,btnExit; //开始停止按钮
- SurfaceView sfv; //绘图所用
- ZoomControls zctlX,zctlY;//频谱图缩放
- Spinner spinner;//下拉菜单
- ArrayList<String> list=new ArrayList<String>();
- ArrayAdapter<String>adapter;//下拉菜单适配器
- TextView tView;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- initControl();
- }
- @Override
- protected void onDestroy(){
- super.onDestroy();
- android.os.Process.killProcess(android.os.Process.myPid());
- }
- //初始化控件信息
- private void initControl() {
- //获取采样率
- tView = (TextView)this.findViewById(R.id.tvSpinner);
- spinner = (Spinner)this.findViewById(R.id.spinnerFre);
- String []ls =getResources().getStringArray(R.array.action);
- for(int i=0;i<ls.length;i++){
- list.add(ls[i]);
- }
- adapter=new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item,list);
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spinner.setAdapter(adapter);
- spinner.setPrompt("请选择采样率");
- spinner.setOnItemSelectedListener(new Spinner.OnItemSelectedListener(){
- @SuppressWarnings("unchecked")
- public void onItemSelected(AdapterView arg0,View agr1,int arg2,long arg3){
- frequency = Integer.parseInt(adapter.getItem(arg2));
- tView.setText("您选择的是:"+adapter.getItem(arg2)+"HZ");
- Log.i("sunshine",String.valueOf(minBufferSize));
- arg0.setVisibility(View.VISIBLE);
- }
- @SuppressWarnings("unchecked")
- public void onNothingSelected(AdapterView arg0){
- arg0.setVisibility(View.VISIBLE);
- }
- });
- Context mContext = getApplicationContext();
- //按键
- btnStart = (Button)this.findViewById(R.id.btnStart);
- btnExit = (Button)this.findViewById(R.id.btnExit);
- //按键事件处理
- btnStart.setOnClickListener(new ClickEvent());
- btnExit.setOnClickListener(new ClickEvent());
- //画笔和画板
- sfv = (SurfaceView)this.findViewById(R.id.SurfaceView01);
- //初始化显示
- audioProcess.initDraw(yMax/2, sfv.getHeight(),mContext,frequency);
- //画板缩放
- zctlY = (ZoomControls)this.findViewById(R.id.zctlY);
- zctlY.setOnZoomInClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if(audioProcess.rateY - 5>yMin){
- audioProcess.rateY = audioProcess.rateY - 5;
- setTitle("Y轴缩小"+String.valueOf(audioProcess.rateY)+"倍");
- }else{
- audioProcess.rateY = 1;
- setTitle("原始尺寸");
- }
- }
- });
- zctlY.setOnZoomOutClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if(audioProcess.rateY<yMax){
- audioProcess.rateY = audioProcess.rateY + 5;
- setTitle("Y轴缩小"+String.valueOf(audioProcess.rateY)+"倍");
- }else {
- setTitle("Y轴已经不能再缩小");
- }
- }
- });
- }
- /**
- * 按键事件处理
- */
- class ClickEvent implements View.OnClickListener{
- @Override
- public void onClick(View v){
- Button button = (Button)v;
- if(button == btnStart){
- if(button.getText().toString().equals("Start")){
- try {
- //录音
- minBufferSize = AudioRecord.getMinBufferSize(frequency,
- channelConfiguration,
- audioEncodeing);
- //minBufferSize = 2 * minBufferSize;
- audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,frequency,
- channelConfiguration,
- audioEncodeing,
- minBufferSize);
- audioProcess.baseLine = sfv.getHeight()-100;
- audioProcess.frequence = frequency;
- audioProcess.start(audioRecord, minBufferSize, sfv);
- Toast.makeText(AudioMaker.this,
- "当前设备支持您所选择的采样率:"+String.valueOf(frequency),
- Toast.LENGTH_SHORT).show();
- btnStart.setText(R.string.btn_exit);
- spinner.setEnabled(false);
- } catch (Exception e) {
- // TODO: handle exception
- Toast.makeText(AudioMaker.this,
- "当前设备不支持你所选择的采样率"+String.valueOf(frequency)+",请重新选择",
- Toast.LENGTH_SHORT).show();
- }
- }else if (button.getText().equals("Stop")) {
- spinner.setEnabled(true);
- btnStart.setText(R.string.btn_start);
- audioProcess.stop(sfv);
- }
- }
- else {
- new AlertDialog.Builder(AudioMaker.this)
- .setTitle("提示")
- .setMessage("确定退出?")
- .setPositiveButton("确定", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int whichButton) {
- setResult(RESULT_OK);//确定按钮事件
- AudioMaker.this.finish();
- finish();
- }
- })
- .setNegativeButton("取消", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int whichButton) {
- //取消按钮事件
- }
- })
- .show();
- }
- }
- }
- }
程序源码下载地址:http://download.csdn.net/detail/sunshine_okey/3790484
详细的看代码吧,有什么写的详细的可以留言
第一次写技术文章,写的不好,大家不要怪罪,将就着看把
基于android的实时音频频谱仪的更多相关文章
- 【毕业设计】基于Android的家校互动平台开发(内含完整代码和所有文档)——爱吖校推(你关注的,我们才推)
☆ 写在前面 之前答应大家的毕业答辩之后把所有文档贡献出来,现在答辩已过,LZ信守承诺,把所有文档开源到了GitHub(这个地址包含所有的代码和文档以及PPT,外层为简单的代码).还望喜欢的朋友们,不 ...
- 基于android的天气预报的设计与实现
目录 应用开发技术及开发平台介绍 应用需求分析 应用功能设计及其描述 应用UI展示 ①开发技术: 本系统是采用面向对象的软件开发方法,基于Android studio开发平台,以Android作为本系 ...
- 基于Android的在线播放器系统的设计与实现
文章结构: 1 引言 1.1系统的研究背景 现在的时代是互联网的时代,互联网高速发展的同时,无线网络也接入了互联网.社会的各个领域都已经被无线网络渗透.小的比如手机,电脑,电视.大的比如灯光系统,智能 ...
- 【源代码】基于Android和蓝牙的单片机温度採集系统
如需转载请标明出处:http://blog.csdn.net/itas109 QQ技术交流群:129518033 STC89C52单片机通过HC-06蓝牙模块与Android手机通信实例- 基于And ...
- 基于 Android 的 3D 视频示例代码
笔者:Mark Liu 下载样本代码 简单介绍 在Android 中,创建一个可以播放视频剪辑的应用很easy:创建一个採用 3D 图形平面的游戏应用也很easy.可是,创建一个可以在 3D 图形对象 ...
- Android开发自学笔记(基于Android Studio1.3.1)—1.环境搭建(转)
一.引言 本套学习笔记的开发环境是Windows 10 专业版和Android Studio 的最新版1.3.1. Android Studio 是一个Android开发环境,基于Intelli ...
- 基于 Android 的 3D 视频样本代码
作者:Mark Liu 下载样本代码 简单介绍 在Android 中,创建一个可以播放视频剪辑的应用很easy:创建一个採用 3D 图形平面的游戏应用也很easy.可是,创建一个可以在 3D 图形对象 ...
- 开发一个基于 Android系统车载智能APP
很久之前就想做一个车载相关的app.需要实现如下功能: (1)每0.2秒更新一次当前车辆的最新速度值. (2)可控制性记录行驶里程. (3)不连接网络情况下获取当前车辆位置.如(北京市X区X路X号) ...
- matlab中的实时音频
音频系统工具箱™针对实时音频处理进行了优化.audioDeviceReader, audioDeviceWriter, audioPlayerRecorder, dsp.AudioFileReader ...
随机推荐
- ActionBar开启Overlay Mode(覆盖模式)
以下内容参考自Android官网http://developer.android.com/training/basics/actionbar/overlaying.html#EnableOverlay ...
- BZOJ 3391: [Usaco2004 Dec]Tree Cutting网络破坏( dfs )
因为是棵树 , 所以直接 dfs 就好了... ---------------------------------------------------------------------------- ...
- 循环调用修正sic86
create or replace procedure rebuild_sic86_wyl(pi_aac001 in number, po_fhz out varchar2, po_msg out v ...
- POJ 2208 Pyramids 欧拉四面体
给出边长,直接就可以求出体积咯 关于欧拉四面体公式的推导及证明过程 2010-08-16 14:18 1,建议x,y,z直角坐标系.设A.B.C少拿点的坐标分别为(a1,b1,c1),(a2,b2,c ...
- XMPP 服务器 Openfire 的 Emoji 支持问题(进行部分修改)
当前最新版3.9.3已经可以支持Emoji ----------------------------------------------------------------------------- ...
- Linux 特殊符号使用: 倒引号`的使用
Linux中有很多特殊符号,这里介绍 ` 倒引号的含义. 我们考虑下这个场景,有时我们需要将一个命令的执行结果赋值给某个变量,或者别的用途. 这时我们可以用两个`倒引号将该命令括起来. 例1: 如 e ...
- 各国iPhone5系列最新裸机价格
美国/加拿大/中国香港/中国大陆iPhone5系列最新裸机价格
- POJ 3422 Kaka's Matrix Travels (最小费用最大流)
POJ 3422 Kaka's Matrix Travels 链接:http://poj.org/problem? id=3422 题意:有一个N*N的方格,每一个方格里面有一个数字.如今卡卡要从左上 ...
- 手动制作rpm包
制作RPM包的过程,简单的说,就是为制作过程提供一个“工作车间”,即一个目录,里面需要包含以下几个子目录: BUILD ————编译相关源码包时的工作目录: RPMS — ...
- 【转】CentOS上安装 jdk:rpm安装和源码安装
1.安装 jdk-8u5-linux-x64.rpm 原文链接:http://www.cnblogs.com/xsi640/p/3756995.html 先下载最新的jdk版本 文件名:jdk-8u5 ...