コンテンツにスキップ

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