| /* GStreamer |
| * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> |
| * 2000 Wim Taymans <wtay@chello.be> |
| * |
| * gstfdsink.c: |
| * |
| * 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:element-fdsink |
| * @short_description: write to a unix file descriptor |
| * @see_also: #GstFdSrc |
| * |
| * Write data to a unix file descriptor. |
| * |
| * This element will sycnhronize on the clock before writing the data on the |
| * socket. For file descriptors where this does not make sense (files, ...) the |
| * ::sync property can be used to disable synchronisation. |
| * |
| * Last reviewed on 2006-04-28 (0.10.6) |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include "../../gst/gst-i18n-lib.h" |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/socket.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| #ifdef _MSC_VER |
| #include <io.h> |
| #endif |
| #include <errno.h> |
| #include <string.h> |
| |
| #include "gstfdsink.h" |
| |
| /* We add a control socket as in fdsrc to make it shutdown quickly when it's blocking on the fd. |
| * Select is used to determine when the fd is ready for use. When the element state is changed, |
| * it happens from another thread while fdsink is select'ing on the fd. The state-change thread |
| * sends a control message, so fdsink wakes up and changes state immediately otherwise |
| * it would stay blocked until it receives some data. */ |
| |
| /* the select call is also performed on the control sockets, that way |
| * we can send special commands to unblock the select call */ |
| #define CONTROL_STOP 'S' /* stop the select call */ |
| #define CONTROL_SOCKETS(sink) sink->control_sock |
| #define WRITE_SOCKET(sink) sink->control_sock[1] |
| #define READ_SOCKET(sink) sink->control_sock[0] |
| |
| #define SEND_COMMAND(sink, command) \ |
| G_STMT_START { \ |
| unsigned char c; c = command; \ |
| write (WRITE_SOCKET(sink), &c, 1); \ |
| } G_STMT_END |
| |
| #define READ_COMMAND(sink, command, res) \ |
| G_STMT_START { \ |
| res = read(READ_SOCKET(sink), &command, 1); \ |
| } G_STMT_END |
| |
| static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS_ANY); |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_fd_sink__debug); |
| #define GST_CAT_DEFAULT gst_fd_sink__debug |
| |
| static const GstElementDetails gst_fd_sink__details = |
| GST_ELEMENT_DETAILS ("Filedescriptor Sink", |
| "Sink/File", |
| "Write data to a file descriptor", |
| "Erik Walthinsen <omega@cse.ogi.edu>"); |
| |
| |
| /* FdSink signals and args */ |
| enum |
| { |
| /* FILL ME */ |
| LAST_SIGNAL |
| }; |
| |
| enum |
| { |
| ARG_0, |
| ARG_FD |
| }; |
| |
| static void gst_fd_sink_uri_handler_init (gpointer g_iface, |
| gpointer iface_data); |
| |
| static void |
| _do_init (GType gst_fd_sink_type) |
| { |
| static const GInterfaceInfo urihandler_info = { |
| gst_fd_sink_uri_handler_init, |
| NULL, |
| NULL |
| }; |
| |
| g_type_add_interface_static (gst_fd_sink_type, GST_TYPE_URI_HANDLER, |
| &urihandler_info); |
| |
| GST_DEBUG_CATEGORY_INIT (gst_fd_sink__debug, "fdsink", 0, "fdsink element"); |
| } |
| |
| GST_BOILERPLATE_FULL (GstFdSink, gst_fd_sink, GstBaseSink, GST_TYPE_BASE_SINK, |
| _do_init); |
| |
| static void gst_fd_sink_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_fd_sink_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| static void gst_fd_sink_dispose (GObject * obj); |
| |
| static gboolean gst_fd_sink_query (GstPad * pad, GstQuery * query); |
| static GstFlowReturn gst_fd_sink_render (GstBaseSink * sink, |
| GstBuffer * buffer); |
| static gboolean gst_fd_sink_start (GstBaseSink * basesink); |
| static gboolean gst_fd_sink_stop (GstBaseSink * basesink); |
| static gboolean gst_fd_sink_unlock (GstBaseSink * basesink); |
| |
| static void |
| gst_fd_sink_base_init (gpointer g_class) |
| { |
| GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); |
| |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&sinktemplate)); |
| gst_element_class_set_details (gstelement_class, &gst_fd_sink__details); |
| } |
| |
| static void |
| gst_fd_sink_class_init (GstFdSinkClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstBaseSinkClass *gstbasesink_class; |
| |
| gobject_class = G_OBJECT_CLASS (klass); |
| gstbasesink_class = GST_BASE_SINK_CLASS (klass); |
| |
| gobject_class->set_property = gst_fd_sink_set_property; |
| gobject_class->get_property = gst_fd_sink_get_property; |
| gobject_class->dispose = gst_fd_sink_dispose; |
| |
| gstbasesink_class->get_times = NULL; |
| gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_fd_sink_render); |
| gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_fd_sink_start); |
| gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_fd_sink_stop); |
| gstbasesink_class->unlock = GST_DEBUG_FUNCPTR (gst_fd_sink_unlock); |
| gstbasesink_class->event = NULL; |
| |
| g_object_class_install_property (gobject_class, ARG_FD, |
| g_param_spec_int ("fd", "fd", "An open file descriptor to write to", |
| 0, G_MAXINT, 1, G_PARAM_READWRITE)); |
| } |
| |
| static void |
| gst_fd_sink_init (GstFdSink * fdsink, GstFdSinkClass * klass) |
| { |
| GstPad *pad; |
| |
| pad = GST_BASE_SINK_PAD (fdsink); |
| gst_pad_set_query_function (pad, GST_DEBUG_FUNCPTR (gst_fd_sink_query)); |
| |
| fdsink->fd = 1; |
| fdsink->uri = g_strdup_printf ("fd://%d", fdsink->fd); |
| fdsink->bytes_written = 0; |
| |
| GST_BASE_SINK (fdsink)->sync = TRUE; |
| } |
| |
| static void |
| gst_fd_sink_dispose (GObject * obj) |
| { |
| GstFdSink *fdsink = GST_FD_SINK (obj); |
| |
| g_free (fdsink->uri); |
| fdsink->uri = NULL; |
| |
| G_OBJECT_CLASS (parent_class)->dispose (obj); |
| } |
| |
| static gboolean |
| gst_fd_sink_query (GstPad * pad, GstQuery * query) |
| { |
| GstFdSink *fdsink; |
| GstFormat format; |
| |
| fdsink = GST_FD_SINK (GST_PAD_PARENT (pad)); |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_POSITION: |
| gst_query_parse_position (query, &format, NULL); |
| switch (format) { |
| case GST_FORMAT_DEFAULT: |
| case GST_FORMAT_BYTES: |
| gst_query_set_position (query, GST_FORMAT_BYTES, |
| fdsink->bytes_written); |
| return TRUE; |
| default: |
| return FALSE; |
| } |
| |
| case GST_QUERY_FORMATS: |
| gst_query_set_formats (query, 2, GST_FORMAT_DEFAULT, GST_FORMAT_BYTES); |
| return TRUE; |
| |
| default: |
| return gst_pad_query_default (pad, query); |
| } |
| } |
| |
| static GstFlowReturn |
| gst_fd_sink_render (GstBaseSink * sink, GstBuffer * buffer) |
| { |
| GstFdSink *fdsink; |
| |
| #ifndef HAVE_WIN32 |
| fd_set readfds; |
| fd_set writefds; |
| gint retval; |
| #endif |
| guint8 *data; |
| guint size; |
| gint written; |
| |
| fdsink = GST_FD_SINK (sink); |
| |
| g_return_val_if_fail (fdsink->fd >= 0, GST_FLOW_ERROR); |
| |
| data = GST_BUFFER_DATA (buffer); |
| size = GST_BUFFER_SIZE (buffer); |
| |
| again: |
| #ifndef HAVE_WIN32 |
| |
| FD_ZERO (&readfds); |
| FD_SET (READ_SOCKET (fdsink), &readfds); |
| |
| FD_ZERO (&writefds); |
| FD_SET (fdsink->fd, &writefds); |
| |
| do { |
| GST_DEBUG_OBJECT (fdsink, "going into select, have %d bytes to write", |
| size); |
| retval = select (FD_SETSIZE, &readfds, &writefds, NULL, NULL); |
| } while ((retval == -1 && errno == EINTR)); |
| |
| if (retval == -1) |
| goto select_error; |
| |
| if (FD_ISSET (READ_SOCKET (fdsink), &readfds)) { |
| /* read all stop commands */ |
| while (TRUE) { |
| gchar command; |
| int res; |
| |
| READ_COMMAND (fdsink, command, res); |
| if (res < 0) { |
| GST_LOG_OBJECT (fdsink, "no more commands"); |
| /* no more commands */ |
| break; |
| } |
| } |
| goto stopped; |
| } |
| #endif |
| |
| GST_DEBUG_OBJECT (fdsink, "writing %d bytes to file descriptor %d", size, |
| fdsink->fd); |
| |
| written = write (fdsink->fd, data, size); |
| |
| /* check for errors */ |
| if (G_UNLIKELY (written < 0)) { |
| /* try to write again on non-fatal errors */ |
| if (errno == EAGAIN || errno == EINTR) |
| goto again; |
| |
| /* else go to our error handler */ |
| goto write_error; |
| } |
| |
| /* all is fine when we get here */ |
| size -= written; |
| data += written; |
| fdsink->bytes_written += written; |
| |
| GST_DEBUG_OBJECT (fdsink, "wrote %d bytes, %d left", written, size); |
| |
| /* short write, select and try to write the remainder */ |
| if (G_UNLIKELY (size > 0)) |
| goto again; |
| |
| return GST_FLOW_OK; |
| |
| #ifndef HAVE_WIN32 |
| select_error: |
| { |
| GST_ELEMENT_ERROR (fdsink, RESOURCE, READ, (NULL), |
| ("select on file descriptor: %s.", g_strerror (errno))); |
| GST_DEBUG_OBJECT (fdsink, "Error during select"); |
| return GST_FLOW_ERROR; |
| } |
| stopped: |
| { |
| GST_DEBUG_OBJECT (fdsink, "Select stopped"); |
| return GST_FLOW_WRONG_STATE; |
| } |
| #endif |
| |
| write_error: |
| { |
| switch (errno) { |
| case ENOSPC: |
| GST_ELEMENT_ERROR (fdsink, RESOURCE, NO_SPACE_LEFT, (NULL), (NULL)); |
| break; |
| default:{ |
| GST_ELEMENT_ERROR (fdsink, RESOURCE, WRITE, |
| (_("Error while writing to file descriptor \"%d\"."), fdsink->fd), |
| ("%s", g_strerror (errno))); |
| } |
| } |
| return GST_FLOW_ERROR; |
| } |
| } |
| |
| static gboolean |
| gst_fd_sink_check_fd (GstFdSink * fdsink, int fd) |
| { |
| struct stat stat_results; |
| off_t result; |
| |
| /* see that it is a valid file descriptor */ |
| if (fstat (fd, &stat_results) < 0) |
| goto invalid; |
| |
| if (!S_ISREG (stat_results.st_mode)) |
| goto not_seekable; |
| |
| /* see if it is a seekable stream */ |
| result = lseek (fd, 0, SEEK_CUR); |
| if (result == -1) { |
| switch (errno) { |
| case EINVAL: |
| case EBADF: |
| goto invalid; |
| |
| case ESPIPE: |
| goto not_seekable; |
| } |
| } else |
| GST_DEBUG_OBJECT (fdsink, "File descriptor \"%d\" is seekable", fd); |
| |
| return TRUE; |
| |
| invalid: |
| { |
| GST_ELEMENT_ERROR (fdsink, RESOURCE, WRITE, |
| (_("File descriptor \"%d\" is not valid."), fd), |
| ("%s", g_strerror (errno))); |
| return FALSE; |
| } |
| not_seekable: |
| { |
| GST_DEBUG_OBJECT (fdsink, "File descriptor \"%d\" is a pipe", fd); |
| return TRUE; |
| } |
| } |
| |
| static gboolean |
| gst_fd_sink_start (GstBaseSink * basesink) |
| { |
| GstFdSink *fdsink; |
| gint control_sock[2]; |
| |
| fdsink = GST_FD_SINK (basesink); |
| if (!gst_fd_sink_check_fd (fdsink, fdsink->fd)) |
| return FALSE; |
| |
| if (socketpair (PF_UNIX, SOCK_STREAM, 0, control_sock) < 0) |
| goto socket_pair; |
| |
| READ_SOCKET (fdsink) = control_sock[0]; |
| WRITE_SOCKET (fdsink) = control_sock[1]; |
| |
| fcntl (READ_SOCKET (fdsink), F_SETFL, O_NONBLOCK); |
| fcntl (WRITE_SOCKET (fdsink), F_SETFL, O_NONBLOCK); |
| |
| return TRUE; |
| |
| /* ERRORS */ |
| socket_pair: |
| { |
| GST_ELEMENT_ERROR (fdsink, RESOURCE, OPEN_READ_WRITE, (NULL), |
| GST_ERROR_SYSTEM); |
| return FALSE; |
| } |
| } |
| |
| static gboolean |
| gst_fd_sink_stop (GstBaseSink * basesink) |
| { |
| GstFdSink *fdsink = GST_FD_SINK (basesink); |
| |
| close (READ_SOCKET (fdsink)); |
| close (WRITE_SOCKET (fdsink)); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_fd_sink_unlock (GstBaseSink * basesink) |
| { |
| GstFdSink *fdsink = GST_FD_SINK (basesink); |
| |
| SEND_COMMAND (fdsink, CONTROL_STOP); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_fd_sink_update_fd (GstFdSink * fdsink, int new_fd) |
| { |
| if (new_fd < 0) |
| return FALSE; |
| |
| if (!gst_fd_sink_check_fd (fdsink, new_fd)) |
| goto invalid; |
| |
| /* assign the fd */ |
| GST_OBJECT_LOCK (fdsink); |
| fdsink->fd = new_fd; |
| g_free (fdsink->uri); |
| fdsink->uri = g_strdup_printf ("fd://%d", fdsink->fd); |
| GST_OBJECT_UNLOCK (fdsink); |
| |
| return TRUE; |
| |
| invalid: |
| { |
| return FALSE; |
| } |
| } |
| |
| static void |
| gst_fd_sink_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstFdSink *fdsink; |
| |
| g_return_if_fail (GST_IS_FD_SINK (object)); |
| |
| fdsink = GST_FD_SINK (object); |
| |
| switch (prop_id) { |
| case ARG_FD:{ |
| int fd; |
| |
| fd = g_value_get_int (value); |
| gst_fd_sink_update_fd (fdsink, fd); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| static void |
| gst_fd_sink_get_property (GObject * object, guint prop_id, GValue * value, |
| GParamSpec * pspec) |
| { |
| GstFdSink *fdsink; |
| |
| g_return_if_fail (GST_IS_FD_SINK (object)); |
| |
| fdsink = GST_FD_SINK (object); |
| |
| switch (prop_id) { |
| case ARG_FD: |
| g_value_set_int (value, fdsink->fd); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /*** GSTURIHANDLER INTERFACE *************************************************/ |
| |
| static guint |
| gst_fd_sink_uri_get_type (void) |
| { |
| return GST_URI_SINK; |
| } |
| static gchar ** |
| gst_fd_sink_uri_get_protocols (void) |
| { |
| static gchar *protocols[] = { "fd", NULL }; |
| |
| return protocols; |
| } |
| static const gchar * |
| gst_fd_sink_uri_get_uri (GstURIHandler * handler) |
| { |
| GstFdSink *sink = GST_FD_SINK (handler); |
| |
| return sink->uri; |
| } |
| |
| static gboolean |
| gst_fd_sink_uri_set_uri (GstURIHandler * handler, const gchar * uri) |
| { |
| gchar *protocol; |
| GstFdSink *sink = GST_FD_SINK (handler); |
| gint fd; |
| |
| protocol = gst_uri_get_protocol (uri); |
| if (strcmp (protocol, "fd") != 0) { |
| g_free (protocol); |
| return FALSE; |
| } |
| g_free (protocol); |
| |
| if (sscanf (uri, "fd://%d", &fd) != 1) |
| return FALSE; |
| |
| return gst_fd_sink_update_fd (sink, fd); |
| } |
| |
| static void |
| gst_fd_sink_uri_handler_init (gpointer g_iface, gpointer iface_data) |
| { |
| GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; |
| |
| iface->get_type = gst_fd_sink_uri_get_type; |
| iface->get_protocols = gst_fd_sink_uri_get_protocols; |
| iface->get_uri = gst_fd_sink_uri_get_uri; |
| iface->set_uri = gst_fd_sink_uri_set_uri; |
| } |