ラベル OutOfMemoryError の投稿を表示しています。 すべての投稿を表示
ラベル OutOfMemoryError の投稿を表示しています。 すべての投稿を表示

2013年6月29日土曜日

[Android] OpenGLの簡易画像ビュアーをAndroid-NDKでビルドする (失敗談)

OpenGL and JNI
AndroidアプリをAndroid-NDKで開発するとネイティブコード(Javaインタープリターを通らないマシン語)で書かれたJNI(Java Native Interface)の動作比率を増やすことができ、Javaだけで開発したアプリに比べて高速な動作やバッテリーの長持ちが期待できます。
Android-NDKで開発するデメリットとして「マルチプラットフォーム(CPUが異なる端末)で動作しなくなる」という点があげられます。
Android-NDK-r8d では
・armeabi
・armeabi-v7a
・MIPS
・x86
の4種類のCPUがサポートされています。
PowerPC系のAndroid端末が普及しない限りマルチプラットフォームの心配はないようです。
そこで、 前回 公開したOpenGLによる簡易画像ビュアーをAndroid-NDKに移植してみました。

Android-NDKの弱点
アプリケーションのresフォルダーに保存された画像や音声ファイルを直接読み込む方法がありません。
SDカードに保存された画像ファイルなら読み込むことはできますが、画像をテクスチャに表示するためには画像ファイルをBitmap(1次元配列)にデコードする必要があります。
ところがAndroid-NDKには画像ファイルをデコードするライブラリーが見当たりません。
Android-NDKで画像を扱うには次の選択肢があります。

1. AndroidのJavaのAPIで画像ファイルをデコードしたint配列をJNIに渡す。
2. 画像ファイルをデコードする処理をC/C++で自作する。
3. C言語で書かれたオープンソースの画像ローダーを利用する。

デバッグのしやすさから1番目の方法で画像ファイルをNDKで扱うことにします。

留意点
Android端末の機種によりますが、ビットマップ画像の扱い中にOutOfMemoryErrorが起きやすいです。
SH-03C(Android2.2)は 1440x1440ピクセルの画像を int配列(約8MB)にバッファーしようとすると必ず次のエラーが発生します。
Out of memory on a 8294416-byte allocation.
試行錯誤した結果、今回は次のような仕様にしました。

1. Javaで画像ファイルを読み込み、512×512に伸縮する。
2. 伸縮した画像をint配列に変換しJNIに渡す。
3. JNIのOpenGL APIで画像をテクスチャとして表示する。

このブログを書いている時点の開発環境は次の通りです。
・Windows8 (64bit)
・Eclipse Juno (32bit) (XP互換モードで起動)
・Android SDK r21.0.1
・Cygwin
・Android-NDK-r8d
※Android-NDKのビルド方法は環境に強く依存するため省略します。

2点以上のタッチ操作が必要なので、Android2.2以降でマルチタッチ対応の端末が必要です
新品で発売されているAndroid端末なら、中華Padも含めてほぼど対応していると思います。

以下ソースです。

MainActivity.java
jni.cpp
glu.h (Android-NDKのsampleの com.example.SanAngeles から抜粋しました)
Android.mk
jni.cpp、glu.h、Android.mk はプロジェクトにjniというフォルダーを追加して、そこにコピーします。
※特別な権限(Permission)は不要なのでマニフェストは省略します。
Android2.2以降で動作します。
Target SDK Version が8以上の設定でビルドできます。
Android-NDKのビルド方法は環境に強く依存するので省略させて頂きます。

留意点でも書きましたが、古いスマートフォン(Android2.2以前)で実際に使ってみると 前回のJavaだけで作ったアプリに比べて
java.lang.OutOfMemoryError: bitmap size exceeds VM budget
が起きやすいです。
画像をint配列に変換するためのメモリーを確保したり、その逆の変換をするためのメモリーを確保したりするためメモリーの空き領域が不足がちになるようです。
さらにJavaだけで書いたアプリと体感速度を比べるても差がありませんでした。
当初の予想とは裏腹にデメリットの方が多かったのでタイトルに「失敗談」と付けました。

Android4.0なら安定して動作しますが体感速度は変わりません。

関連ブログ
[Android] BitmapクラスのOutOfMemoryErrorを防ぐ
[Android] BitmapクラスのOutOfMemoryErrorを防ぐ #2
[Android] フリーサイズの画像をOpenGLのテクスチャに表示する
[Android] OpenGLで簡易画像ビュアーを作る
[Android] ジェスチャーでOpenGL簡易画像ビュアーに拡大縮小機能を追加する
[Android] OpenGLでプロ生ちゃんをアニメーションさせる
[Android] OpenGL ESで文字を表示する
[Android] OpenGLで頂点の多いポリゴンを扱うには
[Android] OpenGLでシャープ製端末用3D(立体視)アプリケーションを作る
[Blender] obj2opengl.plをJava用に改造する
[Windows8][Android] Can't bind to local 8600 for debugger


以上、参考になれば幸いです。

2012年2月18日土曜日

[Android] BitmapクラスのOutOfMemoryErrorを防ぐ #2

How to correct the OutOfMemoryError of Bitmap class #2
前回は画像表示のたびにBitmapFactory.decodeResourceを実行して、表示後にrecycle()メソッドで解放する方法でOutOfMemoryErrorを防ぎました。
この方法は安定して動作しますが、表示のたびにファイルにアクセスしてデコード処理を実行するので効率が悪いような気がします。

そこで他の方法を考えました。
Bitmapクラスは画像を整数の配列に変換する getPixelsメソッド と、整数の配列を画像に戻す setPixelsメソッド があります
画像を配列に変換したり、配列を画像に変換する処理はCPUとメモリー(RAM)の間のデータ転送が主な内容になるはずです。
ファイルアクセスやデコード処理が1回で済むのでもっと効率が上がりそうです。

今回は1024×1024ピクセルの4枚の画像を整数の配列に変換することをを試してみます。
成功すればBitmapFactory.decodeResourceは各画像につき1度しか実行されないので効率が上がるはずです。

Main3
結果
整数の配列の確保(new int[1024 * 1024])でOutOfMemoryErrorが起きてしまいました。
Android1.6と2.1のエミュレーターで確認しました。
Androidのint型は4バイトなのでint[1024 * 1024]の配列は連続した4MBの空きメモリー(RAM)が必要になるわけですが、それを4つも確保するのは難しいようです。
int[1024][1024]の2次元配列を使う方法に直して試みましたがやっぱりOutOfMemoryErrorが起きました。

RAM容量の多い端末で正常動作するかもしれませんが、エミュレーターで動作しない方法なのでお勧めできません。

ちなみにgetPixelsメソッドはJNI(Android-NDK)のOpenGLでポリゴンのテクスチャを貼り付けるときに必須のメソッドです。
http://blog.fujiu.jp/2011/08/android-android-ndk.html

今回はわざと大きい画像や配列を扱ってエラーが出ることを確認しました。
将来のAndroid端末は今以上にRAMの大容量化、画面の高解像度化が予測されます。
Android-SDKやOSも進化して大きいBitmapを扱いやすくなってます。
古い端末でも簡単に扱えるようになったらいいですね。


以上、参考になれば幸いです。

2012年2月11日土曜日

[Android] BitmapクラスのOutOfMemoryErrorを防ぐ

How to correct the OutOfMemoryError of Bitmap class

Android端末は種類が多く、画面解像度も機種ごとに違うので画像の扱いは大きな悩みどころです。
たとえばSurfaceViewに画像を表示する場合、大きめの画像データ(1024×1024ピクセル)を用意して画面解像度に合わせて縮小表示する方法が一番簡単のように思います。
ところが、Androidは大きい画像を下手に扱うと BitmapFactoryクラスが OutOfMemoryError が発生することがよくあります。

すでに多くの開発者がOutOfMemoryErrorを回避する方法を公開しています。
今更ながらMtkもいくつか方法を考えてみました。

4枚の1024×1024ピクセルの画像を1秒ごとに表示するアプリケーションで説明します。
画像は画面解像度に収まるように拡大縮小表示します。

1024×1024ピクセルのこんな画像を4種類用意します。
それぞれのファイル名はimage1.png、image2.png、image3.png、image4.pngとしました。
Androidのプロジェクトのresフォルダーの下にdrawableフォルダーを作成し、この4つの画像を保存します。

OutOfMemoryErrorが出るサンプルです。Android1.6のエミュレーターで確認しています。
※Android2.1のエミュレーターでは正常に動作しましたが実機ではOutOfMemoryErrorが起こり不安定です。
Main.java(Activity)
※Android1.6以上、パーミッション不要のため、マニフェストは省略します
Android1.6エミュレーターでアプリを実行すると
bmp2 = BitmapFactory.decodeResource ・・・
でOutOfMemoryErrorが発生します。
1024×1024ピクセルの画像(Bitmapのインスタンス)をメモリー(RAM)に置くことができないようです。

次はOutOfMemoryErrorが起きないように修正したサンプルです。
Main2.java(Activity)
Bitmapの表示のたびにファイルからロードし、次のbitmapファイルを開く前にrecycle()メソッドを実行します
表示するたびにファイルにアクセスするのは効率が悪いような気がしますが、Bitmapをメモリー(RAM)に置いておけないのだから仕方ありません。

今回はSurfaceViewを使いました。
GLSurfaceViewでポリゴンにテクスチャを貼り付けるときも GLUtils.texImage2D の直前でBitmapをロードし直後にrecycle()すると安定する思います

次回は他の方法を試してみる予定です。
公開しました http://blog.fujiu.jp/2012/02/android-bitmapoutofmemoryerror-2.html


以上、参考になれば幸いです。