Contents
GStreamerで動画テクスチャ
ここではGStreamerというメディア処理ライブラリを用いてOpenGLテクスチャに動画を貼り付ける方法を説明します。
動画のテクスチャは、一定時間毎にテクスチャメモリに格納してある画像を書き換え、もしくはテクスチャを破棄・新規作成することで実現できます。具体的にはGUIウィンドウマネージャのタイマーやアイドルなどのコールバック関数を利用してOpenGLのテクスチャ関数を呼び出せばOKです。OpenGLに関連する部分は基本的にこれだけなのですが、現実的な問題としてプログラム中でどうやって動画データを扱うのか、という部分が意外とやっかいです。glSubTexImage*D()でデータとして与えることができるのは生のピクセル配列です。ということは、例えば動画をファイルから読み込みたいという場合はほぼ必ずデコーダが必要になりますし、カメラから動画を取得する場合はカメラに触る方法が必要になるということです。
こういうときに便利なソフトウェアライブラリが、今回使うGStreamerのようなメディア処理ライブラリです。GStreamerはオープンソースのメディア処理ライブラリで、WindowsならDirectShow、Mac OSならQuickTimeに相当、もしくはそれ以上の機能を提供します。これを使うことで動画や音声などのマルチメディアデータを比較的容易に扱うことができるようになります。
GStreamerはクロスプラットフォーム対応しており、LinuxはもちろんWindowsやMac OS Xでも動作するようです。公式サイトから入手できますし、Linuxはパッケージとして入手できます。
http://www.gstreamer.net/
GStreamerの基本
メディア処理ではよく見られるようにGStreamerも、何らかの処理を表現するエレメントと、それらをつなぐSrc/Sinkパッドによるパイプラインモデルでメディア処理を表現します。例えば「USBカメラから取得したYUV表色系のビデオをRGB表色系に変換してからディスプレイに表示」という一連の処理は、GStreamerでは次の3つのエレメントからなるパイプラインで実現できます。
v4l2src ! ffmpegcolorspace ! ximagesink
それぞれ、v4l2srcはUSBカメラからデータを取得するエレメント、ffmpegcolorspaceはYUVをRGBに変換するエレメント、ximagesinkはXサーバにビデオを表示するエレメントです。それぞれのエレメントは接続可能なSrc(データ出力)またはSink(データ入力)パッドを持っており、パッドをつなぐことでエレメントがつながれ、パイプラインとなります。上の記述ではエレメント間をつなぐ箇所を!で示しています。
gst-launch
記述したパイプラインはGStreamer付属のgst-launchというユーティリティですぐに動作確認をすることができます。USBカメラのついているPCなら、次のコマンドを使うと画面にカメラから取得した動画が表示されると思います。
$ gst-launch v4l2src ! ffmpegcolorspace ! ximagesink
次のコマンドはテスト用動画を何らかのウィンドウに出力するというパイプラインです。
$ gst-launch videotestsrc ! autovideosink
gst-inspect
どのようなエレメントがインストール済みなのかはgst-inspectというユーティリティで確認できます。データ入出力やエンコーダ・デコーダをはじめ、非常に多くのエレメントが利用可能です。
$ gst-inspect
また、あるエレメントがどのような処理をしてどのようなパッドを接続可能なのか、どういった設定項目を持っているのかといった説明もgst-inspectから調べることができます。この場合はgst-inspectの引数にエレメント名もしくはエレメントのグループ名を入力します。
$ gst-inspect v4l2src // v4l2srcエレメントについて詳細表示 $ gst-inspect ffmpeg // ffmpegエンコーダ・デコーダグループの全エレメントについて表示
GStreamerアプリケーションの開発
GStreamerそのものはライブラリであり、広範なAPIを提供しています。まずはGStreamerを使った簡単なアプリケーションの構造を見ましょう。ここでは次のメディア処理パイプラインを動かすアプリケーションを作ります。
videotestsrc ! fakesink
これはテスト用動画出力エレメントvideotestsrcを何もしないfakesinkエレメントの入力につなぐパイプラインです。何も表示されませんが、内部では動画データが流れ続けます。これをC言語を使って簡単に記述したものが次のコードです。
1 #include <gst/gst.h>
2 #include <glib.h>
3
4 /* callback prototype */
5 static gboolean
6 bus_handler (GstBus *bus,
7 GstMessage *msg,
8 gpointer data);
9
10 /* main */
11 int
12 main (int argc,
13 char *argv[])
14 {
15 GMainLoop *loop;
16 GstElement *pipeline, *source, *sink;
17 GstBus *bus;
18
19 /** (1) Initialisation **/
20 gst_init (&argc, &argv);
21
22 /** (2) Objects instantiation **/
23 loop = g_main_loop_new (NULL, FALSE);
24
25 /* Create gstreamer elements */
26 pipeline = gst_pipeline_new ("testpipeline");
27 source = gst_element_factory_make ("videotestsrc", "videosrc");
28 sink = gst_element_factory_make ("fakesink", "fakesink");
29 if (!pipeline || !source || !sink) {
30 g_printerr ("One element could not be created. Exiting.\n");
31 return -1;
32 }
33
34 /** (3) Set up the pipeline **/
35 /* we set the sync property of fakesink to TRUE */
36 g_object_set (G_OBJECT (sink), "sync", TRUE, NULL);
37
38 /* we add a message handler */
39 bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
40 gst_bus_add_watch (bus, bus_handler, loop);
41 gst_object_unref (bus);
42
43 /* we add all elements into the pipeline */
44 gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);
45
46 /* we link the elements together */
47 gst_element_link (source, sink);
48
49 /** (4) Main loop **/
50 /* Set the pipeline to "playing" state */
51 gst_element_set_state (pipeline, GST_STATE_PLAYING);
52
53 g_main_loop_run (loop);
54
55 /* Out of the main loop, clean up nicely */
56 gst_element_set_state (pipeline, GST_STATE_NULL);
57 gst_object_unref (GST_OBJECT (pipeline));
58
59 return 0;
60 }
61
62 /* callback implementation */
63 static gboolean
64 bus_handler (GstBus *bus,
65 GstMessage *msg,
66 gpointer data)
67 {
68 GMainLoop *loop = (GMainLoop *) data;
69
70 switch (GST_MESSAGE_TYPE (msg)) {
71 case GST_MESSAGE_ERROR: {
72 gchar *debug;
73 GError *error;
74
75 gst_message_parse_error (msg, &error, &debug);
76 g_free (debug);
77 g_printerr ("Error: %s\n", error->message);
78 g_error_free (error);
79
80 g_main_loop_quit (loop);
81 break;
82 }
83 default:
84 break;
85 }
86
87 return TRUE;
88 }
(1) GStreamer実行環境の初期化
GStreamerをプログラム中で使うには、ヘッダファイル<gst/gst.h>をインクルードし、最初にgst_init()関数を呼び出す必要があります。
1 #include <gst/gst.h>
1 void gst_init (int *argc, char **argv);
gst_init()関数を呼び出すことでGStreamerが内部的に使う実行環境が初期化され、以降その他のGStreamerの関数を呼び出すことができるようになります。
なお、上記のサンプルではこのほかに<glib.h>というファイルをインクルードしています。これはプログラムのメインループをGLibというライブラリのGMainLoopオブジェクトで実装しているためです。GLibというのはC言語の標準ライブラリを拡張する目的の汎用のライブラリで、各種データ型やアルゴリズムが定義されています。
1 #include <glib.h>
1 GMainLoop *loop;
2 loop = g_main_loop_new (NULL, FALSE);
3 g_main_loop_run (loop);
(2) GStreamerオブジェクトの作成
実行環境を初期化したら、GStreamerパイプラインオブジェクトを作ります。最初にGstElementオブジェクトのポインタを宣言し、それぞれインスタンス化します。
1 GstElement *pipeline, *source, *sink;
1 /* Create gstreamer elements */
2 pipeline = gst_pipeline_new ("testpipeline");
3 source = gst_element_factory_make ("videotestsrc", "videosrc");
4 sink = gst_element_factory_make ("fakesink", "fakesink");
5 if (!pipeline || !source || !sink) {
6 g_printerr ("One element could not be created. Exiting.\n");
7 return -1;
8 }
パイプラインの作成
メディア処理パイプライン本体はGStreamerのエレメントを内包するGstElement型のオブジェクトとして作成します。作成するにはgst_pipeline_new()関数を呼び出します。
1 GstElement* gst_pipeline_new (const gchar *name);
引数はパイプラインに付ける名前です。例では"testpipeline"という名前を付けています。名前は他のオブジェクトと同一でなければ好きなものを付けて構いません。
エレメントの作成
各エレメントはインストールされて既に定義済みのエレメントを作成するgst_element_factory_make()関数を使って作成します。
1 GstElement* gst_element_factory_make(const gchar *factoryname,
2 const gchar *name);
第一引数は作りたいGStreamerエレメントの名前を、第二引数には作ったオブジェクトに付けたい名前を渡します。例ではvideotestsrcエレメントとfakesinkエレメントを作成し、それぞれ"videosrc"と"fakesink"という名前を付けています。
最後に、オブジェクトの作成に失敗した場合プログラムを終了するようにエラー処理をしています。
(3) パイプラインの組立てと設定
オブジェクトを作成したら、それぞれのオブジェクトを使ってメディア処理パイプラインを組み立てます。
エレメントのプロパティを設定
最初に作成したオブジェクト固有のプロパティを設定します。プロパティというのはメディア処理エレメントがどういう動作をするか設定するための項目のようなもので、例えばファイルからデータを読み出してデータを出力する"file-src"エレメントは"location"というファイルの場所を指定するためのプロパティを持っていて、ここで設定されたファイルを開いて処理を開始します。例ではfakesinkエレメントをフレームレートに同期して動作させるかどうかを設定する"sync"プロパティをTRUEに設定することとします。
1 /* we set the sync property of fakesink to TRUE */
2 g_object_set (G_OBJECT (sink), "sync", TRUE, NULL);
プロパティを設定する際に使うのはg_object_set()という関数です。
1 void g_object_set(gpointer object,
2 const gchar *first_property_name,
3 ...);
第一引数がプロパティを設定したいオブジェクトです。例ではG_OBJECT()というマクロを使い、GObject型にキャストしてからsinkオブジェクトを渡しています。GstElementクラスはGObjectクラスという汎用オブジェクト型を継承して定義されているため、プロパティというのもGObjectクラスの機能です。そのためここではGObjectクラスの関数を使ってプロパティを設定しています。ただしC言語なのでGObjectとして型キャストをしてから関数にオブジェクトを渡す必要があります。
第二引数以降はプロパティ名と値のペアを入力します。引数の数はペアの数に応じて可変です。例では"sync"プロパティを設定するだけなので、"sync"とTRUEのペアを続けて引数として渡します。設定するプロパティのペアがもうなければ、最後にNULLを引数として渡します。
なお、設定されているプロパティを取得するにはg_object_get()関数を使うことができます。
メッセージバスの設定
パイプラインに何らかのイベントが発生した場合に実行するコールバックを登録します。
1 GstBus *bus;
1 /* we add a message handler */
2 bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
3 gst_bus_add_watch (bus, bus_handler, loop);
4 gst_object_unref (bus);
メッセージバスというのは、パイプラインなどの各エレメントに何らかのイベントが発生した場合にメッセージが通知される場所です。例えばメディアの再生が終わったりエラーが発生した場合にバスにメッセージを通知することで、アプリケーション側で適切な処理をすることができるようになります。
1 GstBus* gst_pipeline_get_bus(GstPipeline *pipeline);
パイプラインオブジェクトが所有するバスを取得します。引数はパイプラインオブジェクトです。このようにして取得したバスは参照回数が追加されるので、使わなくなったらgst_object_unref()で参照回数を減らしておきます。参照回数についての詳細はまた後ほど説明します。
1 guint gst_bus_add_watch(GstBus *bus,
2 GstBusFunc func,
3 gpointer user_data);
バスにコールバック関数を登録します。第一引数はコールバックを登録するバス、第二引数はコールバック関数、第三引数はコールバック関数に渡すデータのポインタです。コールバック関数を登録することで、メッセージが通知されたときに処理ができるようになります。
例ではbus_handlerという関数を登録し、メインループのポインタを渡しています。bus_handler関数ではエラーメッセージが通知されたときにエラーを表示してメインループを終了するようにしています。
1 static gboolean
2 bus_handler (GstBus *bus,
3 GstMessage *msg,
4 gpointer data)
5 {
6 GMainLoop *loop = (GMainLoop *) data;
7
8 switch (GST_MESSAGE_TYPE (msg)) {
9 case GST_MESSAGE_ERROR: {
10 gchar *debug;
11 GError *error;
12 gst_message_parse_error (msg, &error, &debug);
13 g_free (debug);
14 g_printerr ("Error: %s\n", error->message);
15 g_error_free (error);
16 g_main_loop_quit (loop);
17 break;
18 }
19 default:
20 break;
21 }
22
23 return TRUE;
24 }
エレメントをパイプラインに格納
作成したパイプラインにvideotestsrcとfakesinkを登録します。登録されたエレメントはパイプラインでまとめて再生停止などを操作することができるようになります。
1 /* we add all elements into the pipeline */
2 gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);
1 void gst_bin_add_many(GstBin *bin,
2 GstElement *element_1,
3 ...);
GstBinオブジェクトに複数のエレメントを登録します。第一引数はエレメントを登録するGstBinオブジェクトです。例ではpipelineをGST_BIN()マクロでGstBinオブジェクトに型キャストして引き渡しています。第二引数以降は登録するエレメントを続けて入力し、最後にNULLポインタを渡します。
エレメントを接続
videotestsrcとfakesinkエレメントの入出力パッドをつなぎます。これでエレメントの間にデータを流すことができるようになります。
1 /* we link the elements together */
2 gst_element_link (source, sink);
1 gboolean gst_element_link(GstElement *src,
2 GstElement *dest);
二つのGstElementを接続します。第一引数がデータを出力するエレメント、第二引数がデータを受け取るエレメントです。三つ以上つなぐ場合は代わりにgst_element_link_many()関数を使うこともできます。
(4) メインループ
メディア処理パイプラインが準備できたら、メディア処理を開始します。パイプラインはアプリケーションプログラム本体とは独立したスレッドで動作します。今回の例ではその他に何もしないので、アプリケーション何もしないループに入ります。そしてプログラムを終了するときは、作成したパイプラインを破棄します。
1 /** (4) Main loop **/
2 /* Set the pipeline to "playing" state */
3 gst_element_set_state (pipeline, GST_STATE_PLAYING);
4
5 g_main_loop_run (loop);
6
7 /* Out of the main loop, clean up nicely */
8 gst_element_set_state (pipeline, GST_STATE_NULL);
9 gst_object_unref (GST_OBJECT (pipeline));
なお、今回の例ではメインループを抜け出す方法を用意していません。なので、g_main_loop_run()以降の処理は実際には実行されません。例ではアプリケーションから何らかの方法でメインループを抜け出したとしたときの場合に実行すべき処理を記述しています。今回の例のプログラムを終了するにはCtrl-Cを押してください。
メディア処理を開始・終了
パイプラインの動作状態を変更し、メディア処理を開始したり終了したりします。
1 GstStateChangeReturn gst_element_set_state(GstElement *element,
2 GstState state);
GstElementの動作状態を変更します。動作状態は次の4つが存在します。初期状態はGST_STATE_NULLです。
GST_STATE_NULL |
メモリも何も確保されていない状態 |
|
GST_STATE_READY |
メモリが確保されているが、データはまだ存在しない状態 |
|
GST_STATE_PAUSED |
データが到着しているが、パイプラインが停止している状態 |
|
GST_STATE_PLAYING |
パイプラインにデータが流れ、処理を実行している状態 |
パイプラインの破棄
GStreamerのAPIで作成されたオブジェクトを破棄するにはgst_object_unref()を呼び出します。
1 void gst_object_unref(gpointer object);
GstObjectクラスのオブジェクトの参照回数を減らします。GstObjectクラスはGStreamerの各種オブジェクトのスーパークラスです。GstObjectではオブジェクトの管理に参照回数を用いています。参照回数というのはそのオブジェクトを使っている数で、最初に作成したときは1になっています。gst_object_unref()を使って参照回数を0にすると、メモリ上に作成されたオブジェクトは自動的に解放されます。参照回数が0にならない限り、オブジェクトはメモリ上に確保され続けます。これは複数の対象がオブジェクトを共有するときに便利で、例えば参照回数を適切に設定しておけば、片方の対象がもう使わないからといってオブジェクトを解放してしまったときに使用中のもう片方の対象がそのオブジェクトを使えなくなってしまうということを防ぐことができます。参照回数を増やすにはgst_object_ref()を使います。
なお、同じようにスーパークラスであるGObjectクラスもこの参照回数を利用してオブジェクトの管理をします。
パイプラインのように他のオブジェクトを内包するオブジェクトを破棄した場合、一緒に内包するオブジェクトも破棄されます。一回一回全てのオブジェクトを破棄する必要はありません。
コンパイル・実行方法
環境によってコンパイル方法が異なりますが、Linuxでgccを使う場合は次のようにpkg-configを使うことができます。
$ gcc `pkg-config --cflags --libs gstreamer-0.10` -o example example.c
実行するには次のようにします。
$ ./example
GStreamerとGtkGLExtで動画テクスチャ
GStreamerを使えば比較的簡単に動画データを扱えます。テクスチャに動画データを貼り付けるのも、GStreamerパイプラインから動画データを取りだしてOpenGLに渡すだけで実現することができます。
ここではGStreamerと同じGObjectを使ったGUIウィジェットのGtk+と、それにOpenGLの描画機能を追加するGtkGLExtというライブラリを用いて動画テクスチャを表示するGUIアプリケーションを作りたいと思います。GtkGLExtについてはIntroductionToGtkGLExtを参照してください。
色空間の変換
今回、メディア処理パイプラインは次のように構成します。
videotestsrc ! ffmpegcolorspace ! capsfilter ! fakesink
videotestsrc |
テスト用の動画を生成するエレメント |
|
ffmpegcolorspace |
色空間を変換するエレメント |
|
capsfilter |
決まったデータ形式のみ通すフィルタの役割を果たすエレメント |
|
fakesink |
データを取得するだけのエレメント |
videotestsrcをUSBカメラからビデオを取得するv4l2srcやIEEE1394カメラから取得するdc1394srcに変更することもできます。
ビデオデータはYUV形式でピクセルを表現することが多いため、ここではffmpegcolorspaceエレメントとcapsfilterを使って色空間をRGB形式に変換する処理を挟みます。ffmpegcolorspaceは前後のエレメントが要求するデータ形式から自動的に色変換を行うので、capsfilterでRGB形式を指定しておけばOKです。capsfilterのデータ形式を指定するには、capsfilterのcapsプロパティにメディアデータのフォーマットを示すGstCaps型オブジェクトを指定します。以下はRGB形式、ピクセルあたりのビット幅24、利用するビット深さ24(= Rx8 + Gx8 + Bx8)を指定する場合のcapsfilterの使い方です。GstCapsオブジェクトを作成してcapsfilterに登録したのち、使用済みのオブジェクトを破棄します。
1 GstElement *filter;
2 GstCaps *caps;
3 ...
4 caps = gst_caps_new_simple ("video/x-raw-rgb",
5 "bpp", G_TYPE_INT, 24,
6 "depth", G_TYPE_INT, 24,
7 NULL);
8 g_object_set (G_OBJECT(filter), "caps", caps, NULL);
9 gst_caps_unref(caps);
バッファデータの取得と排他制御
ビデオテクスチャにするためのデータはfakesinkが持っているデータバッファにアクセスすることで取得できます。しかし、GStreamerパイプラインから生データを取り出してアプリケーションで利用する場合、スレッドに気をつける必要があります。基本的にGStreamerパイプラインは独自にスレッドを作成して動作します。したがって、アプリケーション本体とは非同期的に動作し、バッファ用のメモリも独立して管理しています。アプリケーション側のスレッドでパイプラインからデータを取り出して使う場合、データ取得時にGStreamerスレッドにメモリを解放されないようにきちんと排他制御をかけてデータを読みだす必要があります。
Read/Write排他制御
今回は排他制御を扱うのは、アプリケーション側でビデオデータをOpenGLのテクスチャメモリに転送している最中にGStreamer側のスレッドがこのデータを破棄してしまうことを防ぐためです。アプリケーション側は読み込みだけ、GStreamer側はデータを書き込むだけなので、Read/Write排他制御をかけます。
Read/Write排他制御はGStaticRWLockオブジェクトを使って簡単に実装できます。
初期化:
1 GStaticRWLock rwlock;
2 g_static_rw_lock_init (&rwlock);
書き込み保護:
1 g_static_rw_lock_writer_lock (&rwlock);
2 /* some process to write shared data */
3 g_static_rw_lock_writer_unlock (&rwlock);
読み込み保護:
1 g_static_rw_lock_reader_lock (&rwlock);
2 /* some process to read shared data */
3 g_static_rw_lock_reader_unlock (&rwlock);
handoffでのバッファ取得
fakesinkエレメントのバッファへのポインタを取得する方法はいくつかありますが、ここでは最も簡単なhandoffコールバックを利用します。handoffコールバックは、fakesinkが新しいフレームデータを受け取ったときに呼び出されるコールバックです。新しいデータが来た時点でアプリケーション側でそのバッファへのポインタを保持しておけば、違うスレッドからでも好きなタイミングでバッファにアクセスできます。
fakesinkエレメントのhandoffシグナルでコールバック関数を使うには、最初に"signal-handoffs"プロパティをTRUEに設定し、次にg_signal_connectでコールバック関数を登録します。
1 g_object_set (G_OBJECT (sink), "signal-handoffs", TRUE, NULL);
2 g_signal_connect (G_OBJECT (sink), "handoff",
3 G_CALLBACK(handoff_handler), app);
"handoff"コールバック関数は次の形式です。
1 void user_function(GstElement *fakesink,
2 GstBuffer *buffer,
3 GstPad *pad,
4 gpointer user_data);
第二引数の*bufferが新しく到着したデータです。コールバック関数中でアプリケーション側でこのバッファを参照するようにポインタを保持すればよいでしょう。アプリケーション側で読み出すバッファのポインタを書き換えるので書き込み排他制御を忘れずに。
以下ではグローバルに保持したいバッファへのポインタをpbufferとしたとき、"handoff"コールバック内で行う処理です。rwlockは排他制御です。
1 g_static_rw_lock_writer_lock (&rwlock);
2 /* Clean up previous reference */
3 if (pbuffer != NULL) {
4 gst_buffer_unref (pbuffer);
5 pbuffer = NULL;
6 }
7 /* Refer upcoming buffer */
8 pbuffer = gst_buffer_ref (buffer);
9 g_static_rw_lock_writer_unlock (&rwlock);
バッファからテクスチャへの読み出し
描画時、あるいはタイマーを設定して定期的に実行するようにします。
処理内容は、OpenGLのコンテキストが有効になっている場所でglTexSubImage2D()を呼び出し、バッファに含まれるデータへのポインタを渡すだけです。読み込み保護を忘れずに。
1 g_static_rw_lock_reader_lock (&rwlock);
2 glTexSubImage2D (GL_TEXTURE_2D, 0,
3 0, 0,
4 width, height,
5 GL_RGB, GL_UNSIGNED_BYTE,
6 GST_BUFFER_DATA(pbuffer));
7 g_static_rw_lock_reader_unlock (&rwlock);
ソースコード全体
1 /**
2 ** videotexture.c
3 **/
4 #include <gtk/gtk.h>
5 #include <gtk/gtkgl.h>
6 #include <gst/gst.h>
7 #include <GL/gl.h>
8 #include <GL/glu.h>
9
10 /** Globally used data **/
11 typedef struct _ApplicationData {
12 GMainLoop *loop;
13 GstBuffer *buffer;
14 GStaticRWLock rwlock;
15 int width;
16 int height;
17 unsigned int texture_id;
18 } ApplicationData;
19
20 /**
21 ** Function prototypes
22 **/
23
24 /** GStreamer related ****************************************/
25 static GstElement *
26 create_pipeline (ApplicationData *app);
27
28 static gboolean
29 bus_handler (GstBus *bus,
30 GstMessage *msg,
31 gpointer data);
32
33 static void
34 handoff_handler (GstElement *fakesink,
35 GstBuffer *buffer,
36 GstPad *pad,
37 gpointer data);
38
39 /** Gtk+ related **********************************************/
40 static GtkWidget *
41 create_window (ApplicationData *app);
42
43 static gboolean
44 delete_handler (GtkWidget *widget,
45 GdkEvent *event,
46 gpointer data);
47
48 static void
49 realize_handler (GtkWidget *widget,
50 gpointer data);
51
52 static gboolean
53 configure_handler (GtkWidget *widget,
54 GdkEventConfigure *event,
55 gpointer data);
56
57 static gboolean
58 expose_handler (GtkWidget *widget,
59 GdkEventExpose *event,
60 gpointer data);
61
62 static gboolean
63 idle_handler (gpointer data);
64
65 /**
66 ** Function implementation
67 **/
68
69 /** main() ***************************************************/
70 int
71 main (int argc,
72 char *argv[])
73 {
74 ApplicationData app;
75 GstElement *pipeline;
76 GtkWidget *window;
77
78 /* Initialize runtime environment */
79 gst_init (&argc, &argv);
80 gtk_init (&argc, &argv);
81 gtk_gl_init (&argc, &argv);
82
83 /* Initialize application data */
84 app.loop = g_main_loop_new (NULL, FALSE);
85 app.buffer = NULL;
86 g_static_rw_lock_init(&app.rwlock);
87 app.width = 320;
88 app.height = 240;
89 app.texture_id = 0;
90
91 /* Create media pipeline */
92 pipeline = create_pipeline (&app);
93
94 /* Create and show window */
95 window = create_window (&app);
96 if ( window == NULL ) {
97 g_printerr ("Failed to create window\n");
98 return -1;
99 }
100 gtk_widget_show_all (window);
101
102 /* Main loop */
103 g_main_loop_run (app.loop);
104
105 /* Release objects before quit */
106 gst_element_set_state (pipeline, GST_STATE_NULL);
107 gst_object_unref (pipeline);
108
109 gst_buffer_unref (app.buffer);
110 g_main_loop_unref (app.loop);
111
112 return 0;
113 }
114
115
116 /** GStreamer related ****************************************/
117 static GstElement *
118 create_pipeline (ApplicationData *app)
119 {
120 GstElement *pipeline, *source, *csp, *filter, *sink;
121 GstBus *bus;
122 GstPad *pad;
123 GstCaps *caps;
124 GstStructure *structure;
125
126 /* Create gstreamer elements */
127 pipeline = gst_pipeline_new ("pipeline");
128 source = gst_element_factory_make ("videotestsrc", "videosrc");
129 csp = gst_element_factory_make ("ffmpegcolorspace", "csp");
130 filter = gst_element_factory_make ("capsfilter", "filter");
131 sink = gst_element_factory_make ("fakesink", "fakesink");
132 if (!pipeline || !source || !csp || !filter || !sink) {
133 g_printerr ("One element could not be created. Exiting.\n");
134 return NULL;
135 }
136
137 /* we set a property of elements */
138 g_object_set (G_OBJECT (sink), "sync", TRUE, NULL);
139 g_object_set (G_OBJECT (sink), "signal-handoffs", TRUE, NULL);
140 g_signal_connect (G_OBJECT (sink), "handoff",
141 G_CALLBACK(handoff_handler), app);
142 caps = gst_caps_new_simple ("video/x-raw-rgb",
143 "bpp", G_TYPE_INT, 24,
144 "depth", G_TYPE_INT, 24,
145 NULL);
146 g_object_set (G_OBJECT(filter), "caps", caps, NULL);
147 gst_caps_unref(caps);
148
149 /* we add a message handler */
150 bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
151 gst_bus_add_watch (bus, bus_handler, app->loop);
152 gst_object_unref (bus);
153
154 /* we add all elements into the pipeline */
155 gst_bin_add_many (GST_BIN (pipeline), source, csp, filter, sink, NULL);
156
157 /* we link the elements together */
158 gst_element_link_many (source, csp, filter, sink, NULL);
159
160 /* Set the pipeline to "playing" state */
161 gst_element_set_state (pipeline, GST_STATE_PLAYING);
162 if (gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE)
163 != GST_STATE_CHANGE_SUCCESS)
164 {
165 return NULL;
166 }
167
168 /* Get width and height of video data */
169 pad = gst_element_get_static_pad (sink, "sink");
170 caps = GST_PAD_CAPS(pad);
171 structure = gst_caps_get_structure(caps, 0);
172 gst_structure_get_int (structure, "width", &app->width);
173 gst_structure_get_int (structure, "height", &app->height);
174 gst_object_unref (pad);
175
176 return pipeline;
177 }
178
179 static void
180 handoff_handler (GstElement *fakesink,
181 GstBuffer *buffer,
182 GstPad *pad,
183 gpointer data)
184 {
185 ApplicationData *app = (ApplicationData *)data;
186
187 g_static_rw_lock_writer_lock (&app->rwlock);
188 /* Clean up previous reference */
189 if (app->buffer != NULL) {
190 gst_buffer_unref (app->buffer);
191 app->buffer = NULL;
192 }
193 /* Refer upcoming buffer */
194 app->buffer = gst_buffer_ref (buffer);
195 g_static_rw_lock_writer_unlock (&app->rwlock);
196
197 return;
198 }
199
200 static gboolean
201 bus_handler (GstBus *bus,
202 GstMessage *msg,
203 gpointer data)
204 {
205 GMainLoop *loop = (GMainLoop *)data;
206
207 switch (GST_MESSAGE_TYPE (msg)) {
208 case GST_MESSAGE_ERROR: {
209 gchar *debug;
210 GError *error;
211
212 gst_message_parse_error (msg, &error, &debug);
213 g_free (debug);
214 g_printerr ("Error: %s\n", error->message);
215 g_error_free (error);
216
217 g_main_loop_quit (loop);
218 break;
219 }
220 default:
221 break;
222 }
223
224 return TRUE;
225 }
226
227 /** Gtk+ related **********************************************/
228 static GtkWidget *
229 create_window (ApplicationData *app)
230 {
231 GtkWidget *window;
232 GtkWidget *drawing_area;
233 GdkGLConfig *glconfig;
234
235 /** Create an OpenGL configuration **/
236 glconfig = gdk_gl_config_new_by_mode (GDK_GL_MODE_RGB |
237 GDK_GL_MODE_DEPTH |
238 GDK_GL_MODE_DOUBLE);
239 if ( glconfig == NULL ) {
240 g_printerr ("Failed to create rendering configuration\n");
241 return NULL;
242 }
243
244 /** Create a top-level window **/
245 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
246 gtk_window_set_title (GTK_WINDOW (window), "Video Texture");
247 g_signal_connect (G_OBJECT (window), "delete_event",
248 G_CALLBACK (delete_handler), app->loop);
249
250 /** Create a drawing area in which OpenGL renders something **/
251 drawing_area = gtk_drawing_area_new ();
252 gtk_widget_set_size_request (drawing_area, 320, 240);
253 /* Set OpenGL-capability to the widget. */
254 gtk_widget_set_gl_capability (drawing_area,
255 glconfig,
256 NULL,
257 TRUE,
258 GDK_GL_RGBA_TYPE);
259 g_signal_connect_after (G_OBJECT (drawing_area), "realize",
260 G_CALLBACK (realize_handler), app);
261 g_signal_connect (G_OBJECT (drawing_area), "configure_event",
262 G_CALLBACK (configure_handler), NULL);
263 g_signal_connect (G_OBJECT (drawing_area), "expose_event",
264 G_CALLBACK (expose_handler), app);
265 gtk_container_add (GTK_CONTAINER(window), drawing_area);
266
267 /** Register idle callback **/
268 g_idle_add (idle_handler, drawing_area);
269
270 return window;
271 }
272
273 static gboolean
274 delete_handler (GtkWidget *widget,
275 GdkEvent *event,
276 gpointer data)
277 {
278 GMainLoop *loop = (GMainLoop *)data;
279 g_main_loop_quit (loop);
280 return TRUE;
281 }
282
283 static void
284 realize_handler (GtkWidget *widget,
285 gpointer data)
286 {
287 GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
288 GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);
289 ApplicationData *app = (ApplicationData *)data;
290
291 /*** OpenGL BEGIN ***/
292 if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
293 return;
294
295 glClearColor (0.0, 0.0, 0.0, 0.0);
296
297 glGenTextures (1, &app->texture_id);
298 glBindTexture (GL_TEXTURE_2D, app->texture_id);
299 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
300 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
301 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
302 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
303 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
304 app->width, app->height, 0,
305 GL_RGB, GL_UNSIGNED_BYTE, GST_BUFFER_DATA(app->buffer) );
306 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
307
308 gdk_gl_drawable_gl_end (gldrawable);
309 /*** OpenGL END ***/
310 }
311
312 static gboolean
313 configure_handler (GtkWidget *widget,
314 GdkEventConfigure *event,
315 gpointer data)
316 {
317 float aspect;
318 GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
319 GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);
320
321 /*** OpenGL BEGIN ***/
322 if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
323 return FALSE;
324
325 glViewport (0, 0,
326 widget->allocation.width,
327 widget->allocation.height);
328 glMatrixMode (GL_PROJECTION);
329 glLoadIdentity();
330 aspect = (float)widget->allocation.width/(float)widget->allocation.height;
331 gluPerspective (60, aspect, 0.1, 2.0);
332
333 glMatrixMode (GL_MODELVIEW);
334 glLoadIdentity();
335 glTranslatef (0.0, 0.0, -1.0);
336
337 gdk_gl_drawable_gl_end (gldrawable);
338 /*** OpenGL END ***/
339
340 return TRUE;
341 }
342
343 static gboolean
344 expose_handler (GtkWidget *widget,
345 GdkEventExpose *event,
346 gpointer data)
347 {
348 GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
349 GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);
350 ApplicationData *app = (ApplicationData *)data;
351
352 /*** OpenGL BEGIN ***/
353 if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
354 return FALSE;
355
356 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
357
358 glBindTexture (GL_TEXTURE_2D, app->texture_id);
359 g_static_rw_lock_reader_lock (&app->rwlock);
360 glTexSubImage2D (GL_TEXTURE_2D, 0,
361 0, 0,
362 app->width, app->height,
363 GL_RGB, GL_UNSIGNED_BYTE,
364 GST_BUFFER_DATA(app->buffer));
365 g_static_rw_lock_reader_unlock (&app->rwlock);
366 glEnable(GL_TEXTURE_2D);
367 glBegin (GL_QUADS);
368 glTexCoord2f(0.0, 1.0); glVertex3f(-0.5,-0.5, 0.0);
369 glTexCoord2f(0.0, 0.0); glVertex3f(-0.5, 0.5,-0.2);
370 glTexCoord2f(1.0, 0.0); glVertex3f( 0.5, 0.5,-0.2);
371 glTexCoord2f(1.0, 1.0); glVertex3f( 0.5,-0.5, 0.0);
372 glEnd();
373 glDisable(GL_TEXTURE_2D);
374
375 gdk_gl_drawable_swap_buffers (gldrawable);
376 gdk_gl_drawable_gl_end (gldrawable);
377 /*** OpenGL END ***/
378
379 return TRUE;
380 }
381
382 static gboolean
383 idle_handler (gpointer data)
384 {
385 GtkWidget *drawing_area = (GtkWidget *)data;
386 gtk_widget_queue_draw (drawing_area);
387 g_usleep (1000/60);
388 return TRUE;
389 }
コンパイル・実行方法
環境によってコンパイル方法が異なりますが、Linuxでgccを使う場合は次のようにpkg-configを使うことができます。
$ gcc `pkg-config --cflags --libs gtk+-2.0 gtkglext-1.0 gstreamer-0.10` -o videotexture videotexture.c
実行するには次のようにします。
$ ./videotexture