From b916522382aaa33f216636a292e97cd769ac4093 Mon Sep 17 00:00:00 2001 From: Igor Kovalenko Date: Fri, 11 Dec 2020 08:45:06 +0000 Subject: [PATCH] openaptx: add aptX and aptX-HD codecs using libopenaptx Part-of: --- ext/meson.build | 1 + ext/openaptx/gstopenaptxdec.c | 342 +++++++++++++++++++++++++++++++++ ext/openaptx/gstopenaptxdec.h | 45 +++++ ext/openaptx/gstopenaptxenc.c | 317 ++++++++++++++++++++++++++++++ ext/openaptx/gstopenaptxenc.h | 44 +++++ ext/openaptx/meson.build | 20 ++ ext/openaptx/openaptx-plugin.c | 44 +++++ ext/openaptx/openaptx-plugin.h | 61 ++++++ meson_options.txt | 1 + 9 files changed, 875 insertions(+) create mode 100644 ext/openaptx/gstopenaptxdec.c create mode 100644 ext/openaptx/gstopenaptxdec.h create mode 100644 ext/openaptx/gstopenaptxenc.c create mode 100644 ext/openaptx/gstopenaptxenc.h create mode 100644 ext/openaptx/meson.build create mode 100644 ext/openaptx/openaptx-plugin.c create mode 100644 ext/openaptx/openaptx-plugin.h diff --git a/ext/meson.build b/ext/meson.build index 2f034223d5..63844ca82d 100644 --- a/ext/meson.build +++ b/ext/meson.build @@ -36,6 +36,7 @@ subdir('musepack') subdir('neon') subdir('ofa') subdir('openal') +subdir('openaptx') subdir('opencv') subdir('openexr') subdir('openh264') diff --git a/ext/openaptx/gstopenaptxdec.c b/ext/openaptx/gstopenaptxdec.c new file mode 100644 index 0000000000..8a61eba48a --- /dev/null +++ b/ext/openaptx/gstopenaptxdec.c @@ -0,0 +1,342 @@ +/* GStreamer openaptx audio decoder + * + * Copyright (C) 2020 Igor V. Kovalenko + * Copyright (C) 2020 Thomas Weißschuh + * + * 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-openaptxdec + * @title: openaptxdec + * + * This element decodes Bluetooth aptX or aptX-HD stream to raw S24LE integer stereo PCM audio. + * Accepts audio/aptx or audio/aptx-hd input streams. + * + * ## Example pipelines + * |[ + * gst-launch-1.0 -v audiotestsrc ! avenc_aptx ! openaptxdec ! audioconvert ! autoaudiosink + * ]| Decode a sine wave encoded with AV encoder and listen to result. + * |[ + * gst-launch-1.0 -v audiotestsrc ! avenc_aptx ! openaptxdec autosync=0 ! audioconvert ! autoaudiosink + * ]| Decode a sine wave encoded with AV encoder and listen to result, stream autosync disabled. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstopenaptxdec.h" +#include "openaptx-plugin.h" + +enum +{ + PROP_0, + PROP_AUTOSYNC +}; + +GST_DEBUG_CATEGORY_STATIC (openaptx_dec_debug); +#define GST_CAT_DEFAULT openaptx_dec_debug + +#define parent_class gst_openaptx_dec_parent_class +G_DEFINE_TYPE (GstOpenaptxDec, gst_openaptx_dec, GST_TYPE_AUDIO_DECODER); + +static GstStaticPadTemplate openaptx_dec_sink_factory = + GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/aptx-hd, channels = 2, rate = [ 1, MAX ]; " + "audio/aptx, channels = 2, rate = [ 1, MAX ]")); + +static GstStaticPadTemplate openaptx_dec_src_factory = +GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, format = S24LE," + " rate = [ 1, MAX ], channels = 2, layout = interleaved")); + +static GstFlowReturn +gst_openaptx_dec_handle_frame (GstAudioDecoder * audio_dec, GstBuffer * buffer) +{ + GstOpenaptxDec *dec = GST_OPENAPTX_DEC (audio_dec); + GstMapInfo out_map; + GstBuffer *outbuf = NULL; + GstFlowReturn ret; + guint num_frames; + gsize frame_len, output_size; + gssize input_size, processed = 0; + gsize written = 0; + gint synced; + gsize dropped; + + /* no fancy draining */ + if (G_UNLIKELY (!buffer)) + input_size = 0; + else + input_size = gst_buffer_get_size (buffer); + + frame_len = aptx_frame_size (dec->hd); + + if (!dec->autosync) { + /* we assume all frames are of the same size, this is implied by the + * input caps applying to the whole input buffer, and the parser should + * also have made sure of that */ + if (G_UNLIKELY (input_size % frame_len != 0)) + goto mixed_frames; + } + + num_frames = input_size / frame_len; + + /* need one extra frame if autosync is enabled */ + if (dec->autosync) + ++num_frames; + + output_size = num_frames * APTX_SAMPLES_PER_FRAME * APTX_SAMPLE_SIZE; + + outbuf = gst_audio_decoder_allocate_output_buffer (audio_dec, output_size); + + if (outbuf == NULL) + goto no_output_buffer; + + if (!gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE)) { + gst_buffer_replace (&outbuf, NULL); + goto no_output_buffer_map; + } + + if (G_LIKELY (buffer)) { + GstMapInfo in_map; + + if (!gst_buffer_map (buffer, &in_map, GST_MAP_READ)) { + gst_buffer_unmap (outbuf, &out_map); + gst_buffer_replace (&outbuf, NULL); + goto map_failed; + } + + if (dec->autosync) { + processed = aptx_decode_sync (dec->aptx_c, in_map.data, in_map.size, + out_map.data, output_size, &written, &synced, &dropped); + } else { + processed = aptx_decode (dec->aptx_c, in_map.data, in_map.size, + out_map.data, out_map.size, &written); + } + + gst_buffer_unmap (buffer, &in_map); + } else { + if (dec->autosync) { + dropped = aptx_decode_sync_finish (dec->aptx_c); + synced = 1; + } + } + + if (dec->autosync) { + if (!synced) { + GST_WARNING_OBJECT (dec, "%s stream is not synchronized", + aptx_name (dec->hd)); + } + if (dropped) { + GST_WARNING_OBJECT (dec, + "%s decoder dropped %" G_GSIZE_FORMAT " bytes from stream", + aptx_name (dec->hd), dropped); + } + } + + if (processed != input_size) { + GST_WARNING_OBJECT (dec, + "%s decoding error, processed = %" G_GSSIZE_FORMAT ", " + "written = %" G_GSSIZE_FORMAT ", input size = %" G_GSIZE_FORMAT, + aptx_name (dec->hd), processed, written, input_size); + } + + gst_buffer_unmap (outbuf, &out_map); + + GST_LOG_OBJECT (dec, "%s written = %" G_GSSIZE_FORMAT, + aptx_name (dec->hd), written); + +done: + if (G_LIKELY (outbuf)) { + if (G_LIKELY (written > 0)) + gst_buffer_set_size (outbuf, written); + else + gst_buffer_replace (&outbuf, NULL); + } + + ret = gst_audio_decoder_finish_frame (audio_dec, outbuf, 1); + + if (G_UNLIKELY (!buffer)) + ret = GST_FLOW_EOS; + + return ret; + +/* ERRORS */ +mixed_frames: + { + GST_WARNING_OBJECT (dec, "inconsistent input data/frames, skipping"); + goto done; + } +no_output_buffer_map: + { + GST_ELEMENT_ERROR (dec, RESOURCE, FAILED, + ("Could not map output buffer"), + ("Failed to map allocated output buffer for write access.")); + return GST_FLOW_ERROR; + } +no_output_buffer: + { + GST_ELEMENT_ERROR (dec, RESOURCE, FAILED, + ("Could not allocate output buffer"), + ("Audio decoder failed to allocate output buffer to hold an audio frame.")); + return GST_FLOW_ERROR; + } +map_failed: + { + GST_ELEMENT_ERROR (dec, RESOURCE, FAILED, + ("Could not map input buffer"), + ("Failed to map incoming buffer for read access.")); + return GST_FLOW_ERROR; + } +} + +static gboolean +gst_openaptx_dec_set_format (GstAudioDecoder * audio_dec, GstCaps * caps) +{ + GstOpenaptxDec *dec = GST_OPENAPTX_DEC (audio_dec); + GstAudioInfo info; + GstStructure *s; + gint rate; + + s = gst_caps_get_structure (caps, 0); + gst_structure_get_int (s, "rate", &rate); + + /* let's see what is in the output caps */ + dec->hd = gst_structure_has_name (s, "audio/aptx-hd"); + + /* reinitialize codec */ + if (dec->aptx_c) + aptx_finish (dec->aptx_c); + + GST_INFO_OBJECT (dec, "Initialize %s codec", aptx_name (dec->hd)); + dec->aptx_c = aptx_init (dec->hd); + + /* set up output format */ + gst_audio_info_init (&info); + gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S24LE, + rate, APTX_NUM_CHANNELS, NULL); + gst_audio_decoder_set_output_format (audio_dec, &info); + + return TRUE; +} + +static void +gst_openaptx_dec_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstOpenaptxDec *dec = GST_OPENAPTX_DEC (object); + + switch (prop_id) { + case PROP_AUTOSYNC: + dec->autosync = g_value_get_boolean (value); + break; + default: + break; + } +} + +static void +gst_openaptx_dec_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstOpenaptxDec *dec = GST_OPENAPTX_DEC (object); + + switch (prop_id) { + case PROP_AUTOSYNC:{ + g_value_set_boolean (value, dec->autosync); + break; + } + default:{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + } +} + +static gboolean +gst_openaptx_dec_start (GstAudioDecoder * audio_dec) +{ + return TRUE; +} + +static gboolean +gst_openaptx_dec_stop (GstAudioDecoder * audio_dec) +{ + GstOpenaptxDec *dec = GST_OPENAPTX_DEC (audio_dec); + + GST_INFO_OBJECT (dec, "Finish openaptx codec"); + + if (dec->aptx_c) { + aptx_finish (dec->aptx_c); + dec->aptx_c = NULL; + } + + return TRUE; +} + +static void +gst_openaptx_dec_class_init (GstOpenaptxDecClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstAudioDecoderClass *base_class = GST_AUDIO_DECODER_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gobject_class->set_property = + GST_DEBUG_FUNCPTR (gst_openaptx_dec_set_property); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gst_openaptx_dec_get_property); + + g_object_class_install_property (gobject_class, PROP_AUTOSYNC, + g_param_spec_boolean ("autosync", "Auto sync", + "Gracefully handle partially corrupted stream in which some bytes are missing", + APTX_AUTOSYNC_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + base_class->start = GST_DEBUG_FUNCPTR (gst_openaptx_dec_start); + base_class->stop = GST_DEBUG_FUNCPTR (gst_openaptx_dec_stop); + base_class->set_format = GST_DEBUG_FUNCPTR (gst_openaptx_dec_set_format); + base_class->handle_frame = GST_DEBUG_FUNCPTR (gst_openaptx_dec_handle_frame); + + gst_element_class_add_static_pad_template (element_class, + &openaptx_dec_sink_factory); + gst_element_class_add_static_pad_template (element_class, + &openaptx_dec_src_factory); + + gst_element_class_set_static_metadata (element_class, + "Bluetooth aptX/aptX-HD audio decoder using libopenaptx", + "Codec/Decoder/Audio", + "Decode an aptX or aptX-HD audio stream using libopenaptx", + "Igor V. Kovalenko , " + "Thomas Weißschuh "); + + GST_DEBUG_CATEGORY_INIT (openaptx_dec_debug, "openaptxdec", 0, + "openaptx decoding element"); +} + +static void +gst_openaptx_dec_init (GstOpenaptxDec * dec) +{ + gst_audio_decoder_set_needs_format (GST_AUDIO_DECODER (dec), TRUE); + gst_audio_decoder_set_use_default_pad_acceptcaps (GST_AUDIO_DECODER_CAST + (dec), TRUE); + GST_PAD_SET_ACCEPT_TEMPLATE (GST_AUDIO_DECODER_SINK_PAD (dec)); + + dec->aptx_c = NULL; + + dec->autosync = APTX_AUTOSYNC_DEFAULT; +} diff --git a/ext/openaptx/gstopenaptxdec.h b/ext/openaptx/gstopenaptxdec.h new file mode 100644 index 0000000000..bcc9891239 --- /dev/null +++ b/ext/openaptx/gstopenaptxdec.h @@ -0,0 +1,45 @@ +/* GStreamer openaptx audio decoder + * + * Copyright (C) 2020 Igor V. Kovalenko + * Copyright (C) 2020 Thomas Weißschuh + * + * 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 + * + */ +#ifndef __GST_OPENAPTXDEC_H__ +#define __GST_OPENAPTXDEC_H__ + +#include +#include + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_OPENAPTX_DEC (gst_openaptx_dec_get_type()) +G_DECLARE_FINAL_TYPE (GstOpenaptxDec, gst_openaptx_dec, GST, OPENAPTX_DEC, GstAudioEncoder) + +struct _GstOpenaptxDec { + GstAudioDecoder audio_decoder; + + gboolean hd; + gboolean autosync; + + struct aptx_context *aptx_c; +}; + +G_END_DECLS + +#endif /* __GST_OPENAPTXDEC_H__ */ diff --git a/ext/openaptx/gstopenaptxenc.c b/ext/openaptx/gstopenaptxenc.c new file mode 100644 index 0000000000..7d38c982cf --- /dev/null +++ b/ext/openaptx/gstopenaptxenc.c @@ -0,0 +1,317 @@ +/* GStreamer openaptx audio encoder + * + * Copyright (C) 2020 Igor V. Kovalenko + * Copyright (C) 2020 Thomas Weißschuh + * + * 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-openaptxenc + * @title: openaptxenc + * + * This element encodes raw S24LE integer stereo PCM audio into a Bluetooth aptX or aptX-HD stream. + * Accepts audio/aptx or audio/aptx-hd output streams. + * + * ## Example pipelines + * |[ + * gst-launch-1.0 -v audiotestsrc ! openaptxenc ! avdec_aptx ! audioconvert ! autoaudiosink + * ]| Encode a sine wave into aptX, AV decode it and listen to result. + * |[ + * gst-launch-1.0 -v audiotestsrc ! openaptxenc ! avdec_aptx_hd ! audioconvert ! autoaudiosink + * ]| Encode a sine wave into aptX-HD, AV decode it and listen to result. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstopenaptxenc.h" +#include "openaptx-plugin.h" + +GST_DEBUG_CATEGORY_STATIC (openaptx_enc_debug); +#define GST_CAT_DEFAULT openaptx_enc_debug + +#define gst_openaptx_enc_parent_class parent_class + +G_DEFINE_TYPE (GstOpenaptxEnc, gst_openaptx_enc, GST_TYPE_AUDIO_ENCODER); + +static GstStaticPadTemplate openaptx_enc_sink_factory = +GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw, format = S24LE," + " rate = [ 1, MAX ], channels = 2, layout = interleaved")); + +static GstStaticPadTemplate openaptx_enc_src_factory = + GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/aptx-hd, channels = 2, rate = [ 1, MAX ]; " + "audio/aptx, channels = 2, rate = [ 1, MAX ]")); + + +static gboolean gst_openaptx_enc_start (GstAudioEncoder * enc); +static gboolean gst_openaptx_enc_stop (GstAudioEncoder * enc); +static gboolean gst_openaptx_enc_set_format (GstAudioEncoder * enc, + GstAudioInfo * info); +static GstFlowReturn gst_openaptx_enc_handle_frame (GstAudioEncoder * enc, + GstBuffer * buffer); + +static gint64 +gst_openaptx_enc_get_latency (GstOpenaptxEnc * enc, gint rate) +{ + gint64 latency = + gst_util_uint64_scale (APTX_LATENCY_SAMPLES, GST_SECOND, rate); + GST_DEBUG_OBJECT (enc, "Latency: %" GST_TIME_FORMAT, GST_TIME_ARGS (latency)); + return latency; +} + +static gboolean +gst_openaptx_enc_set_format (GstAudioEncoder * audio_enc, GstAudioInfo * info) +{ + GstOpenaptxEnc *enc = GST_OPENAPTX_ENC (audio_enc); + GstStructure *s; + GstCaps *caps, *output_caps = NULL; + gint rate; + gint64 encoder_latency; + gint ret; + + rate = GST_AUDIO_INFO_RATE (info); + + /* negotiate output format based on downstream caps restrictions */ + caps = gst_pad_get_allowed_caps (GST_AUDIO_ENCODER_SRC_PAD (enc)); + + if (caps == NULL) + caps = gst_static_pad_template_get_caps (&openaptx_enc_src_factory); + else if (gst_caps_is_empty (caps)) + goto failure; + + /* let's see what is in the output caps */ + s = gst_caps_get_structure (caps, 0); + enc->hd = gst_structure_has_name (s, "audio/aptx-hd"); + + gst_clear_caps (&caps); + + output_caps = gst_caps_new_simple (enc->hd ? "audio/aptx-hd" : "audio/aptx", + "channels", G_TYPE_INT, APTX_NUM_CHANNELS, + "rate", G_TYPE_INT, rate, NULL); + + GST_INFO_OBJECT (enc, "output caps %" GST_PTR_FORMAT, output_caps); + + /* reinitialize codec */ + if (enc->aptx_c) + aptx_finish (enc->aptx_c); + + GST_INFO_OBJECT (enc, "Initialize %s codec", aptx_name (enc->hd)); + enc->aptx_c = aptx_init (enc->hd); + + encoder_latency = gst_openaptx_enc_get_latency (enc, rate); + gst_audio_encoder_set_latency (audio_enc, encoder_latency, encoder_latency); + + /* we want to be handed all available samples in handle_frame, but always + * enough to encode a frame */ + gst_audio_encoder_set_frame_samples_min (audio_enc, APTX_SAMPLES_PER_CHANNEL); + gst_audio_encoder_set_frame_samples_max (audio_enc, APTX_SAMPLES_PER_CHANNEL); + gst_audio_encoder_set_frame_max (audio_enc, 0); + + /* FIXME: what to do with left-over samples at the end? can we encode them? */ + gst_audio_encoder_set_hard_min (audio_enc, TRUE); + + ret = gst_audio_encoder_set_output_format (audio_enc, output_caps); + gst_caps_unref (output_caps); + + return ret; + +failure: + if (output_caps) + gst_caps_unref (output_caps); + if (caps) + gst_caps_unref (caps); + return FALSE; +} + +static GstFlowReturn +gst_openaptx_enc_handle_frame (GstAudioEncoder * audio_enc, GstBuffer * buffer) +{ + GstOpenaptxEnc *enc = GST_OPENAPTX_ENC (audio_enc); + GstMapInfo out_map; + GstBuffer *outbuf = NULL; + GstFlowReturn ret; + guint frames; + gsize frame_len, output_size; + gssize processed = 0; + gsize written = 0; + + /* fixed encoded frame size hd=0: LLRR, hd=1: LLLRRR */ + frame_len = aptx_frame_size (enc->hd); + + if (G_UNLIKELY (!buffer)) { + GST_DEBUG_OBJECT (enc, "Finish encoding"); + frames = APTX_FINISH_FRAMES; + } else { + frames = gst_buffer_get_size (buffer) / + (APTX_SAMPLE_SIZE * APTX_SAMPLES_PER_FRAME); + + if (frames == 0) { + GST_WARNING_OBJECT (enc, "Odd input stream size detected, skipping"); + goto mixed_frames; + } + } + + output_size = frames * frame_len; + outbuf = gst_audio_encoder_allocate_output_buffer (audio_enc, output_size); + + if (outbuf == NULL) + goto no_output_buffer; + + if (!gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE)) { + gst_buffer_replace (&outbuf, NULL); + goto no_output_buffer_map; + } + + if (G_LIKELY (buffer)) { + GstMapInfo in_map; + + if (!gst_buffer_map (buffer, &in_map, GST_MAP_READ)) { + gst_buffer_unmap (outbuf, &out_map); + gst_buffer_replace (&outbuf, NULL); + goto map_failed; + } + + GST_LOG_OBJECT (enc, + "encoding %" G_GSIZE_FORMAT " samples into %u %s frames", + in_map.size / (APTX_NUM_CHANNELS * APTX_SAMPLE_SIZE), frames, + aptx_name (enc->hd)); + + processed = aptx_encode (enc->aptx_c, in_map.data, in_map.size, + out_map.data, output_size, &written); + + gst_buffer_unmap (buffer, &in_map); + } else { + aptx_encode_finish (enc->aptx_c, out_map.data, output_size, &written); + output_size = written; + } + + if (processed < 0 || written != output_size) { + GST_WARNING_OBJECT (enc, + "%s encoding error, processed = %" G_GSSIZE_FORMAT ", " + "written = %" G_GSSIZE_FORMAT ", expected = %" G_GSIZE_FORMAT, + aptx_name (enc->hd), processed, written, frames * frame_len); + } + + gst_buffer_unmap (outbuf, &out_map); + + GST_LOG_OBJECT (enc, "%s written = %" G_GSSIZE_FORMAT, + aptx_name (enc->hd), written); + +done: + if (G_LIKELY (outbuf)) { + if (G_LIKELY (written > 0)) + gst_buffer_set_size (outbuf, written); + else + gst_buffer_replace (&outbuf, NULL); + } + + ret = gst_audio_encoder_finish_frame (audio_enc, outbuf, + written / frame_len * APTX_SAMPLES_PER_CHANNEL); + + if (G_UNLIKELY (!buffer)) + ret = GST_FLOW_EOS; + + return ret; + +/* ERRORS */ +mixed_frames: + { + GST_WARNING_OBJECT (enc, "inconsistent input data/frames, skipping"); + goto done; + } +no_output_buffer_map: + { + GST_ELEMENT_ERROR (enc, RESOURCE, FAILED, + ("Could not map output buffer"), + ("Failed to map allocated output buffer for write access.")); + return GST_FLOW_ERROR; + } +no_output_buffer: + { + GST_ELEMENT_ERROR (enc, RESOURCE, FAILED, + ("Could not allocate output buffer"), + ("Audio encoder failed to allocate output buffer to hold an audio frame.")); + return GST_FLOW_ERROR; + } +map_failed: + { + GST_ELEMENT_ERROR (enc, RESOURCE, FAILED, + ("Could not map input buffer"), + ("Failed to map incoming buffer for read access.")); + return GST_FLOW_ERROR; + } +} + +static gboolean +gst_openaptx_enc_start (GstAudioEncoder * audio_enc) +{ + return TRUE; +} + +static gboolean +gst_openaptx_enc_stop (GstAudioEncoder * audio_enc) +{ + GstOpenaptxEnc *enc = GST_OPENAPTX_ENC (audio_enc); + + GST_INFO_OBJECT (enc, "Finish openaptx codec"); + + if (enc->aptx_c) { + aptx_finish (enc->aptx_c); + enc->aptx_c = NULL; + } + + return TRUE; +} + +static void +gst_openaptx_enc_class_init (GstOpenaptxEncClass * klass) +{ + GstAudioEncoderClass *base_class = GST_AUDIO_ENCODER_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + base_class->start = GST_DEBUG_FUNCPTR (gst_openaptx_enc_start); + base_class->stop = GST_DEBUG_FUNCPTR (gst_openaptx_enc_stop); + base_class->set_format = GST_DEBUG_FUNCPTR (gst_openaptx_enc_set_format); + base_class->handle_frame = GST_DEBUG_FUNCPTR (gst_openaptx_enc_handle_frame); + + gst_element_class_add_static_pad_template (element_class, + &openaptx_enc_sink_factory); + gst_element_class_add_static_pad_template (element_class, + &openaptx_enc_src_factory); + + gst_element_class_set_static_metadata (element_class, + "Bluetooth aptX/aptX-HD audio encoder using libopenaptx", + "Codec/Encoder/Audio", + "Encode an aptX or aptX-HD audio stream using libopenaptx", + "Igor V. Kovalenko , " + "Thomas Weißschuh "); + + GST_DEBUG_CATEGORY_INIT (openaptx_enc_debug, "openaptxenc", 0, + "openaptx encoding element"); +} + +static void +gst_openaptx_enc_init (GstOpenaptxEnc * enc) +{ + GST_PAD_SET_ACCEPT_TEMPLATE (GST_AUDIO_ENCODER_SINK_PAD (enc)); + + enc->aptx_c = NULL; +} diff --git a/ext/openaptx/gstopenaptxenc.h b/ext/openaptx/gstopenaptxenc.h new file mode 100644 index 0000000000..48bc5afd0c --- /dev/null +++ b/ext/openaptx/gstopenaptxenc.h @@ -0,0 +1,44 @@ +/* GStreamer openaptx audio encoder + * + * Copyright (C) 2020 Igor V. Kovalenko + * Copyright (C) 2020 Thomas Weißschuh + * + * 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 + * + */ +#ifndef __GST_OPENAPTXENC_H__ +#define __GST_OPENAPTXENC_H__ + +#include +#include + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_OPENAPTX_ENC (gst_openaptx_enc_get_type()) +G_DECLARE_FINAL_TYPE (GstOpenaptxEnc, gst_openaptx_enc, GST, OPENAPTX_ENC, GstAudioEncoder) + +struct _GstOpenaptxEnc { + GstAudioEncoder audio_encoder; + + gboolean hd; + + struct aptx_context *aptx_c; +}; + +G_END_DECLS + +#endif /* __GST_OPENAPTXENC_H__ */ diff --git a/ext/openaptx/meson.build b/ext/openaptx/meson.build new file mode 100644 index 0000000000..68be112c50 --- /dev/null +++ b/ext/openaptx/meson.build @@ -0,0 +1,20 @@ +openaptx_sources = [ + 'openaptx-plugin.c', + 'gstopenaptxdec.c', + 'gstopenaptxenc.c', +] + +openaptx_dep = dependency('libopenaptx', version : '>= 0.2', required : get_option('libopenaptx')) + +if openaptx_dep.found() + gstopenaptx = library('gstopenaptx', + openaptx_sources, + c_args : gst_plugins_bad_args, + include_directories : [configinc], + dependencies : [gstaudio_dep, openaptx_dep], + install : true, + install_dir : plugins_install_dir, + ) + pkgconfig.generate(gstopenaptx, install_dir : plugins_pkgconfig_install_dir) + plugins += [gstopenaptx] +endif diff --git a/ext/openaptx/openaptx-plugin.c b/ext/openaptx/openaptx-plugin.c new file mode 100644 index 0000000000..71ee53bd5f --- /dev/null +++ b/ext/openaptx/openaptx-plugin.c @@ -0,0 +1,44 @@ +/* GStreamer openaptx audio plugin + * + * Copyright (C) 2020 Igor V. Kovalenko + * Copyright (C) 2020 Thomas Weißschuh + * + * 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 "openaptx-plugin.h" +#include "gstopenaptxdec.h" +#include "gstopenaptxenc.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gst_element_register (plugin, "openaptxdec", GST_RANK_NONE, + GST_TYPE_OPENAPTX_DEC); + gst_element_register (plugin, "openaptxenc", GST_RANK_NONE, + GST_TYPE_OPENAPTX_ENC); + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + openaptx, + "Open Source implementation of Audio Processing Technology codec (aptX)", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/ext/openaptx/openaptx-plugin.h b/ext/openaptx/openaptx-plugin.h new file mode 100644 index 0000000000..6d0ea9f44b --- /dev/null +++ b/ext/openaptx/openaptx-plugin.h @@ -0,0 +1,61 @@ +/* GStreamer openaptx audio plugin + * + * Copyright (C) 2020 Igor V. Kovalenko + * Copyright (C) 2020 Thomas Weißschuh + * + * 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_OPENAPTX_PLUGIN_H__ +#define __GST_OPENAPTX_PLUGIN_H__ + +#include + +#define APTX_HD_DEFAULT 1 +#define APTX_AUTOSYNC_DEFAULT TRUE + +#define APTX_LATENCY_SAMPLES 90 + +/* always stereo */ +#define APTX_NUM_CHANNELS 2 + +/* always S24LE */ +#define APTX_SAMPLE_SIZE 3 + +/* always 4 samples per channel*/ +#define APTX_SAMPLES_PER_CHANNEL 4 + +/* always 4 stereo samples */ +#define APTX_SAMPLES_PER_FRAME (APTX_SAMPLES_PER_CHANNEL * APTX_NUM_CHANNELS) + +/* fixed encoded frame size hd=0: LLRR, hd=1: LLLRRR */ +#define APTX_FRAME_SIZE (2 * APTX_NUM_CHANNELS) +#define APTX_HD_FRAME_SIZE (3 * APTX_NUM_CHANNELS) + +/* while finishing encoding, up to 92 frames will be produced */ +#define APTX_FINISH_FRAMES 92 + +static inline const char* aptx_name(gboolean hd) +{ + return hd ? "aptX-HD" : "aptX"; +} + +/* fixed encoded frame size hd=FALSE: LLRR, hd=TRUE: LLLRRR */ +static inline gsize aptx_frame_size(gboolean hd) +{ + return hd ? APTX_HD_FRAME_SIZE : APTX_FRAME_SIZE; +} + +#endif /* __GST_OPENAPTX_PLUGIN_H__ */ diff --git a/meson_options.txt b/meson_options.txt index 19ed4ac6ae..1a879a0c40 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -116,6 +116,7 @@ option('ladspa', type : 'feature', value : 'auto', description : 'LADSPA 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('libmms', type : 'feature', value : 'auto', description : 'Microsoft multimedia server network source plugin') +option('libopenaptx', type : 'feature', value : 'auto', description : 'Open Source implementation of Audio Processing Technology codec (aptX) plugin') option('lv2', type : 'feature', value : 'auto', description : 'LV2 audio plugin bridge') option('mediafoundation', type : 'feature', value : 'auto', description : 'Microsoft Media Foundation plugin') option('microdns', type : 'feature', value : 'auto', description : 'libmicrodns-based device provider')