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