Merge branch 'master' into 0.11
[platform/upstream/gst-plugins-good.git] / ext / aalib / gstaasink.c
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., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 /**
20  * SECTION:element-aasink
21  * @see_also: #GstCACASink
22  *
23  * Displays video as b/w ascii art.
24  *
25  * <refsect2>
26  * <title>Example launch line</title>
27  * |[
28  * gst-launch filesrc location=test.avi ! decodebin ! ffmpegcolorspace ! aasink
29  * ]| This pipeline renders a video to ascii art into a separate window.
30  * |[
31  * gst-launch filesrc location=test.avi ! decodebin ! ffmpegcolorspace ! aasink driver=curses
32  * ]| This pipeline renders a video to ascii art into the current terminal.
33  * </refsect2>
34  */
35
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
39
40 #include <string.h>
41 #include <sys/time.h>
42
43 #include "gstaasink.h"
44
45 /* aasink signals and args */
46 enum
47 {
48   LAST_SIGNAL
49 };
50
51
52 enum
53 {
54   PROP_0,
55   PROP_WIDTH,
56   PROP_HEIGHT,
57   PROP_DRIVER,
58   PROP_DITHER,
59   PROP_BRIGHTNESS,
60   PROP_CONTRAST,
61   PROP_GAMMA,
62   PROP_INVERSION,
63   PROP_RANDOMVAL,
64   PROP_FRAMES_DISPLAYED,
65   PROP_FRAME_TIME
66 };
67
68 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
69     GST_PAD_SINK,
70     GST_PAD_ALWAYS,
71     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("I420"))
72     );
73
74 static gboolean gst_aasink_setcaps (GstBaseSink * pad, GstCaps * caps);
75 static void gst_aasink_get_times (GstBaseSink * sink, GstBuffer * buffer,
76     GstClockTime * start, GstClockTime * end);
77 static GstFlowReturn gst_aasink_render (GstBaseSink * basesink,
78     GstBuffer * buffer);
79
80 static void gst_aasink_set_property (GObject * object, guint prop_id,
81     const GValue * value, GParamSpec * pspec);
82 static void gst_aasink_get_property (GObject * object, guint prop_id,
83     GValue * value, GParamSpec * pspec);
84
85 static GstStateChangeReturn gst_aasink_change_state (GstElement * element,
86     GstStateChange transition);
87
88 #define gst_aasink_parent_class parent_class
89 G_DEFINE_TYPE (GstAASink, gst_aasink, GST_TYPE_BASE_SINK);
90
91 #define GST_TYPE_AADRIVERS (gst_aasink_drivers_get_type())
92 static GType
93 gst_aasink_drivers_get_type (void)
94 {
95   static GType driver_type = 0;
96
97   if (!driver_type) {
98     GEnumValue *drivers;
99     const struct aa_driver *driver;
100     gint n_drivers;
101     gint i;
102
103     for (n_drivers = 0; aa_drivers[n_drivers]; n_drivers++) {
104       /* count number of drivers */
105     }
106
107     drivers = g_new0 (GEnumValue, n_drivers + 1);
108
109     for (i = 0; i < n_drivers; i++) {
110       driver = aa_drivers[i];
111       drivers[i].value = i;
112       drivers[i].value_name = g_strdup (driver->name);
113       drivers[i].value_nick = g_utf8_strdown (driver->shortname, -1);
114     }
115     drivers[i].value = 0;
116     drivers[i].value_name = NULL;
117     drivers[i].value_nick = NULL;
118
119     driver_type = g_enum_register_static ("GstAASinkDrivers", drivers);
120   }
121   return driver_type;
122 }
123
124 #define GST_TYPE_AADITHER (gst_aasink_dither_get_type())
125 static GType
126 gst_aasink_dither_get_type (void)
127 {
128   static GType dither_type = 0;
129
130   if (!dither_type) {
131     GEnumValue *ditherers;
132     gint n_ditherers;
133     gint i;
134
135     for (n_ditherers = 0; aa_dithernames[n_ditherers]; n_ditherers++) {
136       /* count number of ditherers */
137     }
138
139     ditherers = g_new0 (GEnumValue, n_ditherers + 1);
140
141     for (i = 0; i < n_ditherers; i++) {
142       ditherers[i].value = i;
143       ditherers[i].value_name = g_strdup (aa_dithernames[i]);
144       ditherers[i].value_nick =
145           g_strdelimit (g_strdup (aa_dithernames[i]), " _", '-');
146     }
147     ditherers[i].value = 0;
148     ditherers[i].value_name = NULL;
149     ditherers[i].value_nick = NULL;
150
151     dither_type = g_enum_register_static ("GstAASinkDitherers", ditherers);
152   }
153   return dither_type;
154 }
155
156 static void
157 gst_aasink_class_init (GstAASinkClass * klass)
158 {
159   GObjectClass *gobject_class;
160   GstElementClass *gstelement_class;
161   GstBaseSinkClass *gstbasesink_class;
162
163   gobject_class = (GObjectClass *) klass;
164   gstelement_class = (GstElementClass *) klass;
165   gstbasesink_class = (GstBaseSinkClass *) klass;
166
167   gobject_class->set_property = gst_aasink_set_property;
168   gobject_class->get_property = gst_aasink_get_property;
169
170   /* FIXME: add long property descriptions */
171   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WIDTH,
172       g_param_spec_int ("width", "width", "width", G_MININT, G_MAXINT, 0,
173           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
174   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HEIGHT,
175       g_param_spec_int ("height", "height", "height", G_MININT, G_MAXINT, 0,
176           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
177   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRIVER,
178       g_param_spec_enum ("driver", "driver", "driver", GST_TYPE_AADRIVERS, 0,
179           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
180   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DITHER,
181       g_param_spec_enum ("dither", "dither", "dither", GST_TYPE_AADITHER, 0,
182           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
183   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BRIGHTNESS,
184       g_param_spec_int ("brightness", "brightness", "brightness", G_MININT,
185           G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
186   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CONTRAST,
187       g_param_spec_int ("contrast", "contrast", "contrast", G_MININT, G_MAXINT,
188           0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
189   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_GAMMA,
190       g_param_spec_float ("gamma", "gamma", "gamma", 0.0, 5.0, 1.0,
191           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
192   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_INVERSION,
193       g_param_spec_boolean ("inversion", "inversion", "inversion", TRUE,
194           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
195   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RANDOMVAL,
196       g_param_spec_int ("randomval", "randomval", "randomval", G_MININT,
197           G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
198   g_object_class_install_property (G_OBJECT_CLASS (klass),
199       PROP_FRAMES_DISPLAYED, g_param_spec_int ("frames-displayed",
200           "frames displayed", "frames displayed", G_MININT, G_MAXINT, 0,
201           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
202   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FRAME_TIME,
203       g_param_spec_int ("frame-time", "frame time", "frame time", G_MININT,
204           G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
205
206   gst_element_class_add_pad_template (gstelement_class,
207       gst_static_pad_template_get (&sink_template));
208
209   gst_element_class_set_details_simple (gstelement_class,
210       "ASCII art video sink", "Sink/Video", "An ASCII art videosink",
211       "Wim Taymans <wim.taymans@chello.be>");
212
213   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_aasink_change_state);
214
215   gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_aasink_setcaps);
216   gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_aasink_get_times);
217   gstbasesink_class->preroll = GST_DEBUG_FUNCPTR (gst_aasink_render);
218   gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_aasink_render);
219 }
220
221 static void
222 gst_aasink_fixate (GstPad * pad, GstCaps * caps)
223 {
224   GstStructure *structure;
225
226   structure = gst_caps_get_structure (caps, 0);
227
228   gst_structure_fixate_field_nearest_int (structure, "width", 320);
229   gst_structure_fixate_field_nearest_int (structure, "height", 240);
230   gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1);
231 }
232
233 static gboolean
234 gst_aasink_setcaps (GstBaseSink * basesink, GstCaps * caps)
235 {
236   GstAASink *aasink;
237   GstVideoInfo info;
238
239   aasink = GST_AASINK (basesink);
240
241   if (!gst_video_info_from_caps (&info, caps))
242     goto invalid_caps;
243
244   aasink->info = info;
245
246   return TRUE;
247
248   /* ERRORS */
249 invalid_caps:
250   {
251     GST_DEBUG_OBJECT (aasink, "invalid caps");
252     return FALSE;
253   }
254 }
255
256 static void
257 gst_aasink_init (GstAASink * aasink)
258 {
259   GstPad *pad;
260
261   pad = GST_BASE_SINK_PAD (aasink);
262   gst_pad_set_fixatecaps_function (pad, gst_aasink_fixate);
263
264   memcpy (&aasink->ascii_surf, &aa_defparams,
265       sizeof (struct aa_hardware_params));
266   aasink->ascii_parms.bright = 0;
267   aasink->ascii_parms.contrast = 16;
268   aasink->ascii_parms.gamma = 1.0;
269   aasink->ascii_parms.dither = 0;
270   aasink->ascii_parms.inversion = 0;
271   aasink->ascii_parms.randomval = 0;
272   aasink->aa_driver = 0;
273 }
274
275 static void
276 gst_aasink_scale (GstAASink * aasink, guchar * src, guchar * dest,
277     gint sw, gint sh, gint dw, gint dh)
278 {
279   gint ypos, yinc, y;
280   gint xpos, xinc, x;
281
282   g_return_if_fail ((dw != 0) && (dh != 0));
283
284   ypos = 0x10000;
285   yinc = (sh << 16) / dh;
286   xinc = (sw << 16) / dw;
287
288   for (y = dh; y; y--) {
289     while (ypos > 0x10000) {
290       ypos -= 0x10000;
291       src += sw;
292     }
293     xpos = 0x10000;
294     {
295       guchar *destp = dest;
296       guchar *srcp = src;
297
298       for (x = dw; x; x--) {
299         while (xpos >= 0x10000L) {
300           srcp++;
301           xpos -= 0x10000L;
302         }
303         *destp++ = *srcp;
304         xpos += xinc;
305       }
306     }
307     dest += dw;
308     ypos += yinc;
309   }
310 }
311
312 static void
313 gst_aasink_get_times (GstBaseSink * sink, GstBuffer * buffer,
314     GstClockTime * start, GstClockTime * end)
315 {
316   *start = GST_BUFFER_TIMESTAMP (buffer);
317   if (GST_BUFFER_DURATION_IS_VALID (buffer))
318     *end = *start + GST_BUFFER_DURATION (buffer);
319 }
320
321 static GstFlowReturn
322 gst_aasink_render (GstBaseSink * basesink, GstBuffer * buffer)
323 {
324   GstAASink *aasink;
325   GstVideoFrame frame;
326
327   aasink = GST_AASINK (basesink);
328
329   GST_DEBUG ("render");
330
331   if (!gst_video_frame_map (&frame, &aasink->info, buffer, GST_MAP_READ))
332     goto invalid_frame;
333
334   gst_aasink_scale (aasink, GST_VIDEO_FRAME_PLANE_DATA (&frame, 0),     /* src */
335       aa_image (aasink->context),       /* dest */
336       GST_VIDEO_INFO_WIDTH (&aasink->info),     /* sw */
337       GST_VIDEO_INFO_HEIGHT (&aasink->info),    /* sh */
338       aa_imgwidth (aasink->context),    /* dw */
339       aa_imgheight (aasink->context));  /* dh */
340
341   aa_render (aasink->context, &aasink->ascii_parms,
342       0, 0, aa_imgwidth (aasink->context), aa_imgheight (aasink->context));
343   aa_flush (aasink->context);
344   aa_getevent (aasink->context, FALSE);
345   gst_video_frame_unmap (&frame);
346
347   return GST_FLOW_OK;
348
349   /* ERRORS */
350 invalid_frame:
351   {
352     GST_DEBUG_OBJECT (aasink, "invalid frame");
353     return GST_FLOW_ERROR;
354   }
355 }
356
357
358 static void
359 gst_aasink_set_property (GObject * object, guint prop_id, const GValue * value,
360     GParamSpec * pspec)
361 {
362   GstAASink *aasink;
363
364   aasink = GST_AASINK (object);
365
366   switch (prop_id) {
367     case PROP_WIDTH:
368       aasink->ascii_surf.width = g_value_get_int (value);
369       break;
370     case PROP_HEIGHT:
371       aasink->ascii_surf.height = g_value_get_int (value);
372       break;
373     case PROP_DRIVER:{
374       aasink->aa_driver = g_value_get_enum (value);
375       break;
376     }
377     case PROP_DITHER:{
378       aasink->ascii_parms.dither = g_value_get_enum (value);
379       break;
380     }
381     case PROP_BRIGHTNESS:{
382       aasink->ascii_parms.bright = g_value_get_int (value);
383       break;
384     }
385     case PROP_CONTRAST:{
386       aasink->ascii_parms.contrast = g_value_get_int (value);
387       break;
388     }
389     case PROP_GAMMA:{
390       aasink->ascii_parms.gamma = g_value_get_float (value);
391       break;
392     }
393     case PROP_INVERSION:{
394       aasink->ascii_parms.inversion = g_value_get_boolean (value);
395       break;
396     }
397     case PROP_RANDOMVAL:{
398       aasink->ascii_parms.randomval = g_value_get_int (value);
399       break;
400     }
401     default:
402       break;
403   }
404 }
405
406 static void
407 gst_aasink_get_property (GObject * object, guint prop_id, GValue * value,
408     GParamSpec * pspec)
409 {
410   GstAASink *aasink;
411
412   aasink = GST_AASINK (object);
413
414   switch (prop_id) {
415     case PROP_WIDTH:{
416       g_value_set_int (value, aasink->ascii_surf.width);
417       break;
418     }
419     case PROP_HEIGHT:{
420       g_value_set_int (value, aasink->ascii_surf.height);
421       break;
422     }
423     case PROP_DRIVER:{
424       g_value_set_enum (value, aasink->aa_driver);
425       break;
426     }
427     case PROP_DITHER:{
428       g_value_set_enum (value, aasink->ascii_parms.dither);
429       break;
430     }
431     case PROP_BRIGHTNESS:{
432       g_value_set_int (value, aasink->ascii_parms.bright);
433       break;
434     }
435     case PROP_CONTRAST:{
436       g_value_set_int (value, aasink->ascii_parms.contrast);
437       break;
438     }
439     case PROP_GAMMA:{
440       g_value_set_float (value, aasink->ascii_parms.gamma);
441       break;
442     }
443     case PROP_INVERSION:{
444       g_value_set_boolean (value, aasink->ascii_parms.inversion);
445       break;
446     }
447     case PROP_RANDOMVAL:{
448       g_value_set_int (value, aasink->ascii_parms.randomval);
449       break;
450     }
451     case PROP_FRAMES_DISPLAYED:{
452       g_value_set_int (value, aasink->frames_displayed);
453       break;
454     }
455     case PROP_FRAME_TIME:{
456       g_value_set_int (value, aasink->frame_time / 1000000);
457       break;
458     }
459     default:{
460       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
461       break;
462     }
463   }
464 }
465
466 static gboolean
467 gst_aasink_open (GstAASink * aasink)
468 {
469   if (!aasink->context) {
470     aa_recommendhidisplay (aa_drivers[aasink->aa_driver]->shortname);
471
472     aasink->context = aa_autoinit (&aasink->ascii_surf);
473     if (aasink->context == NULL) {
474       GST_ELEMENT_ERROR (GST_ELEMENT (aasink), LIBRARY, TOO_LAZY, (NULL),
475           ("error opening aalib context"));
476       return FALSE;
477     }
478     aa_autoinitkbd (aasink->context, 0);
479     aa_resizehandler (aasink->context, (void *) aa_resize);
480   }
481   return TRUE;
482 }
483
484 static gboolean
485 gst_aasink_close (GstAASink * aasink)
486 {
487   aa_close (aasink->context);
488   aasink->context = NULL;
489
490   return TRUE;
491 }
492
493 static GstStateChangeReturn
494 gst_aasink_change_state (GstElement * element, GstStateChange transition)
495 {
496   GstStateChangeReturn ret;
497
498
499   switch (transition) {
500     case GST_STATE_CHANGE_NULL_TO_READY:
501       break;
502     case GST_STATE_CHANGE_READY_TO_PAUSED:
503       if (!gst_aasink_open (GST_AASINK (element)))
504         goto open_failed;
505       break;
506     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
507       break;
508     default:
509       break;
510   }
511
512   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
513
514   switch (transition) {
515     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
516       break;
517     case GST_STATE_CHANGE_PAUSED_TO_READY:
518       break;
519     case GST_STATE_CHANGE_READY_TO_NULL:
520       gst_aasink_close (GST_AASINK (element));
521       break;
522     default:
523       break;
524   }
525
526   return ret;
527
528 open_failed:
529   {
530     return GST_STATE_CHANGE_FAILURE;
531   }
532 }
533
534 static gboolean
535 plugin_init (GstPlugin * plugin)
536 {
537   if (!gst_element_register (plugin, "aasink", GST_RANK_NONE, GST_TYPE_AASINK))
538     return FALSE;
539
540   return TRUE;
541 }
542
543 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
544     GST_VERSION_MINOR,
545     "aasink",
546     "ASCII Art video sink",
547     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);