6dfdb50cd4286004bfb6a00f60fec1805107edde
[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 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <string.h>
25 #include <sys/time.h>
26
27 #include "gstaasink.h"
28 #include <gst/video/video.h>
29
30 /* elementfactory information */
31 static GstElementDetails gst_aasink_details = {
32   "AA sink",
33   "Sink/Video",
34   "An ASCII art videosink",
35   "Wim Taymans <wim.taymans@chello.be>"
36 };
37
38 /* aasink signals and args */
39 enum {
40   SIGNAL_FRAME_DISPLAYED,
41   SIGNAL_HAVE_SIZE,
42   LAST_SIGNAL
43 };
44
45
46 enum {
47   ARG_0,
48   ARG_WIDTH,
49   ARG_HEIGHT,
50   ARG_DRIVER,
51   ARG_DITHER,
52   ARG_BRIGHTNESS,
53   ARG_CONTRAST,
54   ARG_GAMMA,
55   ARG_INVERSION,
56   ARG_RANDOMVAL,
57   ARG_FRAMES_DISPLAYED,
58   ARG_FRAME_TIME,
59 };
60
61 static GstStaticPadTemplate sink_template =
62 GST_STATIC_PAD_TEMPLATE (
63   "sink",
64   GST_PAD_SINK,
65   GST_PAD_ALWAYS,
66   GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420"))
67 );
68
69 static void     gst_aasink_base_init    (gpointer g_class);
70 static void     gst_aasink_class_init   (GstAASinkClass *klass);
71 static void     gst_aasink_init         (GstAASink *aasink);
72
73 static void     gst_aasink_set_clock    (GstElement *element, GstClock *clock);
74 static void     gst_aasink_chain        (GstPad *pad, GstData *_data);
75
76 static void     gst_aasink_set_property (GObject *object, guint prop_id, 
77                                          const GValue *value, GParamSpec *pspec);
78 static void     gst_aasink_get_property (GObject *object, guint prop_id, 
79                                          GValue *value, GParamSpec *pspec);
80
81 static GstElementStateReturn gst_aasink_change_state (GstElement *element);
82
83 static GstElementClass *parent_class = NULL;
84 static guint gst_aasink_signals[LAST_SIGNAL] = { 0 };
85
86 GType
87 gst_aasink_get_type (void)
88 {
89   static GType aasink_type = 0;
90
91   if (!aasink_type) {
92     static const GTypeInfo aasink_info = {
93       sizeof(GstAASinkClass),
94       gst_aasink_base_init,
95       NULL,
96       (GClassInitFunc) gst_aasink_class_init,
97       NULL,
98       NULL,
99       sizeof(GstAASink),
100       0,
101       (GInstanceInitFunc)gst_aasink_init,
102     };
103     aasink_type = g_type_register_static(GST_TYPE_ELEMENT, "GstAASink", &aasink_info, 0);
104   }
105   return aasink_type;
106 }
107
108 #define GST_TYPE_AADRIVERS (gst_aasink_drivers_get_type())
109 static GType
110 gst_aasink_drivers_get_type (void)
111 {
112   static GType driver_type = 0;
113   if (!driver_type) {
114     GEnumValue *drivers;
115     const struct aa_driver *driver;
116     gint n_drivers;
117     gint i;
118
119     for (n_drivers = 0; aa_drivers[n_drivers]; n_drivers++){
120       /* count number of drivers */
121     }
122     
123     drivers = g_new0(GEnumValue, n_drivers + 1);
124
125     for (i = 0; i < n_drivers; i++){
126       driver = aa_drivers[i];
127       drivers[i].value = i;
128       drivers[i].value_name = g_strdup (driver->shortname);
129       drivers[i].value_nick = g_strdup (driver->name);
130     }
131     drivers[i].value = 0;
132     drivers[i].value_name = NULL;
133     drivers[i].value_nick = NULL;
134
135     driver_type = g_enum_register_static ("GstAASinkDrivers", drivers);
136   }
137   return driver_type;
138 }
139
140 #define GST_TYPE_AADITHER (gst_aasink_dither_get_type())
141 static GType
142 gst_aasink_dither_get_type (void)
143 {
144   static GType dither_type = 0;
145   if (!dither_type) {
146     GEnumValue *ditherers;
147     gint n_ditherers;
148     gint i;
149
150     for (n_ditherers = 0; aa_dithernames[n_ditherers]; n_ditherers++){
151       /* count number of ditherers */
152     }
153     
154     ditherers = g_new0(GEnumValue, n_ditherers + 1);
155
156     for (i = 0; i < n_ditherers; i++){
157       ditherers[i].value = i;
158       ditherers[i].value_name = g_strdup (aa_dithernames[i]);
159       ditherers[i].value_nick = g_strdup (aa_dithernames[i]);
160     }
161     ditherers[i].value = 0;
162     ditherers[i].value_name = NULL;
163     ditherers[i].value_nick = NULL;
164
165     dither_type = g_enum_register_static ("GstAASinkDitherers", ditherers);
166   }
167   return dither_type;
168 }
169
170 static void
171 gst_aasink_base_init (gpointer g_class)
172 {
173   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
174
175   gst_element_class_add_pad_template (element_class,
176       gst_static_pad_template_get (&sink_template));
177   gst_element_class_set_details (element_class, &gst_aasink_details);
178 }
179
180 static void
181 gst_aasink_class_init (GstAASinkClass *klass)
182 {
183   GObjectClass *gobject_class;
184   GstElementClass *gstelement_class;
185
186   gobject_class = (GObjectClass*)klass;
187   gstelement_class = (GstElementClass*)klass;
188
189   parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
190
191   g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_WIDTH,
192     g_param_spec_int("width","width","width",
193                      G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); /* CHECKME */
194   g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_HEIGHT,
195     g_param_spec_int("height","height","height",
196                      G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); /* CHECKME */
197   g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_DRIVER,
198     g_param_spec_enum("driver","driver","driver",
199                       GST_TYPE_AADRIVERS,0,G_PARAM_READWRITE)); /* CHECKME! */
200   g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_DITHER,
201     g_param_spec_enum("dither","dither","dither",
202                       GST_TYPE_AADITHER,0,G_PARAM_READWRITE)); /* CHECKME! */
203   g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_BRIGHTNESS,
204     g_param_spec_int("brightness","brightness","brightness",
205                      G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); /* CHECKME */
206   g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_CONTRAST,
207     g_param_spec_int("contrast","contrast","contrast",
208                      G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); /* CHECKME */
209   g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_GAMMA,
210     g_param_spec_float("gamma","gamma","gamma",
211                        0.0,5.0,1.0,G_PARAM_READWRITE)); /* CHECKME */
212   g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_INVERSION,
213     g_param_spec_boolean("inversion","inversion","inversion",
214                          TRUE,G_PARAM_READWRITE)); /* CHECKME */
215   g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_RANDOMVAL,
216     g_param_spec_int("randomval","randomval","randomval",
217                      G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); /* CHECKME */
218   g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_FRAMES_DISPLAYED,
219     g_param_spec_int("frames_displayed","frames_displayed","frames_displayed",
220                      G_MININT,G_MAXINT,0,G_PARAM_READABLE)); /* CHECKME */
221   g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_FRAME_TIME,
222     g_param_spec_int("frame_time","frame_time","frame_time",
223                      G_MININT,G_MAXINT,0,G_PARAM_READABLE)); /* CHECKME */
224
225   gobject_class->set_property = gst_aasink_set_property;
226   gobject_class->get_property = gst_aasink_get_property;
227
228   gst_aasink_signals[SIGNAL_FRAME_DISPLAYED] =
229     g_signal_new ("frame-displayed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST,
230                    G_STRUCT_OFFSET (GstAASinkClass, frame_displayed), NULL, NULL,
231                    g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
232   gst_aasink_signals[SIGNAL_HAVE_SIZE] =
233     g_signal_new ("have-size", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST,
234                    G_STRUCT_OFFSET (GstAASinkClass, have_size), NULL, NULL,
235                    gst_marshal_VOID__INT_INT, G_TYPE_NONE, 2,
236                    G_TYPE_UINT, G_TYPE_UINT);
237
238   gstelement_class->change_state = gst_aasink_change_state;
239   gstelement_class->set_clock    = gst_aasink_set_clock;
240 }
241
242 static GstCaps *
243 gst_aasink_fixate (GstPad *pad, const GstCaps *caps)
244 {
245   GstStructure *structure;
246   GstCaps *newcaps;
247
248   if (gst_caps_get_size (caps) > 1) return NULL;
249
250   newcaps = gst_caps_copy (caps);
251   structure = gst_caps_get_structure (newcaps, 0);
252
253   if (gst_caps_structure_fixate_field_nearest_int (structure, "width", 320)) {
254     return newcaps;
255   }
256   if (gst_caps_structure_fixate_field_nearest_int (structure, "height", 240)) {
257     return newcaps;
258   }
259   if (gst_caps_structure_fixate_field_nearest_double (structure, "framerate",
260                                                       30.0)) {
261     return newcaps;
262   }
263
264   gst_caps_free (newcaps);
265   return NULL;
266 }
267
268 static GstPadLinkReturn
269 gst_aasink_sinkconnect (GstPad *pad, const GstCaps *caps)
270 {
271   GstAASink *aasink;
272   GstStructure *structure;
273
274   aasink = GST_AASINK (gst_pad_get_parent (pad));
275
276   structure = gst_caps_get_structure (caps, 0);
277   gst_structure_get_int (structure, "width", &aasink->width);
278   gst_structure_get_int (structure, "height", &aasink->height);
279
280   /* FIXME aasink->format is never set */
281
282   GST_DEBUG ("aasink: setting %08lx (" GST_FOURCC_FORMAT ")",
283                  aasink->format, GST_FOURCC_ARGS(aasink->format));
284   
285   g_signal_emit( G_OBJECT (aasink), gst_aasink_signals[SIGNAL_HAVE_SIZE], 0,
286                  aasink->width, aasink->height);
287
288   return GST_PAD_LINK_OK;
289 }
290
291 static void
292 gst_aasink_set_clock (GstElement *element, GstClock *clock)
293 {
294   GstAASink *aasink = GST_AASINK (element);
295
296   aasink->clock = clock;
297 }
298
299 static void
300 gst_aasink_init (GstAASink *aasink)
301 {
302   aasink->sinkpad = gst_pad_new_from_template (
303       gst_element_get_pad_template (GST_ELEMENT (aasink), "sink"), "sink");
304   gst_element_add_pad (GST_ELEMENT (aasink), aasink->sinkpad);
305   gst_pad_set_chain_function (aasink->sinkpad, gst_aasink_chain);
306   gst_pad_set_link_function (aasink->sinkpad, gst_aasink_sinkconnect);
307   gst_pad_set_fixate_function (aasink->sinkpad, gst_aasink_fixate);
308
309   memcpy(&aasink->ascii_surf, &aa_defparams, sizeof (struct aa_hardware_params));
310   aasink->ascii_parms.bright = 0;
311   aasink->ascii_parms.contrast = 16;
312   aasink->ascii_parms.gamma = 1.0;
313   aasink->ascii_parms.dither = 0;
314   aasink->ascii_parms.inversion = 0;
315   aasink->ascii_parms.randomval = 0;
316   aasink->aa_driver = 0;
317
318   aasink->width = -1;
319   aasink->height = -1;
320
321   aasink->clock = NULL;
322
323   GST_FLAG_SET(aasink, GST_ELEMENT_THREAD_SUGGESTED);
324 }
325
326 static void
327 gst_aasink_scale (GstAASink *aasink, gchar *src, gchar *dest,
328                   gint sw, gint sh, 
329                   gint dw, gint dh)
330 {
331   gint ypos, yinc, y;
332   gint xpos, xinc, x;
333
334   g_return_if_fail ((dw != 0) && (dh != 0));
335
336   ypos = 0x10000;
337   yinc = (sh << 16) / dh;
338   xinc = (sw << 16) / dw;
339
340   for (y = dh; y; y--) {
341     while (ypos > 0x10000) {
342       ypos -= 0x10000;
343       src  += sw;
344     }
345     xpos = 0x10000;
346     {
347       guchar *destp = dest;
348       guchar *srcp = src;
349
350       for ( x=dw; x; x-- ) {
351         while (xpos >= 0x10000L) {
352           srcp++;
353           xpos -= 0x10000L;
354         }
355         *destp++ = *srcp;
356         xpos     += xinc;
357       }
358     }
359     dest += dw;
360     ypos += yinc;
361   }
362 }
363
364 static void
365 gst_aasink_chain (GstPad *pad, GstData *_data)
366 {
367   GstBuffer *buf = GST_BUFFER (_data);
368   GstAASink *aasink;
369
370   g_return_if_fail (pad != NULL);
371   g_return_if_fail (GST_IS_PAD (pad));
372   g_return_if_fail (buf != NULL);
373
374   aasink = GST_AASINK (gst_pad_get_parent (pad));
375
376   gst_aasink_scale (aasink,
377                     GST_BUFFER_DATA (buf),              /* src */
378                     aa_image (aasink->context),         /* dest */
379                     aasink->width,                      /* sw */
380                     aasink->height,                     /* sh */
381                     aa_imgwidth (aasink->context),      /* dw */
382                     aa_imgheight (aasink->context));    /* dh */
383
384   GST_DEBUG ("videosink: clock wait: %" G_GUINT64_FORMAT, GST_BUFFER_TIMESTAMP(buf));
385
386   if (aasink->clock && GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
387     gst_element_wait (GST_ELEMENT (aasink), GST_BUFFER_TIMESTAMP (buf));
388   }
389
390   aa_render (aasink->context, &aasink->ascii_parms, 
391                   0, 0, aa_imgwidth (aasink->context), aa_imgheight (aasink->context));
392   aa_flush (aasink->context);
393   aa_getevent (aasink->context, FALSE);
394
395   g_signal_emit(G_OBJECT(aasink),gst_aasink_signals[SIGNAL_FRAME_DISPLAYED], 0);
396
397   gst_buffer_unref(buf);
398 }
399
400
401 static void
402 gst_aasink_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
403 {
404   GstAASink *aasink;
405
406   /* it's not null if we got it, but it might not be ours */
407   g_return_if_fail (GST_IS_AASINK (object));
408
409   aasink = GST_AASINK (object);
410
411   switch (prop_id) {
412     case ARG_WIDTH:
413       aasink->ascii_surf.width = g_value_get_int (value);
414       break;
415     case ARG_HEIGHT:
416       aasink->ascii_surf.height = g_value_get_int (value);
417       break;
418     case ARG_DRIVER: {
419       aasink->aa_driver = g_value_get_enum (value);
420       break;
421     }
422     case ARG_DITHER: {
423       aasink->ascii_parms.dither = g_value_get_enum (value);
424       break;
425     }
426     case ARG_BRIGHTNESS: {
427       aasink->ascii_parms.bright = g_value_get_int (value);
428       break;
429     }
430     case ARG_CONTRAST: {
431       aasink->ascii_parms.contrast = g_value_get_int (value);
432       break;
433     }
434     case ARG_GAMMA: {
435       aasink->ascii_parms.gamma = g_value_get_float (value);
436       break;
437     }
438     case ARG_INVERSION: {
439       aasink->ascii_parms.inversion = g_value_get_boolean (value);
440       break;
441     }
442     case ARG_RANDOMVAL: {
443       aasink->ascii_parms.randomval = g_value_get_int (value);
444       break;
445     }
446     default:
447       break;
448   }
449 }
450
451 static void
452 gst_aasink_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
453 {
454   GstAASink *aasink;
455
456   /* it's not null if we got it, but it might not be ours */
457   aasink = GST_AASINK(object);
458
459   switch (prop_id) {
460     case ARG_WIDTH: {
461       g_value_set_int (value, aasink->ascii_surf.width);
462       break;
463     }
464     case ARG_HEIGHT: {
465       g_value_set_int (value, aasink->ascii_surf.height);
466       break;
467     }
468     case ARG_DRIVER: {
469       g_value_set_enum (value, aasink->aa_driver);
470       break;
471     }
472     case ARG_DITHER: {
473       g_value_set_enum (value, aasink->ascii_parms.dither);
474       break;
475     }
476     case ARG_BRIGHTNESS: {
477       g_value_set_int (value, aasink->ascii_parms.bright);
478       break;
479     }
480     case ARG_CONTRAST: {
481       g_value_set_int (value, aasink->ascii_parms.contrast);
482       break;
483     }
484     case ARG_GAMMA: {
485       g_value_set_float (value, aasink->ascii_parms.gamma);
486       break;
487     }
488     case ARG_INVERSION: {
489       g_value_set_boolean (value, aasink->ascii_parms.inversion);
490       break;
491     }
492     case ARG_RANDOMVAL: {
493       g_value_set_int (value, aasink->ascii_parms.randomval);
494       break;
495     }
496     case ARG_FRAMES_DISPLAYED: {
497       g_value_set_int (value, aasink->frames_displayed);
498       break;
499     }
500     case ARG_FRAME_TIME: {
501       g_value_set_int (value, aasink->frame_time/1000000);
502       break;
503     }
504     default: {
505       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
506       break;
507     }
508   }
509 }
510
511 static gboolean
512 gst_aasink_open (GstAASink *aasink)
513 {
514   g_return_val_if_fail (!GST_FLAG_IS_SET (aasink ,GST_AASINK_OPEN), FALSE);
515
516   aa_recommendhidisplay(aa_drivers[aasink->aa_driver]->shortname);
517
518   aasink->context = aa_autoinit (&aasink->ascii_surf);
519   if (aasink->context == NULL) {
520     GST_ELEMENT_ERROR (GST_ELEMENT (aasink), LIBRARY, TOO_LAZY, (NULL),
521                          ("error opening aalib context"));
522     return FALSE;
523   }
524   aa_autoinitkbd(aasink->context, 0);
525   aa_resizehandler(aasink->context, (void *)aa_resize);
526
527   GST_FLAG_SET (aasink, GST_AASINK_OPEN);
528
529   return TRUE;
530 }
531
532 static void
533 gst_aasink_close (GstAASink *aasink)
534 {
535   g_return_if_fail (GST_FLAG_IS_SET (aasink ,GST_AASINK_OPEN));
536
537   aa_close (aasink->context);
538
539   GST_FLAG_UNSET (aasink, GST_AASINK_OPEN);
540 }
541
542 static GstElementStateReturn
543 gst_aasink_change_state (GstElement *element)
544 {
545   g_return_val_if_fail (GST_IS_AASINK (element), GST_STATE_FAILURE);
546
547   if (GST_STATE_PENDING (element) == GST_STATE_NULL) {
548     if (GST_FLAG_IS_SET (element, GST_AASINK_OPEN))
549       gst_aasink_close (GST_AASINK (element));
550   } else {
551     if (!GST_FLAG_IS_SET (element, GST_AASINK_OPEN)) {
552       if (!gst_aasink_open (GST_AASINK (element)))
553         return GST_STATE_FAILURE;
554     }
555   }
556
557   if (GST_ELEMENT_CLASS (parent_class)->change_state)
558     return GST_ELEMENT_CLASS (parent_class)->change_state (element);
559
560   return GST_STATE_SUCCESS;
561 }
562
563 static gboolean
564 plugin_init (GstPlugin *plugin)
565 {
566   if (!gst_element_register (plugin, "aasink", GST_RANK_NONE, GST_TYPE_AASINK))
567     return FALSE;
568
569   return TRUE;
570 }
571
572 GST_PLUGIN_DEFINE (
573   GST_VERSION_MAJOR,
574   GST_VERSION_MINOR,
575   "aasink",
576   "ASCII Art video sink",
577   plugin_init,
578   VERSION,
579   "GPL",
580   GST_PACKAGE,
581   GST_ORIGIN
582 )