Android音乐频谱显示

Android  小知识  2023年6月2日 am8:10发布2年前 (2023)更新 城堡大人
128 0 0

前言

本文抄的,音乐频谱相关,想单独记录于此,方便自己查阅和回顾。

具体可以看:https://github.com/wanliLiu/androidequalizer

Android音乐频谱显示

PS: 图片截图Github上的

正文

VisualizerWaveView2.java

图中第一个

public class VisualizerWaveView2 extends View {
    // bytes数组保存了波形抽样点的值
    private byte[] mBuffer;
    private float[] mPoints;
    private Paint mPaint = new Paint();
    private Rect mRect = new Rect();
    private byte mType = 0;

    public VisualizerWaveView2(Context context) {
        this(context, null);
    }

    public VisualizerWaveView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VisualizerWaveView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mBuffer = null;
        mPaint.setStrokeWidth(1f);
        mPaint.setAntiAlias(true);//抗锯齿
        mPaint.setColor(Color.GREEN);//画笔颜色
        mPaint.setStyle(Paint.Style.FILL);
    }


    public void updateVisualizer(byte[] ftt) {
        mBuffer = ftt;
        // 通知该组件重绘自己。
        invalidate();
    }

    @Override
    public boolean onTouchEvent(MotionEvent me) {
        // 当用户触碰该组件时,切换波形类型
        if (me.getAction() != MotionEvent.ACTION_DOWN) {
            return false;
        }
        mType++;
        if (mType >= 3) {
            mType = 0;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mBuffer == null) {
            return;
        }
        // 绘制白色背景
//        canvas.drawColor(Color.WHITE);
        // 使用rect对象记录该组件的宽度和高度
        mRect.set(0, 0, getWidth(), getHeight());
        switch (mType) {
            // -------绘制块状的波形图-------
            case 0:
                for (int i = 0; i < mBuffer.length - 1; i++) {
                    float left = getWidth() * i * 1.0f / (mBuffer.length - 1);
                    // 根据波形值计算该矩形的高度
                    float top = mRect.height() - (byte) (mBuffer[i + 1] + 128)
                            * 1.0f * mRect.height() / 128;
                    float right = left + 1;
                    float bottom = mRect.height();
                    canvas.drawRect(left, top, right, bottom, mPaint);
                }
                break;
            // -------绘制柱状的波形图(每隔18个抽样点绘制一个矩形)-------
            case 1:
                for (int i = 0; i < mBuffer.length - 1; i += 18) {
                    float left = mRect.width() * 1.0f * i / (mBuffer.length - 1);
                    // 根据波形值计算该矩形的高度
                    float top = mRect.height() - (byte) (mBuffer[i + 1] + 128)
                            * 1.0f * mRect.height() / 128;
                    float right = left + 6;
                    float bottom = mRect.height();
                    canvas.drawRect(left, top, right, bottom, mPaint);
                }
                break;
            // -------绘制曲线波形图-------
            case 2:
                // 如果point数组还未初始化
                if (mPoints == null || mPoints.length < mBuffer.length * 4) {
                    mPoints = new float[mBuffer.length * 4];
                }
                for (int i = 0; i < mBuffer.length - 1; i++) {
                    // 计算第i个点的x坐标
                    mPoints[i * 4] = mRect.width() * 1.0f * i / (mBuffer.length - 1);
                    // 根据bytes[i]的值(波形点的值)计算第i个点的y坐标
                    mPoints[i * 4 + 1] = (mRect.height() * 1.0f / 2)
                            + ((byte) (mBuffer[i] + 128)) * 128
                            / (mRect.height() * 1.0f / 2);
                    // 计算第i+1个点的x坐标
                    mPoints[i * 4 + 2] = mRect.width() * (i + 1) * 1.0f
                            / (mBuffer.length - 1);
                    // 根据bytes[i+1]的值(波形点的值)计算第i+1个点的y坐标
                    mPoints[i * 4 + 3] = (mRect.height() * 1.0f / 2)
                            + ((byte) (mBuffer[i + 1] + 128)) * 128
                            / (mRect.height() * 1.0f / 2);
                }
                // 绘制波形曲线
                canvas.drawLines(mPoints, mPaint);
                break;
        }
    }
}

VisualizerWaveView.java

效果图中第二个

public class VisualizerWaveView extends View {
    private byte[] mBuffer;
    private float[] mPoints;
    private final Rect mRect = new Rect();
    private final Paint mForePaint = new Paint();

    public VisualizerWaveView(Context context) {
        this(context, null);
    }

    public VisualizerWaveView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VisualizerWaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mBuffer = null;
        mForePaint.setStrokeWidth(1f);
        mForePaint.setAntiAlias(true);
        mForePaint.setColor(Color.GREEN);
    }

    public void updateVisualizer(byte[] waveForm) {
        mBuffer = waveForm;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mBuffer == null) {
            return;
        }
        if (mPoints == null || mPoints.length < mBuffer.length * 4) {
            mPoints = new float[mBuffer.length * 4];
        }
        // 绘制白色背景
//        canvas.drawColor(Color.WHITE);

        mRect.set(0, 0, getWidth(), getHeight());
        //绘制波形
        for (int i = 0; i < mBuffer.length - 1; i++) {
            mPoints[i * 4] = mRect.width() * i * 1.0f / (mBuffer.length - 1);
            mPoints[i * 4 + 1] = mRect.height() * 1.0f / 2
                    + ((byte) (mBuffer[i] + 128)) * (mRect.height() * 1.0f / 2) / 128;
            mPoints[i * 4 + 2] = mRect.width() * (i + 1) * 1.0f / (mBuffer.length - 1);
            mPoints[i * 4 + 3] = mRect.height() * 1.0f / 2
                    + ((byte) (mBuffer[i + 1] + 128)) * (mRect.height() * 1.0f / 2) / 128;
        }
        canvas.drawLines(mPoints, mForePaint);
    }
}

VisualizerFFTView.java

图中第三个

public class VisualizerFFTView extends View {
    private byte[] mBuffer;
    private float[] mPoints;
    private final Rect mRect = new Rect();
    private final Paint mForePaint = new Paint();
    private final int mSpectrumNum = 48;

    public VisualizerFFTView(Context context) {
        this(context, null);
    }

    public VisualizerFFTView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VisualizerFFTView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mBuffer = null;
        mForePaint.setStrokeWidth(8f);
        mForePaint.setAntiAlias(true);
        mForePaint.setColor(Color.rgb(0, 128, 255));
    }

    public void updateVisualizer(byte[] fft) {
        byte[] model = new byte[fft.length / 2 + 1];
        model[0] = (byte) Math.abs(fft[0]);
        for (int i = 2, j = 1; j < mSpectrumNum; ) {
            model[j] = (byte) Math.hypot(fft[i], fft[i + 1]);
            i += 2;
            j++;
        }
        mBuffer = model;
        invalidate();;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mBuffer == null) {
            return;
        }
        if (mPoints == null || mPoints.length < mBuffer.length * 4) {
            mPoints = new float[mBuffer.length * 4];
        }
        // 绘制白色背景
//        canvas.drawColor(Color.WHITE);
        mRect.set(0, 0, getWidth(), getHeight());
        //绘制频谱
        final int baseX = mRect.width() / mSpectrumNum;
        final int height = mRect.height();
        for (int i = 0; i < mSpectrumNum; i++) {
            if (mBuffer[i] < 0) {
                mBuffer[i] = 127;
            }
            final int xi = baseX * i + baseX / 2;
            mPoints[i * 4] = xi;
            mPoints[i * 4 + 1] = height;
            mPoints[i * 4 + 2] = xi;
            mPoints[i * 4 + 3] = height - mBuffer[i];
        }
        canvas.drawLines(mPoints, mForePaint);
    }
}

VisualizerFFTView2.java

图中第四个

public class VisualizerFFTView2 extends View {

    private final int DN_W = 470;//view宽度与单个音频块占比 - 正常480 需微调
    private final int DN_H = 360;//view高度与单个音频块占比
    private final int DN_SL = 15;//单个音频块宽度
    private final int DN_SW = 5;//单个音频块高度

    private int hgap = 0;
    private int vgap = 0;
    private int levelStep = 0;
    private float strokeWidth = 0;
    private float strokeLength = 0;

    protected final static int MAX_LEVEL = 30;//音量柱·音频块 - 最大个数

    protected final static int CYLINDER_NUM = 26;//音量柱 - 最大个数

    protected Paint mPaint = null;//画笔

    protected byte[] mData = new byte[CYLINDER_NUM];//音量柱 数组

    boolean mDataEn = true;

    public VisualizerFFTView2(Context context) {
        this(context, null);
    }

    public VisualizerFFTView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VisualizerFFTView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init(){
        mPaint = new Paint();//初始化画笔工具
        mPaint.setAntiAlias(true);//抗锯齿
        mPaint.setColor(Color.RED);//画笔颜色
        mPaint.setStrokeJoin(Paint.Join.ROUND); //频块圆角
        mPaint.setStrokeCap(Paint.Cap.ROUND); //频块圆角
        levelStep = 230 / MAX_LEVEL;
    }

    //执行 Layout 操作
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        float w, h, xr, yr;

        w = right - left;
        h = bottom - top;
        xr = w / (float) DN_W;
        yr = h / (float) DN_H;

        strokeWidth = DN_SW * yr;
        strokeLength = DN_SL * xr;
        hgap = (int) ((w - strokeLength * CYLINDER_NUM) / (CYLINDER_NUM + 1));
        vgap = (int) (h / (MAX_LEVEL + 2));//频谱块高度

        mPaint.setStrokeWidth(strokeWidth); //设置频谱块宽度
    }

    //绘制频谱块和倒影
    protected void drawCylinder(Canvas canvas, float x, byte value) {
        if (value == 0) {
            value = 1;
        }//最少有一个频谱块
        for (int i = 0; i < value; i++) { //每个能量柱绘制value个能量块
            float y = (getHeight() / 2 - i * vgap - vgap);//计算y轴坐标
            float y1 = (getHeight() / 2 + i * vgap + vgap);
            //绘制频谱块
            mPaint.setColor(Color.RED);//画笔颜色
            canvas.drawLine(x, y, (x + strokeLength), y, mPaint);//绘制频谱块

            //绘制音量柱倒影
            if (i <= 6 && value > 0) {
                mPaint.setColor(Color.RED);//画笔颜色
                mPaint.setAlpha(100 - (100 / 6 * i));//倒影颜色
                canvas.drawLine(x, y1, (x + strokeLength), y1, mPaint);//绘制频谱块
            }
        }
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//        canvas.drawColor(Color.WHITE);
        int j = -4;
        for (int i = 0; i < CYLINDER_NUM / 2 - 4; i++) { //绘制25个能量柱

            drawCylinder(canvas, strokeWidth / 2 + hgap + i * (hgap + strokeLength), mData[i]);
        }
        for (int i = CYLINDER_NUM; i >= CYLINDER_NUM / 2 - 4; i--) {
            j++;
            drawCylinder(canvas, strokeWidth / 2 + hgap + (CYLINDER_NUM / 2 + j - 1) * (hgap + strokeLength), mData[i - 1]);
        }
    }

    /**
     * @param fft
     */
    public void updateVisualizer(byte[] fft) {
        byte[] model = new byte[fft.length / 2 + 1];
        if (mDataEn) {
            model[0] = (byte) Math.abs(fft[1]);
            int j = 1;
            for (int i = 2; i < fft.length; ) {
                model[j] = (byte) Math.hypot(fft[i], fft[i + 1]);
                i += 2;
                j++;
            }
        } else {
            for (int i = 0; i < CYLINDER_NUM; i++) {
                model[i] = 0;
            }
        }
        for (int i = 0; i < CYLINDER_NUM; i++) {
            final byte a = (byte) (Math.abs(model[CYLINDER_NUM - i]) / levelStep);

            final byte b = mData[i];
            if (a > b) {
                mData[i] = a;
            } else {
                if (b > 0) {
                    mData[i]--;
                }
            }
        }
        invalidate();
    }
}

参考文章

  1. androidequalizer

版权声明 1、 本站名称: 笔友城堡
2、 本站网址: https://www.biumall.com/
3、 本站部分文章来源于网络,仅供学习与参考,如有侵权,请留言

暂无评论

暂无评论...

随机推荐

lateinit和by lazy简单使用

前言记录一下Kotlin中lateinit和by lazy简单使用。正文lateinit为延迟初始化属性。lateinit用于延迟初始化一个var可变属性。属性类型必须是非空的且不能是原始类型(如 Int, Double)。经常在类中定义属性时class Person{   v...

Ubuntu14编译Android6.0

今天(2016年11月6日)终于编译成功,参考的教程有很多,本来也想写一写,但发现自己可能对编译这东西考虑不全,误导了人,所以只好放弃自己写教程的想法。一:下载Android源码关于这一步,网上有下载好的源码,或者去清华大学开源软件镜像站下载,都很快的。Android6.0源码,网盘下...

Launcher的两次启动

前言这个问题,类似的之前也有记录过,但由于自己记性太差,又忘了怎么分析,因此重新记录于此,方便自己查阅。好记性不如烂笔头正文问题Android P机器开机启动后,发现Launcher3的Activity(也就是Launcher)启动了两次。日志只截取了部分且重要的日志Activit...

HandlerThread的使用

HandlerThread简介HandlerThread是一个Android已封装好的轻量级的异步类。HandlerThread 继承Thread,本质就是个Thread,与普通Thread 的区别在于实现了自己的Looper,可以单独分发和处理消息。作用:用来线程间的消息传递,主要是子线...

Kotlin空值处理简介

前言简单记录一下Kotlin中变量的空值处理的方式。记录于此方便自己查阅。正文Kotlin把变量分成两种类型,一种是可空类型的变量,一种是非空类型的变量。一般情况下,一个变量默认是非空类型。当某个变量的值可以为空时,必须在声明处的数据类型后添加“?”来标识该引用可为空。var name...

Android之获取图片高宽方法的简单记录

前言本文非原创,大佬的基础上进行修改和调试,下面三种方式我都测试过。感谢大佬们分享。好记性不如烂笔头总结如果只获取高宽,推荐使用BitmapFactory.Options如果要加载图片和获取高宽,推荐使用Glide如果只是加载jpg图片,可以考虑ExifInterface,否则不推荐...