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)
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>