android - 如何在Android上绘制音频波形

标签 android bitmap android-canvas android-custom-view

我有一个自定义 View ,我想用它在折线图中显示通过麦克风传入的音频的幅度。 获得幅度和所有我没有问题的东西,画线也不是真正的问题。

我想要做的是显示从最右边缘开始向左移动的振幅。因此,对于每个新样本,我都想将位图向左平移,然后从最后一点到新点画一条线。我不确定实现此目标的最简单方法是什么。我最初能够通过绘制路径并在每个样本的路径中添加一个新点来完成,问题是大约一分钟后路径太大而无法绘制。所以我考虑了一下,想切换到使用缓存位图,在每次迭代中转换它,并从最后一个点绘制到新点。然而,这很难做到(在实验之后)。当我翻译位图时,它不会将最左边的像素移出位图,它只会移动 Canvas 中的整个位图,我无法将像素写入右侧。 以下是我正在尝试做的事情的描述:

鉴于此: enter image description here

我想把它翻译到左边: enter image description here

然后画一条线到一个新的点右边的空间space enter image description here

当然,第 2 步和第 3 步应该基本同时发生。

我怎样才能做到这一点?我对新想法持开放态度,比如可能将所有点保存为最多 1 个屏幕值,并在每次 onDraw 调用时绘制它们。我宁愿将它们保存在位图中并进行某种翻译/剪辑等操作,以实现相同的目的,而且开销可能更少。

private static final int MAX_AMPLITUDE = 32767;
float lx, ly;
private Paint mPaint;
private Bitmap mBitmap;
private Canvas mCanvas;

private void init() {

    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(5);
    mPaint.setColor(Color.Black);
}

 @Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
    if (mBitmap != null) {
        mBitmap.recycle();
    }
    mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    mCanvas = new Canvas(mBitmap);
    height = h;
    width = w;
    ly = height;
    lx = width;
    amplitudeDivisor = ((float) MAX_AMPLITUDE / (float) height);
}

@Override
public void onDraw(Canvas canvas) {
    mAmplitude = (float)(MAX_AMPLITUDE * Math.random());
    float dx = width - delta;
    float dy = height - (mAmplitude / amplitudeDivisor);
    mCanvas.drawLine(lx, ly, dx, dy, mPaint);
    mCanvas.translate(-delta, 0);
    canvas.drawBitmap(mBitmap, 0, 0, mPaint);
    lx = dx;
    ly = dy;
    delta+=10;
    postInvalidateDelayed(200);

}

以上只是一个示例,我只是暂时使用振幅的随机值来简化。我已经尝试了很多没有运气的事情。任何帮助将不胜感激。

最佳答案

我最终通过将这些点保存到一个数组中来完成这项工作。我在录音开始前画了一条白线。请注意,我使用 Guava 库中的 EvictingQueue 作为要在一条线上渲染的点的循环缓冲区。要使用它,一旦录音开始调用 start() 并在结束时调用停止。从您的 Activity 中,您需要将 MediaRecorder getMaxAmplitude() 值发送到此类的 updateAmplitude() 方法,并且每隔 50 毫秒发送一次。该 View 还支持旋转。

public class AmplitudeWaveFormView extends View {
    private static final String TAG = AmplitudeWaveFormView.class.getSimpleName();

    private static final int MAX_AMPLITUDE = 32767;
    private static final int SAMPLES_PER_SCREEN = 100;
    private float mAmplitude = 0;

    private Paint mRecordingPaint, mNotRecordingPaint;
    private int height = -1;
    private int width = -1;
    private boolean mIsStarted;

    private float[] lastPoints;

    private int oldWidth = -1, oldHeight = -1;
    private int mCurrentSample;
    private float amplitudeDivisor = 1;
    private float lx,ly, deltaX;


    private EvictingQueue<Float> mPointQueue;


    private int recordColor;

    private int notRecordingColor;


    public AmplitudeWaveFormView(Context context) {
        super(context);
        init();
    }

    public AmplitudeWaveFormView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public AmplitudeWaveFormView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    public void start() {
        mIsStarted = true;
    }


    public void stop() {
        mIsStarted = false;
    }
    public void updateAmplitude(float amplitude) {
        mAmplitude = amplitude;
        postInvalidate();
    }

    private void init() {
        recordColor = getResources().getColor(R.color.mint);
        notRecordingColor = getResources().getColor(R.color.alpine);
        mRecordingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mRecordingPaint.setStyle(Paint.Style.STROKE);
        mRecordingPaint.setStrokeWidth(5);
        mRecordingPaint.setColor(recordColor);

        mNotRecordingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mNotRecordingPaint.setStyle(Paint.Style.STROKE);
        mNotRecordingPaint.setStrokeWidth(5);
        mNotRecordingPaint.setColor(notRecordingColor);
    }

    @Override
    public void onSizeChanged(int w, int h, int oldw, int oldh) {
        height = h;
        width = w;
        ly = height;
        lx = width;
        deltaX =  (float)width / (float)SAMPLES_PER_SCREEN;
        amplitudeDivisor = ((float) MAX_AMPLITUDE / (float) height);

        mPointQueue = EvictingQueue.create(SAMPLES_PER_SCREEN * 4);
        if (lastPoints != null && lastPoints.length > 0) {
            float xScale = (float) width/oldWidth;
            float yScale = (float) height/oldHeight;
            Matrix matrix = new Matrix();
            matrix.setScale(xScale, yScale);
            matrix.mapPoints(lastPoints);
            mPointQueue.addAll(Floats.asList(lastPoints));
            ly = lastPoints[lastPoints.length-1];
            lx= lastPoints[lastPoints.length -2];
            lastPoints = null;
        }

    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            mCurrentSample = bundle.getInt("sample");
            lastPoints = bundle.getFloatArray("lines");
            oldWidth = bundle.getInt("oldWidth");
            oldHeight = bundle.getInt("oldHeight");
            state = ((Bundle) state).getParcelable("parent");

        }
        super.onRestoreInstanceState(state);
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putFloatArray("lines", Floats.toArray(mPointQueue));
        bundle.putInt("sample", mCurrentSample);
        bundle.putParcelable("parent", super.onSaveInstanceState());
        bundle.putInt("oldWidth", width);
        bundle.putInt("oldHeight", height);
        return bundle;
    }


    @Override
    public void onDraw(Canvas canvas) {

        if (mIsStarted) {
            float x = lx + deltaX;
            float y = height - (mAmplitude / amplitudeDivisor);
            mPointQueue.add(lx);
            mPointQueue.add(ly);
            mPointQueue.add(x);
            mPointQueue.add(y);
            lastPoints = Floats.toArray(mPointQueue);
            lx = x;
            ly = y;
        }
        if (lastPoints != null && lastPoints.length > 0) {
            int len = mPointQueue.size() / 4 >= SAMPLES_PER_SCREEN ? SAMPLES_PER_SCREEN * 4 : mPointQueue.size();
            float translateX = width - lastPoints[lastPoints.length - 2];
            canvas.translate(translateX, 0);
            canvas.drawLines(lastPoints, 0, len, mRecordingPaint);
        }

        if (mCurrentSample <= SAMPLES_PER_SCREEN) {
            drawNotRecordingLine(canvas);
        }
        mCurrentSample++;
    }

    private void drawNotRecordingLine(Canvas canvas) {
        canvas.drawLine(0,height, width, height, mNotRecordingPaint);
    }
}

关于android - 如何在Android上绘制音频波形,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25248963/

相关文章:

Android Canvas : Draw only part of precalculated Path

java - 如何在android中的特定位置绘制矩形

java - 用Java压缩,用Python解压?

android - 加载Android内核模块

java - 用ProgressBar替换ProgressDialog,一半时间不显示

c++ - 从原始标题和图像数据创建位图

android - 如何在 Android Studio 中查看调用堆栈?

android - Android BitmapFactory.decodeByteArray 的正确使用

android - 如何将图像添加到位图

java - 如何使用canvas设置画线延迟