問題の説明
双眼鏡プラグイン。以前の入力解像度は 540*960、出力解像度は 540*960 でした。イベント関数を変更した後、出力解像度を 1080*1920 に変更する必要があります。以前のプラグインは警告を報告しました。スティッキー イベントを送信できませんでした。ネゴシエートされていない結果もあり、保留中のイベントをマークします。
これまで gstreamer のイベントとクエリのメカニズムについてあまり知らなかったのですが、gstreamer のソース コードを 2 日間掘り下げた後、gstreamer のメカニズムについての理解が深まりました。記録させてください。
Gstreamer イベント イベント定義:
イベントイベントのソースコード定義は次のとおりです。
typedef enum {
GST_EVENT_TYPE_UPSTREAM = 1 << 0,
GST_EVENT_TYPE_DOWNSTREAM = 1 << 1,
GST_EVENT_TYPE_SERIALIZED = 1 << 2,
GST_EVENT_TYPE_STICKY = 1 << 3,
GST_EVENT_TYPE_STICKY_MULTI = 1 << 4
} GstEventTypeFlags;
#define GST_EVENT_NUM_SHIFT (8)
#define GST_EVENT_MAKE_TYPE(num,flags) \
(((num) << GST_EVENT_NUM_SHIFT) | (flags))
#define FLAG(name) GST_EVENT_TYPE_##name
typedef enum {
GST_EVENT_UNKNOWN = GST_EVENT_MAKE_TYPE (0, 0),
/* bidirectional events */
GST_EVENT_FLUSH_START = GST_EVENT_MAKE_TYPE (10, FLAG(BOTH)),
GST_EVENT_FLUSH_STOP = GST_EVENT_MAKE_TYPE (20, FLAG(BOTH) | FLAG(SERIALIZED)),
/* downstream serialized events */
GST_EVENT_STREAM_START = GST_EVENT_MAKE_TYPE (40, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)),
GST_EVENT_CAPS = GST_EVENT_MAKE_TYPE (50, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)),
GST_EVENT_SEGMENT = GST_EVENT_MAKE_TYPE (70, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)),
GST_EVENT_TAG = GST_EVENT_MAKE_TYPE (80, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY) | FLAG(STICKY_MULTI)),
GST_EVENT_BUFFERSIZE = GST_EVENT_MAKE_TYPE (90, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)),
GST_EVENT_SINK_MESSAGE = GST_EVENT_MAKE_TYPE (100, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY) | FLAG(STICKY_MULTI)),
GST_EVENT_EOS = GST_EVENT_MAKE_TYPE (110, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)),
GST_EVENT_TOC = GST_EVENT_MAKE_TYPE (120, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY) | FLAG(STICKY_MULTI)),
GST_EVENT_PROTECTION = GST_EVENT_MAKE_TYPE (130, FLAG (DOWNSTREAM) | FLAG (SERIALIZED) | FLAG (STICKY) | FLAG (STICKY_MULTI)),
/* non-sticky downstream serialized */
GST_EVENT_SEGMENT_DONE = GST_EVENT_MAKE_TYPE (150, FLAG(DOWNSTREAM) | FLAG(SERIALIZED)),
GST_EVENT_GAP = GST_EVENT_MAKE_TYPE (160, FLAG(DOWNSTREAM) | FLAG(SERIALIZED)),
/* upstream events */
GST_EVENT_QOS = GST_EVENT_MAKE_TYPE (190, FLAG(UPSTREAM)),
GST_EVENT_SEEK = GST_EVENT_MAKE_TYPE (200, FLAG(UPSTREAM)),
GST_EVENT_NAVIGATION = GST_EVENT_MAKE_TYPE (210, FLAG(UPSTREAM)),
GST_EVENT_LATENCY = GST_EVENT_MAKE_TYPE (220, FLAG(UPSTREAM)),
GST_EVENT_STEP = GST_EVENT_MAKE_TYPE (230, FLAG(UPSTREAM)),
GST_EVENT_RECONFIGURE = GST_EVENT_MAKE_TYPE (240, FLAG(UPSTREAM)),
GST_EVENT_TOC_SELECT = GST_EVENT_MAKE_TYPE (250, FLAG(UPSTREAM)),
/* custom events start here */
GST_EVENT_CUSTOM_UPSTREAM = GST_EVENT_MAKE_TYPE (270, FLAG(UPSTREAM)),
GST_EVENT_CUSTOM_DOWNSTREAM = GST_EVENT_MAKE_TYPE (280, FLAG(DOWNSTREAM) | FLAG(SERIALIZED)),
GST_EVENT_CUSTOM_DOWNSTREAM_OOB = GST_EVENT_MAKE_TYPE (290, FLAG(DOWNSTREAM)),
GST_EVENT_CUSTOM_DOWNSTREAM_STICKY = GST_EVENT_MAKE_TYPE (300, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY) | FLAG(STICKY_MULTI)),
GST_EVENT_CUSTOM_BOTH = GST_EVENT_MAKE_TYPE (310, FLAG(BOTH) | FLAG(SERIALIZED)),
GST_EVENT_CUSTOM_BOTH_OOB = GST_EVENT_MAKE_TYPE (320, FLAG(BOTH))
} GstEventType;
Gstreamer がどのようにマクロを設計するかは、実際にはここから非常に明確です。
GST_EVENT_MAKE_TYPE
実際、このマクロは非常に単純なことを 1 つだけ実行します. 最初のデジタル パラメータを 8 ビット左にシフトし、空いた下位 8 ビットを使用してイベントの性質を格納します. これは、列挙型変数で説明されています。対応するビットGstEventTypeFlags
は 1 または 0 で、このプロパティの有無を表します。C 言語でマクロを設計するこの手法は、C/C++ プログラムを自分で作成するときにも使用できます。
また、一般的なイベントはCAPSやEOSなどスティッキーイベントが多いことが分かりますので、Gstreamerで「スティッキーイベントを送信できませんでした」と表示された場合は、まず何がイベントなのかを判断する必要があります。
gst_pad_peer_query 関数
gboolean
gst_pad_peer_query (GstPad * pad, GstQuery * query)
{
[...]
serialized = GST_QUERY_IS_SERIALIZED (query);
GST_OBJECT_LOCK (pad);
if (GST_PAD_IS_SRC (pad) && serialized) {
/* all serialized queries on the srcpad trigger push of
* sticky events */
if (check_sticky (pad, NULL) != GST_FLOW_OK)
goto sticky_failed;
}
[...]
peerpad = GST_PAD_PEER (pad);
[...]
res = gst_pad_query (peerpad, query);
[...]
return res;
/* ERRORS */
[...]
sticky_failed:
{
GST_WARNING_OBJECT (pad, "could not send sticky events");
GST_OBJECT_UNLOCK (pad);
return FALSE;
}
[...]
}
}
gst_pad_peer_query
パッドのピアパッド (つまり、パッドに正常に接続され、結合されたパッド) にクエリを送信するために使用される重要でない部分が削除されており、実際にピアパッドの関数を呼び出していますgst_pad_query
。
「スティッキー イベントを送信できませんでした」は check_sticky 関数の失敗が原因ですが、この関数は何をしますか?
ここでの主な目的は、現在のパッドに残留イベントがあるかどうかを判断し、存在する場合はそれらをピアパッドに送信してクリアすることです。
check_sticky 関数
/* check sticky events and push them when needed. should be called
* with pad LOCK */
static inline GstFlowReturn
check_sticky (GstPad * pad, GstEvent * event)
{
PushStickyData data = {
GST_FLOW_OK, FALSE, event };
if (G_UNLIKELY (GST_PAD_HAS_PENDING_EVENTS (pad))) {
GST_OBJECT_FLAG_UNSET (pad, GST_PAD_FLAG_PENDING_EVENTS);
GST_DEBUG_OBJECT (pad, "pushing all sticky events");
events_foreach (pad, push_sticky, &data);
/* If there's an EOS event we must push it downstream
* even if sending a previous sticky event failed.
* Otherwise the pipeline might wait forever for EOS.
*
* Only do this if pushing another event than the EOS
* event failed.
*/
if (data.ret != GST_FLOW_OK && !data.was_eos) {
PadEvent *ev = find_event_by_type (pad, GST_EVENT_EOS, 0);
if (ev && !ev->received) {
data.ret = gst_pad_push_event_unchecked (pad, gst_event_ref (ev->event),
GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM);
/* the event could have been dropped. Because this can only
* happen if the user asked for it, it's not an error */
if (data.ret == GST_FLOW_CUSTOM_SUCCESS)
data.ret = GST_FLOW_OK;
}
}
}
return data.ret;
}
check_sticky のソース コードは非常に短く、2 つの部分に分けることができます。
- 未
events_foreach
処理のイベントに対してpush_sticky
関数を繰り返し呼び出す - 処理対象のイベントが EOS の場合、プッシュに失敗しても下流に渡さなければパイプラインは終了しません。
GST_PAD_HAS_PENDING_EVENTS
文字通り、未処理のイベントがあるかどうかを判断するマクロです。前の関数によって渡されたパラメーターのうち、data
構造体にあるものはevent
実際にはNULL
.
data.ret
それが問題の原因であり、この ret は変更されただけでpush_sticky
変更events_foreach
されておらず、パッドの未処理のイベントを 2 番目のパラメーターに順番に送信するだけなので(下記参照)、関数push_sticky
で問題が発生します。push_sticky
push_sticky 関数
static gboolean
push_sticky (GstPad * pad, PadEvent * ev, gpointer user_data)
{
PushStickyData *data = user_data;
GstEvent *event = ev->event;
if (ev->received) {
GST_DEBUG_OBJECT (pad, "event %s was already received",
GST_EVENT_TYPE_NAME (event));
return TRUE;
}
/* If we're called because of an sticky event, only forward
* events that would come before this new event and the
* event itself */
if (data->event && GST_EVENT_IS_STICKY (data->event) &&
GST_EVENT_TYPE (data->event) <= GST_EVENT_SEGMENT &&
GST_EVENT_TYPE (data->event) < GST_EVENT_TYPE (event)) {
data->ret = GST_FLOW_CUSTOM_SUCCESS_1;
} else {
data->ret = gst_pad_push_event_unchecked (pad, gst_event_ref (event),
GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM);
if (data->ret == GST_FLOW_CUSTOM_SUCCESS_1)
data->ret = GST_FLOW_OK;
}
switch (data->ret) {
case GST_FLOW_OK:
ev->received = TRUE;
GST_DEBUG_OBJECT (pad, "event %s marked received",
GST_EVENT_TYPE_NAME (event));
break;
case GST_FLOW_CUSTOM_SUCCESS:
/* we can't assume the event is received when it was dropped */
GST_DEBUG_OBJECT (pad, "event %s was dropped, mark pending",
GST_EVENT_TYPE_NAME (event));
GST_OBJECT_FLAG_SET (pad, GST_PAD_FLAG_PENDING_EVENTS);
data->ret = GST_FLOW_OK;
break;
case GST_FLOW_CUSTOM_SUCCESS_1:
/* event was ignored and should be sent later */
GST_DEBUG_OBJECT (pad, "event %s was ignored, mark pending",
GST_EVENT_TYPE_NAME (event));
GST_OBJECT_FLAG_SET (pad, GST_PAD_FLAG_PENDING_EVENTS);
data->ret = GST_FLOW_OK;
break;
case GST_FLOW_NOT_LINKED:
/* not linked is not a problem, we are sticky so the event will be
* rescheduled to be sent later on re-link, but only for non-EOS events */
GST_DEBUG_OBJECT (pad, "pad was not linked, mark pending");
if (GST_EVENT_TYPE (event) != GST_EVENT_EOS) {
data->ret = GST_FLOW_OK;
ev->received = TRUE;
}
break;
default:
GST_DEBUG_OBJECT (pad, "result %s, mark pending events",
gst_flow_get_name (data->ret));
GST_OBJECT_FLAG_SET (pad, GST_PAD_FLAG_PENDING_EVENTS);
break;
}
if (data->ret != GST_FLOW_OK && GST_EVENT_TYPE (event) == GST_EVENT_EOS)
data->was_eos = TRUE;
return data->ret == GST_FLOW_OK;
}
user_data は前のデータ ポインターです。コード全体は長く見えますが、実際には 1 つの関数しか呼び出されておらgst_pad_push_event_unchecked
ず、この関数以外のコードで ret 値を非交渉にすることはできません。
result not-negotiated, mark pending events
gstreamer でデバッグ レベルが DEBUG に設定されている場合でも、単語は表示されます。このネゴシエートされていないのは data->ret の値であり、これが「スティッキー イベントを送信できませんでした」というエラーが発生した理由です。次にgst_pad_push_event_unchecked
関数を入力します。
補足:GstFlowReturn 列挙型の定義について、前述のネゴシエートされていないのは実際には GST_FLOW_NOT_NEGOTIATED = -4 というマクロなので、以下のデバッグでは ret 変数に GST_FLOW_NOT_NEGOTIATED が代入されているところに注意する必要があります。
typedef enum {
/* custom success starts here */
GST_FLOW_CUSTOM_SUCCESS_2 = 102,
GST_FLOW_CUSTOM_SUCCESS_1 = 101,
GST_FLOW_CUSTOM_SUCCESS = 100,
/* core predefined */
GST_FLOW_OK = 0,
/* expected failures */
GST_FLOW_NOT_LINKED = -1,
GST_FLOW_FLUSHING = -2,
/* error cases */
GST_FLOW_EOS = -3,
GST_FLOW_NOT_NEGOTIATED = -4,
GST_FLOW_ERROR = -5,
GST_FLOW_NOT_SUPPORTED = -6,
/* custom error starts here */
GST_FLOW_CUSTOM_ERROR = -100,
GST_FLOW_CUSTOM_ERROR_1 = -101,
GST_FLOW_CUSTOM_ERROR_2 = -102
} GstFlowReturn;
gst_pad_push_event_unchecked 関数
static GstFlowReturn
gst_pad_push_event_unchecked (GstPad * pad, GstEvent * event,
GstPadProbeType type)
{
GstFlowReturn ret;
GstPad *peerpad;
GstEventType event_type;
gint64 old_pad_offset = pad->offset;
/* pass the adjusted event on. We need to do this even if
* there is no peer pad because of the probes. */
event = apply_pad_offset (pad, event, GST_PAD_IS_SINK (pad));
[...]
/* the pad offset might've been changed by any of the probes above. It
* would've been taken into account when repushing any of the sticky events
* above but not for our current event here */
if (G_UNLIKELY (old_pad_offset != pad->offset)) {
event =
_apply_pad_offset (pad, event, GST_PAD_IS_SINK (pad),
pad->offset - old_pad_offset);
}
[...]
/* now check the peer pad */
peerpad = GST_PAD_PEER (pad);
[...]
ret = gst_pad_send_event_unchecked (peerpad, event, type);
[...]
return ret;
[...]
}
型パラメーターはあまり重要ではないため、関連するコードの一部が削除されています。ここでは、GST_FLOW_NOT_NEGOTIATED を返すタイミングに注目します。メイン関数が gst_pad_send_event_unchecked であることがわかります。次に続行します。
gst_pad_send_event_unchecked 関数
static GstFlowReturn
gst_pad_send_event_unchecked (GstPad * pad, GstEvent * event,
GstPadProbeType type)
{
GstFlowReturn ret;
GstEventType event_type;
gboolean serialized, need_unlock = FALSE, sticky;
GstPadEventFunction eventfunc;
GstPadEventFullFunction eventfullfunc = NULL;
GstObject *parent;
[...]
event = apply_pad_offset (pad, event, GST_PAD_IS_SRC (pad));
[...]
eventfullfunc = GST_PAD_EVENTFULLFUNC (pad);
eventfunc = GST_PAD_EVENTFUNC (pad);
if (G_UNLIKELY (eventfunc == NULL && eventfullfunc == NULL))
goto no_function;
[...]
if (eventfullfunc) {
ret = eventfullfunc (pad, parent, event);
} else if (eventfunc (pad, parent, event)) {
ret = GST_FLOW_OK;
} else {
/* something went wrong */
switch (event_type) {
case GST_EVENT_CAPS:
ret = GST_FLOW_NOT_NEGOTIATED;
break;
default:
ret = GST_FLOW_ERROR;
break;
}
}
[...]
return ret;
[...]
}
ここでも、GST_FLOW_NOT_NEGOTIATED を返すタイミングに注目し、これら 2 つのマクロに注意しGST_PAD_EVENTFUNC
、GST_PAD_EVENTFULLFUNC
さらに、この時点でパッド ポインターが下流のパッド、つまり最初のパッドのピアパッドを指していることに注意してください。公式の指示によると、この 2 つのマクロは、下流のパッドのイベント関数を呼び出す役割を担っています.これは、プラグインを作成したときに gst_pad_set_event_function() と gst_pad_set_event_full_function() によって設定されたイベント関数です.
/**
* GST_PAD_EVENTFUNC:
* @pad: a #GstPad
*
* Get the #GstPadEventFunction from the given @pad, which
* is the function that handles events on the pad. You can
* use this to set your own event handling function on a pad
* after you create it. If your element derives from a base
* class, use the base class's virtual functions instead.
*/
#define GST_PAD_EVENTFUNC(pad) (GST_PAD_CAST(pad)->eventfunc)
/**
* GST_PAD_EVENTFULLFUNC:
* @pad: a #GstPad
*
* Get the #GstPadEventFullFunction from the given @pad, which
* is the function that handles events on the pad. You can
* use this to set your own event handling function on a pad
* after you create it. If your element derives from a base
* class, use the base class's virtual functions instead.
*
* Since: 1.8
*/
#define GST_PAD_EVENTFULLFUNC(pad) (GST_PAD_CAST(pad)->ABI.abi.eventfullfunc)
次にretをGST_FLOW_NOT_NEGOTIATEDにすると、eventfulllfuncとeventfuncがTrueを返さない場合はこうなるので、問題は私が書いたイベント関数がTrueを返す場合とFalseを返す場合です。
要約する
実際、すべてのプロセスを経て、後でこのような問題が発生した場合は、ピアパッドのイベント関数が 0 を返すことを直接判断できます。
ちなみにgst_pad_push_event関数は通常イベント関数内で呼び出される.この関数を見るとイベントがスティッキーならcheck_sticky関数を呼び出しており,上記とクローズドループを形成している.gstreamerのパイプライン全体が呼び出される. level by level. 関数はイベント event を渡します。