相关文章推荐
爱喝酒的楼房  ·  Android OpenSL ES ...·  5 天前    · 
强健的豌豆  ·  snd_pcm_mmap_writei与sn ...·  1 月前    · 
稳重的八宝粥  ·  01 ...·  1 月前    · 
博学的圣诞树  ·  C/C++ ...·  8 月前    · 
聪明的绿茶  ·  C# WPF ...·  1 年前    · 

在Android2.3中增加了对音频混响的支持,这些API包含在 android.media.audiofx 包中。
AudioEffect是android audio framework(android 音频框架)提供的音频效果控制的基类。开发者不能直接使用此类,应该使用它的派生类。派生类有: Equalizer Visualizer BassBoost PresetReverb EnvironmentalReverb
这篇文章我们主要讲一下Visualizer,其他的这里简单介绍一下!
Equalizer ,均衡器:
这个设置我们可以增加或降低某一频率的声音响度来达到想要的效果!
BassBoost ,重低音控制器:
这个设置我们可以增加低音的强度,一般在电子音乐中使用的比较多!
PresetReverb ,预设音场控制器, EnvironmentalReverb ,环境混响控制器
这两个比较特殊,Reverb,回响,这两个设置会使音乐通过声音在不同路径传播下造成的反射叠加产生的声音特效!举个例子:比如流行,古典,爵士,电子等或者比如马路,走廊,室内,大厅等!
推荐在游戏场景中应用EnvironmentalReverb,在音乐场景中应用PresetReverb。如果混响作用于主要的音频输出混音器(mix)上,混响将会话ID指定为0需要 "android.permission.MODIFY_AUDIO_SETTINGS" 权限。

下面到我们的重点Visualizer,就如字面意思所示,可视化!使用它可以将音频直观的反映的界面上!是Android Audio框架中比较重要的一块内容。在官方的Demo中,我们可以了解它的使用:
①获取实例:

visualizer = new Visualizer(mediaPlayer.getAudioSessionId());

②设置采样值:

visualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);

通过Visualizer.getCaptureSizeRange()这一底层实现的方法来返回一个采样值的范围数组,0为最小值128,1为最大值1024!采样值都为2的n次幂!
③设置监听器

setDataCaptureListener(OnDataCaptureListener listener, rate,iswave,isfft )

先说后面三个参数:rate采样的频率,下边通过方法Visualizer.getMaxCaptureRate()返回最大的采样频率,单位为milliHertz毫赫兹,iswave是波形信号,isfft是频域信号。
第一个参数OnDataCaptureListener接口,这里可以一个它的匿名内部类,然后它有两个回调方法:

onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate)
onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate)

这两个回调对应着上边的两个参数iswave和isfft!如果iswave为true,isfft为false则会回调onWaveFormDataCapture方法,如果iswave为false,isfft为true则会回调onFftDataCapture方法
说到这不得不提一下数字信号处理相关的知识,FFT(Fast Fourier Transformation),即快速傅里叶转换,它用于把时域上连续的信号(波形)强度转换成离散的频域信号(频谱)。

这里说一下两个回调方法中的第二个参数byte[] waveform和fft,waveform是波形采样的字节数组,它包含一系列的8位(无符号)的PCM单声道样本,fft是经过FFT转换后频率采样的字节数组,频率范围为0(直流)到采样值的一半!
这里写图片描述
返回的数据如上图所示:n为采样值;Rf和lf分别对应第k个频率的实部和虚部;如果Fs为采样频率,那么第k个频率为(k*Fs)/(n/2)
④开始采样

visualizer.setEnabled(true);

注意:这个方法必须在前面的设置都完成之后调用!

这里我们播放一首歌,然后采样得到波形数据,然后绘制在界面上!
MainActivity

import android.media.MediaPlayer;
import android.media.audiofx.Visualizer;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
    private MediaPlayer mediaPlayer;
    private Visualizer visualizer;
    private VisualizerView visualizerView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        visualizerView= (VisualizerView) findViewById(R.id.myview);
        mediaPlayer=MediaPlayer.create(this,R.raw.goodbye_my_lover);
        init();
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            public void onCompletion(MediaPlayer mediaPlayer) {
                visualizer.setEnabled(false);//歌曲流完毕,则不再采样
        });
        mediaPlayer.start();
    private void init(){
        visualizer = new Visualizer(mediaPlayer.getAudioSessionId());
        visualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
        Log.e("CaptureSizeRange",Visualizer.getCaptureSizeRange()[1]+"");//0为128;1为1024
        visualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener(){
            @Override
            public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) {
                Log.e("onWaveFormDataCapture","调用了!");                visualizerView.updateVisualizer(waveform);
            @Override
            public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
                Log.e("onFftDataCapture","调用了!");
        } , Visualizer.getMaxCaptureRate()/2, true, false);
        visualizer.setEnabled(true);//这个设置必须在参数设置之后,表示开始采样
    @Override
    protected void onPause() {
        super.onPause();
        if (isFinishing() && mediaPlayer != null) {
            visualizer.release();
            mediaPlayer.release();
            mediaPlayer = null;

VisualizerView

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
 * Created by Admin on 2016/11/15.
public class VisualizerView extends View {
    private byte[] mBytes;
    private float[] mPoints;
    private Rect mRect = new Rect();
    private Paint mForePaint = new Paint();
    public VisualizerView(Context context) {
        super(context);
        init();
    public VisualizerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    private void init() {
        mBytes = null;
        mForePaint.setStrokeWidth(1f);
        mForePaint.setAntiAlias(true);
        mForePaint.setColor(Color.rgb(0, 128, 255));
    public void updateVisualizer(byte[] bytes) {
        mBytes = bytes;
        invalidate();
    private byte type = 0;
    @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 (mBytes == null) {
            return;
        mRect.set(0,0,getWidth(),getHeight());
        switch(type) {
            // -------绘制块状的波形图-------
            case 0:
                for (int i = 0; i < mBytes.length - 1; i++) {
                    float left = getWidth() * i / (mBytes.length - 1);
                    // 根据波形值计算该矩形的高度
                    float top = mRect.height()/2 - (byte) (mBytes[i + 1] + 128)
                            * (mRect.height()/2) / 128;
                    float right = left + 1;
                    float bottom = mRect.height()/2;
                    canvas.drawRect(left, top, right, bottom, mForePaint);
                break;
            // -------绘制柱状的波形图(每隔18个抽样点绘制一个矩形)-------
            case 1:
                for (int i = 0; i < mBytes.length - 1; i += 18) {
                    float left = mRect.width() * i / (mBytes.length - 1);
                    // 根据波形值计算该矩形的高度
                    float top = mRect.height()/2 - (byte) (mBytes[i + 1] + 128)
                            * (mRect.height()/2) / 128;
                    float right = left + 6;
                    float bottom = mRect.height()/2;
                    canvas.drawRect(left, top, right, bottom, mForePaint);
                break;
            // -------绘制曲线波形图-------
            case 2:
                // 如果point数组还未初始化
                if (mPoints == null || mPoints.length < mBytes.length * 4) {
                    mPoints = new float[mBytes.length * 4];
                for (int i = 0; i < mBytes.length - 1; i++) {
                    // 计算第i个点的x坐标
                    mPoints[i * 4] = mRect.width() * i / (mBytes.length - 1);
                    // 根据bytes[i]的值(波形点的值)计算第i个点的y坐标
                    mPoints[i * 4 + 1] = mRect.height() / 2
                            + ((byte) (mBytes[i] + 128)) * (mRect.height() / 2) / 128;
                    // 计算第i+1个点的x坐标
                    mPoints[i * 4 + 2] = mRect.width() * (i + 1) / (mBytes.length - 1);
                    // 根据bytes[i+1]的值(波形点的值)计算第i+1个点的y坐标
                    mPoints[i * 4 + 3] = mRect.height() / 2
                            + ((byte) (mBytes[i + 1] + 128)) * (mRect.height() / 2) / 128;
                // 绘制波形曲线
                canvas.drawLines(mPoints, mForePaint);
                break;

最后别忘了声明权限:

<uses-permission android:name="android.permission.RECORD_AUDIO"/>

这样就大功告成了!

如果想要展示频谱图:则在onFftDataCapture回调方法中这样做,这个是在采样本为128的情况下写的,采样本长度越长,FFT算法运行时间就越长!

byte[] model = new byte[fft.length / 2 + 1];
model[0] = (byte) Math.abs(fft[1]);
int j = 1;
for (int i = 2; i < 18;) {
	model[j] = (byte) Math.hypot(fft[i], fft[i + 1]);
	i += 2;
	j++;
visualizerView.updateVisualizer(model);

然后在自定义View的ondraw方法中,修改绘制方法:

if (mPoints == null || mPoints.length < mBytes.length * 4) {
	mPoints = new float[mBytes.length * 4];
for (int i = 0; i < 9; i++) {  
	if (mBytes[i] < 0) { 
		mBytes[i] = 127;   
	mPoints[i * 4] = mRect.width() * i / 9;  
	mPoints[i * 4 + 1] = mRect.height() / 2;  
	mPoints[i * 4 + 2] = mRect.width() * i / 9;  
	mPoints[i * 4 + 3] = 2 + mRect.height() / 2 + mBytes[i];  
canvas.drawLines(mPoints, mForePaint);

demo已传github:MyVisualizerDemo

好了,如果想从源码级再深入的了解Android的音频系统,推荐一位大神:林学森
Android音频系统之音频框架
这是一个系列的文章,从音频基础,整个音频框架开始,到系统的核心AudioFlinger,再到AudioPolicyService,最后到应用层的AudioTrack,讲解的比较细致全面!值得一看!

参考文章:
我的Android进阶之旅Android实现音乐示波器、均衡器、重低音和音场功能
Android 音乐频谱实现
android 录音绘制波形
Visualizer 官方API

最后在Github上面找到一个项目,有时间可以了解一下:
ringdroid

前言在Android2.3中增加了对音频混响的支持,这些API包含在android.media.audiofx包中。 AudioEffect是android audio framework(android 音频框架)提供的音频效果控制的基类。开发者不能直接使用此类,应该使用它的派生类。派生类有:Equalizer,Visualizer,BassBoost,PresetReverb,Environm
wlmusic v1.2.6(讨论群:806397913) 基于FFmpeg OpenSL ES的音频播放SDK。可循环不间断播放短音频;播放raw和assets音频文件;可独立设置音量大小;可实时现在音量分贝大小(用于绘制波形图);可改变音频播放速度和音调(变速不变调、变调不变速、变速又变调);可设置播放声道(左声道、右声道和立体声);可边播边录留住美好音乐;可裁剪指定时间段的音频,制作自己的彩铃;还可以从中获取音频原始PCM数据(可指定采样率),方便二次开发等。 我的视频课程(基础):《(NDK)FFmpeg打造Android万能音频播放器》 我的视频课程(进阶):《(NDK)FFmpeg打造Android视频播放器》 我的视频课程(编码直播推流):《Android视频编码和直播推流》 百度网盘链接: https://pan.baidu.com/s/1mvIflaxujEoufLrnyNNxRQ 提取码: mkki 体验视频播放库 请移步:https://github.com/wanliyang1990/wlmedia 8小时持续播放内存使用情况 CPU和内存使用情况:测
NavigationBar 我的视频课程(基础):《(NDK)FFmpeg打造Android万能音频播放器》 我的视频课程(进阶):《(NDK)FFmpeg打造Android视频播放器》 我的视频课程(编码直播推流):《Android视频编码和直播推流》 标题导航栏,两三句代码即可实现 博客地址:http://blog.csdn.net/ywl5320/article/details/51866799 XML布局: 实例图片: NavitationFollowScrollLayout初始化选择指定标题位置: 更多实例图片: update: 代码如下: * @param context 上下文 * @param titles 标题栏 * @param viewPager * @param unselectedcolor 未选中字体颜色 * @param setectedcolor 选中字体颜色 * @param txtUnselectedSize 未选中字体大小 * @pa
WliveTV 我的视频课程(基础):《(NDK)FFmpeg打造Android万能音频播放器》 我的视频课程(进阶):《(NDK)FFmpeg打造Android视频播放器》 我的视频课程(编码直播推流):《Android视频编码和直播推流》 基于ijkplayer的视频直播软件 注:里面加载图片的Imageloader改了加载图片的链接源代码的,才能实现加载视频流获取图片!(用自己的Imageloader是不能通过流获取图片的!!) 包含以下功能: 1、沉浸模式 2、导航栏滑动变色 3、自定义对话框样式(可以加BaseDialog然后在dialog中设置沉浸模式,弹出的对话框就也是沉浸模式的了) 4、popwindow弹出菜单 5、视频播放全屏切换等 6、application获取崩溃日志,并存储到本地 麻雀虽小,五脏还是俱全的 最新apk下载地址:http://android.myapp.com/myapp/detail.htm?apkName=com.ywl5320.wlivetv 效果图如下: 1、直播列表 2、播放界面 3、全屏播放 by:ywl5320
项目是进行android音视频进阶的整个流程学习流程代码,其中包括1.在 Android 平台绘制一张图片,使用至少 3 种不同的 API,ImageView,SurfaceView,自定义 View 2.在 Android 平台使用 AudioRecord 和 AudioTrack API 完成音频 PCM 数据的采集和播放,并实现读写音频 wav 文件 3.在 Android 平台使用 Camera API 进行视频的采集,分别使用 SurfaceView、TextureView 来预览 Camera 数据,取到 NV21 的数据回调 4.学习 Android 平台的 MediaExtractor 和 MediaMuxer API,知道如何解析和封装 mp4 文件 5.学习 Android 平台 OpenGL ES API,了解 OpenGL 开发的基本流程,使用 OpenGL 绘制一个三角形 6.学习 Android 平台 OpenGL ES API,学习纹理绘制,能够使用 OpenGL 显示一张图片 7.学习 MediaCodec API,完成音频 AAC 硬编、硬解 8.学习 MediaCodec API,完成视频 H.264 的硬编、硬解 9.串联整个音视频录制流程,完成音视频的采集、编码、封包成 mp4 输出 10.串联整个音视频播放流程,完成 mp4 的解析、音视频的解码、播放和渲染 11.将 ffmpeg 库移植到 Android 平台,结合上面积累的经验,编写一款简易的音视频播放器
1.必须要获取系统的声音服务权限 &lt;uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/&gt; 2.声明控制声音的变量 //通话音量 AudioManager mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE...
原生安卓系统只能调出15级音量,而市面上很多已上传的音量APP也只能调整这15级音量,这样的调整方式,级别粗糙,不够贴心。 可以用全局均衡器来进一步微调音量,相当于为15个音量级别的每一级额外增加数千个微调等级。 构造全局均衡器(应用于系统级别)与构造一般均衡器(APP内部有效)一样,无需 context,却别全局均衡器是传入的 session id 为零: mEqualizer = new Equalizer(999, 0); 全局均衡器被一部分官方标注为不推荐,甚至废弃,但没有替代方案,也未被彻底舍弃
&lt;uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /&gt; &lt;uses-permission android:name="androi...
最近做的项目是和语音实时采集并发送,对方实时接收并播放相关,下面记录下实现的核心代码。 很多android开发者应该知道android有个MediaRecorder对象和MediaPlayer对象,用于录制和播放音频。这个弊端在于他们不能实时采集并发送出去,所以,我们只能使用AudioRecord和AudioTrack来实现。 记得申明权限:<uses-permission android:na
下面是一个简单的 Android 收音机程序示例,演示如何实现收音机功能并提供一个 UI 界面供用户操作: 创建一个新的 Android 项目,并添加以下权限到 AndroidManifest.xml 文件中: <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTING
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> 页面xml <TextView android:id="@+id/textSound" android:layout... 在聊天场景中,收到对方语音时,用户可以选择外放播放,也可以选择插入耳机收听.更人性化一点当用户把手机靠近耳朵时屏幕关闭自动切换到听筒中播放,播放完毕后拿开手机屏幕自动点亮.比如微信就是如此. 从上面场景中我们可以得出我们需要的要点: 播放模式切换:外放耳机 播放模式切换:外放听筒 屏幕操作:亮屏息屏亮屏 从需求分析我们可以得出需要代
访问登记属性 android.permission.ACCESS_CHECKIN_PROPERTIES ,读取或写入登记check-in数据库属性表的权限  获取错略位置 android.permission.ACCESS_COARSE_LOCATION,通过WiFi或移动基站的方式获取用户错略的经纬度信息,定位精度大概误差在30~1500米  获取精确位置 android.permiss
如果你已经掌握了 Android 开发的基础知识,想要进一步深入了解 Android framework,可以考虑阅读以下几本进阶书籍: 1.《Android 源码设计模式解析与实战》:本书通过对 Android 源码中的设计模式进行讲解,深入解析了 Android framework 的各个模块,包括 Activity、Service、Broadcast、Content Provider 等。同时,本书还提供了大量的实战案例,帮助读者更好地理解和应用这些知识。 2.《深入理解 Android:卷1》:本书是 Android 官方培训课程的配套教材,全面深入地讲解了 Android framework 的各个方面,包括 Activity、Service、Broadcast、Content Provider、View、Layout 等。本书内容全面,适合对 Android framework 有较深入了解的开发者阅读。 3.《Android 源码情景分析》:本书通过对 Android 源码的分析,深入讲解了 Android framework 的各个模块,包括 Activity、Service、Broadcast、Content Provider、View、Layout 等。本书注重实践,提供了大量的代码示例和案例,帮助读者更好地理解和应用这些知识。 以上几本书都是 Android 开发领域的经典著作,如果你想要进一步深入了解 Android framework,可以选择阅读其中一本或多本。
解决Activity跳转后弹出DialogFragment报错Can not perform this action after onSaveInstanceState haomai6666: 大佬厉害呀,解决了,谢谢 INSTALL_FAILED_TEST_ONLY的原因及解决方案 qq_36771930: 还是方法一可以用,非常感谢博主分享 FileNotFoundException: This file can not be opened as a file descriptor; it is probably compressed 小怪吴吴: 可以的,解决了我的问题,我的是wav文件。[code=html] aaptOptions { noCompress "wav" [/code] Git tag的一切 CSDN-Ada助手: 哇, 你的文章质量真不错,值得学习!不过这么高质量的文章, 还值得进一步提升, 以下的改进点你可以参考下: (1)增加内容的多样性(例如使用标准目录、标题、图片、链接、表格等元素);(2)使用标准目录;(3)使用更多的站内链接。 Android打包报错:Lint found fatal errors while assembling a release target. 丢了派大星: 可以,我的错误在as里没有报,找了很久,这个一下指出在哪一行,太感谢了