• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * (C) 2011 Collabora Ltd.
3  *  Contact: Youness Alaoui <youness.alaoui@collabora.co.uk>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library 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  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 /**
22  * SECTION:element-spanplc
23  * @title: spanplc
24  *
25  * The spanplc (Packet Loss Concealment) element provides a synthetic
26  * fill-in signal, to minimise the audible effect of lost packets in
27  * VoIP applications
28  *
29  */
30 
31 
32 #ifdef HAVE_CONFIG_H
33 #include <config.h>
34 #endif
35 
36 #include "gstspanplc.h"
37 
38 #include <gst/audio/audio.h>
39 
40 G_DEFINE_TYPE (GstSpanPlc, gst_span_plc, GST_TYPE_ELEMENT);
41 GST_ELEMENT_REGISTER_DEFINE (spanplc, "spanplc", GST_RANK_PRIMARY,
42     GST_TYPE_SPAN_PLC);
43 
44 GST_DEBUG_CATEGORY_STATIC (gst_span_plc_debug);
45 #define GST_CAT_DEFAULT gst_span_plc_debug
46 
47 enum
48 {
49   PROP_0,
50   PROP_STATS,
51 };
52 
53 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
54     GST_PAD_SRC,
55     GST_PAD_ALWAYS,
56     GST_STATIC_CAPS ("audio/x-raw, format=\"" GST_AUDIO_NE (S16) "\", "
57         "rate = " GST_AUDIO_RATE_RANGE " , channels = (int) 1")
58     );
59 
60 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
61     GST_PAD_SINK,
62     GST_PAD_ALWAYS,
63     GST_STATIC_CAPS ("audio/x-raw, format=\"" GST_AUDIO_NE (S16) "\", "
64         "rate = " GST_AUDIO_RATE_RANGE " , channels = (int) 1")
65     );
66 
67 static void gst_span_plc_dispose (GObject * object);
68 
69 static GstStateChangeReturn gst_span_plc_change_state (GstElement * element,
70     GstStateChange transition);
71 static void gst_span_plc_setcaps_sink (GstSpanPlc * plc, GstCaps * caps);
72 static GstFlowReturn gst_span_plc_chain (GstPad * pad, GstObject * parent,
73     GstBuffer * buf);
74 static gboolean gst_span_plc_event_sink (GstPad * pad, GstObject * parent,
75     GstEvent * event);
76 
77 static GstStructure *
gst_span_plc_create_stats(GstSpanPlc * self)78 gst_span_plc_create_stats (GstSpanPlc * self)
79 {
80   GstStructure *s = NULL;
81 
82   GST_OBJECT_LOCK (self);
83   if (self->plc_state) {
84     s = gst_structure_new ("application/x-spanplc-stats",
85         "num-pushed", G_TYPE_UINT64, self->num_pushed,
86         "num-gap", G_TYPE_UINT64, self->num_gap,
87         "plc-num-samples", G_TYPE_UINT64, self->plc_num_samples,
88         "plc-duration", G_TYPE_UINT64, self->plc_duration,
89         "pitch", G_TYPE_INT, self->plc_state->pitch,
90         "pitch-offset", G_TYPE_INT, self->plc_state->pitch_offset, NULL);
91   }
92   GST_OBJECT_UNLOCK (self);
93 
94   return s;
95 }
96 
97 static void
gst_span_plc_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)98 gst_span_plc_get_property (GObject * object, guint prop_id, GValue * value,
99     GParamSpec * pspec)
100 {
101   GstSpanPlc *self = GST_SPAN_PLC (object);
102 
103   switch (prop_id) {
104     case PROP_STATS:
105       g_value_take_boxed (value, gst_span_plc_create_stats (self));
106       break;
107     default:
108       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
109       break;
110   }
111 }
112 
113 /* initialize the plugin's class */
114 static void
gst_span_plc_class_init(GstSpanPlcClass * klass)115 gst_span_plc_class_init (GstSpanPlcClass * klass)
116 {
117   GObjectClass *gobject_class = (GObjectClass *) klass;
118   GstElementClass *gstelement_class = (GstElementClass *) klass;
119 
120   gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
121   gst_element_class_add_static_pad_template (gstelement_class, &sink_factory);
122 
123   gst_element_class_set_static_metadata (gstelement_class, "SpanDSP PLC",
124       "Filter/Effect/Audio",
125       "Adds packet loss concealment to audio",
126       "Youness Alaoui <youness.alaoui@collabora.co.uk>");
127 
128   gobject_class->get_property = gst_span_plc_get_property;
129   gobject_class->dispose = gst_span_plc_dispose;
130 
131   gstelement_class->change_state =
132       GST_DEBUG_FUNCPTR (gst_span_plc_change_state);
133 
134   /**
135    * GstSpanPlc:stats:
136    *
137    * Various decoder statistics. This property returns a GstStructure
138    * with name application/x-spanplc-stats with the following fields:
139    *
140    * * #guint64 `num-pushed`: the number of packets pushed out.
141    * * #guint64 `num-gap`: the number of gap packets received.
142    * * #guint64 `plc-num-samples`: the number of samples generated using PLC
143    * * #guint64 `plc-duration`: the total duration, in ns, of samples generated using PLC
144    * * #guint `pitch`: pitch estimate, in Hz
145    * * #guint `pitch-offset`: current offset in pitch period, in Hz
146    *
147    * Since: 1.18
148    */
149   g_object_class_install_property (gobject_class, PROP_STATS,
150       g_param_spec_boxed ("stats", "Statistics",
151           "Various statistics", GST_TYPE_STRUCTURE,
152           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
153 
154   GST_DEBUG_CATEGORY_INIT (gst_span_plc_debug, "spanplc",
155       0, "spanDSP's packet loss concealment");
156 
157 }
158 
159 static void
gst_span_plc_init(GstSpanPlc * plc)160 gst_span_plc_init (GstSpanPlc * plc)
161 {
162   GST_DEBUG_OBJECT (plc, "init");
163 
164   plc->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
165   plc->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
166 
167   gst_pad_set_chain_function (plc->sinkpad,
168       GST_DEBUG_FUNCPTR (gst_span_plc_chain));
169   gst_pad_set_event_function (plc->sinkpad,
170       GST_DEBUG_FUNCPTR (gst_span_plc_event_sink));
171 
172   gst_element_add_pad (GST_ELEMENT (plc), plc->srcpad);
173   gst_element_add_pad (GST_ELEMENT (plc), plc->sinkpad);
174 
175   plc->plc_state = NULL;
176 
177   GST_DEBUG_OBJECT (plc, "init complete");
178 }
179 
180 static void
gst_span_plc_dispose(GObject * object)181 gst_span_plc_dispose (GObject * object)
182 {
183   GstSpanPlc *plc = GST_SPAN_PLC (object);
184 
185   if (plc->plc_state)
186     plc_free (plc->plc_state);
187   plc->plc_state = NULL;
188 
189   G_OBJECT_CLASS (gst_span_plc_parent_class)->dispose (object);
190 }
191 
192 static void
gst_span_plc_flush(GstSpanPlc * plc,gboolean renew)193 gst_span_plc_flush (GstSpanPlc * plc, gboolean renew)
194 {
195   if (plc->plc_state)
196     plc_free (plc->plc_state);
197   if (renew)
198     plc->plc_state = plc_init (NULL);
199   else
200     plc->plc_state = NULL;
201 }
202 
203 static GstStateChangeReturn
gst_span_plc_change_state(GstElement * element,GstStateChange transition)204 gst_span_plc_change_state (GstElement * element, GstStateChange transition)
205 {
206   GstSpanPlc *plc = GST_SPAN_PLC (element);
207   GstStateChangeReturn ret;
208 
209   switch (transition) {
210     case GST_STATE_CHANGE_NULL_TO_READY:
211       gst_span_plc_flush (plc, TRUE);
212       break;
213     default:
214       break;
215   }
216 
217   ret = GST_ELEMENT_CLASS (gst_span_plc_parent_class)->change_state (element,
218       transition);
219 
220   switch (transition) {
221     case GST_STATE_CHANGE_PAUSED_TO_READY:
222       GST_OBJECT_LOCK (plc);
223       plc->num_pushed = 0;
224       plc->num_gap = 0;
225       plc->plc_num_samples = 0;
226       plc->plc_duration = 0;
227       GST_OBJECT_UNLOCK (plc);
228       break;
229     case GST_STATE_CHANGE_READY_TO_NULL:
230       gst_span_plc_flush (plc, FALSE);
231     default:
232       break;
233   }
234 
235   return ret;
236 }
237 
238 static void
gst_span_plc_setcaps_sink(GstSpanPlc * plc,GstCaps * caps)239 gst_span_plc_setcaps_sink (GstSpanPlc * plc, GstCaps * caps)
240 {
241   GstStructure *s = NULL;
242   gint sample_rate;
243 
244   s = gst_caps_get_structure (caps, 0);
245   if (!s)
246     return;
247 
248   gst_structure_get_int (s, "rate", &sample_rate);
249   if (sample_rate != plc->sample_rate) {
250     GST_DEBUG_OBJECT (plc, "setcaps: got sample rate : %d", sample_rate);
251     plc->sample_rate = sample_rate;
252     gst_span_plc_flush (plc, TRUE);
253   }
254 }
255 
256 static GstFlowReturn
gst_span_plc_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)257 gst_span_plc_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
258 {
259   GstSpanPlc *plc = GST_SPAN_PLC (parent);
260   GstMapInfo map;
261 
262   buffer = gst_buffer_make_writable (buffer);
263   gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
264   plc_rx (plc->plc_state, (int16_t *) map.data, map.size / 2);
265   gst_buffer_unmap (buffer, &map);
266 
267   GST_OBJECT_LOCK (plc);
268   plc->num_pushed++;
269   GST_OBJECT_UNLOCK (plc);
270 
271   return gst_pad_push (plc->srcpad, buffer);
272 }
273 
274 static void
gst_span_plc_send_fillin(GstSpanPlc * plc,GstClockTime timestamp,GstClockTime duration)275 gst_span_plc_send_fillin (GstSpanPlc * plc, GstClockTime timestamp,
276     GstClockTime duration)
277 {
278   guint buf_size;
279   GstBuffer *buffer = NULL;
280   GstMapInfo map;
281   gint num_samples;
282 
283   buf_size = ((float) duration / GST_SECOND) * plc->sample_rate;
284   buf_size *= sizeof (guint16);
285   buffer = gst_buffer_new_and_alloc (buf_size);
286   GST_DEBUG_OBJECT (plc, "Missing packet of %" GST_TIME_FORMAT
287       " == %d bytes", GST_TIME_ARGS (duration), buf_size);
288   gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
289   num_samples = plc_fillin (plc->plc_state, (int16_t *) map.data, map.size / 2);
290   gst_buffer_unmap (buffer, &map);
291   GST_BUFFER_PTS (buffer) = timestamp;
292   GST_BUFFER_DURATION (buffer) = duration;
293 
294   GST_OBJECT_LOCK (plc);
295   plc->num_gap++;
296   plc->num_pushed++;
297   plc->plc_num_samples += num_samples;
298   plc->plc_duration += duration;
299   GST_OBJECT_UNLOCK (plc);
300 
301   gst_pad_push (plc->srcpad, buffer);
302 }
303 
304 static gboolean
gst_span_plc_event_sink(GstPad * pad,GstObject * parent,GstEvent * event)305 gst_span_plc_event_sink (GstPad * pad, GstObject * parent, GstEvent * event)
306 {
307   GstSpanPlc *plc = GST_SPAN_PLC (parent);
308 
309   GST_DEBUG_OBJECT (plc, "received event %s", GST_EVENT_TYPE_NAME (event));
310 
311   switch (GST_EVENT_TYPE (event)) {
312     case GST_EVENT_CAPS:
313     {
314       GstCaps *caps;
315       gst_event_parse_caps (event, &caps);
316       gst_span_plc_setcaps_sink (plc, caps);
317       break;
318     }
319     case GST_EVENT_GAP:
320     {
321       GstClockTime timestamp;
322       GstClockTime duration;
323 
324       gst_event_parse_gap (event, &timestamp, &duration);
325       gst_span_plc_send_fillin (plc, timestamp, duration);
326       gst_event_unref (event);
327       return TRUE;
328     }
329     case GST_EVENT_FLUSH_STOP:
330       gst_span_plc_flush (plc, TRUE);
331       break;
332     default:
333       break;
334   }
335 
336   return gst_pad_push_event (plc->srcpad, event);
337 }
338