/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2009 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 "gstsbcutil.h" #include "gstsbcenc.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) BYTE_ORDER, " "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); }