何がしたいの?
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のライブラリを呼び出して構造体を初期化できました。これができればあとは物理で殴るだけですね!(意訳:インプリヘヴィですね!)
参考文献
rustでCで書いた関数を呼ぶ / Cからrustで書いた関数を呼ぶ - 睡分不足
- この中で説明されていることに大いにお世話になりました。
-
- 公式ドキュメントのFFIの章です。穴が開くほど読みました。
FFI in Rust - writing bindings for libcpuid | siciarz.net
- ここにライブラリ中の構造体の使い方が説明されております。
Rust's std:: library · A Guide to Porting C and C++ code to Rust
- RustとCの対応がわかりやすく載っています。データ型とか。
-
- Defaultトレイトの詳しい説明です。とっても詳しいです。
-
- Debugトレイトの詳しい説明です。とっても詳しいです。