/* GStreamer * Copyright (C) 2009 Axis Communications * @author Jonas Holmberg * * gstbufferlist.c: Buffer list * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /** * SECTION:gstbufferlist * @short_description: Grouped scatter data buffer type for data-passing * @see_also: #GstPad, #GstMiniObject * * Buffer lists are units of grouped scatter/gather data transfer in * GStreamer. * * Buffer lists are created with gst_buffer_list_new() and filled with data * using a #GstBufferListIterator. The iterator has no current buffer; its * cursor position lies between buffers, immediately before the buffer that * would be returned by gst_buffer_list_iterator_next(). After iterating to the * end of a group the iterator must be advanced to the next group by a call to * gst_buffer_list_iterator_next_group() before any further calls to * gst_buffer_list_iterator_next() can return buffers again. The cursor position * of a newly created iterator lies before the first group; a call to * gst_buffer_list_iterator_next_group() is necessary before calls to * gst_buffer_list_iterator_next() can return buffers. * * * * +--- group0 ----------------------+--- group1 ------------+ * | buffer0 buffer1 buffer2 | buffer3 buffer4 | * ^ ^ ^ ^ ^ ^ ^ ^ * Iterator positions * * * * The gst_buffer_list_iterator_remove(), gst_buffer_list_iterator_steal(), * gst_buffer_list_iterator_take(), gst_buffer_list_iterator_do() and * gst_buffer_list_iterator_do_data() functions are not defined in terms of the * cursor position; they operate on the last element returned from * gst_buffer_list_iterator_next(). * * The basic use pattern of creating a buffer list with an iterator is as * follows: * * * Creating a buffer list * * GstBufferList *list; * GstBufferListIterator *it; * * list = gst_buffer_list_new (); * it = gst_buffer_list_iterate (list); * gst_buffer_list_iterator_add_group (it); * gst_buffer_list_iterator_add (it, header1); * gst_buffer_list_iterator_add (it, data1); * gst_buffer_list_iterator_add_group (it); * gst_buffer_list_iterator_add (it, header2); * gst_buffer_list_iterator_add (it, data2); * gst_buffer_list_iterator_add_group (it); * gst_buffer_list_iterator_add (it, header3); * gst_buffer_list_iterator_add (it, data3); * ... * gst_buffer_list_iterator_free (it); * * * * The basic use pattern of iterating over a buffer list is as follows: * * * Iterating a buffer list * * GstBufferListIterator *it; * * it = gst_buffer_list_iterate (list); * while (gst_buffer_list_iterator_next_group (it)) { * while ((buffer = gst_buffer_list_iterator_next (it)) != NULL) { * do_something_with_buffer (buffer); * } * } * gst_buffer_list_iterator_free (it); * * * * The basic use pattern of modifying a buffer in a list is as follows: * * * Modifying the data of the first buffer in a list * * GstBufferListIterator *it; * * list = gst_buffer_list_make_writable (list); * it = gst_buffer_list_iterate (list); * if (gst_buffer_list_iterator_next_group (it)) { * GstBuffer *buf * * buf = gst_buffer_list_iterator_next (it); * if (buf != NULL) { * buf = gst_buffer_list_iterator_do (it, * (GstBufferListDoFunction) gst_mini_object_make_writable); * modify_data (GST_BUFFER_DATA (buf)); * } * } * gst_buffer_list_iterator_free (it); * * * * Since: 0.10.24 */ #include "gst_private.h" #include "gstbuffer.h" #include "gstbufferlist.h" #define GST_CAT_DEFAULT GST_CAT_BUFFER_LIST #define GROUP_START NULL static const gpointer STOLEN = ""; /** * GstBufferListIterator: * * Iterator for a #GstBufferList. */ struct _GstBufferListIterator { GstBufferList *list; GList *next; GList *last_returned; }; static GType _gst_buffer_list_type = 0; static GstMiniObjectClass *parent_class = NULL; void _gst_buffer_list_initialize (void) { g_type_class_ref (gst_buffer_list_get_type ()); } static void gst_buffer_list_init (GTypeInstance * instance, gpointer g_class) { GstBufferList *list; list = (GstBufferList *) instance; list->buffers = NULL; GST_LOG ("init %p", list); } static void gst_buffer_list_finalize (GstBufferList * list) { GList *tmp; g_return_if_fail (list != NULL); GST_LOG ("finalize %p", list); tmp = list->buffers; while (tmp) { if (tmp->data != GROUP_START && tmp->data != STOLEN) { gst_buffer_unref (GST_BUFFER_CAST (tmp->data)); } tmp = tmp->next; } g_list_free (list->buffers); parent_class->finalize (GST_MINI_OBJECT_CAST (list)); } static GstBufferList * _gst_buffer_list_copy (GstBufferList * list) { GstBufferList *list_copy; GList *tmp; g_return_val_if_fail (list != NULL, NULL); list_copy = gst_buffer_list_new (); /* shallow copy of list and pointers */ list_copy->buffers = g_list_copy (list->buffers); /* ref all buffers in the list */ tmp = list_copy->buffers; while (tmp) { if (tmp->data != GROUP_START && tmp->data != STOLEN) { tmp->data = gst_buffer_ref (GST_BUFFER_CAST (tmp->data)); } tmp = g_list_next (tmp); } return list_copy; } static void gst_buffer_list_class_init (gpointer g_class, gpointer class_data) { GstBufferListClass *list_class = GST_BUFFER_LIST_CLASS (g_class); parent_class = g_type_class_peek_parent (g_class); list_class->mini_object_class.copy = (GstMiniObjectCopyFunction) _gst_buffer_list_copy; list_class->mini_object_class.finalize = (GstMiniObjectFinalizeFunction) gst_buffer_list_finalize; } /** * gst_buffer_list_new: * * Creates a new, empty #GstBufferList. The caller is responsible for unreffing * the returned #GstBufferList. * * Returns: the new #GstBufferList. gst_buffer_list_unref() after usage. */ GstBufferList * gst_buffer_list_new (void) { GstBufferList *list; list = (GstBufferList *) gst_mini_object_new (_gst_buffer_list_type); GST_LOG ("new %p", list); return list; } /** * gst_buffer_list_n_groups: * @list: a #GstBufferList * * Returns the number of groups in @list. * * Returns: the number of groups in the buffer list */ guint gst_buffer_list_n_groups (GstBufferList * list) { GList *tmp; guint n; g_return_val_if_fail (list != NULL, 0); tmp = list->buffers; n = 0; while (tmp) { if (tmp->data == GROUP_START) { n++; } tmp = g_list_next (tmp); } return n; } GType gst_buffer_list_get_type (void) { if (G_UNLIKELY (_gst_buffer_list_type == 0)) { static const GTypeInfo buffer_list_info = { sizeof (GstBufferListClass), NULL, NULL, gst_buffer_list_class_init, NULL, NULL, sizeof (GstBufferList), 0, gst_buffer_list_init, NULL }; _gst_buffer_list_type = g_type_register_static (GST_TYPE_MINI_OBJECT, "GstBufferList", &buffer_list_info, 0); } return _gst_buffer_list_type; } /** * gst_buffer_list_iterate: * @list: a #GstBufferList * * Iterate the buffers in @list. The owner of the iterator must also be the * owner of a reference to @list while the returned iterator is in use. * * Returns: a new #GstBufferListIterator of the buffers in @list. * gst_buffer_list_iterator_free() after usage */ GstBufferListIterator * gst_buffer_list_iterate (GstBufferList * list) { GstBufferListIterator *it; g_return_val_if_fail (list != NULL, NULL); it = g_slice_new (GstBufferListIterator); it->list = list; it->next = list->buffers; it->last_returned = NULL; return it; } /** * gst_buffer_list_iterator_free: * @it: the #GstBufferListIterator to free * * Free the iterator. */ void gst_buffer_list_iterator_free (GstBufferListIterator * it) { g_return_if_fail (it != NULL); g_slice_free (GstBufferListIterator, it); } /** * gst_buffer_list_iterator_n_buffers: * @it: a #GstBufferListIterator * * Returns the number of buffers left to iterate in the current group. I.e. the * number of calls that can be made to gst_buffer_list_iterator_next() before * it returns NULL. * * This function will not move the implicit cursor or in any other way affect * the state of the iterator @it. * * Returns: the number of buffers left to iterate in the current group */ guint gst_buffer_list_iterator_n_buffers (const GstBufferListIterator * it) { GList *tmp; guint n; g_return_val_if_fail (it != NULL, 0); tmp = it->next; n = 0; while (tmp && tmp->data != GROUP_START) { if (tmp->data != STOLEN) { n++; } tmp = g_list_next (tmp); } return n; } /** * gst_buffer_list_iterator_add: * @it: a #GstBufferListIterator * @buffer: a #GstBuffer * * Inserts @buffer into the #GstBufferList iterated with @it. The buffer is * inserted into the current group, immediately before the buffer that would be * returned by gst_buffer_list_iterator_next(). The buffer is inserted before * the implicit cursor, a subsequent call to gst_buffer_list_iterator_next() * will return the buffer after the inserted buffer, if any. * * This function takes ownership of @buffer. */ void gst_buffer_list_iterator_add (GstBufferListIterator * it, GstBuffer * buffer) { g_return_if_fail (it != NULL); g_return_if_fail (buffer != NULL); /* adding before the first group start is not allowed */ g_return_if_fail (it->next != it->list->buffers); /* cheap insert into the GList */ it->list->buffers = g_list_insert_before (it->list->buffers, it->next, buffer); } /** * gst_buffer_list_iterator_add_group: * @it: a #GstBufferListIterator * * Inserts a new, empty group into the #GstBufferList iterated with @it. The * group is inserted immediately before the group that would be returned by * gst_buffer_list_iterator_next_group(). A subsequent call to * gst_buffer_list_iterator_next_group() will advance the iterator to the group * after the inserted group, if any. */ void gst_buffer_list_iterator_add_group (GstBufferListIterator * it) { g_return_if_fail (it != NULL); /* advance iterator to next group start */ while (it->next != NULL && it->next->data != GROUP_START) { it->next = g_list_next (it->next); } /* cheap insert of a group start into the GList */ it->list->buffers = g_list_insert_before (it->list->buffers, it->next, GROUP_START); } /** * gst_buffer_list_iterator_next: * @it: a #GstBufferListIterator * * Returns the next buffer in the list iterated with @it. If the iterator is at * the end of a group, NULL will be returned. This function may be called * repeatedly to iterate through the current group. * * The caller will not get a new ref to the returned #GstBuffer and must not * unref it. * * Returns: the next buffer in the current group of the buffer list, or NULL */ GstBuffer * gst_buffer_list_iterator_next (GstBufferListIterator * it) { GstBuffer *buffer; g_return_val_if_fail (it != NULL, NULL); while (it->next != NULL && it->next->data != GROUP_START && it->next->data == STOLEN) { it->next = g_list_next (it->next); } if (it->next == NULL || it->next->data == GROUP_START) { goto no_buffer; } buffer = GST_BUFFER_CAST (it->next->data); it->last_returned = it->next; it->next = g_list_next (it->next); return buffer; no_buffer: it->last_returned = NULL; return NULL; } /** * gst_buffer_list_iterator_next_group: * @it: a #GstBufferListIterator * * Advance the iterator @it to the first buffer in the next group. If the * iterator is at the last group, FALSE will be returned. This function may be * called repeatedly to iterate through the groups in a buffer list. * * Returns: TRUE if the iterator could be advanced to the next group, FALSE if * the iterator was already at the last group */ gboolean gst_buffer_list_iterator_next_group (GstBufferListIterator * it) { g_return_val_if_fail (it != NULL, FALSE); /* advance iterator to next group start */ while (it->next != NULL && it->next->data != GROUP_START) { it->next = g_list_next (it->next); } if (it->next) { /* move one step beyond the group start */ it->next = g_list_next (it->next); } it->last_returned = NULL; return (it->next != NULL); } /** * gst_buffer_list_iterator_remove: * @it: a #GstBufferListIterator * * Removes the last buffer returned by gst_buffer_list_iterator_next() from * the #GstBufferList iterated with @it. gst_buffer_list_iterator_next() must * have been called on @it before this function is called. This function can * only be called once per call to gst_buffer_list_iterator_next(). * * The removed buffer is unreffed. */ void gst_buffer_list_iterator_remove (GstBufferListIterator * it) { g_return_if_fail (it != NULL); g_return_if_fail (it->last_returned != NULL); g_assert (it->last_returned->data != GROUP_START); if (it->last_returned->data != STOLEN) { gst_buffer_unref (it->last_returned->data); } it->list->buffers = g_list_delete_link (it->list->buffers, it->last_returned); it->last_returned = NULL; } /** * gst_buffer_list_iterator_take: * @it: a #GstBufferListIterator * @buffer: a #GstBuffer * * Replaces the last buffer returned by gst_buffer_list_iterator_next() with * @buffer in the #GstBufferList iterated with @it and takes ownership of * @buffer. gst_buffer_list_iterator_next() must have been called on @it before * this function is called. gst_buffer_list_iterator_remove() must not have been * called since the last call to gst_buffer_list_iterator_next(). * * This function unrefs the replaced buffer if it has not been stolen with * gst_buffer_list_iterator_steal() and takes ownership of @buffer (i.e. the * refcount of @buffer is not increased). */ void gst_buffer_list_iterator_take (GstBufferListIterator * it, GstBuffer * buffer) { g_return_if_fail (it != NULL); g_return_if_fail (it->last_returned != NULL); g_return_if_fail (buffer != NULL); g_assert (it->last_returned->data != GROUP_START); if (it->last_returned->data != STOLEN) { gst_buffer_unref (it->last_returned->data); } it->last_returned->data = buffer; } /** * gst_buffer_list_iterator_steal: * @it: a #GstBufferListIterator * * Returns the last buffer returned by gst_buffer_list_iterator_next() without * modifying the refcount of the buffer. * * Returns: the last buffer returned by gst_buffer_list_iterator_next() */ GstBuffer * gst_buffer_list_iterator_steal (GstBufferListIterator * it) { GstBuffer *buffer; g_return_val_if_fail (it != NULL, NULL); g_return_val_if_fail (it->last_returned != NULL, NULL); g_return_val_if_fail (it->last_returned->data != STOLEN, NULL); g_assert (it->last_returned->data != GROUP_START); buffer = it->last_returned->data; it->last_returned->data = STOLEN; return buffer; } /** * gst_buffer_list_iterator_do_data: * @it: a #GstBufferListIterator * @do_func: the function to be called * @data: the gpointer to optional user data. * @data_notify: function to be called when @data is no longer used * * Calls the given function for the last buffer returned by * gst_buffer_list_iterator_next(). gst_buffer_list_iterator_next() must have * been called on @it before this function is called. * gst_buffer_list_iterator_remove() and gst_buffer_list_iterator_steal() must * not have been called since the last call to gst_buffer_list_iterator_next(). * * See #GstBufferListDoFunction for more details. * * The @data_notify function is called after @do_func has returned, before this * function returns, usually used to free @data. * * Returns: the return value from @do_func */ GstBuffer * gst_buffer_list_iterator_do_data (GstBufferListIterator * it, GstBufferListDoDataFunction do_func, gpointer data, GDestroyNotify data_notify) { GstBuffer *buffer; g_return_val_if_fail (it != NULL, NULL); g_return_val_if_fail (it->last_returned != NULL, NULL); g_return_val_if_fail (it->last_returned->data != STOLEN, NULL); g_return_val_if_fail (do_func != NULL, NULL); g_return_val_if_fail (gst_buffer_list_is_writable (it->list), NULL); g_assert (it->last_returned->data != GROUP_START); buffer = gst_buffer_list_iterator_steal (it); buffer = do_func (buffer, data); if (buffer == NULL) { gst_buffer_list_iterator_remove (it); } else { gst_buffer_list_iterator_take (it, buffer); } if (data_notify != NULL) { data_notify (data); } return buffer; } static GstBuffer * do_func_no_data (GstBuffer * buffer, GstBufferListDoFunction do_func) { return do_func (buffer); } /** * gst_buffer_list_iterator_do: * @it: a #GstBufferListIterator * @do_func: the function to be called * * Calls the given function for the last buffer returned by * gst_buffer_list_iterator_next(). gst_buffer_list_iterator_next() must have * been called on @it before this function is called. * gst_buffer_list_iterator_remove() or gst_buffer_list_iterator_steal() must * not have been called since the last call to gst_buffer_list_iterator_next(). * * See #GstBufferListDoFunction for more details. * * Returns: the return value from @do_func */ GstBuffer * gst_buffer_list_iterator_do (GstBufferListIterator * it, GstBufferListDoFunction do_func) { g_return_val_if_fail (it != NULL, NULL); g_return_val_if_fail (it->last_returned != NULL, NULL); g_return_val_if_fail (it->last_returned->data != STOLEN, NULL); g_return_val_if_fail (do_func != NULL, NULL); g_return_val_if_fail (gst_buffer_list_is_writable (it->list), NULL); g_assert (it->last_returned->data != GROUP_START); return gst_buffer_list_iterator_do_data (it, (GstBufferListDoDataFunction) do_func_no_data, do_func, NULL); } /** * gst_buffer_list_iterator_merge_group: * @it: a #GstBufferListIterator * * Merge a buffer list group into a normal #GstBuffer by copying its metadata * and memcpying its data into consecutive memory. All buffers in the current * group after the implicit cursor will be merged into one new buffer. The * metadata of the new buffer will be a copy of the metadata of the buffer that * would be returned by gst_buffer_list_iterator_next(). If there is no buffer * in the current group after the implicit cursor, NULL will be returned. * * This function will not move the implicit cursor or in any other way affect * the state of the iterator @it or the list. * * Returns: a new #GstBuffer, gst_buffer_unref() after usage, or NULL */ GstBuffer * gst_buffer_list_iterator_merge_group (const GstBufferListIterator * it) { GList *tmp; guint size; GstBuffer *buf; guint8 *ptr; g_return_val_if_fail (it != NULL, NULL); /* calculate size of merged buffer */ size = 0; tmp = it->next; while (tmp && tmp->data != GROUP_START) { if (tmp->data != STOLEN) { size += GST_BUFFER_SIZE (tmp->data); } tmp = g_list_next (tmp); } if (size == 0) { return NULL; } /* allocate a new buffer */ buf = gst_buffer_new_and_alloc (size); /* copy metadata from the next buffer after the implicit cursor */ gst_buffer_copy_metadata (buf, GST_BUFFER_CAST (it->next->data), GST_BUFFER_COPY_ALL); /* copy data of all buffers before the next group start into the new buffer */ ptr = GST_BUFFER_DATA (buf); tmp = it->next; do { if (tmp->data != STOLEN) { memcpy (ptr, GST_BUFFER_DATA (tmp->data), GST_BUFFER_SIZE (tmp->data)); ptr += GST_BUFFER_SIZE (tmp->data); } tmp = g_list_next (tmp); } while (tmp && tmp->data != GROUP_START); return buf; }