euphonictechnologies’s diary

Haskell超初心者の日記です。OCamlが好きです。

follow us in feedly

RustでCのライブラリを頑張って呼ぶ

何がしたいの?

daala(https://github.com/xiph/daala)動画コーデックライブラリをRustにポートしたいのです。Rustで開発した方がきっと清潔でスピードも早いのでは!ではでは!

そこでまずは

daalaのライブラリに付属の例題であるところのexample_encode.cのRust版を作ってみましょう。これはコマンドラインで使えるエンコーダです。

開発環境を整える

MacにVSCodeプラグイン入れるのが快適です。素直にRustプラグインとRust(rls)プラグイン入れましょう。どっちも入れといて大丈夫っぽいです。 RustのインストールはRust公式の通りで大丈夫そうです。

daalaをGitHubからクローンしてmakeする

これも簡単です。書いてあるとおりにautogen.shして./configureしてmakeしましょう。

プロジェクト作る

> cargo new daara --bin

としましょう。以下、xiph.orgのCで書かれたライブラリをdaala、今から作るものをdaaraと呼びます。laとra(rはRustのrです)の違いです。la〜。シンガポールの人みたいですね。

daalaライブラリとかファイルを配置する

daalaのライブラリはsrcディレクトリの下にまるごとソフトリンクとして配置しちゃいましょう。私のMac上ではdaalaとdaaraは同じディレクトリの下にあります。なので../daala/srcと../daala/includeをsrc/daalaの下にソフトリンクしちゃいましょう。

さらにcargoでdaalaとdaara一緒にビルドするためにcc-rs(https://github.com/alexcrichton/cc-rs)を使うためにCargo.tomlをこんな感じに。

[package]
name = "daara"
version = "0.1.0"
authors = ["yoshi <ysnr.kdm@gmail.com>"]
build = "build.rs"
links = "libdaalaenc"

[dependencies]
libc = "0.2.0"

[build-dependencies]
cc = "1.0"

libdaalaencは今から我々がビルドするdaalaのライブラリのファイル名です。

ビルドのためにbuild.rsをまず実行して、libdaalaencを生成します。そのあとにRustのコードをコンパイルして最後にリンカが動きます。cc-rsがそのあたりを面倒見てくれます。build-dependenciesに書いてあるcc = "1.0"がそれです。

build.rsをまず見てみましょう。

extern crate cc;

fn main() {
    cc::Build::new()
        .file("src/daala/src/accounting.c")
        .file("src/daala/src/encode.c")
        .file("src/daala/src/generic_code.c")
        .file("src/daala/src/infoenc.c")
        .file("src/daala/src/laplace_tables.c")
        .file("src/daala/src/pvq.c")
        .file("src/daala/src/state.c")
        .file("src/daala/src/zigzag32.c")
        .file("src/daala/src/block_size_enc.c")
        .file("src/daala/src/entcode.c")
        .file("src/daala/src/generic_decoder.c")
        .file("src/daala/src/internal.c")
        .file("src/daala/src/logging.c")
        .file("src/daala/src/pvq_decoder.c")
        .file("src/daala/src/switch_table.c")
        .file("src/daala/src/zigzag4.c")
        .file("src/daala/src/dct.c")
        .file("src/daala/src/entdec.c")
        .file("src/daala/src/generic_encoder.c")
        .file("src/daala/src/intra.c")
        .file("src/daala/src/mc.c")
        .file("src/daala/src/pvq_encoder.c")
        .file("src/daala/src/tf.c")
        .file("src/daala/src/zigzag64.c")
        .file("src/daala/src/decode.c")
        .file("src/daala/src/entenc.c")
        .file("src/daala/src/info.c")
        .file("src/daala/src/laplace_decoder.c")
        .file("src/daala/src/mcenc.c")
        .file("src/daala/src/quantizer.c")
        .file("src/daala/src/util.c")
        .file("src/daala/src/zigzag8.c")
        .file("src/daala/src/dering.c")
        .file("src/daala/src/filter.c")
        .file("src/daala/src/infodec.c")
        .file("src/daala/src/laplace_encoder.c")
        .file("src/daala/src/partition.c")
        .file("src/daala/src/rate.c")
        .file("src/daala/src/zigzag16.c")
        .include("src/daala/include")
        .compile("libdaalaenc.a");   
}

力業ですね。src/daala/src/*.cを全部追加しました。.fileのファイルはすべてコンパイルされて.oファイルを吐かせます。最後の.includeでインクルードヘッダを読み込んで.compileでその名前のスタティックライブラリをどこか適当なところに生成します。

ここら辺全部終わるとディレクトリ構成は以下のような感じになっているはずです。

.
├── Cargo.lock
├── Cargo.toml
├── build.rs
├── src
│   ├── daala
│   │   ├── include -> ../../../daala/include
│   │   └── src -> ../../../daala/src
│   └── main.rs
└── target
    ├── ...

とりあえずdaala_infoの初期化だけでも

してみましょう。

extern crate libc;

pub enum daala_enc_ctx {}

#[repr(C)]
#[derive(Default, Debug)]
pub struct daala_plane_info {
  pub xdec: u8,
  pub ydec: u8,
}

#[repr(C)]
#[derive(Default, Debug)]
pub struct daala_info {
  pub version_major: u8,
  pub version_minor: u8,
  pub version_sub: u8,
  /** pic_width,_height form a region of interest to encode */
  pub pic_width: libc::int32_t,
  pub pic_height: libc::int32_t,
  pub pixel_aspect_numerator: libc::uint32_t,
  pub pixel_aspect_denominator: libc::uint32_t,
  pub timebase_numerator: libc::uint32_t,
  pub timebase_denominator: libc::uint32_t,
  pub frame_duration: libc::uint32_t,
  /**The amount to shift to extract the last keyframe number from the granule
   *  position. */
  pub keyframe_granule_shift: i32,
  /** bitdepth_mode is one of the three OD_BITDEPTH_MODE_X choices allowed
   * above. */
  pub bitdepth_mode: i32,
  /**FPR must be on for high-depth, including lossless high-depth.
     When FPR is on for 8-bit or 10-bit content, lossless frames are still
      stored in reference buffers (and input buffers) with 8 + OD_COEFF_SHIFT
      bit depth to allow streams with mixed lossy and lossless frames. Having a
      mix of reference buffers stored in 10-bit and 12-bit precisions would be
      a disaster, so we keep them all at 12-bit internally.
   */
  pub full_precision_references: i32,
  pub nplanes: i32,
  pub plane_info: [daala_plane_info; 4],
   /** key frame rate defined how often a key frame is emitted by encoder in
    * number of frames. So 10 means every 10th frame is a keyframe.  */
  pub keyframe_rate: i32,
}

#[link(name = "daalaenc", kind="static")]
extern {
    fn daala_encode_create(info: *mut daala_info) -> daala_enc_ctx;
    fn daala_info_init(info: *mut daala_info);
}

fn main() {
    println!("Hello, world!");
    let mut di = Box::new(Default::default());
    unsafe { daala_info_init(&mut *di); };
    println!("{:?}", di);
}

daala_encode_createとdaala_info_initはexample_encode.cにあります。daala_info_initはdaala_info構造体を初期化してくれます。これを使っていろいろ設定してdaala_encode_createにそれを渡すとdaala_enc_ctx(エンコード用のコンテキスト)を作ってもらえて、それを使うとエンコードできますね。

上のコードをビルドしてみますと

$ cargo build
   Compiling daara v0.1.0 (file:///hogehogehogehoge/codes/daara)
warning: src/daala/src/laplace_encoder.c:66:34: warning: for loop has empty body [-Wempty-body]
warning:     for (pos = 0; !y[pos]; pos++);
warning:                                  ^
warning: src/daala/src/laplace_encoder.c:66:34: note: put the semicolon on a separate line to silence this warning
warning: 1 warning generated.
warning: foreign function is never used: `daala_encode_create`
  --> src/main.rs:49:5
   |
49 |     fn daala_encode_create(info: *mut daala_info) -> daala_enc_ctx;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: #[warn(dead_code)] on by default

warning: type `daala_enc_ctx` should have a camel case name such as `DaalaEncCtx`
 --> src/main.rs:3:1
  |
3 | pub enum daala_enc_ctx {}
  | ^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: #[warn(non_camel_case_types)] on by default

    Finished dev [unoptimized + debuginfo] target(s) in 6.30 secs

ワーニングが出ますね。未使用ですからね。ビルドできたようです。6秒ちょっとかかりました。

main.rsのみどころ: お膳立て

main.rsでどのように使うのかをさらっと見ていきます。

extern crate libc;

libcクレートが必要です。クレートってめっちゃRustやってますな響きですね!

#[repr(C)]
#[derive(Default, Debug)]
pub struct daala_plane_info {
  pub xdec: u8,
  pub ydec: u8,
}

これはdaalaの中で定義されている構造体をRust側に引っ張り出してきたものです。daalaの本来のコードは

struct daala_plane_info {
  unsigned char xdec;
  unsigned char ydec;
};

ですね。まずunsigned charなのでpub ...: u8に適当に置き換えました。多分あってます。

#[repr(C)]はCの構造体とメモリ構造一緒にしてくれるおまじないです。まじないましょう。

#[derive(Default, Debug)]はDefaultとDebugのトレイトを追加します。Haskellでいう型クラスみたいなもんですね。厳密には違うと思うのですがここらの違いを語り出すとキモい言語オタクみたいになりますね。

Defaultは中身を適当に埋めてくれるトレイトです。空っぽのこいつを作るために毎回フィールド設定するのは面倒です。

Debugはプリティプリンタ用です。println!で{:?}フォーマットを指定すると、フィールドの内容を再帰的に羅列してくれます。再帰的にと言うのは、

#[repr(C)]
#[derive(Default, Debug)]
pub struct daala_info {
  pub version_major: u8,
  pub version_minor: u8,
  pub version_sub: u8,
  /** pic_width,_height form a region of interest to encode */
  pub pic_width: libc::int32_t,
  pub pic_height: libc::int32_t,
  pub pixel_aspect_numerator: libc::uint32_t,
  pub pixel_aspect_denominator: libc::uint32_t,
  pub timebase_numerator: libc::uint32_t,
  pub timebase_denominator: libc::uint32_t,
  pub frame_duration: libc::uint32_t,
  /**The amount to shift to extract the last keyframe number from the granule
   *  position. */
  pub keyframe_granule_shift: i32,
  /** bitdepth_mode is one of the three OD_BITDEPTH_MODE_X choices allowed
   * above. */
  pub bitdepth_mode: i32,
  /**FPR must be on for high-depth, including lossless high-depth.
     When FPR is on for 8-bit or 10-bit content, lossless frames are still
      stored in reference buffers (and input buffers) with 8 + OD_COEFF_SHIFT
      bit depth to allow streams with mixed lossy and lossless frames. Having a
      mix of reference buffers stored in 10-bit and 12-bit precisions would be
      a disaster, so we keep them all at 12-bit internally.
   */
  pub full_precision_references: i32,
  pub nplanes: i32,
  pub plane_info: [daala_plane_info; 4],
   /** key frame rate defined how often a key frame is emitted by encoder in
    * number of frames. So 10 means every 10th frame is a keyframe.  */
  pub keyframe_rate: i32,
}

daala_infoのコピペですね。これもcodec.hにあります。この中にdaala_plane_infoがあります。固定長配列4つ分ですね。トレイトは再帰的に使ってくれるのでDefaultとDebugを両方の構造体宣言につけてあります。

この2つの構造体を使いましょう。

main.rsのみどころ: 呼び出し

#[link(name = "daalaenc", kind="static")]
extern {
    fn daala_encode_create(info: *mut daala_info) -> daala_enc_ctx;
    fn daala_info_init(info: *mut daala_info);
}

fn main() {
    println!("Hello, world!");
    let mut di = Box::new(Default::default());
    unsafe { daala_info_init(&mut *di); };
    println!("{:?}", di);
}

#[link(name = "daalaenc", kind="static")]で、build.rsで生成したlibdaalaenc.aを使えるようにします。スタティックライブラリなのでkind="static"を指定します。libdaalaenc.aなのでlibと.aを除いた名前を指定します。libfoo.aならfooです。libと.aがついていない場合は知りません。変な名前はやめましょう。

externの中にlibdaalaenc.aの中の使いたい関数を書きましょう。とりあえず2つ。今回本当に使うのはdaala_info_initだけです。

main関数の中を見ましょう

Hello, worldは無視しましょう。そのあと

    let mut di = Box::new(Default::default());

として、daala_infoの受け皿を作りましょう。

    unsafe { daala_info_init(&mut *di); };

daala_info_initにさっきのdiのポインタを渡しましょう。当然unsafeで囲んで&mut で渡しましょう。&mut って最高にロックですね。Rustらしくなさ無限大です。

    println!("{:?}", di);

最後に結果を見ましょう。Debugトレイトが表示する中身を全部羅列してくれるので我々は{:?}でprintlnするだけです。

結果を見ましょう

$ cargo run
   Compiling daara v0.1.0 (file://munyamunyamunya/codes/daara)

...中略...

    Finished dev [unoptimized + debuginfo] target(s) in 7.4 secs
     Running `target/debug/daara`
Hello, world!
daala_info { version_major: 0, version_minor: 0, version_sub: 0, pic_width: 0, pic_height: 0, pixel_aspect_numerator: 0, pixel_aspect_denominator: 0, timebase_numerator: 0, timebase_denominator: 0, frame_duration: 0, keyframe_granule_shift: 31, bitdepth_mode: 1, full_precision_references: 0, nplanes: 0, plane_info: [daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }], keyframe_rate: 0 }

というわけで、中身は出てますね。daala_info_initでちゃんとセットされてるのか確認しましょう。daala_info_initはこんな感じのことをします。

// ...
internal.h:# define OD_VERSION_MAJOR (0)
internal.h:# define OD_VERSION_MINOR (0)
internal.h:# define OD_VERSION_SUB   (0)
// ...
#define OD_BITDEPTH_MODE_8 (1)
// ...

void daala_info_init(daala_info *_info) {
  OD_CLEAR(_info, 1);
  _info->version_major = OD_VERSION_MAJOR;
  _info->version_minor = OD_VERSION_MINOR;
  _info->version_sub = OD_VERSION_SUB;
  _info->keyframe_granule_shift = 31;
  _info->bitdepth_mode = OD_BITDEPTH_MODE_8;
  _info->full_precision_references = 0;
  /*TODO: Set other defaults.*/
}

て感じです。OD_CLEARはメモリクリアですね。というわけでkeyframe_granule_shift = 31とbitdepth_mode = 1ぐらいしかセットされませんね。あとは他全部がゼロクリアされていることを確認しましょう。

Hello, world!
daala_info { version_major: 0, version_minor: 0, version_sub: 0, pic_width: 0, pic_height: 0, pixel_aspect_numerator: 0, pixel_aspect_denominator: 0, timebase_numerator: 0, timebase_denominator: 0, frame_duration: 0, keyframe_granule_shift: 31, bitdepth_mode: 1, full_precision_references: 0, nplanes: 0, plane_info: [daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }, daala_plane_info { xdec: 0, ydec: 0 }], keyframe_rate: 0 }

ちゃんとなってますね。全部ゼロ、ただしkeygrame_granule_shiftとbitdepth_modeはセットされています。できた!

まとめ

今日はRustからCのライブラリを呼び出して構造体を初期化できました。これができればあとは物理で殴るだけですね!(意訳:インプリヘヴィですね!)

参考文献

Swift製のオセロエンジンを公開しました

Swift製オセロエンジンです

苦労に苦労を重ねて書いたおかげでとても遅くて弱いエンジンです。まだまだ若輩者です。

これは何か

オセロAIとオセロ環境、あとオセロエンジンと喋るプロトコルとそれをコマンドラインで動かすためのアプリケーションです。

github.com

github.com

GraphiteとGraphene

オセロエンジンはGrapheneといいます。中にはオセロをやるための盤面クラス、ゲーム進行に必要なクラス、高速な盤面処理のためのビットボード、オセロエンジンのための探索、静的評価当一式が入っていてSwiftで書かれたフレームワークの形をしています。使うためには普通にフレームワークとして取り込んでもらえば大丈夫です。

その使用例がGraphiteです。コマンドラインでEdaxっぽく喋ります。

Grapheneは炭素の薄い膜みたいなやつです。それを集めてGraphiteとなるので部品であるAIはGraphene、遊べるアプリケーションはGraphiteという名前にしました。

何が含まれているか

  • Swiftで書かれたオセロAI
  • 普通のAlpha-beta search
  • そこそこ高速なbit board(まだまだ高速化は足りませんがまあまあぐらいのものが入っています)
  • 前時代的な静的評価関数(それでも作者より強いです。作者がオセロ激弱なだけですが)
  • オセロエンジンのための普通の構造(マシなコード構成になっていると思います)
  • 一部高速化のためにCでintrinsicsを呼び出しています(Cのコード呼び出す良いサンプルになっているかと思います)

何が含まれていないか

  • 強いAIエンジン
    • 本物のEdaxに100先100敗のボロ負けするほど弱いです。
  • 高速なAIエンジン
    • Shallow searchもTransposition tableも何も入っていません。Move orderingすらも入っていないので遅いし深くまで読めませんです。
  • 素晴らしい評価関数
    • それでも評価関数の係数はAWSで金かけて最適化しました。

さらに

そのGrapheneを使ったiPhone用のオセロアプリがFlatReversiです。

github.com

まとめ

そんなわけで最近水面下でやっていたことを少しまとめてみました。 Swiftでなんかやりたい人の参考になれば幸いです。

超かんたんなSwift on Linux用のMakefile

あると便利

gist.github.com

こんなもので良ければどうぞ。

使い方

$ make
# make buildに同等。ビルドします。
# コンパイルオプションで分けたりしてもいいと思います。
# make buildでデバッグ版、make build_releaseでリリース版とか。

$ make update
# swift package updateに同等。単にタイプ数少なくてすむ

$ make run
# パスは適宜替えてください。

自明ですがどうぞ。

まとめ

風邪を引きました。みなさんもどうぞご自愛ください。