From 1865c87ec63c4e2778f545f1ab9208d98a3b58d3 Mon Sep 17 00:00:00 2001 From: Taruntej Kanakamalla Date: Fri, 26 Aug 2022 13:35:21 +0530 Subject: [PATCH] lc3: plugin for LC3 audio codec lc3enc: - encodes raw audio into lc3 format - uses the default bitrate property and frame duration from the caps to determine the byte count of the encoded frames if it is not specified in the downstream caps after negotiation - uses the same byte count value for all the channels - all the common session configuration parameters are passed in the src caps lc3dec: - decodes an lc3 encoded audio - sink caps should contain all the common session configuration params - uses frame_duration and frame_bytes (byte count) in the sink caps as parameters along with sample rate and channel count - byte count is same for all the channels Part-of: --- .../gst-plugins-bad/ext/lc3/gstlc3common.h | 30 ++ .../gst-plugins-bad/ext/lc3/gstlc3dec.c | 351 +++++++++++++++ .../gst-plugins-bad/ext/lc3/gstlc3dec.h | 54 +++ .../gst-plugins-bad/ext/lc3/gstlc3enc.c | 426 ++++++++++++++++++ .../gst-plugins-bad/ext/lc3/gstlc3enc.h | 57 +++ .../gst-plugins-bad/ext/lc3/lc3-plugin.c | 41 ++ .../gst-plugins-bad/ext/lc3/meson.build | 20 + subprojects/gst-plugins-bad/ext/meson.build | 1 + subprojects/gst-plugins-bad/meson_options.txt | 1 + subprojects/liblc3.wrap | 6 + 10 files changed, 987 insertions(+) create mode 100644 subprojects/gst-plugins-bad/ext/lc3/gstlc3common.h create mode 100644 subprojects/gst-plugins-bad/ext/lc3/gstlc3dec.c create mode 100644 subprojects/gst-plugins-bad/ext/lc3/gstlc3dec.h create mode 100644 subprojects/gst-plugins-bad/ext/lc3/gstlc3enc.c create mode 100644 subprojects/gst-plugins-bad/ext/lc3/gstlc3enc.h create mode 100644 subprojects/gst-plugins-bad/ext/lc3/lc3-plugin.c create mode 100644 subprojects/gst-plugins-bad/ext/lc3/meson.build create mode 100644 subprojects/liblc3.wrap diff --git a/subprojects/gst-plugins-bad/ext/lc3/gstlc3common.h b/subprojects/gst-plugins-bad/ext/lc3/gstlc3common.h new file mode 100644 index 0000000000..98e216fb0c --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lc3/gstlc3common.h @@ -0,0 +1,30 @@ +/* GStreamer LC3 Bluetooth LE audio decoder + * Copyright (C) 2023 Asymptotic Inc. + * + * 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. + */ + +#pragma once + +#include + +#define SAMPLE_RATES "8000, 16000, 24000, 32000, 48000" +#define FORMAT "S16LE" + +#define FRAME_DURATION_10000US 10000 + +#define FRAME_DURATIONS "10000, 7500" +#define FRAME_BYTES_RANGE "20, 400" diff --git a/subprojects/gst-plugins-bad/ext/lc3/gstlc3dec.c b/subprojects/gst-plugins-bad/ext/lc3/gstlc3dec.c new file mode 100644 index 0000000000..57126b85d4 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lc3/gstlc3dec.c @@ -0,0 +1,351 @@ +/* GStreamer LC3 Bluetooth LE audio decoder + * Copyright (C) 2023 Asymptotic Inc. + * + * 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 Street, Suite 500, + * Boston, MA 02110-1335, USA. + */ + +/** + * SECTION:element-lc3dec + * + * The lc3dec decodes LC3 data into raw audio. + * + * + * Example launch line + * |[ + * gst-launch-1.0 -v filesrc location=encoded.lc3 blocksize=200 ! \ + * audio/x-lc3,frame-bytes=100,frame-duration-us=10000,channels=2,rate=48000,channel-mask=\(bitmask\)0x00000000000000003 !\ + * lc3dec ! wavenc ! filesink location=decoded.wav + * ]| + * + * Decodes the LC3 frames each with 100 bytes of size, converts it to raw audio and saves into a .wav file + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "gstlc3common.h" +#include "gstlc3dec.h" + +GST_DEBUG_CATEGORY_STATIC (gst_lc3_dec_debug_category); +#define GST_CAT_DEFAULT gst_lc3_dec_debug_category + +#define parent_class gst_lc3_dec_parent_class +G_DEFINE_TYPE (GstLc3Dec, gst_lc3_dec, GST_TYPE_AUDIO_DECODER); +GST_ELEMENT_REGISTER_DEFINE (lc3dec, "lc3dec", GST_RANK_NONE, GST_TYPE_LC3_DEC); + +/* prototypes */ +static gboolean gst_lc3_dec_start (GstAudioDecoder * decoder); +static gboolean gst_lc3_dec_stop (GstAudioDecoder * decoder); +static gboolean gst_lc3_dec_set_format (GstAudioDecoder * decoder, + GstCaps * caps); +static GstFlowReturn gst_lc3_dec_handle_frame (GstAudioDecoder * decoder, + GstBuffer * buffer); + +/* pad templates */ +static GstStaticPadTemplate gst_lc3_dec_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = " FORMAT ", layout=interleaved, " + "rate = { " SAMPLE_RATES " }, channels = [1,MAX]") + ); + +static GstStaticPadTemplate gst_lc3_dec_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-lc3, rate = { " SAMPLE_RATES " }, " + "channels = [1,MAX]," + "frame-bytes = (int) [" FRAME_BYTES_RANGE "], " + "frame-duration-us = (int) { " FRAME_DURATIONS " }, " + "framed=(boolean) true") + ); + +/* class initialization */ +static void +gst_lc3_dec_class_init (GstLc3DecClass * klass) +{ + GstAudioDecoderClass *audio_decoder_class = GST_AUDIO_DECODER_CLASS (klass); + + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), + &gst_lc3_dec_src_template); + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), + &gst_lc3_dec_sink_template); + + gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass), + "LC3 Bluetooth Audio decoder", "Codec/Decoder/Audio", + "Decodes an LC3 Audio stream to raw audio", + "Taruntej Kanakamalla "); + + GST_DEBUG_CATEGORY_INIT (gst_lc3_dec_debug_category, "lc3dec", 0, + "debug category for lc3dec element"); + + audio_decoder_class->start = GST_DEBUG_FUNCPTR (gst_lc3_dec_start); + audio_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_lc3_dec_stop); + audio_decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_lc3_dec_set_format); + audio_decoder_class->handle_frame = + GST_DEBUG_FUNCPTR (gst_lc3_dec_handle_frame); +} + +static void +gst_lc3_dec_init (GstLc3Dec * lc3_dec) +{ +} + +static gboolean +gst_lc3_dec_start (GstAudioDecoder * decoder) +{ + /* let the baseclass convert the segment data + * from 'bytes' to 'time' format + */ + gst_audio_decoder_set_estimate_rate (decoder, TRUE); + + /* Inform the base class that the LC3 lib can do PLC */ + gst_audio_decoder_set_plc_aware (decoder, TRUE); + + return TRUE; +} + +static gboolean +gst_lc3_dec_stop (GstAudioDecoder * decoder) +{ + GstLc3Dec *lc3_dec = GST_LC3_DEC (decoder); + + if (lc3_dec->dec_ch != NULL) { + for (int ich = 0; ich < lc3_dec->channels; ich++) { + g_free (lc3_dec->dec_ch[ich]); + lc3_dec->dec_ch[ich] = NULL; + } + + g_free (lc3_dec->dec_ch); + lc3_dec->dec_ch = NULL; + } + + return TRUE; +} + +static gboolean +gst_lc3_dec_set_format (GstAudioDecoder * decoder, GstCaps * caps) +{ + GstLc3Dec *lc3_dec = GST_LC3_DEC (decoder); + GstAudioInfo info; + GstStructure *s; + GstAudioChannelPosition pos[64] = { GST_AUDIO_CHANNEL_POSITION_INVALID, }; + gint in_ch, in_rate; + guint64 in_chmsk = 0; + GstClockTime latency; + + GST_DEBUG_OBJECT (lc3_dec, "set_format"); + GST_DEBUG_OBJECT (lc3_dec, "input caps %" GST_PTR_FORMAT, caps); + + s = gst_caps_get_structure (caps, 0); + if (FALSE == gst_structure_get_int (s, "frame-duration-us", + &lc3_dec->frame_duration_us)) { + GST_ERROR_OBJECT (lc3_dec, + "sink caps does not contain 'frame-duration-us'"); + return FALSE; + } + + if (FALSE == gst_structure_get_int (s, "frame-bytes", &lc3_dec->frame_bytes)) { + GST_ERROR_OBJECT (lc3_dec, "sink caps does not contain 'frame-bytes'"); + return FALSE; + } + /* use rate and channel from input caps to create filter caps */ + gst_structure_get_int (s, "rate", &in_rate); + gst_structure_get_int (s, "channels", &in_ch); + if (!gst_structure_get (s, "channel-mask", GST_TYPE_BITMASK, &in_chmsk, NULL)) { + GST_INFO_OBJECT (lc3_dec, + "channel-mask not present in the sink caps, getting fallback mask"); + in_chmsk = gst_audio_channel_get_fallback_mask (in_ch); + } + s = NULL; + + gst_audio_channel_positions_from_mask (in_ch, in_chmsk, pos); + gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16LE, in_rate, in_ch, + pos); + + + /* get rate, format, channels from the output caps */ + lc3_dec->rate = GST_AUDIO_INFO_RATE (&info); + lc3_dec->channels = GST_AUDIO_INFO_CHANNELS (&info); + + switch (GST_AUDIO_INFO_FORMAT (&info)) { + case GST_AUDIO_FORMAT_S16LE: + lc3_dec->format = LC3_PCM_FORMAT_S16; + break; + case GST_AUDIO_FORMAT_S24LE: + lc3_dec->format = LC3_PCM_FORMAT_S24_3LE; + break; + case GST_AUDIO_FORMAT_F32: + lc3_dec->format = LC3_PCM_FORMAT_FLOAT; + break; + case GST_AUDIO_FORMAT_S24_32LE: + default: + lc3_dec->format = LC3_PCM_FORMAT_S24; + break; + } + + GST_INFO_OBJECT (lc3_dec, "lc3dec params " + "rate: %" G_GINT32_FORMAT ", channels: %" G_GINT32_FORMAT + ", lc3_pcm_format = %" G_GINT32_FORMAT " frame len: %" G_GINT32_FORMAT + ", frame_duration " "%" G_GINT32_FORMAT, lc3_dec->rate, lc3_dec->channels, + lc3_dec->format, lc3_dec->frame_bytes, lc3_dec->frame_duration_us); + + lc3_dec->frame_samples = + lc3_frame_samples (lc3_dec->frame_duration_us, lc3_dec->rate); + lc3_dec->bpf = GST_AUDIO_INFO_BPF (&info); + + latency = + gst_util_uint64_scale_int (lc3_dec->frame_bytes, GST_SECOND, + lc3_dec->rate); + gst_audio_decoder_set_latency (decoder, latency, latency); + + /* Setup and Init decoder handle */ + if (lc3_dec->dec_ch != NULL) { + for (int ich = 0; ich < lc3_dec->channels; ich++) { + g_free (lc3_dec->dec_ch[ich]); + lc3_dec->dec_ch[ich] = NULL; + } + g_free (lc3_dec->dec_ch); + lc3_dec->dec_ch = NULL; + } + + lc3_dec->dec_ch = g_new0 (lc3_decoder_t, lc3_dec->channels); + + for (guint8 i = 0; i < lc3_dec->channels; i++) { + /* The decoder can resample for us. + * But we leave the resampling to before + * decoding explicitly for now. So pass the same + * sample rate for sr_hz and sr_pcm_hz + */ + lc3_dec->dec_ch[i] = + lc3_setup_decoder (lc3_dec->frame_duration_us, lc3_dec->rate, + lc3_dec->rate, g_malloc (lc3_decoder_size (lc3_dec->frame_duration_us, + lc3_dec->rate))); + + if (lc3_dec->dec_ch[i] == NULL) { + GST_ERROR_OBJECT (lc3_dec, + "Failed to create decoder handle for channel %" G_GUINT32_FORMAT, i); + return FALSE; + } + } + + gst_audio_decoder_set_output_format (decoder, &info); + return TRUE; +} + +static GstFlowReturn +gst_lc3_dec_handle_frame (GstAudioDecoder * decoder, GstBuffer * inbuf) +{ + GstLc3Dec *lc3_dec = GST_LC3_DEC (decoder); + GstBuffer *outbuf = NULL; + GstMapInfo out_map; + GstMapInfo in_map; + gssize output_size; + GstAudioClippingMeta *audio_meta; + gboolean do_plc = gst_audio_decoder_get_plc (decoder) && + gst_audio_decoder_get_plc_aware (decoder); + + /* no fancy draining */ + if (G_UNLIKELY (inbuf == NULL)) + return GST_FLOW_OK; + + gst_buffer_map (inbuf, &in_map, GST_MAP_READ); + + if (G_UNLIKELY (in_map.size == 0 && !do_plc)) + return GST_FLOW_ERROR; + + GST_LOG_OBJECT (lc3_dec, "received %lu bytes ", in_map.size); + + /* we expect exactly one frame each time */ + if (G_UNLIKELY (in_map.size == 0 && !do_plc) && + (in_map.size != (lc3_dec->frame_bytes * lc3_dec->channels))) + goto mixed_frames; + + output_size = lc3_dec->frame_samples * lc3_dec->bpf; + GST_LOG_OBJECT (lc3_dec, "allocating %lu bytes to output buff", output_size); + outbuf = gst_audio_decoder_allocate_output_buffer (decoder, output_size); + + if (outbuf == NULL) + goto no_buffer; + + gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE); + + for (guint c = 0; c < lc3_dec->channels; c++) { + gint ret = 0; + void *in = in_map.data ? in_map.data + (c * lc3_dec->frame_bytes) : NULL; + ret = + lc3_decode (lc3_dec->dec_ch[c], + in, lc3_dec->frame_bytes, + lc3_dec->format, + out_map.data + (c * lc3_dec->bpf / lc3_dec->channels), + lc3_dec->channels); + + if (ret < 0) { + GST_ERROR_OBJECT (lc3_dec, + "Failed to decode frame for buffer %" GST_PTR_FORMAT, inbuf); + return GST_FLOW_ERROR; + } else if (ret == 1) { + GST_INFO_OBJECT (lc3_dec, "PLC operated for channel: %d", c + 1); + } + + } + + audio_meta = gst_buffer_get_audio_clipping_meta (inbuf); + if (audio_meta) { + switch (audio_meta->format) { + case GST_FORMAT_DEFAULT: + { + output_size = + output_size - (audio_meta->start * lc3_dec->bpf) - + (audio_meta->end * lc3_dec->bpf); + gst_buffer_resize (outbuf, (audio_meta->start * lc3_dec->bpf), + output_size); + } + break; + default: + GST_WARNING_OBJECT (lc3_dec, "audio meta format: %d not handled", + audio_meta->format); + break; + } + } + + gst_buffer_unmap (outbuf, &out_map); + gst_buffer_unmap (inbuf, &in_map); + + return gst_audio_decoder_finish_frame (decoder, outbuf, 1); + +/* ERRORS */ +mixed_frames: + { + GST_WARNING_OBJECT (lc3_dec, + "inconsistent input data/frames, Need to be %" + G_GINT32_FORMAT " bytes", lc3_dec->frame_bytes * lc3_dec->channels); + return GST_FLOW_ERROR; + } + +no_buffer: + { + GST_ERROR_OBJECT (lc3_dec, "could not allocate output buffer"); + return GST_FLOW_ERROR; + } +} diff --git a/subprojects/gst-plugins-bad/ext/lc3/gstlc3dec.h b/subprojects/gst-plugins-bad/ext/lc3/gstlc3dec.h new file mode 100644 index 0000000000..9b8a866c1b --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lc3/gstlc3dec.h @@ -0,0 +1,54 @@ +/* GStreamer LC3 Bluetooth LE audio decoder + * Copyright (C) 2023 Asymptotic Inc. + * + * 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. + */ + +#ifndef _GST_LC3DEC_H_ +#define _GST_LC3DEC_H_ + +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_LC3_DEC (gst_lc3_dec_get_type()) +G_DECLARE_FINAL_TYPE (GstLc3Dec, gst_lc3_dec, GST, LC3_DEC, GstAudioDecoder) + +struct _GstLc3Dec +{ + GstAudioDecoder base; + lc3_decoder_t *dec_ch; + gint channels; + gint rate; + gint frame_duration_us; + /* byte count per channel, same for all channels */ + gint frame_bytes; + gint frame_samples; + enum lc3_pcm_format format; + int bitrate; + /* bytes per sample */ + gint bpf; +}; + +struct _GstLc3DecClass +{ + GstAudioDecoderClass base_class; +}; + +GST_ELEMENT_REGISTER_DECLARE (lc3dec); + +G_END_DECLS +#endif diff --git a/subprojects/gst-plugins-bad/ext/lc3/gstlc3enc.c b/subprojects/gst-plugins-bad/ext/lc3/gstlc3enc.c new file mode 100644 index 0000000000..b8b54434de --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lc3/gstlc3enc.c @@ -0,0 +1,426 @@ +/* GStreamer LC3 Bluetooth LE audio encoder + * Copyright (C) 2023 Asymptotic Inc. + * + * 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 Street, Suite 500, + * Boston, MA 02110-1335, USA. + */ + +/** + * SECTION:element-lc3enc + * + * The lc3enc element encodes raw audio using the Low Complexity Communication + * Codec (LC3). + * + * + * Example launch pipeline + * |[ + * gst-launch-1.0 audiotestsrc ! lc3enc ! audio/x-lc3,channels=2,rate=48000,frame-duration-us=10000 !\ + * filesink location=audio.lc3 + * ]| + * Encodes a sine wave into LC3 format using the config params frame-duration-us + * specified by the caps downstream and save it to file audio.lc3 + * + */ + +#include "gst/gstbuffer.h" +#include "gst/gstevent.h" +#include "gst/gstpad.h" +#include +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "gstlc3common.h" +#include "gstlc3enc.h" + +GST_DEBUG_CATEGORY_STATIC (gst_lc3_enc_debug_category); +#define GST_CAT_DEFAULT gst_lc3_enc_debug_category + +#define parent_class gst_lc3_enc_parent_class +G_DEFINE_TYPE (GstLc3Enc, gst_lc3_enc, GST_TYPE_AUDIO_ENCODER); +GST_ELEMENT_REGISTER_DEFINE (lc3enc, "lc3enc", GST_RANK_NONE, GST_TYPE_LC3_ENC); + +static gboolean gst_lc3_enc_start (GstAudioEncoder * encoder); +static gboolean gst_lc3_enc_stop (GstAudioEncoder * encoder); +static gboolean gst_lc3_enc_set_format (GstAudioEncoder * encoder, + GstAudioInfo * info); +static GstFlowReturn gst_lc3_enc_handle_frame (GstAudioEncoder * encoder, + GstBuffer * buffer); + +#define DEFAULT_BITRATE_PER_CHANNEL 160000 + +static GstStaticPadTemplate gst_lc3_enc_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-lc3, " + "rate = (int) { " SAMPLE_RATES " }, " + "channels = (int) [1, MAX], " + "frame-bytes = (int) [" FRAME_BYTES_RANGE "], " + "frame-duration-us = (int) { " FRAME_DURATIONS "}, " + "framed=(boolean) true") + ); + +static GstStaticPadTemplate gst_lc3_enc_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, " + "format = " FORMAT ", " + "rate = (int) { " SAMPLE_RATES " }, channels = (int) [1, MAX]") + ); + +static void +gst_lc3_enc_class_init (GstLc3EncClass * klass) +{ + GstAudioEncoderClass *audio_encoder_class = GST_AUDIO_ENCODER_CLASS (klass); + + audio_encoder_class->start = GST_DEBUG_FUNCPTR (gst_lc3_enc_start); + audio_encoder_class->stop = GST_DEBUG_FUNCPTR (gst_lc3_enc_stop); + audio_encoder_class->set_format = GST_DEBUG_FUNCPTR (gst_lc3_enc_set_format); + audio_encoder_class->handle_frame = + GST_DEBUG_FUNCPTR (gst_lc3_enc_handle_frame); + + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), + &gst_lc3_enc_src_template); + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), + &gst_lc3_enc_sink_template); + + gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass), + "LC3 Bluetooth Audio encoder", "Codec/Encoder/Audio", + "Encodes a raw audio stream to LC3", + "Taruntej Kanakamalla "); + + GST_DEBUG_CATEGORY_INIT (gst_lc3_enc_debug_category, "lc3enc", 0, + "debug category for lc3enc element"); +} + +static void +gst_lc3_enc_init (GstLc3Enc * lc3_enc) +{ +} + +static gboolean +gst_lc3_enc_start (GstAudioEncoder * encoder) +{ + GstLc3Enc *lc3_enc = GST_LC3_ENC (encoder); + + lc3_enc->enc_ch = NULL; + lc3_enc->frame_bytes = 0; + /* Set to true at the start of processing */ + lc3_enc->first_frame = TRUE; + lc3_enc->pending_bytes = 0; + + return TRUE; +} + +static gboolean +gst_lc3_enc_stop (GstAudioEncoder * encoder) +{ + GstLc3Enc *lc3_enc = GST_LC3_ENC (encoder); + + if (lc3_enc->enc_ch != NULL) { + for (int ich = 0; ich < lc3_enc->channels; ich++) { + g_free (lc3_enc->enc_ch[ich]); + lc3_enc->enc_ch[ich] = NULL; + } + + g_free (lc3_enc->enc_ch); + lc3_enc->enc_ch = NULL; + } + + return TRUE; +} + +static gboolean +gst_lc3_enc_set_format (GstAudioEncoder * encoder, GstAudioInfo * info) +{ + GstLc3Enc *lc3_enc = GST_LC3_ENC (encoder); + GstCaps *caps = NULL, *filter_caps = NULL; + GstCaps *output_caps = NULL; + GstStructure *s; + GstClockTime latency; + + lc3_enc->bpf = GST_AUDIO_INFO_BPF (info); + + switch (GST_AUDIO_INFO_FORMAT (info)) { + case GST_AUDIO_FORMAT_S16LE: + lc3_enc->format = LC3_PCM_FORMAT_S16; + break; + case GST_AUDIO_FORMAT_S24LE: + lc3_enc->format = LC3_PCM_FORMAT_S24_3LE; + break; + case GST_AUDIO_FORMAT_F32: + lc3_enc->format = LC3_PCM_FORMAT_FLOAT; + break; + case GST_AUDIO_FORMAT_S24_32LE: + default: + lc3_enc->format = LC3_PCM_FORMAT_S24; + break; + } + + caps = gst_pad_get_allowed_caps (GST_AUDIO_ENCODER_SRC_PAD (lc3_enc)); + if (caps == NULL) + caps = gst_static_pad_template_get_caps (&gst_lc3_enc_src_template); + else if (gst_caps_is_empty (caps)) + goto failure; + + filter_caps = gst_caps_new_simple ("audio/x-lc3", "rate", G_TYPE_INT, + GST_AUDIO_INFO_RATE (info), "channels", G_TYPE_INT, + GST_AUDIO_INFO_CHANNELS (info), NULL); + + output_caps = gst_caps_intersect (caps, filter_caps); + + if (output_caps == NULL || gst_caps_is_empty (output_caps)) { + GST_WARNING_OBJECT (lc3_enc, + "Couldn't negotiate filter caps %" GST_PTR_FORMAT + " and allowed output caps %" GST_PTR_FORMAT, filter_caps, caps); + + goto failure; + } + + gst_caps_unref (filter_caps); + filter_caps = NULL; + gst_caps_unref (caps); + caps = NULL; + + GST_DEBUG_OBJECT (lc3_enc, "fixating caps %" GST_PTR_FORMAT, output_caps); + output_caps = gst_caps_truncate (output_caps); + GST_DEBUG_OBJECT (lc3_enc, "truncated caps %" GST_PTR_FORMAT, output_caps); + + s = gst_caps_get_structure (output_caps, 0); + + gst_structure_get_int (s, "rate", &lc3_enc->rate); + gst_structure_get_int (s, "channels", &lc3_enc->channels); + gst_structure_get_int (s, "frame-bytes", &lc3_enc->frame_bytes); + + if (gst_structure_fixate_field (s, "frame-duration-us")) { + gst_structure_get_int (s, "frame-duration-us", &lc3_enc->frame_duration_us); + } else { + lc3_enc->frame_duration_us = FRAME_DURATION_10000US; + + GST_INFO_OBJECT (lc3_enc, "Frame duration not fixed, setting to %d", + lc3_enc->frame_duration_us); + gst_caps_set_simple (output_caps, "frame-duration-us", G_TYPE_INT, + lc3_enc->frame_duration_us, NULL); + } + + if (lc3_enc->frame_bytes == 0) { + /* fixate_field() is always setting the frame_bytes to 20 which is not desired + * since we can get the value using frame duration and default bitrate + * compute the frame bytes and set the value to the caps + */ + + lc3_enc->frame_bytes = lc3_frame_bytes (lc3_enc->frame_duration_us, + DEFAULT_BITRATE_PER_CHANNEL); + GST_INFO_OBJECT (lc3_enc, "frame bytes computed %d using duration %d", + lc3_enc->frame_bytes, lc3_enc->frame_duration_us); + + gst_caps_set_simple (output_caps, "frame-bytes", G_TYPE_INT, + lc3_enc->frame_bytes, NULL); + } + + GST_INFO_OBJECT (lc3_enc, "output caps %" GST_PTR_FORMAT, output_caps); + + lc3_enc->frame_samples = + lc3_frame_samples (lc3_enc->frame_duration_us, lc3_enc->rate); + + gst_audio_encoder_set_frame_samples_min (encoder, lc3_enc->frame_samples); + gst_audio_encoder_set_frame_samples_max (encoder, lc3_enc->frame_samples); + gst_audio_encoder_set_frame_max (encoder, 1); + + latency = + gst_util_uint64_scale_int (lc3_enc->frame_samples, GST_SECOND, + lc3_enc->rate); + gst_audio_encoder_set_latency (encoder, latency, latency); + + /* Free the encoder handles if it was initialised previously */ + if (lc3_enc->enc_ch != NULL) { + for (int ich = 0; ich < lc3_enc->channels; ich++) { + g_free (lc3_enc->enc_ch[ich]); + lc3_enc->enc_ch[ich] = NULL; + } + g_free (lc3_enc->enc_ch); + lc3_enc->enc_ch = NULL; + } + + lc3_enc->enc_ch = + (lc3_encoder_t *) g_malloc (sizeof (lc3_encoder_t) * lc3_enc->channels); + + for (guint8 i = 0; i < lc3_enc->channels; i++) { + /* The encoder can resample for us. But we leave the resampling to + * happen before encoding explicitly for now. So pass the same sample rate + * for sr_hz and sr_pcm_hz + */ + lc3_enc->enc_ch[i] = + lc3_setup_encoder (lc3_enc->frame_duration_us, lc3_enc->rate, + lc3_enc->rate, g_malloc (lc3_encoder_size (lc3_enc->frame_duration_us, + lc3_enc->rate))); + + if (lc3_enc->enc_ch[i] == NULL) { + GST_ERROR_OBJECT (lc3_enc, + "Failed to create encoder handle for channel %" G_GUINT32_FORMAT, i); + goto failure; + } + } + + if (!gst_audio_encoder_set_output_format (encoder, output_caps)) + goto failure; + + gst_caps_unref (output_caps); + + return gst_audio_encoder_negotiate (encoder); + +failure: + if (output_caps) + gst_caps_unref (output_caps); + if (caps) + gst_caps_unref (caps); + if (filter_caps) + gst_caps_unref (filter_caps); + return FALSE; +} + +static GstFlowReturn +gst_lc3_enc_handle_frame (GstAudioEncoder * encoder, GstBuffer * buffer) +{ + GstLc3Enc *lc3_enc = GST_LC3_ENC (encoder); + GstMapInfo in_map = GST_MAP_INFO_INIT, out_map = GST_MAP_INFO_INIT; + GstBuffer *outbuf = NULL; + guint samplesize, stride, req_samples, req_bytes, frame_bytes; + guint8 *pcm_in; + gint ret = -1; + guint64 trim_start = 0, trim_end = 0; + + if (buffer == NULL && !lc3_enc->pending_bytes) + return GST_FLOW_OK; + + if (G_UNLIKELY (lc3_enc->channels == 0)) + return GST_FLOW_ERROR; + + if (buffer && !gst_buffer_map (buffer, &in_map, GST_MAP_READ)) + goto map_failed; + + GST_TRACE_OBJECT (lc3_enc, + "encoding %" G_GSIZE_FORMAT " frame samples of %" G_GSIZE_FORMAT + " bytes", in_map.size / lc3_enc->bpf, in_map.size); + + frame_bytes = lc3_enc->frame_bytes; + + /* allocate frame_bytes for each channel in the output buffer */ + outbuf = + gst_audio_encoder_allocate_output_buffer (encoder, + frame_bytes * lc3_enc->channels); + + if (outbuf == NULL) + goto no_buffer; + + if (!gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE)) + goto map_failed; + + stride = lc3_enc->channels; + samplesize = lc3_enc->bpf / lc3_enc->channels; + + /* Calculate the expected bytes */ + req_samples = lc3_enc->frame_samples; + req_bytes = req_samples * lc3_enc->bpf; + + if (lc3_enc->first_frame) { + /* LC3 encoder introduces extra samples as a part of the + * algorithmic delay at the beginning of the frame + */ + lc3_enc->pending_bytes = + lc3_enc->bpf * lc3_delay_samples (lc3_enc->frame_duration_us, + lc3_enc->rate); + + /* trim start 'delay_samples' bytes for the first frame */ + trim_start = lc3_enc->pending_bytes / lc3_enc->bpf; + lc3_enc->first_frame = FALSE; + } + + if (in_map.size < req_bytes) { + /* update the pending bytes and trim_end */ + if (in_map.size + lc3_enc->pending_bytes > req_bytes) { + lc3_enc->pending_bytes = in_map.size + lc3_enc->pending_bytes - req_bytes; + } else { + trim_end = + (req_bytes - in_map.size - lc3_enc->pending_bytes) / lc3_enc->bpf; + lc3_enc->pending_bytes = 0; + } + + /* The encoder always expects fixed number of bytes in the input + * If we get less bytes than req_bytes, most likely in the last iteration, + * add zero-padding bytes at the end + */ + pcm_in = (guint8 *) g_malloc0 (req_bytes); + if (in_map.size && in_map.data) + memcpy (pcm_in, in_map.data, in_map.size); + } else { + pcm_in = in_map.data; + } + + if (trim_start || trim_end) { + GST_TRACE_OBJECT (lc3_enc, + "Adding trim-start %" G_GUINT64_FORMAT " trim-end %" G_GUINT64_FORMAT, + trim_start, trim_end); + gst_buffer_add_audio_clipping_meta (outbuf, GST_FORMAT_DEFAULT, trim_start, + trim_end); + } + + for (guint8 ch = 0; ch < lc3_enc->channels; ch++) { + ret = lc3_encode (lc3_enc->enc_ch[ch], lc3_enc->format, + pcm_in + (ch * samplesize), stride, frame_bytes, + out_map.data + (ch * frame_bytes)); + + if (ret < 0) { + GST_WARNING_OBJECT (lc3_enc, + "encoding error: invalid enc handle or frame_bytes"); + break; + } + } + + if (in_map.size < req_bytes) + g_free (pcm_in); + + gst_buffer_unmap (outbuf, &out_map); + if (buffer) + gst_buffer_unmap (buffer, &in_map); + + if (ret < 0) + return GST_FLOW_ERROR; + + return gst_audio_encoder_finish_frame (encoder, outbuf, req_samples); + +no_buffer: + { + if (buffer) + gst_buffer_unmap (buffer, &in_map); + GST_ELEMENT_ERROR (lc3_enc, STREAM, FAILED, (NULL), + ("Could not allocate output buffer")); + return GST_FLOW_ERROR; + } + +map_failed: + { + if (buffer) + gst_buffer_unmap (buffer, &in_map); + GST_ELEMENT_ERROR (lc3_enc, STREAM, FAILED, (NULL), + ("Failed to get the buffer memory map")); + return GST_FLOW_ERROR; + } +} diff --git a/subprojects/gst-plugins-bad/ext/lc3/gstlc3enc.h b/subprojects/gst-plugins-bad/ext/lc3/gstlc3enc.h new file mode 100644 index 0000000000..7d1620eb71 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lc3/gstlc3enc.h @@ -0,0 +1,57 @@ +/* GStreamer LC3 Bluetooth LE audio encoder + * Copyright (C) 2023 Asymptotic Inc. + * + * 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. + */ + +#ifndef _GST_LC3ENC_H_ +#define _GST_LC3ENC_H_ + +#include + +#include + +G_BEGIN_DECLS +#define GST_TYPE_LC3_ENC (gst_lc3_enc_get_type()) +G_DECLARE_FINAL_TYPE (GstLc3Enc, gst_lc3_enc, GST, LC3_ENC, GstAudioEncoder) + +struct _GstLc3Enc +{ + GstAudioEncoder base; + lc3_encoder_t *enc_ch; + enum lc3_pcm_format format; + int rate; + int channels; + int frame_duration_us; + /* byte count per channel, same for all channels */ + int frame_bytes; + /* bytes per sample */ + int bpf; + /* pcm samples per encoded frame */ + int frame_samples; + gboolean first_frame; + int pending_bytes; +}; + +struct _GstLc3EncClass +{ + GstAudioEncoderClass base_class; +}; + +GST_ELEMENT_REGISTER_DECLARE (lc3enc); + +G_END_DECLS +#endif diff --git a/subprojects/gst-plugins-bad/ext/lc3/lc3-plugin.c b/subprojects/gst-plugins-bad/ext/lc3/lc3-plugin.c new file mode 100644 index 0000000000..543f737e34 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lc3/lc3-plugin.c @@ -0,0 +1,41 @@ +/* GStreamer LC3 audio plugin + * Copyright (C) 2023 Asymptotic Inc. + * + * 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 "gstlc3dec.h" +#include "gstlc3enc.h" + + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean ret = FALSE; + ret |= GST_ELEMENT_REGISTER (lc3dec, plugin); + ret |= GST_ELEMENT_REGISTER (lc3enc, plugin); + return ret; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + lc3, + "LC3 codec for Bluetooth LE Audio", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/subprojects/gst-plugins-bad/ext/lc3/meson.build b/subprojects/gst-plugins-bad/ext/lc3/meson.build new file mode 100644 index 0000000000..be9cdd760d --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/lc3/meson.build @@ -0,0 +1,20 @@ +lc3_sources = [ + 'lc3-plugin.c', + 'gstlc3dec.c', + 'gstlc3enc.c', +] + +lc3_dep = dependency('liblc3', required:get_option ('lc3')) + +if lc3_dep.found() + gstlc3 = library('gstlc3', + lc3_sources, + c_args: gst_plugins_bad_args, + include_directories: [configinc], + dependencies: [gstaudio_dep, lc3_dep], + install: true, + install_dir: plugins_install_dir, + ) + pkgconfig.generate(gstlc3, install_dir: plugins_pkgconfig_install_dir) + plugins += [gstlc3] +endif diff --git a/subprojects/gst-plugins-bad/ext/meson.build b/subprojects/gst-plugins-bad/ext/meson.build index 169aa059cb..0ca81c79c1 100644 --- a/subprojects/gst-plugins-bad/ext/meson.build +++ b/subprojects/gst-plugins-bad/ext/meson.build @@ -28,6 +28,7 @@ subdir('iqa') subdir('isac') subdir('kate') subdir('ladspa') +subdir('lc3') subdir('ldac') subdir('libde265') subdir('lv2') diff --git a/subprojects/gst-plugins-bad/meson_options.txt b/subprojects/gst-plugins-bad/meson_options.txt index a29d89d3db..a1d9fd2f4f 100644 --- a/subprojects/gst-plugins-bad/meson_options.txt +++ b/subprojects/gst-plugins-bad/meson_options.txt @@ -125,6 +125,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('lc3', type : 'feature', value : 'auto', description : 'LC3 (Bluetooth) LE audio codec plugin') 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('openaptx', type : 'feature', value : 'auto', description : 'Open Source implementation of Audio Processing Technology codec (aptX) plugin') diff --git a/subprojects/liblc3.wrap b/subprojects/liblc3.wrap new file mode 100644 index 0000000000..3520a18c47 --- /dev/null +++ b/subprojects/liblc3.wrap @@ -0,0 +1,6 @@ +[wrap-git] +directory=liblc3 +url=https://github.com/google/liblc3.git +depth=1 +revision=v1.0.3 +