• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer openaptx audio encoder
2  *
3  * Copyright (C) 2020 Igor V. Kovalenko <igor.v.kovalenko@gmail.com>
4  * Copyright (C) 2020 Thomas Weißschuh <thomas@t-8ch.de>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21 
22 /**
23  * SECTION:element-openaptxenc
24  * @title: openaptxenc
25  *
26  * This element encodes raw S24LE integer stereo PCM audio into a Bluetooth aptX or aptX-HD stream.
27  * Accepts audio/aptx or audio/aptx-hd output streams.
28  *
29  * ## Example pipelines
30  * |[
31  * gst-launch-1.0 -v audiotestsrc ! openaptxenc ! avdec_aptx ! audioconvert ! autoaudiosink
32  * ]| Encode a sine wave into aptX, AV decode it and listen to result.
33  * |[
34  * gst-launch-1.0 -v audiotestsrc ! openaptxenc ! avdec_aptx_hd ! audioconvert ! autoaudiosink
35  * ]| Encode a sine wave into aptX-HD, AV decode it and listen to result.
36  *
37  */
38 
39 #ifdef HAVE_CONFIG_H
40 #include <config.h>
41 #endif
42 
43 #include "gstopenaptxenc.h"
44 #include "openaptx-plugin.h"
45 
46 GST_DEBUG_CATEGORY_STATIC (openaptx_enc_debug);
47 #define GST_CAT_DEFAULT openaptx_enc_debug
48 
49 #define gst_openaptx_enc_parent_class parent_class
50 
51 G_DEFINE_TYPE (GstOpenaptxEnc, gst_openaptx_enc, GST_TYPE_AUDIO_ENCODER);
52 GST_ELEMENT_REGISTER_DEFINE (openaptxenc, "openaptxenc", GST_RANK_NONE,
53     GST_TYPE_OPENAPTX_ENC);
54 
55 static GstStaticPadTemplate openaptx_enc_sink_factory =
56 GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
57     GST_STATIC_CAPS ("audio/x-raw, format = S24LE,"
58         " rate = [ 1, MAX ], channels = 2, layout = interleaved"));
59 
60 static GstStaticPadTemplate openaptx_enc_src_factory =
61     GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
62     GST_STATIC_CAPS ("audio/aptx-hd, channels = 2, rate = [ 1, MAX ]; "
63         "audio/aptx, channels = 2, rate = [ 1, MAX ]"));
64 
65 
66 static gboolean gst_openaptx_enc_start (GstAudioEncoder * enc);
67 static gboolean gst_openaptx_enc_stop (GstAudioEncoder * enc);
68 static gboolean gst_openaptx_enc_set_format (GstAudioEncoder * enc,
69     GstAudioInfo * info);
70 static GstFlowReturn gst_openaptx_enc_handle_frame (GstAudioEncoder * enc,
71     GstBuffer * buffer);
72 
73 static gint64
gst_openaptx_enc_get_latency(GstOpenaptxEnc * enc,gint rate)74 gst_openaptx_enc_get_latency (GstOpenaptxEnc * enc, gint rate)
75 {
76   gint64 latency =
77       gst_util_uint64_scale (APTX_LATENCY_SAMPLES, GST_SECOND, rate);
78   GST_DEBUG_OBJECT (enc, "Latency: %" GST_TIME_FORMAT, GST_TIME_ARGS (latency));
79   return latency;
80 }
81 
82 static gboolean
gst_openaptx_enc_set_format(GstAudioEncoder * audio_enc,GstAudioInfo * info)83 gst_openaptx_enc_set_format (GstAudioEncoder * audio_enc, GstAudioInfo * info)
84 {
85   GstOpenaptxEnc *enc = GST_OPENAPTX_ENC (audio_enc);
86   GstStructure *s;
87   GstCaps *caps, *output_caps = NULL;
88   gint rate;
89   gint64 encoder_latency;
90   gint ret;
91 
92   rate = GST_AUDIO_INFO_RATE (info);
93 
94   /* negotiate output format based on downstream caps restrictions */
95   caps = gst_pad_get_allowed_caps (GST_AUDIO_ENCODER_SRC_PAD (enc));
96 
97   if (caps == NULL)
98     caps = gst_static_pad_template_get_caps (&openaptx_enc_src_factory);
99   else if (gst_caps_is_empty (caps))
100     goto failure;
101 
102   /* let's see what is in the output caps */
103   s = gst_caps_get_structure (caps, 0);
104   enc->hd = gst_structure_has_name (s, "audio/aptx-hd");
105 
106   gst_clear_caps (&caps);
107 
108   output_caps = gst_caps_new_simple (enc->hd ? "audio/aptx-hd" : "audio/aptx",
109       "channels", G_TYPE_INT, APTX_NUM_CHANNELS,
110       "rate", G_TYPE_INT, rate, NULL);
111 
112   GST_INFO_OBJECT (enc, "output caps %" GST_PTR_FORMAT, output_caps);
113 
114   /* reinitialize codec */
115   if (enc->aptx_c)
116     aptx_finish (enc->aptx_c);
117 
118   GST_INFO_OBJECT (enc, "Initialize %s codec", aptx_name (enc->hd));
119   enc->aptx_c = aptx_init (enc->hd);
120 
121   encoder_latency = gst_openaptx_enc_get_latency (enc, rate);
122   gst_audio_encoder_set_latency (audio_enc, encoder_latency, encoder_latency);
123 
124   /* we want to be handed all available samples in handle_frame, but always
125    * enough to encode a frame */
126   gst_audio_encoder_set_frame_samples_min (audio_enc, APTX_SAMPLES_PER_CHANNEL);
127   gst_audio_encoder_set_frame_samples_max (audio_enc, APTX_SAMPLES_PER_CHANNEL);
128   gst_audio_encoder_set_frame_max (audio_enc, 0);
129 
130   /* FIXME: what to do with left-over samples at the end? can we encode them? */
131   gst_audio_encoder_set_hard_min (audio_enc, TRUE);
132 
133   ret = gst_audio_encoder_set_output_format (audio_enc, output_caps);
134   gst_caps_unref (output_caps);
135 
136   return ret;
137 
138 failure:
139   if (output_caps)
140     gst_caps_unref (output_caps);
141   if (caps)
142     gst_caps_unref (caps);
143   return FALSE;
144 }
145 
146 static GstFlowReturn
gst_openaptx_enc_handle_frame(GstAudioEncoder * audio_enc,GstBuffer * buffer)147 gst_openaptx_enc_handle_frame (GstAudioEncoder * audio_enc, GstBuffer * buffer)
148 {
149   GstOpenaptxEnc *enc = GST_OPENAPTX_ENC (audio_enc);
150   GstMapInfo out_map;
151   GstBuffer *outbuf = NULL;
152   GstFlowReturn ret;
153   guint frames;
154   gsize frame_len, output_size;
155   gssize processed = 0;
156   gsize written = 0;
157 
158   /* fixed encoded frame size hd=0: LLRR, hd=1: LLLRRR */
159   frame_len = aptx_frame_size (enc->hd);
160 
161   if (G_UNLIKELY (!buffer)) {
162     GST_DEBUG_OBJECT (enc, "Finish encoding");
163     frames = APTX_FINISH_FRAMES;
164   } else {
165     frames = gst_buffer_get_size (buffer) /
166         (APTX_SAMPLE_SIZE * APTX_SAMPLES_PER_FRAME);
167 
168     if (frames == 0) {
169       GST_WARNING_OBJECT (enc, "Odd input stream size detected, skipping");
170       goto mixed_frames;
171     }
172   }
173 
174   output_size = frames * frame_len;
175   outbuf = gst_audio_encoder_allocate_output_buffer (audio_enc, output_size);
176 
177   if (outbuf == NULL)
178     goto no_output_buffer;
179 
180   if (!gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE)) {
181     gst_buffer_replace (&outbuf, NULL);
182     goto no_output_buffer_map;
183   }
184 
185   if (G_LIKELY (buffer)) {
186     GstMapInfo in_map;
187 
188     if (!gst_buffer_map (buffer, &in_map, GST_MAP_READ)) {
189       gst_buffer_unmap (outbuf, &out_map);
190       gst_buffer_replace (&outbuf, NULL);
191       goto map_failed;
192     }
193 
194     GST_LOG_OBJECT (enc,
195         "encoding %" G_GSIZE_FORMAT " samples into %u %s frames",
196         in_map.size / (APTX_NUM_CHANNELS * APTX_SAMPLE_SIZE), frames,
197         aptx_name (enc->hd));
198 
199     processed = aptx_encode (enc->aptx_c, in_map.data, in_map.size,
200         out_map.data, output_size, &written);
201 
202     gst_buffer_unmap (buffer, &in_map);
203   } else {
204     aptx_encode_finish (enc->aptx_c, out_map.data, output_size, &written);
205     output_size = written;
206   }
207 
208   if (processed < 0 || written != output_size) {
209     GST_WARNING_OBJECT (enc,
210         "%s encoding error, processed = %" G_GSSIZE_FORMAT ", "
211         "written = %" G_GSSIZE_FORMAT ", expected = %" G_GSIZE_FORMAT,
212         aptx_name (enc->hd), processed, written, frames * frame_len);
213   }
214 
215   gst_buffer_unmap (outbuf, &out_map);
216 
217   GST_LOG_OBJECT (enc, "%s written = %" G_GSSIZE_FORMAT,
218       aptx_name (enc->hd), written);
219 
220 done:
221   if (G_LIKELY (outbuf)) {
222     if (G_LIKELY (written > 0))
223       gst_buffer_set_size (outbuf, written);
224     else
225       gst_buffer_replace (&outbuf, NULL);
226   }
227 
228   ret = gst_audio_encoder_finish_frame (audio_enc, outbuf,
229       written / frame_len * APTX_SAMPLES_PER_CHANNEL);
230 
231   if (G_UNLIKELY (!buffer))
232     ret = GST_FLOW_EOS;
233 
234   return ret;
235 
236 /* ERRORS */
237 mixed_frames:
238   {
239     GST_WARNING_OBJECT (enc, "inconsistent input data/frames, skipping");
240     goto done;
241   }
242 no_output_buffer_map:
243   {
244     GST_ELEMENT_ERROR (enc, RESOURCE, FAILED,
245         ("Could not map output buffer"),
246         ("Failed to map allocated output buffer for write access."));
247     return GST_FLOW_ERROR;
248   }
249 no_output_buffer:
250   {
251     GST_ELEMENT_ERROR (enc, RESOURCE, FAILED,
252         ("Could not allocate output buffer"),
253         ("Audio encoder failed to allocate output buffer to hold an audio frame."));
254     return GST_FLOW_ERROR;
255   }
256 map_failed:
257   {
258     GST_ELEMENT_ERROR (enc, RESOURCE, FAILED,
259         ("Could not map input buffer"),
260         ("Failed to map incoming buffer for read access."));
261     return GST_FLOW_ERROR;
262   }
263 }
264 
265 static gboolean
gst_openaptx_enc_start(GstAudioEncoder * audio_enc)266 gst_openaptx_enc_start (GstAudioEncoder * audio_enc)
267 {
268   return TRUE;
269 }
270 
271 static gboolean
gst_openaptx_enc_stop(GstAudioEncoder * audio_enc)272 gst_openaptx_enc_stop (GstAudioEncoder * audio_enc)
273 {
274   GstOpenaptxEnc *enc = GST_OPENAPTX_ENC (audio_enc);
275 
276   GST_INFO_OBJECT (enc, "Finish openaptx codec");
277 
278   if (enc->aptx_c) {
279     aptx_finish (enc->aptx_c);
280     enc->aptx_c = NULL;
281   }
282 
283   return TRUE;
284 }
285 
286 static void
gst_openaptx_enc_class_init(GstOpenaptxEncClass * klass)287 gst_openaptx_enc_class_init (GstOpenaptxEncClass * klass)
288 {
289   GstAudioEncoderClass *base_class = GST_AUDIO_ENCODER_CLASS (klass);
290   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
291 
292   base_class->start = GST_DEBUG_FUNCPTR (gst_openaptx_enc_start);
293   base_class->stop = GST_DEBUG_FUNCPTR (gst_openaptx_enc_stop);
294   base_class->set_format = GST_DEBUG_FUNCPTR (gst_openaptx_enc_set_format);
295   base_class->handle_frame = GST_DEBUG_FUNCPTR (gst_openaptx_enc_handle_frame);
296 
297   gst_element_class_add_static_pad_template (element_class,
298       &openaptx_enc_sink_factory);
299   gst_element_class_add_static_pad_template (element_class,
300       &openaptx_enc_src_factory);
301 
302   gst_element_class_set_static_metadata (element_class,
303       "Bluetooth aptX/aptX-HD audio encoder using libopenaptx",
304       "Codec/Encoder/Audio",
305       "Encode an aptX or aptX-HD audio stream using libopenaptx",
306       "Igor V. Kovalenko <igor.v.kovalenko@gmail.com>, "
307       "Thomas Weißschuh <thomas@t-8ch.de>");
308 
309   GST_DEBUG_CATEGORY_INIT (openaptx_enc_debug, "openaptxenc", 0,
310       "openaptx encoding element");
311 }
312 
313 static void
gst_openaptx_enc_init(GstOpenaptxEnc * enc)314 gst_openaptx_enc_init (GstOpenaptxEnc * enc)
315 {
316   GST_PAD_SET_ACCEPT_TEMPLATE (GST_AUDIO_ENCODER_SINK_PAD (enc));
317 
318   enc->aptx_c = NULL;
319 }
320