Bootstrap

Android:音乐特效控制

音乐特效控制

标签(空格分隔): android
作者:陈小默
水平有限,如果错误恳请批评指正


Android提供了用于音乐播放时的音效控制器,比如均衡器、重低音以及显示音乐波形等。这些功能被定义在AudioEffect的子类中完成1

  • AcousticEchoCanceler:回声消除器
  • AutomaticGainControl:自动增强控制器
  • NoiseSuppressor:噪音抑制器
  • BassBoost:重低音调节器
  • Equalizer:均衡器
  • PresetReverb:预设音场控制器
  • Visualizer:示波器

Demo下载地址Android:音乐特效控制demo 注意:Demo中如果跟该博客有冲突,以博客为准。在正文开始之前,我先说一声,我的音频文件(jay_qingtian.mp3)放在了手机的Music文件夹下。下面所有播放按钮的实现都是下面的样子,所以在下面各小节中我就省略不写播放的实现了

val music = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).absolutePath}/jay_qingtian.mp3"
private val mp = MediaPlayer()
...
XXX.setOnClickListener { v ->
    v.isEnabled = false
    mp.setDataSource(music)
    mp.prepare()
    mp.start()
}

Demo首页截图


一、AcousticEchoCanceler:回声消除器

回声消除器的用法相当简单,只要调用它的静态方法创建相应的实例即可调用。但是需要注意的是:不是所有的手机都支持这个功能,所以我们在使用之前应该提前调用isAvailabel()方法判断是否可用。
回声消除界面

    //初始化媒体播放器和回声消除器
    private val mp = MediaPlayer()
    private val canceler = AcousticEchoCanceler.create(mp.audioSessionId)
    ...
    //在回声控制按钮的点击事件中判断可用状态
    aecFunction.setOnClickListener { v ->
        if (AcousticEchoCanceler.isAvailable()) {
            canceler.enabled = !canceler.enabled
        } else {
            Toast.makeText(this, "您的手机不支持回声控制", Toast.LENGTH_SHORT).show()
        }
    }

对了,很重要的一点是在退出Activity的时候关闭媒体播放器

    override fun onDestroy() {
        if (mp.isPlaying) {
            mp.stop()
        }
        canceler?.release()
        super.onDestroy()
    }

二、AutomaticGainControl:自动增强控制器

自动增强控制器页面截图
这个控制器同样基于系统实现,不同的手机可能支持情况也不相同。使用时仍然是三步走:
1,初始化播放器可控制器

    private val mp = MediaPlayer()
    private val control = AutomaticGainControl.create(mp.audioSessionId)

2,点击自动增强时判断状态

agcFunction.setOnClickListener {
    if (AutomaticGainControl.isAvailable()) {
        control.enabled = !control.enabled
    } else {
        Toast.makeText(this, "您的手机不支持自动增强", Toast.LENGTH_SHORT).show()
    }
}

3,离开时关闭(同上)


三、NoiseSuppressor:噪音抑制器

噪音抑制器页面
噪音抑制器的用法与前两个别无二致。主要用法同上

    private val mp = MediaPlayer()
    private var suppressor = NoiseSuppressor.create(mp.audioSessionId)
    ...
    nsFunction.setOnClickListener {
        if (NoiseSuppressor.isAvailable()) {
            suppressor.enabled = !suppressor.enabled
        } else {
            Toast.makeText(this, "您的手机不支持噪音消除", Toast.LENGTH_SHORT).show()
        }
    }

四、BassBoost:重低音调节器

重低音调节器界面
Android中给重低音调节器设置了1000个级别,也就是当我们的seekBar的最大值就是1000。接下来我们看一看重低音调节器的用法
1,初始化

    private val mp = MediaPlayer()
    private val mBass = BassBoost(0, mp.audioSessionId)

其第一个参数代表该音效控制器的优先级,这里设置为0,第二个参数仍然是MediaPlayer的id。
2,给SeekBar设置监听使之能够控制重低音调节器。

        bbSeekBar.max = 1000
        bbSeekBar.progress = 0
        bbSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
                bbCurrent.text = "$progress"
                mBass.setStrength(progress.toShort())
            }

            override fun onStartTrackingTouch(seekBar: SeekBar) {
            }

            override fun onStopTrackingTouch(seekBar: SeekBar) {
            }
        })

五、Equalizer:均衡器

均衡器演示页面
简单说明一下均衡器的原理:调整某一频率声音的分贝。
所以我们在使用均衡器之前需要先获取当前系统支持的所有可调整的频率,从我上面的截图可以看出我的手机目前仅支持60、230、910、3600、14000Hz这些频率。
1,获取能够设置的最小和最大分贝数

        val minLevel = mEqualizer.bandLevelRange[0]
        val maxLevel = mEqualizer.bandLevelRange[1]

2,获取均衡器支持的所有频率

val bands = mEqualizer.numberOfBands

3,针对每一种频率设置布局并给SeekBar设置滑动事件

for (i in 0..bands - 1) {
    val view = layoutInflater.inflate(R.layout.item_e, null)
    val holder = ItemHolder()
    holder.bind(view)
    //设置该均衡器控制器频率
    holder.eCurrent!!.text = "${mEqualizer.getCenterFreq(i.toShort()) / 1000} Hz"
    //设置均衡器最小值
    holder.eMin!!.text = "${minLevel / 100} dB"
    //设置均衡器最大值
    holder.eMax!!.text = "${maxLevel / 100} dB"
    //给SeekBar设置值和拖动监听
    holder.eSeekBar!!.max = maxLevel - minLevel
    holder.eSeekBar.progress = mEqualizer.getBandLevel(i.toShort()).toInt()
    holder.eSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
        override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
            mEqualizer.setBandLevel(i.toShort(), (progress + minLevel).toShort())
        }
        override fun onStartTrackingTouch(seekBar: SeekBar?) {
        }
        override fun onStopTrackingTouch(seekBar: SeekBar?) {
        }
    })
    activity_e.addView(view)
}

六、PresetReverb:预设音场控制器


对于音乐小白们来说,调音乐真是一件麻烦事,还好Android内置了一些音场供我们选择,接下来我们看看怎么设置系统设置的音场吧:
1,初始化(这里初始化的除了音场控制器还有一个均衡器,另外用一个List存储预设音场的名字)

    private val mp = MediaPlayer()
    private val mPresetReverb = PresetReverb(0, mp.audioSessionId)
    private val mEqualizer = Equalizer(0, mp.audioSessionId)
    private val nameList = ArrayList<String>()

2,获取所有预设名字

        for (i in 0..mEqualizer.numberOfPresets - 1) {
            nameList.add(mEqualizer.getPresetName(i.toShort()))
        }

3,使用Spinner作为选择工具,并且在选择监听中使用均衡器(预设音场控制器只定义了音场,具体实现需要均衡器)

        prSpinner.adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, nameList)
        //设置选择监听
        prSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onNothingSelected(parent: AdapterView<*>?) {
            }

            override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
                mEqualizer.usePreset(position.toShort())
            }
        }

七、Visualizer:示波器

示波器是用来显示波形的控件,但是这不是一个View,它只是提供了当前的波形信息(byte数组),我们需要自定义View去显示,这里我们采用了三种方式去显示波形
1,波形图
波形图
2,窄柱状图
窄柱状图
3,宽柱状图
宽柱状图
为了能够实现上述效果需要我们自定义一个MyVisualizerView,这个类的完整代码可在Demo中查看,但是Demo中onDraw方法有错误,以博客为准。
首先我们定义这些属性

    var bytes: ByteArray? = null
    var points: FloatArray? = null
    val paint = Paint()
    var type = 0

第一个用来存放示波器发送来的波形信息,第二个属性用来存放绘制波形图的点信息,第三个是画笔,第四个是表示波形显示使用哪一种方式
现在我们需要View能够接收到示波器发送过来的波形信息,于是提供了接受信息的方法

    fun updateVisualizer(ftt: ByteArray) {
        bytes = ftt
        invalidate()
    }

接收到信息后我们就可以在onDraw方法中绘制波形图了,以下根据不同的情形采用了三种不同的绘制方式。

override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        bytes ?: return
        //绘制白色背景
        canvas.drawColor(Color.WHITE)
        when (type) {
            0 -> {
                //绘制窄柱状图
                for (i in 0..bytes!!.size - 2) {
                    val left = width * i / (bytes!!.size - 1)
                    val top = height - (bytes!![i + 1] + 128).toByte() * height / 64
                    val right = left + 1
                    val bottom = height
                    canvas.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), paint)
                }
            }
            1 -> {
                //绘制宽柱状图
                for (i in 0..bytes!!.size - 2 step 18) {
                    val left = width * i / (bytes!!.size - 1)
                    val top = height - (bytes!![i + 1] + 128).toByte() * height / 64
                    val right = left + 12
                    val bottom = height
                    canvas.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), paint)
                }
            }
            2 -> {
                //绘制曲线波形图
                if (points == null || points!!.size < bytes!!.size)
                    points = FloatArray(bytes!!.size * 4)
                for (i in 0..bytes!!.size - 2) {
                    points!![i * 4] = width.toFloat() * i / (bytes!!.size - 1)
                    points!![i * 4 + 1] = height.toFloat() / 2 + (bytes!![i] + 128).toByte() * 128 / (height / 2)
                    points!![i * 4 + 2] = width.toFloat() * (i + 1) / (bytes!!.size - 1)
                    points!![i * 4 + 3] = height.toFloat() / 2 + (bytes!![i + 1] + 128).toByte() * 128 / (height / 2)
                }
                canvas.drawLines(points, paint)
            }
            else -> {
                Log.e("---error---", "error type")
            }
        }

当然我们的需要状态切换,所以还要个View添加一个触摸事件监听

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (event.action != MotionEvent.ACTION_DOWN) return false
        type = (type + 1) % 3
        return true
    }

View做完了,又该如何获取到波形信息呢?
我们需要在Activity中采用如下方式给示波器设置监听,并且将监听到的波形变化发送给我们自定义的myViaualizerView对象来显式

        mVisualizer.captureSize = Visualizer.getCaptureSizeRange()[1]
        mVisualizer.setDataCaptureListener(object : Visualizer.OnDataCaptureListener {
            override fun onFftDataCapture(visualizer: Visualizer?, fft: ByteArray?, samplingRate: Int) {
            }

            override fun onWaveFormDataCapture(visualizer: Visualizer, waveform: ByteArray, samplingRate: Int) {
                myVisualizerView.updateVisualizer(waveform)
            }
        }, Visualizer.getMaxCaptureRate() / 2, true, true)
        mVisualizer.enabled = true

  1. 李刚.疯狂安卓讲义 2th.电子工业出版社.528-536
;