/* GStreamer Framed Audio Encoder
* Copyright 2009 Collabora Ltd,
* Copyright 2009 Nokia Corporation
* @author: Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <string.h>
#include "gstframedaudioenc.h"
/* generic part */
#ifndef RAW_FRAME_SIZE
/* this will reference caller's debug category;
* there is a copy of this per plugin lib (= debug category) */
GST_DEBUG_CATEGORY_STATIC (framedaudioenc_debug);
#define GST_CAT_DEFAULT framedaudioenc_debug
void
gst_framed_audio_enc_reset (GstFramedAudioEnc * enc)
{
gst_adapter_clear (enc->adapter);
enc->next_ts = GST_CLOCK_TIME_NONE;
}
/* installs @enc as element private for @element's pad,
* and possibly some event and query handler.
* if these need overriding, chain up to them
* chain and setcaps still need to be set by @element */
void
gst_framed_audio_enc_init (GstFramedAudioEnc * enc, GstElement * element,
GstDebugCategory * cat)
{
enc->element = element;
#ifndef GST_DISABLE_GST_DEBUG
framedaudioenc_debug = cat;
#endif
enc->adapter = gst_adapter_new ();
/* hook some */
enc->sinkpad = gst_element_get_pad (enc->element, "sink");
g_assert (enc->sinkpad);
gst_pad_set_element_private (enc->sinkpad, enc);
/* watch downstream events */
gst_pad_set_event_function (enc->sinkpad,
GST_DEBUG_FUNCPTR (gst_framed_audio_enc_sink_event));
gst_framed_audio_enc_reset (enc);
}
void
gst_framed_audio_enc_finalize (GstFramedAudioEnc * enc)
{
gst_object_unref (enc->adapter);
gst_pad_set_element_private (enc->sinkpad, NULL);
gst_object_unref (enc->sinkpad);
}
GstPad *
gst_framed_audio_enc_request_new_pad (GstFramedAudioEnc * enc,
GstPadTemplate * templ, const gchar * req_name, GstPad ** pad_p)
{
GstElement *element;
GstPad *newpad;
GstElementClass *klass;
GstCaps *caps;
g_return_val_if_fail (templ != NULL, NULL);
element = enc->element;
klass = GST_ELEMENT_GET_CLASS (element);
if (templ != gst_element_class_get_pad_template (klass, "cn"))
goto wrong_template;
GST_DEBUG_OBJECT (enc->element, "adding cn pad");
newpad = gst_pad_new_from_template (templ, "cn");
/* set template caps */
caps = gst_caps_copy (gst_pad_get_pad_template_caps (newpad));
gst_pad_set_caps (newpad, caps);
gst_caps_unref (caps);
gst_pad_use_fixed_caps (newpad);
gst_pad_set_active (newpad, TRUE);
/* only 1 pad by name can be added */
if (gst_element_add_pad (element, newpad)) {
GST_OBJECT_LOCK (element);
gst_object_replace ((GstObject **) pad_p, GST_OBJECT_CAST (newpad));
GST_OBJECT_UNLOCK (element);
GST_DEBUG_OBJECT (enc->element, "cn pad added");
} else {
gst_object_unref (newpad);
goto already_requested;
}
return newpad;
/* ERRORS */
wrong_template:
{
GST_ERROR_OBJECT (element, "not our template!");
return NULL;
}
already_requested:
{
GST_ERROR_OBJECT (element, "only 1 instance of a pad can be requested");
return NULL;
}
}
void
gst_framed_audio_enc_release_pad (GstFramedAudioEnc * enc, GstPad * pad,
GstPad ** pad_p, void (disable_cn) (GstElement *))
{
GstElement *element = enc->element;
GST_DEBUG_OBJECT (enc->element, "releasing cn pad");
GST_OBJECT_LOCK (element);
if (pad != *pad_p)
goto wrong_pad;
GST_OBJECT_UNLOCK (element);
/* reconfigure encoder */
disable_cn (element);
if (gst_element_remove_pad (element, pad)) {
GST_OBJECT_LOCK (element);
gst_object_replace ((GstObject **) pad_p, NULL);
GST_OBJECT_UNLOCK (element);
GST_DEBUG_OBJECT (enc->element, "cn pad released");
}
/* ERRORS */
wrong_pad:
{
GST_OBJECT_UNLOCK (element);
GST_ERROR_OBJECT (element, "pad not requested; can not be released!");
return;
}
}
gboolean
gst_framed_audio_enc_sink_event (GstPad * pad, GstEvent * event)
{
GstFramedAudioEnc *enc;
enc = gst_pad_get_element_private (pad);
g_return_val_if_fail (enc, FALSE);
GST_LOG_OBJECT (enc->element, "received %s", GST_EVENT_TYPE_NAME (event));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_FLUSH_STOP:
/* fall-through */
case GST_EVENT_EOS:
gst_adapter_clear (enc->adapter);
enc->next_ts = GST_CLOCK_TIME_NONE;
break;
default:
break;
}
return gst_pad_event_default (pad, event);
}
#else
/* included part */
/* parameters:
RAW_FRAME_SIZE
CODEC_FRAME_SIZE
FRAME_DURATION
AUDIO_SAMPLE_RATE (optional)
callback:
codec_get_data(enc, in, out, dtx)
If one does not mind a few cycles, include'ing can also be replaced by
a regular include & call, at the expense of some additional parameters
passed some way or another.
*/
#ifndef AUDIO_SAMPLE_RATE
#define AUDIO_SAMPLE_RATE (8000)
#endif
/* quite some conditional stuff;
* the (ugly?) cost of trying to stay inner loop optimal */
static GstFlowReturn
gst_framed_audio_enc_chain (GstFramedAudioEnc * enc, GstBuffer * buf,
GstPad * srcpad, GstPad ** _cnpad)
{
GstFlowReturn ret = GST_FLOW_OK;
GstBuffer *obuf = NULL;
#ifdef CN_PAD
GstBuffer *cnbuf = NULL;
GstPad *cnpad = NULL;
#endif
gboolean discont = FALSE;
const guint8 *data;
guint8 *odata;
gint av, flush, osize;
#ifdef CN_PAD
GST_OBJECT_LOCK (enc->element);
if (_cnpad)
cnpad = *_cnpad;
if (cnpad)
gst_object_ref (cnpad);
GST_OBJECT_UNLOCK (enc->element);
#endif
if (G_LIKELY (buf)) {
GST_LOG_OBJECT (enc->element, "input buffer of size %d with ts: %"
GST_TIME_FORMAT, GST_BUFFER_SIZE (buf),
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
discont = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT);
/* reposition to the new buffer's timestamp,
* while correcting for some minor left-over */
if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
if (GST_CLOCK_TIME_IS_VALID (enc->next_ts)) {
GstClockTimeDiff diff, limit;
GstClockTime tleft;
tleft = GST_FRAMES_TO_CLOCK_TIME
(gst_adapter_available (enc->adapter) / 2, AUDIO_SAMPLE_RATE);
diff =
GST_CLOCK_DIFF (enc->next_ts + tleft, GST_BUFFER_TIMESTAMP (buf));
limit = GST_SECOND / AUDIO_SAMPLE_RATE / 2;
/* try for a perfect stream if possible, do not act on rounding errors */
if (diff > limit || diff < -limit) {
enc->next_ts = GST_BUFFER_TIMESTAMP (buf);
if (enc->next_ts > tleft)
enc->next_ts -= tleft;
GST_LOG_OBJECT (enc->element, "marking discont based on timestamps");
discont = TRUE;
}
} else
enc->next_ts = GST_BUFFER_TIMESTAMP (buf);
}
gst_adapter_push (enc->adapter, buf);
buf = NULL;
}
av = gst_adapter_available (enc->adapter);
if (G_UNLIKELY (av < RAW_FRAME_SIZE))
goto done;
data = gst_adapter_peek (enc->adapter, av);
obuf = gst_buffer_new_and_alloc (av / RAW_FRAME_SIZE * CODEC_FRAME_SIZE);
odata = GST_BUFFER_DATA (obuf);
osize = 0;
flush = 0;
while (TRUE) {
gint esize;
#ifdef CN_PAD
GstDtxDecision dtx;
/* safe default to start with, should get set */
dtx = GST_DTX_DECISION_VOICE;
esize = codec_get_data (enc->element, data + flush, odata, &dtx);
#else
esize = codec_get_data (enc->element, data + flush, odata, NULL);
#endif
if (G_UNLIKELY (esize < 0))
goto encode_failed;
#ifdef CN_PAD
/* cn in a separate stream */
switch (dtx) {
case GST_DTX_DECISION_VOICE:
#endif
flush += RAW_FRAME_SIZE;
av -= RAW_FRAME_SIZE;
odata += esize;
osize += esize;
#ifdef CN_PAD
break;
case GST_DTX_DECISION_SID_UPDATE:
GST_LOG_OBJECT (enc->element, "dtx: SID_UPDATE %d", esize);
/* if already data before, need to put SID data separately */
if (G_UNLIKELY (osize)) {
cnbuf = gst_buffer_new_and_alloc (esize);
memcpy (GST_BUFFER_DATA (cnbuf), data + osize, esize);
} else {
cnbuf = obuf;
obuf = NULL;
}
/* and send one or both */
goto send;
break;
case GST_DTX_DECISION_SID_NONE:
GST_LOG_OBJECT (enc->element, "dtx: SID_NONE %d", esize);
/* maybe send preceding voice, if any */
goto send;
break;
}
#endif
#ifdef CODEC_FRAME_VARIABLE
/* flush output after insufficient data */
if (av >= RAW_FRAME_SIZE)
continue;
#else
/* ... or some reduced (e.g. silence) frame */
if (esize >= CODEC_FRAME_SIZE && av >= RAW_FRAME_SIZE)
continue;
#endif
#ifdef CN_PAD
send:
#endif
/* maybe a silent discarded frame */
if (G_LIKELY (osize)) {
GST_BUFFER_SIZE (obuf) = osize;
GST_BUFFER_DURATION (obuf)
= FRAME_DURATION * (flush / RAW_FRAME_SIZE);
GST_BUFFER_TIMESTAMP (obuf) = enc->next_ts;
if (G_UNLIKELY (discont)) {
GST_BUFFER_FLAG_SET (obuf, GST_BUFFER_FLAG_DISCONT);
discont = FALSE;
}
GST_LOG_OBJECT (enc->element,
"pushing buffer of size %d with ts: %" GST_TIME_FORMAT,
GST_BUFFER_SIZE (obuf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (obuf)));
gst_buffer_set_caps (obuf, GST_PAD_CAPS (srcpad));
ret = gst_pad_push (srcpad, obuf);
obuf = NULL;
} else {
ret = GST_FLOW_OK;
}
#ifdef CN_PAD
/* check for stuff to send on cn pad */
if (cnbuf && cnpad) {
/* only at most 1 SID update per buffer */
GST_BUFFER_SIZE (cnbuf) = esize;
GST_BUFFER_DURATION (cnbuf) = FRAME_DURATION;
GST_BUFFER_TIMESTAMP (cnbuf) = enc->next_ts;
GST_LOG_OBJECT (enc->element,
"pushing cn buffer of size %d with ts: %" GST_TIME_FORMAT,
GST_BUFFER_SIZE (cnbuf),
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (cnbuf)));
gst_buffer_set_caps (cnbuf, GST_PAD_CAPS (cnpad));
if (G_LIKELY (ret == GST_FLOW_OK)) {
ret = gst_pad_push (cnpad, cnbuf);
/* cn pad may not be linked */
if (G_UNLIKELY (ret == GST_FLOW_NOT_LINKED))
ret = GST_FLOW_OK;
} else
gst_pad_push (cnpad, cnbuf);
cnbuf = NULL;
} else if (G_UNLIKELY (cnbuf)) {
/* should not occur */
gst_buffer_unref (cnbuf);
cnbuf = NULL;
}
if (dtx != GST_DTX_DECISION_VOICE) {
/* still need to count non-voice encoded frame */
flush += RAW_FRAME_SIZE;
av -= RAW_FRAME_SIZE;
}
#endif /* CN_PAD */
/* remove used part */
gst_adapter_flush (enc->adapter, flush);
if (GST_CLOCK_TIME_IS_VALID (enc->next_ts))
enc->next_ts += FRAME_DURATION * (flush / RAW_FRAME_SIZE);
/* end if insufficient left or error */
if (av < RAW_FRAME_SIZE || ret != GST_FLOW_OK)
break;
/* allocate new buffer */
if (!obuf) {
obuf = gst_buffer_new_and_alloc (av / RAW_FRAME_SIZE * CODEC_FRAME_SIZE);
odata = GST_BUFFER_DATA (obuf);
osize = 0;
}
/* and prepare to consume again */
data = gst_adapter_peek (enc->adapter, av);
flush = 0;
}
if (!av) {
enc->next_ts = GST_CLOCK_TIME_NONE;
}
done:
#ifdef CN_PAD
GST_OBJECT_LOCK (enc->element);
if (cnpad)
gst_object_unref (cnpad);
GST_OBJECT_UNLOCK (enc->element);
#endif
return ret;
/* ERRORS */
encode_failed:
{
GST_ELEMENT_ERROR (enc, STREAM, ENCODE, (NULL), (NULL));
ret = GST_FLOW_ERROR;
gst_buffer_unref (obuf);
goto done;
}
}
#endif