diff options
Diffstat (limited to 'audio/gstsbcenc.c')
| -rw-r--r-- | audio/gstsbcenc.c | 602 | 
1 files changed, 602 insertions, 0 deletions
diff --git a/audio/gstsbcenc.c b/audio/gstsbcenc.c new file mode 100644 index 00000000..65e3da5b --- /dev/null +++ b/audio/gstsbcenc.c @@ -0,0 +1,602 @@ +/* + * + *  BlueZ - Bluetooth protocol stack for Linux + * + *  Copyright (C) 2004-2008  Marcel Holtmann <marcel@holtmann.org> + * + * + *  This library is free software; you can redistribute it and/or + *  modify it under the terms of the GNU Lesser General Public + *  License as published by the Free Software Foundation; either + *  version 2.1 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 + *  Lesser General Public License for more details. + * + *  You should have received a copy of the GNU Lesser General Public + *  License along with this library; if not, write to the Free Software + *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "gstsbcenc.h" +#include "gstsbcutil.h" + +#define SBC_ENC_DEFAULT_MODE SBC_MODE_AUTO +#define SBC_ENC_DEFAULT_BLOCKS 0 +#define SBC_ENC_DEFAULT_SUB_BANDS 0 +#define SBC_ENC_DEFAULT_ALLOCATION SBC_AM_AUTO +#define SBC_ENC_DEFAULT_RATE 0 +#define SBC_ENC_DEFAULT_CHANNELS 0 + +#define SBC_ENC_BITPOOL_AUTO 1 +#define SBC_ENC_BITPOOL_MIN 2 +#define SBC_ENC_BITPOOL_MIN_STR "2" +#define SBC_ENC_BITPOOL_MAX 64 +#define SBC_ENC_BITPOOL_MAX_STR "64" + +GST_DEBUG_CATEGORY_STATIC(sbc_enc_debug); +#define GST_CAT_DEFAULT sbc_enc_debug + +#define GST_TYPE_SBC_MODE (gst_sbc_mode_get_type()) + +static GType gst_sbc_mode_get_type(void) +{ +	static GType sbc_mode_type = 0; +	static GEnumValue sbc_modes[] = { +		{  SBC_MODE_MONO,		"Mono",		"mono"	}, +		{  SBC_MODE_DUAL_CHANNEL,	"Dual Channel",	"dual"	}, +		{  SBC_MODE_STEREO,		"Stereo",	"stereo"}, +		{  SBC_MODE_JOINT_STEREO,	"Joint Stereo",	"joint"	}, +		{  SBC_MODE_AUTO,		"Auto",		"auto"	}, +		{ -1, NULL, NULL} +	}; + +	if (!sbc_mode_type) +		sbc_mode_type = g_enum_register_static("GstSbcMode", sbc_modes); + +	return sbc_mode_type; +} + +#define GST_TYPE_SBC_ALLOCATION (gst_sbc_allocation_get_type()) + +static GType gst_sbc_allocation_get_type(void) +{ +	static GType sbc_allocation_type = 0; +	static GEnumValue sbc_allocations[] = { +		{ SBC_AM_LOUDNESS,	"Loudness",	"loudness" }, +		{ SBC_AM_SNR,		"SNR",		"snr" }, +		{ SBC_AM_AUTO,		"Auto",		"auto" }, +		{ -1, NULL, NULL} +	}; + +	if (!sbc_allocation_type) +		sbc_allocation_type = g_enum_register_static( +				"GstSbcAllocation", sbc_allocations); + +	return sbc_allocation_type; +} + +#define GST_TYPE_SBC_BLOCKS (gst_sbc_blocks_get_type()) + +static GType gst_sbc_blocks_get_type(void) +{ +	static GType sbc_blocks_type = 0; +	static GEnumValue sbc_blocks[] = { +		{ 0,	"Auto",		"auto" }, +		{ 4,	"4",		"4" }, +		{ 8,	"8",		"8" }, +		{ 12,	"12",		"12" }, +		{ 16,	"16",		"16" }, +		{ -1, NULL, NULL} +	}; + +	if (!sbc_blocks_type) +		sbc_blocks_type = g_enum_register_static( +				"GstSbcBlocks", sbc_blocks); + +	return sbc_blocks_type; +} + +#define GST_TYPE_SBC_SUBBANDS (gst_sbc_subbands_get_type()) + +static GType gst_sbc_subbands_get_type(void) +{ +	static GType sbc_subbands_type = 0; +	static GEnumValue sbc_subbands[] = { +		{ 0,	"Auto",		"auto" }, +		{ 4,	"4 subbands",	"4" }, +		{ 8,	"8 subbands",	"8" }, +		{ -1, NULL, NULL} +	}; + +	if (!sbc_subbands_type) +		sbc_subbands_type = g_enum_register_static( +				"GstSbcSubbands", sbc_subbands); + +	return sbc_subbands_type; +} + +enum { +	PROP_0, +	PROP_MODE, +	PROP_ALLOCATION, +	PROP_BLOCKS, +	PROP_SUBBANDS, +	PROP_BITPOOL +}; + +GST_BOILERPLATE(GstSbcEnc, gst_sbc_enc, GstElement, GST_TYPE_ELEMENT); + +static const GstElementDetails sbc_enc_details = +	GST_ELEMENT_DETAILS("Bluetooth SBC encoder", +				"Codec/Encoder/Audio", +				"Encode a SBC audio stream", +				"Marcel Holtmann <marcel@holtmann.org>"); + +static GstStaticPadTemplate sbc_enc_sink_factory = +	GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, +		GST_STATIC_CAPS("audio/x-raw-int, " +				"rate = (int) { 16000, 32000, 44100, 48000 }, " +				"channels = (int) [ 1, 2 ], " +				"endianness = (int) LITTLE_ENDIAN, " +				"signed = (boolean) true, " +				"width = (int) 16, " +				"depth = (int) 16")); + +static GstStaticPadTemplate sbc_enc_src_factory = +	GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, +		GST_STATIC_CAPS("audio/x-sbc, " +				"rate = (int) { 16000, 32000, 44100, 48000 }, " +				"channels = (int) [ 1, 2 ], " +				"mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, " +				"blocks = (int) { 4, 8, 12, 16 }, " +				"subbands = (int) { 4, 8 }, " +				"allocation = (string) { \"snr\", \"loudness\" }, " +				"bitpool = (int) [ " SBC_ENC_BITPOOL_MIN_STR +				", " SBC_ENC_BITPOOL_MAX_STR " ]")); + +gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps); + +static GstCaps* sbc_enc_generate_srcpad_caps(GstSbcEnc *enc) +{ +	GstCaps* src_caps; +	GstStructure *structure; +	GEnumValue *enum_value; +	GEnumClass *enum_class; +	GValue *value; + +	src_caps = gst_caps_copy(gst_pad_get_pad_template_caps(enc->srcpad)); +	structure = gst_caps_get_structure(src_caps, 0); + +	value = g_new0(GValue, 1); + +	if (enc->rate != 0) +		gst_sbc_util_set_structure_int_param(structure, "rate", +			enc->rate, value); + +	if (enc->channels != 0) +		gst_sbc_util_set_structure_int_param(structure, "channels", +			enc->channels, value); + +	if (enc->subbands != 0) +		gst_sbc_util_set_structure_int_param(structure, "subbands", +			enc->subbands, value); + +	if (enc->blocks != 0) +		gst_sbc_util_set_structure_int_param(structure, "blocks", +			enc->blocks, value); + +	if (enc->bitpool != SBC_ENC_BITPOOL_AUTO) +		gst_sbc_util_set_structure_int_param(structure, "bitpool", +			enc->bitpool, value); + +	if (enc->mode != SBC_ENC_DEFAULT_MODE) { +		enum_class = g_type_class_ref(GST_TYPE_SBC_MODE); +		enum_value = g_enum_get_value(enum_class, enc->mode); +		gst_sbc_util_set_structure_string_param(structure, "mode", +			enum_value->value_nick, value); +		g_type_class_unref(enum_class); +	} + +	if (enc->allocation != SBC_AM_AUTO) { +		enum_class = g_type_class_ref(GST_TYPE_SBC_ALLOCATION); +		enum_value = g_enum_get_value(enum_class, enc->allocation); +		gst_sbc_util_set_structure_string_param(structure, "allocation", +			enum_value->value_nick, value); +		g_type_class_unref(enum_class); +	} + +	g_free(value); + +	return src_caps; +} + +static GstCaps* sbc_enc_src_getcaps (GstPad * pad) +{ +	GstSbcEnc *enc; + +	enc = GST_SBC_ENC(GST_PAD_PARENT(pad)); + +	return sbc_enc_generate_srcpad_caps(enc); +} + +static gboolean sbc_enc_src_setcaps (GstPad *pad, GstCaps *caps) +{ +	GstSbcEnc *enc = GST_SBC_ENC(GST_PAD_PARENT(pad)); + +	GST_LOG_OBJECT(enc, "setting srcpad caps"); + +	return gst_sbc_enc_fill_sbc_params(enc, caps); +} + +static GstCaps* sbc_enc_src_caps_fixate(GstSbcEnc *enc, GstCaps *caps) +{ +	gchar *error_message = NULL; +	GstCaps* result; + +	result = gst_sbc_util_caps_fixate(caps, &error_message); + +	if (!result) { +		GST_WARNING_OBJECT (enc, "Invalid input caps caused parsing " +				"error: %s", error_message); +		g_free(error_message); +		return NULL; +	} + +	return result; +} + +static GstCaps* sbc_enc_get_fixed_srcpad_caps(GstSbcEnc *enc) +{ +	GstCaps *caps; +	gboolean res = TRUE; +	GstCaps *result_caps = NULL; + +	caps = gst_pad_get_allowed_caps(enc->srcpad); +	if (caps == NULL) +		caps = sbc_enc_src_getcaps(enc->srcpad); + +	if (caps == GST_CAPS_NONE || gst_caps_is_empty(caps)) { +		res = FALSE; +		goto done; +	} + +	result_caps = sbc_enc_src_caps_fixate(enc, caps); + +done: +	gst_caps_unref(caps); + +	if (!res) +		return NULL; + +	return result_caps; +} + +static gboolean sbc_enc_sink_setcaps (GstPad * pad, GstCaps * caps) +{ +	GstSbcEnc *enc; +	GstStructure *structure; +	GstCaps *src_caps; +	gint rate, channels; +	gboolean res; + +	enc = GST_SBC_ENC(GST_PAD_PARENT (pad)); +	structure = gst_caps_get_structure(caps, 0); + +	if (!gst_structure_get_int(structure, "rate", &rate)) +		return FALSE; +	if (!gst_structure_get_int(structure, "channels", &channels)) +		return FALSE; + +	enc->rate = rate; +	enc->channels = channels; + +	src_caps = sbc_enc_get_fixed_srcpad_caps(enc); +	if (!src_caps) +		return FALSE; +	res = gst_pad_set_caps(enc->srcpad, src_caps); +	gst_caps_unref(src_caps); + +	return res; +} + +gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps) +{ +	if (!gst_caps_is_fixed(caps)) { +		GST_DEBUG_OBJECT(enc, "didn't receive fixed caps, " +				"returning false"); +		return FALSE; +	} + +	if (!gst_sbc_util_fill_sbc_params(&enc->sbc, caps)) +		return FALSE; + +	if (enc->rate != 0 && gst_sbc_parse_rate_from_sbc(enc->sbc.frequency) +				 != enc->rate) +		goto fail; + +	if (enc->channels != 0 && gst_sbc_get_channel_number(enc->sbc.mode) +				!= enc->channels) +		goto fail; + +	if (enc->blocks != 0 && gst_sbc_parse_blocks_from_sbc(enc->sbc.blocks) +				 != enc->blocks) +		goto fail; + +	if (enc->subbands != 0 && gst_sbc_parse_subbands_from_sbc( +				enc->sbc.subbands) != enc->subbands) +		goto fail; + +	if (enc->mode != SBC_ENC_DEFAULT_MODE && enc->sbc.mode != enc->mode) +		goto fail; + +	if (enc->allocation != SBC_AM_AUTO && +				enc->sbc.allocation != enc->allocation) +		goto fail; + +	if (enc->bitpool != SBC_ENC_BITPOOL_AUTO && +				enc->sbc.bitpool != enc->bitpool) +		goto fail; + +	enc->codesize = sbc_get_codesize(&enc->sbc); +	enc->frame_length = sbc_get_frame_length(&enc->sbc); +	enc->frame_duration = sbc_get_frame_duration(&enc->sbc); + +	GST_DEBUG_OBJECT(enc, "codesize: %d, frame_length: %d, frame_duration:" +			" %d", enc->codesize, enc->frame_length, +			enc->frame_duration); + +	return TRUE; + +fail: +	memset(&enc->sbc, 0, sizeof(sbc_t)); +	return FALSE; +} + +static GstFlowReturn sbc_enc_chain(GstPad *pad, GstBuffer *buffer) +{ +	GstSbcEnc *enc = GST_SBC_ENC(gst_pad_get_parent(pad)); +	GstAdapter *adapter = enc->adapter; +	GstFlowReturn res = GST_FLOW_OK; + +	gst_adapter_push(adapter, buffer); + +	while (gst_adapter_available(adapter) >= enc->codesize && +			res == GST_FLOW_OK) { +		GstBuffer *output; +		GstCaps *caps; +		const guint8 *data; +		gint consumed; + +		caps = GST_PAD_CAPS(enc->srcpad); +		res = gst_pad_alloc_buffer_and_set_caps(enc->srcpad, +						GST_BUFFER_OFFSET_NONE, +						enc->frame_length, caps, +						&output); +		if (res != GST_FLOW_OK) +			goto done; + +		data = gst_adapter_peek(adapter, enc->codesize); + +		consumed = sbc_encode(&enc->sbc, (gpointer) data, +					enc->codesize, +					GST_BUFFER_DATA(output), +					GST_BUFFER_SIZE(output), NULL); +		if (consumed <= 0) { +			GST_DEBUG_OBJECT (enc, "comsumed < 0, codesize: %d", +					enc->codesize); +			break; +		} +		gst_adapter_flush(adapter, consumed); + +		GST_BUFFER_TIMESTAMP(output) = GST_BUFFER_TIMESTAMP(buffer); +		/* we have only 1 frame */ +		GST_BUFFER_DURATION(output) = enc->frame_duration; + +		res = gst_pad_push(enc->srcpad, output); + +		if (res != GST_FLOW_OK) +			goto done; +	} + +done: +	gst_object_unref(enc); + +	return res; +} + +static GstStateChangeReturn sbc_enc_change_state(GstElement *element, +						GstStateChange transition) +{ +	GstSbcEnc *enc = GST_SBC_ENC(element); + +	switch (transition) { +	case GST_STATE_CHANGE_READY_TO_PAUSED: +		GST_DEBUG("Setup subband codec"); +		sbc_init(&enc->sbc, 0); +		break; + +	case GST_STATE_CHANGE_PAUSED_TO_READY: +		GST_DEBUG("Finish subband codec"); +		sbc_finish(&enc->sbc); +		break; + +	default: +		break; +	} + +	return parent_class->change_state(element, transition); +} + +static void gst_sbc_enc_dispose(GObject *object) +{ +	GstSbcEnc *enc = GST_SBC_ENC(object); + +	if (enc->adapter != NULL) +		g_object_unref (G_OBJECT (enc->adapter)); + +	enc->adapter = NULL; +} + +static void gst_sbc_enc_base_init(gpointer g_class) +{ +	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + +	gst_element_class_add_pad_template(element_class, +		gst_static_pad_template_get(&sbc_enc_sink_factory)); + +	gst_element_class_add_pad_template(element_class, +		gst_static_pad_template_get(&sbc_enc_src_factory)); + +	gst_element_class_set_details(element_class, &sbc_enc_details); +} + +static void gst_sbc_enc_set_property(GObject *object, guint prop_id, +					const GValue *value, GParamSpec *pspec) +{ +	GstSbcEnc *enc = GST_SBC_ENC(object); + +	/* changes to those properties will only happen on the next caps +	 * negotiation */ + +	switch (prop_id) { +	case PROP_MODE: +		enc->mode = g_value_get_enum(value); +		break; +	case PROP_ALLOCATION: +		enc->allocation = g_value_get_enum(value); +		break; +	case PROP_BLOCKS: +		enc->blocks = g_value_get_enum(value); +		break; +	case PROP_SUBBANDS: +		enc->subbands = g_value_get_enum(value); +		break; +	case PROP_BITPOOL: +		enc->bitpool = g_value_get_int(value); +		break; +	default: +		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); +		break; +	} +} + +static void gst_sbc_enc_get_property(GObject *object, guint prop_id, +					GValue *value, GParamSpec *pspec) +{ +	GstSbcEnc *enc = GST_SBC_ENC(object); + +	switch (prop_id) { +	case PROP_MODE: +		g_value_set_enum(value, enc->mode); +		break; +	case PROP_ALLOCATION: +		g_value_set_enum(value, enc->allocation); +		break; +	case PROP_BLOCKS: +		g_value_set_enum(value, enc->blocks); +		break; +	case PROP_SUBBANDS: +		g_value_set_enum(value, enc->subbands); +		break; +	case PROP_BITPOOL: +		g_value_set_int(value, enc->bitpool); +		break; +	default: +		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); +		break; +	} +} + +static void gst_sbc_enc_class_init(GstSbcEncClass *klass) +{ +	GObjectClass *object_class = G_OBJECT_CLASS(klass); +	GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + +	parent_class = g_type_class_peek_parent(klass); + +	object_class->set_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_set_property); +	object_class->get_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_get_property); +	object_class->dispose = GST_DEBUG_FUNCPTR(gst_sbc_enc_dispose); + +	element_class->change_state = GST_DEBUG_FUNCPTR(sbc_enc_change_state); + +	g_object_class_install_property(object_class, PROP_MODE, +			g_param_spec_enum("mode", "Mode", +				"Encoding mode", GST_TYPE_SBC_MODE, +				SBC_ENC_DEFAULT_MODE, G_PARAM_READWRITE)); + +	g_object_class_install_property(object_class, PROP_ALLOCATION, +			g_param_spec_enum("allocation", "Allocation", +				"Allocation method", GST_TYPE_SBC_ALLOCATION, +				SBC_ENC_DEFAULT_ALLOCATION, G_PARAM_READWRITE)); + +	g_object_class_install_property(object_class, PROP_BLOCKS, +			g_param_spec_enum("blocks", "Blocks", +				"Blocks", GST_TYPE_SBC_BLOCKS, +				SBC_ENC_DEFAULT_BLOCKS, G_PARAM_READWRITE)); + +	g_object_class_install_property(object_class, PROP_SUBBANDS, +			g_param_spec_enum("subbands", "Sub bands", +				"Number of sub bands", GST_TYPE_SBC_SUBBANDS, +				SBC_ENC_DEFAULT_SUB_BANDS, G_PARAM_READWRITE)); + +	g_object_class_install_property(object_class, PROP_BITPOOL, +			g_param_spec_int("bitpool", "Bitpool", +				"Bitpool (use 1 for automatic selection)", +				SBC_ENC_BITPOOL_AUTO, SBC_ENC_BITPOOL_MAX, +				SBC_ENC_BITPOOL_AUTO, G_PARAM_READWRITE)); + +	GST_DEBUG_CATEGORY_INIT(sbc_enc_debug, "sbcenc", 0, +						"SBC encoding element"); +} + +static void gst_sbc_enc_init(GstSbcEnc *self, GstSbcEncClass *klass) +{ +	self->sinkpad = gst_pad_new_from_static_template( +		&sbc_enc_sink_factory, "sink"); +	gst_pad_set_setcaps_function (self->sinkpad, +			GST_DEBUG_FUNCPTR (sbc_enc_sink_setcaps)); +	gst_element_add_pad(GST_ELEMENT(self), self->sinkpad); + +	self->srcpad = gst_pad_new_from_static_template( +		&sbc_enc_src_factory, "src"); +	gst_pad_set_getcaps_function(self->srcpad, +		GST_DEBUG_FUNCPTR(sbc_enc_src_getcaps)); +	gst_pad_set_setcaps_function(self->srcpad, +		GST_DEBUG_FUNCPTR(sbc_enc_src_setcaps)); +	gst_element_add_pad(GST_ELEMENT(self), self->srcpad); + +	gst_pad_set_chain_function(self->sinkpad, +		GST_DEBUG_FUNCPTR(sbc_enc_chain)); + +	self->subbands = SBC_ENC_DEFAULT_SUB_BANDS; +	self->blocks = SBC_ENC_DEFAULT_BLOCKS; +	self->mode = SBC_ENC_DEFAULT_MODE; +	self->allocation = SBC_ENC_DEFAULT_ALLOCATION; +	self->rate = SBC_ENC_DEFAULT_RATE; +	self->channels = SBC_ENC_DEFAULT_CHANNELS; +	self->bitpool = SBC_ENC_BITPOOL_AUTO; + +	self->frame_length = 0; +	self->frame_duration = 0; + +	self->adapter = gst_adapter_new(); +} + +gboolean gst_sbc_enc_plugin_init (GstPlugin * plugin) +{ +	return gst_element_register (plugin, "sbcenc", +			GST_RANK_NONE, GST_TYPE_SBC_ENC); +} + +  | 
