16
|
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
|