Ubuntu 18.04でAndroid emulatorのためにkvmを有効にする

新しいマシンにAndroid studioをinstallしてAVDを作ろとしたときに以下のメッセージが表示されていることに気づいて少しハマったのでメモ。

/dev/kvm is not found

基本的にこのページに書いてあると通りに進めれば良いのですが

help.ubuntu.com

このコマンドを実行する前に sudo addgroup libvirtd を実行しないと libvirtd グループが存在しないで失敗します。

$ sudo adduser `id -un` libvirtd

あと自分のパソコンがHPのワークステーションだったので以下のリンクの手順でBIOSでVTxを有効にする必要がありました。

support.hp.com

git のレポジトリのサブディレクトリをforkするには

元ネタはここから

www.slideshare.net

こういう一つのレポジトリに複数のアプリのコードが入っているとき特定のアプリのサブディレクトリのみを自分のレポジトリにコピーしたくなったのでやってみた。

github.com

git clone git@github.com:googlesamples/android-architecture-components.git
cd android-architecture-components/
git filter-branch --force --subdirectory-filter PersistenceMigrationsSample HEAD
git remote add myrepo git@github.com:hoge/PersistenceMigrationsSample.git
git push myrepo

メソッド内に定義されたロカールクラスで Gson().fromJson できない原因を調査してみた

こんな感じのコードでLoader1 クラスを使ってJSONの文字列を読むことができるが Loader2 クラスを使っては読むことができない。

class Test {
    data class Loader1 (val type: String)

    fun test() {
        val jsonString = """{"type": "abc"}"""

        val ret1 = Gson().fromJson(jsonString, Loader1::class.java)
        assert(ret1.type == "abc")

        data class Loader2 (val type: String)
        val ret2 = Gson().fromJson(jsonString, Loader2::class.java)
        assert(ret2.type == "abc") // ret2 は null
    }
}

デコンパイルして Java にしてみる

Show Kotlin bytecode を押す f:id:daisukefuji:20190717143117p:plain

Decompile を押す f:id:daisukefuji:20190717143120p:plain

JavaDecompile されたコードが表示される。 f:id:daisukefuji:20190717143124p:plain

以下抜粋した Java のコード

public final class Test {
   public final void test() {
         final class Loader2 {
            @NotNull
            private final String type;

            @NotNull
            public final String getType() {
               return this.type;
            }

            public Loader2(@NotNull String type) {
               this.type = type;
            }
         }

         Loader2 ret2 = (Loader2)(new Gson()).fromJson(jsonString, Loader2.class);
      }
   }

Java の local class が使われていることが確認できた。

続いて Gson が local class をどのように扱うのか調べる。

skipDeserializetrue なので null を返す。

      @Override public T read(JsonReader in) throws IOException {
        if (skipDeserialize) {
          in.skipValue();
          return null;
        }
        return delegate().read(in);
      }

https://github.com/google/gson/blob/9d44cbc19a73b45971c4ecb33c8d34d673afa210/gson/src/main/java/com/google/gson/internal/Excluder.java#L125

skipDeserialize 以下で excludeClasstrue になっている。

    boolean excludeClass = excludeClassChecks(rawType);

    final boolean skipSerialize = excludeClass || excludeClassInStrategy(rawType, true);
    final boolean skipDeserialize = excludeClass ||  excludeClassInStrategy(rawType, false);

https://github.com/google/gson/blob/9d44cbc19a73b45971c4ecb33c8d34d673afa210/gson/src/main/java/com/google/gson/internal/Excluder.java#L113

excludeClass に以下のコードでtrue が代入されていた。

      if (isAnonymousOrLocal(clazz)) {
          return true;
      }

isAnonymousOrLocal の実装は以下のようになっておりLoader2.class.isLocalClass() == true なので fromJson できなかった。

  private boolean isAnonymousOrLocal(Class<?> clazz) {
    return !Enum.class.isAssignableFrom(clazz)
        && (clazz.isAnonymousClass() || clazz.isLocalClass());
  }

なぜGsonはAnonymousかLocalクラスを使ってロードできないのか調べていたら以下のissueにたどり着いた。

github.com

Don't use double brace initialization. It prevents serialization and Gson is designed for symmetric serialization and serialization. Original comment by limpbizkit on 7 Feb 2013 at 1:02 Changed state: WontFix

で、最終的にちゃんとユーザーガイドに書いてあることに気づいた。

Gson can also deserialize static nested classes. However, Gson can not automatically deserialize the pure inner classes since their no-args constructor also need a reference to the containing Object which is not available at the time of deserialization. You can address this problem by either making the inner class static or by providing a custom InstanceCreator for it. Here is an example:

sites.google.com

結論: ちゃんとドキュメントを読みましょう。

HP Compaq Pro 6300 SFFを3500円で購入

秋葉原のジャンク通りを歩いていたら、こちらのパソコンが4500円から月曜特価で1000円引きされ3500円売られていた。どうせ使わないかなと思ったけどLinuxマシーンとして遊ぶのも悪くないかなと思い購入。

support.hp.com

ちょうどBangoodでCore i7-4610Yのマシンが1万4824円で売られていることがニュースになっていて少し後悔した。

https://news.nicovideo.jp/watch/nw5676116news.nicovideo.jp

とりあえず上海問屋で120GBのSSDを2000円で購入した。

GPUはGTX1050tiを動かしている人もいたけど、250wの電源とマザーボード側がPCIe 2.0なのでオーバースペック気味と判断しHPのカタログに書かれていたRADEON HD7450(実際に購入したのは6450。これは7450と同じ)を選んだ。

パーツは安いのを探してたら結果的に全部メルカリからの購入になった。(メルカリから買ったのは合計 18500円分)

  • Core i7 3770 6900円
  • PC3-12800 32GB(8GBx4) 10000円
  • シルバーグリス 300円
  • RADEON HD6450 1300円

本体とSSDも合わせると合計 23000円になった。

消費電力はこんな感じ。

パーツ名  最大  TDP(idle)
Intel core i7 3770 77W
Intel Q75 express  6.7W 6.7W?
PC3-12800 32GB 14W(推測)
RADEON HD6450 44w 10w

Core i7-4610Yよりかはパフォーマンスよい。

http://www.cpu-world.com/Compare/229/Intel_Core_i7_Mobile_i7-4610Y_vs_Intel_Core_i7_i7-3770K.html

できるだけ最新のCPUと比較するとIntel Core i5-8305Gと同等の性能。

https://cpu.userbenchmark.com/

PTAM

基本的なpackageはyumでインストール

  • TooN
  • libcvd
  • gvars3

はREADMEのバージョンのものをCVSでとってくる。 すべてPTAMをビルドしても動作しない。どうもV4L2の設定に失敗している模様。 そこで libcvd の解析する progsvideo_play_source というvideoテストプログラムがあるのでこれでテストする。

使い方:

./video_play_source -mono v4l2:///dev/video0 or ./video_play_source -rgb8 v4l2:///dev/video0

VIDIOC_S_FMT が失敗して異常終了する。 そこで cvd_src/Linux/v4lbuffer.cc を解析し V4L2Client メソッドがYUYVをサポートしていないのを発見。

f.pixelformat == fourcc("YVU9") ||
+ f.pixelformat == fourcc("YUYV")))
{
  actual_fmt = f.pixelformat;
}

こんな感じでYUYVを加えると、とりあえず-monoで絵がでるようになった。ただしゴーストがでる。。 どうもformatの設定がおかしい。 formatはfmt変数が要求のformatでv4l2から検索し存在したら actual_fmt にセットする。 ただし、grayscale(つまり-mono)では"422P", "YU12", "YV12", "411P", "YUV9", "YVU9"のいずれかを選択できる。 引数に-rgb8を指定するとfmt=RGB4になり、v4l2から探し出せないので actual_fmtNone になる。で VIDIOC_S_FMTに失敗する。 そこで libcvd をYUYVに対応させるかuvcを他のformatに対応させる必要がある。

animEffectのlinux対応

animEffectはOpenCVで取り込んだ画像に手描き風エフェクトをかけます。 ただ、これはWindows上でしかコンパイルできなかったので ちょいと修正しました。 http://www11.atwiki.jp/fiji?cmd=upload&act=open&pageid=119&file=animEffect2.tar.gz オリジナルはこっち http://www.purple.dti.ne.jp/~t-ogura/animeEffect

MicrodiaのdriverでYUYVがVIDIOC_S_FMTできない

ioctlVIDIOC_S_FMT をセットするとerrno 22が返ってくるので調べてみた。

下環境 - OS: Ubuntu 8.04 - Kernel: linux 2.6.24.22 - microdia driver: http://groups.google.com/group/microdia?hl=en - Groovy GR-CAM130N2 - ベンダーID及びプロダクトID 0c45:627b - Bridge : SN9C201 - Image Sensor : OV7660

まずはsystem callのioctlを呼ぶとcallされるkernel内のv4lのioctl関数を発見。 linux-source-2.6.24/drivers/media/video/videodev.c

433 static int __video_do_ioctl(struct inode *inode, struct file *file,
434 unsigned int cmd, void *arg)
435 {

VIDIOC_S_FMTioctl に渡すと以下のcase文にくる。どうも659行目が失敗しているかんじ。

647 case VIDIOC_S_FMT:
648 {
649   struct v4l2_format *f = (struct v4l2_format *)arg;
650 
651   /* FIXME: Should be one dump per type */
652   dbgarg (cmd, "type=%s\n", prt_names(f->type,
653   v4l2_type_names_FIXME));
654 
655   switch (f->type) {
656     case V4L2_BUF_TYPE_VIDEO_CAPTURE:
657       v4l_print_pix_fmt(vfd,&f->fmt.pix);
658      if (vfd->vidioc_s_fmt_cap)
659        ret=vfd->vidioc_s_fmt_cap(file, fh, f);
660        break;

vidioc_s_fmt_capvfd のメンバ変数なので調べてみる。以下の157目が呼ばれる。

84 struct video_device
85 {
・・・
155   /* VIDIOC_S_FMT handlers */
156   int (*vidioc_s_fmt_cap) (struct file *file, void *fh,
157   struct v4l2_format *f);

続いて microdia driver内のコードで vidioc_s_fmt_cap に実体を代入している箇所を調べる。 microdia-v4l.c

1353 int v4l_microdia_register_video_device(struct usb_microdia *dev)
1354 {
・・・
1382   dev->vdev->vidioc_s_fmt_cap = microdia_vidioc_s_fmt_cap;

v4l_microdia_register_video_device 関数内で microdia_vidioc_s_fmt_cap を代入しているので実体を調べる。

1026 int microdia_vidioc_s_fmt_cap(struct file *file, void *priv,
1027 struct v4l2_format *fmt)
1028 {
・・・
1042   ret = microdia_vidioc_try_fmt_cap(file, priv, fmt);
1043   if (ret)
1044     return -EINVAL;

1042行目があやしい・・ ついに、printk を仕込み977行目で EINVAL を返しているのを確認。どうもここが NULL なのがダメみたい。

956 int microdia_vidioc_try_fmt_cap(struct file *file, void *priv,
957 struct v4l2_format *fmt) 
958 {
・・・
970   for (index = 0; index < dev->camera.nfmts; index++) 
971     if (dev->camera.fmts[index].pix_fmt == fmt->fmt.pix.pixelformat)
972      break;
973 
974    if (index >= dev->camera.nfmts)
975      return -EINVAL;
976 
977    if (dev->camera.fmts[index].set_format == NULL)
978      return -EINVAL;

ちょっと fmts の構造体を調べてみる。 microdia-dev.c

208 static struct microdia_video_format default_fmts[] = {
209 {
・・・
243 {
244   .pix_fmt = V4L2_PIX_FMT_YUYV,
245   .desc = "YUV 4:2:2 (YUYV)",
246   .depth = 16,
247   .modes = {
248     {160, 120, SN9C20X_1_4_SCALE,
249     {0, 0, 1280, 480},
250     {0, 0, 320, 120} },
251     {320, 240, SN9C20X_1_2_SCALE,
252     {0, 0, 1280, 480},
253     {0, 0, 640, 240} },
254     {640, 480, SN9C20X_NO_SCALE,
255     {0, 0, 1280, 480},
256     {0, 0, 1280, 480} },
257   },
258   .set_format = NULL
259 },

V4L2_PIX_FMT_YUYV のところをみると .set_formatNULL が・・・。 これは絶対上記の977行目ではじかれるんじゃね?っと思いつつもう少しコードを読んでみるとどうも後から .set_format をセットしてるような箇所を発見した。

262 int dev_microdia_assign_fmts(struct usb_microdia *dev)
263 {
264   int i, j;
265 
266   dev->camera.fmts = default_fmts;
267   dev->camera.nfmts = ARRAY_SIZE(default_fmts);
268 
269   for (i = 0; i < ARRAY_SIZE(default_fmts); i++) {
270     if (dev->camera.sensor->set_yuv422 != NULL) && 271 (dev->camera.fmts[i].pix_fmt == V4L2_PIX_FMT_YUYV)
272     dev->camera.fmts[i].set_format =
273       dev->camera.sensor->set_yuv422;
274     if (dev->camera.sensor->set_bayer != NULL) && 275 (dev->camera.fmts[i].pix_fmt == V4L2_PIX_FMT_SBGGR8)
276    dev->camera.fmts[i].set_format =
277      dev->camera.sensor->set_bayer;
278    for (j = 0; j < N_MODES; j++) {
279      dev->camera.fmts[i].modes[j].hw_window[0] =
280       dev->camera.sensor->hstart;
281      dev->camera.fmts[i].modes[j].hw_window[1] =
282      dev->camera.sensor->vstart;
283     }
284   }
285 
286   return 0;
287 }

pix_fmt == V4L2_PIX_FMT_YUYV なので pix_fmt == V4L2_PIX_FMT_YUYV が存在していたら set_format にセットするようなので sensor 変数を見てみた。

36 static struct sensor_info sensors[] = {
・・・
76 {
77   .id = OV7660_SENSOR,
78   .name = "OV7660",
79   .address = 0x21,
80   .initialize = ov_initialize,
81   .set_exposure = ov7660_set_exposure,
82   .set_auto_gain = ov_set_autogain,
83   .hstart = 1,
84   .vstart = 1,
85   .get_yavg = ov965x_get_yavg,
86   .min_yavg = 30,
87   .max_yavg = 60,
88   .min_stable_yavg = 32,
89   .max_stable_yavg = 58,
90 },

set_format がない。。つまり未定義なので NULL。 ここまできてなんとなくわかったのはYUYVは基本的に未サポートでセンサーごとに実装するようなつくりになっているということ。 つまり、現段階ではOV7660のYUYVは未サポートでした。 (http://groups.google.com/group/microdia/web/project-status?hl=enのStatusはSupportedになっているのに。)

ちなみに以下の箇所をコメントアウトして動作させるとノイズだらけの映像が出力されたとさ

977 if (dev->camera.fmts[index].set_format == NULL)
978   return -EINVAL;