98ec91d27ecd65ecb4e810f6fa742474de18444b
[platform/upstream/gst-plugins-good.git] / ext / jack / gstjack.c
1 /* -*- Mode: C; c-basic-offset: 4 -*- */
2 /*
3     Copyright (C) 2002, 2003 Andy Wingo <wingo@pobox.com>
4
5     This library is free software; you can redistribute it and/or
6     modify it under the terms of the GNU General Public
7     License as published by the Free Software Foundation; either
8     version 2 of the License, or (at your option) any later version.
9
10     This library is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13     General Public License for more details.
14
15     You should have received a copy of the GNU General Public
16     License along with this library; if not, write to the Free
17     Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 #include <stdlib.h>
24 #include <string.h>
25 #include "gstjack.h"
26 #include <gst/audio/audio.h>
27
28 /* TODO:
29
30    - work out the src side (caps setting, etc)
31
32    future core TODO:
33    - make a jack clock provider
34    - add GST_ELEMENT_FIXED_DATA_RATE, GST_ELEMENT_QOS,
35      GST_ELEMENT_CHANGES_DATA_RATE element flags, and make the scheduler
36      sensitive to them
37 */
38
39 /* elementfactory information */
40 static GstElementDetails gst_jack_bin_details = {
41     "Jack Bin",
42     "Generic/Bin",
43     "GPL",
44     "Jack processing bin",
45     VERSION,
46     "Andy Wingo <wingo@pobox.com>",
47     "(C) 2002, 2003"
48 };
49
50 static GstElementDetails gst_jack_sink_details = {  
51     "Jack Sink",
52     "Sink/Audio",
53     "GPL",
54     "Output to a Jack processing network",
55     VERSION,
56     "Andy Wingo <wingo@pobox.com>",
57     "(C) 2002, 2003"
58 };
59
60 static GstElementDetails gst_jack_src_details = {  
61     "Jack Src",
62     "Source/Audio",
63     "GPL",
64     "Input from a Jack processing network",
65     VERSION,
66     "Andy Wingo <wingo@pobox.com>",
67     "(C) 2002, 2003",
68 };
69
70
71 static GHashTable *port_name_counts = NULL;
72 static GstElementClass *parent_class = NULL;
73
74 static void             gst_jack_init(GstJack *this);
75 static void             gst_jack_class_init(GstJackClass *klass);
76 static void             gst_jack_set_property (GObject *object, guint prop_id,
77                                                const GValue *value, GParamSpec *pspec);
78 static void             gst_jack_get_property (GObject *object, guint prop_id,
79                                                GValue *value, GParamSpec *pspec);
80
81 static GstPadTemplate*  gst_jack_src_request_pad_factory();
82 static GstPadTemplate*  gst_jack_sink_request_pad_factory();
83 static GstPad*          gst_jack_request_new_pad (GstElement *element, GstPadTemplate *templ,
84                                                   const gchar *name);
85 static GstElementStateReturn    gst_jack_change_state (GstElement *element);
86 static GstPadLinkReturn gst_jack_link (GstPad *pad, GstCaps *caps);
87
88 static void             gst_jack_loop (GstElement *element);
89
90
91 enum {
92     ARG_0,
93     ARG_PORT_NAME_PREFIX,
94 };
95
96
97 GType
98 gst_jack_get_type (void) 
99 {
100   static GType jack_type = 0;
101
102   if (!jack_type) {
103     static const GTypeInfo jack_info = {
104       sizeof(GstJackClass),
105       NULL,
106       NULL,
107       NULL,
108       NULL,
109       NULL,
110       sizeof(GstJack),
111       0,
112       NULL,
113     };
114     jack_type = g_type_register_static (GST_TYPE_ELEMENT, "GstJack", &jack_info, 0);
115   }
116   return jack_type;
117 }
118
119 GType
120 gst_jack_sink_get_type (void) 
121 {
122     static GType jack_type = 0;
123
124     if (!jack_type) {
125         static const GTypeInfo jack_info = {
126             sizeof(GstJackClass),
127             NULL,
128             NULL,
129             (GClassInitFunc)gst_jack_class_init,
130             NULL,
131             NULL,
132             sizeof(GstJack),
133             0,
134             (GInstanceInitFunc)gst_jack_init,
135         };
136         jack_type = g_type_register_static (GST_TYPE_JACK, "GstJackSink", &jack_info, 0);
137     }
138     return jack_type;
139 }
140
141 GType
142 gst_jack_src_get_type (void) 
143 {
144     static GType jack_type = 0;
145     
146     if (!jack_type) {
147         static const GTypeInfo jack_info = {
148             sizeof(GstJackClass),
149             NULL,
150             NULL,
151             (GClassInitFunc)gst_jack_class_init,
152             NULL,
153             NULL,
154             sizeof(GstJack),
155             0,
156             (GInstanceInitFunc)gst_jack_init,
157         };
158         jack_type = g_type_register_static (GST_TYPE_JACK, "GstJackSrc", &jack_info, 0);
159     }
160     return jack_type;
161 }
162
163 static void
164 gst_jack_class_init(GstJackClass *klass)
165 {
166     GObjectClass *object_class;
167     GstElementClass *element_class;
168     GParamSpec *pspec;
169     gchar *prefix;
170     
171     object_class = (GObjectClass *)klass;
172     element_class = (GstElementClass *)klass;
173     
174     if (parent_class == NULL)
175         parent_class = g_type_class_ref(GST_TYPE_ELEMENT);
176     
177     object_class->get_property = gst_jack_get_property;
178     object_class->set_property = gst_jack_set_property;
179     
180     if (GST_IS_JACK_SINK_CLASS (klass))
181         prefix = "gst-out-";
182     else
183         prefix = "gst-in-";
184     
185     pspec = g_param_spec_string ("port-name-prefix", "Port name prefix",
186                                  "String to prepend to jack port names",
187                                  prefix, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
188     g_object_class_install_property (object_class, ARG_PORT_NAME_PREFIX, pspec);
189
190     element_class->change_state = gst_jack_change_state;
191     
192     element_class->request_new_pad = gst_jack_request_new_pad;
193 }
194
195 static void
196 gst_jack_init(GstJack *this)
197 {
198     if (G_OBJECT_TYPE (this) == GST_TYPE_JACK_SRC)
199         this->direction = GST_PAD_SRC;
200     else if (G_OBJECT_TYPE (this) == GST_TYPE_JACK_SINK)
201         this->direction = GST_PAD_SINK;
202     else
203         g_assert_not_reached ();
204     
205     gst_element_set_loop_function (GST_ELEMENT (this), gst_jack_loop);
206 }
207
208 static void
209 gst_jack_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
210 {
211     GstJack *this = (GstJack*)object;
212
213     switch (prop_id) {
214     case ARG_PORT_NAME_PREFIX:
215         if (this->port_name_prefix)
216             g_free (this->port_name_prefix);
217         this->port_name_prefix = g_strdup (g_value_get_string (value));
218         break;
219     default:
220         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
221         return;
222     }
223 }
224
225 static void
226 gst_jack_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
227 {
228     GstJack *this = (GstJack*)object;
229
230     switch (prop_id) {
231     case ARG_PORT_NAME_PREFIX:
232         g_value_set_string (value, this->port_name_prefix);
233         break;
234     default:
235         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
236         break;
237     }
238 }
239
240 static GstPadTemplate*
241 gst_jack_src_request_pad_factory (void)
242 {
243     static GstPadTemplate *template = NULL;
244     
245     if (!template) {
246         GstCaps *caps;
247         caps = gst_caps_new ("src",
248                              "audio/x-raw-float",
249                              GST_AUDIO_FLOAT_STANDARD_PAD_TEMPLATE_PROPS);
250         template = gst_pad_template_new ("%s", GST_PAD_SRC,
251                                          GST_PAD_REQUEST, caps, NULL);
252     }
253     
254     return template;
255 }
256
257 static GstPadTemplate*
258 gst_jack_sink_request_pad_factory (void)
259 {
260     static GstPadTemplate *template = NULL;
261     
262     if (!template) {
263         GstCaps *caps;
264         caps = gst_caps_new ("sink",
265                              "audio/x-raw-float",
266                              GST_AUDIO_FLOAT_STANDARD_PAD_TEMPLATE_PROPS);
267         template = gst_pad_template_new ("%s", GST_PAD_SINK,
268                                          GST_PAD_REQUEST, caps, NULL);
269     }
270     
271     return template;
272 }
273
274 static GstPad*
275 gst_jack_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar *name) 
276 {
277     GstJack *this;
278     gchar *newname;
279     GList *l, **pad_list;
280     GstJackPad *pad;
281     gint count;
282     
283     g_return_val_if_fail ((this = GST_JACK (element)), NULL);
284     
285     if (!this->bin)
286         pad_list = &this->pads;
287     else if (this->direction == GST_PAD_SRC)
288         pad_list = &this->bin->src_pads;
289     else
290         pad_list = &this->bin->sink_pads;
291     
292     if (name) {
293         l = *pad_list;
294         while (l) {
295             if (strcmp (GST_JACK_PAD(l)->name, name) == 0) {
296                 g_warning("requested port name %s already in use.", name);
297                 return NULL;
298             }
299             l = l->next;
300         }
301         newname = g_strdup (name);
302     } else {
303         if (this->direction == GST_PAD_SINK)
304             newname = g_strdup ("alsa_pcm:playback_1");
305         else
306             newname = g_strdup ("alsa_pcm:capture_1");
307     }
308     
309     pad = g_new0 (GstJackPad, 1);
310     
311     if (!port_name_counts)
312         port_name_counts = g_hash_table_new (g_str_hash, g_str_equal);
313
314     count = GPOINTER_TO_INT (g_hash_table_lookup (port_name_counts, this->port_name_prefix));
315     g_hash_table_insert (port_name_counts, g_strdup (this->port_name_prefix), GINT_TO_POINTER (count+1));
316
317     pad->name = g_strdup_printf ("%s%d", this->port_name_prefix, count);
318
319     pad->peer_name = newname;
320     pad->pad = gst_pad_new_from_template (templ, newname);
321     gst_element_add_pad (GST_ELEMENT (this), pad->pad);
322     gst_pad_set_link_function (pad->pad, gst_jack_link);
323     
324     this->pads = g_list_append (this->pads, pad);
325     
326     g_print ("returning from request_new_pad, pad %s created, to connect to %s\n", pad->name, pad->peer_name);
327     return pad->pad;
328 }
329
330 static GstElementStateReturn
331 gst_jack_change_state (GstElement *element)
332 {
333     GstJack *this;
334     GList *l = NULL, **pads;
335     GstJackPad *pad;
336     GstCaps *caps;
337     
338     g_return_val_if_fail (element != NULL, FALSE);
339     this = GST_JACK (element);
340     
341     switch (GST_STATE_PENDING (element)) {
342     case GST_STATE_NULL:
343         JACK_DEBUG ("%s: NULL", GST_OBJECT_NAME (GST_OBJECT (this)));
344
345         break;
346         
347     case GST_STATE_READY:
348         JACK_DEBUG ("%s: READY", GST_OBJECT_NAME (GST_OBJECT (this)));
349
350         if (!this->bin) {
351             if (!(this->bin = (GstJackBin*)gst_element_get_managing_bin (element))
352                 || !GST_IS_JACK_BIN (this->bin)) {
353                 this->bin = NULL;
354                 g_warning ("jack element %s needs to be contained in a jack bin.",
355                            GST_OBJECT_NAME (element));
356                 return GST_STATE_FAILURE;
357             }
358
359             /* fixme: verify that all names are unique */
360             l = this->pads;
361             pads = (this->direction == GST_PAD_SRC) ? &this->bin->src_pads : &this->bin->sink_pads;
362             while (l) {
363                 pad = GST_JACK_PAD (l);
364                 JACK_DEBUG ("%s: appending pad %s:%s to list", GST_OBJECT_NAME (this), pad->name, pad->peer_name);
365                 *pads = g_list_append (*pads, pad);
366                 l = g_list_next (l);
367             }
368         }
369         break;
370         
371     case GST_STATE_PAUSED:
372         JACK_DEBUG ("%s: PAUSED", GST_OBJECT_NAME (GST_OBJECT (this)));
373
374         if (GST_STATE (element) == GST_STATE_READY) {
375             /* we're in READY->PAUSED */
376             l = this->pads;
377             while (l) {
378                 pad = GST_JACK_PAD (l);
379                 caps = gst_pad_get_caps (pad->pad);
380                 gst_caps_set (caps, "rate", GST_PROPS_INT_TYPE,
381                               (gint)this->bin->rate, NULL);
382                 gst_caps_set (caps, "buffer-frames", GST_PROPS_INT_TYPE,
383                               (gint)this->bin->nframes, NULL);
384                 if (gst_pad_try_set_caps (pad->pad, caps) <= 0)
385                     return GST_STATE_FAILURE;
386                 l = g_list_next (l);
387             }
388         }
389         break;
390     case GST_STATE_PLAYING:
391         JACK_DEBUG ("%s: PLAYING", GST_OBJECT_NAME (GST_OBJECT (this)));
392         break;
393     }
394     
395     JACK_DEBUG ("%s: state change finished", GST_OBJECT_NAME (this));
396     
397     if (GST_ELEMENT_CLASS (parent_class)->change_state)
398         return GST_ELEMENT_CLASS (parent_class)->change_state (element);
399
400     return GST_STATE_SUCCESS;
401 }
402
403 static GstPadLinkReturn
404 gst_jack_link (GstPad *pad, GstCaps *caps)
405 {
406   GstJack *this;
407   gint rate, buffer_frames;
408   
409   this = GST_JACK (GST_OBJECT_PARENT (pad));
410   
411   if (GST_CAPS_IS_FIXED (caps)) {
412       gst_caps_get_int (caps, "rate", &rate);
413       gst_caps_get_int (caps, "buffer-frames", &buffer_frames);
414       if (this->bin && (rate != this->bin->rate ||
415                         buffer_frames != this->bin->nframes))
416           return GST_PAD_LINK_REFUSED;
417       
418       return GST_PAD_LINK_OK;
419   }
420   
421   return GST_PAD_LINK_DELAYED;
422 }
423
424 static void
425 gst_jack_loop (GstElement *element)
426 {
427     GstJack *this;
428     GList *pads;
429     gint len;
430     GstJackPad *pad;
431     GstBuffer *buffer;
432     
433     this = GST_JACK (element);
434     
435     len = this->bin->nframes * sizeof (sample_t);
436     
437     pads = this->pads;
438     while (pads) {
439         pad = GST_JACK_PAD (pads);
440             
441         if (this->direction == GST_PAD_SINK) {
442             buffer = GST_BUFFER (gst_pad_pull (pad->pad));
443                 
444             if (GST_IS_EVENT (buffer)) {
445                 GstEvent *event = GST_EVENT (buffer);
446                 switch (GST_EVENT_TYPE (buffer)) {
447                 case GST_EVENT_EOS:
448                     gst_element_set_eos (element);
449                     gst_event_unref (event);
450                     return;
451                 default:
452                     gst_pad_event_default (pad->pad, event);
453                     return;
454                 }
455             }
456                 
457             /* if the other plugins only give out buffer-frames or less (as
458                they should), if the length of the GstBuffer is different
459                from nframes then the buffer is short and we will get EOS
460                next */
461             memcpy (pad->data, GST_BUFFER_DATA (buffer),
462                     GST_BUFFER_SIZE (buffer));
463             if (len != GST_BUFFER_SIZE (buffer))
464                 memset (pad->data + GST_BUFFER_SIZE (buffer), 0,
465                         len - GST_BUFFER_SIZE (buffer));
466
467             gst_buffer_unref (buffer);
468         } else {
469             buffer = gst_buffer_new ();
470             gst_buffer_set_data (buffer, pad->data, len);
471             GST_BUFFER_FLAG_SET(buffer, GST_BUFFER_DONTFREE);
472                 
473             gst_pad_push (pad->pad, GST_DATA (buffer));
474         }
475         pads = g_list_next (pads);
476     }
477 }
478
479 static gboolean
480 plugin_init (GModule *module, GstPlugin *plugin)
481 {
482     GstElementFactory *factory;
483     
484     factory = gst_element_factory_new ("jackbin", GST_TYPE_JACK_BIN, &gst_jack_bin_details);
485     g_return_val_if_fail (factory != NULL, FALSE);
486     gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
487     
488     factory = gst_element_factory_new ("jacksrc", GST_TYPE_JACK_SRC, &gst_jack_src_details);
489     g_return_val_if_fail (factory != NULL, FALSE);
490     gst_element_factory_add_pad_template (factory, gst_jack_src_request_pad_factory());
491     gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
492     
493     factory = gst_element_factory_new ("jacksink", GST_TYPE_JACK_SINK, &gst_jack_sink_details);
494     g_return_val_if_fail (factory != NULL, FALSE);
495     gst_element_factory_add_pad_template (factory, gst_jack_sink_request_pad_factory());
496     gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
497     
498     gst_plugin_set_longname (plugin, "JACK plugin library");
499     
500     return TRUE;
501 }
502
503 GstPluginDesc plugin_desc = {
504     GST_VERSION_MAJOR,
505     GST_VERSION_MINOR,
506     "jack",
507     plugin_init
508 };