2012年9月19日水曜日

Android Movie で File をデコードする

Movie を使うとアニメーション Gif を表示できるのですが、まぁバグがあるとかなんとかは自分でぐぐってください。 

よくあるサンプルは assets や raw にある Gif を読むやつばっかりです。
  1. InputStream is = context.getResources().openRawResource(R.drawable.piggy);  
  2. Movie movie = Movie.decodeStream(is);  
http://androidosbeginning.blogspot.jp/2010/09/gif-animation-in-android.html

とか
  1. InputStream is = context.getAssets().open("piggy.gif");  
  2. Movie movie = Movie.decodeStream(is);  
とか



SD にある Gif ファイルから読もうとして
  1. try {  
  2.     InputStream is = new FileInputStream(filePath);  
  3.     Movie movie = Movie.decodeStream(is);  
  4. catch (FileNotFoundException e) {  
  5.     e.printStackTrace();  
  6. }  
とすると Native 側で IOException が起こって表示されません。

LogCat で

09-19 15:38:37.828: W/System.err(7523): java.io.IOException 09-19 15:38:37.828: W/System.err(7523): at java.io.InputStream.reset(InputStream.java:218) 09-19 15:38:37.828: W/System.err(7523): at android.graphics.Movie.decodeStream(Native Method)

と出ていて、どうも InputStream の reset() で起こっていると。

InputStream のコードをみると、サブクラスが Override しないかぎり IOException が投げられるようになっています。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/libcore/luni/src/main/java/java/io/InputStream.java#217
  1. 217     public synchronized void reset() throws IOException {  
  2. 218         throw new IOException();  
  3. 219     }  


FileInputStream は Override してませんでした。
http://tools.oesf.biz/android-4.0.1_r1.0/xref/libcore/luni/src/main/java/java/io/FileInputStream.java

AssetManager の open() で返ってくる InputStream は AssetInputStream です。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/content/res/AssetManager.java#288
  1. 288     public final InputStream open(String fileName) throws IOException {  
  2. 289         return open(fileName, ACCESS_STREAMING);  
  3. 290     }  
  4.   
  5. 309     public final InputStream open(String fileName, int accessMode)  
  6. 310         throws IOException {  
  7. 311         synchronized (this) {  
  8. 312             if (!mOpen) {  
  9. 313                 throw new RuntimeException("Assetmanager has been closed");  
  10. 314             }  
  11. 315             int asset = openAsset(fileName, accessMode);  
  12. 316             if (asset != 0) {  
  13. 317                 AssetInputStream res = new AssetInputStream(asset);  
  14. 318                 incRefsLocked(res.hashCode());  
  15. 319                 return res;  
  16. 320             }  
  17. 321         }  
  18. 322         throw new FileNotFoundException("Asset file: " + fileName);  
  19. 323     }  
この AssetInputStream は reset() を Override しています。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/content/res/AssetManager.java#538
  1. 538     public final class AssetInputStream extends InputStream {  
  2.   
  3. 569         public final void reset() throws IOException {  
  4. 570             seekAsset(mAsset, mMarkPos, -1);  
  5. 571         }  
  6.   
  7. 597     }  


InputStream のサブクラスで reset() を継承しているクラスとして BufferedInputStream があります。

このクラスを使って、次のようにすると IOException が発生せずに読むことができます。
  1. try {  
  2.     InputStream stream = new BufferedInputStream(  
  3.         new FileInputStream(filePath), 16 * 1024);  
  4.     stream.mark(16 * 1024);   
  5.   mMovie = Movie.decodeStream(stream);  
  6. catch (FileNotFoundException e) {  
  7.     e.printStackTrace();  
  8. }  
ポイントは mark() メソッドを呼ぶ必要がある点です。



ちなみに Movie には deocdeFile() というメソッドもあるのですが、なかみは FileInputStream 使ってるだけなので、使えません。。。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/graphics/java/android/graphics/Movie.java#51
  1. 51     public static Movie decodeFile(String pathName) {  
  2. 52         InputStream is;  
  3. 53         try {  
  4. 54             is = new FileInputStream(pathName);  
  5. 55         }  
  6. 56         catch (java.io.FileNotFoundException e) {  
  7. 57             return null;  
  8. 58         }  
  9. 59         return decodeTempStream(is);  
  10. 60     }  




おまけ

Movie.cpp
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/jni/android/graphics/Movie.cpp#82
  1. 82 static jobject movie_decodeStream(JNIEnv* env, jobject clazz, jobject istream) {  
  2. 83   
  3. 84     NPE_CHECK_RETURN_ZERO(env, istream);  
  4. 85   
  5. 86     // what is the lifetime of the array? Can the skstream hold onto it?  
  6. 87     jbyteArray byteArray = env->NewByteArray(16*1024);  
  7. 88     SkStream* strm = CreateJavaInputStreamAdaptor(env, istream, byteArray);  
  8. 89     if (NULL == strm) {  
  9. 90         return 0;  
  10. 91     }  
  11. 92   
  12. 93     SkMovie* moov = SkMovie::DecodeStream(strm);  
  13. 94     strm->unref();  
  14. 95     return create_jmovie(env, moov);  
  15. 96 }  
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp#CreateJavaInputStreamAdaptor
  1.      12 class JavaInputStreamAdaptor : public SkStream {  
  2.      13 public:  
  3.      14     JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar)  
  4.      15         : fEnv(env), fJavaInputStream(js), fJavaByteArray(ar) {  
  5.      16         SkASSERT(ar);  
  6.      17         fCapacity   = env->GetArrayLength(ar);  
  7.      18         SkASSERT(fCapacity > 0);  
  8.      19         fBytesRead  = 0;  
  9.      20     }  
  10.      21   
  11.      22  virtual bool rewind() {  
  12.      23         JNIEnv* env = fEnv;  
  13.      24   
  14.      25         fBytesRead = 0;  
  15.      26   
  16.      27         env->CallVoidMethod(fJavaInputStream, gInputStream_resetMethodID);  
  17.      28         if (env->ExceptionCheck()) {  
  18.      29             env->ExceptionDescribe();  
  19.      30             env->ExceptionClear();  
  20.      31             SkDebugf("------- reset threw an exception\n");  
  21.      32             return false;  
  22.      33         }  
  23.      34         return true;  
  24.      35     }  
  25. ...  
  26.     145 SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,  
  27.     146                                        jbyteArray storage, int markSize) {  
  28.     147     static bool gInited;  
  29.     148   
  30.     149     if (!gInited) {  
  31.     150         jclass inputStream_Clazz = env->FindClass("java/io/InputStream");  
  32.     151         RETURN_NULL_IF_NULL(inputStream_Clazz);  
  33.     152   
  34.     153         gInputStream_resetMethodID      = env->GetMethodID(inputStream_Clazz,  
  35.     154                                                            "reset""()V");  
  36.     155         gInputStream_markMethodID       = env->GetMethodID(inputStream_Clazz,  
  37.     156                                                            "mark""(I)V");  
  38.     157         gInputStream_availableMethodID  = env->GetMethodID(inputStream_Clazz,  
  39.     158                                                            "available""()I");  
  40.     159         gInputStream_readMethodID       = env->GetMethodID(inputStream_Clazz,  
  41.     160                                                            "read""([BII)I");  
  42.     161         gInputStream_skipMethodID       = env->GetMethodID(inputStream_Clazz,  
  43.     162                                                            "skip""(J)J");  
  44.     163   
  45.     164         RETURN_NULL_IF_NULL(gInputStream_resetMethodID);  
  46.     165         RETURN_NULL_IF_NULL(gInputStream_markMethodID);  
  47.     166         RETURN_NULL_IF_NULL(gInputStream_availableMethodID);  
  48.     167         RETURN_NULL_IF_NULL(gInputStream_availableMethodID);  
  49.     168         RETURN_NULL_IF_NULL(gInputStream_skipMethodID);  
  50.     169   
  51.     170         gInited = true;  
  52.     171     }  
  53.     172   
  54.     173     if (markSize) {  
  55.     174         env->CallVoidMethod(stream, gInputStream_markMethodID, markSize);  
  56.     175     }  
  57.     176   
  58.     177     return new JavaInputStreamAdaptor(env, stream, storage);  
  59.     178 }  

0 件のコメント:

コメントを投稿