|
1 /* GStreamer Framed Audio Encoder |
|
2 * Copyright 2009 Collabora Ltd, |
|
3 * Copyright 2009 Nokia Corporation |
|
4 * @author: Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>. |
|
5 * |
|
6 * This library is free software; you can redistribute it and/or |
|
7 * modify it under the terms of the GNU Library General Public |
|
8 * License as published by the Free Software Foundation; either |
|
9 * version 2 of the License, or (at your option) any later version. |
|
10 * |
|
11 * This library is distributed in the hope that it will be useful, |
|
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 * Library General Public License for more details. |
|
15 * |
|
16 * You should have received a copy of the GNU Library General Public |
|
17 * License along with this library; if not, write to the |
|
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
|
19 * Boston, MA 02111-1307, USA. |
|
20 */ |
|
21 |
|
22 #ifdef HAVE_CONFIG_H |
|
23 #include "config.h" |
|
24 #endif |
|
25 #include <gst/gst.h> |
|
26 #include <gst/audio/audio.h> |
|
27 #include <string.h> |
|
28 |
|
29 #include "gstframedaudioenc.h" |
|
30 |
|
31 /* generic part */ |
|
32 #ifndef RAW_FRAME_SIZE |
|
33 |
|
34 /* this will reference caller's debug category; |
|
35 * there is a copy of this per plugin lib (= debug category) */ |
|
36 GST_DEBUG_CATEGORY_STATIC (framedaudioenc_debug); |
|
37 #define GST_CAT_DEFAULT framedaudioenc_debug |
|
38 |
|
39 void |
|
40 gst_framed_audio_enc_reset (GstFramedAudioEnc * enc) |
|
41 { |
|
42 gst_adapter_clear (enc->adapter); |
|
43 enc->next_ts = GST_CLOCK_TIME_NONE; |
|
44 } |
|
45 |
|
46 /* installs @enc as element private for @element's pad, |
|
47 * and possibly some event and query handler. |
|
48 * if these need overriding, chain up to them |
|
49 * chain and setcaps still need to be set by @element */ |
|
50 void |
|
51 gst_framed_audio_enc_init (GstFramedAudioEnc * enc, GstElement * element, |
|
52 GstDebugCategory * cat) |
|
53 { |
|
54 enc->element = element; |
|
55 #ifndef GST_DISABLE_GST_DEBUG |
|
56 framedaudioenc_debug = cat; |
|
57 #endif |
|
58 |
|
59 enc->adapter = gst_adapter_new (); |
|
60 |
|
61 /* hook some */ |
|
62 enc->sinkpad = gst_element_get_pad (enc->element, "sink"); |
|
63 g_assert (enc->sinkpad); |
|
64 gst_pad_set_element_private (enc->sinkpad, enc); |
|
65 |
|
66 /* watch downstream events */ |
|
67 gst_pad_set_event_function (enc->sinkpad, |
|
68 GST_DEBUG_FUNCPTR (gst_framed_audio_enc_sink_event)); |
|
69 |
|
70 gst_framed_audio_enc_reset (enc); |
|
71 } |
|
72 |
|
73 void |
|
74 gst_framed_audio_enc_finalize (GstFramedAudioEnc * enc) |
|
75 { |
|
76 gst_object_unref (enc->adapter); |
|
77 |
|
78 gst_pad_set_element_private (enc->sinkpad, NULL); |
|
79 gst_object_unref (enc->sinkpad); |
|
80 } |
|
81 |
|
82 GstPad * |
|
83 gst_framed_audio_enc_request_new_pad (GstFramedAudioEnc * enc, |
|
84 GstPadTemplate * templ, const gchar * req_name, GstPad ** pad_p) |
|
85 { |
|
86 GstElement *element; |
|
87 GstPad *newpad; |
|
88 GstElementClass *klass; |
|
89 GstCaps *caps; |
|
90 |
|
91 g_return_val_if_fail (templ != NULL, NULL); |
|
92 |
|
93 element = enc->element; |
|
94 klass = GST_ELEMENT_GET_CLASS (element); |
|
95 |
|
96 if (templ != gst_element_class_get_pad_template (klass, "cn")) |
|
97 goto wrong_template; |
|
98 |
|
99 GST_DEBUG_OBJECT (enc->element, "adding cn pad"); |
|
100 newpad = gst_pad_new_from_template (templ, "cn"); |
|
101 /* set template caps */ |
|
102 caps = gst_caps_copy (gst_pad_get_pad_template_caps (newpad)); |
|
103 gst_pad_set_caps (newpad, caps); |
|
104 gst_caps_unref (caps); |
|
105 gst_pad_use_fixed_caps (newpad); |
|
106 gst_pad_set_active (newpad, TRUE); |
|
107 /* only 1 pad by name can be added */ |
|
108 if (gst_element_add_pad (element, newpad)) { |
|
109 GST_OBJECT_LOCK (element); |
|
110 gst_object_replace ((GstObject **) pad_p, GST_OBJECT_CAST (newpad)); |
|
111 GST_OBJECT_UNLOCK (element); |
|
112 GST_DEBUG_OBJECT (enc->element, "cn pad added"); |
|
113 } else { |
|
114 gst_object_unref (newpad); |
|
115 goto already_requested; |
|
116 } |
|
117 |
|
118 return newpad; |
|
119 |
|
120 /* ERRORS */ |
|
121 wrong_template: |
|
122 { |
|
123 GST_ERROR_OBJECT (element, "not our template!"); |
|
124 return NULL; |
|
125 } |
|
126 already_requested: |
|
127 { |
|
128 GST_ERROR_OBJECT (element, "only 1 instance of a pad can be requested"); |
|
129 return NULL; |
|
130 } |
|
131 } |
|
132 |
|
133 void |
|
134 gst_framed_audio_enc_release_pad (GstFramedAudioEnc * enc, GstPad * pad, |
|
135 GstPad ** pad_p, void (disable_cn) (GstElement *)) |
|
136 { |
|
137 GstElement *element = enc->element; |
|
138 |
|
139 GST_DEBUG_OBJECT (enc->element, "releasing cn pad"); |
|
140 |
|
141 GST_OBJECT_LOCK (element); |
|
142 if (pad != *pad_p) |
|
143 goto wrong_pad; |
|
144 GST_OBJECT_UNLOCK (element); |
|
145 |
|
146 /* reconfigure encoder */ |
|
147 disable_cn (element); |
|
148 |
|
149 if (gst_element_remove_pad (element, pad)) { |
|
150 GST_OBJECT_LOCK (element); |
|
151 gst_object_replace ((GstObject **) pad_p, NULL); |
|
152 GST_OBJECT_UNLOCK (element); |
|
153 GST_DEBUG_OBJECT (enc->element, "cn pad released"); |
|
154 } |
|
155 |
|
156 /* ERRORS */ |
|
157 wrong_pad: |
|
158 { |
|
159 GST_OBJECT_UNLOCK (element); |
|
160 GST_ERROR_OBJECT (element, "pad not requested; can not be released!"); |
|
161 return; |
|
162 } |
|
163 } |
|
164 |
|
165 gboolean |
|
166 gst_framed_audio_enc_sink_event (GstPad * pad, GstEvent * event) |
|
167 { |
|
168 GstFramedAudioEnc *enc; |
|
169 |
|
170 enc = gst_pad_get_element_private (pad); |
|
171 g_return_val_if_fail (enc, FALSE); |
|
172 |
|
173 GST_LOG_OBJECT (enc->element, "received %s", GST_EVENT_TYPE_NAME (event)); |
|
174 |
|
175 switch (GST_EVENT_TYPE (event)) { |
|
176 case GST_EVENT_FLUSH_STOP: |
|
177 /* fall-through */ |
|
178 case GST_EVENT_EOS: |
|
179 gst_adapter_clear (enc->adapter); |
|
180 enc->next_ts = GST_CLOCK_TIME_NONE; |
|
181 break; |
|
182 default: |
|
183 break; |
|
184 } |
|
185 |
|
186 return gst_pad_event_default (pad, event); |
|
187 } |
|
188 |
|
189 #else |
|
190 /* included part */ |
|
191 |
|
192 /* parameters: |
|
193 RAW_FRAME_SIZE |
|
194 CODEC_FRAME_SIZE |
|
195 FRAME_DURATION |
|
196 AUDIO_SAMPLE_RATE (optional) |
|
197 callback: |
|
198 codec_get_data(enc, in, out, dtx) |
|
199 |
|
200 If one does not mind a few cycles, include'ing can also be replaced by |
|
201 a regular include & call, at the expense of some additional parameters |
|
202 passed some way or another. |
|
203 */ |
|
204 |
|
205 #ifndef AUDIO_SAMPLE_RATE |
|
206 #define AUDIO_SAMPLE_RATE (8000) |
|
207 #endif |
|
208 |
|
209 /* quite some conditional stuff; |
|
210 * the (ugly?) cost of trying to stay inner loop optimal */ |
|
211 |
|
212 static GstFlowReturn |
|
213 gst_framed_audio_enc_chain (GstFramedAudioEnc * enc, GstBuffer * buf, |
|
214 GstPad * srcpad, GstPad ** _cnpad) |
|
215 { |
|
216 GstFlowReturn ret = GST_FLOW_OK; |
|
217 GstBuffer *obuf = NULL; |
|
218 #ifdef CN_PAD |
|
219 GstBuffer *cnbuf = NULL; |
|
220 GstPad *cnpad = NULL; |
|
221 #endif |
|
222 gboolean discont = FALSE; |
|
223 const guint8 *data; |
|
224 guint8 *odata; |
|
225 gint av, flush, osize; |
|
226 |
|
227 #ifdef CN_PAD |
|
228 GST_OBJECT_LOCK (enc->element); |
|
229 if (_cnpad) |
|
230 cnpad = *_cnpad; |
|
231 if (cnpad) |
|
232 gst_object_ref (cnpad); |
|
233 GST_OBJECT_UNLOCK (enc->element); |
|
234 #endif |
|
235 |
|
236 if (G_LIKELY (buf)) { |
|
237 GST_LOG_OBJECT (enc->element, "input buffer of size %d with ts: %" |
|
238 GST_TIME_FORMAT, GST_BUFFER_SIZE (buf), |
|
239 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); |
|
240 discont = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT); |
|
241 |
|
242 /* reposition to the new buffer's timestamp, |
|
243 * while correcting for some minor left-over */ |
|
244 if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { |
|
245 if (GST_CLOCK_TIME_IS_VALID (enc->next_ts)) { |
|
246 GstClockTimeDiff diff, limit; |
|
247 GstClockTime tleft; |
|
248 |
|
249 tleft = GST_FRAMES_TO_CLOCK_TIME |
|
250 (gst_adapter_available (enc->adapter) / 2, AUDIO_SAMPLE_RATE); |
|
251 diff = |
|
252 GST_CLOCK_DIFF (enc->next_ts + tleft, GST_BUFFER_TIMESTAMP (buf)); |
|
253 limit = GST_SECOND / AUDIO_SAMPLE_RATE / 2; |
|
254 /* try for a perfect stream if possible, do not act on rounding errors */ |
|
255 if (diff > limit || diff < -limit) { |
|
256 enc->next_ts = GST_BUFFER_TIMESTAMP (buf); |
|
257 if (enc->next_ts > tleft) |
|
258 enc->next_ts -= tleft; |
|
259 GST_LOG_OBJECT (enc->element, "marking discont based on timestamps"); |
|
260 discont = TRUE; |
|
261 } |
|
262 } else |
|
263 enc->next_ts = GST_BUFFER_TIMESTAMP (buf); |
|
264 } |
|
265 |
|
266 gst_adapter_push (enc->adapter, buf); |
|
267 buf = NULL; |
|
268 } |
|
269 |
|
270 av = gst_adapter_available (enc->adapter); |
|
271 if (G_UNLIKELY (av < RAW_FRAME_SIZE)) |
|
272 goto done; |
|
273 |
|
274 data = gst_adapter_peek (enc->adapter, av); |
|
275 obuf = gst_buffer_new_and_alloc (av / RAW_FRAME_SIZE * CODEC_FRAME_SIZE); |
|
276 odata = GST_BUFFER_DATA (obuf); |
|
277 osize = 0; |
|
278 flush = 0; |
|
279 |
|
280 while (TRUE) { |
|
281 gint esize; |
|
282 #ifdef CN_PAD |
|
283 GstDtxDecision dtx; |
|
284 |
|
285 /* safe default to start with, should get set */ |
|
286 dtx = GST_DTX_DECISION_VOICE; |
|
287 esize = codec_get_data (enc->element, data + flush, odata, &dtx); |
|
288 #else |
|
289 esize = codec_get_data (enc->element, data + flush, odata, NULL); |
|
290 #endif |
|
291 |
|
292 if (G_UNLIKELY (esize < 0)) |
|
293 goto encode_failed; |
|
294 |
|
295 #ifdef CN_PAD |
|
296 /* cn in a separate stream */ |
|
297 switch (dtx) { |
|
298 case GST_DTX_DECISION_VOICE: |
|
299 #endif |
|
300 flush += RAW_FRAME_SIZE; |
|
301 av -= RAW_FRAME_SIZE; |
|
302 |
|
303 odata += esize; |
|
304 osize += esize; |
|
305 #ifdef CN_PAD |
|
306 break; |
|
307 case GST_DTX_DECISION_SID_UPDATE: |
|
308 GST_LOG_OBJECT (enc->element, "dtx: SID_UPDATE %d", esize); |
|
309 /* if already data before, need to put SID data separately */ |
|
310 if (G_UNLIKELY (osize)) { |
|
311 cnbuf = gst_buffer_new_and_alloc (esize); |
|
312 memcpy (GST_BUFFER_DATA (cnbuf), data + osize, esize); |
|
313 } else { |
|
314 cnbuf = obuf; |
|
315 obuf = NULL; |
|
316 } |
|
317 /* and send one or both */ |
|
318 goto send; |
|
319 break; |
|
320 case GST_DTX_DECISION_SID_NONE: |
|
321 GST_LOG_OBJECT (enc->element, "dtx: SID_NONE %d", esize); |
|
322 /* maybe send preceding voice, if any */ |
|
323 goto send; |
|
324 break; |
|
325 } |
|
326 #endif |
|
327 |
|
328 #ifdef CODEC_FRAME_VARIABLE |
|
329 /* flush output after insufficient data */ |
|
330 if (av >= RAW_FRAME_SIZE) |
|
331 continue; |
|
332 #else |
|
333 /* ... or some reduced (e.g. silence) frame */ |
|
334 if (esize >= CODEC_FRAME_SIZE && av >= RAW_FRAME_SIZE) |
|
335 continue; |
|
336 #endif |
|
337 |
|
338 #ifdef CN_PAD |
|
339 send: |
|
340 #endif |
|
341 /* maybe a silent discarded frame */ |
|
342 if (G_LIKELY (osize)) { |
|
343 GST_BUFFER_SIZE (obuf) = osize; |
|
344 GST_BUFFER_DURATION (obuf) |
|
345 = FRAME_DURATION * (flush / RAW_FRAME_SIZE); |
|
346 GST_BUFFER_TIMESTAMP (obuf) = enc->next_ts; |
|
347 if (G_UNLIKELY (discont)) { |
|
348 GST_BUFFER_FLAG_SET (obuf, GST_BUFFER_FLAG_DISCONT); |
|
349 discont = FALSE; |
|
350 } |
|
351 |
|
352 GST_LOG_OBJECT (enc->element, |
|
353 "pushing buffer of size %d with ts: %" GST_TIME_FORMAT, |
|
354 GST_BUFFER_SIZE (obuf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (obuf))); |
|
355 gst_buffer_set_caps (obuf, GST_PAD_CAPS (srcpad)); |
|
356 ret = gst_pad_push (srcpad, obuf); |
|
357 obuf = NULL; |
|
358 } else { |
|
359 ret = GST_FLOW_OK; |
|
360 } |
|
361 |
|
362 #ifdef CN_PAD |
|
363 /* check for stuff to send on cn pad */ |
|
364 if (cnbuf && cnpad) { |
|
365 /* only at most 1 SID update per buffer */ |
|
366 GST_BUFFER_SIZE (cnbuf) = esize; |
|
367 GST_BUFFER_DURATION (cnbuf) = FRAME_DURATION; |
|
368 GST_BUFFER_TIMESTAMP (cnbuf) = enc->next_ts; |
|
369 |
|
370 GST_LOG_OBJECT (enc->element, |
|
371 "pushing cn buffer of size %d with ts: %" GST_TIME_FORMAT, |
|
372 GST_BUFFER_SIZE (cnbuf), |
|
373 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (cnbuf))); |
|
374 gst_buffer_set_caps (cnbuf, GST_PAD_CAPS (cnpad)); |
|
375 if (G_LIKELY (ret == GST_FLOW_OK)) { |
|
376 ret = gst_pad_push (cnpad, cnbuf); |
|
377 /* cn pad may not be linked */ |
|
378 if (G_UNLIKELY (ret == GST_FLOW_NOT_LINKED)) |
|
379 ret = GST_FLOW_OK; |
|
380 } else |
|
381 gst_pad_push (cnpad, cnbuf); |
|
382 cnbuf = NULL; |
|
383 } else if (G_UNLIKELY (cnbuf)) { |
|
384 /* should not occur */ |
|
385 gst_buffer_unref (cnbuf); |
|
386 cnbuf = NULL; |
|
387 } |
|
388 |
|
389 if (dtx != GST_DTX_DECISION_VOICE) { |
|
390 /* still need to count non-voice encoded frame */ |
|
391 flush += RAW_FRAME_SIZE; |
|
392 av -= RAW_FRAME_SIZE; |
|
393 } |
|
394 #endif /* CN_PAD */ |
|
395 |
|
396 /* remove used part */ |
|
397 gst_adapter_flush (enc->adapter, flush); |
|
398 if (GST_CLOCK_TIME_IS_VALID (enc->next_ts)) |
|
399 enc->next_ts += FRAME_DURATION * (flush / RAW_FRAME_SIZE); |
|
400 |
|
401 /* end if insufficient left or error */ |
|
402 if (av < RAW_FRAME_SIZE || ret != GST_FLOW_OK) |
|
403 break; |
|
404 |
|
405 /* allocate new buffer */ |
|
406 if (!obuf) { |
|
407 obuf = gst_buffer_new_and_alloc (av / RAW_FRAME_SIZE * CODEC_FRAME_SIZE); |
|
408 odata = GST_BUFFER_DATA (obuf); |
|
409 osize = 0; |
|
410 } |
|
411 /* and prepare to consume again */ |
|
412 data = gst_adapter_peek (enc->adapter, av); |
|
413 flush = 0; |
|
414 } |
|
415 |
|
416 if (!av) { |
|
417 enc->next_ts = GST_CLOCK_TIME_NONE; |
|
418 } |
|
419 |
|
420 done: |
|
421 #ifdef CN_PAD |
|
422 GST_OBJECT_LOCK (enc->element); |
|
423 if (cnpad) |
|
424 gst_object_unref (cnpad); |
|
425 GST_OBJECT_UNLOCK (enc->element); |
|
426 #endif |
|
427 |
|
428 return ret; |
|
429 |
|
430 /* ERRORS */ |
|
431 encode_failed: |
|
432 { |
|
433 GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), (NULL)); |
|
434 ret = GST_FLOW_ERROR; |
|
435 gst_buffer_unref (obuf); |
|
436 goto done; |
|
437 } |
|
438 } |
|
439 #endif |