How to Render Image Buffer in Android NDK Native Code


https://vec.io/posts/how-to-render-image-buffer-in-android-ndk-native-code


How to Render Image Buffer in Android NDK Native Code 2012-11-17By Cedric Fung


Android NDK development has been improved quite a lot since Android 2.3 Gingerbread, but still with many limitations. In this post I will show off a general way to render native image pixels to Android Surface from NDK.

All the code below are benchmarked against a Samsung Galaxy S I9000 device, which has an 1 GHz Cortex-A8 single core CPU and 512M RAM.

Java Surface JNI

This method should work for all Android versions, but I haven’t tested it with Android versions prior to 2.1.

At first, create a ByteBuffer in the Java code, then pass its handle to native C code. Each time I want to render some pixels, I only need to copy the pixels to the nativeByteBuffer pointer, then invoke the Java method surfaceRender through JNI.

Please note that you need to set the mSurface object at proper time, such asSurfaceView.onSurfaceChanged. Also the w and h variable correspond to the width and height of the native image.

private Bitmap mBitmap;
private ByteBuffer mByteBuffer;

private ByteBuffer surfaceInit() {
  synchronized (this) {
    mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
    mByteBuffer = ByteBuffer.allocateDirect(w * h * 2);
    return mByteBuffer;
  }
}

private void surfaceRender() {
  synchronized (this) {
    try {
      Canvas c = mSurface.lockCanvas(null);
      mBitmap.copyPixelsFromBuffer(mByteBuffer);
      c.drawBitmap(mBitmap, 0, 0, null);
      mSurface.unlockCanvasAndPost(c);
    } catch (Exception e) {
    }
  }
}

private void surfaceRelease() {
  synchronized (this) {
    mBitmap.recycle();
    mBitmap = null;
    mByteBuffer = null;
  }
}

The C code is very simple, just invoke proper Java methods at proper time to initialize, render and release the JavaByteBuffer resources. The code won’t compile, because I haven’t include the common JNI setup methods.

static jbyte* g_buffer;

bool jni_surface_init(JNIEnv* env) {
  jobject buf = (*env)->CallObjectMethod(env, javaClass, javaSurfaceInit);
  if (buf == NULL) return false;
  
  g_buffer = (jbyte*)(*env)->GetDirectBufferAddress(env, buf);
  if (g_buffer == NULL) return false;

  return JNI_VERSION_1_6;
}

void jni_surface_release(JNIEnv* env) {
  (*env)->CallVoidMethod(env, javaClass, javaSurfaceRelease);
  g_buffer = NULL;
}

void jni_surface_render(JNIEnv* env, uint8_t* pixels, int w, int h) {
  if (g_buffer != NULL) {
    memcpy(g_buffer, pixels, w * h * 2); // RGB565 pixels
    (*env)->CallVoidMethod(env, javaClass, javaSurfaceRender);
  }
}

It may be a bit complicated, but its performance is very eligible thanks to Java nio ByteBuffer. To render an 1280x720 RGB image, the JNI method only needs about 10ms.

OpenGL ES 2 Texture

OpenGL is fast, it can also boost the performance if you need color space conversion, e.g. from YUV to RGB. I won’t list any code here, because there’re too many code and it’s too difficult to describe it clearly. But it will be simple for the programmers who have used OpenGL before.

OpenGL ES 2 is supported by most Android devices, it’s recommended to use this method if you’re familiar with OpenGL.

To render an 1280x720 YUV image, the OpenGL method will cost about 12ms, the color space conversion time included!

NDK ANativeWindow API

For Android versions prior to Android 2.3, there’re no official NDK APIs to render pixels efficiently. Though anandroid/bitmap.h is provided, it’s still too slow and not robust, I don’t recommend it.

But since Android 2.3 Gingerbread, the ANativeWindow API is available, it’s very fast and easy to use.

ANativeWindow* window = ANativeWindow_fromSurface(env, javaSurface);

ANativeWindow_Buffer buffer;
if (ANativeWindow_lock(window, &buffer, NULL) == 0) {
  memcpy(buffer.bits, pixels,  w * h * 2);
  ANativeWindow_unlockAndPost(window);
}

ANativeWindow_release(window);

To render an 1280x720 RGB image, the ANativeWindow API only cost about 7ms.

Private C++ API

Android teams don’t recommend to use the private interfaces hidden in Android source code, so it’s up to you to choose whether to use this method. As I know, many famous Apps have choosen this method, such as Flash, Firefox, VPlayer, MX Player and VLC for Android.

#ifdef FROYO
#include "surfaceflinger/Surface.h"
#else
#include "ui/Surface.h"
#endif

using namespace android;

jclass surfaceClass = env->FindClass("android/view/Surface");
if (surfaceClass == NULL) return;

jfield fid = env->GetFieldID(surfaceClass, "mSurface", "I");
if (fid == NULL) return;

sp<Surface> surface = (Surface*)env->GetIntField(javaSurface, fid);

Surface::SurfaceInfo info;
if (surface->lock(&info) == 0) {
  memcpy(info.bits, pixels,  w * h * 2);
  surface->unlockAndPost();
}

As you can see, the procedure used here is very similiar to the ANativeWindow one. But it’s a bit hard to build this code snippet, because you need to setup properLOCAL_C_INCLUDES and LOCAL_C_LIBS in your Android.mk file. For the header files, you’d better to clone the Android source code. For the shared libslibgui.so and libsurfaceflinger_client.so, you can pull them off from a real Android device or Android emulator.

The performance in this method is also the same as the ANativeWindow one.

Conclusion

There’re so many ways to display image in Android NDK, with so many Android versions! So I have been using several different dynamic shared libraries for different methods and Android versions, and load a poper one at runtime.



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
package { import flash.display.Bitmap; import flash.display.Loader; import flash.display.NativeMenu; import flash.display.NativeMenuItem; import flash.display.NativeWindow; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.filesystem.File; import flash.filesystem.FileMode; import flash.filesystem.FileStream; import flash.net.FileFilter; import flash.net.navigateToURL; import flash.net.URLRequest; import flash.utils.ByteArray; public class Main extends Sprite { private var rootMenu:NativeMenu = new NativeMenu; private var fileMenu:NativeMenu = new NativeMenu; private var openItem:NativeMenuItem = new NativeMenuItem("打开文件"); private var quitItem:NativeMenuItem = new NativeMenuItem("退出"); private var helpMenu:NativeMenu = new NativeMenu; private var fsnhf:NativeMenuItem = new NativeMenuItem("00"); private var window:NativeWindow = stage.nativeWindow; private var loader:Loader = new Loader; private var imageFileFilter:FileFilter = new FileFilter("图片(*.jpg;*.png;*.gif;*.jpeg)", "*.jpg;*.png;*.gif;*.jpeg"); public function Main():void { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; buildMenu(); configListeners(); } private function buildMenu():void { rootMenu.addSubmenu(fileMenu, "文件"); fileMenu.addItem(openItem); fileMenu.addItem(quitItem); rootMenu.addSubmenu(helpMenu, "帮助"); helpMenu.addItem(fsnhf); window.menu = rootMenu; } private function configListeners():void { openItem.addEventListener(Event.SELECT, eventHandler); quitItem.addEventListener(Event.SELECT, eventHandler); fsnhf.addEventListener(Event.SELECT, eventHandler); } private function eventHandler(event:Event):void { switch(event.target) { case openItem: var file:File = new File; file.addEventListener(Event.SELECT, selectFile); file.browseForOpen("打开文件", [imageFileFilter]); break; case quitItem: window.close(); break; case fsnhf: navigateToURL(new URLRequest("00")); break; } } private function selectFile(event:Event):void { var file:File = event.target as File; var stream:FileStream = new FileStream; stream.open(file, FileMode.READ); var bytes:ByteArray = new ByteArray; stream.readBytes(bytes, 0, stream.bytesAvailable); stream.close(); loader.unload(); loader = new Loader; loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoaded); loader.loadBytes(bytes); addChild(loader); } private function imageLoaded(event:Event):void { event.target.removeEventListener(Event.COMPLETE, imageLoaded); var bitmap:Bitmap = Bitmap(event.target.loader.content); window.width = bitmap.width; window.height = bitmap.height + 30; } } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值