43

I am making one music application in android.In this music list coming from server side. I don'tknow how to show waveform of audio in android ? like in soundcloud website. I have attached image below.enter image description here

Alpha
  • 522
  • 1
  • 5
  • 10

3 Answers3

40

Perhaps, you can implements this feature without libraries, of course if you want only visualisation of audio sample. For example:

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;

    /**
     * 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.gray));
        notPlayedStatePainting.setStrokeWidth(1f);
        notPlayedStatePainting.setAntiAlias(true);
        notPlayedStatePainting.setColor(ContextCompat.getColor(getContext(), R.color.colorAccent));
    }

    /**
     * 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(width * percent);
        if (denseness < 0) {
            denseness = 0;
        } else if (denseness > width) {
            denseness = width;
        }
        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);
    }
}

Sorry, code with a small amount of comments, but it is working visualizer. You can attach it to any players you want.

How you can use it: add this view in your xml layout, then you have to update visualizer state with methods

public void updateVisualizer(byte[] bytes) {
    playerVisualizerView.updateVisualizer(bytes);
}

public void updatePlayerProgress(float percent) {
    playerVisualizerView.updatePlayerPercent(percent);
}

In updateVisualizer you pass bytes array with you audio sample, and in updatePlayerProgress you dynamically pass percentage, while audio sample is playing.

for converting file to bytes you can use this helper method

public static byte[] fileToBytes(File file) {
    int size = (int) file.length();
    byte[] bytes = new byte[size];
    try {
        BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file));
        buf.read(bytes, 0, bytes.length);
        buf.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return bytes;
}

and for example(very shortly), how it looks like with Mosby library:

public class AudioRecorderPresenter extends MvpBasePresenter<AudioRecorderView> {

public void onStopRecord() {
        // stopped and released MediaPlayer
        // ...
        // some preparation and saved audio file in audioFileName variable.

        getView().updateVisualizer(FileUtils.fileToBytes(new File(audioFileName)));
        }
    }
}

UPD: I created the library for resolving this case github.com/scrobot/SoundWaveView. It still in status "WIP"(work in progress), but soon I will complete it.

Scrobot
  • 1,911
  • 3
  • 19
  • 36
  • 1
    Tanx, How can I use PlayerVisualizerView? – Hamed Ghadirian Oct 21 '17 at 07:34
  • 1
    I think you give the best answer and give +50 award to your answer, but please add more detail and show the usage. – Hamed Ghadirian Oct 21 '17 at 10:18
  • Would be nice if you comment the code over each line and explain it, specially the bytes movement – Diego Fernando Murillo Valenci Sep 27 '18 at 16:19
  • @Scrobot thank you for this great help. i would be more than greatful if you can add a code for showing bar's bellow straight line also, i mean if we give negetive values we get bars bellow the stright line. to complete the full sound wave (soundcloud type UI) – Kathan Shah Oct 26 '18 at 16:19
  • 1
    @KathanShah hi! I'm working on it )) I took a lot of feedbacks, and decide to create library for this feature) So, the android library will be published soon. )) In particular, negative waves will be 'in box' too )) – Scrobot Oct 31 '18 at 10:10
  • @Scrobot - I'll be happy to buy you a coffee/treat for this library. sooner the better. Amazing work though. help with your contact i have some freelance work for you. Thank You – Kathan Shah Nov 01 '18 at 20:58
  • @Scrobot cat i get your skype ? or can you skype me at skypeid: cattechsol.com Thank You. – Kathan Shah Nov 15 '18 at 11:51
  • @KathanShah I'm still working on it. I don't have enough much time to complete it. Here is the progress -> https://github.com/scrobot/SoundWaveView – Scrobot May 15 '19 at 06:10
  • it would be better to add document to your library i couldn't use it. – moloud ayat Sep 02 '19 at 09:27
  • @moloodayat, of course, I will add README.md, but the library still in progress. it will be ready soon, I hope) – Scrobot Sep 03 '19 at 16:32
  • What is the number 5? Could you explain a bit? – NeoWang Sep 08 '19 at 10:42
  • How do you implement zoom in visualization? – Deepak Sharma Sep 16 '21 at 11:41
10

JETPACK COMPOSE
AudioWaveform is a lightweight Jetpack Compose library that draws a waveform of audio.



XML
WaveformSeekBar is an android library that draws a waveform from a local audio file, resource, and URL using android.view.View (XML approach).

AUDIO PROCESSING
If you're looking for a fast audio processing library, you could use the existing Amplituda library. Amplituda also has caching and compressing features out of the box.

lincollincol
  • 762
  • 2
  • 12
  • 23
8

I believe Scrobot's answer does not work. It assumes the input audio to be in a certain (quite peculiar) encoding (single-channel/mono linear PCM with 5 bit depth). And the algorithm to calculate amplitudes from the wave function is probably flawed. If you use that algorithm with any commonly used audio file format, you will get nothing but random data.

The truth is: It's just a bit more complicated that that.

Here's what's there to be done to achieve the OP's goal:

  1. Use Android's MediaExtractor to read the input audio file (variousformats/encodings are supported)
  2. Use Android's MediaCodec to decode the input audio encoding to a linear PCM encoding with certain bit depth (usually 16 bit)
    • Only after this step you got a byte array which you can linearly read and calculate amplitudes from.
  3. Apply a loudness measure to the PCM-encoded data. There are many of them, some more complicated (e.g. LUFS/LKFS), some more basic (RMS). Let's take RMS (= Root Mean Squares) for example:
    1. Determine the number of samples per bar.
    2. Read all the samples for a single bar. Usually there are 2 channels, so for each sample you will get 2 short ints (16 bit) for PCM-16.
    3. For each sample calculate the mean of all channels.
    4. Maybe you will want to normalize the value in some way, e.g. to get float values between -1 and 1 you can divide by (2 / (1 << 16))
    5. Square each sample (hence the "S" in RMS)
    6. Calculate the mean of all the samples for a bar (hence the "M" in RMS)
    7. Calculate the square root of the resulting value (hence the "R" in RMS)
    8. Now you get a value which you can base the height of the bar on.
    9. Repeat steps 2-8 for all the bars.

To implement this is quite an involved task. I could not find any library providing this whole process already. But at least Android's media API provides the algorithms for reading audio file in any format.

Note: RMS is considered a not very accurate loudness measure. But it seems to yield results which are at least somewhat related to what you can actually hear. For many applications it should be good enough.

aax
  • 394
  • 5
  • 10