‘Android’ カテゴリーのアーカイブ

EclipseにCDTを入れたらexpandUsingDeclarationsAndRemoveObjectsで落ちる

最近のADTはかなり便利で、いちいちcygwinだのndk-buildだのを呼ばなくても直接Eclipseの中からビルドができるようになっています。
ただ、僕の環境がアップデートを繰り返していたからなのか、Window→Preferences→AndroidにNDKの項目がありませんでした。ADTは21で最新になっているのに。
結局これはADTとAndroid Native Development Toolsを両方一旦削除して再インストールすることで直りました。

ところが、プロジェクトを開いた瞬間にEclipseがJavaVMごと落ちて、以降起動しない現象に陥りました。
どうもCDTのせいらしいです。

# JRE version: 6.0_24-b07
# Java VM: Java HotSpot(TM) 64-Bit Server VM (19.1-b02 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# j  org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPSemantics.expandUsingDeclarationsAndRemoveObjects([Lorg/eclipse/cdt/core/dom/ast/IBinding;Lorg/eclipse/cdt/internal/core/dom/parser/cpp/semantics/LookupData;)[Lorg/eclipse/cdt/core/dom/ast/IBinding;+0

eclipse.iniに

-XX:-UseCompressedOops

を追加すると良いと書いてあったのを見つけて、末尾に追加したら解決しました。

Bug 333227 – Oracle Java 6 update 23 crashes when running Eclipse CDT (64-bit version of CDT & JRE) [see comment 7 for workaround]

UseCompressedOopsのオプションについては以下のサイトが詳しいです。

UseCompressedOops オプションのデフォルト値 – めざせ!金持ち父さん


3D物理エンジンbulletをAndroidで使ってみた

Game Physics Simulation

以下のエントリーが詳しいです。今回はこちらで公開されているサンプルを実行してみました。

OpenGLによる3D描画とBulletによる物理演算の縫合メモ – 麗ちゃんとママの実験ノート

自分でビルドしてみようと思ってハマったポイントをいくつか書いておきます。ちなみに元のサンプルは何も問題ないです。余計なことをして個人的にハマっただけです。

bulletのバージョン

bulletのライブラリのバージョンが新しくなっているのでAndroid.mkは更新する必要があります。

bullet – Bullet is a professional free 3D Game Multiphysics Library – Google Project Hosting

僕はbullet-2.81-rev2613.zipをダウンロードしました。
jniフォルダにsrcの中身を展開します。Makefile.amを開いてリストを確認します。今回はBulletMultiThreadedを除いた、拡張子cppのファイルのリストを全部コピーして、Android.mkに貼り付けました。

ヘッダファイルは作りなおした方が良さそうです

作りなおすとヘッダファイルの内容が少し増えます。

もしくはサンプル通りの名前空間でクラスファイルを作りましょう。

後述するUnsatisfiedLinkErrorの関係で、正確なメソッド名がわからなくて困っていました。
javahコマンドで作成できます。

C:\Users\test\Documents\workspace\BulletTest>javah -classpath bin/classes -o jni/info_play_smart_android_bullettest_Cube.h info.play_smart.android.bullettest.Cube

クラスパスが間違っていると以下のようなエラーになります。

C:\Users\test\Documents\workspace\BulletTest>javah -classpath bin -o jni/info_play_smart_android_bullettest_Cube.h info.play_smart.android.bullettest.Cube
エラー:info.play_smart.android.bullettest.Cube にアクセスできません。
info.play_smart.android.bullettest.Cube のクラスファイルが見つかりません
javadoc: エラー - クラス info.play_smart.android.bullettest.Cube が見つかりません。
Error: コマンド行でクラスが指定されませんでした。-help で確認してください。

ただ、引数の名前と数が変わってしまうので、Cube.cppファイルから引数の部分だけコピーしてヘッダファイルを更新しました。

名前空間とアンダースコア

名前空間にアンダースコアが含まれるクラスを作っていたので、以下のようなエラーが発生しました。

01-05 12:56:43.351: W/dalvikvm(20100): threadid=11: thread exiting with uncaught exception (group=0x40c5d300)
01-05 12:56:43.390: E/AndroidRuntime(20100): FATAL EXCEPTION: AsyncTask #1
01-05 12:56:43.390: E/AndroidRuntime(20100): java.lang.RuntimeException: An error occured while executing doInBackground()
01-05 12:56:43.390: E/AndroidRuntime(20100):     at android.os.AsyncTask$3.done(AsyncTask.java:299)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.FutureTask.run(FutureTask.java:137)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.lang.Thread.run(Thread.java:856)
01-05 12:56:43.390: E/AndroidRuntime(20100): Caused by: java.lang.UnsatisfiedLinkError: Native method not found: info.play_smart.android.bullettest.Cube.simulate:(Linfo/play_smart/android/bullettest/Cube;)V
01-05 12:56:43.390: E/AndroidRuntime(20100):     at info.play_smart.android.bullettest.Cube.simulate(Native Method)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at info.play_smart.android.bullettest.Cube.simulate(Cube.java:75)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at info.play_smart.android.bullettest.Cube$AsyncTestTask.doInBackground(Cube.java:69)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at info.play_smart.android.bullettest.Cube$AsyncTestTask.doInBackground(Cube.java:1)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at android.os.AsyncTask$2.call(AsyncTask.java:287)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
01-05 12:56:43.390: E/AndroidRuntime(20100):     ... 5 more

JNIの呼び出しは

JNIEXPORT void JNICALL Java_名前空間_クラス名_関数名

で定義された関数を呼び出します。

.(ドット)は_(アンダースコア)に変換します。

今回、名前空間にinfo.play_smartを使ったのでinfo_play_smartとしていたのですが、それが間違いでした。

すでにあるアンダースコアは「_1」に変換しておく必要があります。

この辺は上記ヘッダファイルを作ったときに初めて知りました。(ヘッダファイル中に記述されるので)

名前空間を元のサンプルに合わせておけばもっと楽にできたので残念です。

一応これで動くようになりました。

LuaかSquirrelとバインドして動くようにできたら便利だろうなぁ。誰か作らないかなぁ。


AndroidのORM、greenDAOを使ってみた

Androidで使えるORMとしてはormliteが有名ですが、より高速だという触れ込みのgreenDAOを使ってみました。バージョンは1.2です。
greenDAO – Android ORM for SQLite | greenDAO is a light & fast ORM solution that maps objects to SQLite databases.

greenDAOはSQLiteをDAO(Data Access Object)で扱うことができます。
ジェネレーターを使ってエンティティとDAOをあらかじめ自動生成することで、データベース定義のコードを書く手間から解放されます。

このスライドも参考になります。後半はパフォーマンスに関する話題です。

まずはExampleを触ってみます。このExampleは自分で一度試してみたほうが良さそうです。今後はDaoExampleGeneratorを編集して自分のプロジェクトに適用することになるので。

EclipseのFileメニューからImport->Existing Projects into Workspaceで
DaoExample
DaoExampleGenerator
を取り込みます。

de.greenrobot.daogenerator.gentest.ExampleDaoGeneratorを見ると、バージョン番号、書き出し先のパッケージ名、スキーマ定義、そして書き出し先の相対パスを指定するだけの内容になっていることがわかります。

実際に使うときはこのファイルを書き換えてスキーマ定義をしていきます。
新規に同じ内容のプロジェクトを作ってしまってもいいですが、毎回このDaoExampleGeneratorをインポートして名前変更するのが楽かと思います。

WS000031

ジェネレーターはJava Applicationとして実行します。

image

ExampleDaoGeneratorを実行するとコンソールにログが出ます。

greenDAO Generator
Copyright 2011-2012 Markus Junginger, greenrobot.de. Licensed under GPL V3.
This program comes with ABSOLUTELY NO WARRANTY
Processing schema version 3...
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\NoteDao.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\Note.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\CustomerDao.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\Customer.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\OrderDao.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\Order.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\DaoMaster.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\DaoSession.java
Processed 3 entities in 334ms

これで上記ファイルが生成されました。

ちなみにExampleでは不要ですが、対象プロジェクトのsrc-genフォルダはあらかじめ作っておく必要があるようです。

また、プロジェクトのプロパティから、Java Build Path→Sourceタブを開いて、Add Folderを実行、src-genを追加する必要があります。

libsフォルダも作成して、greenDAO.jarをコピー、Java Build Path→Librariesタブ→Add JARsでgreenDAO.jarを参照します。

全件読み込みはloadAllメソッドを使えばリストオブジェクトで取得できます。

        List<Note> list = noteDao.loadAll();

Cursorを取得する場合はSQLiteDatabaseに対してqueryを実行することもできます。

        cursor = db.query(noteDao.getTablename(), noteDao.getAllColumns(), null, null, null, null, orderBy);

書き込みはオブジェクトのインスタンスを作成してinsert、という仕組みです。

        Note note = new Note(null, noteText, comment, new Date());
        noteDao.insert(note);

insert系メソッドも、同時に複数追加できるinsertInTxや、insertもしくはupdateで、さらに複数実行のinsertOrReplaceInTxなどもあります。

必要なら生成されたDAOを拡張してメソッド追加すれば色々便利そうです。

この辺はJavaDoc参照です。

http://greendao-orm.com/javadoc/greendao/

複雑な検索はクエリービルダーを使います。

Queries | greenDAO – Android ORM for SQLite

List joes = userDao.queryBuilder()
.where(Properties.FirstName.eq("Joe"))
.orderAsc(Properties.LastName)
.list();

サブクエリも自分で書けるみたいなので、greenDAOの利用が制約にならないというのはいいですね。

あと、バージョン番号は指定できますが、マイグレーション処理については自分で書く必要があります。DaoMaster.OpenHelper(SQLiteOpenHelperの拡張)を拡張してonUpgradeを書くことになります。初期データを入れたい場合も同じようにonCreateをオーバーライドします。そもそもOpenHelperはabstractなので必ずサブクラスを作る必要があります。

OpenHelperとDevOpenHelperがありますが、DevOpenHelperはonUpgrade時にデータベースを一度ドロップする点が違います。リリースビルドではOpenHelperのサブクラスを使います。

public class MyOpenHelper extends OpenHelper {

    private static final String DB_NAME = "clocks-db";

    private SQLiteDatabase db = null;
    private DaoMaster daoMaster = null;
    private DaoSession daoSession = null;

    public MyOpenHelper(Context context, CursorFactory factory) {
        super(context, DB_NAME, factory);

        // onCreate呼び出しのためにデータベース作成
        this.getDatabase();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        super.onCreate(db);

        // ローカル変数としてセッションを作成する
        DaoMaster daoMaster = new DaoMaster(db);
        DaoSession daoSession = daoMaster.newSession();
        // 初期データ作成
        ClockSettingDao clockSettingDao = daoSession.getClockSettingDao();
        ClockSetting data = new ClockSetting(null, "", 0, 0);
        clockSettingDao.insert(data);
    }

    @Override
    public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
    }

    public SQLiteDatabase getDatabase() {
        if (this.db == null) {
            this.db = this.getWritableDatabase();
        }

        return this.db;
    }

    public DaoMaster getDaoMaster() {
        if (this.daoMaster == null) {
            this.daoMaster = new DaoMaster(this.getDatabase());
        }

        return this.daoMaster;
    }

    public DaoSession getDaoSession() {
        if (this.daoSession == null) {
            this.daoSession = this.getDaoMaster().newSession();
        }

        return this.daoSession;
    }

}

セッションはアプリケーションスコープで持つことを推奨されています。

http://greendao-orm.com/documentation/how-to-get-started/#comment-45

public class MyApplication extends Application {

    private MyOpenHelper dbHelper;

    @Override
    public void onCreate() {
        super.onCreate();

        this.dbHelper = new MyOpenHelper(this, null);
    }

    public DaoSession getDaoSession() {
        return this.dbHelper.getDaoSession();
    }

}

ちなみにcloseは不要です。

SQLiteを使う場合の注意点 – 一歩ずつコツコツと

AndroidManifest.xmlのapplicationにMyApplicationへのクラスパスを指定します。

    <application
        android:name="test.MyApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/TestTheme" >

呼び出しはこんな形で。

        MyApplication app = (MyApplication) getApplication();
        DaoSession daoSession = app.getDaoSession();
        ClockSettingDao clockSettingDao = daoSession.getClockSettingDao();

        ClockSetting data = clockSettingDao.queryBuilder()
                .orderAsc(Properties.Priority).limit(1).list().get(0);

使ってみた感想ですが、あまり癖がないので使いやすいと思います。せっかくジェネレートしてくれるなら、自分の処理を追加するための拡張クラスも作っておいてくれるとさらに便利なのですが。

最後に、一点気になる点は、生成されたファイルがCR+LF改行だったことです。Windows環境だからかな…


emo-frameworkを使ってみたので勘所

emo-frameworkが気になったので、勉強としてAndroidでゲームを一本作りました。

emo-framework – open source game engine for Android and iOS

使用したバージョンはemo-framework 0.2.0です。

盛りブロック
https://play.google.com/store/apps/details?id=info.play_smart.android.DishAndBlocks

device-2012-12-23-005339

ゼロから作り始めて3日ほどでリリースできました。
一番時間がかかったのはブロックのfixtureをひとつのbodyとして定義する部分です。

いくつか開発中に気になったところを書いてみます。

Squirrelはわかりやすい

emo-frameworkではSquirrelというスクリプト言語が使われています。
http://www.squirrel-lang.org/doc/squirrel3.html
いろんな言語のいいとこ取りをしたような言語です。
覚える必要もないくらい直感的に使えます。

サンプルが充実している

非常に助かります。とにかくサンプルとphysics.nutを何回も読むことになります。
box2dを使って物理演算するときはfixtureとphysicsInfoとSpriteとbodyの関係はよく把握しておく必要があります。

動作が速い

この程度のゲームなら動作速度に困ることはないようです。

onContactの引数で渡されるfixtureが何かを簡単に判定できない

衝突検知のコールバックであるonContactは衝突した2つのfixtureを引数に持ちますが、fixtureからSpriteを取り出す方法がないので何のSpriteなのかがわかりません。
bodyは取り出せるので、bodyのidを使って既存のSpriteに一致しているものがあるかどうかを調べることはできます。

    function onContact(state, fixtureA, fixtureB, 
            position, normal, normalImpulse, tangentImpulse) {
        if (state == PHYSICS_STATE_ADD) {
            // ID(userdata)から床と接触しているか判定する
            local fixtureId = floor.getPhysicsInfo().getFixture().id;
            if (fixtureA.id == fixtureId || fixtureB.id == fixtureId) {
                // ブロックのidを保持していた配列を全部回して判定する
                foreach (blockId in blockIds) {
                    if (blockId == fixtureA.id || blockId == fixtureB.id) {
                        doGameOver();
                        break;
                    }
                }
            }
        }
    }

もしかしたら他に方法があるのかもしれません。

Zインデックスは常に明示した方が良い

spriteのsetZ(num)でZインデックスを明示するようにしましょう。

あと、show()もセットで実行したほうがいいみたいです。Zオーダーがよくわからないことになるので。

        local score = emo.Sprite("score.png");
        score.move(11, 24);
        score.load();
        score.setZ(3);
        score.show();

1つのbodyに複数のfixtureを定義する場合は自力で書く

emo::Physics::createSpriteはSpriteと同じサイズの長方形Shapeを作成します。

なので、複雑な形状を作成したい場合は、emo::Physics::createDynamicSpriteなどを使わずにemo::Physics::createSpriteをコピーして自前で実装することになります。

body.createFixture(fixtureDef);を複数回実行すると、bodyに複数のfixtureを作成できます。

Android版では広告を同じ画面に入れられない

http://code.google.com/p/emo-framework/issues/detail?id=13

いわゆるView全般を入れられません。

iOS版ではiAdを入れられるようです。

http://www.emo-framework.com/AdvancedTips.html

広告を入れたければゲーム画面ではない、例えばランキング画面などに入れる方法を取る必要がありそうです。

EclipseならSQDEV入れましょう

http://wiki.squirrel-lang.org/default.aspx/SquirrelWiki/SQDEV.html

*.nutファイルのSyntax Highlightとかしてくれます。

ただ、構文エラー判定がおかしいのか、時々何も問題ない箇所でエラーを通知してきます。

エラーが通知されるとビルドできなくなってしまいますが、その行を一度カットしてペーストしなおせばエラーが消えます。

intent連携ができないらしい

どうやらintent連携ができないようです。

前述の問題で、ランキングページを別Activityで作ろうと思っていたのですが、飛べません。

多分ライブラリのJavaGlue.cppとかEmoActivity.javaあたりに手を入れれば実装できると思います。

もしくは、EmoActivity.javaを継承してonCreateOptionsMenuをオーバーライド、オプションメニューから画面遷移、という方法なら簡単に実装できます。

ただ、Twitterで共有などを実装したいとなると厄介です。OS個別の機能はあまり実装が進んでいないようです。

まとめ

box2d系で趣味的なアプリを作る分には開発コストもかなり低いので有効だと思います。

ただ、今年5月以降開発が進んでいないようなのと、開発者がひとりみたいなのでこのまま開発が止まってしまうような不安があります。オープンソースでNew BSDライセンスだから好きにやれという話だとは思いますが。


android update projectのtargetパラメータ

Androidプロジェクトをantでビルドしていたのですが、以下のエラーが発生してコンパイルできない現象が発生。

[renderscript] No RenderScript files to compile.
     [echo] ----------
     [echo] Handling Resources...
     [aapt] Generating resource IDs...
     [aapt] /path/to/Project/res/layout/about.xml:31: error: No resource identifier found for attribute 'contentDescription' in package 'android'
     [aapt] /path/to/Project/res/layout/about.xml:2: error: Error: String types not allowed (at 'layout_width' with value 'match_parent').
     [aapt] /path/to/Project/res/layout/about.xml:2: error: Error: String types not allowed (at 'layout_height' with value 'match_parent').

なんという事はない。android update projectコマンドに渡す–targetオプションでSDKのレベルを指定してやれば良かったのでした。

  Updates an Android project (must already have an AndroidManifest.xml).
Options:
  -l --library    : Directory of an Android library to add, relative to this
                    project's directory.
  -p --path       : The project's directory. [required]
  -n --name       : Project name.
  -t --target     : Target ID to set for the project.
  -s --subprojects: Also updates any projects in sub-folders, such as test
                    projects.

この説明ではターゲットに何を設定すればいいのかわからなくて…

–target 9を指定して、2.3を対象にしました。

それもそもそも

Error: The project either has no target set or the target is invalid.
Please provide a --target to the 'android update' command.

このエラーが出たためにtarget指定することになったわけですが。


Androidのグラデーションが縞にならないようにする

スプラッシュ画面などで、グラデーションのかかった画像を一枚表示することがあります。

その際、グラデーションにマッハバンドと言われる縞が出ることがあり、グラデーションがきれいに見えない原因になります。

解決方法は簡単で、以下2ステップで解消できます。

1.スプラッシュのActivityでWindowのPixelFormatを指定する

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        Window window = getWindow();
        window.setFormat(PixelFormat.RGBA_8888);
    }

2.画像を全解像度用に用意する

ldpi,mdpi,hdpi,xhdpiそれぞれに適切に拡大・縮小した画像を用意しておきます。これをやらないと、内部的に自動で拡大縮小されるので、その際にマッハバンドが発生します。


Android開発で3G回線速度を再現する

ネットワークを利用するAndroidアプリを開発していると、回線速度が遅い場合の処理を気にしない訳にはいかなくなります。

開発中はWi-Fiで接続しているから気付かなかった問題が、SIMの入った実機では頻発するなんてこともあります。

通信の内容確認と、速度の制限のために、PCのProxyを経由して通信するようにします。Wi-FiのProxy設定はICS(4.0)から可能です。

設定済みのネットワークであれば、長押しして設定画面を表示します。

image

プロキシ設定を手動にして、ホスト名にPCのIPアドレスを入力します。

image

PC側にはFiddlerを入れます。

Fiddler側の設定はこちらにまとめています。
Androidエミュレータの通信をFiddlerで見る : blog.loadlimit – digital matter –

Android実機からFiddler経由で接続できるようになったら、速度制限を試してみます。

メニューのRulesから、Performance→Simulate Modem speedsにチェックを入れると、「モデムの回線速度」をエミュレートできます。

WS000027

ただ、モデムなので遅くて最近のネットワークとしては使い物になりません。動画ダウンロードに数時間かかるとかザラです。

ここから速度を調整します。

Rules→Customize Rulesを開きます。デフォルトではメモ帳でJSファイルが開きます。

image

request-trickle-delayという行を探します。

		if (m_SimulateModem){
			// Delay sends by 300ms per KB uploaded.
			oSession["request-trickle-delay"] = "300";
			// Delay receives by 150ms per KB downloaded.
			oSession["response-trickle-delay"] = "150";
		}

ここで1KBごとに実行する遅延時間を設定することができます。request-trickle-delayがアップロードの遅延時間、response-trickle-delayがダウンロードの遅延時間です。数字が小さいほど高速になります。

デフォルトの設定ではアップロードが1KBで300msの遅延なので、3.33KB/s、27.3kbpsくらいです。ダウンロードは54.6kbpsくらいです。1024*(1000/150)*8/1000=54.6133..(kbps)ですね。

例えば好意的に見て3Gが1500Kbpsで上下通信できるとすれば、delay値は5.33、小数を切り捨てて「5」としておきます。5msの遅延ではあまり違いがわからないかもしれないので、僕は単純にそれぞれ30と15にしています。

編集が終わったらJSファイルを保存して閉じます。これで遅いネットワークが再現できます。

Fiddlerを使えば、他にもAutoResponder機能で、レスポンスを全部定義してサーバ代わりにしたり、通信自体にブレークポイントを設定してネットワークが応答しなかった場合の処理の確認も可能です。

追記(2013/06/13)

エミュレータの場合はnetspeedオプションをつけることで、速度を指定できます。

emulator.exe -netspeed umts

とすれば、3G回線速度がエミュレーションされます。

その他の速度についてはこちら。

Using the Emulator | Android Developers

ディレイも設定できるので、

emulator.exe -netdelay umts -netspeed umts

という感じで起動すれば良いかと思います。


g_lag_in_frames out of range [..0]

D/ffmpeg(28670): [libvpx @ 0xb62eb0] Failed to initialize encoder: Invalid parameter
D/ffmpeg(28670): [libvpx @ 0xb62eb0]   Additional information: g_lag_in_frames out of range [..0]

libvpxのconfigureに

–disable-realtime-only

を追加

あとついでに他のffmpegの悩んでた問題。

Output file is empty, nothing was encoded

pass 1とか設定してた。2パスエンコードの1パス目の指定なので、動画が出力されなくて当然。pass設定不要。

今回は色々ハマったけど、何とかAndroid上でffmpegを使って画像シーケンスからwebm動画を作ることに成功したので、あとで方法まとめます。

これで撮影した動画をAndroid上でエフェクトかけて動画に書き戻すことができるようになった。x264外してlibvpx利用したffmpegだからLGPLだし。


Android版OpenCVでファイルからのキャプチャ

Android版のOpenCVで、ローカルのSDカード内にある動画ファイルを開いて編集したかったのですが、VideoCapture.javaにはネイティブメソッドの定義があるものの、Java側にコンストラクタが用意されていなかったので、こんな感じでMyVideoCapture.javaを用意して試してみました。

import org.opencv.highgui.VideoCapture;

public class MyVideoCapture extends VideoCapture {

    protected final long nativeObj;

    public MyVideoCapture(String file) {
        nativeObj = n_VideoCapture(file);

        return;
    }

    private static native long n_VideoCapture(java.lang.String filename);

}

結果としては、UnsatisfiedLinkErrorが発生しました。

09-16 23:34:51.932: W/dalvikvm(23911): No implementation found for native Lcom/example/android/opencv_test/utils/MyVideoCapture;.n_VideoCapture (Ljava/lang/String;)J
09-16 23:35:04.151: D/AndroidRuntime(23911): Shutting down VM
09-16 23:35:04.151: W/dalvikvm(23911): threadid=1: thread exiting with uncaught exception (group=0x40a251f8)
09-16 23:35:04.182: E/AndroidRuntime(23911): FATAL EXCEPTION: main
09-16 23:35:04.182: E/AndroidRuntime(23911): java.lang.UnsatisfiedLinkError: n_VideoCapture
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at com.example.android.opencv_test.utils.MyVideoCapture.n_VideoCapture(Native Method)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at com.example.android.opencv_test.utils.MyVideoCapture.<init>(MyVideoCapture.java:10)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at com.example.android.opencv_test.MainActivity.onClick(MainActivity.java:92)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.view.View.performClick(View.java:3511)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.view.View$PerformClick.run(View.java:14105)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.os.Handler.handleCallback(Handler.java:605)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.os.Handler.dispatchMessage(Handler.java:92)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.os.Looper.loop(Looper.java:137)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.app.ActivityThread.main(ActivityThread.java:4424)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at java.lang.reflect.Method.invokeNative(Native Method)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at java.lang.reflect.Method.invoke(Method.java:511)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at dalvik.system.NativeStart.main(Native Method)

原因はファイルを開いてデコードするのに必要なnative側の機能を、Androidが持っていないため無効になっている、らしいです。

VideoCapture(String) interface – Google グループ

まぁ、キャプチャだけの用途ならMediaMetadataRetriever.getFrameAtTimeが使えるので、回避はできそうですが、先行きが不安です。

追記:

OpenCV 2.4.3ではAndroid 3.0以上に対してこの機能が有効になるそうです。OpenCV 2.4.3は2012年9月上旬にはリリースされるとのこと。

How to read a avi file from Android Opencv? – Google グループ

でもなぁ、最低でもAndroid 2.3はサポートしないとなぁ…


AsyncTaskLoaderのあるActivityに戻ってきたときに再度loadInBackgroundが呼ばれる問題

最近はもっぱらAndroidのSupport LibraryでFragmentActivityな日々ですが、AsyncTaskLoaderが良いというウワサなのでAsyncTaskを置き換える程度の気持ちで使ったらガッツリハマりました。

今回の問題はAsyncTaskLoaderを実装したActivityから他のActivityに移動し、戻るボタンで戻ってきたときに勝手にloadInBackgroundが実行されてしまう問題です。

結論としては、何も考えずに以下サイトのdeliverResult以下全部自分のAsyncTaskLoaderにコピーしておけばいいです。

android – onLoadFinished not called after coming back from a HOME button press – Stack Overflow

一応転載しておきます。

public abstract class AsyncLoader<D> extends AsyncTaskLoader<D> {

    private D data;

    public AsyncLoader(Context context) {
        super(context);
    }

    @Override
    public void deliverResult(D data) {
        if (isReset()) {
            // An async query came in while the loader is stopped
            return;
        }

        this.data = data;

        super.deliverResult(data);
    }


    @Override
    protected void onStartLoading() {
        if (data != null) {
            deliverResult(data);
        }

        if (takeContentChanged() || data == null) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
         // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        data = null;
    }
}

この例では抽象クラスなので同じようなことをする場合はAsyncLoaderをextendsすればいいですね。

こちらも大体同じ構成のようです。

時代は AsyncTask より AsyncTaskLoader – ぐま あーかいぶ

呼び出しに時間のかかるサーバサイドのAPIの処理に利用しようとして、ActivityのonCreateで

getSupportLoaderManager().initLoader(0, null, mCallbacks);

として、呼び出しのたびに

        Bundle bundle = new Bundle(1);
        bundle.putString(KEY_PARAM_1, "something");
        getSupportLoaderManager().restartLoader(0, bundle, mCallbacks);

として呼び出していました。

戻るボタンで戻ってくると、これらを通らずにLoaderがonStartLoadingを実行するので、ここでforceLoadを実行していると、loadInBackgroundが実行されてしまいます。

まぁ、結局問題は条件をつけずに

    @Override
    protected void onStartLoading() {
        forceLoad();
    }

にしてしまっていたことなので、気をつけましょうという話です。

Loaderは概念もうちょっと理解しないと、危ないですね。