Search code examples
androidbitmapandroid-canvassurfaceviewframe-rate

Display a stream of bitmaps at 60fps smoothly on Android 4.x


(This is due to the limitations of the server software I will be using, if I could change it, I would).

I am receiving a sequence of 720x480 JPEG files (about 6kb in size), over a socket. I have benchmarked the network, and have found that I am capable of receiving those JPEGs smoothly, at 60FPS.

My current drawing operation is on a Nexus 10 display of 2560x1600, and here's my decoding method, once I have received the byte array from the socket:

public static void decode(byte[] tmp, Long time) {
    try {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferQualityOverSpeed = false;
        options.inDither = false;
        Bitmap bitmap = BitmapFactory.decodeByteArray(tmp, 0, tmp.length, options);
        Bitmap background = Bitmap.createScaledBitmap
                (bitmap, MainActivity.screenwidth, MainActivity.screenheight, false);
        background.setHasAlpha(false);
            Canvas canvas = MainActivity.surface.getHolder().lockCanvas();
            canvas.drawColor(Color.BLACK);
            canvas.drawBitmap(background, 0, 0, new Paint());
            MainActivity.surface.getHolder().unlockCanvasAndPost(canvas);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

As you can see, I am clearing the canvas from a SurfaceView, and then drawing the Bitmap to the SurfaceView. My issue is that it is very, very, slow.

Some tests based on adding System.currentTimeMillis() before and after the lock operation result in approximately a 30ms difference between getting the canvas, drawing the bitmap, and then pushing the canvas back. The displayed SurfaceView is very laggy, sometimes it jumps back and forth, and the frame rate is terrible.

Is there a referred method for drawing like this? Again, I can't modify what I'm getting from the server, but I'd like the bitmaps to be displayed at 60FPS when possible.

(I've tried setting the contents of an ImageView, and am receiving similar results). I have no other code in the SurfaceView that could impact this. I have set the holder to the RGBA_8888 format:

    getHolder().setFormat(PixelFormat.RGBA_8888);

Is it possible to convert this stream of Bitmaps into a VideoView? Would that be faster?

Thanks.


Solution

  • Whenever you run into performance questions, use Traceview to figure out exactly where your problem lies. Using System.currentTimeMillis() is like attempting to trim a steak with a hammer.

    The #1 thing her is to get the bitmap decoding off the main application thread. Do that in a background thread. Your main application thread should just be drawing the bitmaps, pulling them off of a queue populated by that background thread. Android has the main application thread set to render on a 60fps basis as of Android 4.1 (a.k.a., "Project Butter"), so as long as you can draw your Bitmap in a couple of milliseconds, and assuming that your network and decoding can keep your queue current, you should get 60fps results.

    Also, always use inBitmap with BitmapFactory.Options on Android 3.0+ when you have images of consistent size, as part of your problem will be GC stealing CPU time. Work off a pool of Bitmap objects that you rotate through, so that you generate less garbage and do not fragment your heap so much.

    I suspect that you are better served letting Android scale the image for you in an ImageView (or just by drawing to a View canvas) than you are in having BitmapFactory scale the image, as Android can take advantage of hardware graphics acceleration for rendering, which BitmapFactory cannot. Again, Traceview is your friend here.

    With regards to:

    and have found that I am capable of receiving those JPEGs smoothly, at 60FPS.

    that will only be true sometimes. Mobile devices tend to be mobile. Assuming that by "6kb" you mean 6KB (six kilobytes), you are assuming a ~3Mbps (three megabits per second) connection, and that's far from certain.

    With regards to:

    Is it possible to convert this stream of Bitmaps into a VideoView?

    VideoView is a widget that plays videos, and you do not have a video.

    Push come to shove, you might need to drop down to the NDK and do this in native code, though I would hope not.