package com.cooleshow.musicmerge.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.media.audiofx.Visualizer; import android.util.AttributeSet; import android.view.View; import com.cooleshow.base.utils.LOG; import com.cooleshow.base.utils.SizeUtils; import com.cooleshow.musicmerge.R; import androidx.annotation.Nullable; /** * * on 2021/4/13 * 自定义组件:音乐频谱显示组件 * API-> setMediaPlayer() 外层设置路径 播放之后 自动显示频谱效果 */ public class MusicFrequencyView extends View { private static final String TAG = "MusicFrequencyView"; private int LUMP_COUNT = 120; private int LUMP_WIDTH = SizeUtils.dp2px(1.5f); ; private int LUMP_SPACE = SizeUtils.dp2px(3); private int LUMP_SIZE = LUMP_WIDTH + LUMP_SPACE; private int LUMP_MAX_HEIGHT = SizeUtils.dp2px(80); private int LUMP_MIN_HEIGHT = SizeUtils.dp2px(3); private float SCALE = 0.95f; private int widthsize; private int heightsize; private Visualizer visualizer; private int itemColor = Color.WHITE; private Paint paint1; protected RectF mRect; public MusicFrequencyView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.musicView); itemColor = ta.getColor(R.styleable.musicView_itemColor, Color.WHITE); mRect = new RectF(); waveData = new float[LUMP_COUNT]; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { widthsize = MeasureSpec.getSize(widthMeasureSpec); heightsize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(widthsize, heightsize); init(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mRect.set(0, 0, getWidth(), getHeight()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //因为外部宽度是写死的,不调整坐标算法,最后一个可能绘制不全,暂时不绘制最后一个吧 for (int i = 1; i <= LUMP_COUNT-1; i++) { // canvas.drawLine(mRect.width() * i / LUMP_COUNT, mRect.height() / 2, mRect.width() * i / LUMP_COUNT, -LUMP_MIN_HEIGHT + mRect.height() / 2 - SCALE * waveData[i], paint1); // canvas.drawLine(mRect.width() * i / LUMP_COUNT, mRect.height() / 2, mRect.width() * i / LUMP_COUNT, LUMP_MIN_HEIGHT + mRect.height() / 2 + SCALE * waveData[i], paint1); canvas.drawLine(mRect.width() * i / LUMP_COUNT, -LUMP_MIN_HEIGHT + mRect.height() / 2 - SCALE * waveData[i-1], mRect.width() * i / LUMP_COUNT, LUMP_MIN_HEIGHT + mRect.height() / 2 + SCALE * waveData[i - 1], paint1); } } private Path getPath(float startX, float startY, float endX, float endY, Paint paint) { Path path = new Path(); paint.setStrokeCap(Paint.Cap.SQUARE); path.moveTo(startY, startY); path.lineTo(endX / 2, endY / 2); paint.setStrokeCap(Paint.Cap.ROUND); path.lineTo(endX, endY); return path; } private Visualizer.OnDataCaptureListener dataCaptureListener = new Visualizer.OnDataCaptureListener() { @Override public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) { } @Override public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) { setWaveData(fft); } }; private float[] waveData; private void setWaveData(byte[] fft) { waveData = readyData(fft); // LOG.i(TAG, "setWaveData:" + waveData); invalidate(); } /** * 预处理数据 * * @return */ private float[] readyData(byte[] fft) { float[] model = new float[fft.length / 2 + 1]; model[0] = (float) Math.abs(fft[1]); int j = 1; for (int i = 2; i < LUMP_COUNT * 2; ) { model[j] = (float) Math.hypot(fft[i], fft[i + 1]); i += 2; j++; model[j] = Math.abs(model[j]); } // LOG.i(TAG,"model:"+model.length); float[] results = hananianWindow(model, 2); // LOG.i(TAG,"results:"+results.length); return results; } public static float[] hananianWindow(float[] data, int windowSize) { float[] windowData = new float[data.length - windowSize + 1]; for (int i = 0; i < windowData.length; i++) { float sum = 0; for (int j = 0; j < windowSize; j++) { sum += data[i + j]; } windowData[i] = sum / windowSize; } return windowData; } /** * 只需传入 mediaPlayer 即可 * * @param */ public void setMediaPlayer(final int audioSessionId) { try { if (visualizer == null) { visualizer = new Visualizer(audioSessionId); int captureSize = Visualizer.getCaptureSizeRange()[1]; int captureRate = Visualizer.getMaxCaptureRate() / 2; // 3:设置参数 visualizer.setCaptureSize(captureSize); visualizer.setDataCaptureListener(dataCaptureListener, captureRate, false, true); visualizer.setScalingMode(Visualizer.SCALING_MODE_NORMALIZED); visualizer.setEnabled(true); } } catch (Exception e) { e.printStackTrace(); } } public void setVisualizerEnable(boolean isEnable) { if (visualizer != null) { visualizer.setEnabled(isEnable); } } public void release() { if (visualizer != null) { visualizer.release(); } } /** * 设置频率数据 * * @param data */ public void setMusicData(int[] data) { } /** * 初始化画笔 */ void init() { paint1 = new Paint(); paint1.setStrokeWidth(LUMP_WIDTH); paint1.setColor(itemColor); paint1.setStrokeCap(Paint.Cap.ROUND); paint1.setAntiAlias(true); paint1.setStyle(Paint.Style.FILL); paint1.setStrokeJoin(Paint.Join.BEVEL); // paint1.setMaskFilter(new BlurMaskFilter(5, BlurMaskFilter.Blur.SOLID)); } }