1. 程式人生 > >Android實現音樂示波器、均衡器、重低音和音場功能

Android實現音樂示波器、均衡器、重低音和音場功能

本例項來自於《瘋狂Android講義》,要實現具體的功能,需要了解以下API: MediaPlayer 媒體播放器Visualizer 頻譜Equalizer 均衡器BassBoost 重低音控制器PresetReverb 預設音場控制器Paint 繪圖


來看下效果示意圖,如下所示

豎狀波形圖:


塊狀波形圖


曲線波形圖


調節均衡器、重低音


選擇音場


下面來看具體的實現程式碼 MediaPlayerTest.java

package com.oyp.media;
 
import java.util.ArrayList;
import java.util.List;
 
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.audiofx.BassBoost;
import android.media.audiofx.Equalizer;
import android.media.audiofx.PresetReverb;
import android.media.audiofx.Visualizer;
import android.os.Bundle;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
 
public class MediaPlayerTest extends Activity
{
    // 定義播放聲音的MediaPlayer
    private MediaPlayer mPlayer;
    // 定義系統的頻譜
    private Visualizer mVisualizer; 
    // 定義系統的均衡器
    private Equalizer mEqualizer;
    // 定義系統的重低音控制器
    private BassBoost mBass;
    // 定義系統的預設音場控制器
    private PresetReverb mPresetReverb;
    private LinearLayout layout;
    private List<short> reverbNames = new ArrayList<short>();
    private List<string> reverbVals = new ArrayList<string>();
 
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        //設定音訊流 - STREAM_MUSIC:音樂回放即媒體音量
        setVolumeControlStream(AudioManager.STREAM_MUSIC);
        layout = new LinearLayout(this);//程式碼建立佈局
        layout.setOrientation(LinearLayout.VERTICAL);//設定為線性佈局-上下排列
        setContentView(layout);//將佈局新增到 Activity
        // 建立MediaPlayer物件,並新增音訊
        // 音訊路徑為  res/raw/beautiful.mp3
        mPlayer = MediaPlayer.create(this, R.raw.beautiful);
        // 初始化示波器
        setupVisualizer();
        // 初始化均衡控制器
        setupEqualizer();
        // 初始化重低音控制器
        setupBassBoost();
        // 初始化預設音場控制器
        setupPresetReverb();
        // 開發播放音樂
        mPlayer.start();
    }
    /**
     * 初始化頻譜
     */
    private void setupVisualizer()
    {
        // 建立MyVisualizerView元件,用於顯示波形圖
        final MyVisualizerView mVisualizerView =
            new MyVisualizerView(this);
        mVisualizerView.setLayoutParams(new ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            (int) (120f * getResources().getDisplayMetrics().density)));
        // 將MyVisualizerView元件新增到layout容器中
        layout.addView(mVisualizerView);
        // 以MediaPlayer的AudioSessionId建立Visualizer
        // 相當於設定Visualizer負責顯示該MediaPlayer的音訊資料
        mVisualizer = new Visualizer(mPlayer.getAudioSessionId());
        //設定需要轉換的音樂內容長度,專業的說這就是取樣,該取樣值一般為2的指數倍,如64,128,256,512,1024。
        mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
        // 為mVisualizer設定監聽器
        /*
         * Visualizer.setDataCaptureListener(OnDataCaptureListener listener, int rate, boolean waveform, boolean fft
         *  
         *      listener,表監聽函式,匿名內部類實現該介面,該介面需要實現兩個函式   
                rate, 表示取樣的週期,即隔多久取樣一次,聯絡前文就是隔多久取樣128個數據
                iswave,是波形訊號
                isfft,是FFT訊號,表示是獲取波形訊號還是頻域訊號
             
         */
        mVisualizer.setDataCaptureListener(
            new Visualizer.OnDataCaptureListener()
            {
                //這個回撥應該採集的是快速傅立葉變換有關的資料
                @Override
                public void onFftDataCapture(Visualizer visualizer,
                    byte[] fft, int samplingRate)
                {
                }
                 //這個回撥應該採集的是波形資料
                @Override
                public void onWaveFormDataCapture(Visualizer visualizer,
                    byte[] waveform, int samplingRate)
                {
                    // 用waveform波形資料更新mVisualizerView元件
                    mVisualizerView.updateVisualizer(waveform);
                }
            }, Visualizer.getMaxCaptureRate() / 2, true, false);
        mVisualizer.setEnabled(true);
    }
     
    /**
     * 初始化均衡控制器
     */
    private void setupEqualizer()
    {
        // 以MediaPlayer的AudioSessionId建立Equalizer
        // 相當於設定Equalizer負責控制該MediaPlayer
        mEqualizer = new Equalizer(0, mPlayer.getAudioSessionId());
        // 啟用均衡控制效果
        mEqualizer.setEnabled(true);
        TextView eqTitle = new TextView(this);
        eqTitle.setText(均衡器:);
        layout.addView(eqTitle);
        // 獲取均衡控制器支援最小值和最大值
        final short minEQLevel = mEqualizer.getBandLevelRange()[0];//第一個下標為最低的限度範圍
        short maxEQLevel = mEqualizer.getBandLevelRange()[1];  // 第二個下標為最高的限度範圍
        // 獲取均衡控制器支援的所有頻率
        short brands = mEqualizer.getNumberOfBands();
        for (short i = 0; i < brands; i++)
        {
            TextView eqTextView = new TextView(this);
            // 建立一個TextView,用於顯示頻率
            eqTextView.setLayoutParams(new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));
            eqTextView.setGravity(Gravity.CENTER_HORIZONTAL);
            // 設定該均衡控制器的頻率
            eqTextView.setText((mEqualizer.getCenterFreq(i) / 1000)
                +  Hz);
            layout.addView(eqTextView);
            // 建立一個水平排列元件的LinearLayout
            LinearLayout tmpLayout = new LinearLayout(this);
            tmpLayout.setOrientation(LinearLayout.HORIZONTAL);
            // 建立顯示均衡控制器最小值的TextView
            TextView minDbTextView = new TextView(this);
            minDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));
            // 顯示均衡控制器的最小值
            minDbTextView.setText((minEQLevel / 100) +  dB);
            // 建立顯示均衡控制器最大值的TextView
            TextView maxDbTextView = new TextView(this);
            maxDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));
            // 顯示均衡控制器的最大值          
            maxDbTextView.setText((maxEQLevel / 100) +  dB);
            LinearLayout.LayoutParams layoutParams = new
                LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
            layoutParams.weight = 1;
            // 定義SeekBar做為調整工具
            SeekBar bar = new SeekBar(this);
            bar.setLayoutParams(layoutParams);
            bar.setMax(maxEQLevel - minEQLevel);
            bar.setProgress(mEqualizer.getBandLevel(i));
            final short brand = i;
            // 為SeekBar的拖動事件設定事件監聽器
            bar.setOnSeekBarChangeListener(new SeekBar
                .OnSeekBarChangeListener()
            {
                @Override
                public void onProgressChanged(SeekBar seekBar,
                    int progress, boolean fromUser)
                {
                    // 設定該頻率的均衡值
                    mEqualizer.setBandLevel(brand,
                        (short) (progress + minEQLevel));
                }
                @Override
                public void onStartTrackingTouch(SeekBar seekBar)
                {
                }
                @Override
                public void onStopTrackingTouch(SeekBar seekBar)
                {
                }
            });
            // 使用水平排列元件的LinearLayout“盛裝”3個元件
            tmpLayout.addView(minDbTextView);
            tmpLayout.addView(bar);
            tmpLayout.addView(maxDbTextView);
            // 將水平排列元件的LinearLayout新增到myLayout容器中
            layout.addView(tmpLayout);
        }
    }
 
    /**
     * 初始化重低音控制器
     */
    private void setupBassBoost()
    {
        // 以MediaPlayer的AudioSessionId建立BassBoost
        // 相當於設定BassBoost負責控制該MediaPlayer
        mBass = new BassBoost(0, mPlayer.getAudioSessionId());
        // 設定啟用重低音效果
        mBass.setEnabled(true);
        TextView bbTitle = new TextView(this);
        bbTitle.setText(重低音:);
        layout.addView(bbTitle);
        // 使用SeekBar做為重低音的調整工具 
        SeekBar bar = new SeekBar(this);
        // 重低音的範圍為0~1000
        bar.setMax(1000);
        bar.setProgress(0);
        // 為SeekBar的拖動事件設定事件監聽器
        bar.setOnSeekBarChangeListener(new SeekBar
            .OnSeekBarChangeListener()
        {
            @Override
            public void onProgressChanged(SeekBar seekBar
                , int progress, boolean fromUser)
            {
                // 設定重低音的強度
                mBass.setStrength((short) progress);
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar)
            {
            }
            @Override
            public void onStopTrackingTouch(SeekBar seekBar)
            {
            }
        });
        layout.addView(bar);
    }
 
    /**
     * 初始化預設音場控制器
     */
    private void setupPresetReverb()
    {
        // 以MediaPlayer的AudioSessionId建立PresetReverb
        // 相當於設定PresetReverb負責控制該MediaPlayer
        mPresetReverb = new PresetReverb(0,
            mPlayer.getAudioSessionId());
        // 設定啟用預設音場控制
        mPresetReverb.setEnabled(true);
        TextView prTitle = new TextView(this);
        prTitle.setText(音場);
        layout.addView(prTitle);
        // 獲取系統支援的所有預設音場
        for (short i = 0; i < mEqualizer.getNumberOfPresets(); i++)
        {
            reverbNames.add(i);
            reverbVals.add(mEqualizer.getPresetName(i));
        }
        // 使用Spinner做為音場選擇工具
        Spinner sp = new Spinner(this);
        sp.setAdapter(new ArrayAdapter<string>(MediaPlayerTest.this,
            android.R.layout.simple_spinner_item, reverbVals));
        // 為Spinner的列表項選中事件設定監聽器
        sp.setOnItemSelectedListener(new Spinner
            .OnItemSelectedListener()
        {
            @Override
            public void onItemSelected(AdapterView<!--?--> arg0
                , View arg1, int arg2, long arg3)
            {
                // 設定音場
                mPresetReverb.setPreset(reverbNames.get(arg2));
            }
 
            @Override
            public void onNothingSelected(AdapterView<!--?--> arg0)
            {
            }
        });
        layout.addView(sp);
    }
 
    @Override
    protected void onPause()
    {
        super.onPause();
        if (isFinishing() && mPlayer != null)
        {
            // 釋放所有物件
            mVisualizer.release();
            mEqualizer.release();
            mPresetReverb.release();
            mBass.release();
            mPlayer.release();
            mPlayer = null;
        }
    }
    /**
     * 根據Visualizer傳來的資料動態繪製波形效果,分別為:
     * 塊狀波形、柱狀波形、曲線波形
     */
    private static class MyVisualizerView extends View
    {
        // bytes陣列儲存了波形抽樣點的值
        private byte[] bytes;
        private float[] points;
        private Paint paint = new Paint();
        private Rect rect = new Rect();
        private byte type = 0;
        public MyVisualizerView(Context context)
        {
            super(context);
            bytes = null;
            // 設定畫筆的屬性
            paint.setStrokeWidth(1f);
            paint.setAntiAlias(true);//抗鋸齒
            paint.setColor(Color.YELLOW);//畫筆顏色
            paint.setStyle(Style.FILL);
        }
 
        public void updateVisualizer(byte[] ftt)
        {
            bytes = ftt;
            // 通知該元件重繪自己。
            invalidate();
        }
         
        @Override
        public boolean onTouchEvent(MotionEvent me)
        {
            // 當用戶觸碰該元件時,切換波形型別
            if(me.getAction() != MotionEvent.ACTION_DOWN)
            {
                return false;
            }
            type ++;
            if(type >= 3)
            {
                type = 0;
            }
            return true;
        }
 
        @Override
        protected void onDraw(Canvas canvas)
        {
            super.onDraw(canvas);
            if (bytes == null)
            {
                return;
            }
            // 繪製白色背景
            canvas.drawColor(Color.WHITE);          
            // 使用rect物件記錄該元件的寬度和高度
            rect.set(0,0,getWidth(),getHeight());
            switch(type)
            {
                // -------繪製塊狀的波形圖-------
                case 0: 
                    for (int i = 0; i < bytes.length - 1; i++)
                    {
                        float left = getWidth() * i / (bytes.length - 1);
                        // 根據波形值計算該矩形的高度        
                        float top = rect.height()-(byte)(bytes[i+1]+128)
                            * rect.height() / 128;
                        float right = left + 1;
                        float bottom = rect.height();
                        canvas.drawRect(left, top, right, bottom, paint);
                    }
                    break;
                // -------繪製柱狀的波形圖(每隔18個抽樣點繪製一個矩形)-------
                case 1:
                    for (int i = 0; i < bytes.length - 1; i += 18)
                    {
                        float left = rect.width()*i/(bytes.length - 1);
                        // 根據波形值計算該矩形的高度
                        float top = rect.height()-(byte)(bytes[i+1]+128)
                            * rect.height() / 128;
                        float right = left + 6;
                        float bottom = rect.height();
                        canvas.drawRect(left, top, right, bottom, paint);
                    }
                    break;
                // -------繪製曲線波形圖-------
                case 2:
                    // 如果point陣列還未初始化
                    if (points == null || points.length < bytes.length * 4)
                    {
                        points = new float[bytes.length * 4];
                    }
                    for (int i = 0; i < bytes.length - 1; i++)
                    {
                        // 計算第i個點的x座標
                        points[i * 4] = rect.width()*i/(bytes.length - 1);
                        // 根據bytes[i]的值(波形點的值)計算第i個點的y座標
                        points[i * 4 + 1] = (rect.height() / 2)
                            + ((byte) (bytes[i] + 128)) * 128
                            / (rect.height() / 2);
                        // 計算第i+1個點的x座標
                        points[i * 4 + 2] = rect.width() * (i + 1)
                            / (bytes.length - 1);
                        // 根據bytes[i+1]的值(波形點的值)計算第i+1個點的y座標
                        points[i * 4 + 3] = (rect.height() / 2)
                            + ((byte) (bytes[i + 1] + 128)) * 128
                            / (rect.height() / 2);
                    }
                    // 繪製波形曲線
                    canvas.drawLines(points, paint);
                    break;
            }
        }
    }   
}


AndroidManifest.xml

<!--?xml version=1.0 encoding=utf-8?-->
<manifest android:versioncode="1" android:versionname="1.0" package="com.oyp.media" xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-sdk android:minsdkversion="10" android:targetsdkversion="17/">
    <!-- 使用音場效果必要的許可權 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO">
     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS/">
     
     
         
            <intent-filter>
                 
                <category android:name="android.intent.category.LAUNCHER">
            </category></action></intent-filter>
        </activity>
    </application>
</uses-permission></uses-permission></uses-sdk></manifest>


請在真機環境下執行此程式,如果在模擬器下執行,可能會報錯