openaptx: add aptX and aptX-HD codecs using libopenaptx
authorIgor Kovalenko <igor.v.kovalenko@gmail.com>
Fri, 11 Dec 2020 08:45:06 +0000 (08:45 +0000)
committerIgor V. Kovalenko <igor.v.kovalenko@gmail.com>
Fri, 11 Dec 2020 08:55:54 +0000 (11:55 +0300)
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1871>

ext/meson.build
ext/openaptx/gstopenaptxdec.c [new file with mode: 0644]
ext/openaptx/gstopenaptxdec.h [new file with mode: 0644]
ext/openaptx/gstopenaptxenc.c [new file with mode: 0644]
ext/openaptx/gstopenaptxenc.h [new file with mode: 0644]
ext/openaptx/meson.build [new file with mode: 0644]
ext/openaptx/openaptx-plugin.c [new file with mode: 0644]
ext/openaptx/openaptx-plugin.h [new file with mode: 0644]
meson_options.txt

index 2f03422..63844ca 100644 (file)
@@ -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 (file)
index 0000000..8a61eba
--- /dev/null
@@ -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 (file)
index 0000000..bcc9891
--- /dev/null
@@ -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 (file)
index 0000000..7d38c98
--- /dev/null
@@ -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 (file)
index 0000000..48bc5af
--- /dev/null
@@ -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 (file)
index 0000000..68be112
--- /dev/null
@@ -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 (file)
index 0000000..71ee53b
--- /dev/null
@@ -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 (file)
index 0000000..6d0ea9f
--- /dev/null
@@ -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__ */
index 19ed4ac..1a879a0 100644 (file)
@@ -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')