Search code examples
androidcanvasbitmaprectvisualizer

Bitmap / Canvas: java.lang.IllegalArgumentException: width and height must be > 0


I am trying to add a waveform visual to my already functioning app that will record a sound, and then play it back. The recording/playback code works fine, but when I tried to add the com.pheelicks.app visualizer, and record my own sound, then try to play it back, I get a crash when I hit my own play button.

In his visualizer app, he provides his own mp3 sound files, which play through MediaPlayer. But since I also play my recorded sound through MediaPlayer, I figured it should be easy to adjust my code to include his visualizer part. But something is wrong.

The strange part is that his code runs perfectly in my Android phone (Samsung Galaxy 5), I can see the visualizer to the music.

I have tried to research similar problems, but I don't find answers there. I have tried this and this, both similar errors, but I did not find a solution.

The error seems to be coming from his code, the VisualizerView.java in the Bitmap part where getWidth() and getHeight() is called from Canvas in the onDraw() method. I also tried to log these values, but they are not showing up in my LogCat. I'm using Android Studio. Thanks for your help!

EDIT:

Ah, I see my Log statement did not work because it was placed after the point of the crash. I placed it right before the crash point:

if (mCanvasBitmap == null) {
        mCanvasBitmap = Bitmap.createBitmap(canvas.getWidth(),
                canvas.getHeight(), Config.ARGB_8888);
    }

And I get this from LogCat: Value of getWidth is 924 and value of getHeight is 0. So the question is, why is the height zero?

VisualizerView.java

package org.azurespot.waveform;

/**
 * Created by mizu on 2/2/15.
 */
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.media.MediaPlayer;
import android.media.audiofx.Visualizer;
import android.util.AttributeSet;
import android.view.View;

import java.util.HashSet;
import java.util.Set;

import static android.util.Log.d;

/**
 * A class that draws visualizations of data received from a
 * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture } and
 * {@link Visualizer.OnDataCaptureListener#onFftDataCapture }
 */
public class VisualizerView extends View {
    private static final String TAG = "VisualizerView";

    private byte[] mBytes;
    private byte[] mFFTBytes;
    private Rect mRect = new Rect();
    private Visualizer mVisualizer;

    private Set<Renderer> mRenderers;

    private Paint mFlashPaint = new Paint();
    private Paint mFadePaint = new Paint();

    public VisualizerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs);
        init();
    }

    public VisualizerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VisualizerView(Context context) {
        this(context, null, 0);
    }

    private void init() {
        mBytes = null;
        mFFTBytes = null;

        mFlashPaint.setColor(Color.argb(122, 255, 255, 255));
        mFadePaint.setColor(Color.argb(238, 255, 255, 255)); // Adjust alpha to change how quickly the image fades
        mFadePaint.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY));

        mRenderers = new HashSet<Renderer>();
    }

    /**
     * Links the visualizer to a player
     *
     * @param player - MediaPlayer instance to link to
     */
    public void link(MediaPlayer player) {
        if (player == null) {
            throw new NullPointerException("Cannot link to null MediaPlayer");
        }

        // Create the Visualizer object and attach it to our media player.
        mVisualizer = new Visualizer(player.getAudioSessionId());
        mVisualizer.setEnabled(false);
        mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);

        // Pass through Visualizer data to VisualizerView
        Visualizer.OnDataCaptureListener captureListener = new Visualizer.OnDataCaptureListener() {
            @Override
            public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes,
                                              int samplingRate) {
                updateVisualizer(bytes);
            }

            @Override
            public void onFftDataCapture(Visualizer visualizer, byte[] bytes,
                                         int samplingRate) {
                updateVisualizerFFT(bytes);
            }
        };

        mVisualizer.setDataCaptureListener(captureListener,
                Visualizer.getMaxCaptureRate() / 2, true, true);

        // Enabled Visualizer and disable when we're done with the stream
        mVisualizer.setEnabled(true);
        player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mediaPlayer) {
                mVisualizer.setEnabled(false);
            }
        });
    }

    public void addRenderer(Renderer renderer) {
        if (renderer != null) {
            mRenderers.add(renderer);
        }
    }

    public void clearRenderers() {
        mRenderers.clear();
    }

    /**
     * Call to release the resources used by VisualizerView. Like with the
     * MediaPlayer it is good practice to call this method
     */
    public void release() {
        mVisualizer.release();
    }

    /**
     * Pass data to the visualizer. Typically this will be obtained from the
     * Android Visualizer.OnDataCaptureListener call back. See
     * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture }
     *
     * @param bytes
     */
    public void updateVisualizer(byte[] bytes) {
        mBytes = bytes;
        invalidate();
    }

    /**
     * Pass FFT data to the visualizer. Typically this will be obtained from the
     * Android Visualizer.OnDataCaptureListener call back. See
     * {@link Visualizer.OnDataCaptureListener#onFftDataCapture }
     *
     * @param bytes
     */
    public void updateVisualizerFFT(byte[] bytes) {
        mFFTBytes = bytes;
        invalidate();
    }

    boolean mFlash = false;

    /**
     * Call this to make the visualizer flash. Useful for flashing at the start
     * of a song/loop etc...
     */
    public void flash() {
        mFlash = true;
        invalidate();
    }

    Bitmap mCanvasBitmap;
    Canvas mCanvas;


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // Create canvas once we're ready to draw
        mRect.set(0, 0, getWidth(), getHeight());

        if (mCanvasBitmap == null) {
            mCanvasBitmap = Bitmap.createBitmap(canvas.getWidth(),
                    canvas.getHeight(), Config.ARGB_8888);
        }

        d("DEBUG", "Value of getWidth is " + canvas.getWidth()
                + " and value of getHeight is " + canvas.getHeight());
        if (mCanvas == null) {
            mCanvas = new Canvas(mCanvasBitmap);
        }

        if (mBytes != null) {
            // Render all audio renderers
            AudioData audioData = new AudioData(mBytes);
            for (Renderer r : mRenderers) {
                r.render(mCanvas, audioData, mRect);
            }
        }

        if (mFFTBytes != null) {
            // Render all FFT renderers
            FFTData fftData = new FFTData(mFFTBytes);
            for (Renderer r : mRenderers) {
                r.render(mCanvas, fftData, mRect);
            }
        }

        // Fade out old contents
        mCanvas.drawPaint(mFadePaint);

        if (mFlash) {
            mFlash = false;
            mCanvas.drawPaint(mFlashPaint);
        }

        canvas.drawBitmap(mCanvasBitmap, new Matrix(), null);
    }
}

LogCat

02-02 22:31:16.699  19125-19125/? E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: org.azurespot, PID: 19125
    java.lang.IllegalArgumentException: width and height must be > 0
            at android.graphics.Bitmap.createBitmap(Bitmap.java:922)
            at android.graphics.Bitmap.createBitmap(Bitmap.java:901)
            at android.graphics.Bitmap.createBitmap(Bitmap.java:868)
            at org.azurespot.waveform.VisualizerView.onDraw(VisualizerView.java:176)
            at android.view.View.draw(View.java:15393)
            at android.view.View.getDisplayList(View.java:14287)
            at android.view.View.getDisplayList(View.java:14329)
            at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3284)
            at android.view.View.getDisplayList(View.java:14224)
            at android.view.View.getDisplayList(View.java:14329)
            at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3284)
            at android.view.View.getDisplayList(View.java:14224)
            at android.view.View.getDisplayList(View.java:14329)
            at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3284)
            at android.view.View.getDisplayList(View.java:14224)
            at android.view.View.getDisplayList(View.java:14329)
            at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3284)
            at android.view.View.getDisplayList(View.java:14224)
            at android.view.View.getDisplayList(View.java:14329)
            at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3284)
            at android.view.View.getDisplayList(View.java:14224)
            at android.view.View.getDisplayList(View.java:14329)
            at android.view.HardwareRenderer$GlRenderer.buildDisplayList(HardwareRenderer.java:1576)
            at android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:1455)
            at android.view.ViewRootImpl.draw(ViewRootImpl.java:2754)
            at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2620)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2188)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1249)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6585)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:803)
            at android.view.Choreographer.doCallbacks(Choreographer.java:603)
            at android.view.Choreographer.doFrame(Choreographer.java:573)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:789)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5579)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084)
            at dalvik.system.NativeStart.main(Native Method)

Solution

  • Turns out the FrameLayout that contained my VisualizerView widget in my xml was the culprit! The code came this way, from com.pheelicks.app, so I did not think anything of it, since I have seen FrameLayout show this type of size at 0dp before (in fragments). But then by odd chance, I decided to make it bigger, and the visualizer did show up. Amazing when it's these small things. Leave no stone unturned! Below is the widget in my xml, I changed the height to 200dp (from 0dp) and that fixed it.

    activity_make_sounds.xml

    <FrameLayout
        android:layout_width="fill_parent"
        android:layout_height="200dp"
        android:layout_margin="10dp"
        android:background="#000" >
    
        <org.azurespot.waveform.VisualizerView
            android:id="@+id/visualizerView"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" >
        </org.azurespot.waveform.VisualizerView>
    
    </FrameLayout>