GStreamer を使った C++ アプリ作成
参考情報
- GStreamer Documentation
初期化
参考) 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);
開始・停止
パイプラインの初期状態は NULL。READY / 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;
}