1.GstBaseSrc剖析
gst插件流程分析先看init构造函数gst_base_src_init(本文流程主要基于filesrc插件讲解)
static void
gst_base_src_init (GstBaseSrc * basesrc, gpointer g_class)
{
GstPad *pad;
GstPadTemplate *pad_template;
basesrc->priv = gst_base_src_get_instance_private (basesrc);
basesrc->is_live = FALSE;
g_mutex_init (&basesrc->live_lock);
g_cond_init (&basesrc->live_cond);
basesrc->num_buffers = DEFAULT_NUM_BUFFERS;
basesrc->num_buffers_left = -1;
g_atomic_int_set (&basesrc->priv->automatic_eos, TRUE);
basesrc->can_activate_push = TRUE;
pad_template =
gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src");
g_return_if_fail (pad_template != NULL);
GST_DEBUG_OBJECT (basesrc, "creating src pad");
pad = gst_pad_new_from_template (pad_template, "src");
GST_DEBUG_OBJECT (basesrc, "setting functions on src pad");
gst_pad_set_activatemode_function (pad, gst_base_src_activate_mode);
gst_pad_set_event_function (pad, gst_base_src_event);
gst_pad_set_query_function (pad, gst_base_src_query);
gst_pad_set_getrange_function (pad, gst_base_src_getrange);
/* hold pointer to pad */
basesrc->srcpad = pad;
GST_DEBUG_OBJECT (basesrc, "adding src pad");
gst_element_add_pad (GST_ELEMENT (basesrc), pad);
basesrc->blocksize = DEFAULT_BLOCKSIZE;
basesrc->clock_id = NULL;
/* we operate in BYTES by default */
gst_base_src_set_format (basesrc, GST_FORMAT_BYTES);
basesrc->priv->do_timestamp = DEFAULT_DO_TIMESTAMP;
g_atomic_int_set (&basesrc->priv->have_events, FALSE);
g_cond_init (&basesrc->priv->async_cond);
basesrc->priv->start_result = GST_FLOW_FLUSHING;
GST_OBJECT_FLAG_UNSET (basesrc, GST_BASE_SRC_FLAG_STARTED);
GST_OBJECT_FLAG_UNSET (basesrc, GST_BASE_SRC_FLAG_STARTING);
GST_OBJECT_FLAG_SET (basesrc, GST_ELEMENT_FLAG_SOURCE);
GST_DEBUG_OBJECT (basesrc, "init done");
}
比较关键的点:创建basesrc->srcpad ,注册activatemode、event、query、getrange函数。
在看下gst_base_src_change_state,然而对于非live可以看到并没有触发什么流程,那么src的流程是怎么走的呢?
static GstStateChangeReturn
gst_base_src_change_state (GstElement * element, GstStateChange transition)
{
GstBaseSrc *basesrc;
GstStateChangeReturn result;
gboolean no_preroll = FALSE;
basesrc = GST_BASE_SRC (element);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
no_preroll = gst_base_src_is_live (basesrc);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
GST_DEBUG_OBJECT (basesrc, "PAUSED->PLAYING");
if (gst_base_src_is_live (basesrc)) {
/* now we can start playback */
gst_base_src_set_playing (basesrc, TRUE);
}
break;
default:
break;
}
if ((result =
GST_ELEMENT_CLASS (parent_class)->change_state (element,
transition)) == GST_STATE_CHANGE_FAILURE)
goto failure;
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
GST_DEBUG_OBJECT (basesrc, "PLAYING->PAUSED");
if (gst_base_src_is_live (basesrc)) {
/* make sure we block in the live cond in PAUSED */
gst_base_src_set_playing (basesrc, FALSE);
no_preroll = TRUE;
}
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
{
/* we don't need to unblock anything here, the pad deactivation code
* already did this */
if (g_atomic_int_get (&basesrc->priv->has_pending_eos)) {
GST_OBJECT_LOCK (basesrc);
CLEAR_PENDING_EOS (basesrc);
GST_OBJECT_UNLOCK (basesrc);
}
gst_event_replace (&basesrc->pending_seek, NULL);
break;
}
case GST_STATE_CHANGE_READY_TO_NULL:
break;
default:
break;
}
if (no_preroll && result == GST_STATE_CHANGE_SUCCESS)
result = GST_STATE_CHANGE_NO_PREROLL;
return result;
/* ERRORS */
failure:
{
GST_DEBUG_OBJECT (basesrc, "parent failed state change");
return result;
}
}
先贴一下日志
其实是必须要调用gst_base_src_start,但是首先可以看到查询函数gst_base_src_default_query
case GST_QUERY_SCHEDULING:
{
gboolean random_access;
random_access = gst_base_src_is_random_access (src);
/* we can operate in getrange mode if the native format is bytes
* and we are seekable, this condition is set in the random_access
* flag and is set in the _start() method. */
gst_query_set_scheduling (query, GST_SCHEDULING_FLAG_SEEKABLE, 1, -1, 0);
if (random_access)
gst_query_add_scheduling_mode (query, GST_PAD_MODE_PULL);
gst_query_add_scheduling_mode (query, GST_PAD_MODE_PUSH);
res = TRUE;
break;
}
static gboolean
gst_base_src_is_random_access (GstBaseSrc * src)
{
/* we need to start the basesrc to check random access */
if (!GST_BASE_SRC_IS_STARTED (src)) {
GST_LOG_OBJECT (src, "doing start/stop to check get_range support");
if (G_LIKELY (gst_base_src_start (src))) {
if (gst_base_src_start_wait (src) != GST_FLOW_OK)
goto start_failed;
gst_base_src_stop (src);
}
}
return src->random_access;
/* ERRORS */
start_failed:
{
GST_DEBUG_OBJECT (src, "failed to start");
return FALSE;
}
}
如果还没有GST_BASE_SRC_IS_STARTED则回去调用gst_base_src_start
static gboolean
gst_base_src_start (GstBaseSrc * basesrc)
{
GstBaseSrcClass *bclass;
gboolean result;
GST_LIVE_LOCK (basesrc);
GST_OBJECT_LOCK (basesrc);
if (GST_BASE_SRC_IS_STARTING (basesrc))
goto was_starting;
if (GST_BASE_SRC_IS_STARTED (basesrc))
goto was_started;
basesrc->priv->start_result = GST_FLOW_FLUSHING;
GST_OBJECT_FLAG_SET (basesrc, GST_BASE_SRC_FLAG_STARTING);
gst_segment_init (&basesrc->segment, basesrc->segment.format);
GST_OBJECT_UNLOCK (basesrc);
basesrc->num_buffers_left = basesrc->num_buffers;
basesrc->running = FALSE;
basesrc->priv->segment_pending = FALSE;
basesrc->priv->segment_seqnum = gst_util_seqnum_next ();
basesrc->priv->forced_eos = FALSE;
GST_LIVE_UNLOCK (basesrc);
bclass = GST_BASE_SRC_GET_CLASS (basesrc);
if (bclass->start)
result = bclass->start (basesrc);
else
result = TRUE;
if (!result)
goto could_not_start;
if (!gst_base_src_is_async (basesrc)) {
gst_base_src_start_complete (basesrc, GST_FLOW_OK);
/* not really waiting here, we call this to get the result
* from the start_complete call */
result = gst_base_src_start_wait (basesrc) == GST_FLOW_OK;
}
return result;
/* ERROR */
was_starting:
{
GST_DEBUG_OBJECT (basesrc, "was starting");
GST_OBJECT_UNLOCK (basesrc);
GST_LIVE_UNLOCK (basesrc);
return TRUE;
}
was_started:
{
GST_DEBUG_OBJECT (basesrc, "was started");
GST_OBJECT_UNLOCK (basesrc);
GST_LIVE_UNLOCK (basesrc);
return TRUE;
}
could_not_start:
{
GST_DEBUG_OBJECT (basesrc, "could not start");
/* subclass is supposed to post a message but we post one as a fallback
* just in case. We don't have to call _stop. */
GST_ELEMENT_ERROR (basesrc, CORE, STATE_CHANGE, (NULL),
("Failed to start"));
gst_base_src_start_complete (basesrc, GST_FLOW_ERROR);
return FALSE;
}
}
比较关键的流程是会调用bclass->start (basesrc),这里就是调用到filesrc中的start函数了。
继续走到gst_base_src_start_complete,但是这时候srcpad还没有active,所以会goto not_activated_yet。后续gst_base_src_start_wait会返回error。所以gst_base_src_start最后是error状态,没有到达正常started状态。日志可以看到后面的一次query依旧是如此。
oid
gst_base_src_start_complete (GstBaseSrc * basesrc, GstFlowReturn ret)
{
gboolean have_size;
guint64 size;
gboolean seekable;
GstFormat format;
GstPadMode mode;
GstEvent *event;
if (ret != GST_FLOW_OK)
goto error;
GST_DEBUG_OBJECT (basesrc, "starting source");
format = basesrc->segment.format;
/* figure out the size */
have_size = FALSE;
size = -1;
if (format == GST_FORMAT_BYTES) {
GstBaseSrcClass *bclass = GST_BASE_SRC_GET_CLASS (basesrc);
if (bclass->get_size) {
if (!(have_size = bclass->get_size (basesrc, &size)))
size = -1;
}
GST_DEBUG_OBJECT (basesrc, "setting size %" G_GUINT64_FORMAT, size);
/* only update the size when operating in bytes, subclass is supposed
* to set duration in the start method for other formats */
GST_OBJECT_LOCK (basesrc);
basesrc->segment.duration = size;
GST_OBJECT_UNLOCK (basesrc);
}
GST_DEBUG_OBJECT (basesrc,
"format: %s, have size: %d, size: %" G_GUINT64_FORMAT ", duration: %"
G_GINT64_FORMAT, gst_format_get_name (format), have_size, size,
basesrc->segment.duration);
seekable = gst_base_src_seekable (basesrc);
GST_DEBUG_OBJECT (basesrc, "is seekable: %d", seekable);
/* update for random access flag */
basesrc->random_access = seekable && format == GST_FORMAT_BYTES;
GST_DEBUG_OBJECT (basesrc, "is random_access: %d", basesrc->random_access);
gst_pad_mark_reconfigure (GST_BASE_SRC_PAD (basesrc));
GST_OBJECT_LOCK (basesrc->srcpad);
mode = GST_PAD_MODE (basesrc->srcpad);
GST_OBJECT_UNLOCK (basesrc->srcpad);
/* take the stream lock here, we only want to let the task run when we have
* set the STARTED flag */
GST_PAD_STREAM_LOCK (basesrc->srcpad);
switch (mode) {
case GST_PAD_MODE_PUSH:
/* do initial seek, which will start the task */
GST_OBJECT_LOCK (basesrc);
event = basesrc->pending_seek;
basesrc->pending_seek = NULL;
GST_OBJECT_UNLOCK (basesrc);
/* The perform seek code will start the task when finished. We don't have to
* unlock the streaming thread because it is not running yet */
if (G_UNLIKELY (!gst_base_src_perform_seek (basesrc, event, FALSE)))
goto seek_failed;
if (event)
gst_event_unref (event);
break;
case GST_PAD_MODE_PULL:
/* if not random_access, we cannot operate in pull mode for now */
if (G_UNLIKELY (!basesrc->random_access))
goto no_get_range;
break;
default:
goto not_activated_yet;
break;
}
GST_OBJECT_LOCK (basesrc);
GST_OBJECT_FLAG_SET (basesrc, GST_BASE_SRC_FLAG_STARTED);
GST_OBJECT_FLAG_UNSET (basesrc, GST_BASE_SRC_FLAG_STARTING);
basesrc->priv->start_result = ret;
GST_ASYNC_SIGNAL (basesrc);
GST_OBJECT_UNLOCK (basesrc);
GST_PAD_STREAM_UNLOCK (basesrc->srcpad);
return;
seek_failed:
{
GST_PAD_STREAM_UNLOCK (basesrc->srcpad);
GST_ERROR_OBJECT (basesrc, "Failed to perform initial seek");
gst_base_src_stop (basesrc);
if (event)
gst_event_unref (event);
ret = GST_FLOW_ERROR;
goto error;
}
no_get_range:
{
GST_PAD_STREAM_UNLOCK (basesrc->srcpad);
gst_base_src_stop (basesrc);
GST_ERROR_OBJECT (basesrc, "Cannot operate in pull mode, stopping");
ret = GST_FLOW_ERROR;
goto error;
}
not_activated_yet:
{
GST_PAD_STREAM_UNLOCK (basesrc->srcpad);
gst_base_src_stop (basesrc);
GST_WARNING_OBJECT (basesrc, "pad not activated yet");
ret = GST_FLOW_ERROR;
goto error;
}
error:
{
GST_OBJECT_LOCK (basesrc);
basesrc->priv->start_result = ret;
GST_OBJECT_FLAG_UNSET (basesrc, GST_BASE_SRC_FLAG_STARTING);
GST_ASYNC_SIGNAL (basesrc);
GST_OBJECT_UNLOCK (basesrc);
return;
}
}
那start到底如何被调用到呢?init中我们看到注册了gst_pad_set_activatemode_function (pad, gst_base_src_activate_mode)函数。
static gboolean
gst_base_src_activate_mode (GstPad * pad, GstObject * parent,
GstPadMode mode, gboolean active)
{
gboolean res;
GstBaseSrc *src = GST_BASE_SRC (parent);
src->priv->stream_start_pending = FALSE;
GST_DEBUG_OBJECT (pad, "activating in mode %d", mode);
switch (mode) {
case GST_PAD_MODE_PULL:
res = gst_base_src_activate_pull (pad, parent, active);
break;
case GST_PAD_MODE_PUSH:
src->priv->stream_start_pending = active;
res = gst_base_src_activate_push (pad, parent, active);
break;
default:
GST_LOG_OBJECT (pad, "unknown activation mode %d", mode);
res = FALSE;
break;
}
return res;
}
static gboolean
gst_base_src_activate_pull (GstPad * pad, GstObject * parent, gboolean active)
{
GstBaseSrc *basesrc;
basesrc = GST_BASE_SRC (parent);
/* prepare subclass first */
if (active) {
GST_DEBUG_OBJECT (basesrc, "Activating in pull mode");
if (G_UNLIKELY (!gst_base_src_start (basesrc)))
goto error_start;
} else {
GST_DEBUG_OBJECT (basesrc, "Deactivating in pull mode");
if (G_UNLIKELY (!gst_base_src_stop (basesrc)))
goto error_stop;
}
return TRUE;
/* ERRORS */
error_start:
{
GST_ERROR_OBJECT (basesrc, "Failed to start in pull mode");
return FALSE;
}
error_stop:
{
GST_ERROR_OBJECT (basesrc, "Failed to stop in pull mode");
return FALSE;
}
}
以GST_PAD_MODE_PULL为例,如果需要active会调用gst_base_src_start。对应日志看是走到这里。Pad 的激活是为了确保它能够正确处理数据流、事件和查询。而Pad 的激活模式决定了它如何参与数据流动。激活模式有三种:GST_PAD_MODE_NONE,GST_PAD_MODE_PUSH,GST_PAD_MODE_PULL
那么我们看下basesrc的激活函数是如何被触发的?直接说结论吧是在下游插件typefind中
static gboolean
gst_type_find_element_activate_sink (GstPad * pad, GstObject * parent)
{
GstQuery *query;
gboolean pull_mode;
query = gst_query_new_scheduling ();
if (!gst_pad_peer_query (pad, query)) {
gst_query_unref (query);
goto typefind_push;
}
pull_mode = gst_query_has_scheduling_mode_with_flags (query,
GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE);
gst_query_unref (query);
if (!pull_mode)
goto typefind_push;
if (!gst_pad_activate_mode (pad, GST_PAD_MODE_PULL, TRUE))
goto typefind_push;
/* only start our task if we ourselves decide to start in pull mode */
return gst_pad_start_task (pad, (GstTaskFunction) gst_type_find_element_loop,
pad, NULL);
typefind_push:
{
return gst_pad_activate_mode (pad, GST_PAD_MODE_PUSH, TRUE);
}
}
从typefind的sinkpad发起scheduling模式的查询,如果支持pull_mode则gst_pad_activate_mode设置并且激活pad,(前面我们看到查询函数中GST_QUERY_SCHEDULING中是支持PULL和PUSH模式的)简单看下activate_mode_internal
static gboolean
activate_mode_internal (GstPad * pad, GstObject * parent, GstPadMode mode,
gboolean active)
{
gboolean res = FALSE;
GstPadMode old, new;
GstPadDirection dir;
GstPad *peer;
GST_OBJECT_LOCK (pad);
old = GST_PAD_MODE (pad);
dir = GST_PAD_DIRECTION (pad);
GST_OBJECT_UNLOCK (pad);
new = active ? mode : GST_PAD_MODE_NONE;
if (old == new)
goto was_ok;
if (active && old != mode && old != GST_PAD_MODE_NONE) {
/* pad was activate in the wrong direction, deactivate it
* and reactivate it in the requested mode */
GST_DEBUG_OBJECT (pad, "deactivating pad from %s mode",
gst_pad_mode_get_name (old));
if (G_UNLIKELY (!activate_mode_internal (pad, parent, old, FALSE)))
goto deactivate_failed;
old = GST_PAD_MODE_NONE;
}
switch (mode) {
case GST_PAD_MODE_PULL:
{
if (dir == GST_PAD_SINK) {
if ((peer = gst_pad_get_peer (pad))) {
GST_DEBUG_OBJECT (pad, "calling peer");
if (G_UNLIKELY (!gst_pad_activate_mode (peer, mode, active)))
goto peer_failed;
gst_object_unref (peer);
} else {
/* there is no peer, this is only fatal when we activate. When we
* deactivate, we must assume the application has unlinked the peer and
* will deactivate it eventually. */
if (active)
goto not_linked;
else
GST_DEBUG_OBJECT (pad, "deactivating unlinked pad");
}
} else {
if (G_UNLIKELY (GST_PAD_GETRANGEFUNC (pad) == NULL))
goto failure; /* Can't activate pull on a src without a
getrange function */
}
break;
}
default:
break;
}
/* Mark pad as needing reconfiguration */
if (active)
GST_OBJECT_FLAG_SET (pad, GST_PAD_FLAG_NEED_RECONFIGURE);
/* pre_activate returns TRUE if we weren't already in the process of
* switching to the 'new' mode */
if (pre_activate (pad, new)) {
if (GST_PAD_ACTIVATEMODEFUNC (pad)) {
if (G_UNLIKELY (!GST_PAD_ACTIVATEMODEFUNC (pad) (pad, parent, mode,
active)))
goto failure;
} else {
/* can happen for sinks of passthrough elements */
}
post_activate (pad, new);
}
GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "%s in %s mode",
active ? "activated" : "deactivated", gst_pad_mode_get_name (mode));
exit_success:
res = TRUE;
/* Clear sticky flags on deactivation */
if (!active) {
GST_OBJECT_LOCK (pad);
GST_OBJECT_FLAG_UNSET (pad, GST_PAD_FLAG_NEED_RECONFIGURE);
GST_OBJECT_FLAG_UNSET (pad, GST_PAD_FLAG_EOS);
GST_OBJECT_UNLOCK (pad);
}
exit:
return res;
was_ok:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad, "already %s in %s mode",
active ? "activated" : "deactivated", gst_pad_mode_get_name (mode));
goto exit_success;
}
deactivate_failed:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad,
"failed to %s in switch to %s mode from %s mode",
(active ? "activate" : "deactivate"), gst_pad_mode_get_name (mode),
gst_pad_mode_get_name (old));
goto exit;
}
peer_failed:
{
GST_OBJECT_LOCK (peer);
GST_CAT_DEBUG_OBJECT (GST_CAT_PADS, pad,
"activate_mode on peer (%s:%s) failed", GST_DEBUG_PAD_NAME (peer));
GST_OBJECT_UNLOCK (peer);
gst_object_unref (peer);
goto exit;
}
not_linked:
{
GST_CAT_INFO_OBJECT (GST_CAT_PADS, pad, "can't activate unlinked sink "
"pad in pull mode");
goto exit;
}
failure:
{
GST_OBJECT_LOCK (pad);
GST_CAT_INFO_OBJECT (GST_CAT_PADS, pad, "failed to %s in %s mode",
active ? "activate" : "deactivate", gst_pad_mode_get_name (mode));
GST_PAD_SET_FLUSHING (pad);
GST_PAD_MODE (pad) = old;
pad->priv->in_activation = FALSE;
g_cond_broadcast (&pad->priv->activation_cond);
GST_OBJECT_UNLOCK (pad);
goto exit;
}
}
关键流程
peer = gst_pad_get_peer (pad)就是获取typefind->sink的peer,也就是basesrc的srcpad。
然后调用gst_pad_activate_mode(srcpad),然后调用GST_PAD_ACTIVATEMODEFUNC(GST_PAD_ACTIVATEMODEFUNC),也就调用到了basesrc中。
(2)pre_activate、post_activate
到这里basesrc已经start并且是PULL模式,PULL模式需要upstream插件请求数据。也就是gst_base_src_getrange函数
/* must be called with LIVE_LOCK */
static GstFlowReturn
gst_base_src_get_range (GstBaseSrc * src, guint64 offset, guint length,
GstBuffer ** buf)
{
GstFlowReturn ret;
GstBaseSrcClass *bclass;
GstClockReturn status;
GstBuffer *res_buf;
GstBuffer *in_buf;
gboolean own_res_buf;
bclass = GST_BASE_SRC_GET_CLASS (src);
again:
if (src->is_live) {
if (G_UNLIKELY (!src->live_running)) {
ret = gst_base_src_wait_playing_unlocked (src);
if (ret != GST_FLOW_OK)
goto stopped;
}
}
if (G_UNLIKELY (!GST_BASE_SRC_IS_STARTED (src)
&& !GST_BASE_SRC_IS_STARTING (src)))
goto not_started;
if (G_UNLIKELY (!bclass->create))
goto no_function;
if (G_UNLIKELY (!gst_base_src_update_length (src, offset, &length, FALSE)))
goto unexpected_length;
/* track position */
GST_OBJECT_LOCK (src);
if (src->segment.format == GST_FORMAT_BYTES)
src->segment.position = offset;
GST_OBJECT_UNLOCK (src);
/* normally we don't count buffers */
if (G_UNLIKELY (src->num_buffers_left >= 0)) {
if (src->num_buffers_left == 0)
goto reached_num_buffers;
else
src->num_buffers_left--;
}
/* don't enter the create function if a pending EOS event was set. For the
* logic of the has_pending_eos, check the event function of this class. */
if (G_UNLIKELY (g_atomic_int_get (&src->priv->has_pending_eos))) {
src->priv->forced_eos = TRUE;
goto eos;
}
GST_DEBUG_OBJECT (src,
"calling create offset %" G_GUINT64_FORMAT " length %u, time %"
G_GINT64_FORMAT, offset, length, src->segment.time);
res_buf = in_buf = *buf;
own_res_buf = (*buf == NULL);
GST_LIVE_UNLOCK (src);
ret = bclass->create (src, offset, length, &res_buf);
GST_LIVE_LOCK (src);
/* As we released the LIVE_LOCK, the state may have changed */
if (src->is_live) {
if (G_UNLIKELY (!src->live_running)) {
GstFlowReturn wait_ret;
wait_ret = gst_base_src_wait_playing_unlocked (src);
if (wait_ret != GST_FLOW_OK) {
if (ret == GST_FLOW_OK && own_res_buf)
gst_buffer_unref (res_buf);
ret = wait_ret;
goto stopped;
}
}
}
/* The create function could be unlocked because we have a pending EOS. It's
* possible that we have a valid buffer from create that we need to
* discard when the create function returned _OK. */
if (G_UNLIKELY (g_atomic_int_get (&src->priv->has_pending_eos))) {
if (ret == GST_FLOW_OK) {
if (own_res_buf)
gst_buffer_unref (res_buf);
}
src->priv->forced_eos = TRUE;
goto eos;
}
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto not_ok;
/* fallback in case the create function didn't fill a provided buffer */
if (in_buf != NULL && res_buf != in_buf) {
GstMapInfo info;
gsize copied_size;
GST_CAT_DEBUG_OBJECT (GST_CAT_PERFORMANCE, src, "create function didn't "
"fill the provided buffer, copying");
if (!gst_buffer_map (in_buf, &info, GST_MAP_WRITE))
goto map_failed;
copied_size = gst_buffer_extract (res_buf, 0, info.data, info.size);
gst_buffer_unmap (in_buf, &info);
gst_buffer_set_size (in_buf, copied_size);
gst_buffer_copy_into (in_buf, res_buf, GST_BUFFER_COPY_METADATA, 0, -1);
gst_buffer_unref (res_buf);
res_buf = in_buf;
}
if (res_buf == NULL) {
GstBufferList *pending_list = src->priv->pending_bufferlist;
if (pending_list == NULL || gst_buffer_list_length (pending_list) == 0)
goto null_buffer;
res_buf = gst_buffer_list_get_writable (pending_list, 0);
own_res_buf = FALSE;
}
/* no timestamp set and we are at offset 0, we can timestamp with 0 */
if (offset == 0 && src->segment.time == 0
&& GST_BUFFER_DTS (res_buf) == -1 && !src->is_live) {
GST_DEBUG_OBJECT (src, "setting first timestamp to 0");
res_buf = gst_buffer_make_writable (res_buf);
GST_BUFFER_DTS (res_buf) = 0;
}
/* now sync before pushing the buffer */
status = gst_base_src_do_sync (src, res_buf);
/* waiting for the clock could have made us flushing */
if (G_UNLIKELY (src->priv->flushing))
goto flushing;
switch (status) {
case GST_CLOCK_EARLY:
/* the buffer is too late. We currently don't drop the buffer. */
GST_DEBUG_OBJECT (src, "buffer too late!, returning anyway");
break;
case GST_CLOCK_OK:
/* buffer synchronised properly */
GST_DEBUG_OBJECT (src, "buffer ok");
break;
case GST_CLOCK_UNSCHEDULED:
/* this case is triggered when we were waiting for the clock and
* it got unlocked because we did a state change. In any case, get rid of
* the buffer. */
if (own_res_buf)
gst_buffer_unref (res_buf);
if (!src->live_running) {
/* We return FLUSHING when we are not running to stop the dataflow also
* get rid of the produced buffer. */
GST_DEBUG_OBJECT (src,
"clock was unscheduled (%d), returning FLUSHING", status);
ret = GST_FLOW_FLUSHING;
} else {
/* If we are running when this happens, we quickly switched between
* pause and playing. We try to produce a new buffer */
GST_DEBUG_OBJECT (src,
"clock was unscheduled (%d), but we are running", status);
goto again;
}
break;
default:
/* all other result values are unexpected and errors */
GST_ELEMENT_ERROR (src, CORE, CLOCK,
(_("Internal clock error.")),
("clock returned unexpected return value %d", status));
if (own_res_buf)
gst_buffer_unref (res_buf);
ret = GST_FLOW_ERROR;
break;
}
if (G_LIKELY (ret == GST_FLOW_OK))
*buf = res_buf;
return ret;
/* ERROR */
stopped:
{
GST_DEBUG_OBJECT (src, "wait_playing returned %d (%s)", ret,
gst_flow_get_name (ret));
return ret;
}
not_ok:
{
GST_DEBUG_OBJECT (src, "create returned %d (%s)", ret,
gst_flow_get_name (ret));
return ret;
}
map_failed:
{
GST_ELEMENT_ERROR (src, RESOURCE, BUSY,
(_("Failed to map buffer.")),
("failed to map result buffer in WRITE mode"));
if (own_res_buf)
gst_buffer_unref (res_buf);
return GST_FLOW_ERROR;
}
not_started:
{
GST_DEBUG_OBJECT (src, "getrange but not started");
return GST_FLOW_FLUSHING;
}
no_function:
{
GST_DEBUG_OBJECT (src, "no create function");
return GST_FLOW_NOT_SUPPORTED;
}
unexpected_length:
{
GST_DEBUG_OBJECT (src, "unexpected length %u (offset=%" G_GUINT64_FORMAT
", size=%" G_GINT64_FORMAT ")", length, offset, src->segment.duration);
return GST_FLOW_EOS;
}
reached_num_buffers:
{
GST_DEBUG_OBJECT (src, "sent all buffers");
return GST_FLOW_EOS;
}
flushing:
{
GST_DEBUG_OBJECT (src, "we are flushing");
if (own_res_buf)
gst_buffer_unref (res_buf);
return GST_FLOW_FLUSHING;
}
eos:
{
GST_DEBUG_OBJECT (src, "we are EOS");
return GST_FLOW_EOS;
}
null_buffer:
{
GST_ELEMENT_ERROR (src, STREAM, FAILED,
(_("Internal data flow error.")),
("Subclass %s neither returned a buffer nor submitted a buffer list "
"from its create function", G_OBJECT_TYPE_NAME (src)));
return GST_FLOW_ERROR;
}
}
关键流程:
(1)bclass->create (src, offset, length, &res_buf),读取数据到res_buf,子类filesrc没有重写此函数,则调用gst_base_src_default_create
(2)bclass->alloc (src, offset, size, &res_buf),为res_buf创建buffer分配内存,子类filesrc没有重写此函数,则调用gst_base_src_default_alloc
(3)bclass->fill (src, offset, size, res_buf),为读取数据并写到res_buf,子类filesrc实现。具体细节就不讲解了
static GstFlowReturn
gst_base_src_default_alloc (GstBaseSrc * src, guint64 offset,
guint size, GstBuffer ** buffer)
{
GstFlowReturn ret;
GstBaseSrcPrivate *priv = src->priv;
GstBufferPool *pool = NULL;
GstAllocator *allocator = NULL;
GstAllocationParams params;
GST_OBJECT_LOCK (src);
if (priv->pool) {
pool = gst_object_ref (priv->pool);
} else if (priv->allocator) {
allocator = gst_object_ref (priv->allocator);
}
params = priv->params;
GST_OBJECT_UNLOCK (src);
if (pool) {
ret = gst_buffer_pool_acquire_buffer (pool, buffer, NULL);
} else if (size != -1) {
*buffer = gst_buffer_new_allocate (allocator, size, ¶ms);
if (G_UNLIKELY (*buffer == NULL))
goto alloc_failed;
ret = GST_FLOW_OK;
} else {
GST_WARNING_OBJECT (src, "Not trying to alloc %u bytes. Blocksize not set?",
size);
goto alloc_failed;
}
done:
if (pool)
gst_object_unref (pool);
if (allocator)
gst_object_unref (allocator);
return ret;
/* ERRORS */
alloc_failed:
{
GST_ERROR_OBJECT (src, "Failed to allocate %u bytes", size);
ret = GST_FLOW_ERROR;
goto done;
}
}
主要流程基本就这里,当我们需要实现一个source插件时,继承basesrc,并重写其几个关键函数就可以了。整个流程还是basesrc把控,重写几个关键挂件函数,完成数据获取等功能。
gstreamer媒体插件都是此架构,base类实现了相关功能的逻辑控制,子类通过重写相关接口,就可以实现对应功能,大大缩减了开发难度,例如 sink,parse,decoder,filter等模块
filesrc例子:
有几个属性可以看下:
(1)PROP_NUM_BUFFERS:Number of buffers to output before sending EOS
gst_base_src_get_range中逻辑,限制buffer数量;
/* normally we don't count buffers */
if (G_UNLIKELY (src->num_buffers_left >= 0)) {
if (src->num_buffers_left == 0)
goto reached_num_buffers;
else
src->num_buffers_left--;
}
(2)DEFAULT_BLOCKSIZE:Size in bytes to read per buffer
PUSH模式中每次推送的buffer大小,gst_base_src_loop
blocksize = src->blocksize;
/* if we operate in bytes, we can calculate an offset */
if (src->segment.format == GST_FORMAT_BYTES) {
position = src->segment.position;
/* for negative rates, start with subtracting the blocksize */
if (src->segment.rate < 0.0) {
/* we cannot go below segment.start */
if (position > src->segment.start + blocksize)
position -= blocksize;
else {
/* last block, remainder up to segment.start */
blocksize = position - src->segment.start;
position = src->segment.start;
}
}
} else
position = -1;
GST_LOG_OBJECT (src, "next_ts %" GST_TIME_FORMAT " size %u",
GST_TIME_ARGS (position), blocksize);
/* clean up just in case we got interrupted or so last time round */
if (src->priv->pending_bufferlist != NULL) {
gst_buffer_list_unref (src->priv->pending_bufferlist);
src->priv->pending_bufferlist = NULL;
}
ret = gst_base_src_get_range (src, position, blocksize, &buf);
2.GstPushSrc剖析
GstPushSrc是GstBaseSrc的一个子类,顾名思义仅支持PUSH模式,如CurlHttpSrc、SoupHttpSrc插件。
static void
gst_push_src_class_init (GstPushSrcClass * klass)
{
GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_push_src_create);
gstbasesrc_class->alloc = GST_DEBUG_FUNCPTR (gst_push_src_alloc);
gstbasesrc_class->fill = GST_DEBUG_FUNCPTR (gst_push_src_fill);
gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_push_src_query);
}
实现上没有太大区别,重写相关函数,可以看到GST_QUERY_SCHEDULING仅支持PUSH模式。
static gboolean
gst_push_src_query (GstBaseSrc * src, GstQuery * query)
{
gboolean ret;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_SCHEDULING:
{
/* a pushsrc can by default never operate in pull mode override
* if you want something different. */
gst_query_set_scheduling (query, GST_SCHEDULING_FLAG_SEQUENTIAL, 1, -1,
0);
gst_query_add_scheduling_mode (query, GST_PAD_MODE_PUSH);
ret = TRUE;
break;
}
default:
ret = GST_BASE_SRC_CLASS (parent_class)->query (src, query);
break;
}
return ret;
}
3.filesrc剖析
Source需要实现接口
static void gst_file_src_uri_handler_init (gpointer g_iface,
gpointer iface_data);
#define _do_init \
G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_file_src_uri_handler_init); \
GST_DEBUG_CATEGORY_INIT (gst_file_src_debug, "filesrc", 0, "filesrc element");
#define gst_file_src_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstFileSrc, gst_file_src, GST_TYPE_BASE_SRC, _do_init);
GST_ELEMENT_REGISTER_DEFINE (filesrc, "filesrc", GST_RANK_PRIMARY,
GST_TYPE_FILE_SRC);
static void
gst_file_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
{
GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
iface->get_type = gst_file_src_uri_get_type;
iface->get_protocols = gst_file_src_uri_get_protocols;
iface->get_uri = gst_file_src_uri_get_uri;
iface->set_uri = gst_file_src_uri_set_uri;
}
/*** GSTURIHANDLER INTERFACE *************************************************/
static GstURIType
gst_file_src_uri_get_type (GType type)
{
return GST_URI_SRC;
}
static const gchar *const *
gst_file_src_uri_get_protocols (GType type)
{
static const gchar *protocols[] = { "file", NULL };
return protocols;
}
static gchar *
gst_file_src_uri_get_uri (GstURIHandler * handler)
{
GstFileSrc *src = GST_FILE_SRC (handler);
/* FIXME: make thread-safe */
return g_strdup (src->uri);
}
static gboolean
gst_file_src_uri_set_uri (GstURIHandler * handler, const gchar * uri,
GError ** err)
{
gchar *location, *hostname = NULL;
gboolean ret = FALSE;
GstFileSrc *src = GST_FILE_SRC (handler);
if (strcmp (uri, "file://") == 0) {
/* Special case for "file://" as this is used by some applications
* to test with gst_element_make_from_uri if there's an element
* that supports the URI protocol. */
gst_file_src_set_location (src, NULL, NULL);
return TRUE;
}
location = g_filename_from_uri (uri, &hostname, err);
if (!location || (err != NULL && *err != NULL)) {
GST_WARNING_OBJECT (src, "Invalid URI '%s' for filesrc: %s", uri,
(err != NULL && *err != NULL) ? (*err)->message : "unknown error");
goto beach;
}
if ((hostname) && (strcmp (hostname, "localhost"))) {
/* Only 'localhost' is permitted */
GST_WARNING_OBJECT (src, "Invalid hostname '%s' for filesrc", hostname);
g_set_error (err, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
"File URI with invalid hostname '%s'", hostname);
goto beach;
}
#ifdef G_OS_WIN32
/* Unfortunately, g_filename_from_uri() doesn't handle some UNC paths
* correctly on windows, it leaves them with an extra backslash
* at the start if they're of the mozilla-style file:/host/path/file
* form. Correct this.
*/
if (location[0] == '\\' && location[1] == '\\' && location[2] == '\\')
memmove (location, location + 1, strlen (location + 1) + 1);
#endif
ret = gst_file_src_set_location (src, location, err);
beach:
if (location)
g_free (location);
if (hostname)
g_free (hostname);
return ret;
}