android音乐柱状频谱实现

时间:2021-11-13 19:50:56

from: http://blog.csdn.net/topgun38/article/details/7663849

原文地址:http://blog.csdn.net/caryee89/article/details/6935237

注意android2.3以后才可用,主要用到这个类Visualizer,这个源码其实是apiDemos中一个例子,但例子中实现的是两种中的波形显示,而不是频谱显示,

原文博主实现了另一种频谱显示,并分享出来,精神可嘉。我做了些修改,使稍微好看了些,继续分享。

官方文档解释:

public int getFft (byte[] fft)

Since: API Level 9

Returns a frequency capture of currently playing audio content.

This method must be called when the Visualizer is enabled.

The capture is an 8-bit magnitude FFT, the frequency range covered being 0 (DC) to half of the sampling rate returned by getSamplingRate(). The capture returns the real and imaginary parts of a number of frequency points equal to half of the capture size plus one.

Note: only the real part is returned for the first point (DC) and the last point (sampling frequency / 2).

The layout in the returned byte array is as follows:

  • n is the capture size returned by getCaptureSize()
  • Rfk, Ifk are respectively the real and imaginary parts of the kth frequency component
  • If Fs is the sampling frequency retuned by getSamplingRate() the kth frequency is: (k*Fs)/(n/2)
Index 0 1 2 3 4 5 ... n - 2 n - 1
Data Rf0 Rf(n/2) Rf1 If1 Rf2 If2 ... Rf(n-1)/2 If(n-1)/2
Parameters
fft array of bytes where the FFT should be returned
Returns

实部和虚部的平方和就是振幅的平方,因为是byte类型,所以最大值是127。

对原文的代码做了一些修改,使更好看一些,代码中用到的歌曲谁要用到,自己重新放一首就行,代码如下:

  1. /*
  2. * Copyright (C) 2010 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. *      http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.AudioFx;
  17. import android.app.Activity;
  18. import android.content.Context;
  19. import android.graphics.Canvas;
  20. import android.graphics.Color;
  21. import android.graphics.Paint;
  22. import android.graphics.Rect;
  23. import android.media.AudioManager;
  24. import android.media.MediaPlayer;
  25. import android.media.audiofx.Equalizer;
  26. import android.media.audiofx.Visualizer;
  27. import android.os.Bundle;
  28. import android.util.Log;
  29. import android.view.Gravity;
  30. import android.view.View;
  31. import android.view.ViewGroup;
  32. import android.view.WindowManager;
  33. import android.widget.LinearLayout;
  34. import android.widget.SeekBar;
  35. import android.widget.TextView;
  36. public class AudioFxActivity extends Activity
  37. {
  38. private static final String TAG = "AudioFxActivity";
  39. private static final float VISUALIZER_HEIGHT_DIP = 160f;
  40. private MediaPlayer mMediaPlayer;
  41. private Visualizer mVisualizer;
  42. private Equalizer mEqualizer;
  43. private LinearLayout mLinearLayout;
  44. private VisualizerView mVisualizerView;
  45. private TextView mStatusTextView;
  46. private TextView mInfoView;
  47. @Override
  48. public void onCreate(Bundle icicle)
  49. {
  50. super.onCreate(icicle);
  51. mStatusTextView = new TextView(this);
  52. mLinearLayout = new LinearLayout(this);
  53. mLinearLayout.setOrientation(LinearLayout.VERTICAL);
  54. mLinearLayout.addView(mStatusTextView);
  55. setContentView(mLinearLayout);
  56. // Create the MediaPlayer
  57. mMediaPlayer = MediaPlayer.create(this, R.raw.my_life);
  58. Log.d(TAG,
  59. "MediaPlayer audio session ID: "
  60. + mMediaPlayer.getAudioSessionId());
  61. setupVisualizerFxAndUI();
  62. setupEqualizerFxAndUI();
  63. // Make sure the visualizer is enabled only when you actually want to
  64. // receive data, and
  65. // when it makes sense to receive data.
  66. mVisualizer.setEnabled(true);
  67. // When the stream ends, we don't need to collect any more data. We
  68. // don't do this in
  69. // setupVisualizerFxAndUI because we likely want to have more,
  70. // non-Visualizer related code
  71. // in this callback.
  72. mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
  73. {
  74. public void onCompletion(MediaPlayer mediaPlayer)
  75. {
  76. mVisualizer.setEnabled(false);
  77. getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  78. setVolumeControlStream(AudioManager.STREAM_SYSTEM);
  79. mStatusTextView.setText("音乐播放完毕");
  80. }
  81. });
  82. getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  83. setVolumeControlStream(AudioManager.STREAM_MUSIC);
  84. mMediaPlayer.start();
  85. mStatusTextView.setText("播放音乐中....");
  86. }
  87. private void setupEqualizerFxAndUI()
  88. {
  89. // Create the Equalizer object (an AudioEffect subclass) and attach it
  90. // to our media player,
  91. // with a default priority (0).
  92. mEqualizer = new Equalizer(0, mMediaPlayer.getAudioSessionId());
  93. mEqualizer.setEnabled(true);
  94. TextView eqTextView = new TextView(this);
  95. eqTextView.setText("均衡器:");
  96. mLinearLayout.addView(eqTextView);
  97. short bands = mEqualizer.getNumberOfBands();
  98. final short minEQLevel = mEqualizer.getBandLevelRange()[0];
  99. final short maxEQLevel = mEqualizer.getBandLevelRange()[1];
  100. for (short i = 0; i < bands; i++)
  101. {
  102. final short band = i;
  103. TextView freqTextView = new TextView(this);
  104. freqTextView.setLayoutParams(new ViewGroup.LayoutParams(
  105. ViewGroup.LayoutParams.FILL_PARENT,
  106. ViewGroup.LayoutParams.WRAP_CONTENT));
  107. freqTextView.setGravity(Gravity.CENTER_HORIZONTAL);
  108. freqTextView.setText((mEqualizer.getCenterFreq(band) / 1000)
  109. + " Hz");
  110. mLinearLayout.addView(freqTextView);
  111. LinearLayout row = new LinearLayout(this);
  112. row.setOrientation(LinearLayout.HORIZONTAL);
  113. TextView minDbTextView = new TextView(this);
  114. minDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
  115. ViewGroup.LayoutParams.WRAP_CONTENT,
  116. ViewGroup.LayoutParams.WRAP_CONTENT));
  117. minDbTextView.setText((minEQLevel / 100) + " dB");
  118. TextView maxDbTextView = new TextView(this);
  119. maxDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
  120. ViewGroup.LayoutParams.WRAP_CONTENT,
  121. ViewGroup.LayoutParams.WRAP_CONTENT));
  122. maxDbTextView.setText((maxEQLevel / 100) + " dB");
  123. LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
  124. ViewGroup.LayoutParams.FILL_PARENT,
  125. ViewGroup.LayoutParams.WRAP_CONTENT);
  126. layoutParams.weight = 1;
  127. SeekBar bar = new SeekBar(this);
  128. bar.setLayoutParams(layoutParams);
  129. bar.setMax(maxEQLevel - minEQLevel);
  130. bar.setProgress(mEqualizer.getBandLevel(band));
  131. bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener()
  132. {
  133. public void onProgressChanged(SeekBar seekBar, int progress,
  134. boolean fromUser)
  135. {
  136. mEqualizer.setBandLevel(band, (short) (progress + minEQLevel));
  137. }
  138. public void onStartTrackingTouch(SeekBar seekBar)
  139. {
  140. }
  141. public void onStopTrackingTouch(SeekBar seekBar)
  142. {
  143. }
  144. });
  145. row.addView(minDbTextView);
  146. row.addView(bar);
  147. row.addView(maxDbTextView);
  148. mLinearLayout.addView(row);
  149. }
  150. }
  151. private void setupVisualizerFxAndUI()
  152. {
  153. mVisualizerView = new VisualizerView(this);
  154. mVisualizerView.setLayoutParams(new ViewGroup.LayoutParams(
  155. ViewGroup.LayoutParams.FILL_PARENT,
  156. (int) (VISUALIZER_HEIGHT_DIP * getResources()
  157. .getDisplayMetrics().density)));
  158. mLinearLayout.addView(mVisualizerView);
  159. mInfoView = new TextView(this);
  160. String infoStr = "";
  161. int[] csr = Visualizer.getCaptureSizeRange();
  162. if(csr != null)
  163. {
  164. String csrStr = "CaptureSizeRange: ";
  165. for(int i = 0; i < csr.length; i ++)
  166. {
  167. csrStr += csr[i];
  168. csrStr +=" ";
  169. }
  170. infoStr += csrStr;
  171. }
  172. final int maxCR = Visualizer.getMaxCaptureRate();
  173. infoStr = infoStr + "\nMaxCaptureRate: " + maxCR;
  174. mInfoView.setText(infoStr);
  175. mLinearLayout.addView(mInfoView);
  176. mVisualizer = new Visualizer(mMediaPlayer.getAudioSessionId());
  177. mVisualizer.setCaptureSize(256);
  178. mVisualizer.setDataCaptureListener(
  179. new Visualizer.OnDataCaptureListener()
  180. {
  181. public void onWaveFormDataCapture(Visualizer visualizer,
  182. byte[] bytes, int samplingRate)
  183. {
  184. mVisualizerView.updateVisualizer(bytes);
  185. }
  186. public void onFftDataCapture(Visualizer visualizer,
  187. byte[] fft, int samplingRate)
  188. {
  189. mVisualizerView.updateVisualizer(fft);
  190. }
  191. }, maxCR / 2, false, true);
  192. }
  193. @Override
  194. protected void onPause()
  195. {
  196. super.onPause();
  197. if (isFinishing() && mMediaPlayer != null)
  198. {
  199. mVisualizer.release();
  200. mEqualizer.release();
  201. mMediaPlayer.release();
  202. mMediaPlayer = null;
  203. }
  204. }
  205. /**
  206. * A simple class that draws waveform data received from a
  207. * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture }
  208. */
  209. class VisualizerView extends View
  210. {
  211. private byte[] mBytes;
  212. private float[] mPoints;
  213. private Rect mRect = new Rect();
  214. private Paint mForePaint = new Paint();
  215. private int mSpectrumNum = 48;
  216. private boolean mFirst = true;
  217. public VisualizerView(Context context)
  218. {
  219. super(context);
  220. init();
  221. }
  222. private void init()
  223. {
  224. mBytes = null;
  225. mForePaint.setStrokeWidth(8f);
  226. mForePaint.setAntiAlias(true);
  227. mForePaint.setColor(Color.rgb(0, 128, 255));
  228. }
  229. public void updateVisualizer(byte[] fft)
  230. {
  231. if(mFirst )
  232. {
  233. mInfoView.setText(mInfoView.getText().toString() + "\nCaptureSize: " + fft.length);
  234. mFirst = false;
  235. }
  236. byte[] model = new byte[fft.length / 2 + 1];
  237. model[0] = (byte) Math.abs(fft[0]);
  238. for (int i = 2, j = 1; j < mSpectrumNum;)
  239. {
  240. model[j] = (byte) Math.hypot(fft[i], fft[i + 1]);
  241. i += 2;
  242. j++;
  243. }
  244. mBytes = model;
  245. invalidate();
  246. }
  247. @Override
  248. protected void onDraw(Canvas canvas)
  249. {
  250. super.onDraw(canvas);
  251. if (mBytes == null)
  252. {
  253. return;
  254. }
  255. if (mPoints == null || mPoints.length < mBytes.length * 4)
  256. {
  257. mPoints = new float[mBytes.length * 4];
  258. }
  259. mRect.set(0, 0, getWidth(), getHeight());
  260. //绘制波形
  261. // for (int i = 0; i < mBytes.length - 1; i++) {
  262. // mPoints[i * 4] = mRect.width() * i / (mBytes.length - 1);
  263. // mPoints[i * 4 + 1] = mRect.height() / 2
  264. // + ((byte) (mBytes[i] + 128)) * (mRect.height() / 2) / 128;
  265. // mPoints[i * 4 + 2] = mRect.width() * (i + 1) / (mBytes.length - 1);
  266. // mPoints[i * 4 + 3] = mRect.height() / 2
  267. // + ((byte) (mBytes[i + 1] + 128)) * (mRect.height() / 2) / 128;
  268. // }
  269. //绘制频谱
  270. final int baseX = mRect.width()/mSpectrumNum;
  271. final int height = mRect.height();
  272. for (int i = 0; i < mSpectrumNum ; i++)
  273. {
  274. if (mBytes[i] < 0)
  275. {
  276. mBytes[i] = 127;
  277. }
  278. final int xi = baseX*i + baseX/2;
  279. mPoints[i * 4] = xi;
  280. mPoints[i * 4 + 1] = height;
  281. mPoints[i * 4 + 2] = xi;
  282. mPoints[i * 4 + 3] = height - mBytes[i];
  283. }
  284. canvas.drawLines(mPoints, mForePaint);
  285. }
  286. }
  287. }

运行效果如下:

android音乐柱状频谱实现