Skip to main content

GStreamer を使った C++ アプリ作成

参考情報

初期化

参考) Application Development Manual - Initializing GStreamer

#include <gst/gst.h>

void main() {
gst_init(nullptr, nullptr);

...
}

引数に argc, argv を渡すことで、オプションを指定することができる。

パイプラインの構築

コマンドライン指定

参考) Tutorials - Basic tutorials 1: Hello world!

コマンドラインツール(gst-launch-1.0)の指定をそのまま指定する場合。

#include <gst/gst.h>

static GstElement *create_pipeline() {
return gst_parse_launch(
"rtspsrc location=\"rtsp://xxxx\" protocols=tcp"
" ! rtph264depay"
" ! queue"
" ! h264parse"
" ! video/x-h264,stream-format=avc,alignment=au"
" ! kvssink stream-name=\"stream-test\" storage-size=512"
"", nullptr
);
}

プログラムで構築

#include <gst/gst.h>

static GstElement *create_pipeline() {
GstElement *pipeline, *source, *depay, *queue, *parse, *sink;

// パイプライン生成
pipeline = gst_pipeline_new("test");

// 各 Element を生成
source = gst_element_factory_make("rtspsrc", "rtspsrc");
g_object_set(source, "location", "rtsp://xxxx", nullptr);
g_object_set(source, "protocols", "tcp", nullptr);

depay = gst_element_factory_make("rtph264depay", "rtph264depay");

queue = gst_element_factory_make("queue", "queue");

parse = gst_element_factory_make("h264parse", "parse");

sink = gst_element_factory_make("kvssink", "kvssink");
g_object_set(sink, "stream-name", "stream-test", nullptr);
g_object_set(sink, "storage-size", 512, nullptr);

// すべての Element を Bin にまとめる
gst_bin_add_many(GST_BIN(data->pipeline), source, depay, queue, parse, sink, nullptr);

// depay -> queue -> parse -> sink をリンク
gst_element_link_many(depay, queue, parse, sink, nullptr);

// pad-added を受けてから source -> depay をリンク
g_signal_connect(source, "pad-added", G_CALLBACK(*[](GstElement *src, GstPad *srcPad, GstElement *depay) {
gst_pad_link(srcPad, gst_element_get_static_pad(depay, "sink"));
}), depay);
}

分岐させる場合は gst_element_link_many() でそれぞれリンクする。

gst_element_link_many(a, b, nullptr);
gst_element_link_many(b, c, d, nullptr);
gst_element_link_many(b, e, f, nullptr);

開始・停止

パイプラインの初期状態は NULLREADY / PAUSED / PLAYING という状態があるので、状態を変更する。

#include <gst/gst.h>

// 開始
void main() {
gst_init(nullptr, nullptr);

// パイプライン生成
GstElement *pipeline = create_pipeline();

// 開始
GstStateChangeReturn ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr("Unable to set the pipeline to the playing state.\n");
gst_object_unref(pipeline);
return;
}

// メッセージループ (後述)
mainloop(pipeline);

// 停止
gst_element_set_state(pipeline, GST_STATE_NULL);

// パイプライン解放
gst_object_unref(pipeline);
}

メッセージループ

ストリームを開始したら、状態変化などのメッセージを受信する。

同期待ち

参考) Tutorials - Basic tutorials 1: Hello world!

gst_bus_timed_pop_filtered() で指定したイベントを待つ場合。

#include <gst/gst.h>

static void mainloop(GstElement *piepline) {
GstBus *bus;
GstMessage *msg;
gboolean terminate = FALSE;

bus = gst_element_get_bus(pipeline);
do {
msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS));
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR: // エラー発生
// TODO
break;
case GST_MESSAGE_EOS:
// ストリームの終了
terminate = TRUE;
break;
default:
// ありえない
break;
}
} while (terminate == FALSE);

// 停止
gst_element_set_state(pipeline, GST_STATE_NULL);

// 解放
gst_object_unref(pipeline);
}

コールバック

メッセージをコールバック関数で処理する場合。

#include <gst/gst.h>

static GMainLoop *loop;

static gboolean my_bus_callback(GstBus *bus, GstMessage *message, gpointer data) {
switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_ERROR: {
g_main_loop_quit(loop); // メインループを抜ける
break;
}
case GST_MESSAGE_EOS:
g_main_loop_quit(loop); // メインループを抜ける
break;
default:
break;
}

return TRUE;
}

static void mainloop(GstElement *piepline) {
GstBus *bus;
guint bus_watch_id;

// コールバック設定
bus = gst_element_get_bus(pipeline);
bus_watch_id = gst_bus_add_watch(bus, my_bus_callback, data);
gst_object_unref(bus);

// メインループ
loop = g_main_loop_new(nullptr, FALSE);
g_main_loop_run(loop);

// 解放
g_source_remove(bus_watch_id);
g_main_loop_unref(loop);
}

Elements 例

FPS 変換

    GstElement *videorate, *filter;

videorate = gst_element_factory_make("videorate", "videorate");

filter = gst_element_factory_make("capsfilter", "filter");
g_object_set(G_OBJECT(data->filter_rate), "caps", gst_caps_new_simple(
"video/x-raw",
"framerate", GST_TYPE_FRACTION, 1, 1,
nullptr), nullptr);

キーフレームだけ処理

参考) How to make Gstreamer return only keyframes?

こちらのコードを参考に真似てみた。

static void gst_seek_next_keyframe(PipelineData *data) {
g_debug("Gst configuring pipeline to seek only keyframes...");
gint64 pos;
auto found = gst_element_query_position(data->pipeline, GST_FORMAT_TIME, &pos);
if (!found) {
g_warning("Gst current pipeline position not found.");
return;
}
g_debug("Gst current pipeline position: %lld", pos);
auto isEventHandled = gst_element_seek(
data->pipeline,
1.0, // keep rate close to real time
GST_FORMAT_TIME,
GstSeekFlags(
GST_SEEK_FLAG_FLUSH |
GST_SEEK_FLAG_KEY_UNIT |
GST_SEEK_FLAG_TRICKMODE |
GST_SEEK_FLAG_SNAP_AFTER |
GST_SEEK_FLAG_TRICKMODE_KEY_UNITS |
GST_SEEK_FLAG_TRICKMODE_NO_AUDIO
),
GST_SEEK_TYPE_SET, pos,
GST_SEEK_TYPE_END, 0
);
g_debug("Gst pipeline configured to seek only keyframes: %d", isEventHandled);
}

static GMainLoop *loop;

static gboolean my_bus_callback(GstBus *bus, GstMessage *message, gpointer data) {
switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_ERROR:
case GST_MESSAGE_EOS:
g_main_loop_quit(loop);
break;
case GST_MESSAGE_STREAM_START:
case GST_MESSAGE_ASYNC_DONE:
// ★GST_MESSAGE_STREAM_START と GST_MESSAGE_ASYNC_DONE をハンドルする。
gst_seek_next_keyframe((PipelineData *) data);
break;
default:
/* unhandled message */
break;
}
return TRUE;
}