Android smooth audio waveform

174 views Asked by At

I'm developing a music app. Right now I need to implement something like the image below.

enter image description here

In my searches I came across the following code which seems to work correctly. ( Of course, I have some doubts about its functionality )

public class PlayerVisualizerView extends View {

/**
 * constant value for Height of the bar
 */
public static final int VISUALIZER_HEIGHT = 28;

/**
 * bytes array converted from file.
 */
private byte[] bytes;

/**
 * Percentage of audio sample scale
 * Should updated dynamically while audioPlayer is played
 */
private float denseness;

private float last = 0;

/**
 * Canvas painting for sample scale, filling played part of audio sample
 */
private Paint playedStatePainting = new Paint();
/**
 * Canvas painting for sample scale, filling not played part of audio sample
 */
private Paint notPlayedStatePainting = new Paint();

private int width;
private int height;

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

public PlayerVisualizerView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    init();
}

private void init() {
    bytes = null;

    playedStatePainting.setStrokeWidth(1f);
    playedStatePainting.setAntiAlias(true);
    playedStatePainting.setColor(ContextCompat.getColor(getContext(), R.color.colorPrimaryRipple));
    notPlayedStatePainting.setStrokeWidth(1f);
    notPlayedStatePainting.setAntiAlias(true);
    notPlayedStatePainting.setColor(ContextCompat.getColor(getContext(), R.color.colorAmber_A400));
}

/**
 * update and redraw Visualizer view
 */
public void updateVisualizer(byte[] bytes) {
    this.bytes = bytes;
    invalidate();
}

/**
 * Update player percent. 0 - file not played, 1 - full played
 *
 * @param percent
 */
public void updatePlayerPercent(float percent) {
    denseness = (int) Math.ceil((percent * width) / 100 );
    if (denseness <= 0) {
        denseness = 0;
    } else if (denseness > width) {
        denseness = width;
    }
    if(percent != last)
    {
        Log.e("UpdateUI" , percent + "");
        last = percent;
        invalidate();
    }
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    width = getMeasuredWidth();
    height = getMeasuredHeight();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (bytes == null || width == 0) {
        return;
    }
    float totalBarsCount = width / dp(3);
    if (totalBarsCount <= 0.1f) {
        return;
    }
    byte value;
    int samplesCount = (bytes.length * 8 / 5);
    float samplesPerBar = samplesCount / totalBarsCount;
    float barCounter = 0;
    int nextBarNum = 0;

    int y = (height - dp(VISUALIZER_HEIGHT)) / 2;
    int barNum = 0;
    int lastBarNum;
    int drawBarCount;

    for (int a = 0; a < samplesCount; a++) {
        if (a != nextBarNum) {
            continue;
        }
        drawBarCount = 0;
        lastBarNum = nextBarNum;
        while (lastBarNum == nextBarNum) {
            barCounter += samplesPerBar;
            nextBarNum = (int) barCounter;
            drawBarCount++;
        }

        int bitPointer = a * 5;
        int byteNum = bitPointer / Byte.SIZE;
        int byteBitOffset = bitPointer - byteNum * Byte.SIZE;
        int currentByteCount = Byte.SIZE - byteBitOffset;
        int nextByteRest = 5 - currentByteCount;
        value = (byte) ((bytes[byteNum] >> byteBitOffset) & ((2 << (Math.min(5, currentByteCount) - 1)) - 1));
        if (nextByteRest > 0) {
            value <<= nextByteRest;
            value |= bytes[byteNum + 1] & ((2 << (nextByteRest - 1)) - 1);
        }

        for (int b = 0; b < drawBarCount; b++) {
            int x = barNum * dp(3);
            float left = x;
            float top = y + dp(VISUALIZER_HEIGHT - Math.max(1, VISUALIZER_HEIGHT * value / 31.0f));
            float right = x + dp(2);
            float bottom = y + dp(VISUALIZER_HEIGHT);
            if (x < denseness && x + dp(2) < denseness) {
                canvas.drawRect(left, top, right, bottom, notPlayedStatePainting);
            } else {
                canvas.drawRect(left, top, right, bottom, playedStatePainting);
                if (x < denseness) {
                    canvas.drawRect(left, top, right, bottom, notPlayedStatePainting);
                }
            }
            barNum++;
        }
    }
}

public int dp(float value) {
    if (value == 0) {
        return 0;
    }
    return (int) Math.ceil(getContext().getResources().getDisplayMetrics().density * value);
}

And what is the problem?

it works slowly when using large audio files ( More than 2 MB ) . This problem is especially noticeable when this view is used inside the RecyclerView.

Do you see any problem in the above code that needs to be optimized?

If you introduce a library or other code snippet.

0

There are 0 answers