どうもこんにちは、コンバット○前です。 嘘です。nakazawa-kです。この数日少しずつ涼しくなってきていますね。 KLab若手エンジニアブログでiPhoneやiPadばかり書かれていてAndroidが 全く書かれていないことに気付いたので、少しずつ勉強した内容などを書いてみます。 現在KLabの社内ではMacユーザ率の上昇に合わせてiPhone開発者増加の 兆しが見えているのですが、開発の取っつきやすさではAndroidだって負けちゃいない。 なんたってMacが無くても不自由なく開発出来る(←ここ重要!)のですから。 というわけで(どういうわけか)Androidなお話です。 今回はカメラからの映像に適当なオーバーレイ要素を追加してリアルタイム エンコードすることがAndroidとそれを走らせているハードウェア上で 実現出来るのか?を調べるためにMediaRecorder関連のソースを追いかけてみました。 ひとつひとつ丹念に見ていけばOSのソースって案外辿って行けて面白いよ!というのをなんとなく感じて頂けると幸いです。
  • Androidは最高の学習環境!?
Androidはご存じの通りオープンソースなモバイル向けOSで、その比較的 新しいコードがAOSP(Android Open Source Project)として公開されています。
ちなみにそのリリース方法はLinuxのカーネル本体とは異なり、Google社が各OS世代の早期パートナー企業(端末メーカ)との間で最新のコードベースでの開発を行い、端末リリース前後のタイミングでAOSPでのコード公開を行う、という流れになっているようです。開発に協力するパートナー企業としては最新OSを搭載した端末を他社に先駆けて市場へ投入出来、またOSに対して自社の望む仕様を公式仕様として追加し易いというメリットが考えられます。
閑話休題、ソースコードが公開されているので、「ここはどういう風に 実装されているんだろう?」という疑問を持った際に、必要であれば OSのコードまでさかのぼって調査することが出来ます。 とはいえ、片っ端からコードの山をひっくり返していけば目的のコードへ 辿り着ける、というわけではありません。なんといってもコード量が莫大です。 gitからソースコードを取得することが出来ますが、AOSPのドキュメントに 従い全て取得すると4.1GB(10/08/30時点)となります。 これ位のコード量になってくると全体に対してgrepをかけるのも一苦労ですし、 検索結果に物凄い数のファイルが見つかって途方に暮れたりするものです。 このような大量のファイルから必要な情報を効率的に得るためには、システムの アーキテクチャ認識を持ち、『必要なものがありそうな場所の勘が働くようにする』 ことが結構重要なことだと思います。 システムの中での呼び出しの流れとデータの流れ(デバイスからの入出力を含め)が 掴めてくると、割と膨大なソースとも付き合いやすくなるのではないでしょうか!? 目の前で動作している端末がどのようなアーキテクチャ、システムで動作しているのかを 学ぶことは非常に良い勉強になるものと思います。せっかくソースコードが 公開されているのでどんどん読みましょう。 ということでだいぶ前置きが長くなってしまいました。nakazawa-kがソースを読み始めたというのが今回の話です。
  • 今回のターゲット:MediaRecorder
先にお断りしておきます。今回の話は、ブログ記事1本分では決着しませんでした。 目的の達成は次回までお預けとなってしまっています。
発端は「Androidのカメラから入力された映像データに対してリアルタイムに オーバーレイをかけ、H.264等で出力することは出来ないものか」と思ったところでした。 ビデオカメラ(CamCoder)といえば通常、MediaRecorder http://developer.android.com/reference/android/media/MediaRecorder.html クラスを用いてソースと出力フォーマット、出力圧縮形式、 出力先を指定した上でstart()を呼んでやれば後は適当にやってくれるという感じです。 逆に、この映像に対して加工を行うことが出来ればARアプリなどで自分の見ていた 世界を動画に記録してそのままYouTubeなどにアップロードする、というような 使い方が出来そうです(ARアプリ内で完結するなら、当然それらをメタデータとして 別途格納しておき、再生時にオーバーレイする方法が考えられますが)。 これを実現するためには
+------------+               +----------------+
| 映像ソース | --フィルタ--> |動画出力エンジン|
+------------+               +----------------+
としてやる必要があります。しかし、残念ながらMediaRecorderで提供されているのは 前述のような入出力設定のみで、実際の動画出力などがどのような仕組みにて 提供されているかは見えなくなっています。
  • 前準備
まずは今回やりたいことがどのようにして実現出来るか、あたりを付けるためにざっと情報を整理してみます。 一般にH.264等のリアルタイムエンコードを行うには携帯端末向けのCPUでは力不足です。そのため、ほとんどの携帯端末ではH.264等の圧縮を丸ごと行う、あるいは部分的にサポートするDSPを用いたエンコードを行っていると考えられます。この入力データに任意のストリームを渡せるのか、それともカメラからの入力を統合チップ内部でバイパスして渡すような方法を採らざるを得ないのか、によって今回の目的の実現可能性は大きく変わってきます(場合によっては一部WindowsMobile機のようにWMVエンコードのソフトウェア実装を持ち込んで力業でエンコードするような方針になります)。 ソースを読み始める前に、少しだけデータシート(というかチップのパンフ的な資料)を見てみましょう。 手元に転がっていたHT-03Aに搭載されている統合チップはMSM7200Aで、どうやら http://www.datasheetpro.com/268119_download_MSM7200A_datasheet.html で見ることが出来ます。 性能的には30fpsでWVGAの動画をキャプチャ出来るほどのチップであることは分かりますが、残念ながら動画エンコード機能に対して任意のストリームを投入出来るか否かについて判断は出来ませんでした。ひょっとすると入力(カメラ)と出力(動画圧縮エンジン)のやりとりをチップ内で完結させなければスループットを出せない(=フレームレートが不足する)かもしれません。 まあ多少フレームサイズやフレームレートが落ちても、加工済みの動画がリアルタイム出力出来れば良いのです。仮にカメラからのJPEG出力系を流用してMJEPGになったとしても、大失敗ではないのです。
  • 本題
それでは、AndroidのMediaRecorderがどのような実装になっているのかを見ていきましょう。 Androidのアーキテクチャは http://developer.android.com/guide/basics/what-is-android.html#os_architecture に記載されているような形になっていています。 今回の場合、Javaで書かれたカメラアプリがAndroid独自のJava系VMである Dalvik Virtual Machineにて実行され、内部からMedia Frameworkが呼び出され、 そこからLinux KernelのCamera DriverやFlash Memory Driverなどを呼び出していると 考えられます。 とりあえず、コードを取得しましょう。
curl http://android.git.kernel.org/repo > repo chmod +x repo sudo mv repo /usr/local/bin repo init -u git://android.git.kernel.org/platform/manifest.git (AOSPのドキュメントではinit-uとなっていますが、正しくは上記の通りです)
まず、おさらいの部分ですがカメラアプリのコードから追いましょう。 アプリケーションのソースはpackages/apps/Camera以下にあります。 MediaRecorderの初期化を行っているのはpackages/apps/Camera/src/com/android/camera/VideoCamera.java VideoCameraクラスのinitializeRecorderメソッドです。

 895         mMediaRecorder = new MediaRecorder();
 896
 897         mMediaRecorder.setCamera(mCameraDevice);
 898         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
 899         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
 900         mMediaRecorder.setProfile(mProfile);
 901         mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
...
以下、基本的に録画開始を指示するstart()メソッドの流れを追いかけていきます。 次は当然、android.media.MediaRecorderの定義を調べます。 早速すこしずつややこしくなってきます。ほとんどがプロキシのリレーです。 MediaRecorderの実装はほとんどJavaで行われておらず、ネイティブ実装になっています。 Java側のMediaRecorder定義はframeworks/base/media以下にあります。 frameworks/base/media/java/android/media/MediaRecorder.javaでは
 58 public class MediaRecorder
 59 {
 60     static {
 61         System.loadLibrary("media_jni");
 62         native_init();
 63     }
...
513     public native void start() throws IllegalStateException;
との定義がされています。JNIで処理をC系のライブラリに委ねている形です。 これに対応するコードがframeworks/base/media/jni/android_media_MediaRecorder.cppにあります。
339 static void
340 android_media_MediaRecorder_start(JNIEnv *env, jobject thiz)
341 {
342     LOGV("start");
343     sp mr = getMediaRecorder(env, thiz);
344     process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed.");
345 }
...
466     {"start",                "()V",                             (void *)android_media_MediaRecorder_start},
C++側でのMediaRecorderクラスは
421 static void
422 android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
423 {
424     LOGV("setup");
425     sp mr = new MediaRecorder();
で作られます。このあたりは frameworks/base/include/media/mediarecorder.hに定義があります。 各種定数もこのファイルに定義があるので参考になります。 実装はこちらです。frameworks/base/media/libmedia/mediarecorder.cpp このファイルではインスタンスが存在する場合、そのメソッドを呼び出したり 例外処理を行っているだけなので、重要なのはインスタンスの生成部分です。
586 MediaRecorder::MediaRecorder()
587 {
588     LOGV("constructor");
589
590     const sp& service(getMediaPlayerService());
591     if (service != NULL) {
592         mMediaRecorder = service->createMediaRecorder(getpid());
593     }
594     if (mMediaRecorder != NULL) {
595         mCurrentState = MEDIA_RECORDER_IDLE;
596     }
597     doCleanUp();
598 }
この呼び出しはframeworks/base/media/libmediaplayerservice/MediaPlayerService.cppの
 229 sp MediaPlayerService::createMediaRecorder(pid_t pid)
 230 {
 231 #ifndef NO_OPENCORE
 232     sp recorder = new MediaRecorderClient(this, pid);
 233     wp w = recorder;
 234     Mutex::Autolock lock(mLock);
 235     mMediaRecorderClients.add(w);
 236 #else
 237     sp recorder = NULL;
 238 #endif
 239     LOGV("Create new media recorder client from pid %d", pid);
 240     return recorder;
 241 }
に対応しています。ここで、OpenCoreライブラリを利用しない設定でのビルド時には レコーダを無効化しています。MediaRecorderClientクラスを追いかけましょう。 frameworks/base/media/libmediaplayerservice/MediaRecorderClient.cppです。
292 MediaRecorderClient::MediaRecorderClient(const sp& service, pid_t pid)
293 {
294     LOGV("Client constructor");
295     mPid = pid;
296
297 #if BUILD_WITH_FULL_STAGEFRIGHT
298     char value[PROPERTY_VALUE_MAX];
299     if (property_get("media.stagefright.enable-record", value, NULL)
300         && (!strcmp(value, "1") || !strcasecmp(value, "true"))) {
301         mRecorder = new StagefrightRecorder;
302     } else
303 #endif
304 #ifndef NO_OPENCORE
305     {
306         mRecorder = new PVMediaRecorder();
307     }
308 #else
309     {
310         mRecorder = NULL;
311     }
312 #endif
313
314     mMediaPlayerService = service;
315 }
OpenCoreが利用出来る際にはPVMediaRecorderを生成していますね。 PVでOpenCoreと来ればpacket-videoのOpenCoreです。OpenCoreはAndroid内で 利用されているマルチメディア処理ライブラリで、公式サイトは http://www.opencore.net/ です。repoコマンドで取得したツリーでは、 external/opencore/doc/にドキュメントも取得されているはずです。 実はここまで読み進めた後でOpenCoreのドキュメントを読まずに external/opencore/engines/author/src/pvauthorengine.cppあたりまで 追いかけてみたのですが、細かな個別実装ばかりが見えてきて全体が 掴みにくくなってきたのでまずはOpenCoreのドキュメントを読み、ざっと 構造を把握してから続きを読むように方針転換しました。 かなり長くなったので今回はこのあたりにして、次回OpenCoreの構造あたりから 続けていきたいと思います。