jamrubyのサンプルアプリ作った

jamrubyを使ったサンプルアプリを作って公開しました。

当然buildにはjamrubyが必要で、jamrubyを使うってことはmrubyも必要になります。


ということで、ここに置いてあります。
Eclipseで適当にimportして使って下さい。

https://github.com/crimsonwoods/JamrubyApp

$ git clone https://github.com/crimsonwoods/JamrubyApp.git

起動するとこんな感じのちょーださださ手抜き画面が出てきます。

f:id:crimsonwoods:20120517013239p:plain

画面上部の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とか以外だと、

  1. Android NDK
  2. bison

くらいでしょうか。


やることはこんな感じ。

  1. mrubyのソースコード取得
  2. android用のディレクトリ作成
  3. ホスト用のbuild
  4. NDK用のAndroid.mk作成
  5. NDKでbuild


ということでやりましょう。

$ 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);
				}
			}
		}
	};
}