diff --git a/ext/ldac/gstldacenc.c b/ext/ldac/gstldacenc.c new file mode 100644 index 0000000000..2d414b0cd7 --- /dev/null +++ b/ext/ldac/gstldacenc.c @@ -0,0 +1,618 @@ +/* GStreamer LDAC audio encoder + * Copyright (C) 2020 Asymptotic + * + * 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 + * + */ + +/** + * SECTION:element-ldacenc + * @title: ldacenc + * + * This element encodes raw integer PCM audio into a Bluetooth LDAC audio. + * + * ## Example pipeline + * |[ + * gst-launch-1.0 -v audiotestsrc ! ldacenc ! rtpldacpay mtu=679 ! avdtpsink + * ]| Encode a sine wave into LDAC, RTP payload it and send over bluetooth + * + * Since: 1.20 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "gstldacenc.h" + +/* + * MTU size required for LDAC A2DP streaming. Required for initializing the + * encoder. + */ +#define GST_LDAC_MTU_REQUIRED 679 + +GST_DEBUG_CATEGORY_STATIC (ldac_enc_debug); +#define GST_CAT_DEFAULT ldac_enc_debug + +#define parent_class gst_ldac_enc_parent_class +G_DEFINE_TYPE (GstLdacEnc, gst_ldac_enc, GST_TYPE_AUDIO_ENCODER); + +static GstStaticPadTemplate ldac_enc_sink_factory = +GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS + ("audio/x-raw, format=(string) { S16LE, S24LE, S32LE, F32LE }, " + "rate = (int) { 44100, 48000, 88200, 96000 }, " + "channels = (int) [ 1, 2 ] ")); + +static GstStaticPadTemplate ldac_enc_src_factory = + GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-ldac, " + "rate = (int) { 44100, 48000, 88200, 96000 }, " + "channels = (int) 1, channel-mode = (string)mono; " + "audio/x-ldac, " + "rate = (int) { 44100, 48000, 882000, 96000 }, " + "channels = (int) 2, " "channel-mode = (string) { dual, stereo }")); + +enum +{ + PROP_0, + PROP_EQMID +}; + +static void gst_ldac_enc_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec); +static void gst_ldac_enc_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec); + +static gboolean gst_ldac_enc_start (GstAudioEncoder * enc); +static gboolean gst_ldac_enc_stop (GstAudioEncoder * enc); +static gboolean gst_ldac_enc_set_format (GstAudioEncoder * enc, + GstAudioInfo * info); +static gboolean gst_ldac_enc_negotiate (GstAudioEncoder * enc); +static GstFlowReturn gst_ldac_enc_handle_frame (GstAudioEncoder * enc, + GstBuffer * buffer); +static guint gst_ldac_enc_get_num_frames (guint eqmid, guint channels); +static guint gst_ldac_enc_get_frame_length (guint eqmid, guint channels); +static guint gst_ldac_enc_get_num_samples (guint rate); + +#define GST_LDAC_EQMID (gst_ldac_eqmid_get_type ()) +static GType +gst_ldac_eqmid_get_type (void) +{ + static GType ldac_eqmid_type = 0; + static const GEnumValue eqmid_types[] = { + {GST_LDAC_EQMID_HQ, "HQ", "hq"}, + {GST_LDAC_EQMID_SQ, "SQ", "sq"}, + {GST_LDAC_EQMID_MQ, "MQ", "mq"}, + {0, NULL, NULL} + }; + + if (!ldac_eqmid_type) + ldac_eqmid_type = g_enum_register_static ("GstLdacEqmid", eqmid_types); + + return ldac_eqmid_type; +} + +static void +gst_ldac_enc_class_init (GstLdacEncClass * klass) +{ + GstAudioEncoderClass *encoder_class = GST_AUDIO_ENCODER_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GObjectClass *gobject_class = (GObjectClass *) klass; + + gobject_class->set_property = gst_ldac_enc_set_property; + gobject_class->get_property = gst_ldac_enc_get_property; + + encoder_class->start = GST_DEBUG_FUNCPTR (gst_ldac_enc_start); + encoder_class->stop = GST_DEBUG_FUNCPTR (gst_ldac_enc_stop); + encoder_class->set_format = GST_DEBUG_FUNCPTR (gst_ldac_enc_set_format); + encoder_class->handle_frame = GST_DEBUG_FUNCPTR (gst_ldac_enc_handle_frame); + encoder_class->negotiate = GST_DEBUG_FUNCPTR (gst_ldac_enc_negotiate); + + g_object_class_install_property (gobject_class, PROP_EQMID, + g_param_spec_enum ("eqmid", "Encode Quality Mode Index", + "Encode Quality Mode Index. 0: High Quality 1: Standard Quality " + "2: Mobile Use Quality", GST_LDAC_EQMID, + GST_LDAC_EQMID_SQ, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gst_element_class_add_static_pad_template (element_class, + &ldac_enc_sink_factory); + gst_element_class_add_static_pad_template (element_class, + &ldac_enc_src_factory); + + gst_element_class_set_static_metadata (element_class, + "Bluetooth LDAC audio encoder", "Codec/Encoder/Audio", + "Encode an LDAC audio stream", + "Sanchayan Maity "); + + GST_DEBUG_CATEGORY_INIT (ldac_enc_debug, "ldacenc", 0, + "LDAC encoding element"); +} + +static void +gst_ldac_enc_init (GstLdacEnc * self) +{ + GST_PAD_SET_ACCEPT_TEMPLATE (GST_AUDIO_ENCODER_SINK_PAD (self)); + self->eqmid = GST_LDAC_EQMID_SQ; + self->channel_mode = 0; + self->init_done = FALSE; +} + +static void +gst_ldac_enc_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GstLdacEnc *self = GST_LDAC_ENC (object); + + switch (property_id) { + case PROP_EQMID: + self->eqmid = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gst_ldac_enc_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GstLdacEnc *self = GST_LDAC_ENC (object); + + switch (property_id) { + case PROP_EQMID: + g_value_set_enum (value, self->eqmid); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GstCaps * +gst_ldac_enc_do_negotiate (GstAudioEncoder * audio_enc) +{ + GstLdacEnc *enc = GST_LDAC_ENC (audio_enc); + GstCaps *caps, *filter_caps; + GstCaps *output_caps = NULL; + GstStructure *s; + + /* Negotiate output format based on downstream caps restrictions */ + caps = gst_pad_get_allowed_caps (GST_AUDIO_ENCODER_SRC_PAD (enc)); + if (caps == GST_CAPS_NONE || gst_caps_is_empty (caps)) + goto failure; + + if (caps == NULL) + caps = gst_static_pad_template_get_caps (&ldac_enc_src_factory); + + /* Fixate output caps */ + filter_caps = gst_caps_new_simple ("audio/x-ldac", "rate", G_TYPE_INT, + enc->rate, "channels", G_TYPE_INT, enc->channels, NULL); + output_caps = gst_caps_intersect (caps, filter_caps); + gst_caps_unref (filter_caps); + + if (output_caps == NULL || gst_caps_is_empty (output_caps)) { + GST_WARNING_OBJECT (enc, "Couldn't negotiate output caps with input rate " + "%d and input channels %d and allowed output caps %" GST_PTR_FORMAT, + enc->rate, enc->channels, caps); + goto failure; + } + + gst_clear_caps (&caps); + + GST_DEBUG_OBJECT (enc, "fixating caps %" GST_PTR_FORMAT, output_caps); + output_caps = gst_caps_truncate (output_caps); + s = gst_caps_get_structure (output_caps, 0); + if (enc->channels == 1) + gst_structure_fixate_field_string (s, "channel-mode", "mono"); + else + gst_structure_fixate_field_string (s, "channel-mode", "stereo"); + s = NULL; + + /* In case there's anything else left to fixate */ + output_caps = gst_caps_fixate (output_caps); + gst_caps_set_simple (output_caps, "framed", G_TYPE_BOOLEAN, TRUE, NULL); + + GST_INFO_OBJECT (enc, "output caps %" GST_PTR_FORMAT, output_caps); + + if (enc->channels == 1) + enc->channel_mode = LDACBT_CHANNEL_MODE_MONO; + else + enc->channel_mode = LDACBT_CHANNEL_MODE_STEREO; + + return output_caps; + +failure: + if (output_caps) + gst_caps_unref (output_caps); + if (caps) + gst_caps_unref (caps); + return NULL; +} + +static gboolean +gst_ldac_enc_negotiate (GstAudioEncoder * audio_enc) +{ + GstLdacEnc *enc = GST_LDAC_ENC (audio_enc); + GstCaps *output_caps = NULL; + + output_caps = gst_ldac_enc_do_negotiate (audio_enc); + if (output_caps == NULL) { + GST_ERROR_OBJECT (enc, "failed to negotiate"); + return FALSE; + } + + if (!gst_audio_encoder_set_output_format (audio_enc, output_caps)) { + GST_ERROR_OBJECT (enc, "failed to configure output caps on src pad"); + gst_caps_unref (output_caps); + return FALSE; + } + gst_caps_unref (output_caps); + + return GST_AUDIO_ENCODER_CLASS (parent_class)->negotiate (audio_enc); +} + +static gboolean +gst_ldac_enc_set_format (GstAudioEncoder * audio_enc, GstAudioInfo * info) +{ + GstLdacEnc *enc = GST_LDAC_ENC (audio_enc); + GstCaps *output_caps = NULL; + guint num_ldac_frames, num_samples; + gint ret = 0; + + enc->rate = GST_AUDIO_INFO_RATE (info); + enc->channels = GST_AUDIO_INFO_CHANNELS (info); + + switch (GST_AUDIO_INFO_FORMAT (info)) { + case GST_AUDIO_FORMAT_S16: + enc->ldac_fmt = LDACBT_SMPL_FMT_S16; + break; + case GST_AUDIO_FORMAT_S24: + enc->ldac_fmt = LDACBT_SMPL_FMT_S24; + break; + case GST_AUDIO_FORMAT_S32: + enc->ldac_fmt = LDACBT_SMPL_FMT_S32; + break; + case GST_AUDIO_FORMAT_F32: + enc->ldac_fmt = LDACBT_SMPL_FMT_F32; + break; + default: + GST_ERROR_OBJECT (enc, "Invalid audio format"); + return FALSE; + } + + output_caps = gst_ldac_enc_do_negotiate (audio_enc); + if (output_caps == NULL) { + GST_ERROR_OBJECT (enc, "failed to negotiate"); + return FALSE; + } + + if (!gst_audio_encoder_set_output_format (audio_enc, output_caps)) { + GST_ERROR_OBJECT (enc, "failed to configure output caps on src pad"); + gst_caps_unref (output_caps); + return FALSE; + } + gst_caps_unref (output_caps); + + num_samples = gst_ldac_enc_get_num_samples (enc->rate); + num_ldac_frames = gst_ldac_enc_get_num_frames (enc->eqmid, enc->channels); + gst_audio_encoder_set_frame_samples_min (audio_enc, + num_samples * num_ldac_frames); + + /* + * If initialisation was already done means caps have changed, close the + * handle. Closed handle can be initialised and used again. + */ + if (enc->init_done) { + ldacBT_close_handle (enc->ldac); + enc->init_done = FALSE; + } + + /* + * libldac exposes a bluetooth centric API and emits multiple LDAC frames + * depending on the MTU. The MTU is required for LDAC A2DP streaming, is + * inclusive of the RTP header and is required by the encoder. The internal + * encoder API is not exposed in the public interface. + */ + ret = + ldacBT_init_handle_encode (enc->ldac, GST_LDAC_MTU_REQUIRED, enc->eqmid, + enc->channel_mode, enc->ldac_fmt, enc->rate); + if (ret != 0) { + GST_ERROR_OBJECT (enc, "Failed to initialize LDAC handle, ret: %d", ret); + return FALSE; + } + enc->init_done = TRUE; + + return TRUE; +} + +static GstFlowReturn +gst_ldac_enc_handle_frame (GstAudioEncoder * audio_enc, GstBuffer * buffer) +{ + GstLdacEnc *enc = GST_LDAC_ENC (audio_enc); + GstMapInfo in_map, out_map; + GstAudioInfo *info; + GstBuffer *outbuf; + const guint8 *in_data; + guint8 *out_data; + gint encoded, to_encode = 0; + gint samples_consumed = 0; + guint frames, frame_len; + guint ldac_enc_read = 0; + guint frame_count = 0; + + if (buffer == NULL) + return GST_FLOW_OK; + + if (!gst_buffer_map (buffer, &in_map, GST_MAP_READ)) { + GST_ELEMENT_ERROR (audio_enc, STREAM, FAILED, (NULL), + ("Failed to map data from input buffer")); + return GST_FLOW_ERROR; + } + + info = gst_audio_encoder_get_audio_info (audio_enc); + ldac_enc_read = LDACBT_ENC_LSU * info->bpf; + /* + * We may produce extra frames at the end of encoding process (See below). + * Consider some additional frames while allocating output buffer if this + * happens. + */ + frames = (in_map.size / ldac_enc_read) + 4; + + frame_len = gst_ldac_enc_get_frame_length (enc->eqmid, info->channels); + outbuf = gst_audio_encoder_allocate_output_buffer (audio_enc, + frames * frame_len); + if (outbuf == NULL) + goto no_buffer; + + gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE); + in_data = in_map.data; + out_data = out_map.data; + to_encode = in_map.size; + + /* + * ldacBT_encode does not generate an output frame each time it is called. + * For each invocation, it consumes number of sample * bpf bytes of data. + * Depending on the eqmid setting and channels, it will emit multiple frames + * only after the required number of frames are packed for payloading. Below + * for loop exists primarily to handle this. + */ + for (;;) { + guint8 pcm[LDACBT_MAX_LSU * 4 /* bytes/sample */ * 2 /* ch */ ] = { 0 }; + gint ldac_frame_num, written; + guint8 *inp_data = NULL; + gboolean done = FALSE; + gint ret; + + /* + * Even with minimum frame samples specified in set_format with EOS, + * we may get a buffer which is not a multiple of LDACBT_ENC_LSU. LDAC + * encoder always reads a multiple of this and to handle this scenario + * we use local PCM array and in the last iteration when buffer bytes + * < LDACBT_ENC_LSU * bpf, we copy only to_encode bytes to prevent + * walking off the end of input buffer and the rest of the bytes in + * PCM buffer would be zero, so should be safe from encoding point of + * view. + */ + if (to_encode < 0) { + /* + * We got < LDACBT_ENC_LSU * bpf for last iteration. Force the encoder + * to encode the remaining bytes in buffer by passing NULL to the input + * PCM buffer argument. + */ + inp_data = NULL; + done = TRUE; + } else if (to_encode >= ldac_enc_read) { + memcpy (pcm, in_data, ldac_enc_read); + inp_data = &pcm[0]; + } else if (to_encode > 0 && to_encode < ldac_enc_read) { + memcpy (pcm, in_data, to_encode); + inp_data = &pcm[0]; + } + + /* + * Note that while we do not explicitly pass length of data to library + * anywhere, based on the initialization considering eqmid and rate, the + * library will consume a fix number of samples per call. This combined + * with the previous step ensures that the library does not read outside + * of in_data and out_data. + */ + ret = ldacBT_encode (enc->ldac, (void *) inp_data, &encoded, + (guint8 *) out_data, &written, &ldac_frame_num); + if (ret < 0) { + GST_ERROR_OBJECT (enc, "encoding error, ret = %d written = %d", + ret, ldac_frame_num); + goto encoding_error; + } else { + to_encode -= encoded; + in_data = in_data + encoded; + out_data = out_data + written; + frame_count += ldac_frame_num; + + GST_LOG_OBJECT (enc, + "To Encode: %d, Encoded: %d, Written: %d, LDAC Frames: %d", to_encode, + encoded, written, ldac_frame_num); + + if (done || (to_encode == 0 && encoded == ldac_enc_read)) + break; + } + } + + gst_buffer_unmap (outbuf, &out_map); + + if (frame_count > 0) { + samples_consumed = in_map.size / info->bpf; + gst_buffer_set_size (outbuf, frame_count * frame_len); + } else { + samples_consumed = 0; + gst_buffer_replace (&outbuf, NULL); + } + + gst_buffer_unmap (buffer, &in_map); + + return gst_audio_encoder_finish_frame (audio_enc, outbuf, samples_consumed); + +no_buffer: + { + gst_buffer_unmap (buffer, &in_map); + + GST_ERROR_OBJECT (enc, "could not allocate output buffer"); + + return GST_FLOW_ERROR; + } +encoding_error: + { + gst_buffer_unmap (buffer, &in_map); + + ldacBT_free_handle (enc->ldac); + + enc->ldac = NULL; + + return GST_FLOW_ERROR; + } +} + +static gboolean +gst_ldac_enc_start (GstAudioEncoder * audio_enc) +{ + GstLdacEnc *enc = GST_LDAC_ENC (audio_enc); + + GST_INFO_OBJECT (enc, "Setup LDAC codec"); + /* Note that this only allocates the LDAC handle */ + enc->ldac = ldacBT_get_handle (); + if (enc->ldac == NULL) { + GST_ERROR_OBJECT (enc, "Failed to get LDAC handle"); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_ldac_enc_stop (GstAudioEncoder * audio_enc) +{ + GstLdacEnc *enc = GST_LDAC_ENC (audio_enc); + + GST_INFO_OBJECT (enc, "Finish LDAC codec"); + + if (enc->ldac) { + ldacBT_free_handle (enc->ldac); + enc->ldac = NULL; + } + + enc->eqmid = GST_LDAC_EQMID_SQ; + enc->channel_mode = 0; + enc->init_done = FALSE; + + return TRUE; +} + +/** + * gst_ldac_enc_get_frame_length + * @eqmid: Encode Quality Mode Index + * @channels: Number of channels + * + * Returns: Frame length. + */ +static guint +gst_ldac_enc_get_frame_length (guint eqmid, guint channels) +{ + g_assert (channels == 1 || channels == 2); + + switch (eqmid) { + /* Encode setting for High Quality */ + case GST_LDAC_EQMID_HQ: + return 165 * channels; + /* Encode setting for Standard Quality */ + case GST_LDAC_EQMID_SQ: + return 110 * channels; + /* Encode setting for Mobile use Quality */ + case GST_LDAC_EQMID_MQ: + return 55 * channels; + default: + break; + } + + g_assert_not_reached (); + + /* If assertion gets compiled out */ + return 110 * channels; +} + +/** + * gst_ldac_enc_get_num_frames + * @eqmid: Encode Quality Mode Index + * @channels: Number of channels + * + * Returns: Number of LDAC frames per packet. + */ +static guint +gst_ldac_enc_get_num_frames (guint eqmid, guint channels) +{ + g_assert (channels == 1 || channels == 2); + + switch (eqmid) { + /* Encode setting for High Quality */ + case GST_LDAC_EQMID_HQ: + return 4 / channels; + /* Encode setting for Standard Quality */ + case GST_LDAC_EQMID_SQ: + return 6 / channels; + /* Encode setting for Mobile use Quality */ + case GST_LDAC_EQMID_MQ: + return 12 / channels; + default: + break; + } + + g_assert_not_reached (); + + /* If assertion gets compiled out */ + return 6 / channels; +} + +/** + * gst_ldac_enc_get_num_samples + * @rate: Sampling rate + * + * Number of samples in input PCM signal for encoding is fixed to + * LDACBT_ENC_LSU viz. 128 samples/channel and it is not affected + * by sampling frequency. However, frame size is 128 samples at 44.1 + * and 48 KHz and 256 at 88.2 and 96 KHz. + * + * Returns: Number of samples / channel + */ +static guint +gst_ldac_enc_get_num_samples (guint rate) +{ + switch (rate) { + case 44100: + case 48000: + return 128; + case 88200: + case 96000: + return 256; + default: + break; + } + + g_assert_not_reached (); + + /* If assertion gets compiled out */ + return 128; +} diff --git a/ext/ldac/gstldacenc.h b/ext/ldac/gstldacenc.h new file mode 100644 index 0000000000..2eafb7bcce --- /dev/null +++ b/ext/ldac/gstldacenc.h @@ -0,0 +1,66 @@ +/* GStreamer LDAC audio encoder + * Copyright (C) 2020 Asymptotic + * + * 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 + */ + +#include +#include + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_LDAC_ENC \ + (gst_ldac_enc_get_type()) +#define GST_LDAC_ENC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_LDAC_ENC,GstLdacEnc)) +#define GST_LDAC_ENC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_LDAC_ENC,GstLdacEncClass)) +#define GST_IS_LDAC_ENC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_LDAC_ENC)) +#define GST_IS_LDAC_ENC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_LDAC_ENC)) + +typedef struct _GstLdacEnc GstLdacEnc; +typedef struct _GstLdacEncClass GstLdacEncClass; + +typedef enum +{ + GST_LDAC_EQMID_HQ = 0, + GST_LDAC_EQMID_SQ, + GST_LDAC_EQMID_MQ +} GstLdacEqmid; + +struct _GstLdacEnc { + GstAudioEncoder audio_encoder; + GstLdacEqmid eqmid; + + guint rate; + guint channels; + guint channel_mode; + gboolean init_done; + + LDACBT_SMPL_FMT_T ldac_fmt; + HANDLE_LDAC_BT ldac; +}; + +struct _GstLdacEncClass { + GstAudioEncoderClass audio_encoder_class; +}; + +GType gst_ldac_enc_get_type (void); + +G_END_DECLS diff --git a/ext/ldac/ldac-plugin.c b/ext/ldac/ldac-plugin.c new file mode 100644 index 0000000000..f9af7b2382 --- /dev/null +++ b/ext/ldac/ldac-plugin.c @@ -0,0 +1,38 @@ +/* GStreamer LDAC audio plugin + * Copyright (C) 2020 Asymptotic + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstldacenc.h" +#include + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gst_element_register (plugin, "ldacenc", GST_RANK_NONE, GST_TYPE_LDAC_ENC); + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + ldac, + "LDAC bluetooth audio support", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/ext/ldac/meson.build b/ext/ldac/meson.build new file mode 100644 index 0000000000..e79fc9bdee --- /dev/null +++ b/ext/ldac/meson.build @@ -0,0 +1,19 @@ +ldac_sources = [ + 'ldac-plugin.c', + 'gstldacenc.c', +] + +ldac_dep = cc.find_library('ldacBT_enc', required : get_option('ldac')) + +if ldac_dep.found() + gstldac = library('gstldac', + ldac_sources, + c_args : gst_plugins_bad_args, + include_directories : [configinc], + dependencies : [gstaudio_dep, ldac_dep], + install : true, + install_dir : plugins_install_dir, + ) + pkgconfig.generate(gstldac, install_dir : plugins_pkgconfig_install_dir) + plugins += [gstldac] +endif diff --git a/ext/meson.build b/ext/meson.build index 33f2cbd9f7..2f034223d5 100644 --- a/ext/meson.build +++ b/ext/meson.build @@ -24,6 +24,7 @@ subdir('iqa') subdir('isac') subdir('kate') subdir('ladspa') +subdir('ldac') subdir('libde265') subdir('libmms') subdir('lv2') diff --git a/meson_options.txt b/meson_options.txt index 9c9e2311c4..19ed4ac6ae 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -113,6 +113,7 @@ option('iqa', type : 'feature', value : 'auto', description : 'Image quality ass option('kate', type : 'feature', value : 'auto', description : 'Kate subtitle parser, tagger, and codec plugin') option('kms', type : 'feature', value : 'auto', description : 'KMS video sink plugin') option('ladspa', type : 'feature', value : 'auto', description : 'LADSPA plugin bridge') +option('ldac', type : 'feature', value : 'auto', description : 'LDAC bluetooth audio codec plugin') option('libde265', type : 'feature', value : 'auto', description : 'HEVC/H.265 video decoder plugin') option('libmms', type : 'feature', value : 'auto', description : 'Microsoft multimedia server network source plugin') option('lv2', type : 'feature', value : 'auto', description : 'LV2 audio plugin bridge')