summaryrefslogtreecommitdiff
path: root/ext/openaptx
diff options
context:
space:
mode:
authorIgor Kovalenko <igor.v.kovalenko@gmail.com>2020-12-11 08:45:06 +0000
committerIgor V. Kovalenko <igor.v.kovalenko@gmail.com>2020-12-11 11:55:54 +0300
commitb916522382aaa33f216636a292e97cd769ac4093 (patch)
treee89a67c4d81fd2ac08a21d89f63791d476affc7b /ext/openaptx
parent3bcb876c29bb9da233f1db71714da75d7abd8916 (diff)
downloadgstreamer-plugins-bad-b916522382aaa33f216636a292e97cd769ac4093.tar.gz
openaptx: add aptX and aptX-HD codecs using libopenaptx
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1871>
Diffstat (limited to 'ext/openaptx')
-rw-r--r--ext/openaptx/gstopenaptxdec.c342
-rw-r--r--ext/openaptx/gstopenaptxdec.h45
-rw-r--r--ext/openaptx/gstopenaptxenc.c317
-rw-r--r--ext/openaptx/gstopenaptxenc.h44
-rw-r--r--ext/openaptx/meson.build20
-rw-r--r--ext/openaptx/openaptx-plugin.c44
-rw-r--r--ext/openaptx/openaptx-plugin.h61
7 files changed, 873 insertions, 0 deletions
diff --git a/ext/openaptx/gstopenaptxdec.c b/ext/openaptx/gstopenaptxdec.c
new file mode 100644
index 000000000..8a61eba48
--- /dev/null
+++ b/ext/openaptx/gstopenaptxdec.c
@@ -0,0 +1,342 @@
+/* GStreamer openaptx audio decoder
+ *
+ * Copyright (C) 2020 Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
+ * Copyright (C) 2020 Thomas Weißschuh <thomas@t-8ch.de>
+ *
+ * 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 <config.h>
+#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 <igor.v.kovalenko@gmail.com>, "
+ "Thomas Weißschuh <thomas@t-8ch.de>");
+
+ 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 000000000..bcc989123
--- /dev/null
+++ b/ext/openaptx/gstopenaptxdec.h
@@ -0,0 +1,45 @@
+/* GStreamer openaptx audio decoder
+ *
+ * Copyright (C) 2020 Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
+ * Copyright (C) 2020 Thomas Weißschuh <thomas@t-8ch.de>
+ *
+ * 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 <gst/gst.h>
+#include <gst/audio/audio.h>
+
+#include <openaptx.h>
+
+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 000000000..7d38c982c
--- /dev/null
+++ b/ext/openaptx/gstopenaptxenc.c
@@ -0,0 +1,317 @@
+/* GStreamer openaptx audio encoder
+ *
+ * Copyright (C) 2020 Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
+ * Copyright (C) 2020 Thomas Weißschuh <thomas@t-8ch.de>
+ *
+ * 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 <config.h>
+#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 <igor.v.kovalenko@gmail.com>, "
+ "Thomas Weißschuh <thomas@t-8ch.de>");
+
+ 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 000000000..48bc5afd0
--- /dev/null
+++ b/ext/openaptx/gstopenaptxenc.h
@@ -0,0 +1,44 @@
+/* GStreamer openaptx audio encoder
+ *
+ * Copyright (C) 2020 Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
+ * Copyright (C) 2020 Thomas Weißschuh <thomas@t-8ch.de>
+ *
+ * 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 <gst/gst.h>
+#include <gst/audio/audio.h>
+
+#include <openaptx.h>
+
+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 000000000..68be112c5
--- /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 000000000..71ee53bd5
--- /dev/null
+++ b/ext/openaptx/openaptx-plugin.c
@@ -0,0 +1,44 @@
+/* GStreamer openaptx audio plugin
+ *
+ * Copyright (C) 2020 Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
+ * Copyright (C) 2020 Thomas Weißschuh <thomas@t-8ch.de>
+ *
+ * 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 <config.h>
+#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 000000000..6d0ea9f44
--- /dev/null
+++ b/ext/openaptx/openaptx-plugin.h
@@ -0,0 +1,61 @@
+/* GStreamer openaptx audio plugin
+ *
+ * Copyright (C) 2020 Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
+ * Copyright (C) 2020 Thomas Weißschuh <thomas@t-8ch.de>
+ *
+ * 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 <glib.h>
+
+#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__ */