Android平台音频信号FFT的实现

时间:2021-10-28 21:02:45

转载请标明出处:http://blog.csdn.net/sctu_vroy/article/details/45871823

功能:加载本地SD卡中moveDsp文件夹中的音频文件(包括录音获取文件和MP3文件),播放实时FFT,绘制出信号的时域和频域波形。

设计步骤:
第一步:页面布局,编写录音工具类URecorder(设置录音属性)和接口IVoiceManager
  1. public class URecorder implements IVoiceManager{
  2. private static final String TAG = "URecorder";
  3. private Context context = null;
  4. private String path = null;
  5. private MediaRecorder mRecorder = null;
  6. public URecorder(Context context, String path) {
  7. this.context = context;
  8. this.path = path;
  9. mRecorder = new MediaRecorder();
  10. }
  11. @Override
  12. public boolean start() {
  13. //设置音源为Micphone
  14. mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
  15. //设置封装格式
  16. mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
  17. mRecorder.setOutputFile(path);
  18. //设置编码格式
  19. mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
  20. try {
  21. mRecorder.prepare();
  22. } catch (IOException e) {
  23. Log.e(TAG, "prepare() failed");
  24. }
  25. //录音
  26. mRecorder.start();
  27. return false;
  28. }
  29. @Override
  30. public boolean stop() {
  31. mRecorder.stop();
  32. mRecorder.release();
  33. mRecorder = null;
  34. return false;
  35. }
  36. }
  1. public interface IVoiceManager {
  2. public boolean start();
  3. public boolean stop();
  4. }

第二步:继承父类View,编写自定义绘图类VisualizerView(显示时域波形)和VisualizerFFTView(显示频域波形(频谱)),重写onDraw()方法

时域波形显示类:
  1. public class VisualizerView extends View {
  2. private byte[] mBytes;
  3. private float[] mPoints;
  4. private Rect mRect = new Rect();
  5. private Paint mForePaint = new Paint();
  6. public VisualizerView(Context context) {
  7. super(context);
  8. init();
  9. }
  10. /**
  11. * 初始化
  12. */
  13. private void init() {
  14. mBytes = null;
  15. mForePaint.setStrokeWidth(1f);
  16. mForePaint.setAntiAlias(true);
  17. mForePaint.setColor(Color.GREEN);
  18. }
  19. public void updateVisualizer(byte[] waveForm)
  20. {
  21. mBytes = waveForm;
  22. invalidate();
  23. }
  24. @Override
  25. protected void onDraw(Canvas canvas) {
  26. super.onDraw(canvas);
  27. if (mBytes == null)
  28. {
  29. return;
  30. }
  31. if (mPoints == null || mPoints.length < mBytes.length * 4)
  32. {
  33. mPoints = new float[mBytes.length * 4];
  34. }
  35. mRect.set(0, 0, getWidth(), getHeight());
  36. //绘制波形
  37. for (int i = 0; i < mBytes.length - 1; i++) {
  38. mPoints[i * 4] = mRect.width() * i / (mBytes.length - 1);
  39. mPoints[i * 4 + 1] = mRect.height() / 2
  40. + ((byte) (mBytes[i] + 128)) * (mRect.height() / 2) / 128;
  41. mPoints[i * 4 + 2] = mRect.width() * (i + 1) / (mBytes.length - 1);
  42. mPoints[i * 4 + 3] = mRect.height() / 2
  43. + ((byte) (mBytes[i + 1] + 128)) * (mRect.height() / 2) / 128;
  44. }
  45. canvas.drawLines(mPoints, mForePaint);
  46. }
  47. }

频谱显示类:

  1. public class VisualizerFFTView extends View {
  2. private byte[] mBytes;
  3. private float[] mPoints;
  4. private Rect mRect = new Rect();
  5. private Paint mForePaint = new Paint();
  6. private int mSpectrumNum = 48;
  7. public VisualizerFFTView(Context context) {
  8. super(context);
  9. init();
  10. }
  11. /**
  12. * 初始化
  13. */
  14. private void init() {
  15. mBytes = null;
  16. mForePaint.setStrokeWidth(8f);
  17. mForePaint.setAntiAlias(true);
  18. mForePaint.setColor(Color.rgb(0, 128, 255));
  19. }
  20. public void updateVisualizer(byte[] fft)
  21. {
  22. byte[] model = new byte[fft.length / 2 + 1];
  23. model[0] = (byte) Math.abs(fft[0]);
  24. for (int i = 2, j = 1; j < mSpectrumNum;)
  25. {
  26. model[j] = (byte) Math.hypot(fft[i], fft[i + 1]);
  27. i += 2;
  28. j++;
  29. }
  30. mBytes = model;
  31. invalidate();
  32. }
  33. @Override
  34. protected void onDraw(Canvas canvas) {
  35. super.onDraw(canvas);
  36. if (mBytes == null)
  37. {
  38. return;
  39. }
  40. if (mPoints == null || mPoints.length < mBytes.length * 4)
  41. {
  42. mPoints = new float[mBytes.length * 4];
  43. }
  44. mRect.set(0, 0, getWidth(), getHeight());
  45. //绘制频谱
  46. final int baseX = mRect.width()/mSpectrumNum;
  47. final int height = mRect.height();
  48. for (int i = 0; i < mSpectrumNum ; i++)
  49. {
  50. if (mBytes[i] < 0)
  51. {
  52. mBytes[i] = 127;
  53. }
  54. final int xi = baseX*i + baseX/2;
  55. mPoints[i * 4] = xi;
  56. mPoints[i * 4 + 1] = height;
  57. mPoints[i * 4 + 2] = xi;
  58. mPoints[i * 4 + 3] = height - mBytes[i];
  59. }
  60. canvas.drawLines(mPoints, mForePaint);
  61. }
  62. }

第三步:通过URI新建一个MediaPlayer对象,其他方式当执行getAudioSessionId()时将为null

  1. //uri = Uri.parse(AudioPath);  // 解析录音文件路径到uri
  2. uri = Uri.parse(Mp3Path);  // 解析MP3文件路径到uri
  3. mMedia = MediaPlayer.create(this, uri);  // 实例化mMedia对象,并通过uri将资源文件加载到该对象

调用Android SDK 2.3以上版本中一个工具包android.media.audiofx.Visualizer,程序需要做的就是实例化一个Visualizer对象,通过获得一个实例化的音频媒体类对象的SessionId,并设置该对象的需要转换的音乐内容长度和采样率。最后为visualizer设置监听器setDataCaptureListener(OnDataCaptureListener listener, rate, iswave, isfft),当采样得到的数据长度达到之前设置的内容长度后,将会触发两个函数,在这两个函数中即可分别得到音频信号的时域信号数据以及经过快速傅里叶变换(FFT)处理的频域信号数据,均为字节数组形式(即:byte[])。

  1. mWaveView = new VisualizerView(this);   // 创建VisualizerView对象
  2. mFFtView = new VisualizerFFTView(this);   // 创建VisualizerFFTView对象
  3. final int maxCR = Visualizer.getMaxCaptureRate();   // 获取最大采样率
  4. mVisualizer = new Visualizer(mMedia.getAudioSessionId());   // 实例化mVisualizer
  5. mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);   // 设置内容长度为1024
  6. mVisualizer.setDataCaptureListener(
  7. new Visualizer.OnDataCaptureListener()
  8. {
  9. public void onWaveFormDataCapture(Visualizer visualizer,
  10. byte[] waveform, int samplingRate)
  11. {
  12. mWaveView.updateVisualizer(waveform);  // 更新时域波形数据
  13. }
  14. public void onFftDataCapture(Visualizer visualizer,
  15. byte[] fft, int samplingRate)
  16. {
  17. mFFtView.updateVisualizer(fft);  // 更新频域波形数据
  18. }
  19. }, maxCR / 2, true, true);   // 采样速率为512MHz,设置同时获取时域、频域波形数据

需要注意的是:停止播放时,除了release播放类对象外,还要释放Visualizer对象。
音频信号FFT效果图:

Android平台音频信号FFT的实现
源码下载链接: