• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 /**
20  * SECTION:element-smokeenc
21  * @title: smokeenc
22  *
23  * Encodes images in smoke format.
24  */
25 
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29 #include <string.h>
30 
31 #include "gstsmokeenc.h"
32 #include <gst/video/video.h>
33 
34 GST_DEBUG_CATEGORY_STATIC (smokeenc_debug);
35 #define GST_CAT_DEFAULT smokeenc_debug
36 
37 
38 /* SmokeEnc signals and args */
39 enum
40 {
41   FRAME_ENCODED,
42   /* FILL ME */
43   LAST_SIGNAL
44 };
45 
46 #define DEFAULT_PROP_MIN_QUALITY 10
47 #define DEFAULT_PROP_MAX_QUALITY 85
48 #define DEFAULT_PROP_THRESHOLD 3000
49 #define DEFAULT_PROP_KEYFRAME 20
50 
51 enum
52 {
53   PROP_0,
54   PROP_MIN_QUALITY,
55   PROP_MAX_QUALITY,
56   PROP_THRESHOLD,
57   PROP_KEYFRAME
58       /* FILL ME */
59 };
60 
61 static void gst_smokeenc_base_init (gpointer g_class);
62 static void gst_smokeenc_class_init (GstSmokeEnc * klass);
63 static void gst_smokeenc_init (GstSmokeEnc * smokeenc);
64 static void gst_smokeenc_finalize (GObject * object);
65 
66 static GstStateChangeReturn
67 gst_smokeenc_change_state (GstElement * element, GstStateChange transition);
68 
69 static GstFlowReturn gst_smokeenc_chain (GstPad * pad, GstBuffer * buf);
70 static GstCaps *gst_smokeenc_getcaps (GstPad * pad);
71 static gboolean gst_smokeenc_setcaps (GstPad * pad, GstCaps * caps);
72 
73 static gboolean gst_smokeenc_resync (GstSmokeEnc * smokeenc);
74 static void gst_smokeenc_set_property (GObject * object, guint prop_id,
75     const GValue * value, GParamSpec * pspec);
76 static void gst_smokeenc_get_property (GObject * object, guint prop_id,
77     GValue * value, GParamSpec * pspec);
78 
79 static GstElementClass *parent_class = NULL;
80 
81 GType
gst_smokeenc_get_type(void)82 gst_smokeenc_get_type (void)
83 {
84   static GType smokeenc_type = 0;
85 
86   if (!smokeenc_type) {
87     static const GTypeInfo smokeenc_info = {
88       sizeof (GstSmokeEncClass),
89       (GBaseInitFunc) gst_smokeenc_base_init,
90       NULL,
91       (GClassInitFunc) gst_smokeenc_class_init,
92       NULL,
93       NULL,
94       sizeof (GstSmokeEnc),
95       0,
96       (GInstanceInitFunc) gst_smokeenc_init,
97     };
98 
99     smokeenc_type =
100         g_type_register_static (GST_TYPE_ELEMENT, "GstSmokeEnc", &smokeenc_info,
101         0);
102   }
103   return smokeenc_type;
104 }
105 
106 GST_ELEMENT_REGISTER_DEFINE (smokeenc, "smokeenc", GST_RANK_PRIMARY,
107     GST_TYPE_SMOKEENC);
108 
109 static GstStaticPadTemplate gst_smokeenc_sink_pad_template =
110 GST_STATIC_PAD_TEMPLATE ("sink",
111     GST_PAD_SINK,
112     GST_PAD_ALWAYS,
113     GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
114     );
115 
116 static GstStaticPadTemplate gst_smokeenc_src_pad_template =
117 GST_STATIC_PAD_TEMPLATE ("src",
118     GST_PAD_SRC,
119     GST_PAD_ALWAYS,
120     GST_STATIC_CAPS ("video/x-smoke, "
121         "width = (int) [ 16, 4096 ], "
122         "height = (int) [ 16, 4096 ], " "framerate = (fraction) [ 0/1, MAX ]")
123     );
124 
125 static void
gst_smokeenc_base_init(gpointer g_class)126 gst_smokeenc_base_init (gpointer g_class)
127 {
128   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
129 
130   gst_element_class_add_static_pad_template (element_class,
131       &gst_smokeenc_sink_pad_template);
132   gst_element_class_add_static_pad_template (element_class,
133       &gst_smokeenc_src_pad_template);
134   gst_element_class_set_static_metadata (element_class, "Smoke video encoder",
135       "Codec/Encoder/Video", "Encode images into the Smoke format",
136       "Wim Taymans <wim@fluendo.com>");
137 }
138 
139 static void
gst_smokeenc_class_init(GstSmokeEnc * klass)140 gst_smokeenc_class_init (GstSmokeEnc * klass)
141 {
142   GObjectClass *gobject_class;
143   GstElementClass *gstelement_class;
144 
145   gobject_class = (GObjectClass *) klass;
146   gstelement_class = (GstElementClass *) klass;
147 
148   parent_class = g_type_class_peek_parent (klass);
149 
150   gobject_class->finalize = gst_smokeenc_finalize;
151   gobject_class->set_property = gst_smokeenc_set_property;
152   gobject_class->get_property = gst_smokeenc_get_property;
153 
154   g_object_class_install_property (gobject_class, PROP_MIN_QUALITY,
155       g_param_spec_int ("qmin", "Qmin", "Minimum quality",
156           0, 100, DEFAULT_PROP_MIN_QUALITY,
157           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
158   g_object_class_install_property (gobject_class, PROP_MAX_QUALITY,
159       g_param_spec_int ("qmax", "Qmax", "Maximum quality",
160           0, 100, DEFAULT_PROP_MAX_QUALITY,
161           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
162   g_object_class_install_property (gobject_class, PROP_THRESHOLD,
163       g_param_spec_int ("threshold", "Threshold", "Motion estimation threshold",
164           0, 100000000, DEFAULT_PROP_THRESHOLD,
165           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
166   g_object_class_install_property (gobject_class, PROP_KEYFRAME,
167       g_param_spec_int ("keyframe", "Keyframe",
168           "Insert keyframe every N frames", 1, 100000,
169           DEFAULT_PROP_KEYFRAME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
170 
171   gstelement_class->change_state =
172       GST_DEBUG_FUNCPTR (gst_smokeenc_change_state);
173 
174   GST_DEBUG_CATEGORY_INIT (smokeenc_debug, "smokeenc", 0,
175       "Smoke encoding element");
176 }
177 
178 static void
gst_smokeenc_init(GstSmokeEnc * smokeenc)179 gst_smokeenc_init (GstSmokeEnc * smokeenc)
180 {
181   /* create the sink and src pads */
182   smokeenc->sinkpad =
183       gst_pad_new_from_static_template (&gst_smokeenc_sink_pad_template,
184       "sink");
185   gst_pad_set_chain_function (smokeenc->sinkpad, gst_smokeenc_chain);
186   gst_pad_set_getcaps_function (smokeenc->sinkpad, gst_smokeenc_getcaps);
187   gst_pad_set_setcaps_function (smokeenc->sinkpad, gst_smokeenc_setcaps);
188   gst_element_add_pad (GST_ELEMENT (smokeenc), smokeenc->sinkpad);
189 
190   smokeenc->srcpad =
191       gst_pad_new_from_static_template (&gst_smokeenc_src_pad_template, "src");
192   gst_pad_set_getcaps_function (smokeenc->srcpad, gst_smokeenc_getcaps);
193   gst_pad_use_fixed_caps (smokeenc->srcpad);
194   gst_element_add_pad (GST_ELEMENT (smokeenc), smokeenc->srcpad);
195 
196   smokeenc->min_quality = DEFAULT_PROP_MIN_QUALITY;
197   smokeenc->max_quality = DEFAULT_PROP_MAX_QUALITY;
198   smokeenc->threshold = DEFAULT_PROP_THRESHOLD;
199   smokeenc->keyframe = DEFAULT_PROP_KEYFRAME;
200 }
201 
202 static void
gst_smokeenc_finalize(GObject * object)203 gst_smokeenc_finalize (GObject * object)
204 {
205   GstSmokeEnc *enc = GST_SMOKEENC (object);
206 
207   if (enc->info)
208     smokecodec_info_free (enc->info);
209 
210   G_OBJECT_CLASS (parent_class)->finalize (object);
211 }
212 
213 static GstCaps *
gst_smokeenc_getcaps(GstPad * pad)214 gst_smokeenc_getcaps (GstPad * pad)
215 {
216   GstSmokeEnc *smokeenc = GST_SMOKEENC (gst_pad_get_parent (pad));
217   GstPad *otherpad;
218   GstCaps *result, *caps;
219   const GstCaps *tcaps;
220   const char *name;
221   int i;
222   GstStructure *structure = NULL;
223 
224   /* we want to proxy properties like width, height and framerate from the
225      other end of the element */
226   otherpad = (pad == smokeenc->srcpad) ? smokeenc->sinkpad : smokeenc->srcpad;
227 
228   /* get template caps, we always need this to fiter the peer caps */
229   tcaps = gst_pad_get_pad_template_caps (otherpad);
230 
231   /* get any constraints on the peer pad */
232   caps = gst_pad_peer_get_caps (otherpad);
233 
234   if (caps == NULL)
235     caps = gst_caps_copy (tcaps);
236   else
237     caps = gst_caps_make_writable (caps);
238 
239   /* intersect with the template */
240   result = gst_caps_intersect (caps, tcaps);
241   gst_caps_unref (caps);
242 
243   if (pad == smokeenc->srcpad) {
244     name = "video/x-smoke";
245   } else {
246     name = "video/x-raw-yuv";
247   }
248 
249   /* we can only copy width, height, framerate from one side to the other */
250   for (i = 0; i < gst_caps_get_size (result); i++) {
251     structure = gst_caps_get_structure (result, i);
252 
253     gst_structure_set_name (structure, name);
254     gst_structure_remove_field (structure, "format");
255     /* ... but for the sink pad, we only do I420 anyway, so add that */
256     if (pad == smokeenc->sinkpad) {
257       gst_structure_set (structure, "format", GST_TYPE_FOURCC,
258           GST_STR_FOURCC ("I420"), NULL);
259     }
260   }
261 
262   gst_object_unref (smokeenc);
263 
264   return result;
265 }
266 
267 static gboolean
gst_smokeenc_setcaps(GstPad * pad,GstCaps * caps)268 gst_smokeenc_setcaps (GstPad * pad, GstCaps * caps)
269 {
270   GstSmokeEnc *smokeenc;
271   GstStructure *structure;
272   const GValue *framerate;
273   gboolean ret;
274   GstCaps *srccaps;
275 
276   smokeenc = GST_SMOKEENC (gst_pad_get_parent (pad));
277 
278   structure = gst_caps_get_structure (caps, 0);
279   framerate = gst_structure_get_value (structure, "framerate");
280   if (framerate) {
281     smokeenc->fps_num = gst_value_get_fraction_numerator (framerate);
282     smokeenc->fps_denom = gst_value_get_fraction_denominator (framerate);
283   } else {
284     smokeenc->fps_num = 0;
285     smokeenc->fps_denom = 1;
286   }
287 
288   gst_structure_get_int (structure, "width", &smokeenc->width);
289   gst_structure_get_int (structure, "height", &smokeenc->height);
290 
291   if ((smokeenc->width & 0x0f) != 0 || (smokeenc->height & 0x0f) != 0)
292     goto width_or_height_notx16;
293 
294   if (!gst_smokeenc_resync (smokeenc))
295     goto init_failed;
296 
297   srccaps = gst_caps_new_simple ("video/x-smoke",
298       "width", G_TYPE_INT, smokeenc->width,
299       "height", G_TYPE_INT, smokeenc->height,
300       "framerate", GST_TYPE_FRACTION, smokeenc->fps_num, smokeenc->fps_denom,
301       NULL);
302 
303   ret = gst_pad_set_caps (smokeenc->srcpad, srccaps);
304   gst_caps_unref (srccaps);
305 
306   gst_object_unref (smokeenc);
307 
308   return ret;
309 
310 width_or_height_notx16:
311   {
312     GST_WARNING_OBJECT (smokeenc, "width and height must be multiples of 16"
313         ", %dx%d not allowed", smokeenc->width, smokeenc->height);
314     gst_object_unref (smokeenc);
315     return FALSE;
316   }
317 init_failed:
318   {
319     GST_WARNING_OBJECT (smokeenc, "could not init decoder");
320     gst_object_unref (smokeenc);
321     return FALSE;
322   }
323 }
324 
325 static gboolean
gst_smokeenc_resync(GstSmokeEnc * smokeenc)326 gst_smokeenc_resync (GstSmokeEnc * smokeenc)
327 {
328   int ret;
329 
330   GST_DEBUG ("resync: %dx%d@%d/%dfps", smokeenc->width, smokeenc->height,
331       smokeenc->fps_num, smokeenc->fps_denom);
332 
333   if (smokeenc->info)
334     smokecodec_info_free (smokeenc->info);
335 
336   ret = smokecodec_encode_new (&smokeenc->info, smokeenc->width,
337       smokeenc->height, smokeenc->fps_num, smokeenc->fps_denom);
338 
339   if (ret != SMOKECODEC_OK)
340     goto init_failed;
341 
342   smokecodec_set_quality (smokeenc->info, smokeenc->min_quality,
343       smokeenc->max_quality);
344 
345   GST_DEBUG ("resync done");
346   return TRUE;
347 
348   /* ERRORS */
349 init_failed:
350   {
351     GST_WARNING_OBJECT (smokeenc, "smokecodec_encode_new() failed: %d", ret);
352     return FALSE;
353   }
354 }
355 
356 static GstFlowReturn
gst_smokeenc_chain(GstPad * pad,GstBuffer * buf)357 gst_smokeenc_chain (GstPad * pad, GstBuffer * buf)
358 {
359   GstSmokeEnc *smokeenc;
360   guchar *data, *outdata;
361   gulong size;
362   gint outsize;
363   guint encsize;
364   GstBuffer *outbuf;
365   SmokeCodecFlags flags;
366   GstFlowReturn ret;
367 
368   smokeenc = GST_SMOKEENC (GST_OBJECT_PARENT (pad));
369 
370   data = GST_BUFFER_DATA (buf);
371   size = GST_BUFFER_SIZE (buf);
372 
373   GST_LOG_OBJECT (smokeenc, "got buffer of %lu bytes", size);
374 
375   if (smokeenc->need_header) {
376     outbuf = gst_buffer_new_and_alloc (256);
377     outdata = GST_BUFFER_DATA (outbuf);
378 
379     GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (buf);
380     GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (buf);
381 
382     smokecodec_encode_id (smokeenc->info, outdata, &encsize);
383 
384     GST_BUFFER_SIZE (outbuf) = encsize;
385     gst_buffer_set_caps (outbuf, GST_PAD_CAPS (smokeenc->srcpad));
386 
387     ret = gst_pad_push (smokeenc->srcpad, outbuf);
388     if (ret != GST_FLOW_OK)
389       goto done;
390 
391     smokeenc->need_header = FALSE;
392   }
393 
394   encsize = outsize = smokeenc->width * smokeenc->height * 3;
395   outbuf = gst_buffer_new_and_alloc (outsize);
396   outdata = GST_BUFFER_DATA (outbuf);
397 
398   GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (buf);
399   GST_BUFFER_DURATION (outbuf) =
400       gst_util_uint64_scale_int (GST_SECOND, smokeenc->fps_denom,
401       smokeenc->fps_num);
402   gst_buffer_set_caps (outbuf, GST_PAD_CAPS (smokeenc->srcpad));
403 
404   flags = 0;
405   if ((smokeenc->frame % smokeenc->keyframe) == 0) {
406     flags |= SMOKECODEC_KEYFRAME;
407   }
408   smokecodec_set_quality (smokeenc->info, smokeenc->min_quality,
409       smokeenc->max_quality);
410   smokecodec_set_threshold (smokeenc->info, smokeenc->threshold);
411   smokecodec_encode (smokeenc->info, data, flags, outdata, &encsize);
412   gst_buffer_unref (buf);
413 
414   GST_BUFFER_SIZE (outbuf) = encsize;
415   GST_BUFFER_OFFSET (outbuf) = smokeenc->frame;
416   GST_BUFFER_OFFSET_END (outbuf) = smokeenc->frame + 1;
417 
418   ret = gst_pad_push (smokeenc->srcpad, outbuf);
419 
420   smokeenc->frame++;
421 
422 done:
423 
424   return ret;
425 }
426 
427 static void
gst_smokeenc_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)428 gst_smokeenc_set_property (GObject * object, guint prop_id,
429     const GValue * value, GParamSpec * pspec)
430 {
431   GstSmokeEnc *smokeenc;
432 
433   g_return_if_fail (GST_IS_SMOKEENC (object));
434   smokeenc = GST_SMOKEENC (object);
435 
436   switch (prop_id) {
437     case PROP_MIN_QUALITY:
438       smokeenc->min_quality = g_value_get_int (value);
439       break;
440     case PROP_MAX_QUALITY:
441       smokeenc->max_quality = g_value_get_int (value);
442       break;
443     case PROP_THRESHOLD:
444       smokeenc->threshold = g_value_get_int (value);
445       break;
446     case PROP_KEYFRAME:
447       smokeenc->keyframe = g_value_get_int (value);
448       break;
449     default:
450       break;
451   }
452 }
453 
454 static void
gst_smokeenc_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)455 gst_smokeenc_get_property (GObject * object, guint prop_id, GValue * value,
456     GParamSpec * pspec)
457 {
458   GstSmokeEnc *smokeenc;
459 
460   g_return_if_fail (GST_IS_SMOKEENC (object));
461   smokeenc = GST_SMOKEENC (object);
462 
463   switch (prop_id) {
464     case PROP_MIN_QUALITY:
465       g_value_set_int (value, smokeenc->min_quality);
466       break;
467     case PROP_MAX_QUALITY:
468       g_value_set_int (value, smokeenc->max_quality);
469       break;
470     case PROP_THRESHOLD:
471       g_value_set_int (value, smokeenc->threshold);
472       break;
473     case PROP_KEYFRAME:
474       g_value_set_int (value, smokeenc->keyframe);
475       break;
476     default:
477       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
478       break;
479   }
480 }
481 
482 static GstStateChangeReturn
gst_smokeenc_change_state(GstElement * element,GstStateChange transition)483 gst_smokeenc_change_state (GstElement * element, GstStateChange transition)
484 {
485   GstStateChangeReturn ret;
486   GstSmokeEnc *enc;
487 
488   enc = GST_SMOKEENC (element);
489 
490   switch (transition) {
491     case GST_STATE_CHANGE_READY_TO_PAUSED:
492       /* reset the initial video state */
493       enc->width = 0;
494       enc->height = 0;
495       enc->frame = 0;
496       enc->need_header = TRUE;
497     default:
498       break;
499   }
500 
501   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
502   if (ret != GST_STATE_CHANGE_SUCCESS)
503     return ret;
504 
505   switch (transition) {
506     case GST_STATE_CHANGE_PAUSED_TO_READY:
507       break;
508     default:
509       break;
510   }
511 
512   return ret;
513 }
514