/* GStreamer * Copyright (C) <1999> Erik Walthinsen * * 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-jpegenc * * Encodes jpeg images. * * * Example launch line * |[ * gst-launch videotestsrc num-buffers=50 ! video/x-raw-yuv, framerate='(fraction)'5/1 ! jpegenc ! avimux ! filesink location=mjpeg.avi * ]| a pipeline to mux 5 JPEG frames per second into a 10 sec. long motion jpeg * avi. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "gstjpegenc.h" #include "gstjpeg.h" #include /* experimental */ /* setting smoothig seems to have no effect in libjepeg #define ENABLE_SMOOTHING 1 */ GST_DEBUG_CATEGORY_STATIC (jpegenc_debug); #define GST_CAT_DEFAULT jpegenc_debug #define JPEG_DEFAULT_QUALITY 85 #define JPEG_DEFAULT_SMOOTHING 0 #define JPEG_DEFAULT_IDCT_METHOD JDCT_FASTEST /* These macros are adapted from videotestsrc.c * and/or gst-plugins/gst/games/gstvideoimage.c */ /* I420 */ #define I420_Y_ROWSTRIDE(width) (GST_ROUND_UP_4(width)) #define I420_U_ROWSTRIDE(width) (GST_ROUND_UP_8(width)/2) #define I420_V_ROWSTRIDE(width) ((GST_ROUND_UP_8(I420_Y_ROWSTRIDE(width)))/2) #define I420_Y_OFFSET(w,h) (0) #define I420_U_OFFSET(w,h) (I420_Y_OFFSET(w,h)+(I420_Y_ROWSTRIDE(w)*GST_ROUND_UP_2(h))) #define I420_V_OFFSET(w,h) (I420_U_OFFSET(w,h)+(I420_U_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2)) #define I420_SIZE(w,h) (I420_V_OFFSET(w,h)+(I420_V_ROWSTRIDE(w)*GST_ROUND_UP_2(h)/2)) /* JpegEnc signals and args */ enum { FRAME_ENCODED, /* FILL ME */ LAST_SIGNAL }; enum { PROP_0, PROP_QUALITY, PROP_SMOOTHING, PROP_IDCT_METHOD }; static void gst_jpegenc_base_init (gpointer g_class); static void gst_jpegenc_class_init (GstJpegEnc * klass); static void gst_jpegenc_init (GstJpegEnc * jpegenc); static void gst_jpegenc_finalize (GObject * object); static GstFlowReturn gst_jpegenc_chain (GstPad * pad, GstBuffer * buf); static gboolean gst_jpegenc_setcaps (GstPad * pad, GstCaps * caps); static GstCaps *gst_jpegenc_getcaps (GstPad * pad); static void gst_jpegenc_resync (GstJpegEnc * jpegenc); static void gst_jpegenc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_jpegenc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static GstStateChangeReturn gst_jpegenc_change_state (GstElement * element, GstStateChange transition); static GstElementClass *parent_class = NULL; static guint gst_jpegenc_signals[LAST_SIGNAL] = { 0 }; GType gst_jpegenc_get_type (void) { static GType jpegenc_type = 0; if (!jpegenc_type) { static const GTypeInfo jpegenc_info = { sizeof (GstJpegEnc), (GBaseInitFunc) gst_jpegenc_base_init, NULL, (GClassInitFunc) gst_jpegenc_class_init, NULL, NULL, sizeof (GstJpegEnc), 0, (GInstanceInitFunc) gst_jpegenc_init, }; jpegenc_type = g_type_register_static (GST_TYPE_ELEMENT, "GstJpegEnc", &jpegenc_info, 0); } return jpegenc_type; } static GstStaticPadTemplate gst_jpegenc_sink_pad_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420")) ); static GstStaticPadTemplate gst_jpegenc_src_pad_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("image/jpeg, " "width = (int) [ 16, 4096 ], " "height = (int) [ 16, 4096 ], " "framerate = (fraction) [ 0/1, MAX ]") ); static void gst_jpegenc_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&gst_jpegenc_sink_pad_template)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&gst_jpegenc_src_pad_template)); gst_element_class_set_details_simple (element_class, "JPEG image encoder", "Codec/Encoder/Image", "Encode images in JPEG format", "Wim Taymans "); } static void gst_jpegenc_class_init (GstJpegEnc * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; parent_class = g_type_class_peek_parent (klass); gst_jpegenc_signals[FRAME_ENCODED] = g_signal_new ("frame-encoded", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstJpegEncClass, frame_encoded), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); gobject_class->set_property = gst_jpegenc_set_property; gobject_class->get_property = gst_jpegenc_get_property; g_object_class_install_property (gobject_class, PROP_QUALITY, g_param_spec_int ("quality", "Quality", "Quality of encoding", 0, 100, JPEG_DEFAULT_QUALITY, G_PARAM_READWRITE)); #ifdef ENABLE_SMOOTHING /* disabled, since it doesn't seem to work */ g_object_class_install_property (gobject_class, PROP_SMOOTHING, g_param_spec_int ("smoothing", "Smoothing", "Smoothing factor", 0, 100, JPEG_DEFAULT_SMOOTHING, G_PARAM_READWRITE)); #endif g_object_class_install_property (gobject_class, PROP_IDCT_METHOD, g_param_spec_enum ("idct-method", "IDCT Method", "The IDCT algorithm to use", GST_TYPE_IDCT_METHOD, JPEG_DEFAULT_IDCT_METHOD, G_PARAM_READWRITE)); gstelement_class->change_state = gst_jpegenc_change_state; gobject_class->finalize = gst_jpegenc_finalize; GST_DEBUG_CATEGORY_INIT (jpegenc_debug, "jpegenc", 0, "JPEG encoding element"); } static void gst_jpegenc_init_destination (j_compress_ptr cinfo) { GST_DEBUG ("gst_jpegenc_chain: init_destination"); } static boolean gst_jpegenc_flush_destination (j_compress_ptr cinfo) { GstBuffer *overflow_buffer; guint32 old_buffer_size; GstJpegEnc *jpegenc = (GstJpegEnc *) (cinfo->client_data); GST_DEBUG_OBJECT (jpegenc, "gst_jpegenc_chain: flush_destination: buffer too small"); /* Our output buffer wasn't big enough. * Make a new buffer that's twice the size, */ old_buffer_size = GST_BUFFER_SIZE (jpegenc->output_buffer); gst_pad_alloc_buffer_and_set_caps (jpegenc->srcpad, GST_BUFFER_OFFSET_NONE, old_buffer_size * 2, GST_PAD_CAPS (jpegenc->srcpad), &overflow_buffer); memcpy (GST_BUFFER_DATA (overflow_buffer), GST_BUFFER_DATA (jpegenc->output_buffer), old_buffer_size); gst_buffer_copy_metadata (overflow_buffer, jpegenc->output_buffer, GST_BUFFER_COPY_TIMESTAMPS); /* drop it into place, */ gst_buffer_unref (jpegenc->output_buffer); jpegenc->output_buffer = overflow_buffer; /* and last, update libjpeg on where to work. */ jpegenc->jdest.next_output_byte = GST_BUFFER_DATA (jpegenc->output_buffer) + old_buffer_size; jpegenc->jdest.free_in_buffer = GST_BUFFER_SIZE (jpegenc->output_buffer) - old_buffer_size; return TRUE; } static void gst_jpegenc_term_destination (j_compress_ptr cinfo) { GstJpegEnc *jpegenc = (GstJpegEnc *) (cinfo->client_data); GST_DEBUG_OBJECT (jpegenc, "gst_jpegenc_chain: term_source"); /* Trim the buffer size and push it. */ GST_BUFFER_SIZE (jpegenc->output_buffer) = GST_ROUND_UP_4 (GST_BUFFER_SIZE (jpegenc->output_buffer) - jpegenc->jdest.free_in_buffer); g_signal_emit (G_OBJECT (jpegenc), gst_jpegenc_signals[FRAME_ENCODED], 0); jpegenc->last_ret = gst_pad_push (jpegenc->srcpad, jpegenc->output_buffer); jpegenc->output_buffer = NULL; } static void gst_jpegenc_init (GstJpegEnc * jpegenc) { /* create the sink and src pads */ jpegenc->sinkpad = gst_pad_new_from_static_template (&gst_jpegenc_sink_pad_template, "sink"); gst_pad_set_chain_function (jpegenc->sinkpad, GST_DEBUG_FUNCPTR (gst_jpegenc_chain)); gst_pad_set_getcaps_function (jpegenc->sinkpad, GST_DEBUG_FUNCPTR (gst_jpegenc_getcaps)); gst_pad_set_setcaps_function (jpegenc->sinkpad, GST_DEBUG_FUNCPTR (gst_jpegenc_setcaps)); gst_element_add_pad (GST_ELEMENT (jpegenc), jpegenc->sinkpad); jpegenc->srcpad = gst_pad_new_from_static_template (&gst_jpegenc_src_pad_template, "src"); gst_pad_use_fixed_caps (jpegenc->srcpad); gst_element_add_pad (GST_ELEMENT (jpegenc), jpegenc->srcpad); /* reset the initial video state */ jpegenc->width = -1; jpegenc->height = -1; /* setup jpeglib */ memset (&jpegenc->cinfo, 0, sizeof (jpegenc->cinfo)); memset (&jpegenc->jerr, 0, sizeof (jpegenc->jerr)); jpegenc->cinfo.err = jpeg_std_error (&jpegenc->jerr); jpeg_create_compress (&jpegenc->cinfo); jpegenc->jdest.init_destination = gst_jpegenc_init_destination; jpegenc->jdest.empty_output_buffer = gst_jpegenc_flush_destination; jpegenc->jdest.term_destination = gst_jpegenc_term_destination; jpegenc->cinfo.dest = &jpegenc->jdest; jpegenc->cinfo.client_data = jpegenc; /* init properties */ jpegenc->quality = JPEG_DEFAULT_QUALITY; jpegenc->smoothing = JPEG_DEFAULT_SMOOTHING; jpegenc->idct_method = JPEG_DEFAULT_IDCT_METHOD; } static void gst_jpegenc_finalize (GObject * object) { GstJpegEnc *filter = GST_JPEGENC (object); jpeg_destroy_compress (&filter->cinfo); G_OBJECT_CLASS (parent_class)->finalize (object); } static GstCaps * gst_jpegenc_getcaps (GstPad * pad) { GstJpegEnc *jpegenc = GST_JPEGENC (gst_pad_get_parent (pad)); GstCaps *caps; int i; GstStructure *structure = NULL; /* we want to proxy properties like width, height and framerate from the other end of the element */ caps = gst_pad_get_allowed_caps (jpegenc->srcpad); if (caps == NULL) { caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); } else if (gst_caps_is_any (caps)) { gst_caps_unref (caps); caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); } else { caps = gst_caps_make_writable (caps); } for (i = 0; i < gst_caps_get_size (caps); i++) { structure = gst_caps_get_structure (caps, i); gst_structure_set_name (structure, "video/x-raw-yuv"); gst_structure_set (structure, "format", GST_TYPE_FOURCC, GST_STR_FOURCC ("I420"), NULL); } gst_object_unref (jpegenc); return caps; } static gboolean gst_jpegenc_setcaps (GstPad * pad, GstCaps * caps) { GstJpegEnc *jpegenc; const GValue *framerate; GstStructure *structure; GstCaps *pcaps; gboolean ret; jpegenc = GST_JPEGENC (gst_pad_get_parent (pad)); structure = gst_caps_get_structure (caps, 0); framerate = gst_structure_get_value (structure, "framerate"); gst_structure_get_int (structure, "width", &jpegenc->width); gst_structure_get_int (structure, "height", &jpegenc->height); pcaps = gst_caps_new_simple ("image/jpeg", "width", G_TYPE_INT, jpegenc->width, "height", G_TYPE_INT, jpegenc->height, NULL); structure = gst_caps_get_structure (pcaps, 0); if (framerate) gst_structure_set_value (structure, "framerate", framerate); ret = gst_pad_set_caps (jpegenc->srcpad, pcaps); if (ret) gst_jpegenc_resync (jpegenc); gst_caps_unref (pcaps); gst_object_unref (jpegenc); return ret; } static void gst_jpegenc_resync (GstJpegEnc * jpegenc) { gint width, height; GST_DEBUG_OBJECT (jpegenc, "resync"); jpegenc->cinfo.image_width = width = jpegenc->width; jpegenc->cinfo.image_height = height = jpegenc->height; jpegenc->cinfo.input_components = 3; GST_DEBUG_OBJECT (jpegenc, "width %d, height %d", width, height); #ifdef ENABLE_COLORSPACE_RGB switch (jpegenc->format) { case GST_COLORSPACE_RGB24: jpegenc->bufsize = jpegenc->width * jpegenc->height * 3; GST_DEBUG ("gst_jpegenc_resync: setting format to RGB24"); jpegenc->cinfo.in_color_space = JCS_RGB; jpegenc->cinfo.raw_data_in = FALSE; break; case GST_COLORSPACE_YUV420P: #endif GST_DEBUG_OBJECT (jpegenc, "setting format to YUV420P"); jpegenc->bufsize = I420_SIZE (jpegenc->width, jpegenc->height); jpegenc->cinfo.in_color_space = JCS_YCbCr; jpeg_set_defaults (&jpegenc->cinfo); /* these are set in _chain() jpeg_set_quality (&jpegenc->cinfo, jpegenc->quality, TRUE); jpegenc->cinfo.smoothing_factor = jpegenc->smoothing; jpegenc->cinfo.dct_method = jpegenc->idct_method; */ jpegenc->cinfo.raw_data_in = TRUE; if (height != -1) { jpegenc->line[0] = g_realloc (jpegenc->line[0], height * sizeof (char *)); jpegenc->line[1] = g_realloc (jpegenc->line[1], height * sizeof (char *) / 2); jpegenc->line[2] = g_realloc (jpegenc->line[2], height * sizeof (char *) / 2); } GST_DEBUG_OBJECT (jpegenc, "setting format done"); #ifdef ENABLE_COLORSPACE_RGB break; default: printf ("gst_jpegenc_resync: unsupported colorspace, using RGB\n"); jpegenc->bufsize = jpegenc->width * jpegenc->height * 3; jpegenc->cinfo.in_color_space = JCS_RGB; break; } #endif /* guard against a potential error in gst_jpegenc_term_destination which occurs iff bufsize % 4 < free_space_remaining */ jpegenc->bufsize = GST_ROUND_UP_4 (jpegenc->bufsize); jpeg_suppress_tables (&jpegenc->cinfo, TRUE); GST_DEBUG_OBJECT (jpegenc, "resync done"); } static GstFlowReturn gst_jpegenc_chain (GstPad * pad, GstBuffer * buf) { GstFlowReturn ret; GstJpegEnc *jpegenc; guchar *data; gulong size; guint height, width; guchar *base[3], *end[3]; gint i, j, k; jpegenc = GST_JPEGENC (GST_OBJECT_PARENT (pad)); if (G_UNLIKELY (jpegenc->width <= 0 || jpegenc->height <= 0)) goto not_negotiated; data = GST_BUFFER_DATA (buf); size = GST_BUFFER_SIZE (buf); GST_LOG_OBJECT (jpegenc, "got buffer of %lu bytes", size); ret = gst_pad_alloc_buffer_and_set_caps (jpegenc->srcpad, GST_BUFFER_OFFSET_NONE, jpegenc->bufsize, GST_PAD_CAPS (jpegenc->srcpad), &jpegenc->output_buffer); if (ret != GST_FLOW_OK) goto done; gst_buffer_copy_metadata (jpegenc->output_buffer, buf, GST_BUFFER_COPY_TIMESTAMPS); width = jpegenc->width; height = jpegenc->height; base[0] = data + I420_Y_OFFSET (width, height); base[1] = data + I420_U_OFFSET (width, height); base[2] = data + I420_V_OFFSET (width, height); end[0] = base[0] + height * I420_Y_ROWSTRIDE (width); end[1] = base[1] + (height / 2) * I420_U_ROWSTRIDE (width); end[2] = base[2] + (height / 2) * I420_V_ROWSTRIDE (width); /* FIXME: shouldn't we also set * - jpegenc->cinfo.max_{v,h}_samp_factor * - jpegenc->cinfo.comp_info[0,1,2].{v,h}_samp_factor * accordingly? */ jpegenc->jdest.next_output_byte = GST_BUFFER_DATA (jpegenc->output_buffer); jpegenc->jdest.free_in_buffer = GST_BUFFER_SIZE (jpegenc->output_buffer); /* prepare for raw input */ #if JPEG_LIB_VERSION >= 70 jpegenc->cinfo.do_fancy_downsampling = FALSE; #endif jpegenc->cinfo.smoothing_factor = jpegenc->smoothing; jpegenc->cinfo.dct_method = jpegenc->idct_method; jpeg_set_quality (&jpegenc->cinfo, jpegenc->quality, TRUE); jpeg_start_compress (&jpegenc->cinfo, TRUE); GST_LOG_OBJECT (jpegenc, "compressing"); for (i = 0; i < height; i += 2 * DCTSIZE) { /*g_print ("next scanline: %d\n", jpegenc->cinfo.next_scanline); */ for (j = 0, k = 0; j < (2 * DCTSIZE); j += 2, k++) { jpegenc->line[0][j] = base[0]; if (base[0] + I420_Y_ROWSTRIDE (width) < end[0]) base[0] += I420_Y_ROWSTRIDE (width); jpegenc->line[0][j + 1] = base[0]; if (base[0] + I420_Y_ROWSTRIDE (width) < end[0]) base[0] += I420_Y_ROWSTRIDE (width); jpegenc->line[1][k] = base[1]; if (base[1] + I420_U_ROWSTRIDE (width) < end[1]) base[1] += I420_U_ROWSTRIDE (width); jpegenc->line[2][k] = base[2]; if (base[2] + I420_V_ROWSTRIDE (width) < end[2]) base[2] += I420_V_ROWSTRIDE (width); } jpeg_write_raw_data (&jpegenc->cinfo, jpegenc->line, 2 * DCTSIZE); } /* This will ensure that gst_jpegenc_term_destination is called; we push the final output buffer from there */ jpeg_finish_compress (&jpegenc->cinfo); GST_LOG_OBJECT (jpegenc, "compressing done"); done: gst_buffer_unref (buf); return ret; /* ERRORS */ not_negotiated: { GST_WARNING_OBJECT (jpegenc, "no input format set (no caps on buffer)"); ret = GST_FLOW_NOT_NEGOTIATED; goto done; } } static void gst_jpegenc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstJpegEnc *jpegenc = GST_JPEGENC (object); GST_OBJECT_LOCK (jpegenc); switch (prop_id) { case PROP_QUALITY: jpegenc->quality = g_value_get_int (value); break; #ifdef ENABLE_SMOOTHING case PROP_SMOOTHING: jpegenc->smoothing = g_value_get_int (value); break; #endif case PROP_IDCT_METHOD: jpegenc->idct_method = g_value_get_enum (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_OBJECT_UNLOCK (jpegenc); } static void gst_jpegenc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstJpegEnc *jpegenc = GST_JPEGENC (object); GST_OBJECT_LOCK (jpegenc); switch (prop_id) { case PROP_QUALITY: g_value_set_int (value, jpegenc->quality); break; #ifdef ENABLE_SMOOTHING case PROP_SMOOTHING: g_value_set_int (value, jpegenc->smoothing); break; #endif case PROP_IDCT_METHOD: g_value_set_enum (value, jpegenc->idct_method); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_OBJECT_UNLOCK (jpegenc); } static GstStateChangeReturn gst_jpegenc_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; GstJpegEnc *filter = GST_JPEGENC (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: GST_DEBUG_OBJECT (element, "setting line buffers"); filter->line[0] = NULL; filter->line[1] = NULL; filter->line[2] = NULL; break; default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) return ret; switch (transition) { case GST_STATE_CHANGE_READY_TO_NULL: g_free (filter->line[0]); g_free (filter->line[1]); g_free (filter->line[2]); filter->line[0] = NULL; filter->line[1] = NULL; filter->line[2] = NULL; filter->width = -1; filter->height = -1; break; default: break; } return ret; }