jamrubyのサンプルアプリ作った
jamrubyを使ったサンプルアプリを作って公開しました。
当然buildにはjamrubyが必要で、jamrubyを使うってことはmrubyも必要になります。
ということで、ここに置いてあります。
Eclipseで適当にimportして使って下さい。
https://github.com/crimsonwoods/JamrubyApp
$ git clone https://github.com/crimsonwoods/JamrubyApp.git
起動するとこんな感じのちょーださださ手抜き画面が出てきます。
画面上部のEditTextにRubyのコードを書いて、「Run」ボタンを押して実行です。
最後に評価された値の内容が、画面下部のTextViewに吐き出されます。
標準出力へ印字した文字は、現状ではlogcatに吐き出すようになっています。
ということでなんとなーくRubyのコードが動いてるねーというのは見れるかなと思います。
ちなみにRubyのコードに対して何か値を渡したいときは、jamruby.runの第2引数以降に実行時引数を書くことができます。
ここに書いた引数はARGVを通してRubyのコードからアクセスすることができます。
中で何してるかは、コード見てください。
ソースコードのライセンスとか特に考えてなかったけど、何も考えずにmrubyと同じMITライセンスで良いかなぁとか。
mrubyをndk-buildするのに必要なもの公開しました。
ということで公開しました。
注意点は、buildするまえにbisonを忘れずに入れておくことと、
jniディレクトリ内でndk-buidを呼び出すことくらいです。
細かいことはREADMEにコマンドをそのまま書いておいたので、そちらを見てください。
https://github.com/crimsonwoods/mruby_ndk-build
Enjoy mruby!
mrubyをAndroid用にNDKでビルドする
Rubyを勉強するか!と思い立ったので、とりあえずmrubyをbuildしてみます。
<追記>
ここにbuildに必要なものとか手順を公開しています。
</追記>
gccのcross-compileで普通にbuildしても良いのですが、それは先人がすでにやっているようなので、
NDKに付属しているndk-buildでbuildできるようにしてみたいと思います。
ビルドに必要なものでgccとか以外だと、
- Android NDK
- bison
くらいでしょうか。
やることはこんな感じ。
ということでやりましょう。
$ git clone https://github.com/mruby/mruby.git $ cd mruby $ mkdir -p android/jni $ make
ここまでで、ホスト用のbuildが終わります。
次にAndroid.mkをandroid/jniに作ります。
こんな感じです。
LOCAL_PATH := $(call my-dir) BASE_CFLAGS := -Wall -Werror-implicit-function-declaration -O3 RUBY_ROOT := ../.. YACC := bison YC := $(RUBY_ROOT)/src/y.tab.c YSRC := $(RUBY_ROOT)/src/parse.y DLIB := $(RUBY_ROOT)/mrblib/mrblib.ctmp RLIB := $(RUBY_ROOT)/mrblib/mrblib.rbtmp MRBS := $(RUBY_ROOT)/mrblib/*.rb include $(CLEAR_VARS) $(shell $(YACC) -o $(YC) $(YSRC)) LOCAL_MODULE := mruby LOCAL_CFLAGS := $(BASE_CFLAGS) LOCAL_C_INCLUDES:= $(RUBY_ROOT)/include $(RUBY_ROOT)/src LOCAL_SRC_FILES := $(RUBY_ROOT)/tools/mruby/mruby.c LOCAL_LDLIBS := -lm LOCAL_STATIC_LIBRARIES := mruby_lib mrblib include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) LOCAL_MODULE := mruby_lib LOCAL_CFLAGS := $(BASE_CFLAGS) LOCAL_C_INCLUDES := $(RUBY_ROOT)/include $(RUBY_ROOT)/src LOCAL_SRC_FILES := $(wildcard $(RUBY_ROOT)/src/*.c) LOCAL_LDLIBS := LOCAL_SHARED_LIBRARIES := include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) $(shell cat $(MRBS) > $(RLIB)) $(shell $(RUBY_ROOT)/bin/mrbc -Bmrblib_irep -o$(DLIB) $(RLIB)) $(shell cat $(RUBY_ROOT)/mrblib/init_mrblib.c $(DLIB) > $(RUBY_ROOT)/mrblib/mrblib.c) LOCAL_MODULE := mrblib LOCAL_CFLAGS := $(BASE_CFLAGS) LOCAL_C_INCLUDES := $(RUBY_ROOT)/include $(RUBY_ROOT)/src LOCAL_SRC_FILES := $(RUBY_ROOT)/mrblib/mrblib.c LOCAL_LDLIBS := LOCAL_SHARED_LIBRARIES := include $(BUILD_STATIC_LIBRARY)
もともと入っているMakefileを元に適当に作ってみました。
あとは適当にApplicaiton.mkとかを用意して、ndk-buildするだけですね。
OpenCVで特定の色領域だけ抜き出すコード(JNI版)
OpenCV for Androidで特定の色領域だけ抜き出すコードを書きました。
OpenCV for AndroidではMatクラスのnative instanceはMat.nativeObjで取得できます。
ここには単純にOpenCV内部のMatクラスへのポインタが格納されているので、えいやっとそいつを取ってきて、あとはゴニョゴニョするだけですね。
処理自体はHSV色空間でやっているので、RGB(A)→HSV→RGBAと変換しています。
具体的には特定範囲の色相で、且つ一定値以上の彩度・明度をもった色を残してあとは黒で塗るということをしています。
#include "opencv2/core/core.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <android/log.h> #define LOG_TAG "android_log_tag" #define LOGD(...) (void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__); static void throw_CvException(JNIEnv *env, cv::Exception &e); JNIEXPORT void JNICALL Java_jni_class_fqn_colorMask (JNIEnv *env, jclass clazz, jlong src, jlong dst, jint minHue, jint maxHue, jint minSaturation, jint minValue) { try { cv::Mat const *srcMat = static_cast<cv::Mat const *>(reinterpret_cast<void const *>(src)); cv::Mat *dstMat = static_cast<cv::Mat*>(reinterpret_cast<void *>(dst)); std::vector<cv::Mat> hsv; cv::Mat hsvMat; cv::cvtColor(*srcMat, hsvMat, CV_BGR2HSV, 3); cv::split(hsvMat, hsv); int const c = srcMat->cols; int const r = srcMat->rows; for (int y = 0; y < c; ++y) { for (int x = 0; x < r; ++x) { int const h = (hsv[0].at<uint8_t>(x, y) & 0xff) << 1; bool isMasked = false; if ((minHue <= h) && (h <= maxHue)) { int const s = hsv[1].at<uint8_t>(x, y) & 0xff; if (minSaturation <= s) { int const v = hsv[2].at<uint8_t>(x, y) & 0xff; if (minValue <= v) { isMasked = true; } } } if (!isMasked) { hsv[1].at<uint8_t>(x, y) = 0; hsv[2].at<uint8_t>(x, y) = 0; } } } cv::Mat merged; cv::merge(hsv, merged); cv::cvtColor(merged, *dstMat, CV_HSV2BGR, 4); } catch (cv::Exception& e) { LOGD("colorMask() catched cv::Exception: %s", e.what()); throw_CvException(env, e); } } static void throw_exception(JNIEnv *env, char const * const className, char const * const message) { jclass cls = env->FindClass(className); if (!cls) { cls = env->FindClass("java/lang/Exception"); } env->ThrowNew(cls, message); env->DeleteLocalRef(cls); } static void throw_CvException(JNIEnv *env, cv::Exception &e) { throw_exception(env, "org/opencv/core/CvException", e.what()); }
NDKでbuildするにはopencvのshared libraryが必要なので、こんな感じでAndroid.mkを書いてみました。
LOCAL_PATH := $(call my-dir) OPENCV_PATH := /path/to/opencv OPENCV_LIBS := /path/to/opencv/libs # dummy. # copy libopencv_java.so into "obj/local/armeabi" manually. include $(CLEAR_VARS) LOCAL_MODULE := opencv_java include $(BUILD_SHARED_LIBRARY) # build library include $(CLEAR_VARS) LOCAL_MODULE := your_project LOCAL_CPPFLAGS := -Wall -Werror -O2 LOCAL_SRC_FILES := your_source.cpp LOCAL_LDLIBS += -llog LOCAL_C_INCLUDES := $(OPENCV_PATH)/modules/core/include $(OPENCV_PATH)/modules/imgproc/include LOCAL_SHARED_LIBRARIES := libopencv_java include $(BUILD_SHARED_LIBRARY)
中に書いてあるように、libopencv_java.soをリンクさせるためにdummy projectを作っています。
こんな感じのshell scriptを書いて適当にlibopencv_java.soをコピると良いかと思います。
#!/bin/sh OPENCV_LIB=/path/to/opencv/libs mkdir -p ../obj/local/armeabi mkdir -p ../obj/local/armeabi-v7a cp $OPENCV_LIB/armeabi/libopencv_java.so ../obj/local/armeabi cp $OPENCV_LIB/armeabi-v7a/libopencv_java.so ../obj/local/armeabi-v7a ndk-build if [ -e ../libs/armeabi/libopencv_java.so ]; then rm ../libs/armeabi/libopencv_java.so fi if [ -e ../libs/armeabi-v7a/libopencv_java.so ]; then rm ../libs/armeabi-v7a/libopencv_java.so fi
armeabi-v7aがいらないひとは適当にけずってください。
AndroidでUVCカメラ使うためのライブラリ
AndroidでUVC(USB Video Class)カメラ使うためのライブラリを公開しました。
中身はV4L2でごーりごーりやってるのをかるーくJavaでラップしただけです。
ソースコードはGithubで公開してます。
https://github.com/crimsonwoods/UVCCapture_jni
あとAndroid標準のCameraとは互換性がまったく無いので注意。
割と無理矢理な感じだけどAndroidのCameraとまとめて使えるようにするためのglue codeを書くとこんな感じかなっていうのを書いてみた。
import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import android.graphics.ImageFormat; import android.hardware.Camera.Parameters; import crimsonwoods.android.libs.uvccap.PixelFormat; import crimsonwoods.android.libs.uvccap.UVCCamera; public class Camera { private android.hardware.Camera androidCamera; private UVCCamera uvcCamera; private boolean isPreviewing = false; private PreviewThread previewThread; private PreviewCallback previewCallback; private AutoFocusCallback autoFocusCallback; public Camera(int id, boolean isUvc) { if (isUvc) { uvcCamera = UVCCamera.open(id); } else { try { Method m = android.hardware.Camera.class.getMethod("open", int.class); if (null == m) { throw new NoSuchMethodException(); } androidCamera = (android.hardware.Camera)m.invoke(null, id); } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } catch (NoSuchMethodException e) { } if (null == androidCamera) { androidCamera = android.hardware.Camera.open(); } } } @Override protected void finalize() throws Throwable { try { super.finalize(); } finally { release(); } } public synchronized void init(int width, int height) throws IOException { if (null != androidCamera) { final Parameters params = androidCamera.getParameters(); params.setPreviewSize(width, height); params.setPreviewFormat(ImageFormat.NV21); } if (null != uvcCamera) { uvcCamera.init(width, height, PixelFormat.YUYV); if (uvcCamera.getPixelFormat() != PixelFormat.YUYV) { uvcCamera.release(); throw new IOException("Unsupported pixel format."); } } } public synchronized void release() { if (null != androidCamera) { androidCamera.release(); androidCamera = null; } if (null != uvcCamera) { uvcCamera.release(); uvcCamera = null; } } public synchronized void startPreview() { if (isPreviewing) { return; } if (null != androidCamera) { androidCamera.startPreview(); isPreviewing = true; } if (null != uvcCamera) { if (null == previewThread) { previewThread = new PreviewThread(uvcCamera); } if (null != previewCallback) { previewThread.setPreviewCallback(previewCallback); } previewThread.start(); isPreviewing = true; } } public void stopPreview() { synchronized(this) { if (!isPreviewing) { return; } if (null != androidCamera) { androidCamera.stopPreview(); isPreviewing = false; } if (null == uvcCamera) { return; } previewThread.interrupt(); } if (Thread.currentThread().getId() != previewThread.getId()) { try { previewThread.join(); } catch (InterruptedException e) { } finally { previewThread = null; } } } public synchronized void setPreviewCallback(PreviewCallback cb) { previewCallback = cb; if (null != androidCamera) { if (null != cb) { androidCamera.setPreviewCallback(androidCameraPreviewCallback); } else { androidCamera.setPreviewCallback(null); } } if (null != uvcCamera) { if (null == previewThread) { previewThread = new PreviewThread(uvcCamera); } previewThread.setPreviewCallback(cb); } } public synchronized void autoFocus(AutoFocusCallback cb) { autoFocusCallback = cb; if (null != androidCamera) { androidCamera.autoFocus(androidCameraAutoFocusCallback); } } public synchronized void cancelAutoFocus() { if (null != androidCamera) { androidCamera.cancelAutoFocus(); } autoFocusCallback = null; } public synchronized int getPreviewWidth() { if (null != androidCamera) { final Parameters params = androidCamera.getParameters(); return params.getPreviewSize().width; } if (null != uvcCamera) { return uvcCamera.getWidth(); } throw new IllegalStateException(); } public synchronized int getPreviewHeight() { if (null != androidCamera) { final Parameters params = androidCamera.getParameters(); return params.getPreviewSize().height; } if (null != uvcCamera) { return uvcCamera.getHeight(); } throw new IllegalStateException(); } public synchronized PixelFormat getPixelFormat() { if (null != androidCamera) { switch(androidCamera.getParameters().getPreviewFormat()) { case ImageFormat.NV21: return PixelFormat.NV21; case ImageFormat.YUY2: return PixelFormat.YUYV; default: // Others are not supported. return PixelFormat.UNKNOWN; } } if (null != uvcCamera) { return uvcCamera.getPixelFormat(); } return PixelFormat.UNKNOWN; } private final class PreviewThread extends Thread { private UVCCamera camera_; private PreviewCallback callback_; PreviewThread(UVCCamera camera) { camera_ = camera; setName(getClass().getSimpleName()); } public synchronized void setPreviewCallback(PreviewCallback cb) { callback_ = cb; } private void callback(byte[] pixels) { final PreviewCallback cb; synchronized(this) { if (null == callback_) { return; } cb = callback_; } cb.onPreviewFrame(pixels, Camera.this); } @Override public void run() { final byte[][] pixels = new byte[4][camera_.getFrameSize()]; int idx = 0; try { while(!Thread.interrupted()) { camera_.capture(pixels[idx]); callback(pixels[idx]); idx = (idx + 1) % pixels.length; } } catch (Throwable t) { Log.e(t, "Uncaught exception."); } } } public interface PreviewCallback { void onPreviewFrame(byte[] data, Camera camera); } public interface AutoFocusCallback { void onAutoFocus(boolean success, Camera camera); } private android.hardware.Camera.PreviewCallback androidCameraPreviewCallback = new android.hardware.Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, android.hardware.Camera camera) { final PreviewCallback cb; synchronized(Camera.this) { cb = previewCallback; } if (null != cb) { cb.onPreviewFrame(data, Camera.this); } } }; private android.hardware.Camera.AutoFocusCallback androidCameraAutoFocusCallback = new android.hardware.Camera.AutoFocusCallback() { @Override public void onAutoFocus(boolean success, android.hardware.Camera camera) { synchronized(Camera.this) { final AutoFocusCallback cb; synchronized(Camera.this) { cb = autoFocusCallback; } if (null != cb) { cb.onAutoFocus(success, Camera.this); } } } }; }