今回は前回製作したcvcaptureモジュールを改良し、リソースを使った継続的なキャプチャを実装します。
追加する機能は、CvCapture構造体を保持するリソース“cvcapture”を定義し、カメラからcvcaptureリソースを作成する関数cv_create_camera_capture()、ファイルからcvcaprureリソースを作成する関数cv_create_file_capture()、フレームを取得し、静止画として保存する関数cv_save_capture()です。
specファイルを記述する
前回のspecファイルをベースに、リソース定義と新しい関数の定義を追加します。
リソースを定義する
まずはリソースです。リソースは<resources>タグ内の<resource>タグで記述します。<resoure>タグではname属性でリソース名、payload属性でリソースに保持する型を、allocで保持する値(payload属性で指定した型のポインタ)がZend APIのメモリを確保する関数emalloc()で確保された領域のアドレスかどうかを指定します。
また、<resource>タグ内の<destruct>タグでリソースに保持されている値を破棄する処理を記述します。ここで使っている“resoure”はCodeGen_PECLによって自動で定義される変数で、型はpayload属性で指定した型のポインタ(この場合は“CvCapture *”)です。
関数を定義する
次に、関数を定義します。今回作成する関数は、前回作成した関数をリソースを作成する関数と静止画を保存する関数に分割したものとなります。
リソースを作成する関数cv_create_camera_capture()とcv_create_file_capture()は、それぞれカメラを指定する数値、動画ファイルのパスを引数とします。リソースを返す関数は戻り値の型を“resource リソースの種類”と指定します。今回はcvcaptureリソースを返すので、“resource cvcapture”です。テストでは戻り値がcvcaptureリソースかどうかを調べています。
cv_save_capture()は作成したcvcaptureリソースを第1引数とし、第2引数で保存先のパス、第3引数が指定されていればキャプチャした画像の高さのペアの配列を代入します。戻り値はキャプチャに成功すればtrue、失敗した場合はfalseです。
ソースコードを生成する
ここまでできたら、前回同様にpecl-genコマンドでモジュールのソースコードを生成します。
ソースコード解説
生成されたソースコードのうち、リソース定義に関わる箇所の解説をします。
php_cvcapture.hではリスト4のようなリソースを開放するマクロが定義されています。Z_LVAL_Pは変数の整数としての値(long型)にアクセスするマクロです。リソースの実体はハッシュテーブルEG(regular_list)に登録されており、変数にはそのキーが整数として格納されているのです。
CodeGen_PECLが生成するコードではZ_LVAL_Pを使っていますが、php/Zend/zend_operators.hではリソースIDにアクセスするマクロZ_RESVAL_Pも定義されています。今のところ両者は同じもので、実装方法からして仕様が変わる可能性も極めて低いと考えられますが、それでもZ_RESVAL_Pを使った方が無難だと思います。もっとも、リソースは通常の変数と同様に、unset()やリソースが作成されたスコープを抜けて不要になった時点でも破棄されるので、cvcaptureモジュールではリソースを破棄する関数を用意しておらず、このマクロも使っていません。
次にcvcapture.cの最初のあたりを見てください。リスト5のようなコードが生成されています。
cvcapture_dtor()はリソースを格納した変数が不要になったときに呼ばれるコールバック関数です。<resource>タグのalloc属性がyesの場合は、do~whileブロックの後にefree()で開放するコードも追加されます。
le_cvcaptureはリソースの種類を識別するのに使われるグローバル変数で、モジュール初期化関数内で初期化されます。このようにPHPではいくつかの重要な値をグローバル変数として持っているため、CLIを除くZTS(マルチスレッド)モードのSAPIではdl()関数が使えないのです。
関数を実装する
ここからはcvcapture.cに関数を実装していきます。前回作成した関数に変更はありませんので、そのままコピー&ペーストしてください。
リソースを返す関数
まずはcv_create_camera_capture()から実装していきましょう。リスト7はpecl-gen直後の状態です。
戻り値をリソースにするコードは生成されているので、あとは未実装エラーを出力している箇所をcv_camera_capture()と同じキャプチャを作成する処理で置き換えれば実装完了です。cv_camera_capture()の該当箇所との違いは変数名が“capture”から自動生成された変数名“return_res”になっている点だけです。
cv_create_file_capture()の実装も、自動生成されたコードの未実装エラーを出力している箇所をcv_file_capture()のキャプチャを作成する処理で置き換えるだけなので、省略します。
リソースを受け取る関数
次に、cv_save_capture()を実装します。リスト9はpecl-gen直後の状態です。
引数をリソースをして取得する場合、指示子は“r”で、対応する型は“zval *”です。このままではキャプチャ構造体にアクセスできないので、続いてマクロZEND_FETCH_RESOURCE()でリソースの実体を取得し、“CvCapture *”型にキャストしています。
もし引数がリソースでない場合はzend_parse_parameter()でエラー、リソースだけどタイプがle_cvcaptureでない場合はZEND_FETCH_RESOURCE()でエラーとなり、falseを返します。capture_idの値を-1以外にすると、引数の代わりにcapture_idと同じIDのリソースを取得できますが、ここでは-1のままにしておいてください。文字列の"cvcapture"は、エラーメッセージに表示されるリソースタイプ名です。
この関数の実装もcv_camera_capture()関数から流用できます。先頭に変数の宣言(リスト10)を追加し、引数のパースの後にリスト11の内容を加えれば完了です。
インストール
実装が終わったら、お決まりの手順でインストールします。テストも忘れずにしておきましょう。
実際に使ってみる
リスト12は前回のサンプルをリソースを使うように書き直したものです。
今回の目的の一つは、リソースを使って毎回カメラに接続するオーバーヘッドを節約することでしたので、サンプルに少し手を加えてキャプチャにかかる時間を計ってみました。
表1 リソースを使わないときと使ったときの比較
| リソースを使わないとき | リソースを使ったとき |
初期化 | - | 0.312秒 |
1回目 | 1.085秒 | 0.548秒 |
2回目 | 0.949秒 | 0.040秒 |
3回目 | 0.564秒 | 0.039秒 |
4回目 | 0.565秒 | 0.038秒 |
5回目 | 0.565秒 | 0.039秒 |
6回目 | 0.565秒 | 0.039秒 |
7回目 | 0.564秒 | 0.038秒 |
8回目 | 0.568秒 | 0.038秒 |
9回目 | 0.570秒 | 0.037秒 |
10回目 | 0.565秒 | 0.041秒 |
合計 | 6.566秒 | 1.214秒 |
どうやら期待通りの結果が得られたようです。このようにリソースを使えばPHPの組み込み型でない構造体を使い回すことができます。
おわりに
今回はリソースとリソースを扱う関数を定義しました。しかし、今どきのPHPプログラミングでリソースをそのまま使うのは少々時代遅れと言えます。そこで、次回はこれをラップして、リソースをプロパティとして持つクラスを定義し、拡張モジュールでオブジェクト指向のAPIを実装する方法を紹介します。
サンプルファイルのダウンロード
付録は後日掲載