2011年12月15日

Android 4.0のスクリーンキャプチャ

スクリーンキャプチャについて調べました。KZM-A9-DualにポーティングしたAndroid4.0でうまく動かなかった部分があったのでソースを修正してみました。



生のフレームバッファをのぞく

Androidに限らず、ポーティングをしていて特にエラーになっていないのにもかかわらず画面が真っ黒のままという場合、フレームバッファデバイスを直接読み出してファイルに書き出し、それをグラフィックソフトでRaw画像として開いてみてみれば、切り分けすることができます。フレームバッファには何か描いてあるのであれば、フレームバッファのデバイスドライバの問題だし、そうでなければユーザーランド側の問題です。

# cat /dev/graphics/fb0 > /data/local/tmp/img.raw

このimg.rawをグラフィックソフト、例えばGimpで開きます。offset, width, heightも指定する必要があります。この場合ならoffset=0。widthはスクリーンの幅。(KZM-A9-Dualならば800)。heightはimg.rawのファイルのサイズ / 4(bytes per piexel) / width を指定します。

screencapコマンド

Android 4.0以前ではスクリーンショットを撮るためにはUSBケーブルで接続してDDMSの機能を使う必要がありました。Android4.0ではscreencapコマンドでデバイス単体で簡単にスクリーンショットを撮ることができるようになりました。

$ screencap -h
usage: screencap [-hp] [FILENAME]
   -h: this message
   -p: save the file as a png.
If FILENAME ends with .png it will be saved as a png.
If FILENAME is not given, the results will be printed to stdout.
$

指定したファイル名の末尾が.pngの場合はpngフォーマットで、そうでなければ、width, height, formatの各4バイト、計12バイトのヘッダをつけてその後にフレームバッファの生データを出力します。

DDMSはスクリーンショットを撮るときにデバイス側のadbdと通信するのですが、Android2.3まではadbdが直接 /dev/gaphics/fb0 をオープンして中を読んでいましたが、Android4.0ではadbdからscreencapコマンドをfork&execするようになりました。

system/core/adbd/framebuffer_service.c

screencapのバグ

KZM-A9-DualボードにポーティングしたAndroid4.0でスクリーンショットを撮ると、赤と青の色が逆になった画像になってしまいました。screencapコマンドでpngファイルを生成する場合とDDMSを使う場合の両方を試しましたが、どちらもそうなりました。

KZM-A9-DualのフレームバッファのフォーマットはBGRA_8888ですが、screencapコマンドのソースを見ると、32bits per pixelのフレームは常にRGBA_8888として扱うようになっていました。そのために赤と青がひっくり返っていました。

PARTNER-Jetでブレークポイントを仕掛けてみて、原因の部分が特定できたので簡単に修正してみました。以下はそのパッチ。

frameworks/base

diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 7a599e9..46c7386 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -73,8 +73,11 @@ static status_t vinfoToPixelFormat(const fb_var_screeninfo& vinfo,
             *bytespp = 3;
             break;
         case 32:
-            // TODO: do better decoding of vinfo here
-            *f = PIXEL_FORMAT_RGBX_8888;
+            if (vinfo.red.offset == 16) { // ARGB8888
+                *f = PIXEL_FORMAT_BGRA_8888;
+            } else {
+                *f = PIXEL_FORMAT_RGBX_8888;
+            }
             *bytespp = 4;
             break;
         default:
@@ -83,6 +86,30 @@ static status_t vinfoToPixelFormat(const fb_var_screeninfo& vinfo,
     return NO_ERROR;
 }
 
+char const*
+exchange_rb(char const* base, size_t size)
+{
+    char* mapbase;
+    char r, g, b, a;
+    int i;
+
+    mapbase = (char*)mmap(0, size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
+    if (mapbase != MAP_FAILED) {
+        while (i < size) {
+            b = base[i];
+            g = base[i + 1];
+            r = base[i + 2];
+            a = base[i + 3];
+            mapbase[i] = r;
+            mapbase[i + 1] = g;
+            mapbase[i + 2] = b;
+            mapbase[i + 3] = a;
+            i += 4;
+        }
+    }
+    return mapbase;
+}
+
 int main(int argc, char** argv)
 {
     const char* pname = argv[0];
@@ -163,6 +190,10 @@ int main(int argc, char** argv)
     if (base) {
         if (png) {
             SkBitmap b;
+            if (f == PIXEL_FORMAT_BGRA_8888) {
+                base = (void const*)exchange_rb((char const*)base, size);
+                f = PIXEL_FORMAT_RGBX_8888;
+            }
             b.setConfig(flinger2skia(f), w, h);
             b.setPixels((void*)base);
             SkDynamicMemoryWStream stream;

pngファイルを作るときにはskiaのライブラリを使っていますが、このライブラリはBGRA_8888のフォーマットをサポートしていないようだったので、イメージをコピーしてRとBをひっくり返すようにしました。

screencapコマンドでpngファイルを生成する場合とDDMSを使う場合の両方でうまくいくことを確認しました。

でも本当は、フレームバッファのフォーマットはデバイス依存なので、HALのモジュールにこの機能を移すほうが正しいやりかたです。フレームバッファに書き込むほうはHALのgrallocを通すようになっているわけですから。

おまけ

Android4.0のソースを"screenshot"で検索すると、TakeScreenshotServiceというServiceを見つけました。

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java

そのServiceはPhoneWindowManager.javaで使われています。

frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

VolumeDownキーとPowerキーが同時に長押しされて、なおかつVolumeUpキーが押されていないときにこのServiceが開始されるようです。

Galaxy Nexusではこの操作でスクリーンショットが撮れたそうです。

おまけ2

screencapコマンド以外にもうひとつscreenshotコマンドもあります。

$ screenshot -h
unknown option -- husage: screenshot [-s soundfile] filename.png
   -s: play a sound effect to signal success
   -i: autoincrement to avoid overwriting filename.png
$

こちらは同時に鳴らす音声ファイルを指定することができます。

こちらもBGRA_8888のフォーマットをサポートしていません。



トラックバックURL

コメントする

名前
URL
 
  絵文字
 
 
記事検索
最新コメント
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード