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