diff --git a/sys/v4l/gstv4lmjpegsink.c b/sys/v4l/gstv4lmjpegsink.c new file mode 100644 index 0000000000..b189430c36 --- /dev/null +++ b/sys/v4l/gstv4lmjpegsink.c @@ -0,0 +1,389 @@ +/* G-Streamer hardware MJPEG video sink plugin + * Copyright (C) 2001 Ronald Bultje + * + * 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. + */ + +#include +#include "v4lmjpegsink_calls.h" + +static GstElementDetails gst_v4lmjpegsink_details = { + "Video (video4linux/MJPEG) sink", + "Sink/Video", + "Writes MJPEG-encoded frames to a zoran MJPEG/video4linux device", + VERSION, + "Ronald Bultje ", + "(C) 2001", +}; + +/* v4lmjpegsink signals and args */ +enum { + SIGNAL_FRAME_DISPLAYED, + SIGNAL_HAVE_SIZE, + LAST_SIGNAL +}; + +enum { + ARG_0, + ARG_NUMBUFS, + ARG_BUFSIZE, + ARG_X_OFFSET, + ARG_Y_OFFSET, + ARG_FRAMES_DISPLAYED, + ARG_FRAME_TIME, +}; + +GST_PADTEMPLATE_FACTORY (sink_template, + "sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_CAPS_NEW ( + "v4lmjpegsink_caps", + "video/jpeg", + "width", GST_PROPS_INT_RANGE (0, G_MAXINT), + "height", GST_PROPS_INT_RANGE (0, G_MAXINT) + ) +) + + +/* init functions */ +static void gst_v4lmjpegsink_class_init (GstV4lMjpegSinkClass *klass); +static void gst_v4lmjpegsink_init (GstV4lMjpegSink *v4lmjpegsink); + +/* the chain of buffers */ +static void gst_v4lmjpegsink_chain (GstPad *pad, + GstBuffer *buf); + +/* get/set gst object functions */ +static void gst_v4lmjpegsink_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gst_v4lmjpegsink_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gst_v4lmjpegsink_close (GstV4lMjpegSink *v4lmjpegsink); +static GstElementStateReturn gst_v4lmjpegsink_change_state (GstElement *element); + +static GstElementClass *parent_class = NULL; +static guint gst_v4lmjpegsink_signals[LAST_SIGNAL] = { 0 }; + + +GType +gst_v4lmjpegsink_get_type (void) +{ + static GType v4lmjpegsink_type = 0; + + if (!v4lmjpegsink_type) { + static const GTypeInfo v4lmjpegsink_info = { + sizeof(GstV4lMjpegSinkClass), + NULL, + NULL, + (GClassInitFunc)gst_v4lmjpegsink_class_init, + NULL, + NULL, + sizeof(GstV4lMjpegSink), + 0, + (GInstanceInitFunc)gst_v4lmjpegsink_init, + }; + v4lmjpegsink_type = g_type_register_static(GST_TYPE_V4LELEMENT, "GstV4lMjpegSink", &v4lmjpegsink_info, 0); + } + return v4lmjpegsink_type; +} + + +static void +gst_v4lmjpegsink_class_init (GstV4lMjpegSinkClass *klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass*)klass; + gstelement_class = (GstElementClass*)klass; + + parent_class = g_type_class_ref (GST_TYPE_V4LELEMENT); + + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_NUMBUFS, + g_param_spec_int("num_buffers","num_buffers","num_buffers", + G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_BUFSIZE, + g_param_spec_int("buffer_size","buffer_size","buffer_size", + G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_X_OFFSET, + g_param_spec_int("x_offset","x_offset","x_offset", + G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_Y_OFFSET, + g_param_spec_int("y_offset","y_offset","y_offset", + G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); + + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_FRAMES_DISPLAYED, + g_param_spec_int("frames_displayed","frames_displayed","frames_displayed", + G_MININT,G_MAXINT,0,G_PARAM_READABLE)); + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_FRAME_TIME, + g_param_spec_int("frame_time","frame_time","frame_time", + G_MININT,G_MAXINT,0,G_PARAM_READABLE)); + + gobject_class->set_property = gst_v4lmjpegsink_set_property; + gobject_class->get_property = gst_v4lmjpegsink_get_property; + + gst_v4lmjpegsink_signals[SIGNAL_FRAME_DISPLAYED] = + g_signal_new ("frame_displayed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstV4lMjpegSinkClass, frame_displayed), NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + gst_v4lmjpegsink_signals[SIGNAL_HAVE_SIZE] = + g_signal_new ("have_size", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstV4lMjpegSinkClass, have_size), NULL, NULL, + gst_marshal_VOID__INT_INT, G_TYPE_NONE, 2, + G_TYPE_UINT, G_TYPE_UINT); + + + gstelement_class->change_state = gst_v4lmjpegsink_change_state; +} + +static void +gst_v4lmjpegsink_newcaps (GstPad *pad, + GstCaps *caps) +{ + GstV4lMjpegSink *v4lmjpegsink; + + v4lmjpegsink = GST_V4LMJPEGSINK (gst_pad_get_parent (pad)); + + v4lmjpegsink->width = gst_caps_get_int (caps, "width"); + v4lmjpegsink->height = gst_caps_get_int (caps, "height"); + + gst_v4lmjpegsink_set_playback(v4lmjpegsink, + v4lmjpegsink->width, v4lmjpegsink->height, + v4lmjpegsink->x_offset, v4lmjpegsink->y_offset, + GST_V4LELEMENT(v4lmjpegsink)->norm, 0); /* TODO: interlacing */ + + g_signal_emit (G_OBJECT (v4lmjpegsink), gst_v4lmjpegsink_signals[SIGNAL_HAVE_SIZE], 0, + v4lmjpegsink->width, v4lmjpegsink->height); +} + + +static void +gst_v4lmjpegsink_init (GstV4lMjpegSink *v4lmjpegsink) +{ + v4lmjpegsink->sinkpad = gst_pad_new_from_template(GST_PADTEMPLATE_GET (sink_template), "sink"); + gst_element_add_pad (GST_ELEMENT (v4lmjpegsink), v4lmjpegsink->sinkpad); + gst_pad_set_chain_function (v4lmjpegsink->sinkpad, gst_v4lmjpegsink_chain); + gst_pad_set_newcaps_function (v4lmjpegsink->sinkpad, gst_v4lmjpegsink_newcaps); + + v4lmjpegsink->clock = gst_clock_get_system(); + gst_clock_register(v4lmjpegsink->clock, GST_OBJECT(v4lmjpegsink)); + + v4lmjpegsink->width = -1; + v4lmjpegsink->height = -1; + + v4lmjpegsink->x_offset = -1; + v4lmjpegsink->y_offset = -1; + + v4lmjpegsink->numbufs = 64; + v4lmjpegsink->bufsize = 256; + + GST_FLAG_SET(v4lmjpegsink, GST_ELEMENT_THREAD_SUGGESTED); +} + + +static void +gst_v4lmjpegsink_chain (GstPad *pad, + GstBuffer *buf) +{ + GstV4lMjpegSink *v4lmjpegsink; + GstClockTimeDiff jitter; + gint num; + + g_return_if_fail (pad != NULL); + g_return_if_fail (GST_IS_PAD (pad)); + g_return_if_fail (buf != NULL); + + v4lmjpegsink = GST_V4LMJPEGSINK (gst_pad_get_parent (pad)); + + if (!GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLUSH)) { + GST_DEBUG (0,"videosink: clock wait: %llu\n", GST_BUFFER_TIMESTAMP(buf)); + + jitter = gst_clock_current_diff(v4lmjpegsink->clock, GST_BUFFER_TIMESTAMP (buf)); + + if (jitter > 500000 || jitter < -500000) + { + GST_DEBUG (0, "jitter: %lld\n", jitter); + gst_clock_set (v4lmjpegsink->clock, GST_BUFFER_TIMESTAMP (buf)); + } + else { + gst_clock_wait(v4lmjpegsink->clock, GST_BUFFER_TIMESTAMP(buf), GST_OBJECT(v4lmjpegsink)); + } + } + + /* check size */ + if (GST_BUFFER_SIZE(buf) > v4lmjpegsink->breq.size) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Buffer too big (%d KB), max. buffersize is %d KB", + GST_BUFFER_SIZE(buf)/1024, v4lmjpegsink->breq.size/1024); + return; + } + + /* put JPEG data to the device */ + gst_v4lmjpegsink_wait_frame(v4lmjpegsink, &num); + memcpy(gst_v4lmjpegsink_get_buffer(v4lmjpegsink, num), + GST_BUFFER_DATA(buf), GST_BUFFER_SIZE(buf)); + gst_v4lmjpegsink_play_frame(v4lmjpegsink, num); + + g_signal_emit(G_OBJECT(v4lmjpegsink),gst_v4lmjpegsink_signals[SIGNAL_FRAME_DISPLAYED],0); + + gst_buffer_unref(buf); +} + + +static void +gst_v4lmjpegsink_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GstV4lMjpegSink *v4lmjpegsink; + + /* it's not null if we got it, but it might not be ours */ + g_return_if_fail (GST_IS_V4LMJPEGSINK (object)); + + v4lmjpegsink = GST_V4LMJPEGSINK (object); + + switch (prop_id) + { + case ARG_NUMBUFS: + v4lmjpegsink->numbufs = g_value_get_int(value); + break; + case ARG_BUFSIZE: + v4lmjpegsink->bufsize = g_value_get_int(value); + break; + case ARG_X_OFFSET: + v4lmjpegsink->x_offset = g_value_get_int(value); + break; + case ARG_Y_OFFSET: + v4lmjpegsink->y_offset = g_value_get_int(value); + break; + default: + parent_class->set_property(object, prop_id, value, pspec); + break; + } +} + + +static void +gst_v4lmjpegsink_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GstV4lMjpegSink *v4lmjpegsink; + + /* it's not null if we got it, but it might not be ours */ + v4lmjpegsink = GST_V4LMJPEGSINK(object); + + switch (prop_id) { + case ARG_FRAMES_DISPLAYED: + g_value_set_int (value, v4lmjpegsink->frames_displayed); + break; + case ARG_FRAME_TIME: + g_value_set_int (value, v4lmjpegsink->frame_time/1000000); + break; + case ARG_NUMBUFS: + g_value_set_int (value, v4lmjpegsink->numbufs); + break; + case ARG_BUFSIZE: + g_value_set_int (value, v4lmjpegsink->bufsize); + break; + default: + parent_class->get_property(object, prop_id, value, pspec); + break; + } +} + + +static GstElementStateReturn +gst_v4lmjpegsink_change_state (GstElement *element) +{ + GstV4lMjpegSink *v4lmjpegsink; + + g_return_val_if_fail (GST_IS_V4LMJPEGSINK (element), GST_STATE_FAILURE); + v4lmjpegsink = GST_V4LMJPEGSINK(element); + + /* set up change state */ + switch (GST_STATE_PENDING(element)) { + case GST_STATE_READY: + if (GST_V4L_IS_ACTIVE(GST_V4LELEMENT(v4lmjpegsink))) { + /* stop playback, unmap all buffers */ + if (!gst_v4lmjpegsink_playback_deinit(v4lmjpegsink)) + return GST_STATE_FAILURE; + } + break; + case GST_STATE_PAUSED: + if (!GST_V4L_IS_ACTIVE(GST_V4LELEMENT(v4lmjpegsink))) { + /* set buffer info */ + if (!gst_v4lmjpegsink_set_buffer(v4lmjpegsink, + v4lmjpegsink->numbufs, v4lmjpegsink->bufsize)) + return GST_STATE_FAILURE; + if (!gst_v4lmjpegsink_playback_init(v4lmjpegsink)) + return GST_STATE_FAILURE; + } + else { + /* de-queue all queued buffers */ + if (!gst_v4lmjpegsink_playback_stop(v4lmjpegsink)) + return GST_STATE_FAILURE; + } + break; + case GST_STATE_PLAYING: + /* start */ + if (!gst_v4lmjpegsink_playback_start(v4lmjpegsink)) + return GST_STATE_FAILURE; + break; + } + + if (GST_ELEMENT_CLASS (parent_class)->change_state) + return GST_ELEMENT_CLASS (parent_class)->change_state (element); + + return GST_STATE_SUCCESS; +} + + +static gboolean +plugin_init (GModule *module, + GstPlugin *plugin) +{ + GstElementFactory *factory; + + /* create an elementfactory for the v4lmjpegsink element */ + factory = gst_elementfactory_new("v4lmjpegsink",GST_TYPE_V4LMJPEGSINK, + &gst_v4lmjpegsink_details); + g_return_val_if_fail(factory != NULL, FALSE); + + gst_elementfactory_add_padtemplate (factory, + GST_PADTEMPLATE_GET (sink_template)); + + gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory)); + + return TRUE; +} + + +GstPluginDesc plugin_desc = { + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "v4lmjpegsink", + plugin_init +}; diff --git a/sys/v4l/gstv4lmjpegsink.h b/sys/v4l/gstv4lmjpegsink.h new file mode 100644 index 0000000000..7c8debba6f --- /dev/null +++ b/sys/v4l/gstv4lmjpegsink.h @@ -0,0 +1,98 @@ +/* G-Streamer hardware MJPEG video sink plugin + * Copyright (C) 2001 Ronald Bultje + * + * 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. + */ + +#ifndef __GST_V4LMJPEGSINK_H__ +#define __GST_V4LMJPEGSINK_H__ + +#include +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define GST_TYPE_V4LMJPEGSINK \ + (gst_v4lmjpegsink_get_type()) +#define GST_V4LMJPEGSINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_V4LMJPEGSINK,GstV4lMjpegSink)) +#define GST_V4LMJPEGSINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_V4LMJPEGSINK,GstV4lMjpegSinkClass)) +#define GST_IS_V4LMJPEGSINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_V4LMJPEGSINK)) +#define GST_IS_V4LMJPEGSINK_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_V4LMJPEGSINK)) + +typedef struct _GstV4lMjpegSink GstV4lMjpegSink; +typedef struct _GstV4lMjpegSinkClass GstV4lMjpegSinkClass; + +struct _GstV4lMjpegSink { + GstV4lElement v4lelement; + + /* the sink pas */ + GstPad *sinkpad; + + /* frame properties for common players */ + gint frames_displayed; + guint64 frame_time; + + /* system clock object */ + GstClock *clock; + + /* buffer/capture info */ + struct mjpeg_sync bsync; + struct mjpeg_requestbuffers breq; + + /* thread to keep track of synced frames */ + gint8 *isqueued_queued_frames; /* 1 = queued, 0 = unqueued, -1 = error */ + pthread_t thread_queued_frames; + pthread_mutex_t mutex_queued_frames; + pthread_cond_t *cond_queued_frames; + gint current_frame; + + /* width/height/norm of the jpeg stream */ + gint width; + gint height; + + /* cache values */ + gint x_offset; + gint y_offset; + + gint numbufs; + gint bufsize; /* in KB */ +}; + +struct _GstV4lMjpegSinkClass { + GstV4lElementClass parent_class; + + /* signals */ + void (*frame_displayed) (GstElement *element); + void (*have_size) (GstElement *element, guint width, guint height); +}; + +GType gst_v4lmjpegsink_get_type(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GST_SDLVIDEOSINK_H__ */ diff --git a/sys/v4l/v4lmjpegsink_calls.c b/sys/v4l/v4lmjpegsink_calls.c new file mode 100644 index 0000000000..dc49768e96 --- /dev/null +++ b/sys/v4l/v4lmjpegsink_calls.c @@ -0,0 +1,579 @@ +/* G-Streamer hardware MJPEG video sink plugin + * Copyright (C) 2001 Ronald Bultje + * + * 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. + */ + +//#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include "v4lmjpegsink_calls.h" + +/* On some systems MAP_FAILED seems to be missing */ +#ifndef MAP_FAILED +#define MAP_FAILED ( (caddr_t) -1 ) +#endif + + +/****************************************************** + * gst_v4lmjpegsink_sync_thread() + * thread keeps track of played frames + ******************************************************/ + +static void * +gst_v4lmjpegsink_sync_thread (void *arg) +{ + GstV4lMjpegSink *v4lmjpegsink = GST_V4LMJPEGSINK(arg); + gint frame = 0; /* frame that we're currently syncing on */ + +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_sync_thread()\n"); +#endif + + /* Allow easy shutting down by other processes... */ + pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, NULL ); + pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS, NULL ); + + while (1) + { + pthread_mutex_lock(&(v4lmjpegsink->mutex_queued_frames)); + if (!v4lmjpegsink->isqueued_queued_frames[frame]) + { + pthread_cond_wait(&(v4lmjpegsink->cond_queued_frames[frame]), + &(v4lmjpegsink->mutex_queued_frames)); + } + if (v4lmjpegsink->isqueued_queued_frames[frame] != 1) + { + pthread_mutex_unlock(&(v4lmjpegsink->mutex_queued_frames)); + goto end; + } + pthread_mutex_unlock(&(v4lmjpegsink->mutex_queued_frames)); + + if (ioctl(GST_V4LELEMENT(v4lmjpegsink)->video_fd, MJPIOC_SYNC, + &(v4lmjpegsink->bsync)) < 0) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Failed to sync on frame %d: %s", + frame, sys_errlist[errno]); + pthread_mutex_lock(&(v4lmjpegsink->mutex_queued_frames)); + v4lmjpegsink->isqueued_queued_frames[frame] = -1; + pthread_cond_broadcast(&(v4lmjpegsink->cond_queued_frames[frame])); + pthread_mutex_unlock(&(v4lmjpegsink->mutex_queued_frames)); + goto end; + } + else + { + /* be sure that we're not confusing */ + if (frame != v4lmjpegsink->bsync.frame) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Internal error: frame number confusion"); + goto end; + } + pthread_mutex_lock(&(v4lmjpegsink->mutex_queued_frames)); + v4lmjpegsink->isqueued_queued_frames[frame] = 0; + pthread_cond_broadcast(&(v4lmjpegsink->cond_queued_frames[frame])); + pthread_mutex_unlock(&(v4lmjpegsink->mutex_queued_frames)); + } + + frame = (frame+1)%v4lmjpegsink->breq.count; + } + +end: + gst_element_info(GST_ELEMENT(v4lmjpegsink), + "Sync thread got signalled to exit"); + pthread_exit(NULL); +} + + +/****************************************************** + * gst_v4lmjpegsink_queue_frame() + * queue a frame for playback + * return value: TRUE on success, FALSE on error + ******************************************************/ + +static gboolean +gst_v4lmjpegsink_queue_frame (GstV4lMjpegSink *v4lmjpegsink, + gint num) +{ +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_queue_frame(), num = %d\n", + num); +#endif + + /* queue on this frame */ + if (ioctl(GST_V4LELEMENT(v4lmjpegsink)->video_fd, MJPIOC_QBUF_PLAY, &num) < 0) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Failed to queue frame %d: %s", + num, sys_errlist[errno]); + return FALSE; + } + + pthread_mutex_lock(&(v4lmjpegsink->mutex_queued_frames)); + v4lmjpegsink->isqueued_queued_frames[num] = 1; + pthread_cond_broadcast(&(v4lmjpegsink->cond_queued_frames[num])); + pthread_mutex_unlock(&(v4lmjpegsink->mutex_queued_frames)); + + return TRUE; +} + + +/****************************************************** + * gst_v4lmjpegsink_sync_frame() + * wait for a frame to be finished playing + * return value: TRUE on success, FALSE on error + ******************************************************/ + +static gboolean +gst_v4lmjpegsink_sync_frame (GstV4lMjpegSink *v4lmjpegsink, + gint *num) +{ +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_sync_frame(), num = %d\n", + num); +#endif + + /* calculate next frame */ + v4lmjpegsink->current_frame = (v4lmjpegsink->current_frame+1)%v4lmjpegsink->breq.count; + *num = v4lmjpegsink->current_frame; + + pthread_mutex_lock(&(v4lmjpegsink->mutex_queued_frames)); + if (v4lmjpegsink->isqueued_queued_frames[*num] == 1) + { + pthread_cond_wait(&(v4lmjpegsink->cond_queued_frames[*num]), + &(v4lmjpegsink->mutex_queued_frames)); + } + if (v4lmjpegsink->isqueued_queued_frames[*num] != 0) + { + pthread_mutex_unlock(&(v4lmjpegsink->mutex_queued_frames)); + return FALSE; + } + else + pthread_mutex_unlock(&(v4lmjpegsink->mutex_queued_frames)); + + return TRUE; +} + + +/****************************************************** + * gst_v4lmjpegsink_set_buffer() + * set buffer options + * return value: TRUE on success, FALSE on error + ******************************************************/ + +gboolean +gst_v4lmjpegsink_set_buffer (GstV4lMjpegSink *v4lmjpegsink, + gint numbufs, + gint bufsize) +{ +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_set_buffer(), numbufs = %d, bufsize = %d KB\n", + numbufs, bufsize); +#endif + + GST_V4L_CHECK_OPEN(GST_V4LELEMENT(v4lmjpegsink)); + GST_V4L_CHECK_NOT_ACTIVE(GST_V4LELEMENT(v4lmjpegsink)); + + v4lmjpegsink->breq.size = bufsize * 1024; + v4lmjpegsink->breq.count = numbufs; + + return TRUE; +} + + +/****************************************************** + * gst_v4lmjpegsink_set_playback() + * set playback options (video, interlacing, etc.) + * return value: TRUE on success, FALSE on error + ******************************************************/ + +gboolean +gst_v4lmjpegsink_set_playback (GstV4lMjpegSink *v4lmjpegsink, + gint width, + gint height, + gint x_offset, + gint y_offset, + gint norm, + gint interlacing) +{ + gint mw, mh; + struct mjpeg_params bparm; + +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_set_playback(), " + "size = %dx%d, X/Y-offsets = %d/%d, norm = %d, interlacing = %d\n", + width, height, x_offset, y_offset, norm, interlacing); +#endif + + GST_V4L_CHECK_OPEN(GST_V4LELEMENT(v4lmjpegsink)); + GST_V4L_CHECK_NOT_ACTIVE(GST_V4LELEMENT(v4lmjpegsink)); + + if (ioctl(GST_V4LELEMENT(v4lmjpegsink)->video_fd, MJPIOC_G_PARAMS, &bparm) < 0) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Error getting playback parameters: %s", + sys_errlist[errno]); + return FALSE; + } + + bparm.input = 0; + bparm.norm = norm; + bparm.decimation = 0; /* we'll set proper values later on */ + + /* maxwidth is broken on marvel cards */ + mw = GST_V4LELEMENT(v4lmjpegsink)->vcap.maxwidth; + if (mw != 768 && mw != 640) + mw = 720; + mh = (norm==VIDEO_MODE_NTSC?480:576); + + if (width > mw || height > mh) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Video dimensions (%dx%d) are larger than device max (%dx%d)", + width, height, mw, mh); + return FALSE; + } + + if (width <= mw/4) + bparm.HorDcm = 4; + else if (width <= mw/2) + bparm.HorDcm = 2; + else + bparm.HorDcm = 1; + + /* TODO: add proper interlacing handling */ +#if 0 + if (interlacing != INTERLACING_NOT_INTERLACED) + { + bparm.field_per_buff = 2; + bparm.TmpDcm = 1; + + if (height <= mh/2) + bparm.VerDcm = 2; + else + bparm.VerDcm = 1; + } + else +#endif + { + if (height > mh/2) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Video dimensions (%dx%d) too large for non-interlaced playback (%dx%d)", + width, height, mw, mh/2); + return FALSE; + } + + bparm.field_per_buff = 1; + bparm.TmpDcm = 2; + + if (height <= mh/4) + bparm.VerDcm = 2; + else + bparm.VerDcm = 1; + } + + /* TODO: add proper interlacing handling */ +#if 0 + bparm.odd_even = (interlacing==INTERLACING_TOP_FIRST); +#endif + + bparm.quality = 100; + bparm.img_width = bparm.HorDcm * width; + bparm.img_height = bparm.VerDcm * height/bparm.field_per_buff; + + /* image X/Y offset on device */ + if (x_offset < 0) + bparm.img_x = (mw - bparm.img_width)/2; + else + { + if (x_offset + bparm.img_width > mw) + bparm.img_x = mw - bparm.img_width; + else + bparm.img_x = x_offset; + } + + if (y_offset < 0) + bparm.img_y = (mh/2 - bparm.img_height)/2; + else + { + if (y_offset + bparm.img_height*2 > mh) + bparm.img_y = mh/2 - bparm.img_height; + else + bparm.img_y = y_offset/2; + } + + if (ioctl(GST_V4LELEMENT(v4lmjpegsink)->video_fd, MJPIOC_S_PARAMS, &bparm) < 0) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Error setting playback parameters: %s", + sys_errlist[errno]); + return FALSE; + } + + return TRUE; +} + + +/****************************************************** + * gst_v4lmjpegsink_playback_init() + * initialize playback system, set up buffer, etc. + * return value: TRUE on success, FALSE on error + ******************************************************/ + +gboolean +gst_v4lmjpegsink_playback_init (GstV4lMjpegSink *v4lmjpegsink) +{ + gint n; + +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_playback_init()\n"); +#endif + + GST_V4L_CHECK_OPEN(GST_V4LELEMENT(v4lmjpegsink)); + GST_V4L_CHECK_NOT_ACTIVE(GST_V4LELEMENT(v4lmjpegsink)); + + /* Request buffers */ + if (ioctl(GST_V4LELEMENT(v4lmjpegsink)->video_fd, MJPIOC_REQBUFS, &(v4lmjpegsink->breq)) < 0) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Error requesting video buffers: %s", + sys_errlist[errno]); + return FALSE; + } + + gst_element_info(GST_ELEMENT(v4lmjpegsink), + "Got %ld buffers of size %ld KB", + v4lmjpegsink->breq.count, v4lmjpegsink->breq.size/1024); + + /* Map the buffers */ + GST_V4LELEMENT(v4lmjpegsink)->buffer = mmap(0, + v4lmjpegsink->breq.count * v4lmjpegsink->breq.size, + PROT_READ, MAP_SHARED, GST_V4LELEMENT(v4lmjpegsink)->video_fd, 0); + if (GST_V4LELEMENT(v4lmjpegsink)->buffer == MAP_FAILED) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Error mapping video buffers: %s", + sys_errlist[errno]); + GST_V4LELEMENT(v4lmjpegsink)->buffer = NULL; + return FALSE; + } + + /* TODO: allocate/init the pthread thingies */ + pthread_mutex_init(&(v4lmjpegsink->mutex_queued_frames), NULL); + v4lmjpegsink->isqueued_queued_frames = (gint8 *) + malloc(sizeof(gint8) * v4lmjpegsink->breq.count); + if (!v4lmjpegsink->isqueued_queued_frames) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Failed to create queue tracker: %s", + sys_errlist[errno]); + return FALSE; + } + v4lmjpegsink->cond_queued_frames = (pthread_cond_t *) + malloc(sizeof(pthread_cond_t) * v4lmjpegsink->breq.count); + if (!v4lmjpegsink->cond_queued_frames) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Failed to create queue condition holders: %s", + sys_errlist[errno]); + return FALSE; + } + for (n=0;nbreq.count;n++) + pthread_cond_init(&(v4lmjpegsink->cond_queued_frames[n]), NULL); + + return TRUE; +} + + +/****************************************************** + * gst_v4lmjpegsink_playback_start() + * start playback system + * return value: TRUE on success, FALSE on error + ******************************************************/ + +gboolean +gst_v4lmjpegsink_playback_start (GstV4lMjpegSink *v4lmjpegsink) +{ + gint n; + +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_playback_start()\n"); +#endif + + GST_V4L_CHECK_OPEN(GST_V4LELEMENT(v4lmjpegsink)); + GST_V4L_CHECK_ACTIVE(GST_V4LELEMENT(v4lmjpegsink)); + + /* mark all buffers as unqueued */ + for (n=0;nbreq.count;n++) + v4lmjpegsink->isqueued_queued_frames[n] = 0; + + v4lmjpegsink->current_frame = -1; + + /* create sync() thread */ + if (pthread_create(&(v4lmjpegsink->thread_queued_frames), NULL, + gst_v4lmjpegsink_sync_thread, (void *) v4lmjpegsink)) + { + gst_element_error(GST_ELEMENT(v4lmjpegsink), + "Failed to create sync thread: %s", + sys_errlist[errno]); + return FALSE; + } + + return TRUE; +} + + +/****************************************************** + * gst_v4lmjpegsink_get_buffer() + * get address of a buffer + * return value: buffer's address or NULL + ******************************************************/ + +guint8 * +gst_v4lmjpegsink_get_buffer (GstV4lMjpegSink *v4lmjpegsink, + gint num) +{ +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_get_buffer(), num = %d\n", + num); +#endif + + if (!GST_V4L_IS_ACTIVE(GST_V4LELEMENT(v4lmjpegsink)) || + !GST_V4L_IS_OPEN(GST_V4LELEMENT(v4lmjpegsink))) + return NULL; + + if (num < 0 || num >= v4lmjpegsink->breq.count) + return NULL; + + return GST_V4LELEMENT(v4lmjpegsink)->buffer+(v4lmjpegsink->breq.size*num); +} + + +/****************************************************** + * gst_v4lmjpegsink_play_frame() + * queue a new buffer + * return value: TRUE on success, FALSE on error + ******************************************************/ + +gboolean +gst_v4lmjpegsink_play_frame (GstV4lMjpegSink *v4lmjpegsink, + gint num) +{ +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_play_frame()\n"); +#endif + + GST_V4L_CHECK_OPEN(GST_V4LELEMENT(v4lmjpegsink)); + GST_V4L_CHECK_ACTIVE(GST_V4LELEMENT(v4lmjpegsink)); + + if (!gst_v4lmjpegsink_queue_frame(v4lmjpegsink, num)) + return FALSE; + + return TRUE; +} + + +/****************************************************** + * gst_v4lmjpegsink_wait_frame() + * wait for buffer to be actually played + * return value: TRUE on success, FALSE on error + ******************************************************/ + +gboolean +gst_v4lmjpegsink_wait_frame (GstV4lMjpegSink *v4lmjpegsink, + gint *num) +{ +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_wait_frame(), num = %d\n", + num); +#endif + + GST_V4L_CHECK_OPEN(GST_V4LELEMENT(v4lmjpegsink)); + GST_V4L_CHECK_ACTIVE(GST_V4LELEMENT(v4lmjpegsink)); + + if (!gst_v4lmjpegsink_sync_frame(v4lmjpegsink, num)) + return FALSE; + + return TRUE; +} + + +/****************************************************** + * gst_v4lmjpegsink_playback_stop() + * stop playback system and sync on remaining frames + * return value: TRUE on success, FALSE on error + ******************************************************/ + +gboolean +gst_v4lmjpegsink_playback_stop (GstV4lMjpegSink *v4lmjpegsink) +{ + gint num; + +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_playback_stop()\n"); +#endif + + GST_V4L_CHECK_OPEN(GST_V4LELEMENT(v4lmjpegsink)); + GST_V4L_CHECK_ACTIVE(GST_V4LELEMENT(v4lmjpegsink)); + + /* mark next buffer as wrong */ + if (!gst_v4lmjpegsink_sync_frame(v4lmjpegsink, &num) || + !gst_v4lmjpegsink_queue_frame(v4lmjpegsink, num)) + { + return FALSE; + } + + /* .. and wait for all buffers to be queued on */ + pthread_join(v4lmjpegsink->thread_queued_frames, NULL); + + return TRUE; +} + + +/****************************************************** + * gst_v4lmjpegsink_playback_deinit() + * deinitialize the playback system and unmap buffer + * return value: TRUE on success, FALSE on error + ******************************************************/ + +gboolean +gst_v4lmjpegsink_playback_deinit (GstV4lMjpegSink *v4lmjpegsink) +{ +#ifdef DEBUG + fprintf(stderr, "V4LMJPEGSINK: gst_v4lmjpegsink_playback_deinit()\n"); +#endif + + GST_V4L_CHECK_OPEN(GST_V4LELEMENT(v4lmjpegsink)); + GST_V4L_CHECK_ACTIVE(GST_V4LELEMENT(v4lmjpegsink)); + + /* free pthread thingies */ + free(v4lmjpegsink->cond_queued_frames); + free(v4lmjpegsink->isqueued_queued_frames); + + /* unmap the buffer */ + munmap(GST_V4LELEMENT(v4lmjpegsink)->buffer, v4lmjpegsink->breq.size * v4lmjpegsink->breq.count); + GST_V4LELEMENT(v4lmjpegsink)->buffer = NULL; + + return TRUE; +} diff --git a/sys/v4l/v4lmjpegsink_calls.h b/sys/v4l/v4lmjpegsink_calls.h new file mode 100644 index 0000000000..bfa90d882a --- /dev/null +++ b/sys/v4l/v4lmjpegsink_calls.h @@ -0,0 +1,59 @@ +/* G-Streamer hardware MJPEG video sink plugin + * Copyright (C) 2001 Ronald Bultje + * + * 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. + */ + +#ifndef __V4L_MJPEG_SINK_CALLS_H__ +#define __V4L_MJPEG_SINK_CALLS_H__ + +#include "gstv4lmjpegsink.h" +#include "v4l_calls.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* frame playback on device */ +gboolean gst_v4lmjpegsink_set_buffer (GstV4lMjpegSink *v4lmjpegsink, + gint numbufs, + gint bufsize); +gboolean gst_v4lmjpegsink_set_playback (GstV4lMjpegSink *v4lmjpegsink, + gint width, + gint height, + gint x_offset, + gint y_offset, + gint norm, + gint interlacing); +gboolean gst_v4lmjpegsink_playback_init (GstV4lMjpegSink *v4lmjpegsink); +gboolean gst_v4lmjpegsink_playback_start (GstV4lMjpegSink *v4lmjpegsink); +guint8 * gst_v4lmjpegsink_get_buffer (GstV4lMjpegSink *v4lmjpegsink, + gint num); +gboolean gst_v4lmjpegsink_play_frame (GstV4lMjpegSink *v4lmjpegsink, + gint num); +gboolean gst_v4lmjpegsink_wait_frame (GstV4lMjpegSink *v4lmjpegsink, + gint *num); +gboolean gst_v4lmjpegsink_playback_stop (GstV4lMjpegSink *v4lmjpegsink); +gboolean gst_v4lmjpegsink_playback_deinit (GstV4lMjpegSink *v4lmjpegsink); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __V4L_MJPEG_SINK_CALLS_H__ */