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