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

alt gst-launch

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を参照してください。

alt videotexture

色空間の変換

今回、メディア処理パイプラインは次のように構成します。

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);

バッファデータの取得と排他制御

alt videotextureapp

ビデオテクスチャにするためのデータは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

VideoTexture (last edited 2009-08-28 18:40:07 by KotaYamaguchi)